diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java b/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java deleted file mode 100644 index 97a16acc3..000000000 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java +++ /dev/null @@ -1,23 +0,0 @@ -package fr.free.nrw.commons.filepicker; - -public interface Constants { - String DEFAULT_FOLDER_NAME = "CommonsContributions"; - - /** - * Provides the request codes for permission handling - */ - interface RequestCodes { - int LOCATION = 1; - int STORAGE = 2; - } - - /** - * Provides locations as string for corresponding operations - */ - 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"; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/Costants.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/Costants.kt new file mode 100644 index 000000000..e405a6d52 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/Costants.kt @@ -0,0 +1,29 @@ +package fr.free.nrw.commons.filepicker + +interface Constants { + companion object { + const val DEFAULT_FOLDER_NAME = "CommonsContributions" + } + + /** + * Provides the request codes for permission handling + */ + interface RequestCodes { + companion object { + const val LOCATION = 1 + const val STORAGE = 2 + } + } + + /** + * Provides locations as string for corresponding operations + */ + interface BundleKeys { + companion object { + const val FOLDER_NAME = "fr.free.nrw.commons.folder_name" + const val ALLOW_MULTIPLE = "fr.free.nrw.commons.allow_multiple" + const val COPY_TAKEN_PHOTOS = "fr.free.nrw.commons.copy_taken_photos" + const val COPY_PICKED_IMAGES = "fr.free.nrw.commons.copy_picked_images" + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/DefaultCallback.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/DefaultCallback.kt index e8373dc6f..baaba67b5 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/DefaultCallback.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/DefaultCallback.kt @@ -1,16 +1,12 @@ -package fr.free.nrw.commons.filepicker; +package fr.free.nrw.commons.filepicker /** * Provides abstract methods which are overridden while handling Contribution Results - * inside the ContributionsController + * inside the ContributionsController */ -public abstract class DefaultCallback implements FilePicker.Callbacks { +abstract class DefaultCallback: FilePicker.Callbacks { - @Override - public void onImagePickerError(Exception e, FilePicker.ImageSource source, int type) { - } + override fun onImagePickerError(e: Exception, source: FilePicker.ImageSource, type: Int) {} - @Override - public void onCanceled(FilePicker.ImageSource source, int type) { - } + override fun onCanceled(source: FilePicker.ImageSource, type: Int) {} } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/ExtendedFileProvider.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/ExtendedFileProvider.kt index af3dc8622..746058fc4 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/ExtendedFileProvider.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/ExtendedFileProvider.kt @@ -1,7 +1,5 @@ -package fr.free.nrw.commons.filepicker; +package fr.free.nrw.commons.filepicker -import androidx.core.content.FileProvider; +import androidx.core.content.FileProvider -public class ExtendedFileProvider extends FileProvider { - -} \ No newline at end of file +class ExtendedFileProvider: FileProvider() {} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt index b64db24c5..e7a120a8b 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt @@ -1,60 +1,73 @@ -package fr.free.nrw.commons.filepicker; +package fr.free.nrw.commons.filepicker -import static fr.free.nrw.commons.filepicker.PickedFiles.singleFileList; +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import android.provider.MediaStore +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.preference.PreferenceManager +import fr.free.nrw.commons.customselector.model.Image +import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity +import fr.free.nrw.commons.filepicker.PickedFiles.singleFileList +import java.io.File +import java.io.IOException +import java.net.URISyntaxException -import android.app.Activity; -import android.content.ClipData; -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.provider.MediaStore; -import android.text.TextUtils; -import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultLauncher; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.preference.PreferenceManager; -import fr.free.nrw.commons.customselector.model.Image; -import fr.free.nrw.commons.customselector.ui.selector.CustomSelectorActivity; -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -public class FilePicker implements Constants { +object FilePicker : Constants { - 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 const val KEY_PHOTO_URI = "photo_uri" + private const val KEY_VIDEO_URI = "video_uri" + private const val KEY_LAST_CAMERA_PHOTO = "last_photo" + private const val KEY_LAST_CAMERA_VIDEO = "last_video" + private const val KEY_TYPE = "type" /** * Returns the uri of the clicked image so that it can be put in MediaStore */ - 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; + @Throws(IOException::class) + + @JvmStatic + private fun createCameraPictureFile(context: Context): Uri { + val imagePath = PickedFiles.getCameraPicturesLocation(context) + val uri = PickedFiles.getUriToFile(context, imagePath) + val 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 Intent createGalleryIntent(@NonNull Context context, int type, - boolean openDocumentIntentPreferred) { + + @JvmStatic + private fun createGalleryIntent( + context: Context, + type: Int, + openDocumentIntentPreferred: Boolean + ): Intent { // storing picked image type to shared preferences - storeType(context, type); - //Supported types are SVG, PNG and JPEG,GIF, TIFF, WebP, XCF - final String[] mimeTypes = { "image/jpg","image/png","image/jpeg", "image/gif", "image/tiff", "image/webp", "image/xcf", "image/svg+xml", "image/webp"}; + storeType(context, type) + // Supported types are SVG, PNG and JPEG, GIF, TIFF, WebP, XCF + val mimeTypes = arrayOf( + "image/jpg", + "image/png", + "image/jpeg", + "image/gif", + "image/tiff", + "image/webp", + "image/xcf", + "image/svg+xml", + "image/webp" + ) return plainGalleryPickerIntent(openDocumentIntentPreferred) - .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery()) - .putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + .putExtra( + Intent.EXTRA_ALLOW_MULTIPLE, + configuration(context).allowsMultiplePickingInGallery() + ) + .putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) } /** @@ -63,107 +76,149 @@ public class FilePicker implements Constants { * @param type * @return Custom selector intent */ - private static Intent createCustomSelectorIntent(@NonNull Context context, int type) { - storeType(context, type); - return new Intent(context, CustomSelectorActivity.class); + @JvmStatic + private fun createCustomSelectorIntent(context: Context, type: Int): Intent { + storeType(context, type) + return Intent(context, CustomSelectorActivity::class.java) } - private static Intent createCameraForImageIntent(@NonNull Context context, int type) { - storeType(context, type); + @JvmStatic + private fun createCameraForImageIntent(context: Context, type: Int): Intent { + storeType(context, type) - Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + val intent = 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(); + val 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 (e: Exception) { + e.printStackTrace() } - return intent; + 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); + @JvmStatic + private fun revokeWritePermission(context: Context, uri: Uri) { + context.revokeUriPermission( + uri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION + ) } - private static void grantWritePermission(@NonNull Context context, Intent intent, Uri uri) { - List 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); + @JvmStatic + private fun grantWritePermission(context: Context, intent: Intent, uri: Uri) { + val resInfoList = + context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) + for (resolveInfo in resInfoList) { + val packageName = resolveInfo.activityInfo.packageName + context.grantUriPermission( + packageName, + uri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION + ) } } - private static void storeType(@NonNull Context context, int type) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(KEY_TYPE, type).apply(); + @JvmStatic + private fun storeType(context: Context, type: Int) { + PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(KEY_TYPE, type).apply() } - private static int restoreType(@NonNull Context context) { - return PreferenceManager.getDefaultSharedPreferences(context).getInt(KEY_TYPE, 0); + @JvmStatic + private fun restoreType(context: Context): Int { + return PreferenceManager.getDefaultSharedPreferences(context).getInt(KEY_TYPE, 0) } /** - * Opens default galery or a available galleries picker if there is no default + * Opens default gallery or 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, ActivityResultLauncher resultLauncher, int type, boolean openDocumentIntentPreferred) { - Intent intent = createGalleryIntent(activity, type, openDocumentIntentPreferred); - resultLauncher.launch(intent); + @JvmStatic + fun openGallery( + activity: Activity, + resultLauncher: ActivityResultLauncher, + type: Int, + openDocumentIntentPreferred: Boolean + ) { + val intent = createGalleryIntent(activity, type, openDocumentIntentPreferred) + resultLauncher.launch(intent) } /** * Opens Custom Selector */ - public static void openCustomSelector(Activity activity, ActivityResultLauncher resultLauncher, int type) { - Intent intent = createCustomSelectorIntent(activity, type); - resultLauncher.launch(intent); + @JvmStatic + fun openCustomSelector( + activity: Activity, + resultLauncher: ActivityResultLauncher, + type: Int + ) { + val intent = createCustomSelectorIntent(activity, type) + resultLauncher.launch(intent) } /** - * Opens the camera app to pick image clicked by user + * Opens the camera app to pick image clicked by user */ - public static void openCameraForImage(Activity activity, ActivityResultLauncher resultLauncher, int type) { - Intent intent = createCameraForImageIntent(activity, type); - resultLauncher.launch(intent); + @JvmStatic + fun openCameraForImage( + activity: Activity, + resultLauncher: ActivityResultLauncher, + type: Int + ) { + val intent = createCameraForImageIntent(activity, type) + resultLauncher.launch(intent) } - @Nullable - private static UploadableFile takenCameraPicture(Context context) throws URISyntaxException { - String lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_PHOTO, null); - if (lastCameraPhoto != null) { - return new UploadableFile(new File(lastCameraPhoto)); + @Throws(URISyntaxException::class) + @JvmStatic + private fun takenCameraPicture(context: Context): UploadableFile? { + val lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context) + .getString(KEY_LAST_CAMERA_PHOTO, null) + return if (lastCameraPhoto != null) { + UploadableFile(File(lastCameraPhoto)) } else { - return null; + null } } - @Nullable - private static UploadableFile takenCameraVideo(Context context) throws URISyntaxException { - String lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_VIDEO, null); - if (lastCameraPhoto != null) { - return new UploadableFile(new File(lastCameraPhoto)); + @Throws(URISyntaxException::class) + @JvmStatic + private fun takenCameraVideo(context: Context): UploadableFile? { + val lastCameraVideo = PreferenceManager.getDefaultSharedPreferences(context) + .getString(KEY_LAST_CAMERA_VIDEO, null) + return if (lastCameraVideo != null) { + UploadableFile(File(lastCameraVideo)) } else { - return null; + null } } - public static List handleExternalImagesPicked(Intent data, Activity activity) { - try { - return getFilesFromGalleryPictures(data, activity); - } catch (IOException | SecurityException e) { - e.printStackTrace(); + @JvmStatic + fun handleExternalImagesPicked(data: Intent?, activity: Activity): List { + return try { + getFilesFromGalleryPictures(data, activity) + } catch (e: IOException) { + e.printStackTrace() + emptyList() + } catch (e: SecurityException) { + e.printStackTrace() + emptyList() } - return new ArrayList<>(); } - private static boolean isPhoto(Intent data) { - return data == null || (data.getData() == null && data.getClipData() == null); + @JvmStatic + private fun isPhoto(data: Intent?): Boolean { + return data == null || (data.data == null && data.clipData == null) } - private static Intent plainGalleryPickerIntent(boolean openDocumentIntentPreferred) { + @JvmStatic + private fun plainGalleryPickerIntent( + openDocumentIntentPreferred: Boolean + ): Intent { /* * Asking for ACCESS_MEDIA_LOCATION at runtime solved the location-loss issue * in the custom selector in Contributions fragment. @@ -192,32 +247,40 @@ public class FilePicker implements Constants { * from EXIF. * */ - Intent intent; - if (openDocumentIntentPreferred) { - intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + val intent = if (openDocumentIntentPreferred) { + Intent(Intent.ACTION_OPEN_DOCUMENT) } else { - intent = new Intent(Intent.ACTION_GET_CONTENT); + Intent(Intent.ACTION_GET_CONTENT) } - intent.setType("image/*"); - return intent; + intent.type = "image/*" + return intent } - public static void onPictureReturnedFromDocuments(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { - if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){ + @JvmStatic + fun onPictureReturnedFromDocuments( + result: ActivityResult, + activity: Activity, + callbacks: Callbacks + ) { + if (result.resultCode == Activity.RESULT_OK && !isPhoto(result.data)) { try { - Uri photoPath = result.getData().getData(); - UploadableFile photoFile = PickedFiles.pickedExistingPicture(activity, photoPath); - callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); + val photoPath = result.data?.data + val photoFile = PickedFiles.pickedExistingPicture(activity, photoPath!!) + callbacks.onImagesPicked( + singleFileList(photoFile), + ImageSource.DOCUMENTS, + restoreType(activity) + ) if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { - PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)); + PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)) } - } catch (Exception e) { - e.printStackTrace(); - callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); + } catch (e: Exception) { + e.printStackTrace() + callbacks.onImagePickerError(e, ImageSource.DOCUMENTS, restoreType(activity)) } } else { - callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); + callbacks.onCanceled(ImageSource.DOCUMENTS, restoreType(activity)) } } @@ -225,131 +288,155 @@ public class FilePicker implements Constants { * onPictureReturnedFromCustomSelector. * Retrieve and forward the images to upload wizard through callback. */ - public static void onPictureReturnedFromCustomSelector(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { - if(result.getResultCode() == Activity.RESULT_OK){ - try { - List files = getFilesFromCustomSelector(result.getData(), activity); - callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); - } catch (Exception e) { - e.printStackTrace(); - callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); - } - } else { - callbacks.onCanceled(ImageSource.CUSTOM_SELECTOR, restoreType(activity)); - } + @JvmStatic + fun onPictureReturnedFromCustomSelector( + result: ActivityResult, + activity: Activity, + callbacks: Callbacks + ) { + if (result.resultCode == Activity.RESULT_OK) { + try { + val files = getFilesFromCustomSelector(result.data, activity) + callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity)) + } catch (e: Exception) { + e.printStackTrace() + callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity)) + } + } else { + callbacks.onCanceled(ImageSource.CUSTOM_SELECTOR, restoreType(activity)) + } } /** * Get files from custom selector * Retrieve and process the selected images from the custom selector. */ - private static List getFilesFromCustomSelector(Intent data, Activity activity) throws IOException, SecurityException { - List files = new ArrayList<>(); - ArrayList images = data.getParcelableArrayListExtra("Images"); - for(Image image : images) { - Uri uri = image.getUri(); - UploadableFile file = PickedFiles.pickedExistingPicture(activity, uri); - files.add(file); + @Throws(IOException::class, SecurityException::class) + @JvmStatic + private fun getFilesFromCustomSelector( + data: Intent?, + activity: Activity + ): List { + val files = mutableListOf() + val images = data?.getParcelableArrayListExtra("Images") + images?.forEach { image -> + val uri = image.uri + val file = PickedFiles.pickedExistingPicture(activity, uri) + files.add(file) } if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { - PickedFiles.copyFilesInSeparateThread(activity, files); + PickedFiles.copyFilesInSeparateThread(activity, files) } - return files; + return files } - public static void onPictureReturnedFromGallery(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { - if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){ + @JvmStatic + fun onPictureReturnedFromGallery( + result: ActivityResult, + activity: Activity, + callbacks: Callbacks + ) { + if (result.resultCode == Activity.RESULT_OK && !isPhoto(result.data)) { try { - List files = getFilesFromGalleryPictures(result.getData(), activity); - callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity)); - } catch (Exception e) { - e.printStackTrace(); - callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity)); + val files = getFilesFromGalleryPictures(result.data, activity) + callbacks.onImagesPicked(files, ImageSource.GALLERY, restoreType(activity)) + } catch (e: Exception) { + e.printStackTrace() + callbacks.onImagePickerError(e, ImageSource.GALLERY, restoreType(activity)) } - } else{ - callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity)); - } - } - - private static List getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException, SecurityException { - List files = new ArrayList<>(); - ClipData clipData = data.getClipData(); - if (clipData == null) { - Uri uri = data.getData(); - UploadableFile file = PickedFiles.pickedExistingPicture(activity, uri); - files.add(file); } else { - for (int i = 0; i < clipData.getItemCount(); i++) { - Uri uri = clipData.getItemAt(i).getUri(); - UploadableFile file = PickedFiles.pickedExistingPicture(activity, uri); - files.add(file); + callbacks.onCanceled(ImageSource.GALLERY, restoreType(activity)) + } + } + + @Throws(IOException::class, SecurityException::class) + @JvmStatic + private fun getFilesFromGalleryPictures( + data: Intent?, + activity: Activity + ): List { + val files = mutableListOf() + val clipData = data?.clipData + if (clipData == null) { + val uri = data?.data + val file = PickedFiles.pickedExistingPicture(activity, uri!!) + files.add(file) + } else { + for (i in 0 until clipData.itemCount) { + val uri = clipData.getItemAt(i).uri + val file = PickedFiles.pickedExistingPicture(activity, uri) + files.add(file) } } if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { - PickedFiles.copyFilesInSeparateThread(activity, files); + PickedFiles.copyFilesInSeparateThread(activity, files) } - return files; + return files } - public static void onPictureReturnedFromCamera(ActivityResult activityResult, Activity activity, @NonNull FilePicker.Callbacks callbacks) { - if(activityResult.getResultCode() == Activity.RESULT_OK){ + @JvmStatic + fun onPictureReturnedFromCamera( + activityResult: ActivityResult, + activity: Activity, + callbacks: Callbacks + ) { + if (activityResult.resultCode == Activity.RESULT_OK) { try { - String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null); - if (!TextUtils.isEmpty(lastImageUri)) { - revokeWritePermission(activity, Uri.parse(lastImageUri)); + val lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity) + .getString(KEY_PHOTO_URI, null) + if (!lastImageUri.isNullOrEmpty()) { + revokeWritePermission(activity, Uri.parse(lastImageUri)) } - UploadableFile photoFile = FilePicker.takenCameraPicture(activity); - List files = new ArrayList<>(); - files.add(photoFile); + val photoFile = takenCameraPicture(activity) + val files = mutableListOf() + photoFile?.let { files.add(it) } if (photoFile == null) { - Exception e = new IllegalStateException("Unable to get the picture returned from camera"); - callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); + val e = IllegalStateException("Unable to get the picture returned from camera") + callbacks.onImagePickerError(e, ImageSource.CAMERA_IMAGE, restoreType(activity)) } else { if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) { - PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)); + PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)) } - - callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); + callbacks.onImagesPicked(files, ImageSource.CAMERA_IMAGE, restoreType(activity)) } - PreferenceManager.getDefaultSharedPreferences(activity) - .edit() + 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)); + .apply() + } catch (e: Exception) { + e.printStackTrace() + callbacks.onImagePickerError(e, ImageSource.CAMERA_IMAGE, restoreType(activity)) } } else { - callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); + callbacks.onCanceled(ImageSource.CAMERA_IMAGE, restoreType(activity)) } } - public static FilePickerConfiguration configuration(@NonNull Context context) { - return new FilePickerConfiguration(context); + @JvmStatic + fun configuration(context: Context): FilePickerConfiguration { + return FilePickerConfiguration(context) } - - public enum ImageSource { + enum class ImageSource { GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO, CUSTOM_SELECTOR } - public interface Callbacks { - void onImagePickerError(Exception e, FilePicker.ImageSource source, int type); + interface Callbacks { + fun onImagePickerError(e: Exception, source: ImageSource, type: Int) - void onImagesPicked(@NonNull List imageFiles, FilePicker.ImageSource source, int type); + fun onImagesPicked(imageFiles: List, source: ImageSource, type: Int) - void onCanceled(FilePicker.ImageSource source, int type); + fun onCanceled(source: ImageSource, type: Int) } - public interface HandleActivityResult{ - void onHandleActivityResult(FilePicker.Callbacks callbacks); + interface HandleActivityResult { + fun onHandleActivityResult(callbacks: Callbacks) } -} +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePickerConfiguration.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePickerConfiguration.kt index 08a204e8b..db025a544 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePickerConfiguration.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePickerConfiguration.kt @@ -1,44 +1,46 @@ -package fr.free.nrw.commons.filepicker; +package fr.free.nrw.commons.filepicker -import android.content.Context; -import androidx.preference.PreferenceManager; +import android.content.Context +import androidx.preference.PreferenceManager -public class FilePickerConfiguration implements Constants { +class FilePickerConfiguration( + private val context: Context +): Constants { - private Context context; - - FilePickerConfiguration(Context context) { - this.context = context; - } - - public FilePickerConfiguration setAllowMultiplePickInGallery(boolean allowMultiple) { + fun setAllowMultiplePickInGallery(allowMultiple: Boolean): FilePickerConfiguration { PreferenceManager.getDefaultSharedPreferences(context).edit() - .putBoolean(BundleKeys.ALLOW_MULTIPLE, allowMultiple) - .apply(); - return this; + .putBoolean(Constants.BundleKeys.ALLOW_MULTIPLE, allowMultiple) + .apply() + return this } - public FilePickerConfiguration setCopyTakenPhotosToPublicGalleryAppFolder(boolean copy) { + fun setCopyTakenPhotosToPublicGalleryAppFolder(copy: Boolean): FilePickerConfiguration { PreferenceManager.getDefaultSharedPreferences(context).edit() - .putBoolean(BundleKeys.COPY_TAKEN_PHOTOS, copy) - .apply(); - return this; + .putBoolean(Constants.BundleKeys.COPY_TAKEN_PHOTOS, copy) + .apply() + return this } - public String getFolderName() { - return PreferenceManager.getDefaultSharedPreferences(context).getString(BundleKeys.FOLDER_NAME, DEFAULT_FOLDER_NAME); + fun getFolderName(): String { + return PreferenceManager.getDefaultSharedPreferences(context) + .getString( + Constants.BundleKeys.FOLDER_NAME, + Constants.DEFAULT_FOLDER_NAME + ) ?: Constants.DEFAULT_FOLDER_NAME } - public boolean allowsMultiplePickingInGallery() { - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.ALLOW_MULTIPLE, false); + fun allowsMultiplePickingInGallery(): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(Constants.BundleKeys.ALLOW_MULTIPLE, false) } - public boolean shouldCopyTakenPhotosToPublicGalleryAppFolder() { - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.COPY_TAKEN_PHOTOS, false); + fun shouldCopyTakenPhotosToPublicGalleryAppFolder(): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(Constants.BundleKeys.COPY_TAKEN_PHOTOS, false) } - public boolean shouldCopyPickedImagesToPublicGalleryAppFolder() { - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.COPY_PICKED_IMAGES, false); + fun shouldCopyPickedImagesToPublicGalleryAppFolder(): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean(Constants.BundleKeys.COPY_PICKED_IMAGES, false) } - } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/MimeTypeMapWrapper.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/MimeTypeMapWrapper.kt index e6c82f5c1..0cf21cc02 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/MimeTypeMapWrapper.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/MimeTypeMapWrapper.kt @@ -1,26 +1,24 @@ -package fr.free.nrw.commons.filepicker; +package fr.free.nrw.commons.filepicker -import android.webkit.MimeTypeMap; +import android.webkit.MimeTypeMap -import com.facebook.common.internal.ImmutableMap; +class MimeTypeMapWrapper { -import java.util.Map; + companion object { + private val sMimeTypeMap = MimeTypeMap.getSingleton() -public class MimeTypeMapWrapper { + private val sMimeTypeToExtensionMap = mapOf( + "image/heif" to "heif", + "image/heic" to "heic" + ) - private static final MimeTypeMap sMimeTypeMap = MimeTypeMap.getSingleton(); - - private static final Map sMimeTypeToExtensionMap = - ImmutableMap.of( - "image/heif", "heif", - "image/heic", "heic"); - - public static String getExtensionFromMimeType(String mimeType) { - String result = sMimeTypeToExtensionMap.get(mimeType); - if (result != null) { - return result; + @JvmStatic + fun getExtensionFromMimeType(mimeType: String): String? { + val result = sMimeTypeToExtensionMap[mimeType] + if (result != null) { + return result + } + return sMimeTypeMap.getExtensionFromMimeType(mimeType) } - return sMimeTypeMap.getExtensionFromMimeType(mimeType); } - } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.kt index ca1abba62..df0c2236c 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/PickedFiles.kt @@ -1,67 +1,62 @@ -package fr.free.nrw.commons.filepicker; +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.webkit.MimeTypeMap; +import android.content.ContentResolver +import android.content.Context +import android.media.MediaScannerConnection +import android.net.Uri +import android.os.Environment +import android.webkit.MimeTypeMap +import androidx.core.content.FileProvider +import fr.free.nrw.commons.filepicker.Constants.Companion.DEFAULT_FOLDER_NAME +import timber.log.Timber +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.UUID -import androidx.annotation.NonNull; -import androidx.core.content.FileProvider; - -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; - -import timber.log.Timber; /** * PickedFiles. * Process the upload items. */ -public class PickedFiles implements Constants { +object PickedFiles : Constants { /** * Get Folder Name - * @param context * @return default application folder name. */ - private static String getFolderName(@NonNull Context context) { - return FilePicker.configuration(context).getFolderName(); + private fun getFolderName(context: Context): String { + return FilePicker.configuration(context).getFolderName() } /** * tempImageDirectory - * @param context * @return temporary image directory to copy and perform exif changes. */ - private static File tempImageDirectory(@NonNull Context context) { - File privateTempDir = new File(context.getCacheDir(), DEFAULT_FOLDER_NAME); - if (!privateTempDir.exists()) privateTempDir.mkdirs(); - return privateTempDir; + private fun tempImageDirectory(context: Context): File { + val privateTempDir = File(context.cacheDir, DEFAULT_FOLDER_NAME) + if (!privateTempDir.exists()) privateTempDir.mkdirs() + return privateTempDir } /** * writeToFile - * writes inputStream data to the destination file. - * @param in input stream of source file. - * @param file destination file + * Writes inputStream data to the destination file. */ - private static void writeToFile(InputStream in, File file) throws IOException { - try (OutputStream out = new FileOutputStream(file)) { - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); + @Throws(IOException::class) + private fun writeToFile(inputStream: InputStream, file: File) { + inputStream.use { input -> + FileOutputStream(file).use { output -> + val buffer = ByteArray(1024) + var length: Int + while (input.read(buffer).also { length = it } > 0) { + output.write(buffer, 0, length) + } } } } @@ -69,129 +64,111 @@ public class PickedFiles implements Constants { /** * Copy file function. * Copies source file to destination file. - * @param src source file - * @param dst destination file - * @throws IOException (File input stream exception) */ - private static void copyFile(File src, File dst) throws IOException { - try (InputStream in = new FileInputStream(src)) { - writeToFile(in, dst); + @Throws(IOException::class) + private fun copyFile(src: File, dst: File) { + FileInputStream(src).use { inputStream -> + writeToFile(inputStream, dst) } } /** * Copy files in separate thread. * Copies all the uploadable files to the temp image folder on background thread. - * @param context - * @param filesToCopy uploadable file list to be copied. */ - static void copyFilesInSeparateThread(final Context context, final List filesToCopy) { - new Thread(() -> { - List copiedFiles = new ArrayList<>(); - int i = 1; - for (UploadableFile uploadableFile : filesToCopy) { - File fileToCopy = uploadableFile.getFile(); - File dstDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), getFolderName(context)); - if (!dstDir.exists()) { - dstDir.mkdirs(); - } + fun copyFilesInSeparateThread(context: Context, filesToCopy: List) { + Thread { + val copiedFiles = mutableListOf() + var index = 1 + filesToCopy.forEach { uploadableFile -> + val fileToCopy = uploadableFile.file + val dstDir = 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); + val filenameSplit = fileToCopy.name.split(".") + val extension = ".${filenameSplit.last()}" + val filename = "IMG_${SimpleDateFormat( + "yyyyMMdd_HHmmss", + Locale.getDefault()).format(Date())}_$index$extension" + val dstFile = File(dstDir, filename) - File dstFile = new File(dstDir, filename); try { - dstFile.createNewFile(); - copyFile(fileToCopy, dstFile); - copiedFiles.add(dstFile); - } catch (IOException e) { - e.printStackTrace(); + dstFile.createNewFile() + copyFile(fileToCopy, dstFile) + copiedFiles.add(dstFile) + } catch (e: IOException) { + e.printStackTrace() } - i++; + index++ } - scanCopiedImages(context, copiedFiles); - }).run(); + scanCopiedImages(context, copiedFiles) + }.start() } /** - * singleFileList. - * converts a single uploadableFile to list of uploadableFile. - * @param file uploadable file - * @return + * singleFileList + * Converts a single uploadableFile to list of uploadableFile. */ - static List singleFileList(UploadableFile file) { - List list = new ArrayList<>(); - list.add(file); - return list; + fun singleFileList(file: UploadableFile): List { + return listOf(file) } /** * ScanCopiedImages - * Scan copied images metadata using media scanner. - * @param context - * @param copiedImages copied images list. + * Scans copied images metadata using media scanner. */ - static void scanCopiedImages(Context context, List copiedImages) { - String[] paths = new String[copiedImages.size()]; - for (int i = 0; i < copiedImages.size(); i++) { - paths[i] = copiedImages.get(i).toString(); + fun scanCopiedImages(context: Context, copiedImages: List) { + val paths = copiedImages.map { it.toString() }.toTypedArray() + MediaScannerConnection.scanFile(context, paths, null) { path, uri -> + Timber.d("Scanned $path:") + Timber.d("-> uri=$uri") } - - MediaScannerConnection.scanFile(context, - paths, null, - (path, uri) -> { - Timber.d("Scanned " + path + ":"); - Timber.d("-> uri=%s", uri); - }); } /** * pickedExistingPicture - * convert the image into uploadable file. - * @param photoUri Uri of the image. - * @return Uploadable file ready for tag redaction. + * Convert the image into uploadable file. */ - public static UploadableFile pickedExistingPicture(@NonNull Context context, Uri photoUri) throws IOException, SecurityException {// SecurityException for those file providers who share URI but forget to grant necessary permissions - File directory = tempImageDirectory(context); - File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri)); + @Throws(IOException::class, SecurityException::class) + fun pickedExistingPicture(context: Context, photoUri: Uri): UploadableFile { + val directory = tempImageDirectory(context) + val mimeType = getMimeType(context, photoUri) + val photoFile = File(directory, "${UUID.randomUUID()}.$mimeType") + if (photoFile.createNewFile()) { - try (InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri)) { - writeToFile(pictureInputStream, photoFile); + context.contentResolver.openInputStream(photoUri)?.use { inputStream -> + writeToFile(inputStream, photoFile) } } else { - throw new IOException("could not create photoFile to write upon"); + throw IOException("Could not create photoFile to write upon") } - return new UploadableFile(photoUri, photoFile); + return UploadableFile(photoUri, photoFile) } /** * getCameraPictureLocation */ - static File getCameraPicturesLocation(@NonNull Context context) throws IOException { - File dir = tempImageDirectory(context); - return File.createTempFile(UUID.randomUUID().toString(), ".jpg", dir); + @Throws(IOException::class) + fun getCameraPicturesLocation(context: Context): File { + val dir = tempImageDirectory(context) + return File.createTempFile(UUID.randomUUID().toString(), ".jpg", dir) } /** - * To find out the extension of required object in given uri - * Solution by http://stackoverflow.com/a/36514823/1171484 + * To find out the extension of the required object in a given uri */ - 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)); + private fun getMimeType(context: Context, uri: Uri): String { + return if (uri.scheme == ContentResolver.SCHEME_CONTENT) { + context.contentResolver.getType(uri) + ?.let { MimeTypeMapWrapper.getExtensionFromMimeType(it) } } 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; + MimeTypeMap.getFileExtensionFromUrl( + Uri.fromFile(uri.path?.let { File(it) }).toString() + ) + } ?: "jpg" // Default to jpg if unable to determine type } /** @@ -199,10 +176,9 @@ public class PickedFiles implements Constants { * @param file get uri of file * @return uri of requested file. */ - static Uri getUriToFile(@NonNull Context context, @NonNull File file) { - String packageName = context.getApplicationContext().getPackageName(); - String authority = packageName + ".provider"; - return FileProvider.getUriForFile(context, authority, file); + fun getUriToFile(context: Context, file: File): Uri { + val packageName = context.applicationContext.packageName + val authority = "$packageName.provider" + return FileProvider.getUriForFile(context, authority, file) } - -} \ No newline at end of file +} diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt index 1fe306a8b..1398e7785 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt @@ -1,213 +1,168 @@ -package fr.free.nrw.commons.filepicker; +package fr.free.nrw.commons.filepicker -import android.annotation.SuppressLint; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; +import android.annotation.SuppressLint +import android.content.Context +import android.database.Cursor +import android.net.Uri +import android.os.Parcel +import android.os.Parcelable -import androidx.annotation.Nullable; -import androidx.exifinterface.media.ExifInterface; +import androidx.exifinterface.media.ExifInterface -import fr.free.nrw.commons.upload.FileUtils; -import java.io.File; -import java.io.IOException; -import java.util.Date; -import timber.log.Timber; +import fr.free.nrw.commons.upload.FileUtils +import java.io.File +import java.io.IOException +import java.util.Date +import timber.log.Timber -public class UploadableFile implements Parcelable { - public static final Creator CREATOR = new Creator() { - @Override - public UploadableFile createFromParcel(Parcel in) { - return new UploadableFile(in); - } +class UploadableFile : Parcelable { - @Override - public UploadableFile[] newArray(int size) { - return new UploadableFile[size]; - } - }; + val contentUri: Uri + val file: File - private final Uri contentUri; - private final File file; - - public UploadableFile(Uri contentUri, File file) { - this.contentUri = contentUri; - this.file = file; + constructor(contentUri: Uri, file: File) { + this.contentUri = contentUri + this.file = file } - public UploadableFile(File file) { - this.file = file; - this.contentUri = Uri.fromFile(new File(file.getPath())); + constructor(file: File) { + this.file = file + this.contentUri = Uri.fromFile(File(file.path)) } - public UploadableFile(Parcel in) { - this.contentUri = in.readParcelable(Uri.class.getClassLoader()); - file = (File) in.readSerializable(); + private constructor(parcel: Parcel) { + contentUri = parcel.readParcelable(Uri::class.java.classLoader)!! + file = parcel.readSerializable() as File } - public Uri getContentUri() { - return contentUri; + fun getFilePath(): String { + return file.path } - public File getFile() { - return file; + fun getMediaUri(): Uri { + return Uri.parse(getFilePath()) } - public String getFilePath() { - return file.getPath(); + fun getMimeType(context: Context): String? { + return FileUtils.getMimeType(context, getMediaUri()) } - public Uri getMediaUri() { - return Uri.parse(getFilePath()); - } + override fun describeContents(): Int = 0 - public String getMimeType(Context context) { - return FileUtils.getMimeType(context, getMediaUri()); - } - - @Override - public int describeContents() { - return 0; + /** + * First try to get the file creation date from EXIF, else fall back to Content Provider (CP) + */ + fun getFileCreatedDate(context: Context): DateTimeWithSource? { + return getDateTimeFromExif() ?: getFileCreatedDateFromCP(context) } /** - * First try to get the file creation date from EXIF else fall back to CP - * @param context - * @return + * Get filePath creation date from URI using all possible content providers */ - @Nullable - public DateTimeWithSource getFileCreatedDate(Context context) { - DateTimeWithSource dateTimeFromExif = getDateTimeFromExif(); - if (dateTimeFromExif == null) { - return getFileCreatedDateFromCP(context); - } else { - return dateTimeFromExif; - } - } - - /** - * Get filePath creation date from uri from all possible content providers - * - * @return - */ - private DateTimeWithSource getFileCreatedDateFromCP(Context context) { - try { - Cursor cursor = context.getContentResolver().query(contentUri, null, null, null, null); - if (cursor == null) { - return null;//Could not fetch last_modified + private fun getFileCreatedDateFromCP(context: Context): DateTimeWithSource? { + return try { + val cursor: Cursor? = context.contentResolver.query(contentUri, null, null, null, null) + cursor?.use { + val lastModifiedColumnIndex = cursor + .getColumnIndex( + "last_modified" + ).takeIf { it != -1 } + ?: cursor.getColumnIndex("datetaken") + if (lastModifiedColumnIndex == -1) return null // No valid column found + cursor.moveToFirst() + DateTimeWithSource( + cursor.getLong( + lastModifiedColumnIndex + ), DateTimeWithSource.CP_SOURCE) } - //Content provider contracts for opening gallery from the app and that by sharing from gallery from outside are different and we need to handle both the cases - int lastModifiedColumnIndex = cursor.getColumnIndex("last_modified");//If gallery is opened from in app - if (lastModifiedColumnIndex == -1) { - lastModifiedColumnIndex = cursor.getColumnIndex("datetaken"); - } - //If both the content providers do not give the data, lets leave it to Jesus - if (lastModifiedColumnIndex == -1) { - cursor.close(); - return null; - } - cursor.moveToFirst(); - return new DateTimeWithSource(cursor.getLong(lastModifiedColumnIndex), DateTimeWithSource.CP_SOURCE); - } catch (Exception e) { - return null;////Could not fetch last_modified + } catch (e: Exception) { + Timber.tag("UploadableFile").d(e) + null } } /** - * Indicate whether the EXIF contains the location (both latitude and longitude). - * - * @return whether the location exists for the file's EXIF + * Indicates whether the EXIF contains the location (both latitude and longitude). */ - public boolean hasLocation() { - try { - ExifInterface exif = new ExifInterface(file.getAbsolutePath()); - final String latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE); - final String longitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE); - return latitude != null && longitude != null; - } catch (IOException | NumberFormatException | IndexOutOfBoundsException e) { - Timber.tag("UploadableFile"); - Timber.d(e); + fun hasLocation(): Boolean { + return try { + val exif = ExifInterface(file.absolutePath) + val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) + val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE) + latitude != null && longitude != null + } catch (e: IOException) { + Timber.tag("UploadableFile").d(e) + false } - return false; } /** - * Get filePath creation date from uri from EXIF - * - * @return + * Get filePath creation date from URI using EXIF data */ - private DateTimeWithSource getDateTimeFromExif() { - try { - ExifInterface exif = new ExifInterface(file.getAbsolutePath()); - // TAG_DATETIME returns the last edited date, we need TAG_DATETIME_ORIGINAL for creation date - // See issue https://github.com/commons-app/apps-android-commons/issues/1971 - String dateTimeSubString = exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL); - if (dateTimeSubString!=null) { //getAttribute may return null - String year = dateTimeSubString.substring(0,4); - String month = dateTimeSubString.substring(5,7); - String day = dateTimeSubString.substring(8,10); - // This date is stored as a string (not as a date), the rason is we don't want to include timezones - String dateCreatedString = String.format("%04d-%02d-%02d", Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); - if (dateCreatedString.length() == 10) { //yyyy-MM-dd format of date is expected - @SuppressLint("RestrictedApi") Long dateTime = exif.getDateTimeOriginal(); - if(dateTime != null){ - Date date = new Date(dateTime); - return new DateTimeWithSource(date, dateCreatedString, DateTimeWithSource.EXIF_SOURCE); + private fun getDateTimeFromExif(): DateTimeWithSource? { + return try { + val exif = ExifInterface(file.absolutePath) + val dateTimeSubString = exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL) + if (dateTimeSubString != null) { + val year = dateTimeSubString.substring(0, 4).toInt() + val month = dateTimeSubString.substring(5, 7).toInt() + val day = dateTimeSubString.substring(8, 10).toInt() + val dateCreatedString = "%04d-%02d-%02d".format(year, month, day) + if (dateCreatedString.length == 10) { + @SuppressLint("RestrictedApi") + val dateTime = exif.dateTimeOriginal + if (dateTime != null) { + val date = Date(dateTime) + return DateTimeWithSource(date, dateCreatedString, DateTimeWithSource.EXIF_SOURCE) } } } - } catch (IOException | NumberFormatException | IndexOutOfBoundsException e) { - Timber.tag("UploadableFile"); - Timber.d(e); + null + } catch (e: Exception) { + Timber.tag("UploadableFile").d(e) + null } - return null; } - @Override - public void writeToParcel(Parcel parcel, int i) { - parcel.writeParcelable(contentUri, 0); - parcel.writeSerializable(file); + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeParcelable(contentUri, flags) + parcel.writeSerializable(file) } - /** - * This class contains the epochDate along with the source from which it was extracted - */ - public class DateTimeWithSource { - public static final String CP_SOURCE = "contentProvider"; - public static final String EXIF_SOURCE = "exif"; - - private final long epochDate; - private String dateString; // this does not includes timezone information - private final String source; - - public DateTimeWithSource(long epochDate, String source) { - this.epochDate = epochDate; - this.source = source; + class DateTimeWithSource { + companion object { + const val CP_SOURCE = "contentProvider" + const val EXIF_SOURCE = "exif" } - public DateTimeWithSource(Date date, String source) { - this.epochDate = date.getTime(); - this.source = source; + val epochDate: Long + var dateString: String? = null + val source: String + + constructor(epochDate: Long, source: String) { + this.epochDate = epochDate + this.source = source } - public DateTimeWithSource(Date date, String dateString, String source) { - this.epochDate = date.getTime(); - this.dateString = dateString; - this.source = source; + constructor(date: Date, source: String) { + epochDate = date.time + this.source = source } - public long getEpochDate() { - return epochDate; + constructor(date: Date, dateString: String, source: String) { + epochDate = date.time + this.dateString = dateString + this.source = source + } + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): UploadableFile { + return UploadableFile(parcel) } - public String getDateString() { - return dateString; - } - - public String getSource() { - return source; + override fun newArray(size: Int): Array { + return arrayOfNulls(size) } } } diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt index 86ee5c4fe..91146059d 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt @@ -38,6 +38,7 @@ import fr.free.nrw.commons.campaigns.CampaignView import fr.free.nrw.commons.contributions.ContributionController import fr.free.nrw.commons.contributions.MainActivity import fr.free.nrw.commons.di.ApplicationlessInjection +import fr.free.nrw.commons.filepicker.FilePicker import fr.free.nrw.commons.kvstore.JsonKvStore import fr.free.nrw.commons.location.LocationServiceManager import fr.free.nrw.commons.logging.CommonsLogSender @@ -83,9 +84,17 @@ class SettingsFragment : PreferenceFragmentCompat() { private val cameraPickLauncherForResult: ActivityResultLauncher = registerForActivityResult(StartActivityForResult()) { result -> - contributionController.handleActivityResultWithCallback(requireActivity()) { callbacks -> - contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks) - } + contributionController.handleActivityResultWithCallback( + requireActivity(), + object: FilePicker.HandleActivityResult { + override fun onHandleActivityResult(callbacks: FilePicker.Callbacks) { + contributionController.onPictureReturnedFromCamera( + result, + requireActivity(), + callbacks + ) + } + }) } /** diff --git a/app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt index fc80252fc..62bd3f1a9 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/CustomSelectorUtils.kt @@ -63,7 +63,7 @@ class CustomSelectorUtils { fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact()) val sha1 = fileUtilsWrapper.getSHA1( - fileUtilsWrapper.getFileInputStream(uploadableFile.filePath), + fileUtilsWrapper.getFileInputStream(uploadableFile.getFilePath()), ) uploadableFile.file.delete() sha1