Merge pull request #153 from brarcher/content-imports

Support importing/exporting via content providers
This commit is contained in:
Branden Archer
2017-11-21 21:24:52 -05:00
committed by GitHub
5 changed files with 133 additions and 56 deletions

View File

@@ -68,6 +68,15 @@
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:grantUriPermissions="true"
android:exported="false"
android:authorities="${applicationId}">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths"/>
</provider>
</application>
</manifest>

View File

@@ -7,12 +7,15 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.provider.OpenableColumns;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
@@ -24,6 +27,10 @@ import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
public class ImportExportActivity extends AppCompatActivity
@@ -132,24 +139,34 @@ public class ImportExportActivity extends AppCompatActivity
@Override
public void onClick(View v)
{
startImport(exportFile);
Uri uri = Uri.fromFile(exportFile);
try
{
FileInputStream stream = new FileInputStream(exportFile);
startImport(stream, uri);
}
catch(FileNotFoundException e)
{
Log.e(TAG, "Could not import file " + exportFile.getAbsolutePath(), e);
onImportComplete(false, uri);
}
}
});
}
private void startImport(File target)
private void startImport(final InputStream target, final Uri targetUri)
{
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success, File file)
public void onTaskComplete(boolean success)
{
onImportComplete(success, file);
onImportComplete(success, targetUri);
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
true, DataFormat.CSV, target, listener);
DataFormat.CSV, target, listener);
importExporter.execute();
}
@@ -158,14 +175,14 @@ public class ImportExportActivity extends AppCompatActivity
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
{
@Override
public void onTaskComplete(boolean success, File file)
public void onTaskComplete(boolean success)
{
onExportComplete(success, file);
onExportComplete(success, exportFile);
}
};
importExporter = new ImportExportTask(ImportExportActivity.this,
false, DataFormat.CSV, exportFile, listener);
DataFormat.CSV, exportFile, listener);
importExporter.execute();
}
@@ -220,7 +237,34 @@ public class ImportExportActivity extends AppCompatActivity
return super.onOptionsItemSelected(item);
}
private void onImportComplete(boolean success, File path)
private String fileNameFromUri(Uri uri)
{
if("file".equals(uri.getScheme()))
{
return uri.getPath();
}
Cursor returnCursor =
getContentResolver().query(uri, null, null, null, null);
if(returnCursor == null)
{
return null;
}
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if(returnCursor.moveToFirst() == false)
{
returnCursor.close();
return null;
}
String name = returnCursor.getString(nameIndex);
returnCursor.close();
return name;
}
private void onImportComplete(boolean success, Uri path)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
@@ -236,7 +280,15 @@ public class ImportExportActivity extends AppCompatActivity
int messageId = success ? R.string.importedFrom : R.string.importFailed;
final String template = getResources().getString(messageId);
final String message = String.format(template, path.getAbsolutePath());
// Get the filename of the file being imported
String filename = fileNameFromUri(path);
if(filename == null)
{
filename = "(unknown)";
}
final String message = String.format(template, filename);
builder.setMessage(message);
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
{
@@ -286,11 +338,14 @@ public class ImportExportActivity extends AppCompatActivity
@Override
public void onClick(DialogInterface dialog, int which)
{
Uri outputUri = Uri.fromFile(path);
Uri outputUri = FileProvider.getUriForFile(ImportExportActivity.this, BuildConfig.APPLICATION_ID, path);
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_STREAM, outputUri);
sendIntent.setType("text/plain");
// set flag to give temporary permission to external app to use the FileProvider
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
sendLabel));
@@ -344,34 +399,28 @@ public class ImportExportActivity extends AppCompatActivity
{
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
if (resultCode != RESULT_OK || requestCode != CHOOSE_EXPORT_FILE)
{
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
return;
}
Uri uri = data.getData();
if(uri == null)
{
Log.e(TAG, "Activity returned a NULL URI");
return;
}
try
{
InputStream reader = getContentResolver().openInputStream(uri);
Log.e(TAG, "Starting file import with: " + uri.toString());
startImport(reader, uri);
}
catch (FileNotFoundException e)
{
Log.e(TAG, "Failed to import file: " + uri.toString(), e);
}
}
}

