Skip to content

Commit 41f8a57

Browse files
authored
Merge pull request #128 from brarcher/content-provider
Support content providers for import and exports
2 parents 1978f61 + b038fc0 commit 41f8a57

13 files changed

+218
-113
lines changed

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ android {
1919
}
2020
buildTypes {
2121
release {
22-
minifyEnabled true
22+
minifyEnabled false
2323
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
2424
}
2525
debug {
26-
minifyEnabled true
26+
minifyEnabled false
2727
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
2828
}
2929
}

app/config/findbugs/exclude.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,8 @@
77
<Class name="~.*Manifest\$.*"/>
88
</Match>
99

10+
<Match>
11+
<Bug pattern="OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE" />
12+
</Match>
13+
1014
</FindBugsFilter>

app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
<uses-feature android:required="true" android:name="android.hardware.camera"/>
99

1010
<application
11-
android:name="protect.budgetwatch.BudgetWatchApplication"
1211
android:allowBackup="false"
1312
android:icon="@mipmap/ic_launcher"
1413
android:label="@string/app_name"
@@ -74,6 +73,15 @@
7473
</intent-filter>
7574
<meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_provider"/>
7675
</receiver>
76+
<provider
77+
android:name="android.support.v4.content.FileProvider"
78+
android:grantUriPermissions="true"
79+
android:exported="false"
80+
android:authorities="${applicationId}">
81+
<meta-data
82+
android:name="android.support.FILE_PROVIDER_PATHS"
83+
android:resource="@xml/file_provider_paths"/>
84+
</provider>
7785
</application>
7886

7987
</manifest>

app/src/main/java/protect/budgetwatch/BudgetWatchApplication.java

Lines changed: 0 additions & 29 deletions
This file was deleted.

app/src/main/java/protect/budgetwatch/DataFormat.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,31 @@
22

33
public enum DataFormat
44
{
5-
CSV,
6-
JSON,
7-
ZIP,
5+
CSV("text/csv"),
6+
JSON("application/json"),
7+
ZIP("application/zip"),
88
;
99

10+
private final String mimetype;
11+
12+
DataFormat(String mimetype)
13+
{
14+
this.mimetype = mimetype;
15+
}
16+
1017
/**
1118
* @return the file extension name for this data format.
1219
*/
1320
public String extension()
1421
{
1522
return this.name().toLowerCase();
1623
}
24+
25+
/**
26+
* @return the mime type for this data format.
27+
*/
28+
public String mimetype()
29+
{
30+
return mimetype;
31+
}
1732
}

app/src/main/java/protect/budgetwatch/ImportExportActivity.java

Lines changed: 110 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@
77
import android.content.Intent;
88
import android.content.pm.PackageManager;
99
import android.content.pm.ResolveInfo;
10+
import android.database.Cursor;
1011
import android.net.Uri;
1112
import android.os.AsyncTask;
1213
import android.os.Build;
1314
import android.os.Bundle;
1415
import android.os.Environment;
16+
import android.provider.OpenableColumns;
1517
import android.support.annotation.NonNull;
1618
import android.support.v4.app.ActivityCompat;
1719
import android.support.v4.content.ContextCompat;
20+
import android.support.v4.content.FileProvider;
1821
import android.support.v7.app.ActionBar;
1922
import android.support.v7.app.AlertDialog;
2023
import android.support.v7.app.AppCompatActivity;
@@ -30,6 +33,10 @@
3033
import com.google.common.collect.ImmutableMap;
3134

3235
import java.io.File;
36+
import java.io.FileInputStream;
37+
import java.io.FileNotFoundException;
38+
import java.io.IOException;
39+
import java.io.InputStream;
3340
import java.util.ArrayList;
3441
import java.util.List;
3542
import java.util.Map;
@@ -162,8 +169,18 @@ public void onClick(View v)
162169
DataFormat format = getSelectedFormat(R.id.importFileFormatSpinner);
163170
File importFile = new File(sdcardDir, exportFilename + "." + format.extension());
164171

