From 8337f4225f0acc3a0a16e5679d5c2410e257a15c Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 27 Jul 2023 13:50:26 -0600 Subject: [PATCH 01/20] update PreferenceManager to use androidx version for getDefaultSharedPreferences --- mapcache/build.gradle | 2 +- .../src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mapcache/build.gradle b/mapcache/build.gradle index e633ecc6..24f22013 100644 --- a/mapcache/build.gradle +++ b/mapcache/build.gradle @@ -51,7 +51,7 @@ dependencies { api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" api 'androidx.appcompat:appcompat:1.3.0' api 'com.google.android.material:material:1.6.0' - api 'androidx.preference:preference:1.2.0' + api 'androidx.preference:preference:1.2.1' api 'androidx.lifecycle:lifecycle-extensions:2.2.0' api 'mil.nga.geopackage.map:geopackage-android-map:6.7.1' // comment out to build locally //api project(':geopackage-map') // uncomment me to build locally diff --git a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java index 3078b557..8761886c 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java +++ b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java @@ -19,7 +19,7 @@ import android.os.Looper; import android.os.VibrationEffect; import android.os.Vibrator; -import android.preference.PreferenceManager; +import androidx.preference.PreferenceManager; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; From c4f8ebd64429d73b36d218cc17a9e06f2d586681 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 27 Jul 2023 15:22:22 -0600 Subject: [PATCH 02/20] preference manager library update --- .../main/java/mil/nga/mapcache/data/GeoPackageDatabases.java | 2 +- .../mil/nga/mapcache/wizards/createtile/NewTileLayerUI.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mapcache/src/main/java/mil/nga/mapcache/data/GeoPackageDatabases.java b/mapcache/src/main/java/mil/nga/mapcache/data/GeoPackageDatabases.java index 4843311f..d42dafca 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/data/GeoPackageDatabases.java +++ b/mapcache/src/main/java/mil/nga/mapcache/data/GeoPackageDatabases.java @@ -4,7 +4,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; -import android.preference.PreferenceManager; +import androidx.preference.PreferenceManager; import android.util.Log; import java.io.FileInputStream; diff --git a/mapcache/src/main/java/mil/nga/mapcache/wizards/createtile/NewTileLayerUI.java b/mapcache/src/main/java/mil/nga/mapcache/wizards/createtile/NewTileLayerUI.java index 64eeab69..cf9ddb06 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/wizards/createtile/NewTileLayerUI.java +++ b/mapcache/src/main/java/mil/nga/mapcache/wizards/createtile/NewTileLayerUI.java @@ -4,7 +4,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; -import android.preference.PreferenceManager; +import androidx.preference.PreferenceManager; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; From c9339bd99108658c5362ab33e9690cc59f5c121b Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 31 Jul 2023 12:28:42 -0600 Subject: [PATCH 03/20] remove viewmodel ref from shareTask --- .../nga/mapcache/GeoPackageMapFragment.java | 10 +- .../java/mil/nga/mapcache/load/ShareTask.java | 135 ++++++++++-------- 2 files changed, 83 insertions(+), 62 deletions(-) diff --git a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java index 8761886c..1b1ec3dc 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java +++ b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java @@ -100,6 +100,7 @@ import org.jetbrains.annotations.NotNull; import org.locationtech.proj4j.units.Units; +import java.io.File; import java.lang.reflect.Method; import java.sql.SQLException; import java.text.DecimalFormat; @@ -1076,7 +1077,10 @@ public void onRenameGP(String oldName, String newName) { public void onShareGP(String gpName) { // Set the geopackage name before we ask permissions and get routed back through MainActivity // to exportGeoPackageToExternal() + File databaseFile = geoPackageViewModel.getDatabaseFile(gpName); + shareTask.setFileExternal(geoPackageViewModel.isExternal(gpName)); shareTask.setGeoPackageName(gpName); + shareTask.setGeoPackageFile(databaseFile); getImportPermissions(MainActivity.MANAGER_PERMISSIONS_REQUEST_ACCESS_EXPORT_DATABASE); } @@ -1900,8 +1904,10 @@ public void importGeopackageFromFile() { * Save a GeoPackage to external disk (after we've been given permission) */ public void exportGeoPackageToExternal() { - if (shareTask != null && shareTask.getGeoPackageName() != null) { - shareTask.askToSaveOrShare(shareTask.getGeoPackageName()); + + if (shareTask != null && shareTask.getGeoPackageName() != null && + shareTask.getGeoPackageFile() != null) { + shareTask.askToSaveOrShare(); } } diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java index 22f7a07e..b1da5d4b 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java @@ -12,10 +12,14 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.core.content.FileProvider; import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProviders; +import androidx.lifecycle.ViewModelStore; +import androidx.lifecycle.ViewModelStoreOwner; import java.io.File; import java.io.IOException; @@ -51,43 +55,39 @@ public class ShareTask { private Activity activity; /** - * Need access to the viewModel to retrieve database files + * Name should be saved to the task */ - private GeoPackageViewModel geoPackageViewModel; + private String geoPackageName; /** - * Name should be saved to the task + * GeoPackage file for sharing */ - private String geoPackageName; + private File geoPackageFile; + + /** + * Is the saved geoPackage an external file + */ + private boolean isFileExternal = false; public ShareTask(FragmentActivity activity) { this.activity = activity; - geoPackageViewModel = ViewModelProviders.of(activity).get(GeoPackageViewModel.class); } /** * Share database with external apps via intent - * - * @param database GeoPackage name */ - public void shareDatabaseOption(final String database) { - + private void shareDatabaseOption() { try { - // Get the database file - File databaseFile = geoPackageViewModel.getDatabaseFile(database); - // Create the share intent Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setType("*/*"); // If external database, no permission is needed - if (geoPackageViewModel.isExternal(database)) { + if (isFileExternal) { // Create the Uri and share - Uri databaseUri = FileProvider.getUriForFile(activity, - AUTHORITY, - databaseFile); + Uri databaseUri = FileProvider.getUriForFile(activity, AUTHORITY, geoPackageFile); shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); launchShareIntent(shareIntent, databaseUri); } @@ -95,9 +95,8 @@ public void shareDatabaseOption(final String database) { else { // Launch the share copy task ShareCopyTask shareCopyTask = new ShareCopyTask(shareIntent); - shareCopyTask.execute(databaseFile, database); + shareCopyTask.execute(geoPackageFile, geoPackageName); } - } catch (Exception e) { GeoPackageUtils.showMessage(activity, "Error sharing GeoPackage", e.getMessage()); } @@ -107,21 +106,16 @@ public void shareDatabaseOption(final String database) { /** * Save the given database to the downloads directory - * @param database GeoPackage name */ - private void saveDatabaseOption(final String database){ + private void saveDatabaseOption(){ try { - // Get the database file - File databaseFile = geoPackageViewModel.getDatabaseFile(database); - // Create the share intent Intent shareIntent = new Intent(); shareIntent.setAction(Intent.ACTION_SEND); // Launch the save to disk task SaveToDiskTask saveTask = new SaveToDiskTask(shareIntent); - saveTask.execute(databaseFile, database); - + saveTask.execute(geoPackageFile, geoPackageName); } catch (Exception e) { GeoPackageUtils.showMessage(activity, "Error saving to file", e.getMessage()); } @@ -133,41 +127,48 @@ private void saveDatabaseOption(final String database){ * Shows a popup to ask if the user wants to save to disk or share with external apps * @return constant representing either share or save */ - public void askToSaveOrShare(String database){ - // Create Alert window with basic input text layout - LayoutInflater inflater = LayoutInflater.from(activity); - View alertView = inflater.inflate(R.layout.share_file_popup, null); - ViewAnimation.setScaleAnimatiom(alertView, 200); - // title - TextView titleText = (TextView) alertView.findViewById(R.id.alert_title); - titleText.setText("Share GeoPackage"); - - // Initial dialog asking for create or import - AlertDialog.Builder dialog = new AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle) - .setView(alertView); - final AlertDialog alertDialog = dialog.create(); - - // Click listener for "Share" - alertView.findViewById(R.id.share_menu_share_card) - .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - shareDatabaseOption(database); - alertDialog.dismiss(); - } - }); + public void askToSaveOrShare(){ + try { + if(geoPackageFile == null || geoPackageName == null){ + throw new Exception("GeoPackage could not be found"); + } + // Create Alert window with basic input text layout + LayoutInflater inflater = LayoutInflater.from(activity); + View alertView = inflater.inflate(R.layout.share_file_popup, null); + ViewAnimation.setScaleAnimatiom(alertView, 200); + // title + TextView titleText = (TextView) alertView.findViewById(R.id.alert_title); + titleText.setText("Share GeoPackage"); + + // Initial dialog asking for create or import + AlertDialog.Builder dialog = new AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle) + .setView(alertView); + final AlertDialog alertDialog = dialog.create(); + + // Click listener for "Share" + alertView.findViewById(R.id.share_menu_share_card) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + shareDatabaseOption(); + alertDialog.dismiss(); + } + }); - // Click listener for "Save" - alertView.findViewById(R.id.share_menu_save_card) - .setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - saveDatabaseOption(database); - alertDialog.dismiss(); - } - }); + // Click listener for "Save" + alertView.findViewById(R.id.share_menu_save_card) + .setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + saveDatabaseOption(); + alertDialog.dismiss(); + } + }); - alertDialog.show(); + alertDialog.show(); + } catch (Exception e) { + GeoPackageUtils.showMessage(activity, "Error sharing", e.getMessage()); + } } @@ -190,13 +191,11 @@ private File getDatabaseCacheDirectory() { * @param databaseUri */ private void launchShareIntent(Intent shareIntent, Uri databaseUri) { - // Add the Uri shareIntent.putExtra(Intent.EXTRA_STREAM, databaseUri); // Start the share activity for result to delete the cache when done activity.startActivityForResult(Intent.createChooser(shareIntent, "Share"), ACTIVITY_SHARE_FILE); - } @@ -431,6 +430,22 @@ public String getGeoPackageName() { public void setGeoPackageName(String geoPackageName) { this.geoPackageName = geoPackageName; } + + public void setGeoPackageFile(File geoPackageFile){ + this.geoPackageFile = geoPackageFile; + } + + public File getGeoPackageFile(){ + return this.geoPackageFile; + } + + public boolean isFileExternal() { + return isFileExternal; + } + + public void setFileExternal(boolean fileExternal) { + isFileExternal = fileExternal; + } } From b3a11ff1d6e6f505ec8a81c70b978203c2bf76d6 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 1 Aug 2023 16:22:54 -0600 Subject: [PATCH 04/20] unit tests for DataTypeConverter --- mapcache/build.gradle | 9 ++++ mapcache/src/test/DataTypeConverterTest.java | 52 ++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 mapcache/src/test/DataTypeConverterTest.java diff --git a/mapcache/build.gradle b/mapcache/build.gradle index 24f22013..11ca34fa 100644 --- a/mapcache/build.gradle +++ b/mapcache/build.gradle @@ -38,6 +38,13 @@ android { exclude 'META-INF/NOTICE' exclude 'META-INF/NOTICE.txt' } + sourceSets { + main { + java { + srcDirs 'src/main/java', 'src/test' + } + } + } } task androidAppVersion { @@ -64,8 +71,10 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'org.locationtech.jts:jts-core:1.18.2' + implementation 'junit:junit:4.12' testImplementation 'androidx.multidex:multidex:2.0.1' testImplementation 'junit:junit:4.13.1' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' implementation 'com.github.matomo-org:matomo-sdk-android:v2.0.0' } diff --git a/mapcache/src/test/DataTypeConverterTest.java b/mapcache/src/test/DataTypeConverterTest.java new file mode 100644 index 00000000..79d019c9 --- /dev/null +++ b/mapcache/src/test/DataTypeConverterTest.java @@ -0,0 +1,52 @@ +import mil.nga.geopackage.db.GeoPackageDataType; +import mil.nga.mapcache.utils.DataTypeConverter; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class DataTypeConverterTest { + + @Before + public void setUp(){System.out.println("DataTypeConverter test ready");} + + @After + public void tearDown(){ + System.out.println("DataTypeConverter test tear down"); + } + + @Test + public void testText(){ + GeoPackageDataType type = DataTypeConverter.getGeoPackageDataType("text"); + GeoPackageDataType typeCaps = DataTypeConverter.getGeoPackageDataType("TEXT"); + assertEquals("Expected GeoPackageDataType for text not correct", GeoPackageDataType.TEXT, type); + assertEquals("Expected GeoPackageDataType for text not correct", GeoPackageDataType.TEXT, typeCaps); + } + + @Test + public void testNumber(){ + GeoPackageDataType type = DataTypeConverter.getGeoPackageDataType("number"); + GeoPackageDataType typeCaps = DataTypeConverter.getGeoPackageDataType("NUMBER"); + assertEquals("Expected GeoPackageDataType for number not correct", GeoPackageDataType.DOUBLE, type); + assertEquals("Expected GeoPackageDataType for number not correct", GeoPackageDataType.DOUBLE, typeCaps); + } + + @Test + public void testCheckbox(){ + GeoPackageDataType type = DataTypeConverter.getGeoPackageDataType("checkbox"); + GeoPackageDataType typeCaps = DataTypeConverter.getGeoPackageDataType("CHECKBOX"); + GeoPackageDataType typeSpace = DataTypeConverter.getGeoPackageDataType("check box"); + GeoPackageDataType typeSpaceCaps = DataTypeConverter.getGeoPackageDataType("CHECK BOX"); + assertEquals("Expected GeoPackageDataType for text not correct", GeoPackageDataType.BOOLEAN, type); + assertEquals("Expected GeoPackageDataType for text not correct", GeoPackageDataType.BOOLEAN, typeCaps); + } + + @Test + public void testBadText(){ + GeoPackageDataType type = DataTypeConverter.getGeoPackageDataType("bad"); + assertNull("Expected GeoPackageDataType for number not correct", type); + } + +} From 1c50358065cf42fef3a09e17027fee2fb7d5ca5e Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Wed, 2 Aug 2023 13:04:03 -0600 Subject: [PATCH 05/20] fix for userAgentValue when build version less than 30 --- .../main/java/mil/nga/mapcache/utils/HttpUtils.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/mapcache/src/main/java/mil/nga/mapcache/utils/HttpUtils.java b/mapcache/src/main/java/mil/nga/mapcache/utils/HttpUtils.java index a856098d..48c8163b 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/utils/HttpUtils.java +++ b/mapcache/src/main/java/mil/nga/mapcache/utils/HttpUtils.java @@ -99,9 +99,15 @@ public String getContentEncodingKey() { * @return This apps user agent value. */ public String getUserAgentValue(Activity activity) { - return activity.getString(R.string.app_name) - + " " + activity.getString(R.string.app_version) - + " Android " + Build.VERSION.RELEASE_OR_CODENAME; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return activity.getString(R.string.app_name) + + " " + activity.getString(R.string.app_version) + + " Android " + Build.VERSION.RELEASE_OR_CODENAME; + } else { + return activity.getString(R.string.app_name) + + " " + activity.getString(R.string.app_version) + + " Android " + Build.VERSION.RELEASE; + } } /** From 6170768db85dc4e1123e2608be4d159487eecdcd Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 4 Aug 2023 15:09:39 -0600 Subject: [PATCH 06/20] new gpkg downloader without asynctask dependency --- .../nga/mapcache/GeoPackageMapFragment.java | 13 ++-- .../nga/mapcache/io/MapCacheFileUtils.java | 23 +++---- .../java/mil/nga/mapcache/load/Downloader.kt | 65 +++++++++++++++++++ 3 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt diff --git a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java index 1b1ec3dc..0b41af5f 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java +++ b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java @@ -161,6 +161,7 @@ import mil.nga.mapcache.listeners.OnDialogButtonClickListener; import mil.nga.mapcache.listeners.SensorCallback; import mil.nga.mapcache.load.DownloadTask; +import mil.nga.mapcache.load.Downloader; import mil.nga.mapcache.load.ILoadTilesTask; import mil.nga.mapcache.load.ImportTask; import mil.nga.mapcache.load.ShareTask; @@ -593,6 +594,8 @@ private enum EditType { */ ActivityResultLauncher importGeoPackageActivityResultLauncher; ActivityResultLauncher preferencePageActivityResultLauncher; + ActivityResultLauncher downloadTaskResultLauncher; + /** @@ -1846,7 +1849,7 @@ public void showMapIcons() { ViewAnimation.rotateFadeIn(settingsIcon, 200); layerFab.show(); } - + /** * Launches a wizard to create a new tile layer in the given geopackage @@ -2032,9 +2035,11 @@ public View getView(int position, View convertView, ViewGroup parent) { if (nameValid && urlValid) { String database = inputName.getText() != null ? inputName.getText().toString() : ""; String url = inputUrl.getText() != null ? inputUrl.getText().toString() : ""; - DownloadTask downloadTask = new DownloadTask(database, url, getActivity()); - - downloadTask.execute(); + // Use new Downloader to import the GeoPackage + Downloader geoPackageDownloader = new Downloader(getActivity()); + geoPackageDownloader.downloadGeoPackage(geoPackageViewModel, url, database); +// DownloadTask downloadTask = new DownloadTask(database, url, getActivity()); +// downloadTask.execute(); alertDialog.dismiss(); } else if (!nameValid) { inputName.requestFocus(); diff --git a/mapcache/src/main/java/mil/nga/mapcache/io/MapCacheFileUtils.java b/mapcache/src/main/java/mil/nga/mapcache/io/MapCacheFileUtils.java index 96ce399f..3d6fd60e 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/io/MapCacheFileUtils.java +++ b/mapcache/src/main/java/mil/nga/mapcache/io/MapCacheFileUtils.java @@ -51,25 +51,22 @@ public static String getDisplayName(Context context, Uri uri, String path) { * @param uri * @return */ - @TargetApi(Build.VERSION_CODES.KITKAT) private static String getDisplayName(Context context, Uri uri) { String name = null; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - ContentResolver resolver = context.getContentResolver(); - Cursor nameCursor = resolver.query(uri, null, null, null, null); - try { - if (nameCursor.getCount() > 0) { - int displayNameIndex = nameCursor - .getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME); - if (displayNameIndex >= 0 && nameCursor.moveToFirst()) { - name = nameCursor.getString(displayNameIndex); - } + ContentResolver resolver = context.getContentResolver(); + Cursor nameCursor = resolver.query(uri, null, null, null, null); + try { + if (nameCursor.getCount() > 0) { + int displayNameIndex = nameCursor + .getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME); + if (displayNameIndex >= 0 && nameCursor.moveToFirst()) { + name = nameCursor.getString(displayNameIndex); } - } finally { - nameCursor.close(); } + } finally { + nameCursor.close(); } if (name == null) { diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt b/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt new file mode 100644 index 00000000..72bc5428 --- /dev/null +++ b/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt @@ -0,0 +1,65 @@ +package mil.nga.mapcache.load + +import android.app.AlertDialog +import androidx.fragment.app.FragmentActivity +import mil.nga.geopackage.io.GeoPackageProgress +import mil.nga.mapcache.viewmodel.GeoPackageViewModel +import java.net.URL +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +/** + * Downloads a GeoPackage via the GeoPackageViewModel + */ +class Downloader(val activity : FragmentActivity) : GeoPackageProgress{ + + private var max: Int = 0 + private var progress: Int = 0 + private val alertDialog: AlertDialog + private val myExecutor: ExecutorService = Executors.newSingleThreadExecutor() + + init { + val builder = AlertDialog.Builder(activity) + builder.setTitle("Import") + builder.setPositiveButton("Cancel") {alertDialog, which -> + myExecutor.shutdownNow() + } + alertDialog = builder.create() + } + + + fun downloadGeoPackage(viewModel : GeoPackageViewModel, url : String, database : String){ + val theUrl = URL(url) + alertDialog.setMessage("Importing: $database") + alertDialog.show() + myExecutor.submit { + try { + if (!viewModel.importGeoPackage(database, theUrl, this)) { + val failure = "Failed to import GeoPackage '$database' at url '$url'" + alertDialog.dismiss() + } else { + val success = "GeoPackage imported" + alertDialog.dismiss() + } + } catch (e: InterruptedException){ + Thread.currentThread().interrupt() + } + } + } + + override fun setMax(max: Int) { + this.max = max + } + + override fun addProgress(progress: Int) { + this.progress += progress + } + + override fun isActive(): Boolean { + return true + } + + override fun cleanupOnCancel(): Boolean { + return true + } +} \ No newline at end of file From ad905f48059f1025263b3354d09451e1699582ec Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 7 Aug 2023 14:16:08 -0600 Subject: [PATCH 07/20] updates for download task / progress dialog --- .../java/mil/nga/mapcache/load/Downloader.kt | 59 ++++++++++++++++--- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt b/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt index 72bc5428..2085dcfb 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt +++ b/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt @@ -1,15 +1,22 @@ package mil.nga.mapcache.load import android.app.AlertDialog +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageView import androidx.fragment.app.FragmentActivity import mil.nga.geopackage.io.GeoPackageProgress +import mil.nga.mapcache.R import mil.nga.mapcache.viewmodel.GeoPackageViewModel import java.net.URL import java.util.concurrent.ExecutorService import java.util.concurrent.Executors /** - * Downloads a GeoPackage via the GeoPackageViewModel + * Downloads a GeoPackage via the GeoPackageViewModel, providing feedback and cancel action via + * an AlertDialog */ class Downloader(val activity : FragmentActivity) : GeoPackageProgress{ @@ -17,42 +24,78 @@ class Downloader(val activity : FragmentActivity) : GeoPackageProgress{ private var progress: Int = 0 private val alertDialog: AlertDialog private val myExecutor: ExecutorService = Executors.newSingleThreadExecutor() + private var geoPackageName : String = "" + /** + * Create the alert dialog + */ init { - val builder = AlertDialog.Builder(activity) - builder.setTitle("Import") + val builder = AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle) + + // Create Alert window with basic input text layout + val inflater = LayoutInflater.from(activity) + val alertView: View = inflater.inflate(R.layout.basic_label_alert, null) + + // Set dialog view info + val alertLogo = alertView.findViewById(R.id.alert_logo) + alertLogo.setImageResource(R.drawable.material_add_box) + val titleText = alertView.findViewById(R.id.alert_title) + titleText.setText(R.string.import_geopackage_url) + val actionLabel = alertView.findViewById(R.id.action_label) as TextView + actionLabel.setText("Importing GeoPackage") + actionLabel.visibility = View.VISIBLE + + // Cancel button builder.setPositiveButton("Cancel") {alertDialog, which -> myExecutor.shutdownNow() } + + builder.setView(alertView) alertDialog = builder.create() } + /** + * Ask the viewmodel to download the given database from the given url. Show the alert dialog + * and allow cancel + */ fun downloadGeoPackage(viewModel : GeoPackageViewModel, url : String, database : String){ + val handler = android.os.Handler(Looper.getMainLooper()) val theUrl = URL(url) - alertDialog.setMessage("Importing: $database") + var completeMessage : String = "Import failed" + geoPackageName = database alertDialog.show() myExecutor.submit { try { if (!viewModel.importGeoPackage(database, theUrl, this)) { - val failure = "Failed to import GeoPackage '$database' at url '$url'" - alertDialog.dismiss() + completeMessage = "Failed to import GeoPackage '$database' at url '$url'" } else { - val success = "GeoPackage imported" - alertDialog.dismiss() + completeMessage = "GeoPackage imported" } } catch (e: InterruptedException){ Thread.currentThread().interrupt() } + handler.post { + alertDialog.dismiss() + } } } + /** + * Sets the max download + */ override fun setMax(max: Int) { this.max = max } + /** + * Add download progress, then update the alert dialog + */ override fun addProgress(progress: Int) { this.progress += progress + val percentComplete = (this.progress / max.toDouble() * 100).toInt() + val actionLabel = alertDialog.findViewById(R.id.action_label) as TextView + actionLabel.text = "Importing $geoPackageName: $percentComplete%" } override fun isActive(): Boolean { From 9f0b2d4e0180c23ede19204ea0487dc68f631672 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 8 Aug 2023 15:43:45 -0600 Subject: [PATCH 08/20] keep window open until download completes --- mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt b/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt index 2085dcfb..47df9cb2 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt +++ b/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt @@ -51,6 +51,7 @@ class Downloader(val activity : FragmentActivity) : GeoPackageProgress{ } builder.setView(alertView) + builder.setCancelable(false) alertDialog = builder.create() } From 46a70f828b0ef21d78bcb82359e3a5742be46835 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Wed, 9 Aug 2023 14:14:36 -0600 Subject: [PATCH 09/20] rework the share-copy gpkg file task to get rid of asynctask --- .../nga/mapcache/load/ShareCopyExecutor.kt | 74 +++++++++++++++++++ .../java/mil/nga/mapcache/load/ShareTask.java | 2 + 2 files changed, 76 insertions(+) create mode 100644 mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt b/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt new file mode 100644 index 00000000..0a062cf0 --- /dev/null +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt @@ -0,0 +1,74 @@ +package mil.nga.mapcache.load + +import android.app.AlertDialog +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageView +import androidx.fragment.app.FragmentActivity +import mil.nga.geopackage.GeoPackageConstants +import mil.nga.geopackage.io.GeoPackageIOUtils +import mil.nga.mapcache.R +import java.io.File +import java.io.IOException +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +/** + * Copy an internal database to a shareable location and share + */ +class ShareCopyExecutor(val activity : FragmentActivity) { + + private val alertDialog: AlertDialog + private val myExecutor: ExecutorService = Executors.newSingleThreadExecutor() + private var geoPackageName : String = "" + + init { + val builder = AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle) + + // Create Alert window with basic input text layout + val inflater = LayoutInflater.from(activity) + val alertView: View = inflater.inflate(R.layout.basic_label_alert, null) + + // Set dialog view info + val alertLogo = alertView.findViewById(R.id.alert_logo) + alertLogo.setImageResource(R.drawable.material_share) + val titleText = alertView.findViewById(R.id.alert_title) + titleText.setText(R.string.geopackage_share_label) + val actionLabel = alertView.findViewById(R.id.action_label) as TextView + actionLabel.setText(R.string.geopackage_share_copy_message) + actionLabel.visibility = View.VISIBLE + + // Cancel button + builder.setPositiveButton("Cancel") {alertDialog, which -> + myExecutor.shutdownNow() + } + + builder.setView(alertView) + builder.setCancelable(false) + alertDialog = builder.create() + } + + /** + * Copy the gpkgFile to the cacheDir with the geoPackageName + */ + fun shareGeoPackage(cacheDir : File, gpkgFile : File, geoPackageName : String){ + this.geoPackageName = geoPackageName + cacheDir.mkdir() + val handler = android.os.Handler(Looper.getMainLooper()) + val cacheFile = File(cacheDir, geoPackageName + "." + GeoPackageConstants.EXTENSION) + myExecutor.submit { + try { + GeoPackageIOUtils.copyFile(gpkgFile, cacheFile) + } catch (e: IOException) { + + } catch (e: InterruptedException){ + Thread.currentThread().interrupt() + } + handler.post { + alertDialog.dismiss() + } + } + } +} \ No newline at end of file diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java index b1da5d4b..bd367774 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java @@ -94,6 +94,8 @@ private void shareDatabaseOption() { // If internal database, file must be copied to cache for permission else { // Launch the share copy task + + //TODO: change to ShareCopyExecutor ShareCopyTask shareCopyTask = new ShareCopyTask(shareIntent); shareCopyTask.execute(geoPackageFile, geoPackageName); } From 8bd57f31a1206f335ce551b601c159b852f475c1 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 10 Aug 2023 09:23:25 -0600 Subject: [PATCH 10/20] new save to disk executor to get rid of async task when saving gpkg --- .../nga/mapcache/load/SaveToDiskExecutor.kt | 106 ++++++++++++++++++ .../java/mil/nga/mapcache/load/ShareTask.java | 4 + mapcache/src/main/res/values/strings.xml | 2 + 3 files changed, 112 insertions(+) create mode 100644 mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt b/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt new file mode 100644 index 00000000..24f07d44 --- /dev/null +++ b/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt @@ -0,0 +1,106 @@ +package mil.nga.mapcache.load + +import android.app.Activity +import android.app.AlertDialog +import android.os.Environment +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import android.widget.Toast +import androidx.appcompat.widget.AppCompatImageView +import androidx.fragment.app.FragmentActivity +import mil.nga.geopackage.GeoPackageConstants +import mil.nga.geopackage.io.GeoPackageIOUtils +import mil.nga.mapcache.R +import java.io.File +import java.io.IOException +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +/** + * Save a file to the downloads directory + */ +class SaveToDiskExecutor(val activity : Activity) { + + private val myExecutor: ExecutorService = Executors.newSingleThreadExecutor() + private var geoPackageName : String = "" + private val alertDialog: AlertDialog + + init { + val builder = AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle) + + // Create Alert window with basic input text layout + val inflater = LayoutInflater.from(activity) + val alertView: View = inflater.inflate(R.layout.basic_label_alert, null) + + // Set dialog view info + val alertLogo = alertView.findViewById(R.id.alert_logo) + alertLogo.setImageResource(R.drawable.material_share) + val titleText = alertView.findViewById(R.id.alert_title) + titleText.setText(R.string.geopackage_save_label) + val actionLabel = alertView.findViewById(R.id.action_label) as TextView + actionLabel.setText(R.string.geopackage_save_message) + actionLabel.visibility = View.VISIBLE + + // Cancel button + builder.setPositiveButton("Cancel") {alertDialog, which -> + myExecutor.shutdownNow() + } + + builder.setView(alertView) + builder.setCancelable(false) + alertDialog = builder.create() + } + + /** + * Save the gpkgFile to the downloads directory using the cacheDir and geoPackageName + */ + fun saveToDisk(cacheDir : File, gpkgFile : File, geoPackageName : String){ + val downloadDir = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + cacheDir.mkdir() + val handler = android.os.Handler(Looper.getMainLooper()) + var cacheFile = File(cacheDir, geoPackageName + "." + GeoPackageConstants.EXTENSION) + + // If file already exists, add a number on the end to ensure we don't overwrite + var fileNumber = 0 + while (cacheFile.exists()) { + fileNumber++ + cacheFile = File( + downloadDir, geoPackageName + fileNumber + "." + + GeoPackageConstants.EXTENSION + ) + } + myExecutor.submit { + try { + GeoPackageIOUtils.copyFile(gpkgFile, cacheFile) + } catch (e: IOException) { + System.out.println("IOException: $e") + } catch (e: InterruptedException){ + System.out.println("Exception: $e") + + Thread.currentThread().interrupt() + } + handler.post { + deleteCachedDatabaseFiles(cacheDir) + Toast.makeText(activity, "GeoPackage saved to Downloads", Toast.LENGTH_SHORT).show() + alertDialog.dismiss() + } + } + } + + + /** + * Delete the given temporary cache directory + */ + private fun deleteCachedDatabaseFiles(cacheDir : File){ + if (cacheDir.exists()) { + val cacheFiles: Array = cacheDir.listFiles() + for (cacheFile in cacheFiles) { + cacheFile.delete() + } + cacheDir.delete() + } + } +} \ No newline at end of file diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java index bd367774..8312001c 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java @@ -30,6 +30,7 @@ import mil.nga.mapcache.GeoPackageUtils; import mil.nga.mapcache.R; import mil.nga.mapcache.utils.ViewAnimation; +import mil.nga.mapcache.load.SaveToDiskExecutor; import mil.nga.mapcache.viewmodel.GeoPackageViewModel; @@ -116,6 +117,9 @@ private void saveDatabaseOption(){ shareIntent.setAction(Intent.ACTION_SEND); // Launch the save to disk task + //TODO: Switch to SaveToDiskExecutor + //SaveToDiskExecutor diskExecutor = new SaveToDiskExecutor(activity); + //diskExecutor.saveToDisk(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); SaveToDiskTask saveTask = new SaveToDiskTask(shareIntent); saveTask.execute(geoPackageFile, geoPackageName); } catch (Exception e) { diff --git a/mapcache/src/main/res/values/strings.xml b/mapcache/src/main/res/values/strings.xml index e295b377..21c38a50 100644 --- a/mapcache/src/main/res/values/strings.xml +++ b/mapcache/src/main/res/values/strings.xml @@ -137,6 +137,8 @@ GeoPackage Name Export Share + Save to Downloads + Saving GeoPackage to the Downloads directory Preparing internal GeoPackage for sharing Download Create From bec1c02493bb8adf46ea8ee86d0c1749be087aa3 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 10 Aug 2023 16:00:06 -0600 Subject: [PATCH 11/20] fix for geopackage save to downloads dir --- .../main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt | 5 ++--- mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt b/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt index 24f07d44..f3aba25a 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt +++ b/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt @@ -24,7 +24,6 @@ import java.util.concurrent.Executors class SaveToDiskExecutor(val activity : Activity) { private val myExecutor: ExecutorService = Executors.newSingleThreadExecutor() - private var geoPackageName : String = "" private val alertDialog: AlertDialog init { @@ -61,7 +60,7 @@ class SaveToDiskExecutor(val activity : Activity) { Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) cacheDir.mkdir() val handler = android.os.Handler(Looper.getMainLooper()) - var cacheFile = File(cacheDir, geoPackageName + "." + GeoPackageConstants.EXTENSION) + var cacheFile = File(downloadDir, geoPackageName + "." + GeoPackageConstants.EXTENSION) // If file already exists, add a number on the end to ensure we don't overwrite var fileNumber = 0 @@ -72,6 +71,7 @@ class SaveToDiskExecutor(val activity : Activity) { + GeoPackageConstants.EXTENSION ) } + alertDialog.show() myExecutor.submit { try { GeoPackageIOUtils.copyFile(gpkgFile, cacheFile) @@ -79,7 +79,6 @@ class SaveToDiskExecutor(val activity : Activity) { System.out.println("IOException: $e") } catch (e: InterruptedException){ System.out.println("Exception: $e") - Thread.currentThread().interrupt() } handler.post { diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java index 8312001c..dab3e855 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java @@ -117,9 +117,9 @@ private void saveDatabaseOption(){ shareIntent.setAction(Intent.ACTION_SEND); // Launch the save to disk task - //TODO: Switch to SaveToDiskExecutor - //SaveToDiskExecutor diskExecutor = new SaveToDiskExecutor(activity); - //diskExecutor.saveToDisk(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); + //TODO: Switch to SaveToDiskExecutor +// SaveToDiskExecutor diskExecutor = new SaveToDiskExecutor(activity); +// diskExecutor.saveToDisk(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); SaveToDiskTask saveTask = new SaveToDiskTask(shareIntent); saveTask.execute(geoPackageFile, geoPackageName); } catch (Exception e) { From 5a15a7ce3ec2523f1b77c679882affe6be88bae6 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 11 Aug 2023 15:16:52 -0600 Subject: [PATCH 12/20] update share and save executors --- .../nga/mapcache/load/ShareCopyExecutor.kt | 25 ++++++++++++++++--- .../java/mil/nga/mapcache/load/ShareTask.java | 19 +++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt b/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt index 0a062cf0..4a79dc67 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt @@ -1,14 +1,18 @@ package mil.nga.mapcache.load +import android.app.Activity import android.app.AlertDialog +import android.content.Intent +import android.net.Uri import android.os.Looper import android.view.LayoutInflater import android.view.View import android.widget.TextView import androidx.appcompat.widget.AppCompatImageView -import androidx.fragment.app.FragmentActivity +import androidx.core.content.FileProvider import mil.nga.geopackage.GeoPackageConstants import mil.nga.geopackage.io.GeoPackageIOUtils +import mil.nga.mapcache.BuildConfig import mil.nga.mapcache.R import java.io.File import java.io.IOException @@ -18,11 +22,13 @@ import java.util.concurrent.Executors /** * Copy an internal database to a shareable location and share */ -class ShareCopyExecutor(val activity : FragmentActivity) { +class ShareCopyExecutor(val activity : Activity, val shareIntent : Intent) { private val alertDialog: AlertDialog private val myExecutor: ExecutorService = Executors.newSingleThreadExecutor() private var geoPackageName : String = "" + private val AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider" + init { val builder = AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle) @@ -62,13 +68,26 @@ class ShareCopyExecutor(val activity : FragmentActivity) { try { GeoPackageIOUtils.copyFile(gpkgFile, cacheFile) } catch (e: IOException) { - + //TODO better handle exceptions + alertDialog.dismiss() } catch (e: InterruptedException){ Thread.currentThread().interrupt() } handler.post { alertDialog.dismiss() + // Create the content Uri and add intent permissions + val databaseUri = FileProvider.getUriForFile(activity, AUTHORITY, cacheFile) + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + launchShareIntent(shareIntent, databaseUri) } } } + + private fun launchShareIntent(shareIntent: Intent, databaseUri: Uri) { + shareIntent.putExtra(Intent.EXTRA_STREAM, databaseUri) + activity.startActivityForResult( + Intent.createChooser(shareIntent, "Share"), + ShareTask.ACTIVITY_SHARE_FILE + ) + } } \ No newline at end of file diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java index dab3e855..aaf5be54 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java @@ -97,8 +97,10 @@ private void shareDatabaseOption() { // Launch the share copy task //TODO: change to ShareCopyExecutor - ShareCopyTask shareCopyTask = new ShareCopyTask(shareIntent); - shareCopyTask.execute(geoPackageFile, geoPackageName); + ShareCopyExecutor shareCopyExecutor = new ShareCopyExecutor(activity, shareIntent); + shareCopyExecutor.shareGeoPackage(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); +// ShareCopyTask shareCopyTask = new ShareCopyTask(shareIntent); +// shareCopyTask.execute(geoPackageFile, geoPackageName); } } catch (Exception e) { GeoPackageUtils.showMessage(activity, "Error sharing GeoPackage", e.getMessage()); @@ -117,11 +119,11 @@ private void saveDatabaseOption(){ shareIntent.setAction(Intent.ACTION_SEND); // Launch the save to disk task - //TODO: Switch to SaveToDiskExecutor -// SaveToDiskExecutor diskExecutor = new SaveToDiskExecutor(activity); -// diskExecutor.saveToDisk(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); - SaveToDiskTask saveTask = new SaveToDiskTask(shareIntent); - saveTask.execute(geoPackageFile, geoPackageName); + //TODO: Switch to SaveToDiskExecutor + SaveToDiskExecutor diskExecutor = new SaveToDiskExecutor(activity); + diskExecutor.saveToDisk(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); +// SaveToDiskTask saveTask = new SaveToDiskTask(shareIntent); +// saveTask.execute(geoPackageFile, geoPackageName); } catch (Exception e) { GeoPackageUtils.showMessage(activity, "Error saving to file", e.getMessage()); } @@ -197,10 +199,7 @@ private File getDatabaseCacheDirectory() { * @param databaseUri */ private void launchShareIntent(Intent shareIntent, Uri databaseUri) { - // Add the Uri shareIntent.putExtra(Intent.EXTRA_STREAM, databaseUri); - - // Start the share activity for result to delete the cache when done activity.startActivityForResult(Intent.createChooser(shareIntent, "Share"), ACTIVITY_SHARE_FILE); } From 7b45103101d4689fb0f42bf8958be6ac99037af3 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 17 Aug 2023 14:44:21 -0600 Subject: [PATCH 13/20] improved feedback during save to downloads and sharing via intent --- .../nga/mapcache/load/SaveToDiskExecutor.kt | 23 ++++++++---- .../nga/mapcache/load/ShareCopyExecutor.kt | 35 +++++++++++++------ mapcache/src/main/res/values/strings.xml | 2 ++ 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt b/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt index f3aba25a..8450d546 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt +++ b/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt @@ -2,12 +2,15 @@ package mil.nga.mapcache.load import android.app.Activity import android.app.AlertDialog +import android.content.DialogInterface +import android.os.Build import android.os.Environment import android.os.Looper import android.view.LayoutInflater import android.view.View import android.widget.TextView import android.widget.Toast +import androidx.annotation.RequiresApi import androidx.appcompat.widget.AppCompatImageView import androidx.fragment.app.FragmentActivity import mil.nga.geopackage.GeoPackageConstants @@ -19,13 +22,17 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.Executors /** - * Save a file to the downloads directory + * Save a file to the downloads directory using Executor threads and showing an alert dialog */ class SaveToDiskExecutor(val activity : Activity) { private val myExecutor: ExecutorService = Executors.newSingleThreadExecutor() private val alertDialog: AlertDialog + private val actionLabel: TextView + /** + * Create an alert dialog for feedback + */ init { val builder = AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle) @@ -38,11 +45,11 @@ class SaveToDiskExecutor(val activity : Activity) { alertLogo.setImageResource(R.drawable.material_share) val titleText = alertView.findViewById(R.id.alert_title) titleText.setText(R.string.geopackage_save_label) - val actionLabel = alertView.findViewById(R.id.action_label) as TextView + actionLabel = alertView.findViewById(R.id.action_label) as TextView actionLabel.setText(R.string.geopackage_save_message) actionLabel.visibility = View.VISIBLE - // Cancel button + // Cancel button - interrupt thread builder.setPositiveButton("Cancel") {alertDialog, which -> myExecutor.shutdownNow() } @@ -56,6 +63,7 @@ class SaveToDiskExecutor(val activity : Activity) { * Save the gpkgFile to the downloads directory using the cacheDir and geoPackageName */ fun saveToDisk(cacheDir : File, gpkgFile : File, geoPackageName : String){ + // This appears to have been undeprecated in 2022 val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) cacheDir.mkdir() @@ -71,20 +79,21 @@ class SaveToDiskExecutor(val activity : Activity) { + GeoPackageConstants.EXTENSION ) } + var statusMessage: String = "File saved to downloads" alertDialog.show() myExecutor.submit { try { GeoPackageIOUtils.copyFile(gpkgFile, cacheFile) } catch (e: IOException) { - System.out.println("IOException: $e") + statusMessage = "Error saving file: $e" } catch (e: InterruptedException){ - System.out.println("Exception: $e") + statusMessage = "File save interrupted" Thread.currentThread().interrupt() } handler.post { deleteCachedDatabaseFiles(cacheDir) - Toast.makeText(activity, "GeoPackage saved to Downloads", Toast.LENGTH_SHORT).show() - alertDialog.dismiss() + actionLabel.text = statusMessage + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(R.string.button_ok_label) } } } diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt b/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt index 4a79dc67..ea6ef425 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt @@ -2,6 +2,7 @@ package mil.nga.mapcache.load import android.app.Activity import android.app.AlertDialog +import android.content.DialogInterface import android.content.Intent import android.net.Uri import android.os.Looper @@ -20,7 +21,7 @@ import java.util.concurrent.ExecutorService import java.util.concurrent.Executors /** - * Copy an internal database to a shareable location and share + * Copy an internal database to a shareable location and share. feedback provided by alertdialog */ class ShareCopyExecutor(val activity : Activity, val shareIntent : Intent) { @@ -28,8 +29,11 @@ class ShareCopyExecutor(val activity : Activity, val shareIntent : Intent) { private val myExecutor: ExecutorService = Executors.newSingleThreadExecutor() private var geoPackageName : String = "" private val AUTHORITY = BuildConfig.APPLICATION_ID + ".fileprovider" + private val actionLabel: TextView - + /** + * Create an alert dialog for feedback + */ init { val builder = AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle) @@ -42,7 +46,7 @@ class ShareCopyExecutor(val activity : Activity, val shareIntent : Intent) { alertLogo.setImageResource(R.drawable.material_share) val titleText = alertView.findViewById(R.id.alert_title) titleText.setText(R.string.geopackage_share_label) - val actionLabel = alertView.findViewById(R.id.action_label) as TextView + actionLabel = alertView.findViewById(R.id.action_label) as TextView actionLabel.setText(R.string.geopackage_share_copy_message) actionLabel.visibility = View.VISIBLE @@ -64,25 +68,36 @@ class ShareCopyExecutor(val activity : Activity, val shareIntent : Intent) { cacheDir.mkdir() val handler = android.os.Handler(Looper.getMainLooper()) val cacheFile = File(cacheDir, geoPackageName + "." + GeoPackageConstants.EXTENSION) + var failedShare: Boolean = false + alertDialog.show() myExecutor.submit { try { GeoPackageIOUtils.copyFile(gpkgFile, cacheFile) } catch (e: IOException) { - //TODO better handle exceptions - alertDialog.dismiss() + actionLabel.text = activity.getString(R.string.share_exception, e) + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(R.string.button_ok_label) + failedShare = true } catch (e: InterruptedException){ + actionLabel.text = activity.getString(R.string.share_interrupted) + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(R.string.button_ok_label) + failedShare = true Thread.currentThread().interrupt() } handler.post { - alertDialog.dismiss() - // Create the content Uri and add intent permissions - val databaseUri = FileProvider.getUriForFile(activity, AUTHORITY, cacheFile) - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - launchShareIntent(shareIntent, databaseUri) + if(!failedShare) { + alertDialog.dismiss() + // Create the content Uri and add intent permissions + val databaseUri = FileProvider.getUriForFile(activity, AUTHORITY, cacheFile) + shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + launchShareIntent(shareIntent, databaseUri) + } } } } + /** + * Share the file via system sharing + */ private fun launchShareIntent(shareIntent: Intent, databaseUri: Uri) { shareIntent.putExtra(Intent.EXTRA_STREAM, databaseUri) activity.startActivityForResult( diff --git a/mapcache/src/main/res/values/strings.xml b/mapcache/src/main/res/values/strings.xml index 21c38a50..ed1296c1 100644 --- a/mapcache/src/main/res/values/strings.xml +++ b/mapcache/src/main/res/values/strings.xml @@ -137,6 +137,8 @@ GeoPackage Name Export Share + Error preparing file: %1$s + File sharing was interrupted Save to Downloads Saving GeoPackage to the Downloads directory Preparing internal GeoPackage for sharing From ac86a3e7dd027c39ae264e534efddc4cfb574110 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 17 Aug 2023 14:45:13 -0600 Subject: [PATCH 14/20] update version and build code --- mapcache/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mapcache/build.gradle b/mapcache/build.gradle index 11ca34fa..5a63b392 100644 --- a/mapcache/build.gradle +++ b/mapcache/build.gradle @@ -15,8 +15,8 @@ android { resValue "string", "applicationId", applicationId minSdkVersion 28 targetSdkVersion 31 - versionCode 53 - versionName '2.1.7' + versionCode 54 + versionName '2.1.8' multiDexEnabled true } buildTypes { From 05408b3247a00da3f9c94361e939531d6c6735e2 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 17 Aug 2023 14:46:18 -0600 Subject: [PATCH 15/20] release notes update --- mapcache/src/main/res/values/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mapcache/src/main/res/values/strings.xml b/mapcache/src/main/res/values/strings.xml index ed1296c1..2b0453ab 100644 --- a/mapcache/src/main/res/values/strings.xml +++ b/mapcache/src/main/res/values/strings.xml @@ -2,7 +2,7 @@ MapCache - Version 2.1.7 + Version 2.1.8 Released July 2023 MapCache Map @@ -386,9 +386,9 @@ <a href=http://ngageoint.github.io/geopackage-android>GeoPackage Android on Github</a> <a href=http://www.geopackage.org/#implementations_nga>OGC GeoPackage</a> - Release Notes - 2.1.7\n \n - - New support for dark mode\n - - Backend performance improvements + Release Notes - 2.1.8\n \n + - Backend performance improvements\n + - Usability enhancements From f88691504c36fd8f7c77eebc64cbb0541c4ff53a Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 17 Aug 2023 14:46:39 -0600 Subject: [PATCH 16/20] release date update --- mapcache/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapcache/src/main/res/values/strings.xml b/mapcache/src/main/res/values/strings.xml index 2b0453ab..7c664b5c 100644 --- a/mapcache/src/main/res/values/strings.xml +++ b/mapcache/src/main/res/values/strings.xml @@ -3,7 +3,7 @@ MapCache Version 2.1.8 - Released July 2023 + Released Aug 2023 MapCache Map Manager From 6ed31ffe887e7785b128ff6144e14af7607dbafc Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 17 Aug 2023 14:49:38 -0600 Subject: [PATCH 17/20] done with old asynctasks for download and share gpkg --- .../java/mil/nga/mapcache/load/ShareTask.java | 232 ------------------ 1 file changed, 232 deletions(-) diff --git a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java index aaf5be54..72c4ba03 100644 --- a/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareTask.java @@ -95,12 +95,8 @@ private void shareDatabaseOption() { // If internal database, file must be copied to cache for permission else { // Launch the share copy task - - //TODO: change to ShareCopyExecutor ShareCopyExecutor shareCopyExecutor = new ShareCopyExecutor(activity, shareIntent); shareCopyExecutor.shareGeoPackage(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); -// ShareCopyTask shareCopyTask = new ShareCopyTask(shareIntent); -// shareCopyTask.execute(geoPackageFile, geoPackageName); } } catch (Exception e) { GeoPackageUtils.showMessage(activity, "Error sharing GeoPackage", e.getMessage()); @@ -119,11 +115,8 @@ private void saveDatabaseOption(){ shareIntent.setAction(Intent.ACTION_SEND); // Launch the save to disk task - //TODO: Switch to SaveToDiskExecutor SaveToDiskExecutor diskExecutor = new SaveToDiskExecutor(activity); diskExecutor.saveToDisk(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); -// SaveToDiskTask saveTask = new SaveToDiskTask(shareIntent); -// saveTask.execute(geoPackageFile, geoPackageName); } catch (Exception e) { GeoPackageUtils.showMessage(activity, "Error saving to file", e.getMessage()); } @@ -203,231 +196,6 @@ private void launchShareIntent(Intent shareIntent, Uri databaseUri) { activity.startActivityForResult(Intent.createChooser(shareIntent, "Share"), ACTIVITY_SHARE_FILE); } - - - /** - * Copy an internal database to a shareable location and share - */ - private class ShareCopyTask extends AsyncTask { - - /** - * Share intent - */ - private Intent shareIntent; - - /** - * Share copy dialog - */ - private ProgressDialog shareCopyDialog = null; - - /** - * Cache file created - */ - private File cacheFile = null; - - - /** - * Constructor - * - * @param shareIntent - */ - ShareCopyTask(Intent shareIntent) { - this.shareIntent = shareIntent; - } - - /** - * {@inheritDoc} - */ - @Override - protected void onPreExecute() { - shareCopyDialog = new ProgressDialog(activity); - shareCopyDialog - .setMessage("Preparing internal GeoPackage for sharing"); - shareCopyDialog.setCancelable(false); - shareCopyDialog.setIndeterminate(true); - shareCopyDialog.setButton(ProgressDialog.BUTTON_NEGATIVE, - "Cancel", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - cancel(true); - } - }); - shareCopyDialog.show(); - } - - /** - * {@inheritDoc} - */ - @Override - protected String doInBackground(Object... params) { - - File databaseFile = (File) params[0]; - String database = (String) params[1]; - - // Copy the database to cache - File cacheDirectory = getDatabaseCacheDirectory(); - cacheDirectory.mkdir(); - cacheFile = new File(cacheDirectory, database + "." - + GeoPackageConstants.EXTENSION); - try { - GeoPackageIOUtils.copyFile(databaseFile, cacheFile); - } catch (IOException e) { - return e.getMessage(); - } - - return null; - } - - /** - * {@inheritDoc} - */ - @Override - protected void onCancelled(String result) { - shareCopyDialog.dismiss(); - deleteCachedDatabaseFiles(); - } - - /** - * {@inheritDoc} - */ - @Override - protected void onPostExecute(String result) { - shareCopyDialog.dismiss(); - if (result != null) { - GeoPackageUtils.showMessage(activity, - "Share", result); - } else { - // Create the content Uri and add intent permissions - Uri databaseUri = FileProvider.getUriForFile(activity, - AUTHORITY, - cacheFile); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - launchShareIntent(shareIntent, databaseUri); - } - } -} - - - /** - * Saves a given file to disk - */ - private class SaveToDiskTask extends AsyncTask { - /** - * save intent - */ - private Intent saveIntent; - - /** - * Cache file created - */ - private File cacheFile = null; - - /** - * Save dialog - */ - private ProgressDialog saveDialog = null; - - /** - * Constructor - * - * @param saveIntent - */ - SaveToDiskTask(Intent saveIntent) { - this.saveIntent = saveIntent; - } - - /** - * pre execute - show dialog - */ - @Override - protected void onPreExecute() { - saveDialog = new ProgressDialog(activity); - saveDialog - .setMessage("Saving GeoPackage to Downloads"); - saveDialog.setCancelable(false); - saveDialog.setIndeterminate(true); - saveDialog.setButton(ProgressDialog.BUTTON_NEGATIVE, - "Cancel", - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - cancel(true); - } - }); - saveDialog.show(); - } - - /** - * Save file to the downloads directory - * @param params - * @return - */ - @Override - protected String doInBackground(Object... params) { - - File downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - File databaseFile = (File) params[0]; - String database = (String) params[1]; - - // Copy the database to cache - File cacheDirectory = getDatabaseCacheDirectory(); - cacheDirectory.mkdir(); - cacheFile = new File(downloadDir, database + "." - + GeoPackageConstants.EXTENSION); - - // If file already exists, add a number on the end to ensure we don't overwrite - int fileNumber = 0; - while(cacheFile.exists()){ - fileNumber++; - cacheFile = new File(downloadDir, database + fileNumber + "." - + GeoPackageConstants.EXTENSION); - } - - try { - GeoPackageIOUtils.copyFile(databaseFile, cacheFile); - } catch (IOException e) { - return e.getMessage(); - } - return null; - } - - /** - * post execute - close dialog - */ - @Override - protected void onPostExecute(String result) { - saveDialog.dismiss(); - if (result != null) { - GeoPackageUtils.showMessage(activity, - "Save", result); - } - Toast.makeText(activity, "GeoPackage saved to Downloads", Toast.LENGTH_SHORT).show(); - deleteCachedDatabaseFiles(); - } - } - - - - - - - /** - * Delete any cached database files - */ - private void deleteCachedDatabaseFiles() { - File databaseCache = getDatabaseCacheDirectory(); - if (databaseCache.exists()) { - File[] cacheFiles = databaseCache.listFiles(); - if (cacheFiles != null) { - for (File cacheFile : cacheFiles) { - cacheFile.delete(); - } - } - databaseCache.delete(); - } - } - public String getGeoPackageName() { return geoPackageName; } From 0ad7a8cea1defc2ff5a894b208766b214d7336bc Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Fri, 18 Aug 2023 14:08:38 -0600 Subject: [PATCH 18/20] taret android sdk 33 --- mapcache/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mapcache/build.gradle b/mapcache/build.gradle index 5a63b392..024721b1 100644 --- a/mapcache/build.gradle +++ b/mapcache/build.gradle @@ -4,7 +4,7 @@ def googleMapsApiReleaseKey = hasProperty('RELEASE_MAPS_MAPCACHE_API_KEY') ? REL def googleMapsApiKeyDebug = hasProperty('DEBUG_MAPS_API_KEY') ? DEBUG_MAPS_API_KEY : '' android { - compileSdkVersion 31 + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_11 @@ -14,7 +14,7 @@ android { applicationId "mil.nga.mapcache" resValue "string", "applicationId", applicationId minSdkVersion 28 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 54 versionName '2.1.8' multiDexEnabled true From a610c69fa3e20e45221df10e9af72e69c0907bee Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Tue, 22 Aug 2023 08:28:18 -0600 Subject: [PATCH 19/20] version code bump --- mapcache/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mapcache/build.gradle b/mapcache/build.gradle index 024721b1..dc20d2fc 100644 --- a/mapcache/build.gradle +++ b/mapcache/build.gradle @@ -15,7 +15,7 @@ android { resValue "string", "applicationId", applicationId minSdkVersion 28 targetSdkVersion 33 - versionCode 54 + versionCode 55 versionName '2.1.8' multiDexEnabled true } From 3e4b202208567c71c93e0be1a867159a2c2b9142 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Thu, 24 Aug 2023 13:27:59 -0600 Subject: [PATCH 20/20] update gpkg detail page enable all swtich theme --- mapcache/src/main/res/layout/detail_header_layout.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/mapcache/src/main/res/layout/detail_header_layout.xml b/mapcache/src/main/res/layout/detail_header_layout.xml index 60d4f7e8..c51fde29 100644 --- a/mapcache/src/main/res/layout/detail_header_layout.xml +++ b/mapcache/src/main/res/layout/detail_header_layout.xml @@ -171,7 +171,6 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" android:checked="true" - android:theme="@style/SwitchCompatTheme" android:paddingStart="8dp"/>