View File

@@ -12,6 +12,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
@@ -24,29 +25,46 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
private boolean doImport;
private DataFormat format;
private File target;
private InputStream inputStream;
private TaskCompleteListener listener;
private ProgressDialog progress;
public ImportExportTask(Activity activity, boolean doImport, DataFormat format, File target,
/**
* Constructor which will setup a task for exporting to the given file
*/
ImportExportTask(Activity activity, DataFormat format, File target,
TaskCompleteListener listener)
{
super();
this.activity = activity;
this.doImport = doImport;
this.doImport = false;
this.format = format;
this.target = target;
this.listener = listener;
}
private boolean performImport(File importFile, DBHelper db)
/**
* Constructor which will setup a task for importing from the given InputStream.
*/
ImportExportTask(Activity activity, DataFormat format, InputStream input,
TaskCompleteListener listener)
{
super();
this.activity = activity;
this.doImport = true;
this.format = format;
this.inputStream = input;
this.listener = listener;
}
private boolean performImport(InputStream stream, DBHelper db)
{
boolean result = false;
try
{
FileInputStream fileReader = new FileInputStream(importFile);
InputStreamReader reader = new InputStreamReader(fileReader, Charset.forName("UTF-8"));
InputStreamReader reader = new InputStreamReader(stream, Charset.forName("UTF-8"));
result = MultiFormatImporter.importData(db, reader, format);
reader.close();
}
@@ -55,7 +73,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
Log.e(TAG, "Unable to import file", e);
}
Log.i(TAG, "Import of '" + importFile.getAbsolutePath() + "' result: " + result);
Log.i(TAG, "Import result: " + result);
return result;
}
@@ -105,7 +123,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
if(doImport)
{
result = performImport(target, db);
result = performImport(inputStream, db);
}
else
{
@@ -117,7 +135,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
protected void onPostExecute(Boolean result)
{
listener.onTaskComplete(result, target);
listener.onTaskComplete(result);
progress.dismiss();
Log.i(TAG, (doImport ? "Import" : "Export") + " Complete");
@@ -130,7 +148,7 @@ class ImportExportTask extends AsyncTask<Void, Void, Boolean>
}
interface TaskCompleteListener
{
void onTaskComplete(boolean success, File file);
void onTaskComplete(boolean success);
}
}

View File

@@ -0,0 +1,3 @@
<paths>
<external-path name="exportPath" path="/" />
</paths>

View File

@@ -17,6 +17,8 @@ import org.robolectric.annotation.Config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
@@ -211,17 +213,15 @@ public class ImportExportTest
class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener
{
Boolean success;
File file;
public void onTaskComplete(boolean success, File file)
public void onTaskComplete(boolean success)
{
this.success = success;
this.file = file;
}
}
@Test
public void useImportExportTask()
public void useImportExportTask() throws FileNotFoundException
{
final int NUM_CARDS = 10;
@@ -235,7 +235,7 @@ public class ImportExportTest
TestTaskCompleteListener listener = new TestTaskCompleteListener();
// Export to the file
ImportExportTask task = new ImportExportTask(activity, false, format, exportFile, listener);
ImportExportTask task = new ImportExportTask(activity, format, exportFile, listener);
task.execute();
// Actually run the task to completion
@@ -244,8 +244,6 @@ public class ImportExportTest
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
assertNotNull(listener.file);
assertEquals(exportFile, listener.file);
clearDatabase();
@@ -253,7 +251,9 @@ public class ImportExportTest
listener = new TestTaskCompleteListener();
task = new ImportExportTask(activity, true, format, exportFile, listener);
FileInputStream fileStream = new FileInputStream(exportFile);
task = new ImportExportTask(activity, format, fileStream, listener);
task.execute();
// Actually run the task to completion
@@ -262,8 +262,6 @@ public class ImportExportTest
// Check that the listener was executed
assertNotNull(listener.success);
assertEquals(true, listener.success);
assertNotNull(listener.file);
assertEquals(exportFile, listener.file);
assertEquals(NUM_CARDS, db.getLoyaltyCardCount());