165-
Log.d(TAG, "Starting import from fixed location: " + importFile.getAbsolutePath());
166-
startImport(importFile, format);
172+
Uri uri = Uri.fromFile(importFile);
173+
try
174+
{
175+
FileInputStream stream = new FileInputStream(importFile);
176+
Log.d(TAG, "Starting import from fixed location: " + importFile.getAbsolutePath());
177+
startImport(format, stream, uri);
178+
}
179+
catch(FileNotFoundException e)
180+
{
181+
Log.e(TAG, "Could not import file " + importFile.getAbsolutePath(), e);
182+
onImportComplete(false, uri);
183+
}
167184
}
168185
});
169186
}
@@ -176,26 +193,32 @@ private DataFormat getSelectedFormat(int id)
176193
return format;
177194
}
178195

179-
private void startImport(File target, DataFormat format)
196+
private void startImport(DataFormat format, final InputStream target, final Uri targetUri)
180197
{
181198
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
182199
{
183200
@Override
184-
public void onTaskComplete(boolean success, File file)
201+
public void onTaskComplete(boolean success)
185202
{
186-
onImportComplete(success, file);
203+
onImportComplete(success, targetUri);
187204
}
188205
};
189206

190-
if(format == null)
207+
String filename = fileNameFromUri(targetUri);
208+
if(filename == null)
209+
{
210+
filename = targetUri.getPath();
211+
}
212+
213+
if(format == null && filename != null)
191214
{
192215
// Attempt to guess the data format based on the extension
193-
Log.d(TAG, "Attempting to determine file type for: " + target.getName());
216+
Log.d(TAG, "Attempting to determine file type for: " + filename);
194217

195218
for(Map.Entry<String, DataFormat> item : _fileFormatMap.entrySet())
196219
{
197220
String key = item.getKey();
198-
if(target.getName().toLowerCase().endsWith(key.toLowerCase()))
221+
if(filename.toLowerCase().endsWith(key.toLowerCase()))
199222
{
200223
format = item.getValue();
201224
break;
@@ -205,34 +228,43 @@ public void onTaskComplete(boolean success, File file)
205228

206229
if(format != null)
207230
{
208-
Log.d(TAG, "Starting import of file: " + target.getName());
231+
Log.d(TAG, "Starting import of file: " + filename);
209232
importExporter = new ImportExportTask(ImportExportActivity.this,
210-
true, format, target, listener);
233+
format, target, listener);
211234
importExporter.execute();
212235
}
213236
else
214237
{
215238
// If format is still null, then we do not know what to import
216-
Log.w(TAG, "Could not import " + target.getAbsolutePath() + ", could not determine extension");
217-
onImportComplete(false, target);
239+
Log.w(TAG, "Could not import " + filename + ", could not determine extension");
240+
onImportComplete(false, targetUri);
241+
242+
try
243+
{
244+
target.close();
245+
}
246+
catch (IOException e)
247+
{
248+
Log.w(TAG, "Failed to close stream during import", e);
249+
}
218250
}
219251
}
220252

221-
private void startExport(DataFormat format)
253+
private void startExport(final DataFormat format)
222254
{
255+
final File exportFile = new File(sdcardDir, exportFilename + "." + format.extension());
256+
223257
ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener()
224258
{
225259
@Override
226-
public void onTaskComplete(boolean success, File file)
260+
public void onTaskComplete(boolean success)
227261
{
228-
onExportComplete(success, file);
262+
onExportComplete(success, exportFile, format);
229263
}
230264
};
231265

232-
File exportFile = new File(sdcardDir, exportFilename + "." + format.extension());
233-
234266
importExporter = new ImportExportTask(ImportExportActivity.this,
235-
false, format, exportFile, listener);
267+
format, exportFile, listener);
236268
importExporter.execute();
237269
}
238270

@@ -287,7 +319,34 @@ public boolean onOptionsItemSelected(MenuItem item)
287319
return super.onOptionsItemSelected(item);
288320
}
289321

