Share file with camera using cache and FileProvider

* Use cache instead of external storage to share file with camera
* Execute ExistingFileAsync after permission is granted
This commit is contained in:
Yusuke Matsubara 2017-06-10 16:47:18 +09:00
parent 9e0792f1e2
commit 9c69539276
9 changed files with 333 additions and 177 deletions

View file

@ -125,6 +125,16 @@
android:resource="@xml/modifications_sync_adapter" />
</service>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
<provider
android:name=".contributions.ContributionsContentProvider"
android:label="@string/provider_contributions"

View file

@ -3,10 +3,12 @@ package fr.free.nrw.commons.contributions;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.app.Fragment;
import android.support.v4.content.FileProvider;
import java.io.File;
import java.io.IOException;
@ -29,34 +31,24 @@ public class ContributionController {
}
// See http://stackoverflow.com/a/5054673/17865 for why this is done
private Uri lastGeneratedCaptureURI;
private Uri lastGeneratedCaptureUri;
private Uri reGenerateImageCaptureURI() {
String storageState = Environment.getExternalStorageState();
if(storageState.equals(Environment.MEDIA_MOUNTED)) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg";
File _photoFile = new File(path);
try {
if(!_photoFile.exists()) {
_photoFile.getParentFile().mkdirs();
_photoFile.createNewFile();
}
} catch (IOException e) {
Timber.e(e, "Could not create file: %s", path);
}
return Uri.fromFile(_photoFile);
} else {
throw new RuntimeException("No external storage found!");
}
private Uri reGenerateImageCaptureUriInCache() {
File photoFile = new File(fragment.getContext().getCacheDir() + "/images",
new Date().getTime() + ".jpg");
photoFile.getParentFile().mkdirs();
return FileProvider.getUriForFile(
fragment.getContext(),
fragment.getActivity().getApplicationContext().getPackageName() + ".provider",
photoFile);
}
public void startCameraCapture() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
lastGeneratedCaptureURI = reGenerateImageCaptureURI();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureURI);
lastGeneratedCaptureUri = reGenerateImageCaptureUriInCache();
takePictureIntent.setFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureUri);
fragment.startActivityForResult(takePictureIntent, SELECT_FROM_CAMERA);
}
@ -80,7 +72,7 @@ public class ContributionController {
break;
case SELECT_FROM_CAMERA:
shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type
shareIntent.putExtra(Intent.EXTRA_STREAM, lastGeneratedCaptureURI);
shareIntent.putExtra(Intent.EXTRA_STREAM, lastGeneratedCaptureUri);
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA);
break;
}
@ -93,12 +85,12 @@ public class ContributionController {
}
public void saveState(Bundle outState) {
outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureURI);
outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureUri);
}
public void loadState(Bundle savedInstanceState) {
if(savedInstanceState != null) {
lastGeneratedCaptureURI = savedInstanceState.getParcelable("lastGeneratedCaptureURI");
lastGeneratedCaptureUri = savedInstanceState.getParcelable("lastGeneratedCaptureURI");
}
}

View file

@ -197,7 +197,7 @@ public class MediaDetailFragment extends Fragment {
extractor.fetch();
return Boolean.TRUE;
} catch (IOException e) {
e.printStackTrace();
Timber.d(e);
}
return Boolean.FALSE;
}

View file

@ -6,13 +6,13 @@ import android.content.Intent;
import android.os.AsyncTask;
import android.support.v7.app.AlertDialog;
import fr.free.nrw.commons.MWApi;
import org.mediawiki.api.ApiResult;
import java.io.IOException;
import java.util.ArrayList;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.MWApi;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsActivity;
import timber.log.Timber;
@ -22,13 +22,22 @@ import timber.log.Timber;
* Displays a warning to the user if the file already exists on Commons
*/
public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
interface Callback {
void onResult(Result result);
}
public enum Result {
NO_DUPLICATE,
DUPLICATE_PROCEED,
DUPLICATE_CANCELLED
}
private final String fileSHA1;
private final Context context;
private final Callback callback;
private String fileSHA1;
private Context context;
public ExistingFileAsync(String fileSHA1, Context context) {
public ExistingFileAsync(String fileSHA1, Context context, Callback callback) {
this.fileSHA1 = fileSHA1;
this.context = context;
this.callback = callback;
}
@Override
@ -79,17 +88,20 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
//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);
}
}
}

View file

@ -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,37 @@ 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 source_ = source.getChannel();
FileChannel dest_ = destination.getChannel();
source_.transferTo(0, source_.size(), dest_);
}
/**
* 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));
}
}

View file

@ -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,26 +111,15 @@ 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) {
if (exif == null || exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
if (useGPS) {
registerLocationManager();
imageCoordsExists = false;
@ -114,7 +128,9 @@ public class GPSExtractor {
//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)
//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);
@ -123,8 +139,9 @@ public class GPSExtractor {
// No coords found
return null;
}
} else if (exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
} else {
return null;
}
} else {
//If image has EXIF data, extract image coords
imageCoordsExists = true;

View file

@ -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,13 +107,19 @@ public class ShareActivity
}
}
private void uploadBegins() {
if (locationPermission) {
getFileMetadata(true);
} else {
getFileMetadata(false);
@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() {
getFileMetadata(locationPermitted);
Toast startingToast = Toast.makeText(
CommonsApplication.getInstance(),
R.string.uploading_started,
@ -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,101 @@ 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);
} 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);
}
}).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 (useNewPermissions && storagePermission && !locationPermission) {
getFileMetadata(true);
} else if(!useNewPermissions || (storagePermission && locationPermission)) {
getFileMetadata(true);
}
preuploadProcessingOfFile();
}
@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;
preuploadProcessingOfFile();
}
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;
preuploadProcessingOfFile();
}
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;
preuploadProcessingOfFile();
}
if (grantResults.length > 1
if (grantResults.length >= 2
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
getFileMetadata(false);
locationPermitted = true;
preuploadProcessingOfFile();
}
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);
preuploadProcessingOfFile();
//Uploading only begins if storage permission granted from arrow icon
uploadBegins();
@ -376,23 +364,111 @@ public class ShareActivity
}
}
/**
* Gets coordinates for category suggestions, either from EXIF data or user location
* @param gpsEnabled
*/
public void getFileMetadata(boolean gpsEnabled) {
String filePath = FileUtils.getPath(getApplicationContext(), mediaUri);
Timber.d("Filepath: %s", filePath);
Timber.d("Calling GPSExtractor");
if(imageObj == null) {
imageObj = new GPSExtractor(filePath);
private void preuploadProcessingOfFile() {
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: ");
}
}
if (filePath != null && !filePath.equals("")) {
getFileMetadata(locationPermitted);
} else {
Timber.w("not ready for preprocess: 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 if true use GPS
*/
private void getFileMetadata(boolean gpsEnabled) {
Timber.d("Calling GPSExtractor");
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 (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);
}
}
/**

View file

@ -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();
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="images" path="images/" />
</paths>