diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bce7c0642..882e1fb13 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -125,6 +125,16 @@
android:resource="@xml/modifications_sync_adapter" />
+
+
+
+
{
+ interface Callback {
+ void onResult(Result result);
+ }
- private String fileSHA1;
- private Context context;
+ public enum Result {
+ NO_DUPLICATE,
+ DUPLICATE_PROCEED,
+ DUPLICATE_CANCELLED
+ }
- public ExistingFileAsync(String fileSHA1, Context context) {
- this.fileSHA1 = fileSHA1;
+ private final String fileSha1;
+ private final Context context;
+ private final Callback callback;
+
+ public ExistingFileAsync(String fileSha1, Context context, Callback callback) {
+ this.fileSha1 = fileSha1;
this.context = context;
+ this.callback = callback;
}
@Override
@@ -46,7 +57,7 @@ public class ExistingFileAsync extends AsyncTask {
result = api.action("query")
.param("format", "xml")
.param("list", "allimages")
- .param("aisha1", fileSHA1)
+ .param("aisha1", fileSha1)
.get();
Timber.d("Searching Commons API for existing file: %s", result);
} catch (IOException e) {
@@ -79,17 +90,20 @@ public class ExistingFileAsync extends AsyncTask {
//Go back to ContributionsActivity
Intent intent = new Intent(context, ContributionsActivity.class);
context.startActivity(intent);
+ callback.onResult(Result.DUPLICATE_CANCELLED);
}
});
builder.setNegativeButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
- //No need to do anything, user remains on upload screen
+ callback.onResult(Result.DUPLICATE_PROCEED);
}
});
AlertDialog dialog = builder.create();
dialog.show();
+ } else {
+ callback.onResult(Result.NO_DUPLICATE);
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java
index 206e44f81..a211a38bf 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java
@@ -9,6 +9,16 @@ import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+
+import timber.log.Timber;
public class FileUtils {
@@ -23,6 +33,7 @@ public class FileUtils {
*/
// Can be safely suppressed, checks for isKitKat before running isDocumentUri
@SuppressLint("NewApi")
+ @Nullable
public static String getPath(Context context, Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
@@ -93,6 +104,7 @@ public class FileUtils {
* @param selectionArgs (Optional) Selection arguments used in the query.
* @return The value of the _data column, which is typically a file path.
*/
+ @Nullable
public static String getDataColumn(Context context, Uri uri, String selection,
String[] selectionArgs) {
@@ -108,6 +120,8 @@ public class FileUtils {
final int column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
+ } catch (IllegalArgumentException e) {
+ Timber.d(e);
} finally {
if (cursor != null)
cursor.close();
@@ -119,7 +133,7 @@ public class FileUtils {
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
- public static boolean isExternalStorageDocument(Uri uri) {
+ private static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
@@ -127,7 +141,7 @@ public class FileUtils {
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
- public static boolean isDownloadsDocument(Uri uri) {
+ private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
@@ -135,7 +149,39 @@ public class FileUtils {
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
- public static boolean isMediaDocument(Uri uri) {
+ private static boolean isMediaDocument(Uri uri) {
return "com.android.providers.media.documents".equals(uri.getAuthority());
}
+
+ /**
+ * Check if the URI is owned by the current app.
+ */
+ public static boolean isSelfOwned(Context context, Uri uri) {
+ return uri.getAuthority().equals(context.getPackageName() + ".provider");
+ }
+
+ /**
+ * Copy content from source file to destination file.
+ * @param source stream copied from
+ * @param destination stream copied to
+ * @throws IOException thrown when failing to read source or opening destination file
+ */
+ public static void copy(@NonNull FileInputStream source, @NonNull FileOutputStream destination)
+ throws IOException {
+ FileChannel sourceChannel = source.getChannel();
+ FileChannel destinationChannel = destination.getChannel();
+ sourceChannel.transferTo(0, sourceChannel.size(), destinationChannel);
+ }
+
+ /**
+ * Copy content from source file to destination file.
+ * @param source file descriptor copied from
+ * @param destination file path copied to
+ * @throws IOException thrown when failing to read source or opening destination file
+ */
+ public static void copy(@NonNull FileDescriptor source, @NonNull String destination)
+ throws IOException {
+ copy(new FileInputStream(source), new FileOutputStream(destination));
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java b/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
index ab006bd94..bf6675caa 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/GPSExtractor.java
@@ -7,10 +7,14 @@ import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.media.ExifInterface;
+import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
+import java.io.FileDescriptor;
import java.io.IOException;
import fr.free.nrw.commons.CommonsApplication;
@@ -23,17 +27,38 @@ import timber.log.Timber;
*/
public class GPSExtractor {
- private String filePath;
- private double decLatitude, decLongitude;
+ private ExifInterface exif;
+ private double decLatitude;
+ private double decLongitude;
private Double currentLatitude = null;
private Double currentLongitude = null;
public boolean imageCoordsExists;
private MyLocationListener myLocationListener;
private LocationManager locationManager;
+ /**
+ * Construct from the file descriptor of the image (only for API 24 or newer).
+ * @param fileDescriptor the file descriptor of the image
+ */
+ @RequiresApi(24)
+ public GPSExtractor(@NonNull FileDescriptor fileDescriptor) {
+ try {
+ exif = new ExifInterface(fileDescriptor);
+ } catch (IOException | IllegalArgumentException e) {
+ Timber.w(e);
+ }
+ }
- public GPSExtractor(String filePath) {
- this.filePath = filePath;
+ /**
+ * Construct from the file path of the image.
+ * @param path file path of the image
+ */
+ public GPSExtractor(@NonNull String path) {
+ try {
+ exif = new ExifInterface(path);
+ } catch (IOException | IllegalArgumentException e) {
+ Timber.w(e);
+ }
}
/**
@@ -86,45 +111,37 @@ public class GPSExtractor {
*/
@Nullable
public String getCoords(boolean useGPS) {
-
- ExifInterface exif;
String latitude = "";
String longitude = "";
String latitude_ref = "";
String longitude_ref = "";
String decimalCoords = "";
- try {
- exif = new ExifInterface(filePath);
- } catch (IOException e) {
- Timber.w(e);
- return null;
- } catch (IllegalArgumentException e) {
- Timber.w(e);
- return null;
- }
-
//If image has no EXIF data and user has enabled GPS setting, get user's location
- if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null && useGPS) {
- registerLocationManager();
+ if (exif == null || exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
+ if (useGPS) {
+ registerLocationManager();
- imageCoordsExists = false;
- Timber.d("EXIF data has no location info");
+ imageCoordsExists = false;
+ Timber.d("EXIF data has no location info");
- //Check what user's preference is for automatic location detection
- boolean gpsPrefEnabled = gpsPreferenceEnabled();
+ //Check what user's preference is for automatic location detection
+ boolean gpsPrefEnabled = gpsPreferenceEnabled();
- //Check that currentLatitude and currentLongitude have been explicitly set by MyLocationListener and do not default to (0.0,0.0)
- if (gpsPrefEnabled && currentLatitude != null && currentLongitude != null) {
- Timber.d("Current location values: Lat = %f Long = %f",
- currentLatitude, currentLongitude);
- return String.valueOf(currentLatitude) + "|" + String.valueOf(currentLongitude);
+ //Check that currentLatitude and currentLongitude have been
+ // explicitly set by MyLocationListener
+ // and do not default to (0.0,0.0)
+ if (gpsPrefEnabled && currentLatitude != null && currentLongitude != null) {
+ Timber.d("Current location values: Lat = %f Long = %f",
+ currentLatitude, currentLongitude);
+ return String.valueOf(currentLatitude) + "|" + String.valueOf(currentLongitude);
+ } else {
+ // No coords found
+ return null;
+ }
} else {
- // No coords found
return null;
}
- } else if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
- return null;
} else {
//If image has EXIF data, extract image coords
imageCoordsExists = true;
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
index 5d9ec5fa1..3be35832b 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/ShareActivity.java
@@ -8,6 +8,9 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
import android.support.design.widget.Snackbar;
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.app.ActivityCompat;
@@ -20,9 +23,11 @@ import android.widget.Toast;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
+import java.util.Date;
import java.util.List;
import butterknife.ButterKnife;
@@ -48,13 +53,16 @@ public class ShareActivity
implements SingleUploadFragment.OnUploadActionInitiated,
CategorizationFragment.OnCategoriesSaveHandler {
+ private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1;
+ private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2;
+ private static final int REQUEST_PERM_ON_CREATE_STORAGE_AND_LOCATION = 3;
+ private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
private CategorizationFragment categorizationFragment;
private CommonsApplication app;
private String source;
private String mimeType;
- private String mediaUriString;
private Uri mediaUri;
private Contribution contribution;
@@ -68,15 +76,16 @@ public class ShareActivity
private String decimalCoords;
private boolean useNewPermissions = false;
- private boolean storagePermission = false;
- private boolean locationPermission = false;
+ private boolean storagePermitted = false;
+ private boolean locationPermitted = false;
private String title;
private String description;
private Snackbar snackbar;
+ private boolean duplicateCheckPassed = false;
/**
- * Called when user taps the submit button
+ * Called when user taps the submit button.
*/
@Override
public void uploadActionInitiated(String title, String description) {
@@ -85,10 +94,11 @@ public class ShareActivity
this.description = description;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- //Check for Storage permission that is required for upload. Do not allow user to proceed without permission, otherwise will crash
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- //See http://stackoverflow.com/questions/33169455/onrequestpermissionsresult-not-being-called-in-dialog-fragment
- requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 4);
+ // Check for Storage permission that is required for upload.
+ // Do not allow user to proceed without permission, otherwise will crash
+ if (needsToRequestStoragePermission()) {
+ requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+ REQUEST_PERM_ON_SUBMIT_STORAGE);
} else {
uploadBegins();
}
@@ -97,12 +107,18 @@ public class ShareActivity
}
}
+ @RequiresApi(16)
+ private boolean needsToRequestStoragePermission() {
+ // We need to ask storage permission when
+ // the file is not owned by this app, (e.g. shared from the Gallery)
+ // and permission is not obtained.
+ return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri)
+ && (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED);
+ }
+
private void uploadBegins() {
- if (locationPermission) {
- getFileMetadata(true);
- } else {
- getFileMetadata(false);
- }
+ getFileMetadata(locationPermitted);
Toast startingToast = Toast.makeText(
CommonsApplication.getInstance(),
@@ -232,9 +248,9 @@ public class ShareActivity
//Receive intent from ContributionController.java when user selects picture to upload
Intent intent = getIntent();
- if(intent.getAction().equals(Intent.ACTION_SEND)) {
+ if (intent.getAction().equals(Intent.ACTION_SEND)) {
mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
- if(intent.hasExtra(UploadService.EXTRA_SOURCE)) {
+ if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
} else {
source = Contribution.SOURCE_EXTERNAL;
@@ -243,129 +259,103 @@ public class ShareActivity
}
if (mediaUri != null) {
- mediaUriString = mediaUri.toString();
- backgroundImageView.setImageURI(mediaUriString);
-
- //Test SHA1 of image to see if it matches SHA1 of a file on Commons
- try {
- InputStream inputStream = getContentResolver().openInputStream(mediaUri);
- Timber.d("Input stream created from %s", mediaUriString);
- String fileSHA1 = Utils.getSHA1(inputStream);
- Timber.d("File SHA1 is: %s", fileSHA1);
-
- ExistingFileAsync fileAsyncTask = new ExistingFileAsync(fileSHA1, this);
- fileAsyncTask.execute();
-
- } catch (IOException e) {
- Timber.d(e, "IO Exception: ");
- }
+ backgroundImageView.setImageURI(mediaUri);
}
- if(savedInstanceState != null) {
+ if (savedInstanceState != null) {
contribution = savedInstanceState.getParcelable("contribution");
}
requestAuthToken();
- Timber.d("Uri: %s", mediaUriString);
+ Timber.d("Uri: %s", mediaUri.toString());
Timber.d("Ext storage dir: %s", Environment.getExternalStorageDirectory());
+ useNewPermissions = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
useNewPermissions = true;
- if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
- storagePermission = true;
+
+ if (!needsToRequestStoragePermission()) {
+ storagePermitted = true;
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
- locationPermission = true;
+ locationPermitted = true;
}
}
// Check storage permissions if marshmallow or newer
- if (useNewPermissions && (!storagePermission || !locationPermission)) {
- if (!storagePermission && !locationPermission) {
- String permissionRationales = getResources().getString(R.string.storage_permission_rationale) + "\n" + getResources().getString(R.string.location_permission_rationale);
- snackbar = Snackbar.make(findViewById(android.R.id.content), permissionRationales,
- Snackbar.LENGTH_INDEFINITE)
- .setAction(R.string.ok, new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- ActivityCompat.requestPermissions(ShareActivity.this,
- new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_FINE_LOCATION}, 3);
- }
- });
- snackbar.show();
+ if (useNewPermissions && (!storagePermitted || !locationPermitted)) {
+ if (!storagePermitted && !locationPermitted) {
+ String permissionRationales =
+ getResources().getString(R.string.storage_permission_rationale) + "\n"
+ + getResources().getString(R.string.location_permission_rationale);
+ snackbar = requestPermissionUsingSnackBar(
+ permissionRationales,
+ new String[]{
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.ACCESS_FINE_LOCATION},
+ REQUEST_PERM_ON_CREATE_STORAGE_AND_LOCATION);
View snackbarView = snackbar.getView();
TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
textView.setMaxLines(3);
- } else if (!storagePermission) {
- Snackbar.make(findViewById(android.R.id.content), R.string.storage_permission_rationale,
- Snackbar.LENGTH_INDEFINITE)
- .setAction(R.string.ok, new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- ActivityCompat.requestPermissions(ShareActivity.this,
- new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
- }
- }).show();
- } else if (!locationPermission) {
- Snackbar.make(findViewById(android.R.id.content), R.string.location_permission_rationale,
- Snackbar.LENGTH_INDEFINITE)
- .setAction(R.string.ok, new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- ActivityCompat.requestPermissions(ShareActivity.this,
- new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 2);
- }
- }).show();
+ } else if (!storagePermitted) {
+ requestPermissionUsingSnackBar(
+ getString(R.string.storage_permission_rationale),
+ new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+ REQUEST_PERM_ON_CREATE_STORAGE);
+ } else if (!locationPermitted) {
+ requestPermissionUsingSnackBar(
+ getString(R.string.location_permission_rationale),
+ new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
+ REQUEST_PERM_ON_CREATE_LOCATION);
}
- } else if (useNewPermissions && storagePermission && !locationPermission) {
- getFileMetadata(true);
- } else if(!useNewPermissions || (storagePermission && locationPermission)) {
- getFileMetadata(true);
}
-
+ performPreuploadProcessingOfFile();
}
@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
switch (requestCode) {
- // 1 = Storage (from snackbar)
- case 1: {
- if (grantResults.length > 0
+ case REQUEST_PERM_ON_CREATE_STORAGE: {
+ if (grantResults.length >= 1
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- getFileMetadata(true);
+ backgroundImageView.setImageURI(mediaUri);
+ storagePermitted = true;
+ performPreuploadProcessingOfFile();
}
return;
}
- // 2 = Location
- case 2: {
- if (grantResults.length > 0
+ case REQUEST_PERM_ON_CREATE_LOCATION: {
+ if (grantResults.length >= 1
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- getFileMetadata(false);
+ locationPermitted = true;
+ performPreuploadProcessingOfFile();
}
return;
}
- // 3 = Storage + Location
- case 3: {
- if (grantResults.length > 1
+ case REQUEST_PERM_ON_CREATE_STORAGE_AND_LOCATION: {
+ if (grantResults.length >= 2
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- getFileMetadata(true);
+ backgroundImageView.setImageURI(mediaUri);
+ storagePermitted = true;
+ performPreuploadProcessingOfFile();
}
- if (grantResults.length > 1
+ if (grantResults.length >= 2
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
- getFileMetadata(false);
+ locationPermitted = true;
+ performPreuploadProcessingOfFile();
}
return;
}
- // 4 = Storage (from submit button) - this needs to be separate from (1) because only the
+ // Storage (from submit button) - this needs to be separate from (1) because only the
// submit button should bring user to next screen
- case 4: {
- if (grantResults.length > 0
+ case REQUEST_PERM_ON_SUBMIT_STORAGE: {
+ if (grantResults.length >= 1
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//It is OK to call this at both (1) and (4) because if perm had been granted at
//snackbar, user should not be prompted at submit button
- getFileMetadata(true);
+ performPreuploadProcessingOfFile();
//Uploading only begins if storage permission granted from arrow icon
uploadBegins();
@@ -376,22 +366,111 @@ public class ShareActivity
}
}
+ private void performPreuploadProcessingOfFile() {
+ if (!useNewPermissions || storagePermitted) {
+ if (!duplicateCheckPassed) {
+ //Test SHA1 of image to see if it matches SHA1 of a file on Commons
+ try {
+ InputStream inputStream = getContentResolver().openInputStream(mediaUri);
+ Timber.d("Input stream created from %s", mediaUri.toString());
+ String fileSHA1 = Utils.getSHA1(inputStream);
+ Timber.d("File SHA1 is: %s", fileSHA1);
+
+ ExistingFileAsync fileAsyncTask =
+ new ExistingFileAsync(fileSHA1, this, new ExistingFileAsync.Callback() {
+ @Override
+ public void onResult(ExistingFileAsync.Result result) {
+ Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
+ duplicateCheckPassed =
+ result == ExistingFileAsync.Result.DUPLICATE_PROCEED
+ || result == ExistingFileAsync.Result.NO_DUPLICATE;
+ }
+ });
+ fileAsyncTask.execute();
+ } catch (IOException e) {
+ Timber.d(e, "IO Exception: ");
+ }
+ }
+
+ getFileMetadata(locationPermitted);
+ } else {
+ Timber.w("not ready for preprocessing: useNewPermissions=%s storage=%s location=%s",
+ useNewPermissions, storagePermitted, locationPermitted);
+ }
+ }
+
+ private Snackbar requestPermissionUsingSnackBar(
+ String rationale, final String[] perms, final int code) {
+ Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), rationale,
+ Snackbar.LENGTH_INDEFINITE)
+ .setAction(R.string.ok, new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ ActivityCompat.requestPermissions(ShareActivity.this,
+ perms, code);
+ }
+ });
+ snackbar.show();
+ return snackbar;
+ }
+
+ @Nullable
+ private String getPathOfMediaOrCopy() {
+ String filePath = FileUtils.getPath(getApplicationContext(), mediaUri);
+ Timber.d("Filepath: " + filePath);
+ if (filePath == null) {
+ // in older devices getPath() may fail depending on the source URI
+ // creating and using a copy of the file seems to work instead.
+ // TODO: there might be a more proper solution than this
+ String copyPath = getApplicationContext().getCacheDir().getAbsolutePath()
+ + "/" + new Date().getTime() + ".jpg";
+ try {
+ ParcelFileDescriptor descriptor
+ = getContentResolver().openFileDescriptor(mediaUri, "r");
+ if (descriptor != null) {
+ FileUtils.copy(
+ descriptor.getFileDescriptor(),
+ copyPath);
+ Timber.d("Filepath (copied): %s", copyPath);
+ return copyPath;
+ }
+ } catch (IOException e) {
+ Timber.w(e, "Error in file " + copyPath);
+ return null;
+ }
+ }
+ return filePath;
+ }
+
/**
* Gets coordinates for category suggestions, either from EXIF data or user location
- * @param gpsEnabled
+ * @param gpsEnabled if true use GPS
*/
- public void getFileMetadata(boolean gpsEnabled) {
- String filePath = FileUtils.getPath(getApplicationContext(), mediaUri);
- Timber.d("Filepath: %s", filePath);
+ private void getFileMetadata(boolean gpsEnabled) {
Timber.d("Calling GPSExtractor");
- if(imageObj == null) {
- imageObj = new GPSExtractor(filePath);
- }
+ try {
+ if (imageObj == null) {
+ ParcelFileDescriptor descriptor
+ = getContentResolver().openFileDescriptor(mediaUri, "r");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (descriptor != null) {
+ imageObj = new GPSExtractor(descriptor.getFileDescriptor());
+ }
+ } else {
+ String filePath = getPathOfMediaOrCopy();
+ if (filePath != null) {
+ imageObj = new GPSExtractor(filePath);
+ }
+ }
+ }
- if (filePath != null && !filePath.equals("")) {
- // Gets image coords from exif data or user location
- decimalCoords = imageObj.getCoords(gpsEnabled);
- useImageCoords();
+ if (imageObj != null) {
+ // Gets image coords from exif data or user location
+ decimalCoords = imageObj.getCoords(gpsEnabled);
+ useImageCoords();
+ }
+ } catch (FileNotFoundException e) {
+ Timber.w("File not found: " + mediaUri, e);
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java
index 832062ed5..716a492f9 100644
--- a/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java
+++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadController.java
@@ -113,35 +113,36 @@ public class UploadController {
}
contribution.setDataLength(length);
}
- } catch(IOException e) {
+ } catch (IOException e) {
Timber.e(e, "IO Exception: ");
- } catch(NullPointerException e) {
+ } catch (NullPointerException e) {
Timber.e(e, "Null Pointer Exception: ");
- } catch(SecurityException e) {
+ } catch (SecurityException e) {
Timber.e(e, "Security Exception: ");
}
String mimeType = (String)contribution.getTag("mimeType");
Boolean imagePrefix = false;
- if(mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
+ if (mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
mimeType = app.getContentResolver().getType(contribution.getLocalUri());
}
- if(mimeType != null) {
+ if (mimeType != null) {
contribution.setTag("mimeType", mimeType);
imagePrefix = mimeType.startsWith("image/");
Timber.d("MimeType is: %s", mimeType);
}
- if(imagePrefix && contribution.getDateCreated() == null) {
+ if (imagePrefix && contribution.getDateCreated() == null) {
+ Timber.d("local uri " + contribution.getLocalUri());
Cursor cursor = app.getContentResolver().query(contribution.getLocalUri(),
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
- if(cursor != null && cursor.getCount() != 0) {
+ if (cursor != null && cursor.getCount() != 0 && cursor.getColumnCount() != 0) {
cursor.moveToFirst();
Date dateCreated = new Date(cursor.getLong(0));
Date epochStart = new Date(0);
- if(dateCreated.equals(epochStart) || dateCreated.before(epochStart)) {
+ if (dateCreated.equals(epochStart) || dateCreated.before(epochStart)) {
// If date is incorrect (1st second of unix time) then set it to the current date
dateCreated = new Date();
}
diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml
new file mode 100644
index 000000000..aa6993d43
--- /dev/null
+++ b/app/src/main/res/xml/provider_paths.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/fr/free/nrw/commons/FileUtilsTest.java b/app/src/test/java/fr/free/nrw/commons/FileUtilsTest.java
new file mode 100644
index 000000000..c6febc1e4
--- /dev/null
+++ b/app/src/test/java/fr/free/nrw/commons/FileUtilsTest.java
@@ -0,0 +1,40 @@
+package fr.free.nrw.commons;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import fr.free.nrw.commons.upload.FileUtils;
+
+import static org.hamcrest.CoreMatchers.is;
+
+public class FileUtilsTest {
+ @Test public void copiedFileIsIdenticalToSource() throws IOException {
+ File source = File.createTempFile("temp", "");
+ File dest = File.createTempFile("temp", "");
+ writeToFile(source, "Hello, World");
+ FileUtils.copy(new FileInputStream(source), new FileOutputStream(dest));
+ Assert.assertThat(getString(dest), is(getString(source)));
+ }
+
+ private static void writeToFile(File file, String s) throws IOException {
+ BufferedOutputStream buf = new BufferedOutputStream(new FileOutputStream(file));
+ buf.write(s.getBytes());
+ buf.close();
+ }
+
+ private static String getString(File file) throws IOException {
+ int size = (int) file.length();
+ byte[] bytes = new byte[size];
+ BufferedInputStream buf = new BufferedInputStream(new FileInputStream(file));
+ buf.read(bytes, 0, bytes.length);
+ buf.close();
+ return new String(bytes);
+ }
+}