290-
private void onImportComplete(boolean success, File path)
322+
private String fileNameFromUri(Uri uri)
323+
{
324+
if("file".equals(uri.getScheme()))
325+
{
326+
return uri.getPath();
327+
}
328+
329+
Cursor returnCursor =
330+
getContentResolver().query(uri, null, null, null, null);
331+
if(returnCursor == null)
332+
{
333+
return null;
334+
}
335+
336+
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
337+
if(returnCursor.moveToFirst() == false)
338+
{
339+
returnCursor.close();
340+
return null;
341+
}
342+
343+
String name = returnCursor.getString(nameIndex);
344+
returnCursor.close();
345+
346+
return name;
347+
}
348+
349+
private void onImportComplete(boolean success, Uri path)
291350
{
292351
AlertDialog.Builder builder = new AlertDialog.Builder(this);
293352

@@ -303,7 +362,15 @@ private void onImportComplete(boolean success, File path)
303362
int messageId = success ? R.string.importedFrom : R.string.importFailed;
304363

305364
final String template = getResources().getString(messageId);
306-
final String message = String.format(template, path.getAbsolutePath());
365+
366+
// Get the filename of the file being imported
367+
String filename = fileNameFromUri(path);
368+
if(filename == null)
369+
{
370+
filename = "(unknown)";
371+
}
372+
373+
final String message = String.format(template, filename);
307374
builder.setMessage(message);
308375
builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
309376
{
@@ -317,7 +384,7 @@ public void onClick(DialogInterface dialog, int which)
317384
builder.create().show();
318385
}
319386

320-
private void onExportComplete(boolean success, final File path)
387+
private void onExportComplete(boolean success, final File path, final DataFormat format)
321388
{
322389
AlertDialog.Builder builder = new AlertDialog.Builder(this);
323390

@@ -353,10 +420,13 @@ public void onClick(DialogInterface dialog, int which)
353420
@Override
354421
public void onClick(DialogInterface dialog, int which)
355422
{
356-
Uri outputUri = Uri.fromFile(path);
423+
Uri outputUri = FileProvider.getUriForFile(ImportExportActivity.this, BuildConfig.APPLICATION_ID, path);
357424
Intent sendIntent = new Intent(Intent.ACTION_SEND);
358425
sendIntent.putExtra(Intent.EXTRA_STREAM, outputUri);
359-
sendIntent.setType("text/plain");
426+
sendIntent.setType(format.mimetype());
427+
428+
// set flag to give temporary permission to external app to use the FileProvider
429+
sendIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
360430

361431
ImportExportActivity.this.startActivity(Intent.createChooser(sendIntent,
362432
sendLabel));
@@ -411,34 +481,28 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data)
411481
{
412482
super.onActivityResult(requestCode, resultCode, data);
413483

414-
if (resultCode == RESULT_OK && requestCode == CHOOSE_EXPORT_FILE)
484+
if (resultCode != RESULT_OK || requestCode != CHOOSE_EXPORT_FILE)
415485
{
416-
String path = null;
417-
418-
Uri uri = data.getData();
419-
if(uri != null && uri.toString().startsWith("/"))
420-
{
421-
uri = Uri.parse("file://" + uri.toString());
422-
}
486+
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
487+
return;
488+
}
423489

424-
if(uri != null)
425-
{
426-
path = uri.getPath();
427-
}
490+
Uri uri = data.getData();
491+
if(uri == null)
492+
{
493+
Log.e(TAG, "Activity returned a NULL URI");
494+
return;
495+
}
428496

429-
if(path != null)
430-
{
431-
Log.e(TAG, "Starting file import with: " + uri.toString());
432-
startImport(new File(path), null);
433-
}
434-
else
435-
{
436-
Log.e(TAG, "Fail to make sense of URI returned from activity: " + (uri != null ? uri.toString() : "null"));
437-
}
497+
try
498+
{
499+
InputStream reader = getContentResolver().openInputStream(uri);
500+
Log.e(TAG, "Starting file import with: " + uri.toString());
501+
startImport(null, reader, uri);
438502
}
439-
else
503+
catch (FileNotFoundException e)
440504
{
441-
Log.w(TAG, "Failed onActivityResult(), result=" + resultCode);
505+
Log.e(TAG, "Failed to import file: " + uri.toString(), e);
442506
}
443507
}
444508
}

0 commit comments

Comments
 (0)