mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
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:
parent
9e0792f1e2
commit
9c69539276
9 changed files with 333 additions and 177 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,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);
|
||||
}
|
||||
}).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);
|
||||
}
|
||||
|
||||
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,22 +364,110 @@ public class ShareActivity
|
|||
}
|
||||
}
|
||||
|
||||
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: ");
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
4
app/src/main/res/xml/provider_paths.xml
Normal file
4
app/src/main/res/xml/provider_paths.xml
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue