diff --git a/mapcache/build.gradle b/mapcache/build.gradle index e633ecc6..dc20d2fc 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,9 +14,9 @@ android { applicationId "mil.nga.mapcache" resValue "string", "applicationId", applicationId minSdkVersion 28 - targetSdkVersion 31 - versionCode 53 - versionName '2.1.7' + targetSdkVersion 33 + versionCode 55 + versionName '2.1.8' multiDexEnabled true } buildTypes { @@ -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 { @@ -51,7 +58,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 @@ -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/main/java/mil/nga/mapcache/GeoPackageMapFragment.java b/mapcache/src/main/java/mil/nga/mapcache/GeoPackageMapFragment.java index 3078b557..0b41af5f 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; @@ -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; @@ -160,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; @@ -592,6 +594,8 @@ private enum EditType { */ ActivityResultLauncher importGeoPackageActivityResultLauncher; ActivityResultLauncher preferencePageActivityResultLauncher; + ActivityResultLauncher downloadTaskResultLauncher; + /** @@ -1076,7 +1080,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); } @@ -1842,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 @@ -1900,8 +1907,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(); } } @@ -2026,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/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/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..47df9cb2 --- /dev/null +++ b/mapcache/src/main/java/mil/nga/mapcache/load/Downloader.kt @@ -0,0 +1,109 @@ +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, providing feedback and cancel action via + * an AlertDialog + */ +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() + private var geoPackageName : String = "" + + /** + * Create the alert dialog + */ + 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_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) + builder.setCancelable(false) + 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) + var completeMessage : String = "Import failed" + geoPackageName = database + alertDialog.show() + myExecutor.submit { + try { + if (!viewModel.importGeoPackage(database, theUrl, this)) { + completeMessage = "Failed to import GeoPackage '$database' at url '$url'" + } else { + 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 { + return true + } + + override fun cleanupOnCancel(): Boolean { + return true + } +} \ No newline at end of file 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..8450d546 --- /dev/null +++ b/mapcache/src/main/java/mil/nga/mapcache/load/SaveToDiskExecutor.kt @@ -0,0 +1,114 @@ +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 +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 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) + + // 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) + actionLabel = alertView.findViewById(R.id.action_label) as TextView + actionLabel.setText(R.string.geopackage_save_message) + actionLabel.visibility = View.VISIBLE + + // Cancel button - interrupt thread + 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){ + // This appears to have been undeprecated in 2022 + val downloadDir = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + cacheDir.mkdir() + val handler = android.os.Handler(Looper.getMainLooper()) + 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 + while (cacheFile.exists()) { + fileNumber++ + cacheFile = File( + downloadDir, geoPackageName + fileNumber + "." + + GeoPackageConstants.EXTENSION + ) + } + var statusMessage: String = "File saved to downloads" + alertDialog.show() + myExecutor.submit { + try { + GeoPackageIOUtils.copyFile(gpkgFile, cacheFile) + } catch (e: IOException) { + statusMessage = "Error saving file: $e" + } catch (e: InterruptedException){ + statusMessage = "File save interrupted" + Thread.currentThread().interrupt() + } + handler.post { + deleteCachedDatabaseFiles(cacheDir) + actionLabel.text = statusMessage + alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(R.string.button_ok_label) + } + } + } + + + /** + * 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/ShareCopyExecutor.kt b/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt new file mode 100644 index 00000000..ea6ef425 --- /dev/null +++ b/mapcache/src/main/java/mil/nga/mapcache/load/ShareCopyExecutor.kt @@ -0,0 +1,108 @@ +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 +import android.view.LayoutInflater +import android.view.View +import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageView +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 +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +/** + * Copy an internal database to a shareable location and share. feedback provided by alertdialog + */ +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" + private val actionLabel: TextView + + /** + * Create an alert dialog for feedback + */ + 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) + 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) + var failedShare: Boolean = false + alertDialog.show() + myExecutor.submit { + try { + GeoPackageIOUtils.copyFile(gpkgFile, cacheFile) + } catch (e: IOException) { + 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 { + 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( + 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 22f7a07e..72c4ba03 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; @@ -26,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; @@ -51,53 +56,48 @@ 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); } // If internal database, file must be copied to cache for permission else { // Launch the share copy task - ShareCopyTask shareCopyTask = new ShareCopyTask(shareIntent); - shareCopyTask.execute(databaseFile, database); + ShareCopyExecutor shareCopyExecutor = new ShareCopyExecutor(activity, shareIntent); + shareCopyExecutor.shareGeoPackage(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); } - } catch (Exception e) { GeoPackageUtils.showMessage(activity, "Error sharing GeoPackage", e.getMessage()); } @@ -107,21 +107,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); - + SaveToDiskExecutor diskExecutor = new SaveToDiskExecutor(activity); + diskExecutor.saveToDisk(getDatabaseCacheDirectory(), geoPackageFile, geoPackageName); } catch (Exception e) { GeoPackageUtils.showMessage(activity, "Error saving to file", e.getMessage()); } @@ -133,41 +128,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(); - } - }); - - // 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(); - } - }); - - alertDialog.show(); + 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(); + alertDialog.dismiss(); + } + }); + + alertDialog.show(); + } catch (Exception e) { + GeoPackageUtils.showMessage(activity, "Error sharing", e.getMessage()); + } } @@ -190,246 +192,32 @@ 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); - - } - - - - /** - * 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(); + public String getGeoPackageName() { + return geoPackageName; } - /** - * {@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); - } + public void setGeoPackageName(String geoPackageName) { + this.geoPackageName = geoPackageName; } -} - - - /** - * 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(); - } + public void setGeoPackageFile(File geoPackageFile){ + this.geoPackageFile = geoPackageFile; } - - - - - - /** - * 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 File getGeoPackageFile(){ + return this.geoPackageFile; } - public String getGeoPackageName() { - return geoPackageName; + public boolean isFileExternal() { + return isFileExternal; } - public void setGeoPackageName(String geoPackageName) { - this.geoPackageName = geoPackageName; + public void setFileExternal(boolean fileExternal) { + isFileExternal = fileExternal; } } 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; + } } /** 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; 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"/> diff --git a/mapcache/src/main/res/values/strings.xml b/mapcache/src/main/res/values/strings.xml index e295b377..7c664b5c 100644 --- a/mapcache/src/main/res/values/strings.xml +++ b/mapcache/src/main/res/values/strings.xml @@ -2,8 +2,8 @@ MapCache - Version 2.1.7 - Released July 2023 + Version 2.1.8 + Released Aug 2023 MapCache Map Manager @@ -137,6 +137,10 @@ 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 Download Create @@ -382,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 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); + } + +}