mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-31 14:53:59 +01:00 
			
		
		
		
	Migrated filepicker module from Java to Kotlin
This commit is contained in:
		
							parent
							
								
									5c2a68102e
								
							
						
					
					
						commit
						3e1208ee3d
					
				
					 11 changed files with 595 additions and 568 deletions
				
			
		|  | @ -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"; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										29
									
								
								app/src/main/java/fr/free/nrw/commons/filepicker/Costants.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/src/main/java/fr/free/nrw/commons/filepicker/Costants.kt
									
										
									
									
									
										Normal file
									
								
							|  | @ -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" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -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 |  * 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 |     override fun onImagePickerError(e: Exception, source: FilePicker.ImageSource, type: Int) {} | ||||||
|     public void onImagePickerError(Exception e, FilePicker.ImageSource source, int type) { |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     @Override |     override fun onCanceled(source: FilePicker.ImageSource, type: Int) {} | ||||||
|     public void onCanceled(FilePicker.ImageSource source, int type) { |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | @ -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 { | class ExtendedFileProvider: FileProvider() {} | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -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 const val KEY_PHOTO_URI = "photo_uri" | ||||||
|     private static final String KEY_VIDEO_URI = "video_uri"; |     private const val KEY_VIDEO_URI = "video_uri" | ||||||
|     private static final String KEY_LAST_CAMERA_PHOTO = "last_photo"; |     private const val KEY_LAST_CAMERA_PHOTO = "last_photo" | ||||||
|     private static final String KEY_LAST_CAMERA_VIDEO = "last_video"; |     private const val KEY_LAST_CAMERA_VIDEO = "last_video" | ||||||
|     private static final String KEY_TYPE = "type"; |     private const val KEY_TYPE = "type" | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns the uri of the clicked image so that it can be put in MediaStore |      * Returns the uri of the clicked image so that it can be put in MediaStore | ||||||
|      */ |      */ | ||||||
|     private static Uri createCameraPictureFile(@NonNull Context context) throws IOException { |     @Throws(IOException::class) | ||||||
|         File imagePath = PickedFiles.getCameraPicturesLocation(context); | 
 | ||||||
|         Uri uri = PickedFiles.getUriToFile(context, imagePath); |     @JvmStatic | ||||||
|         SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit(); |     private fun createCameraPictureFile(context: Context): Uri { | ||||||
|         editor.putString(KEY_PHOTO_URI, uri.toString()); |         val imagePath = PickedFiles.getCameraPicturesLocation(context) | ||||||
|         editor.putString(KEY_LAST_CAMERA_PHOTO, imagePath.toString()); |         val uri = PickedFiles.getUriToFile(context, imagePath) | ||||||
|         editor.apply(); |         val editor = PreferenceManager.getDefaultSharedPreferences(context).edit() | ||||||
|         return uri; |         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 |         // storing picked image type to shared preferences | ||||||
|         storeType(context, type); |         storeType(context, type) | ||||||
|         //Supported types are SVG, PNG and JPEG,GIF, TIFF, WebP, XCF |         // 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"}; |         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) |         return plainGalleryPickerIntent(openDocumentIntentPreferred) | ||||||
|                 .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery()) |             .putExtra( | ||||||
|             .putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); |                 Intent.EXTRA_ALLOW_MULTIPLE, | ||||||
|  |                 configuration(context).allowsMultiplePickingInGallery() | ||||||
|  |             ) | ||||||
|  |             .putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -63,107 +76,149 @@ public class FilePicker implements Constants { | ||||||
|      * @param type |      * @param type | ||||||
|      * @return Custom selector intent |      * @return Custom selector intent | ||||||
|      */ |      */ | ||||||
|     private static Intent createCustomSelectorIntent(@NonNull Context context, int type) { |     @JvmStatic | ||||||
|         storeType(context, type); |     private fun createCustomSelectorIntent(context: Context, type: Int): Intent { | ||||||
|         return new Intent(context, CustomSelectorActivity.class); |         storeType(context, type) | ||||||
|  |         return Intent(context, CustomSelectorActivity::class.java) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static Intent createCameraForImageIntent(@NonNull Context context, int type) { |     @JvmStatic | ||||||
|         storeType(context, type); |     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 { |         try { | ||||||
|             Uri capturedImageUri = createCameraPictureFile(context); |             val capturedImageUri = createCameraPictureFile(context) | ||||||
|             //We have to explicitly grant the write permission since Intent.setFlag works only on API Level >=20 |             // We have to explicitly grant the write permission since Intent.setFlag works only on API Level >=20 | ||||||
|             grantWritePermission(context, intent, capturedImageUri); |             grantWritePermission(context, intent, capturedImageUri) | ||||||
|             intent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri); |             intent.putExtra(MediaStore.EXTRA_OUTPUT, capturedImageUri) | ||||||
|         } catch (Exception e) { |         } catch (e: Exception) { | ||||||
|             e.printStackTrace(); |             e.printStackTrace() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return intent; |         return intent | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static void revokeWritePermission(@NonNull Context context, Uri uri) { |     @JvmStatic | ||||||
|         context.revokeUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); |     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) { |     @JvmStatic | ||||||
|         List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); |     private fun grantWritePermission(context: Context, intent: Intent, uri: Uri) { | ||||||
|         for (ResolveInfo resolveInfo : resInfoList) { |         val resInfoList = | ||||||
|             String packageName = resolveInfo.activityInfo.packageName; |             context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) | ||||||
|             context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); |         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) { |     @JvmStatic | ||||||
|         PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(KEY_TYPE, type).apply(); |     private fun storeType(context: Context, type: Int) { | ||||||
|  |         PreferenceManager.getDefaultSharedPreferences(context).edit().putInt(KEY_TYPE, type).apply() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static int restoreType(@NonNull Context context) { |     @JvmStatic | ||||||
|         return PreferenceManager.getDefaultSharedPreferences(context).getInt(KEY_TYPE, 0); |     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 |      * @param type Custom type of your choice, which will be returned with the images | ||||||
|      */ |      */ | ||||||
|     public static void openGallery(Activity activity, ActivityResultLauncher<Intent> resultLauncher, int type, boolean openDocumentIntentPreferred) { |     @JvmStatic | ||||||
|         Intent intent = createGalleryIntent(activity, type, openDocumentIntentPreferred); |     fun openGallery( | ||||||
|         resultLauncher.launch(intent); |         activity: Activity, | ||||||
|  |         resultLauncher: ActivityResultLauncher<Intent>, | ||||||
|  |         type: Int, | ||||||
|  |         openDocumentIntentPreferred: Boolean | ||||||
|  |     ) { | ||||||
|  |         val intent = createGalleryIntent(activity, type, openDocumentIntentPreferred) | ||||||
|  |         resultLauncher.launch(intent) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Opens Custom Selector |      * Opens Custom Selector | ||||||
|      */ |      */ | ||||||
|     public static void openCustomSelector(Activity activity, ActivityResultLauncher<Intent> resultLauncher, int type) { |     @JvmStatic | ||||||
|         Intent intent = createCustomSelectorIntent(activity, type); |     fun openCustomSelector( | ||||||
|         resultLauncher.launch(intent); |         activity: Activity, | ||||||
|  |         resultLauncher: ActivityResultLauncher<Intent>, | ||||||
|  |         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<Intent> resultLauncher, int type) { |     @JvmStatic | ||||||
|         Intent intent = createCameraForImageIntent(activity, type); |     fun openCameraForImage( | ||||||
|         resultLauncher.launch(intent); |         activity: Activity, | ||||||
|  |         resultLauncher: ActivityResultLauncher<Intent>, | ||||||
|  |         type: Int | ||||||
|  |     ) { | ||||||
|  |         val intent = createCameraForImageIntent(activity, type) | ||||||
|  |         resultLauncher.launch(intent) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |     @Throws(URISyntaxException::class) | ||||||
|     private static UploadableFile takenCameraPicture(Context context) throws URISyntaxException { |     @JvmStatic | ||||||
|         String lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_PHOTO, null); |     private fun takenCameraPicture(context: Context): UploadableFile? { | ||||||
|         if (lastCameraPhoto != null) { |         val lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context) | ||||||
|             return new UploadableFile(new File(lastCameraPhoto)); |             .getString(KEY_LAST_CAMERA_PHOTO, null) | ||||||
|  |         return if (lastCameraPhoto != null) { | ||||||
|  |             UploadableFile(File(lastCameraPhoto)) | ||||||
|         } else { |         } else { | ||||||
|             return null; |             null | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |     @Throws(URISyntaxException::class) | ||||||
|     private static UploadableFile takenCameraVideo(Context context) throws URISyntaxException { |     @JvmStatic | ||||||
|         String lastCameraPhoto = PreferenceManager.getDefaultSharedPreferences(context).getString(KEY_LAST_CAMERA_VIDEO, null); |     private fun takenCameraVideo(context: Context): UploadableFile? { | ||||||
|         if (lastCameraPhoto != null) { |         val lastCameraVideo = PreferenceManager.getDefaultSharedPreferences(context) | ||||||
|             return new UploadableFile(new File(lastCameraPhoto)); |             .getString(KEY_LAST_CAMERA_VIDEO, null) | ||||||
|  |         return if (lastCameraVideo != null) { | ||||||
|  |             UploadableFile(File(lastCameraVideo)) | ||||||
|         } else { |         } else { | ||||||
|             return null; |             null | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static List<UploadableFile> handleExternalImagesPicked(Intent data, Activity activity) { |     @JvmStatic | ||||||
|         try { |     fun handleExternalImagesPicked(data: Intent?, activity: Activity): List<UploadableFile> { | ||||||
|             return getFilesFromGalleryPictures(data, activity); |         return try { | ||||||
|         } catch (IOException | SecurityException e) { |             getFilesFromGalleryPictures(data, activity) | ||||||
|             e.printStackTrace(); |         } catch (e: IOException) { | ||||||
|  |             e.printStackTrace() | ||||||
|  |             emptyList() | ||||||
|  |         } catch (e: SecurityException) { | ||||||
|  |             e.printStackTrace() | ||||||
|  |             emptyList() | ||||||
|         } |         } | ||||||
|         return new ArrayList<>(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static boolean isPhoto(Intent data) { |     @JvmStatic | ||||||
|         return data == null || (data.getData() == null && data.getClipData() == null); |     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 |          * Asking for ACCESS_MEDIA_LOCATION at runtime solved the location-loss issue | ||||||
|          * in the custom selector in Contributions fragment. |          * in the custom selector in Contributions fragment. | ||||||
|  | @ -192,32 +247,40 @@ public class FilePicker implements Constants { | ||||||
|          * from EXIF. |          * from EXIF. | ||||||
|          * |          * | ||||||
|          */ |          */ | ||||||
|         Intent intent; |         val intent = if (openDocumentIntentPreferred) { | ||||||
|         if (openDocumentIntentPreferred) { |             Intent(Intent.ACTION_OPEN_DOCUMENT) | ||||||
|             intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); |  | ||||||
|         } else { |         } else { | ||||||
|             intent = new Intent(Intent.ACTION_GET_CONTENT); |             Intent(Intent.ACTION_GET_CONTENT) | ||||||
|         } |         } | ||||||
|         intent.setType("image/*"); |         intent.type = "image/*" | ||||||
|         return intent; |         return intent | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static void onPictureReturnedFromDocuments(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { |     @JvmStatic | ||||||
|         if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){ |     fun onPictureReturnedFromDocuments( | ||||||
|  |         result: ActivityResult, | ||||||
|  |         activity: Activity, | ||||||
|  |         callbacks: Callbacks | ||||||
|  |     ) { | ||||||
|  |         if (result.resultCode == Activity.RESULT_OK && !isPhoto(result.data)) { | ||||||
|             try { |             try { | ||||||
|                 Uri photoPath = result.getData().getData(); |                 val photoPath = result.data?.data | ||||||
|                 UploadableFile photoFile = PickedFiles.pickedExistingPicture(activity, photoPath); |                 val photoFile = PickedFiles.pickedExistingPicture(activity, photoPath!!) | ||||||
|                 callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); |                 callbacks.onImagesPicked( | ||||||
|  |                     singleFileList(photoFile), | ||||||
|  |                     ImageSource.DOCUMENTS, | ||||||
|  |                     restoreType(activity) | ||||||
|  |                 ) | ||||||
| 
 | 
 | ||||||
|                 if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { |                 if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { | ||||||
|                     PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)); |                     PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)) | ||||||
|                 } |                 } | ||||||
|             } catch (Exception e) { |             } catch (e: Exception) { | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace() | ||||||
|                 callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); |                 callbacks.onImagePickerError(e, ImageSource.DOCUMENTS, restoreType(activity)) | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); |             callbacks.onCanceled(ImageSource.DOCUMENTS, restoreType(activity)) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -225,131 +288,155 @@ public class FilePicker implements Constants { | ||||||
|      * onPictureReturnedFromCustomSelector. |      * onPictureReturnedFromCustomSelector. | ||||||
|      * Retrieve and forward the images to upload wizard through callback. |      * Retrieve and forward the images to upload wizard through callback. | ||||||
|      */ |      */ | ||||||
|     public static void onPictureReturnedFromCustomSelector(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { |     @JvmStatic | ||||||
|        if(result.getResultCode() == Activity.RESULT_OK){ |     fun onPictureReturnedFromCustomSelector( | ||||||
|            try { |         result: ActivityResult, | ||||||
|                List<UploadableFile> files = getFilesFromCustomSelector(result.getData(), activity); |         activity: Activity, | ||||||
|                callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); |         callbacks: Callbacks | ||||||
|            } catch (Exception e) { |     ) { | ||||||
|                e.printStackTrace(); |         if (result.resultCode == Activity.RESULT_OK) { | ||||||
|                callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); |             try { | ||||||
|            } |                 val files = getFilesFromCustomSelector(result.data, activity) | ||||||
|        } else { |                 callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity)) | ||||||
|            callbacks.onCanceled(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 |      * Get files from custom selector | ||||||
|      * Retrieve and process the selected images from the custom selector. |      * Retrieve and process the selected images from the custom selector. | ||||||
|      */ |      */ | ||||||
|     private static List<UploadableFile> getFilesFromCustomSelector(Intent data, Activity activity) throws  IOException, SecurityException { |     @Throws(IOException::class, SecurityException::class) | ||||||
|         List<UploadableFile> files = new ArrayList<>(); |     @JvmStatic | ||||||
|         ArrayList<Image> images = data.getParcelableArrayListExtra("Images"); |     private fun getFilesFromCustomSelector( | ||||||
|         for(Image image : images) { |         data: Intent?, | ||||||
|             Uri uri = image.getUri(); |         activity: Activity | ||||||
|             UploadableFile file = PickedFiles.pickedExistingPicture(activity, uri); |     ): List<UploadableFile> { | ||||||
|             files.add(file); |         val files = mutableListOf<UploadableFile>() | ||||||
|  |         val images = data?.getParcelableArrayListExtra<Image>("Images") | ||||||
|  |         images?.forEach { image -> | ||||||
|  |             val uri = image.uri | ||||||
|  |             val file = PickedFiles.pickedExistingPicture(activity, uri) | ||||||
|  |             files.add(file) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { |         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) { |     @JvmStatic | ||||||
|         if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){ |     fun onPictureReturnedFromGallery( | ||||||
|  |         result: ActivityResult, | ||||||
|  |         activity: Activity, | ||||||
|  |         callbacks: Callbacks | ||||||
|  |     ) { | ||||||
|  |         if (result.resultCode == Activity.RESULT_OK && !isPhoto(result.data)) { | ||||||
|             try { |             try { | ||||||
|                 List<UploadableFile> files = getFilesFromGalleryPictures(result.getData(), activity); |                 val files = getFilesFromGalleryPictures(result.data, activity) | ||||||
|                 callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity)); |                 callbacks.onImagesPicked(files, ImageSource.GALLERY, restoreType(activity)) | ||||||
|             } catch (Exception e) { |             } catch (e: Exception) { | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace() | ||||||
|                 callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity)); |                 callbacks.onImagePickerError(e, ImageSource.GALLERY, restoreType(activity)) | ||||||
|             } |             } | ||||||
|         } else{ |  | ||||||
|             callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static List<UploadableFile> getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException, SecurityException { |  | ||||||
|         List<UploadableFile> files = new ArrayList<>(); |  | ||||||
|         ClipData clipData = data.getClipData(); |  | ||||||
|         if (clipData == null) { |  | ||||||
|             Uri uri = data.getData(); |  | ||||||
|             UploadableFile file = PickedFiles.pickedExistingPicture(activity, uri); |  | ||||||
|             files.add(file); |  | ||||||
|         } else { |         } else { | ||||||
|             for (int i = 0; i < clipData.getItemCount(); i++) { |             callbacks.onCanceled(ImageSource.GALLERY, restoreType(activity)) | ||||||
|                 Uri uri = clipData.getItemAt(i).getUri(); |         } | ||||||
|                 UploadableFile file = PickedFiles.pickedExistingPicture(activity, uri); |     } | ||||||
|                 files.add(file); | 
 | ||||||
|  |     @Throws(IOException::class, SecurityException::class) | ||||||
|  |     @JvmStatic | ||||||
|  |     private fun getFilesFromGalleryPictures( | ||||||
|  |         data: Intent?, | ||||||
|  |         activity: Activity | ||||||
|  |     ): List<UploadableFile> { | ||||||
|  |         val files = mutableListOf<UploadableFile>() | ||||||
|  |         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()) { |         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) { |     @JvmStatic | ||||||
|         if(activityResult.getResultCode() == Activity.RESULT_OK){ |     fun onPictureReturnedFromCamera( | ||||||
|  |         activityResult: ActivityResult, | ||||||
|  |         activity: Activity, | ||||||
|  |         callbacks: Callbacks | ||||||
|  |     ) { | ||||||
|  |         if (activityResult.resultCode == Activity.RESULT_OK) { | ||||||
|             try { |             try { | ||||||
|                 String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null); |                 val lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity) | ||||||
|                 if (!TextUtils.isEmpty(lastImageUri)) { |                     .getString(KEY_PHOTO_URI, null) | ||||||
|                     revokeWritePermission(activity, Uri.parse(lastImageUri)); |                 if (!lastImageUri.isNullOrEmpty()) { | ||||||
|  |                     revokeWritePermission(activity, Uri.parse(lastImageUri)) | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 UploadableFile photoFile = FilePicker.takenCameraPicture(activity); |                 val photoFile = takenCameraPicture(activity) | ||||||
|                 List<UploadableFile> files = new ArrayList<>(); |                 val files = mutableListOf<UploadableFile>() | ||||||
|                 files.add(photoFile); |                 photoFile?.let { files.add(it) } | ||||||
| 
 | 
 | ||||||
|                 if (photoFile == null) { |                 if (photoFile == null) { | ||||||
|                     Exception e = new IllegalStateException("Unable to get the picture returned from camera"); |                     val e = IllegalStateException("Unable to get the picture returned from camera") | ||||||
|                     callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); |                     callbacks.onImagePickerError(e, ImageSource.CAMERA_IMAGE, restoreType(activity)) | ||||||
|                 } else { |                 } else { | ||||||
|                     if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) { |                     if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) { | ||||||
|                         PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)); |                         PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)) | ||||||
|                     } |                     } | ||||||
| 
 |                     callbacks.onImagesPicked(files, ImageSource.CAMERA_IMAGE, restoreType(activity)) | ||||||
|                     callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); |  | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 PreferenceManager.getDefaultSharedPreferences(activity) |                 PreferenceManager.getDefaultSharedPreferences(activity).edit() | ||||||
|                     .edit() |  | ||||||
|                     .remove(KEY_LAST_CAMERA_PHOTO) |                     .remove(KEY_LAST_CAMERA_PHOTO) | ||||||
|                     .remove(KEY_PHOTO_URI) |                     .remove(KEY_PHOTO_URI) | ||||||
|                     .apply(); |                     .apply() | ||||||
|             } catch (Exception e) { |             } catch (e: Exception) { | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace() | ||||||
|                 callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); |                 callbacks.onImagePickerError(e, ImageSource.CAMERA_IMAGE, restoreType(activity)) | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); |             callbacks.onCanceled(ImageSource.CAMERA_IMAGE, restoreType(activity)) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static FilePickerConfiguration configuration(@NonNull Context context) { |     @JvmStatic | ||||||
|         return new FilePickerConfiguration(context); |     fun configuration(context: Context): FilePickerConfiguration { | ||||||
|  |         return FilePickerConfiguration(context) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |     enum class ImageSource { | ||||||
|     public enum ImageSource { |  | ||||||
|         GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO, CUSTOM_SELECTOR |         GALLERY, DOCUMENTS, CAMERA_IMAGE, CAMERA_VIDEO, CUSTOM_SELECTOR | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public interface Callbacks { |     interface Callbacks { | ||||||
|         void onImagePickerError(Exception e, FilePicker.ImageSource source, int type); |         fun onImagePickerError(e: Exception, source: ImageSource, type: Int) | ||||||
| 
 | 
 | ||||||
|         void onImagesPicked(@NonNull List<UploadableFile> imageFiles, FilePicker.ImageSource source, int type); |         fun onImagesPicked(imageFiles: List<UploadableFile>, source: ImageSource, type: Int) | ||||||
| 
 | 
 | ||||||
|         void onCanceled(FilePicker.ImageSource source, int type); |         fun onCanceled(source: ImageSource, type: Int) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public interface HandleActivityResult{ |     interface HandleActivityResult { | ||||||
|         void onHandleActivityResult(FilePicker.Callbacks callbacks); |         fun onHandleActivityResult(callbacks: Callbacks) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -1,44 +1,46 @@ | ||||||
| package fr.free.nrw.commons.filepicker; | package fr.free.nrw.commons.filepicker | ||||||
| 
 | 
 | ||||||
| import android.content.Context; | import android.content.Context | ||||||
| import androidx.preference.PreferenceManager; | import androidx.preference.PreferenceManager | ||||||
| 
 | 
 | ||||||
| public class FilePickerConfiguration implements Constants { | class FilePickerConfiguration( | ||||||
|  |     private val context: Context | ||||||
|  | ): Constants { | ||||||
| 
 | 
 | ||||||
|     private Context context; |     fun setAllowMultiplePickInGallery(allowMultiple: Boolean): FilePickerConfiguration { | ||||||
| 
 |  | ||||||
|     FilePickerConfiguration(Context context) { |  | ||||||
|         this.context = context; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public FilePickerConfiguration setAllowMultiplePickInGallery(boolean allowMultiple) { |  | ||||||
|         PreferenceManager.getDefaultSharedPreferences(context).edit() |         PreferenceManager.getDefaultSharedPreferences(context).edit() | ||||||
|                 .putBoolean(BundleKeys.ALLOW_MULTIPLE, allowMultiple) |             .putBoolean(Constants.BundleKeys.ALLOW_MULTIPLE, allowMultiple) | ||||||
|                 .apply(); |             .apply() | ||||||
|         return this; |         return this | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public FilePickerConfiguration setCopyTakenPhotosToPublicGalleryAppFolder(boolean copy) { |     fun setCopyTakenPhotosToPublicGalleryAppFolder(copy: Boolean): FilePickerConfiguration { | ||||||
|         PreferenceManager.getDefaultSharedPreferences(context).edit() |         PreferenceManager.getDefaultSharedPreferences(context).edit() | ||||||
|                 .putBoolean(BundleKeys.COPY_TAKEN_PHOTOS, copy) |             .putBoolean(Constants.BundleKeys.COPY_TAKEN_PHOTOS, copy) | ||||||
|                 .apply(); |             .apply() | ||||||
|         return this; |         return this | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String getFolderName() { |     fun getFolderName(): String { | ||||||
|         return PreferenceManager.getDefaultSharedPreferences(context).getString(BundleKeys.FOLDER_NAME, DEFAULT_FOLDER_NAME); |         return PreferenceManager.getDefaultSharedPreferences(context) | ||||||
|  |             .getString( | ||||||
|  |                 Constants.BundleKeys.FOLDER_NAME, | ||||||
|  |                 Constants.DEFAULT_FOLDER_NAME | ||||||
|  |             ) ?: Constants.DEFAULT_FOLDER_NAME | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean allowsMultiplePickingInGallery() { |     fun allowsMultiplePickingInGallery(): Boolean { | ||||||
|         return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.ALLOW_MULTIPLE, false); |         return PreferenceManager.getDefaultSharedPreferences(context) | ||||||
|  |             .getBoolean(Constants.BundleKeys.ALLOW_MULTIPLE, false) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean shouldCopyTakenPhotosToPublicGalleryAppFolder() { |     fun shouldCopyTakenPhotosToPublicGalleryAppFolder(): Boolean { | ||||||
|         return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.COPY_TAKEN_PHOTOS, false); |         return PreferenceManager.getDefaultSharedPreferences(context) | ||||||
|  |             .getBoolean(Constants.BundleKeys.COPY_TAKEN_PHOTOS, false) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean shouldCopyPickedImagesToPublicGalleryAppFolder() { |     fun shouldCopyPickedImagesToPublicGalleryAppFolder(): Boolean { | ||||||
|         return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BundleKeys.COPY_PICKED_IMAGES, false); |         return PreferenceManager.getDefaultSharedPreferences(context) | ||||||
|  |             .getBoolean(Constants.BundleKeys.COPY_PICKED_IMAGES, false) | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | @ -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(); |         @JvmStatic | ||||||
| 
 |         fun getExtensionFromMimeType(mimeType: String): String? { | ||||||
|     private static final Map<String, String> sMimeTypeToExtensionMap = |             val result = sMimeTypeToExtensionMap[mimeType] | ||||||
|             ImmutableMap.of( |             if (result != null) { | ||||||
|                     "image/heif", "heif", |                 return result | ||||||
|                     "image/heic", "heic"); |             } | ||||||
| 
 |             return sMimeTypeMap.getExtensionFromMimeType(mimeType) | ||||||
|     public static String getExtensionFromMimeType(String mimeType) { |  | ||||||
|         String result = sMimeTypeToExtensionMap.get(mimeType); |  | ||||||
|         if (result != null) { |  | ||||||
|             return result; |  | ||||||
|         } |         } | ||||||
|         return sMimeTypeMap.getExtensionFromMimeType(mimeType); |  | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | @ -1,67 +1,62 @@ | ||||||
| package fr.free.nrw.commons.filepicker; | package fr.free.nrw.commons.filepicker | ||||||
| 
 | 
 | ||||||
| import android.content.ContentResolver; | import android.content.ContentResolver | ||||||
| import android.content.Context; | import android.content.Context | ||||||
| import android.media.MediaScannerConnection; | import android.media.MediaScannerConnection | ||||||
| import android.net.Uri; | import android.net.Uri | ||||||
| import android.os.Environment; | import android.os.Environment | ||||||
| import android.webkit.MimeTypeMap; | 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. |  * PickedFiles. | ||||||
|  * Process the upload items. |  * Process the upload items. | ||||||
|  */ |  */ | ||||||
| public class PickedFiles implements Constants { | object PickedFiles : Constants { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get Folder Name |      * Get Folder Name | ||||||
|      * @param context |  | ||||||
|      * @return default application folder name. |      * @return default application folder name. | ||||||
|      */ |      */ | ||||||
|     private static String getFolderName(@NonNull Context context) { |     private fun getFolderName(context: Context): String { | ||||||
|         return FilePicker.configuration(context).getFolderName(); |         return FilePicker.configuration(context).getFolderName() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * tempImageDirectory |      * tempImageDirectory | ||||||
|      * @param context |  | ||||||
|      * @return temporary image directory to copy and perform exif changes. |      * @return temporary image directory to copy and perform exif changes. | ||||||
|      */ |      */ | ||||||
|     private static File tempImageDirectory(@NonNull Context context) { |     private fun tempImageDirectory(context: Context): File { | ||||||
|         File privateTempDir = new File(context.getCacheDir(), DEFAULT_FOLDER_NAME); |         val privateTempDir = File(context.cacheDir, DEFAULT_FOLDER_NAME) | ||||||
|         if (!privateTempDir.exists()) privateTempDir.mkdirs(); |         if (!privateTempDir.exists()) privateTempDir.mkdirs() | ||||||
|         return privateTempDir; |         return privateTempDir | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * writeToFile |      * writeToFile | ||||||
|      * writes inputStream data to the destination file. |      * Writes inputStream data to the destination file. | ||||||
|      * @param in input stream of source file. |  | ||||||
|      * @param file destination file |  | ||||||
|      */ |      */ | ||||||
|     private static void writeToFile(InputStream in, File file) throws IOException { |     @Throws(IOException::class) | ||||||
|         try (OutputStream out = new FileOutputStream(file)) { |     private fun writeToFile(inputStream: InputStream, file: File) { | ||||||
|             byte[] buf = new byte[1024]; |         inputStream.use { input -> | ||||||
|             int len; |             FileOutputStream(file).use { output -> | ||||||
|             while ((len = in.read(buf)) > 0) { |                 val buffer = ByteArray(1024) | ||||||
|                 out.write(buf, 0, len); |                 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. |      * Copy file function. | ||||||
|      * Copies source file to destination file. |      * 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 { |     @Throws(IOException::class) | ||||||
|         try (InputStream in = new FileInputStream(src)) { |     private fun copyFile(src: File, dst: File) { | ||||||
|             writeToFile(in, dst); |         FileInputStream(src).use { inputStream -> | ||||||
|  |             writeToFile(inputStream, dst) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Copy files in separate thread. |      * Copy files in separate thread. | ||||||
|      * Copies all the uploadable files to the temp image folder on background 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<UploadableFile> filesToCopy) { |     fun copyFilesInSeparateThread(context: Context, filesToCopy: List<UploadableFile>) { | ||||||
|         new Thread(() -> { |         Thread { | ||||||
|             List<File> copiedFiles = new ArrayList<>(); |             val copiedFiles = mutableListOf<File>() | ||||||
|             int i = 1; |             var index = 1 | ||||||
|             for (UploadableFile uploadableFile : filesToCopy) { |             filesToCopy.forEach { uploadableFile -> | ||||||
|                 File fileToCopy = uploadableFile.getFile(); |                 val fileToCopy = uploadableFile.file | ||||||
|                 File dstDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), getFolderName(context)); |                 val dstDir = File( | ||||||
|                 if (!dstDir.exists()) { |                     Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), | ||||||
|                     dstDir.mkdirs(); |                     getFolderName(context) | ||||||
|                 } |                 ) | ||||||
|  |                 if (!dstDir.exists()) dstDir.mkdirs() | ||||||
| 
 | 
 | ||||||
|                 String[] filenameSplit = fileToCopy.getName().split("\\."); |                 val filenameSplit = fileToCopy.name.split(".") | ||||||
|                 String extension = "." + filenameSplit[filenameSplit.length - 1]; |                 val extension = ".${filenameSplit.last()}" | ||||||
|                 String filename = String.format("IMG_%s_%d.%s", new SimpleDateFormat("yyyyMMdd_HHmmss").format(Calendar.getInstance().getTime()), i, extension); |                 val filename = "IMG_${SimpleDateFormat( | ||||||
|  |                     "yyyyMMdd_HHmmss", | ||||||
|  |                     Locale.getDefault()).format(Date())}_$index$extension" | ||||||
|  |                 val dstFile = File(dstDir, filename) | ||||||
| 
 | 
 | ||||||
|                 File dstFile = new File(dstDir, filename); |  | ||||||
|                 try { |                 try { | ||||||
|                     dstFile.createNewFile(); |                     dstFile.createNewFile() | ||||||
|                     copyFile(fileToCopy, dstFile); |                     copyFile(fileToCopy, dstFile) | ||||||
|                     copiedFiles.add(dstFile); |                     copiedFiles.add(dstFile) | ||||||
|                 } catch (IOException e) { |                 } catch (e: IOException) { | ||||||
|                     e.printStackTrace(); |                     e.printStackTrace() | ||||||
|                 } |                 } | ||||||
|                 i++; |                 index++ | ||||||
|             } |             } | ||||||
|             scanCopiedImages(context, copiedFiles); |             scanCopiedImages(context, copiedFiles) | ||||||
|         }).run(); |         }.start() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * singleFileList. |      * singleFileList | ||||||
|      * converts a single uploadableFile to list of uploadableFile. |      * Converts a single uploadableFile to list of uploadableFile. | ||||||
|      * @param file uploadable file |  | ||||||
|      * @return |  | ||||||
|      */ |      */ | ||||||
|     static List<UploadableFile> singleFileList(UploadableFile file) { |     fun singleFileList(file: UploadableFile): List<UploadableFile> { | ||||||
|         List<UploadableFile> list = new ArrayList<>(); |         return listOf(file) | ||||||
|         list.add(file); |  | ||||||
|         return list; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * ScanCopiedImages |      * ScanCopiedImages | ||||||
|      * Scan copied images metadata using media scanner. |      * Scans copied images metadata using media scanner. | ||||||
|      * @param context |  | ||||||
|      * @param copiedImages copied images list. |  | ||||||
|      */ |      */ | ||||||
|     static void scanCopiedImages(Context context, List<File> copiedImages) { |     fun scanCopiedImages(context: Context, copiedImages: List<File>) { | ||||||
|         String[] paths = new String[copiedImages.size()]; |         val paths = copiedImages.map { it.toString() }.toTypedArray() | ||||||
|         for (int i = 0; i < copiedImages.size(); i++) { |         MediaScannerConnection.scanFile(context, paths, null) { path, uri -> | ||||||
|             paths[i] = copiedImages.get(i).toString(); |             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 |      * pickedExistingPicture | ||||||
|      * convert the image into uploadable file. |      * Convert the image into uploadable file. | ||||||
|      * @param photoUri Uri of the image. |  | ||||||
|      * @return Uploadable file ready for tag redaction. |  | ||||||
|      */ |      */ | ||||||
|     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 |     @Throws(IOException::class, SecurityException::class) | ||||||
|         File directory = tempImageDirectory(context); |     fun pickedExistingPicture(context: Context, photoUri: Uri): UploadableFile { | ||||||
|         File photoFile = new File(directory, UUID.randomUUID().toString() + "." + getMimeType(context, photoUri)); |         val directory = tempImageDirectory(context) | ||||||
|  |         val mimeType = getMimeType(context, photoUri) | ||||||
|  |         val photoFile = File(directory, "${UUID.randomUUID()}.$mimeType") | ||||||
|  | 
 | ||||||
|         if (photoFile.createNewFile()) { |         if (photoFile.createNewFile()) { | ||||||
|             try (InputStream pictureInputStream = context.getContentResolver().openInputStream(photoUri)) { |             context.contentResolver.openInputStream(photoUri)?.use { inputStream -> | ||||||
|                 writeToFile(pictureInputStream, photoFile); |                 writeToFile(inputStream, photoFile) | ||||||
|             } |             } | ||||||
|         } else { |         } 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 |      * getCameraPictureLocation | ||||||
|      */ |      */ | ||||||
|     static File getCameraPicturesLocation(@NonNull Context context) throws IOException { |     @Throws(IOException::class) | ||||||
|         File dir = tempImageDirectory(context); |     fun getCameraPicturesLocation(context: Context): File { | ||||||
|         return File.createTempFile(UUID.randomUUID().toString(), ".jpg", dir); |         val dir = tempImageDirectory(context) | ||||||
|  |         return File.createTempFile(UUID.randomUUID().toString(), ".jpg", dir) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * To find out the extension of required object in given uri |      * To find out the extension of the required object in a given uri | ||||||
|      * Solution by http://stackoverflow.com/a/36514823/1171484 |  | ||||||
|      */ |      */ | ||||||
|     private static String getMimeType(@NonNull Context context, @NonNull Uri uri) { |     private fun getMimeType(context: Context, uri: Uri): String { | ||||||
|         String extension; |         return if (uri.scheme == ContentResolver.SCHEME_CONTENT) { | ||||||
| 
 |             context.contentResolver.getType(uri) | ||||||
|         //Check uri format to avoid null |                 ?.let { MimeTypeMapWrapper.getExtensionFromMimeType(it) } | ||||||
|         if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { |  | ||||||
|             //If scheme is a content |  | ||||||
|             extension = MimeTypeMapWrapper.getExtensionFromMimeType(context.getContentResolver().getType(uri)); |  | ||||||
|         } else { |         } else { | ||||||
|             //If scheme is a File |             MimeTypeMap.getFileExtensionFromUrl( | ||||||
|             //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. |                 Uri.fromFile(uri.path?.let { File(it) }).toString() | ||||||
|             extension = MimeTypeMap.getFileExtensionFromUrl(Uri.fromFile(new File(uri.getPath())).toString()); |             ) | ||||||
| 
 |         } ?: "jpg" // Default to jpg if unable to determine type | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return extension; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -199,10 +176,9 @@ public class PickedFiles implements Constants { | ||||||
|      * @param file get uri of file |      * @param file get uri of file | ||||||
|      * @return uri of requested file. |      * @return uri of requested file. | ||||||
|      */ |      */ | ||||||
|     static Uri getUriToFile(@NonNull Context context, @NonNull File file) { |     fun getUriToFile(context: Context, file: File): Uri { | ||||||
|         String packageName = context.getApplicationContext().getPackageName(); |         val packageName = context.applicationContext.packageName | ||||||
|         String authority = packageName + ".provider"; |         val authority = "$packageName.provider" | ||||||
|         return FileProvider.getUriForFile(context, authority, file); |         return FileProvider.getUriForFile(context, authority, file) | ||||||
|     } |     } | ||||||
| 
 | } | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,213 +1,168 @@ | ||||||
| package fr.free.nrw.commons.filepicker; | package fr.free.nrw.commons.filepicker | ||||||
| 
 | 
 | ||||||
| import android.annotation.SuppressLint; | import android.annotation.SuppressLint | ||||||
| import android.content.Context; | import android.content.Context | ||||||
| import android.database.Cursor; | import android.database.Cursor | ||||||
| import android.net.Uri; | import android.net.Uri | ||||||
| import android.os.Parcel; | import android.os.Parcel | ||||||
| import android.os.Parcelable; | 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 fr.free.nrw.commons.upload.FileUtils | ||||||
| import java.io.File; | import java.io.File | ||||||
| import java.io.IOException; | import java.io.IOException | ||||||
| import java.util.Date; | import java.util.Date | ||||||
| import timber.log.Timber; | import timber.log.Timber | ||||||
| 
 | 
 | ||||||
| public class UploadableFile implements Parcelable { | class UploadableFile : Parcelable { | ||||||
|     public static final Creator<UploadableFile> CREATOR = new Creator<UploadableFile>() { |  | ||||||
|         @Override |  | ||||||
|         public UploadableFile createFromParcel(Parcel in) { |  | ||||||
|             return new UploadableFile(in); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         @Override |     val contentUri: Uri | ||||||
|         public UploadableFile[] newArray(int size) { |     val file: File | ||||||
|             return new UploadableFile[size]; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
| 
 | 
 | ||||||
|     private final Uri contentUri; |     constructor(contentUri: Uri, file: File) { | ||||||
|     private final File file; |         this.contentUri = contentUri | ||||||
| 
 |         this.file = file | ||||||
|     public UploadableFile(Uri contentUri, File file) { |  | ||||||
|         this.contentUri = contentUri; |  | ||||||
|         this.file = file; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public UploadableFile(File file) { |     constructor(file: File) { | ||||||
|         this.file = file; |         this.file = file | ||||||
|         this.contentUri = Uri.fromFile(new File(file.getPath())); |         this.contentUri = Uri.fromFile(File(file.path)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public UploadableFile(Parcel in) { |     private constructor(parcel: Parcel) { | ||||||
|         this.contentUri = in.readParcelable(Uri.class.getClassLoader()); |         contentUri = parcel.readParcelable(Uri::class.java.classLoader)!! | ||||||
|         file = (File) in.readSerializable(); |         file = parcel.readSerializable() as File | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Uri getContentUri() { |     fun getFilePath(): String { | ||||||
|         return contentUri; |         return file.path | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public File getFile() { |     fun getMediaUri(): Uri { | ||||||
|         return file; |         return Uri.parse(getFilePath()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String getFilePath() { |     fun getMimeType(context: Context): String? { | ||||||
|         return file.getPath(); |         return FileUtils.getMimeType(context, getMediaUri()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public Uri getMediaUri() { |     override fun describeContents(): Int = 0 | ||||||
|         return Uri.parse(getFilePath()); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     public String getMimeType(Context context) { |     /** | ||||||
|         return FileUtils.getMimeType(context, getMediaUri()); |      * First try to get the file creation date from EXIF, else fall back to Content Provider (CP) | ||||||
|     } |      */ | ||||||
| 
 |     fun getFileCreatedDate(context: Context): DateTimeWithSource? { | ||||||
|     @Override |         return getDateTimeFromExif() ?: getFileCreatedDateFromCP(context) | ||||||
|     public int describeContents() { |  | ||||||
|         return 0; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * First try to get the file creation date from EXIF else fall back to CP |      * Get filePath creation date from URI using all possible content providers | ||||||
|      * @param context |  | ||||||
|      * @return |  | ||||||
|      */ |      */ | ||||||
|     @Nullable |     private fun getFileCreatedDateFromCP(context: Context): DateTimeWithSource? { | ||||||
|     public DateTimeWithSource getFileCreatedDate(Context context) { |         return try { | ||||||
|         DateTimeWithSource dateTimeFromExif = getDateTimeFromExif(); |             val cursor: Cursor? = context.contentResolver.query(contentUri, null, null, null, null) | ||||||
|         if (dateTimeFromExif == null) { |             cursor?.use { | ||||||
|             return getFileCreatedDateFromCP(context); |                 val lastModifiedColumnIndex = cursor | ||||||
|         } else { |                     .getColumnIndex( | ||||||
|             return dateTimeFromExif; |                         "last_modified" | ||||||
|         } |                     ).takeIf { it != -1 } | ||||||
|     } |                     ?: cursor.getColumnIndex("datetaken") | ||||||
| 
 |                 if (lastModifiedColumnIndex == -1) return null // No valid column found | ||||||
|     /** |                 cursor.moveToFirst() | ||||||
|      * Get filePath creation date from uri from all possible content providers |                 DateTimeWithSource( | ||||||
|      * |                     cursor.getLong( | ||||||
|      * @return |                         lastModifiedColumnIndex | ||||||
|      */ |                     ), DateTimeWithSource.CP_SOURCE) | ||||||
|     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 |  | ||||||
|             } |             } | ||||||
|             //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 |         } catch (e: Exception) { | ||||||
|             int lastModifiedColumnIndex = cursor.getColumnIndex("last_modified");//If gallery is opened from in app |             Timber.tag("UploadableFile").d(e) | ||||||
|             if (lastModifiedColumnIndex == -1) { |             null | ||||||
|                 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 |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Indicate whether the EXIF contains the location (both latitude and longitude). |      * Indicates whether the EXIF contains the location (both latitude and longitude). | ||||||
|      * |  | ||||||
|      * @return whether the location exists for the file's EXIF |  | ||||||
|      */ |      */ | ||||||
|     public boolean hasLocation() { |     fun hasLocation(): Boolean { | ||||||
|         try { |         return try { | ||||||
|             ExifInterface exif = new ExifInterface(file.getAbsolutePath()); |             val exif = ExifInterface(file.absolutePath) | ||||||
|             final String latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE); |             val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) | ||||||
|             final String longitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE); |             val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE) | ||||||
|             return latitude != null && longitude != null; |             latitude != null && longitude != null | ||||||
|         } catch (IOException | NumberFormatException | IndexOutOfBoundsException e) { |         } catch (e: IOException) { | ||||||
|             Timber.tag("UploadableFile"); |             Timber.tag("UploadableFile").d(e) | ||||||
|             Timber.d(e); |             false | ||||||
|         } |         } | ||||||
|         return false; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get filePath creation date from uri from EXIF |      * Get filePath creation date from URI using EXIF data | ||||||
|      * |  | ||||||
|      * @return |  | ||||||
|      */ |      */ | ||||||
|     private DateTimeWithSource getDateTimeFromExif() { |     private fun getDateTimeFromExif(): DateTimeWithSource? { | ||||||
|         try { |         return try { | ||||||
|             ExifInterface exif = new ExifInterface(file.getAbsolutePath()); |             val exif = ExifInterface(file.absolutePath) | ||||||
|             // TAG_DATETIME returns the last edited date, we need TAG_DATETIME_ORIGINAL for creation date |             val dateTimeSubString = exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL) | ||||||
|             // See issue https://github.com/commons-app/apps-android-commons/issues/1971 |             if (dateTimeSubString != null) { | ||||||
|             String dateTimeSubString = exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL); |                 val year = dateTimeSubString.substring(0, 4).toInt() | ||||||
|             if (dateTimeSubString!=null) { //getAttribute may return null |                 val month = dateTimeSubString.substring(5, 7).toInt() | ||||||
|                 String year = dateTimeSubString.substring(0,4); |                 val day = dateTimeSubString.substring(8, 10).toInt() | ||||||
|                 String month = dateTimeSubString.substring(5,7); |                 val dateCreatedString = "%04d-%02d-%02d".format(year, month, day) | ||||||
|                 String day = dateTimeSubString.substring(8,10); |                 if (dateCreatedString.length == 10) { | ||||||
|                 // This date is stored as a string (not as a date), the rason is we don't want to include timezones |                     @SuppressLint("RestrictedApi") | ||||||
|                 String dateCreatedString = String.format("%04d-%02d-%02d", Integer.parseInt(year), Integer.parseInt(month), Integer.parseInt(day)); |                     val dateTime = exif.dateTimeOriginal | ||||||
|                 if (dateCreatedString.length() == 10) { //yyyy-MM-dd format of date is expected |                     if (dateTime != null) { | ||||||
|                     @SuppressLint("RestrictedApi") Long dateTime = exif.getDateTimeOriginal(); |                         val date = Date(dateTime) | ||||||
|                     if(dateTime != null){ |                         return DateTimeWithSource(date, dateCreatedString, DateTimeWithSource.EXIF_SOURCE) | ||||||
|                         Date date = new Date(dateTime); |  | ||||||
|                         return new DateTimeWithSource(date, dateCreatedString, DateTimeWithSource.EXIF_SOURCE); |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } catch (IOException | NumberFormatException | IndexOutOfBoundsException e) { |             null | ||||||
|             Timber.tag("UploadableFile"); |         } catch (e: Exception) { | ||||||
|             Timber.d(e); |             Timber.tag("UploadableFile").d(e) | ||||||
|  |             null | ||||||
|         } |         } | ||||||
|         return null; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     override fun writeToParcel(parcel: Parcel, flags: Int) { | ||||||
|     public void writeToParcel(Parcel parcel, int i) { |         parcel.writeParcelable(contentUri, flags) | ||||||
|         parcel.writeParcelable(contentUri, 0); |         parcel.writeSerializable(file) | ||||||
|         parcel.writeSerializable(file); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     class DateTimeWithSource { | ||||||
|      * This class contains the epochDate along with the source from which it was extracted |         companion object { | ||||||
|      */ |             const val CP_SOURCE = "contentProvider" | ||||||
|     public class DateTimeWithSource { |             const val EXIF_SOURCE = "exif" | ||||||
|         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; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public DateTimeWithSource(Date date, String source) { |         val epochDate: Long | ||||||
|             this.epochDate = date.getTime(); |         var dateString: String? = null | ||||||
|             this.source = source; |         val source: String | ||||||
|  | 
 | ||||||
|  |         constructor(epochDate: Long, source: String) { | ||||||
|  |             this.epochDate = epochDate | ||||||
|  |             this.source = source | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public DateTimeWithSource(Date date, String dateString, String source) { |         constructor(date: Date, source: String) { | ||||||
|             this.epochDate = date.getTime(); |             epochDate = date.time | ||||||
|             this.dateString = dateString; |             this.source = source | ||||||
|             this.source = source; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public long getEpochDate() { |         constructor(date: Date, dateString: String, source: String) { | ||||||
|             return epochDate; |             epochDate = date.time | ||||||
|  |             this.dateString = dateString | ||||||
|  |             this.source = source | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     companion object CREATOR : Parcelable.Creator<UploadableFile> { | ||||||
|  |         override fun createFromParcel(parcel: Parcel): UploadableFile { | ||||||
|  |             return UploadableFile(parcel) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public String getDateString() { |         override fun newArray(size: Int): Array<UploadableFile?> { | ||||||
|             return dateString; |             return arrayOfNulls(size) | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public String getSource() { |  | ||||||
|             return source; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -38,6 +38,7 @@ import fr.free.nrw.commons.campaigns.CampaignView | ||||||
| import fr.free.nrw.commons.contributions.ContributionController | import fr.free.nrw.commons.contributions.ContributionController | ||||||
| import fr.free.nrw.commons.contributions.MainActivity | import fr.free.nrw.commons.contributions.MainActivity | ||||||
| import fr.free.nrw.commons.di.ApplicationlessInjection | 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.kvstore.JsonKvStore | ||||||
| import fr.free.nrw.commons.location.LocationServiceManager | import fr.free.nrw.commons.location.LocationServiceManager | ||||||
| import fr.free.nrw.commons.logging.CommonsLogSender | import fr.free.nrw.commons.logging.CommonsLogSender | ||||||
|  | @ -83,9 +84,17 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
| 
 | 
 | ||||||
|     private val cameraPickLauncherForResult: ActivityResultLauncher<Intent> = |     private val cameraPickLauncherForResult: ActivityResultLauncher<Intent> = | ||||||
|         registerForActivityResult(StartActivityForResult()) { result -> |         registerForActivityResult(StartActivityForResult()) { result -> | ||||||
|         contributionController.handleActivityResultWithCallback(requireActivity()) { callbacks -> |         contributionController.handleActivityResultWithCallback( | ||||||
|             contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks) |             requireActivity(), | ||||||
|         } |             object: FilePicker.HandleActivityResult { | ||||||
|  |                 override fun onHandleActivityResult(callbacks: FilePicker.Callbacks) { | ||||||
|  |                     contributionController.onPictureReturnedFromCamera( | ||||||
|  |                         result, | ||||||
|  |                         requireActivity(), | ||||||
|  |                         callbacks | ||||||
|  |                     ) | ||||||
|  |                 } | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ class CustomSelectorUtils { | ||||||
|                 fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact()) |                 fileProcessor.redactExifTags(exifInterface, fileProcessor.getExifTagsToRedact()) | ||||||
|                 val sha1 = |                 val sha1 = | ||||||
|                     fileUtilsWrapper.getSHA1( |                     fileUtilsWrapper.getSHA1( | ||||||
|                         fileUtilsWrapper.getFileInputStream(uploadableFile.filePath), |                         fileUtilsWrapper.getFileInputStream(uploadableFile.getFilePath()), | ||||||
|                     ) |                     ) | ||||||
|                 uploadableFile.file.delete() |                 uploadableFile.file.delete() | ||||||
|                 sha1 |                 sha1 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Saifuddin
						Saifuddin