diff --git a/app/src/main/java/protect/card_locker/ImportExportActivity.java b/app/src/main/java/protect/card_locker/ImportExportActivity.java index 95d9750ab..95829b1c9 100644 --- a/app/src/main/java/protect/card_locker/ImportExportActivity.java +++ b/app/src/main/java/protect/card_locker/ImportExportActivity.java @@ -1,25 +1,42 @@ package protect.card_locker; import android.Manifest; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.Toast; +import java.io.File; +import java.util.List; + public class ImportExportActivity extends AppCompatActivity { - private static final int PERMISSIONS_EXTERNAL_STORAGE_IMPORT = 1; - private static final int PERMISSIONS_EXTERNAL_STORAGE_EXPORT = 2; + private static final String TAG = "LoyaltyCardLocker"; - ImportExportTask importExporter; + private static final int PERMISSIONS_EXTERNAL_STORAGE = 1; + private static final int CHOOSE_EXPORT_FILE = 2; + + private ImportExportTask importExporter; + + private final File sdcardDir = Environment.getExternalStorageDirectory(); + private final File exportFile = new File(sdcardDir, "LoyaltyCardLocker.csv"); @Override protected void onCreate(Bundle savedInstanceState) @@ -34,25 +51,20 @@ public class ImportExportActivity extends AppCompatActivity actionBar.setDisplayHomeAsUpEnabled(true); } - Button importButton = (Button)findViewById(R.id.importButton); - importButton.setOnClickListener(new View.OnClickListener() + // If the application does not have permissions to external + // storage, as for it now + + if (ContextCompat.checkSelfPermission(ImportExportActivity.this, + Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(ImportExportActivity.this, + Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - @Override - public void onClick(View v) - { - if (ContextCompat.checkSelfPermission(ImportExportActivity.this, - Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) - { - startImport(); - } - else - { - ActivityCompat.requestPermissions(ImportExportActivity.this, - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - PERMISSIONS_EXTERNAL_STORAGE_IMPORT); - } - } - }); + ActivityCompat.requestPermissions(ImportExportActivity.this, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE}, + PERMISSIONS_EXTERNAL_STORAGE); + } + Button exportButton = (Button)findViewById(R.id.exportButton); exportButton.setOnClickListener(new View.OnClickListener() @@ -60,56 +72,120 @@ public class ImportExportActivity extends AppCompatActivity @Override public void onClick(View v) { - if (ContextCompat.checkSelfPermission(ImportExportActivity.this, - Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) - { - startExport(); - } - else - { - ActivityCompat.requestPermissions(ImportExportActivity.this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSIONS_EXTERNAL_STORAGE_EXPORT); - } + startExport(); + } + }); + + + // Check that there is an activity that can bring up a file chooser + final Intent intentPickAction = new Intent(Intent.ACTION_PICK); + intentPickAction.setData(Uri.parse("file://")); + + Button importFilesystem = (Button) findViewById(R.id.importOptionFilesystemButton); + importFilesystem.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + chooseFileWithIntent(intentPickAction); + } + }); + + if(isCallable(getApplicationContext(), intentPickAction) == false) + { + findViewById(R.id.dividerImportFilesystem).setVisibility(View.GONE); + findViewById(R.id.importOptionFilesystemTitle).setVisibility(View.GONE); + findViewById(R.id.importOptionFilesystemExplanation).setVisibility(View.GONE); + importFilesystem.setVisibility(View.GONE); + } + + + // Check that there is an application that can find content + final Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); + intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); + intentGetContentAction.setType("*/*"); + + Button importApplication = (Button) findViewById(R.id.importOptionApplicationButton); + importApplication.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + chooseFileWithIntent(intentGetContentAction); + } + }); + + if(isCallable(getApplicationContext(), intentGetContentAction) == false) + { + findViewById(R.id.dividerImportApplication).setVisibility(View.GONE); + findViewById(R.id.importOptionApplicationTitle).setVisibility(View.GONE); + findViewById(R.id.importOptionApplicationExplanation).setVisibility(View.GONE); + importApplication.setVisibility(View.GONE); + } + + + // This option, to import from the fixed location, should always be present + + Button importButton = (Button)findViewById(R.id.importOptionFixedButton); + importButton.setOnClickListener(new View.OnClickListener() + { + @Override + public void onClick(View v) + { + startImport(exportFile); } }); } - private void startImport() + private void startImport(File target) { + ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() + { + @Override + public void onTaskComplete(boolean success, File file) + { + onImportComplete(success, file); + } + }; + importExporter = new ImportExportTask(ImportExportActivity.this, - true, DataFormat.CSV); + true, DataFormat.CSV, target, listener); importExporter.execute(); } private void startExport() { + ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() + { + @Override + public void onTaskComplete(boolean success, File file) + { + onExportComplete(success, file); + } + }; + importExporter = new ImportExportTask(ImportExportActivity.this, - false, DataFormat.CSV); + false, DataFormat.CSV, exportFile, listener); importExporter.execute(); } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { - if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT || - requestCode == PERMISSIONS_EXTERNAL_STORAGE_EXPORT) + if(requestCode == PERMISSIONS_EXTERNAL_STORAGE) { // If request is cancelled, the result arrays are empty. - if (grantResults.length > 0 && - grantResults[0] == PackageManager.PERMISSION_GRANTED) + boolean success = grantResults.length > 0; + + for(int grant : grantResults) { - // permission was granted. - if(requestCode == PERMISSIONS_EXTERNAL_STORAGE_IMPORT) + if(grant != PackageManager.PERMISSION_GRANTED) { - startImport(); - } - else - { - startExport(); + success = false; } } - else + + if(success == false) { // External storage permission rejected, inform user that // import/export is prevented @@ -143,4 +219,137 @@ public class ImportExportActivity extends AppCompatActivity return super.onOptionsItemSelected(item); } + + private void onImportComplete(boolean success, File path) + { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + + if(success) + { + builder.setTitle(R.string.importSuccessfulTitle); + } + else + { + builder.setTitle(R.string.importFailedTitle); + } + + int messageId = success ? R.string.importedFrom : R.string.importFailed; + + final String template = getResources().getString(messageId); + final String message = String.format(template, path.getAbsolutePath()); + builder.setMessage(message); + builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + } + }); + + builder.create().show(); + } + + private void onExportComplete(boolean success, File path) + { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + + if(success) + { + builder.setTitle(R.string.exportSuccessfulTitle); + } + else + { + builder.setTitle(R.string.exportFailedTitle); + } + + int messageId = success ? R.string.exportedTo : R.string.exportFailed; + + final String template = getResources().getString(messageId); + final String message = String.format(template, path.getAbsolutePath()); + builder.setMessage(message); + builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + dialog.dismiss(); + } + }); + + builder.create().show(); + } + + /** + * Determines if there is at least one activity that can perform the given intent + */ + private boolean isCallable(Context context, final Intent intent) + { + PackageManager manager = context.getPackageManager(); + if(manager == null) + { + return false; + } + + List list = manager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + + for(ResolveInfo info : list) + { + if(info.activityInfo.exported) + { + // There is one activity which is available to be called + return true; + } + } + + return false; + } + + private void chooseFileWithIntent(Intent intent) + { + try + { + startActivityForResult(intent, CHOOSE_EXPORT_FILE); + } + catch (ActivityNotFoundException e) + { + Log.e(TAG, "No activity found to handle intent", e); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode == RESULT_OK && requestCode == CHOOSE_EXPORT_FILE) + { + String path = null; + + Uri uri = data.getData(); + if(uri != null && uri.toString().startsWith("/")) + { + uri = Uri.parse("file://" + uri.toString()); + } + + if(uri != null) + { + path = uri.getPath(); + } + + if(path != null) + { + Log.e(TAG, "Starting file import with: " + uri.toString()); + startImport(new File(path)); + } + else + { + Log.e(TAG, "Fail to make sense of URI returned from activity: " + (uri != null ? uri.toString() : "null")); + } + } + else + { + Log.w(TAG, "Failed onActivityResult(), result=" + resultCode); + } + } } \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/ImportExportTask.java b/app/src/main/java/protect/card_locker/ImportExportTask.java index ee0edab72..a883d9761 100644 --- a/app/src/main/java/protect/card_locker/ImportExportTask.java +++ b/app/src/main/java/protect/card_locker/ImportExportTask.java @@ -16,49 +16,31 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.Charset; -class ImportExportTask extends AsyncTask +class ImportExportTask extends AsyncTask { private static final String TAG = "LoyaltyCardLocker"; - private static final String TARGET_FILE = "LoyaltyCardLocker.csv"; - private Activity activity; private boolean doImport; private DataFormat format; + private File target; + private TaskCompleteListener listener; private ProgressDialog progress; - public ImportExportTask(Activity activity, boolean doImport, DataFormat format) + public ImportExportTask(Activity activity, boolean doImport, DataFormat format, File target, + TaskCompleteListener listener) { super(); this.activity = activity; this.doImport = doImport; this.format = format; + this.target = target; + this.listener = listener; } - private void toastWithArg(int stringId, String argument) + private boolean performImport(File importFile, DBHelper db) { - final String template = activity.getResources().getString(stringId); - final String message = String.format(template, argument); - - activity.runOnUiThread(new Runnable() - { - @Override - public void run() - { - Toast.makeText(activity, message, Toast.LENGTH_LONG).show(); - } - }); - } - - private void performImport(File importFile, DBHelper db) - { - if(importFile.exists() == false) - { - toastWithArg(R.string.fileMissing, importFile.getAbsolutePath()); - return; - } - boolean result = false; try @@ -73,11 +55,12 @@ class ImportExportTask extends AsyncTask Log.e(TAG, "Unable to import file", e); } - int messageId = result ? R.string.importedFrom : R.string.importFailed; - toastWithArg(messageId, importFile.getAbsolutePath()); + Log.i(TAG, "Import of '" + importFile.getAbsolutePath() + "' result: " + result); + + return result; } - private void performExport(File exportFile, DBHelper db) + private boolean performExport(File exportFile, DBHelper db) { boolean result = false; @@ -93,8 +76,7 @@ class ImportExportTask extends AsyncTask Log.e(TAG, "Unable to export file", e); } - int messageId = result ? R.string.exportedTo : R.string.exportFailed; - toastWithArg(messageId, exportFile.getAbsolutePath()); + return result; } protected void onPreExecute() @@ -114,26 +96,27 @@ class ImportExportTask extends AsyncTask progress.show(); } - protected Void doInBackground(Void... nothing) + protected Boolean doInBackground(Void... nothing) { - final File sdcardDir = Environment.getExternalStorageDirectory(); - final File importExportFile = new File(sdcardDir, TARGET_FILE); final DBHelper db = new DBHelper(activity); + boolean result; if(doImport) { - performImport(importExportFile, db); + result = performImport(target, db); } else { - performExport(importExportFile, db); + result = performExport(target, db); } - return null; + return result; } - protected void onPostExecute(Void result) + protected void onPostExecute(Boolean result) { + listener.onTaskComplete(result, target); + progress.dismiss(); Log.i(TAG, (doImport ? "Import" : "Export") + " Complete"); } @@ -143,4 +126,9 @@ class ImportExportTask extends AsyncTask progress.dismiss(); Log.i(TAG, (doImport ? "Import" : "Export") + " Cancelled"); } + + interface TaskCompleteListener + { + void onTaskComplete(boolean success, File file); + } } diff --git a/app/src/main/res/layout/import_export_activity.xml b/app/src/main/res/layout/import_export_activity.xml index 3291f87f3..64a3789ea 100644 --- a/app/src/main/res/layout/import_export_activity.xml +++ b/app/src/main/res/layout/import_export_activity.xml @@ -19,36 +19,149 @@ - - + - -