mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-29 13:53:54 +01:00
Add module for file picker for camera and gallery uploads (#2375)
* Use easy image for image picker * Do not use harcoded mime type * Use uploadable file for image uploads * Add picker files in filepicker module * Remove redundant checks for file * Make usage of file extensions consistent * Add javadocs * Fix tests * Enable image upload using bookmark activity * Fix multiple uploads * Fix external image uploads * Fix chooser intents * Fix image quality checks * Segregate internal and external upload intents * Invoke all error messages from one place * Minor changes * Fix tests * Add image processing service tests
This commit is contained in:
parent
fb5a40bba5
commit
52ab39381e
39 changed files with 1553 additions and 574 deletions
|
|
@ -0,0 +1,22 @@
|
|||
package fr.free.nrw.commons.filepicker;
|
||||
|
||||
public interface Constants {
|
||||
String DEFAULT_FOLDER_NAME = "CommonsContributions";
|
||||
|
||||
interface RequestCodes {
|
||||
int FILE_PICKER_IMAGE_IDENTIFICATOR = 0b1101101100; //876
|
||||
int SOURCE_CHOOSER = 1 << 15;
|
||||
|
||||
int PICK_PICTURE_FROM_DOCUMENTS = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 11);
|
||||
int PICK_PICTURE_FROM_GALLERY = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 12);
|
||||
int TAKE_PICTURE = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 13);
|
||||
int CAPTURE_VIDEO = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 14);
|
||||
}
|
||||
|
||||
interface BundleKeys {
|
||||
String FOLDER_NAME = "fr.free.nrw.commons.folder_name";
|
||||
String ALLOW_MULTIPLE = "fr.free.nrw.commons.allow_multiple";
|
||||
String COPY_TAKEN_PHOTOS = "fr.free.nrw.commons.copy_taken_photos";
|
||||
String COPY_PICKED_IMAGES = "fr.free.nrw.commons.copy_picked_images";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package fr.free.nrw.commons.filepicker;
|
||||
|
||||
public abstract class DefaultCallback implements FilePicker.Callbacks {
|
||||
|
||||
@Override
|
||||
public void onImagePickerError(Exception e, FilePicker.ImageSource source, int type) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled(FilePicker.ImageSource source, int type) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package fr.free.nrw.commons.filepicker;
|
||||
|
||||
import android.support.v4.content.FileProvider;
|
||||
|
||||
public class ExtendedFileProvider extends FileProvider {
|
||||
|
||||
}
|
||||
550
app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
Normal file
550
app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java
Normal file
|
|
@ -0,0 +1,550 @@
|
|||
package fr.free.nrw.commons.filepicker;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Parcelable;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static fr.free.nrw.commons.filepicker.PickedFiles.singleFileList;
|
||||
|
||||
@SuppressWarnings({"unused", "FieldCanBeLocal", "ResultOfMethodCallIgnored"})
|
||||
public class FilePicker implements Constants {
|
||||
|
||||
private static final boolean SHOW_GALLERY_IN_CHOOSER = false;
|
||||
private static final String KEY_PHOTO_URI = "photo_uri";
|
||||
private static final String KEY_VIDEO_URI = "video_uri";
|
||||
private static final String KEY_LAST_CAMERA_PHOTO = "last_photo";
|
||||
private static final String KEY_LAST_CAMERA_VIDEO = "last_video";
|
||||
private static final String KEY_TYPE = "type";
|
||||
|
||||
private static Uri createCameraPictureFile(@NonNull Context context) throws IOException {
|
||||
File imagePath = PickedFiles.getCameraPicturesLocation(context);
|
||||
Uri uri = PickedFiles.getUriToFile(context, imagePath);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putString(KEY_PHOTO_URI, uri.toString());
|
||||
editor.putString(KEY_LAST_CAMERA_PHOTO, imagePath.toString());
|
||||
editor.apply();
|
||||
return uri;
|
||||
}
|
||||
|
||||
private static Uri createCameraVideoFile(@NonNull Context context) throws IOException {
|
||||
File imagePath = PickedFiles.getCameraVideoLocation(context);
|
||||
Uri uri = PickedFiles.getUriToFile(context, imagePath);
|
||||
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
|
||||
editor.putString(KEY_VIDEO_URI, uri.toString());
|
||||
editor.putString(KEY_LAST_CAMERA_VIDEO, imagePath.toString());
|
||||
editor.apply();
|
||||
return uri;
|
||||
}
|
||||
|
||||
private static Intent createDocumentsIntent(@NonNull Context context, int type) {
|
||||
storeType(context, type);
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("image/*");
|
||||
return intent;
|
||||
}
|
||||
|
||||
private static Intent createGalleryIntent(@NonNull Context context, int type) {
|
||||
storeType(context, type);
|
||||
Intent intent = plainGalleryPickerIntent();
|
||||
if (Build.VERSION.SDK_INT >= 18) {
|
||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery());
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
|
||||
private static Intent createCameraForImageIntent(@NonNull Context context, int type) {
|
||||
storeType(context, type);
|
||||
|
||||
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
try {
|
||||
Uri capturedImageUri = createCameraPictureFile(context);
|
||||
//We have to explicitly grant the write permission since Intent.setFlag works only on API Level >=20
|
||||
grantWritePermission(context, intent, capturedImageUri);
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
private static Intent createCameraForVideoIntent(@NonNull Context context, int type) {
|
||||
storeType(context, type);
|
||||
|
||||
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
|
||||
try {
|
||||
Uri capturedImageUri = createCameraVideoFile(context);
|
||||
//We have to explicitly grant the write permission since Intent.setFlag works only on API Level >=20
|
||||
grantWritePermission(context, intent, capturedImageUri);
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
private static void revokeWritePermission(@NonNull Context context, Uri uri) {
|
||||
context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
|
||||
private static void grantWritePermission(@NonNull Context context, Intent intent, Uri uri) {
|
||||
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
|
||||
for (ResolveInfo resolveInfo : resInfoList) {
|
||||
String packageName = resolveInfo.activityInfo.packageName;
|
||||
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
}
|
||||
}
|
||||
|
||||
private static Intent createChooserIntent(@NonNull Context context, @Nullable String chooserTitle, int type) throws IOException {
|
||||
return createChooserIntent(context, chooserTitle, SHOW_GALLERY_IN_CHOOSER, type);
|
||||
}
|
||||
|
||||
private static Intent createChooserIntent(@NonNull Context context, @Nullable String chooserTitle, boolean showGallery, int type) throws IOException {
|
||||
storeType(context, type);
|
||||
|
||||
Uri outputFileUri = createCameraPictureFile(context);
|
||||
List<Intent> cameraIntents = new ArrayList<>();
|
||||
Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
List<ResolveInfo> camList = packageManager.queryIntentActivities(captureIntent, 0);
|
||||
for (ResolveInfo res : camList) {
|
||||
final String packageName = res.activityInfo.packageName;
|
||||
final Intent intent = new Intent(captureIntent);
|
||||
intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
|
||||
intent.setPackage(packageName);
|
||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
|
||||
grantWritePermission(context, intent, outputFileUri);
|
||||
cameraIntents.add(intent);
|
||||
}
|
||||
Intent galleryIntent;
|
||||
|
||||
if (showGallery) {
|
||||
galleryIntent = createGalleryIntent(context, type);
|
||||
} else {
|
||||
galleryIntent = createDocumentsIntent(context, type);
|
||||
}
|
||||
|
||||
Intent chooserIntent = Intent.createChooser(galleryIntent, chooserTitle);
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[cameraIntents.size()]));
|
||||
|
||||
return chooserIntent;
|
||||
}
|
||||
|
||||
private static void storeType(@NonNull Context context, int type) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(KEY_TYPE, type).commit();
|
||||
}
|
||||
|
||||
private static int restoreType(@NonNull Context context) {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getInt(KEY_TYPE, 0);
|
||||
}
|
||||
|
||||
public static void openChooserWithDocuments(Activity activity, @Nullable String chooserTitle, int type) {
|
||||
try {
|
||||
Intent intent = createChooserIntent(activity, chooserTitle, type);
|
||||
activity.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void openChooserWithDocuments(Fragment fragment, @Nullable String chooserTitle, int type) {
|
||||
try {
|
||||
Intent intent = createChooserIntent(fragment.getActivity(), chooserTitle, type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void openChooserWithDocuments(android.app.Fragment fragment, @Nullable String chooserTitle, int type) {
|
||||
try {
|
||||
Intent intent = createChooserIntent(fragment.getActivity(), chooserTitle, type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void openChooserWithGallery(Activity activity, @Nullable String chooserTitle, int type) {
|
||||
try {
|
||||
Intent intent = createChooserIntent(activity, chooserTitle, true, type);
|
||||
activity.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void openChooserWithGallery(Fragment fragment, @Nullable String chooserTitle, int type) {
|
||||
try {
|
||||
Intent intent = createChooserIntent(fragment.getActivity(), chooserTitle, true, type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void openChooserWithGallery(android.app.Fragment fragment, @Nullable String chooserTitle, int type) {
|
||||
try {
|
||||
Intent intent = createChooserIntent(fragment.getActivity(), chooserTitle, true, type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.SOURCE_CHOOSER | RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void openDocuments(Activity activity, int type) {
|
||||
Intent intent = createDocumentsIntent(activity, type);
|
||||
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||
}
|
||||
|
||||
public static void openDocuments(Fragment fragment, int type) {
|
||||
Intent intent = createDocumentsIntent(fragment.getContext(), type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||
}
|
||||
|
||||
public static void openDocuments(android.app.Fragment fragment, int type) {
|
||||
Intent intent = createDocumentsIntent(fragment.getActivity(), type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_DOCUMENTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens default galery or a available galleries picker if there is no default
|
||||
*
|
||||
* @param type Custom type of your choice, which will be returned with the images
|
||||
*/
|
||||
public static void openGallery(Activity activity, int type) {
|
||||
Intent intent = createGalleryIntent(activity, type);
|
||||
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens default galery or a available galleries picker if there is no default
|
||||
*
|
||||
* @param type Custom type of your choice, which will be returned with the images
|
||||
*/
|
||||
public static void openGallery(Fragment fragment, int type) {
|
||||
Intent intent = createGalleryIntent(fragment.getContext(), type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens default galery or a available galleries picker if there is no default
|
||||
*
|
||||
* @param type Custom type of your choice, which will be returned with the images
|
||||
*/
|
||||
public static void openGallery(android.app.Fragment fragment, int type) {
|
||||
Intent intent = createGalleryIntent(fragment.getActivity(), type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
|
||||
}
|
||||
|
||||
public static void openCameraForImage(Activity activity, int type) {
|
||||
Intent intent = createCameraForImageIntent(activity, type);
|
||||
activity.startActivityForResult(intent, RequestCodes.TAKE_PICTURE);
|
||||
}
|
||||
|
||||
public static void openCameraForImage(Fragment fragment, int type) {
|
||||
Intent intent = createCameraForImageIntent(fragment.getActivity(), type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.TAKE_PICTURE);
|
||||
}
|
||||
|
||||
public static void openCameraForImage(android.app.Fragment fragment, int type) {
|
||||
Intent intent = createCameraForImageIntent(fragment.getActivity(), type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.TAKE_PICTURE);
|
||||
}
|
||||
|
||||
public static void openCameraForVideo(Activity activity, int type) {
|
||||
Intent intent = createCameraForVideoIntent(activity, type);
|
||||
activity.startActivityForResult(intent, RequestCodes.CAPTURE_VIDEO);
|
||||
}
|
||||
|
||||
public static void openCameraForVideo(Fragment fragment, int type) {
|
||||
Intent intent = createCameraForVideoIntent(fragment.getActivity(), type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.CAPTURE_VIDEO);
|
||||
}
|
||||
|
||||
public static void openCameraForVideo(android.app.Fragment fragment, int type) {
|
||||
Intent intent = createCameraForVideoIntent(fragment.getActivity(), type);
|
||||
fragment.startActivityForResult(intent, RequestCodes.CAPTURE_VIDEO);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static File takenCameraPicture(Context context) throws IOException, URISyntaxException {
|
||||
String lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_PHOTO, null);
|
||||
if (lastCameraPhoto != null) {
|
||||
return new File(lastCameraPhoto);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static File takenCameraVideo(Context context) throws IOException, URISyntaxException {
|
||||
String lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_VIDEO, null);
|
||||
if (lastCameraPhoto != null) {
|
||||
return new File(lastCameraPhoto);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void handleActivityResult(int requestCode, int resultCode, Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||
boolean isHandledPickedFile = (requestCode & RequestCodes.FILE_PICKER_IMAGE_IDENTIFICATOR) > 0;
|
||||
if (isHandledPickedFile) {
|
||||
requestCode &= ~RequestCodes.SOURCE_CHOOSER;
|
||||
if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY ||
|
||||
requestCode == RequestCodes.TAKE_PICTURE ||
|
||||
requestCode == RequestCodes.CAPTURE_VIDEO ||
|
||||
requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS && !isPhoto(data)) {
|
||||
onPictureReturnedFromDocuments(data, activity, callbacks);
|
||||
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY && !isPhoto(data)) {
|
||||
onPictureReturnedFromGallery(data, activity, callbacks);
|
||||
} else if (requestCode == RequestCodes.TAKE_PICTURE) {
|
||||
onPictureReturnedFromCamera(activity, callbacks);
|
||||
} else if (requestCode == RequestCodes.CAPTURE_VIDEO) {
|
||||
onVideoReturnedFromCamera(activity, callbacks);
|
||||
} else if (isPhoto(data)) {
|
||||
onPictureReturnedFromCamera(activity, callbacks);
|
||||
} else {
|
||||
onPictureReturnedFromDocuments(data, activity, callbacks);
|
||||
}
|
||||
} else {
|
||||
if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) {
|
||||
callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
|
||||
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY) {
|
||||
callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity));
|
||||
} else {
|
||||
callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<File> handleExternalImagesPicked(Intent data, Activity activity) {
|
||||
try {
|
||||
return getFilesFromGalleryPictures(data, activity);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
private static boolean isPhoto(Intent data) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
return data == null || (data.getData() == null && data.getClipData() == null);
|
||||
} else {
|
||||
return data == null || (data.getData() == null);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean willHandleActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (requestCode == RequestCodes.SOURCE_CHOOSER || requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY || requestCode == RequestCodes.TAKE_PICTURE || requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Intent plainGalleryPickerIntent() {
|
||||
return new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||
}
|
||||
|
||||
public static boolean canDeviceHandleGallery(@NonNull Context context) {
|
||||
return plainGalleryPickerIntent().resolveActivity(context.getPackageManager()) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context context
|
||||
* @return File containing lastly taken (using camera) photo. Returns null if there was no photo taken or it doesn't exist anymore.
|
||||
*/
|
||||
public static File lastlyTakenButCanceledPhoto(@NonNull Context context) {
|
||||
String filePath = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_PHOTO, null);
|
||||
if (filePath == null) return null;
|
||||
File file = new File(filePath);
|
||||
if (file.exists()) {
|
||||
return file;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static File lastlyTakenButCanceledVideo(@NonNull Context context) {
|
||||
String filePath = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_VIDEO, null);
|
||||
if (filePath == null) return null;
|
||||
File file = new File(filePath);
|
||||
if (file.exists()) {
|
||||
return file;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void onPictureReturnedFromDocuments(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||
try {
|
||||
Uri photoPath = data.getData();
|
||||
File photoFile = PickedFiles.pickedExistingPicture(activity, photoPath);
|
||||
callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
|
||||
|
||||
if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) {
|
||||
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
|
||||
}
|
||||
}
|
||||
|
||||
private static void onPictureReturnedFromGallery(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||
try {
|
||||
List<File> files = getFilesFromGalleryPictures(data, activity);
|
||||
callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<File> getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException {
|
||||
List<File> files = new ArrayList<>();
|
||||
ClipData clipData = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
clipData = data.getClipData();
|
||||
}
|
||||
if (clipData == null) {
|
||||
Uri uri = data.getData();
|
||||
File file = PickedFiles.pickedExistingPicture(activity, uri);
|
||||
files.add(file);
|
||||
} else {
|
||||
for (int i = 0; i < clipData.getItemCount(); i++) {
|
||||
Uri uri = clipData.getItemAt(i).getUri();
|
||||
File file = PickedFiles.pickedExistingPicture(activity, uri);
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) {
|
||||
PickedFiles.copyFilesInSeparateThread(activity, files);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private static void onPictureReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||
try {
|
||||
String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null);
|
||||
if (!TextUtils.isEmpty(lastImageUri)) {
|
||||
revokeWritePermission(activity, Uri.parse(lastImageUri));
|
||||
}
|
||||
|
||||
File photoFile = FilePicker.takenCameraPicture(activity);
|
||||
List<File> files = new ArrayList<>();
|
||||
files.add(photoFile);
|
||||
|
||||
if (photoFile == null) {
|
||||
Exception e = new IllegalStateException("Unable to get the picture returned from camera");
|
||||
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
|
||||
} else {
|
||||
if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) {
|
||||
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
|
||||
}
|
||||
|
||||
callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
|
||||
}
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.edit()
|
||||
.remove(KEY_LAST_CAMERA_PHOTO)
|
||||
.remove(KEY_PHOTO_URI)
|
||||
.apply();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
|
||||
}
|
||||
}
|
||||
|
||||
private static void onVideoReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) {
|
||||
try {
|
||||
String lastVideoUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_VIDEO_URI, null);
|
||||
if (!TextUtils.isEmpty(lastVideoUri)) {
|
||||
revokeWritePermission(activity, Uri.parse(lastVideoUri));
|
||||
}
|
||||
|
||||
File photoFile = FilePicker.takenCameraVideo(activity);
|
||||
List<File> files = new ArrayList<>();
|
||||
files.add(photoFile);
|
||||
|
||||
if (photoFile == null) {
|
||||
Exception e = new IllegalStateException("Unable to get the video returned from camera");
|
||||
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity));
|
||||
} else {
|
||||
if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) {
|
||||
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
|
||||
}
|
||||
|
||||
callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity));
|
||||
}
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
.edit()
|
||||
.remove(KEY_LAST_CAMERA_VIDEO)
|
||||
.remove(KEY_VIDEO_URI)
|
||||
.apply();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to clear configuration. Would likely be used in onDestroy(), onDestroyView()...
|
||||
*
|
||||
* @param context context
|
||||
*/
|
||||
public static void clearConfiguration(@NonNull Context context) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.remove(BundleKeys.FOLDER_NAME)
|
||||
.remove(BundleKeys.ALLOW_MULTIPLE)
|
||||
.remove(BundleKeys.COPY_TAKEN_PHOTOS)
|
||||
.remove(BundleKeys.COPY_PICKED_IMAGES)
|
||||
.apply();
|
||||
}
|
||||
|
||||
public static FilePickerConfiguration configuration(@NonNull Context context) {
|
||||
return new FilePickerConfiguration(context);
|
||||
}
|
||||
|
||||
|
||||
public enum ImageSource {
|
||||
GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO
|
||||
}
|
||||
|
||||
public interface Callbacks {
|
||||
void onImagePickerError(Exception e, FilePicker.ImageSource source, int type);
|
||||
|
||||
void onImagesPicked(@NonNull List<File> imageFiles, FilePicker.ImageSource source, int type);
|
||||
|
||||
void onCanceled(FilePicker.ImageSource source, int type);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package fr.free.nrw.commons.filepicker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
public class FilePickerConfiguration implements Constants {
|
||||
|
||||
private Context context;
|
||||
|
||||
FilePickerConfiguration(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public FilePickerConfiguration setImagesFolderName(String folderName) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit().putString(BundleKeys.FOLDER_NAME, folderName)
|
||||
.commit();
|
||||
return this;
|
||||
}
|
||||
|
||||
public FilePickerConfiguration setAllowMultiplePickInGallery(boolean allowMultiple) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.putBoolean(BundleKeys.ALLOW_MULTIPLE, allowMultiple)
|
||||
.commit();
|
||||
return this;
|
||||
}
|
||||
|
||||
public FilePickerConfiguration setCopyTakenPhotosToPublicGalleryAppFolder(boolean copy) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.putBoolean(BundleKeys.COPY_TAKEN_PHOTOS, copy)
|
||||
.commit();
|
||||
return this;
|
||||
}
|
||||
|
||||
public FilePickerConfiguration setCopyPickedImagesToPublicGalleryAppFolder(boolean copy) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit()
|
||||
.putBoolean(BundleKeys.COPY_PICKED_IMAGES, copy)
|
||||
.commit();
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getFolderName() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getString(BundleKeys.FOLDER_NAME, DEFAULT_FOLDER_NAME);
|
||||
}
|
||||
|
||||
public boolean allowsMultiplePickingInGallery() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.ALLOW_MULTIPLE, false);
|
||||
}
|
||||
|
||||
public boolean shouldCopyTakenPhotosToPublicGalleryAppFolder() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.COPY_TAKEN_PHOTOS, false);
|
||||
}
|
||||
|
||||
public boolean shouldCopyPickedImagesToPublicGalleryAppFolder() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.COPY_PICKED_IMAGES, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
package fr.free.nrw.commons.filepicker;
|
||||
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import com.facebook.common.internal.ImmutableMap;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class MimeTypeMapWrapper {
|
||||
|
||||
private static final MimeTypeMap sMimeTypeMap = MimeTypeMap.getSingleton();
|
||||
|
||||
private static final Map<String, String> sMimeTypeToExtensionMap =
|
||||
ImmutableMap.of(
|
||||
"image/heif", "heif",
|
||||
"image/heic", "heic");
|
||||
|
||||
private static final Map<String, String> sExtensionToMimeTypeMap =
|
||||
ImmutableMap.of(
|
||||
"heif", "image/heif",
|
||||
"heic", "image/heic");
|
||||
|
||||
public static String getExtensionFromMimeType(String mimeType) {
|
||||
String result = sMimeTypeToExtensionMap.get(mimeType);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
return sMimeTypeMap.getExtensionFromMimeType(mimeType);
|
||||
}
|
||||
|
||||
public static String getMimeTypeFromExtension(String extension) {
|
||||
String result = sExtensionToMimeTypeMap.get(extension);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
return sMimeTypeMap.getMimeTypeFromExtension(extension);
|
||||
}
|
||||
|
||||
public static boolean hasExtension(String extension) {
|
||||
return sExtensionToMimeTypeMap.containsKey(extension) || sMimeTypeMap.hasExtension(extension);
|
||||
}
|
||||
|
||||
public static boolean hasMimeType(String mimeType) {
|
||||
return sMimeTypeToExtensionMap.containsKey(mimeType) || sMimeTypeMap.hasMimeType(mimeType);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
package fr.free.nrw.commons.filepicker;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
class PickedFiles implements Constants {
|
||||
|
||||
private static String getFolderName(@NonNull Context context) {
|
||||
return FilePicker.configuration(context).getFolderName();
|
||||
}
|
||||
|
||||
private static File tempImageDirectory(@NonNull Context context) {
|
||||
File privateTempDir = new File(context.getCacheDir(), DEFAULT_FOLDER_NAME);
|
||||
if (!privateTempDir.exists()) privateTempDir.mkdirs();
|
||||
return privateTempDir;
|
||||
}
|
||||
|
||||
private static void writeToFile(InputStream in, File file) {
|
||||
try {
|
||||
OutputStream out = new FileOutputStream(file);
|
||||
byte[] buf = new byte[1024];
|
||||
int len;
|
||||
while ((len = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, len);
|
||||
}
|
||||
out.close();
|
||||
in.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyFile(File src, File dst) throws IOException {
|
||||
InputStream in = new FileInputStream(src);
|
||||
writeToFile(in, dst);
|
||||
}
|
||||
|
||||
static void copyFilesInSeparateThread(final Context context, final List<File> filesToCopy) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
List<File> copiedFiles = new ArrayList<>();
|
||||
int i = 1;
|
||||
for (File fileToCopy : filesToCopy) {
|
||||
File dstDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), getFolderName(context));
|
||||
if (!dstDir.exists()) dstDir.mkdirs();
|
||||
|
||||
String[] filenameSplit = fileToCopy.getName().split("\\.");
|
||||
String extension = "." + filenameSplit[filenameSplit.length - 1];
|
||||
String filename = String.format("IMG_%s_%d.%s", new SimpleDateFormat("yyyyMMdd_HHmmss").format(Calendar.getInstance().getTime()), i, extension);
|
||||
|
||||
File dstFile = new File(dstDir, filename);
|
||||
try {
|
||||
dstFile.createNewFile();
|
||||
copyFile(fileToCopy, dstFile);
|
||||
copiedFiles.add(dstFile);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
i++;
|
||||
}
|
||||
scanCopiedImages(context, copiedFiles);
|
||||
}
|
||||
}).run();
|
||||
}
|
||||
|
||||
static List<File> singleFileList(File file) {
|
||||
List<File> list = new ArrayList<>();
|
||||
list.add(file);
|
||||
return list;
|
||||
}
|
||||
|
||||
static void scanCopiedImages(Context context, List<File> copiedImages) {
|
||||
String[] paths = new String[copiedImages.size()];
|
||||
for (int i = 0; i < copiedImages.size(); i++) {
|
||||
paths[i] = copiedImages.get(i).toString();
|
||||
}
|
||||
|
||||
MediaScannerConnection.scanFile(context,
|
||||
paths, null,
|
||||
new MediaScannerConnection.OnScanCompletedListener() {
|
||||
public void onScanCompleted(String path, Uri uri) {
|
||||
Log.d(getClass().getSimpleName(), "Scanned " + path + ":");
|
||||
Log.d(getClass().getSimpleName(), "-> uri=" + uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static File pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException {
|
||||
InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri);
|
||||
File directory = tempImageDirectory(context);
|
||||
File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri));
|
||||
photoFile.createNewFile();
|
||||
writeToFile(pictureInputStream, photoFile);
|
||||
return photoFile;
|
||||
}
|
||||
|
||||
static File getCameraPicturesLocation(@NonNull Context context) throws IOException {
|
||||
File dir = tempImageDirectory(context);
|
||||
return File.createTempFile(UUID.randomUUID().toString(), ".jpg", dir);
|
||||
}
|
||||
|
||||
static File getCameraVideoLocation(@NonNull Context context) throws IOException {
|
||||
File dir = tempImageDirectory(context);
|
||||
return File.createTempFile(UUID.randomUUID().toString(), ".mp4", dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* To find out the extension of required object in given uri
|
||||
* Solution by http://stackoverflow.com/a/36514823/1171484
|
||||
*/
|
||||
private static String getMimeType(@NonNull Context context, @NonNull Uri uri) {
|
||||
String extension;
|
||||
|
||||
//Check uri format to avoid null
|
||||
if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
|
||||
//If scheme is a content
|
||||
extension = MimeTypeMapWrapper.getExtensionFromMimeType(context.getContentResolver().getType(uri));
|
||||
} else {
|
||||
//If scheme is a File
|
||||
//This will replace white spaces with %20 and also other special characters. This will avoid returning null values on file name with spaces and special characters.
|
||||
extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(new File(uri.getPath())).toString());
|
||||
|
||||
}
|
||||
|
||||
return extension;
|
||||
}
|
||||
|
||||
static Uri getUriToFile(@NonNull Context context, @NonNull File file) {
|
||||
String packageName = context.getApplicationContext().getPackageName();
|
||||
String authority = packageName + ".provider";
|
||||
return FileProvider.getUriForFile(context, authority, file);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue