diff --git a/app/build.gradle b/app/build.gradle index 0efd85dff..146b1ea7a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -91,8 +91,9 @@ dependencies { implementation "androidx.cardview:cardview:1.0.0" implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "androidx.exifinterface:exifinterface:1.0.0" - //metadata extractor - implementation 'com.drewnoakes:metadata-extractor:2.11.0' + + //swipe_layout + implementation 'com.daimajia.swipelayout:library:1.2.0@aar' } android { diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java index 80109c6f5..41b8c3551 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.java @@ -1,18 +1,15 @@ 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 androidx.exifinterface.media.ExifInterface; import androidx.annotation.Nullable; -import com.drew.imaging.ImageMetadataReader; -import com.drew.imaging.ImageProcessingException; -import com.drew.metadata.Metadata; -import com.drew.metadata.exif.ExifSubIFDDirectory; - import java.io.File; import java.io.IOException; import java.util.Date; @@ -125,16 +122,13 @@ public class UploadableFile implements Parcelable { * @return */ private DateTimeWithSource getDateTimeFromExif() { - Metadata metadata; try { - metadata = ImageMetadataReader.readMetadata(file); - ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); - if (directory!=null && directory.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) { - Date date = directory.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL); + ExifInterface exif = new ExifInterface(file.getAbsolutePath()); + @SuppressLint("RestrictedApi") long dateTime = exif.getDateTime(); + if (dateTime != -1) { + Date date = new Date(dateTime); return new DateTimeWithSource(date, DateTimeWithSource.EXIF_SOURCE); } - } catch (ImageProcessingException e) { - e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/EXIFReader.java b/app/src/main/java/fr/free/nrw/commons/upload/EXIFReader.java index 70e568956..0dd13acea 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/EXIFReader.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/EXIFReader.java @@ -1,60 +1,36 @@ package fr.free.nrw.commons.upload; -import com.drew.imaging.ImageMetadataReader; -import com.drew.imaging.ImageProcessingException; -import com.drew.metadata.Directory; -import com.drew.metadata.Metadata; - -import java.io.File; -import java.io.IOException; +import androidx.exifinterface.media.ExifInterface; import javax.inject.Inject; import javax.inject.Singleton; import fr.free.nrw.commons.utils.ImageUtils; import io.reactivex.Single; -import timber.log.Timber; /** -* We try to avoid copyright violations in commons app. -* For doing that we read EXIF data using the library metadata-reader -* If an image doesn't have any EXIF Directoris in it's metadata then the image is an -* internet download image(and not the one taken using phone's camera) */ - + * We try to minimize uploads from the Commons app that might be copyright violations. + * If an image does not have any Exif metadata, then it was likely downloaded from the internet, + * and is probably not an original work by the user. We detect these kinds of images by looking + * for the presence of some basic Exif metadata. + */ @Singleton public class EXIFReader { @Inject public EXIFReader() { - //Empty } - /** - * The method takes in path of the image and reads metadata using the library metadata-extractor - * And the checks for the presence of EXIF Directories in metadata object - * */ public Single processMetadata(String path) { - Metadata readMetadata = null; try { - readMetadata = ImageMetadataReader.readMetadata(new File(path)); - } catch (ImageProcessingException e) { - Timber.d(e.toString()); - } catch (IOException e) { - Timber.d(e.toString()); - } - if (readMetadata != null) { - for (Directory directory : readMetadata.getDirectories()) { - // In case of internet downloaded image these three fields are not present - if (directory.getName().equals("Exif IFD0") //Contains information about the device capturing the photo - || directory.getName().equals("Exif SubIFD") //contains information like date, time and pixels of the image - || directory.getName().equals("Exif Thumbnail")) //contains information about image thumbnail like compression and reolution - { - Timber.d(directory.getName() + " Contains metadata"); - return Single.just(ImageUtils.IMAGE_OK); - } + ExifInterface exif = new ExifInterface(path); + if (exif.getAttribute(ExifInterface.TAG_MAKE) != null + || exif.getAttribute(ExifInterface.TAG_DATETIME) != null) { + return Single.just(ImageUtils.IMAGE_OK); } + } catch (Exception e) { + return Single.just(ImageUtils.FILE_NO_EXIF); } return Single.just(ImageUtils.FILE_NO_EXIF); } - } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java index 9fbd8dc63..775b30dd7 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/ImageProcessingService.java @@ -1,12 +1,9 @@ package fr.free.nrw.commons.upload; import android.content.Context; -import android.net.Uri; import org.apache.commons.lang3.StringUtils; -import java.io.IOException; - import javax.inject.Inject; import javax.inject.Singleton; @@ -23,10 +20,7 @@ import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK; /** * Methods for pre-processing images to be uploaded - *//*if (dataInBytes[0] == 70 && dataInBytes[1] == 66 && dataInBytes[2] == 77 && dataInBytes[3] == 68) { - Timber.d("Contains FBMD"); - return Single.just(ImageUtils.FILE_FBMD); - }*/ + */ @Singleton public class ImageProcessingService { private final FileUtilsWrapper fileUtilsWrapper; @@ -64,12 +58,11 @@ public class ImageProcessingService { } Timber.d("Checking the validity of image"); String filePath = uploadItem.getMediaUri().getPath(); - Uri contentUri=uploadItem.getContentUri(); Single duplicateImage = checkDuplicateImage(filePath); Single wrongGeoLocation = checkImageGeoLocation(uploadItem.getPlace(), filePath); Single darkImage = checkDarkImage(filePath); Single itemTitle = checkTitle ? validateItemTitle(uploadItem) : Single.just(ImageUtils.IMAGE_OK); - Single checkFBMD = checkFBMD(context,contentUri); + Single checkFBMD = checkFBMD(filePath); Single checkEXIF = checkEXIF(filePath); Single zipResult = Single.zip(duplicateImage, wrongGeoLocation, darkImage, itemTitle, @@ -84,31 +77,21 @@ public class ImageProcessingService { } /** - * Other than the Image quality we need to check that using this Image doesn't violate's facebook's copyright's. - * Whenever a user tries to upload an image that was downloaded from Facebook then we warn the user with a message to stop the upload - * To know whether the Image is downloaded from facebook: - * -We read the metadata of any Image and check for FBMD - * -Facebook downloaded image's contains metadata of the type IPTC - * - From this IPTC metadata we extract a byte array that contains FBMD as it's initials. If the image was downloaded from facebook - * Thus we successfully protect common's from Facebook's copyright violation + * We want to discourage users from uploading images to Commons that were taken from Facebook. + * This attempts to detect whether an image was downloaded from Facebook by heuristically + * searching for metadata that is specific to images that come from Facebook. */ - - public Single checkFBMD(Context context,Uri contentUri) { - try { - return readFBMD.processMetadata(context,contentUri); - } catch (IOException e) { - return Single.just(ImageUtils.FILE_FBMD); - } + private Single checkFBMD(String filepath) { + return readFBMD.processMetadata(filepath); } /** - * To avoid copyright we check for EXIF data in any image. - * Images that are downloaded from internet generally don't have any EXIF data in them - * while images taken via camera or screenshots in phone have EXIF data with them. - * So we check if the image has no EXIF data then we display a warning to the user - * * */ - - public Single checkEXIF(String filepath){ + * We try to minimize uploads from the Commons app that might be copyright violations. + * If an image does not have any Exif metadata, then it was likely downloaded from the internet, + * and is probably not an original work by the user. We detect these kinds of images by looking + * for the presence of some basic Exif metadata. + */ + private Single checkEXIF(String filepath) { return EXIFReader.processMetadata(filepath); } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java b/app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java index a0c677b3b..99b2a5a5e 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/ReadFBMD.java @@ -1,14 +1,6 @@ package fr.free.nrw.commons.upload; -import android.content.Context; -import android.net.Uri; - -import com.drew.imaging.ImageMetadataReader; -import com.drew.imaging.ImageProcessingException; -import com.drew.metadata.Metadata; -import com.drew.metadata.Tag; -import com.drew.metadata.iptc.IptcDirectory; - +import java.io.FileInputStream; import java.io.IOException; import javax.inject.Inject; @@ -17,37 +9,38 @@ import javax.inject.Singleton; import fr.free.nrw.commons.utils.ImageUtils; import io.reactivex.Single; +/** + * We want to discourage users from uploading images to Commons that were taken from Facebook. + * This attempts to detect whether an image was downloaded from Facebook by heuristically + * searching for metadata that is specific to images that come from Facebook. + */ @Singleton public class ReadFBMD { @Inject public ReadFBMD() { - } - public Single processMetadata(Context context, Uri contentUri) throws IOException { - Metadata readMetadata = null; + public Single processMetadata(String path) { try { - readMetadata = ImageMetadataReader.readMetadata(context.getContentResolver().openInputStream(contentUri)); - } catch (ImageProcessingException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } + int psBlockOffset; + int fbmdOffset; - IptcDirectory iptcDirectory = readMetadata != null ? readMetadata.getFirstDirectoryOfType(IptcDirectory.class) : null; - if (iptcDirectory == null) { - return Single.just(ImageUtils.IMAGE_OK); - } - /** - * We parse through all the tags in the IPTC directory if the tagname equals "Special Instructions". - * And the description string starts with FBMD. - * Then the source of image is facebook - * */ - for (Tag tag : iptcDirectory.getTags()) { - if (tag.getTagName().equals("Special Instructions") && tag.getDescription().substring(0, 4).equals("FBMD")) { + try (FileInputStream fs = new FileInputStream(path)) { + byte[] bytes = new byte[4096]; + fs.read(bytes); + fs.close(); + String fileStr = new String(bytes); + psBlockOffset = fileStr.indexOf("8BIM"); + fbmdOffset = fileStr.indexOf("FBMD"); + } + + if (psBlockOffset > 0 && fbmdOffset > 0 + && fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) { return Single.just(ImageUtils.FILE_FBMD); } + } catch (IOException e) { + e.printStackTrace(); } return Single.just(ImageUtils.IMAGE_OK); } diff --git a/app/src/main/res/values-en-gb/error.xml b/app/src/main/res/values-en-gb/error.xml deleted file mode 100644 index af6db412b..000000000 --- a/app/src/main/res/values-en-gb/error.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - Commons has crashed - Oops. Something went wrong! - Tell us what you were doing, then share it via email to us. Will help us fix it! - Thank you! -