package mil.nga.dice;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.PowerManager;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import java.io.File;
import java.io.InputStream;
import mil.nga.dice.io.DICEFileUtils;
import mil.nga.dice.map.geopackage.GeoPackageSelected;
import mil.nga.dice.report.ReportManager;
import mil.nga.geopackage.GeoPackageManager;
import mil.nga.geopackage.factory.GeoPackageFactory;
import mil.nga.geopackage.io.GeoPackageIOUtils;
import mil.nga.geopackage.io.GeoPackageProgress;
import mil.nga.geopackage.validate.GeoPackageValidate;
/**
* GeoPackage cache for importing GeoPackage files
*/
public class GeoPackageCache {
/**
* Creating activity
*/
private final Activity activity;
/**
* GeoPackage manager
*/
private final GeoPackageManager manager;
/**
* Progress dialog for network operations
*/
private ProgressDialog progressDialog;
/**
* Import external GeoPackage name holder when asking for external write permission
*/
private String importExternalName;
/**
* Import external GeoPackage URI holder when asking for external write permission
*/
private Uri importExternalUri;
/**
* Import external GeoPackage path holder when asking for external write permission
*/
private String importExternalPath;
/**
* Selected GeoPackages
*/
private GeoPackageSelected selected;
/**
* Constructor
*
* @param activity
*/
public GeoPackageCache(Activity activity) {
this.activity = activity;
this.manager = GeoPackageFactory.getManager(activity);
this.selected = new GeoPackageSelected(activity);
}
/**
* Check if the name contains a GeoPackage extension
*
* @param name file name
* @return true if a GeoPackage name
*/
public boolean hasGeoPackageExtension(String name) {
return GeoPackageValidate.hasGeoPackageExtension(new File(name));
}
/**
* Import the GeoPackage file, by copying a remote file or linking a local file
*
* @param name database name
* @param uri file uri
* @param path file path
*/
public void importFile(String name, Uri uri, String path) {
name = DICEFileUtils.removeExtension(name);
if (path == null || DICEFileUtils.isTemporaryPath(path)) {
importGeoPackage(name, uri, path);
} else {
importGeoPackageExternalLinkWithPermissions(name, uri, path);
}
}
/**
* Import a GeoPackage as an externally linked file and handling permission requests
*
* @param name database name
* @param uri file uri
* @param path file path
*/
private void importGeoPackageExternalLinkWithPermissions(final String name, final Uri uri, String path) {
// Check for permission
if (ContextCompat.checkSelfPermission(activity, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
importGeoPackageExternalLink(name, uri, path);
} else {
// Save off the values and ask for permission
importExternalName = name;
importExternalUri = uri;
importExternalPath = path;
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
new AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle)
.setTitle(R.string.storage_access_rational_title)
.setMessage(R.string.storage_access_rational_message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(activity, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, ReportCollectionActivity.PERMISSIONS_REQUEST_IMPORT_GEOPACKAGE);
}
})
.create()
.show();
} else {
ActivityCompat.requestPermissions(activity, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, ReportCollectionActivity.PERMISSIONS_REQUEST_IMPORT_GEOPACKAGE);
}
}
}
/**
* Import the GeoPackage by linking to the file after write external storage permission was granted
*
* @param granted true if permission was granted
*/
public void importGeoPackageExternalLinkAfterPermissionGranted(boolean granted) {
if (granted) {
importGeoPackageExternalLink(importExternalName, importExternalUri, importExternalPath);
} else {
showDisabledExternalImportPermissionsDialog();
}
}
/**
* Import the GeoPackage by linking to the file
*
* @param name database name
* @param uri file uri
* @param path file path
*/
private void importGeoPackageExternalLink(final String name, final Uri uri, String path) {
if(manager.importGeoPackageAsExternalLink(path, name, true)){
selected.addSelected(name);
}
}
/**
* Run the import task to import a GeoPackage by copying it
*
* @param name database name
* @param uri file uri
* @param path file path
*/
private void importGeoPackage(final String name, Uri uri, String path) {
if(manager.exists(name)){
manager.delete(name);
selected.removeSelected(name);
}
ImportTask importTask = new ImportTask(name, path, uri);
progressDialog = createImportProgressDialog(name,
importTask, path, uri, null);
progressDialog.setIndeterminate(true);
importTask.execute();
}
/**
* Import a GeoPackage from a stream in the background
*/
private class ImportTask extends AsyncTask<String, Integer, String>
implements GeoPackageProgress {
private Integer max = null;
private int progress = 0;
private final String database;
private final String path;
private final Uri uri;
private PowerManager.WakeLock wakeLock;
/**
* Constructor
*
* @param database
* @param path
* @param uri
*/
public ImportTask(String database, String path, Uri uri) {
this.database = database;
this.path = path;
this.uri = uri;
}
/**
* {@inheritDoc}
*/
@Override
public void setMax(int max) {
this.max = max;
}
/**
* {@inheritDoc}
*/
@Override
public void addProgress(int progress) {
this.progress += progress;
if (max != null) {
int total = (int) (this.progress / ((double) max) * 100);
publishProgress(total);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isActive() {
return !isCancelled();
}
/**
* {@inheritDoc}
*/
@Override
public boolean cleanupOnCancel() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
progressDialog.show();
PowerManager pm = (PowerManager) activity.getSystemService(
Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
wakeLock.acquire();
}
/**
* {@inheritDoc}
*/
@Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
// If the indeterminate progress dialog is still showing, swap to a
// determinate horizontal bar
if (progressDialog.isIndeterminate()) {
String messageSuffix = "\n\n"
+ GeoPackageIOUtils.formatBytes(max);
ProgressDialog newProgressDialog = createImportProgressDialog(
database, this, path, uri, messageSuffix);
newProgressDialog
.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
newProgressDialog.setIndeterminate(false);
newProgressDialog.setMax(100);
newProgressDialog.show();
progressDialog.dismiss();
progressDialog = newProgressDialog;
}
// Set the progress
progressDialog.setProgress(progress[0]);
}
/**
* {@inheritDoc}
*/
@Override
protected void onCancelled(String result) {
wakeLock.release();
progressDialog.dismiss();
}
/**
* {@inheritDoc}
*/
@Override
protected void onPostExecute(String result) {
wakeLock.release();
progressDialog.dismiss();
if (result != null) {
showMessage("Import",
"Failed to import: "
+ (path != null ? path : uri.getPath()));
}else{
selected.addSelected(database);
ReportManager.getInstance().refreshReports(activity);
}
}
/**
* {@inheritDoc}
*/
@Override
protected String doInBackground(String... params) {
try {
final ContentResolver resolver = activity.getContentResolver();
InputStream stream = resolver.openInputStream(uri);
if (!manager.importGeoPackage(database, stream, true, this)) {
return "Failed to import GeoPackage '" + database + "'";
}
} catch (final Exception e) {
return e.toString();
}
return null;
}
}
/**
* Create a import progress dialog
*
* @param database
* @param importTask
* @param path
* @param uri
* @param suffix
* @return
*/
private ProgressDialog createImportProgressDialog(String database, final ImportTask importTask,
String path, Uri uri,
String suffix) {
ProgressDialog dialog = new ProgressDialog(activity);
dialog.setMessage(activity.getString(R.string.import_label) + " "
+ database + "\n\n" + (path != null ? path : uri.getPath()) + (suffix != null ? suffix : ""));
dialog.setCancelable(false);
dialog.setButton(ProgressDialog.BUTTON_NEGATIVE,
activity.getString(R.string.button_cancel_label),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
importTask.cancel(true);
}
});
return dialog;
}
/**
* Show a message with an OK button
*
* @param title
* @param message
*/
private void showMessage(String title,
String message) {
if (title != null || message != null) {
new AlertDialog.Builder(activity)
.setTitle(title != null ? title : "")
.setMessage(message != null ? message : "")
.setNeutralButton(
activity.getString(R.string.button_ok_label),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int id) {
dialog.cancel();
}
}).show();
}
}
/**
* Show a disabled read external storage permissions dialog when external files can not be read
*/
private void showDisabledExternalImportPermissionsDialog() {
// If the user has declared to no longer get asked about permissions
if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, android.Manifest.permission.READ_EXTERNAL_STORAGE)) {
showDisabledPermissionsDialog(
activity.getResources().getString(R.string.read_external_access_denied_title),
activity.getResources().getString(R.string.read_external_access_denied_message));
}
}
/**
* Show a disabled permissions dialog
*
* @param title
* @param message
*/
private void showDisabledPermissionsDialog(String title, String message) {
new android.support.v7.app.AlertDialog.Builder(activity, R.style.AppCompatAlertDialogStyle)
.setTitle(title)
.setMessage(message)
.setPositiveButton(R.string.settings, new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", activity.getPackageName(), null));
activity.startActivityForResult(intent, ReportCollectionActivity.ACTIVITY_APP_SETTINGS);
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}