Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
63a09d6
[WIP] Added preferences for EXIF tags
VitalyVPinchuk Apr 4, 2019
32d981b
[WIP] Added arrays, keys, strings to support EXIF preferences
VitalyVPinchuk Apr 4, 2019
15dbe90
[WIP] Updated SettingsFragment to setup summary of added preferences(…
VitalyVPinchuk Apr 4, 2019
08da024
[WIP] Added methods getStringSet()in BasicKvStore, KeyValueStore to s…
VitalyVPinchuk Apr 4, 2019
eb5ae30
[WIP] Added methods for removing EXIF tags and anonimyzing location c…
VitalyVPinchuk Apr 4, 2019
9ee28b9
[WIP] Fixed errors in preferences EXIF tags, added XMP removal routine
VitalyVPinchuk Apr 4, 2019
11aee92
Merge branch 'master' into pr-1712
VitalyVPinchuk Apr 5, 2019
8cf6cfd
[WIP] Removed erroneous location accuracy handling
VitalyVPinchuk Apr 11, 2019
d654110
Merge branch 'master' into pr-1712
VitalyVPinchuk Apr 11, 2019
65afb07
[WIP] Fixed mistyped GPS Tags
VitalyVPinchuk Apr 11, 2019
ff6606c
Reverted BasicKvStore. Removed Set<String> support in BasicKvStore as…
VitalyVPinchuk Apr 19, 2019
4516704
FileProcessor: Replaced throwing runtime exception with warning if EX…
VitalyVPinchuk Apr 19, 2019
18e6bc1
FileMetadataUtils: Javadoc added
VitalyVPinchuk Apr 20, 2019
a0312b6
[WIP] Removed XMP metadata due to unusable handling
VitalyVPinchuk Apr 25, 2019
9d660c1
[WIP] FileMetadataUtilsTest added
VitalyVPinchuk Apr 25, 2019
419c6e6
[WIP] FileMetadataUtilsTest: added javadoc
VitalyVPinchuk Apr 25, 2019
a570062
[WIP] FileMetadataUtilsTest: added javadoc
VitalyVPinchuk Apr 26, 2019
82100ce
[WIP] FileProcessor: fixed disposing observables
VitalyVPinchuk Apr 26, 2019
88c67cd
[WIP] FileMetadataUtils.getTagsFromPref: changed return type from obs…
VitalyVPinchuk Apr 26, 2019
b385a29
[WIP] FileProcessorTest: added test for redactExifTags
VitalyVPinchuk Apr 29, 2019
29e9964
Merge branch 'master' into pr-1712
VitalyVPinchuk Apr 29, 2019
06f2bad
Merge branch 'master' into pr-1712
VitalyVPinchuk Apr 30, 2019
6ab1bff
[WIP] FileProcessorTest: redactExifTags() doesn't work properly
VitalyVPinchuk Apr 30, 2019
6d040fd
Merge branch 'master' into pr-1712
VitalyVPinchuk May 7, 2019
2fbecab
Merge branch 'master' into pr-1712
VitalyVPinchuk May 13, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/main/java/fr/free/nrw/commons/settings/Prefs.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class Prefs {
public static final String DEFAULT_LICENSE = "defaultLicense";
public static final String UPLOADS_SHOWING = "uploadsshowing";
public static final String IS_CONTRIBUTION_COUNT_CHANGED = "ccontributionCountChanged";
public static final String MANAGED_EXIF_TAGS = "managedExifTags";

public static class Licenses {
public static final String CC_BY_SA_3 = "CC BY-SA 3.0";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.net.Uri;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.MultiSelectListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.SwitchPreference;
Expand All @@ -14,6 +15,11 @@
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.single.BasePermissionListener;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;

Expand Down Expand Up @@ -59,6 +65,14 @@ public void onCreate(Bundle savedInstanceState) {
return true;
});

MultiSelectListPreference multiSelectListPref = (MultiSelectListPreference) findPreference("manageExifTags");
if (multiSelectListPref != null) {
multiSelectListPref.setOnPreferenceChangeListener((preference, newValue) -> {
defaultKvStore.putJson(Prefs.MANAGED_EXIF_TAGS, newValue);
return true;
});
}

final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads");
int currentUploadLimit = defaultKvStore.getInt(Prefs.UPLOADS_SHOWING, 100);
uploadLimit.setText(Integer.toString(currentUploadLimit));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package fr.free.nrw.commons.ui.LongTitlePreferences;

import android.content.Context;
import android.preference.MultiSelectListPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;

public class LongTitleMultiSelectListPreference extends MultiSelectListPreference {
/*
public LongTitleMultiSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

public LongTitleMultiSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
*/
public LongTitleMultiSelectListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}

public LongTitleMultiSelectListPreference(Context context) {
super(context);
}

@Override
protected void onBindView(View view)
{
super.onBindView(view);

TextView title= view.findViewById(android.R.id.title);
if (title != null) {
title.setSingleLine(false);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package fr.free.nrw.commons.upload;

import timber.log.Timber;

import static androidx.exifinterface.media.ExifInterface.*;

/**
* Support utils for EXIF metadata handling
*
*/
public class FileMetadataUtils {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add unit test cases.


/**
* Takes EXIF label from sharedPreferences as input and returns relevant EXIF tags
*
* @param pref EXIF sharedPreference label
* @return EXIF tags
*/
public static String[] getTagsFromPref(String pref) {
Timber.d("Retuning tags for pref:%s", pref);
switch (pref) {
case "Author":
return new String[]{TAG_ARTIST, TAG_CAMARA_OWNER_NAME};
case "Copyright":
return new String[]{TAG_COPYRIGHT};
case "Location":
return new String[]{TAG_GPS_LATITUDE, TAG_GPS_LATITUDE_REF,
TAG_GPS_LONGITUDE, TAG_GPS_LONGITUDE_REF,
TAG_GPS_ALTITUDE, TAG_GPS_ALTITUDE_REF};
case "Camera Model":
return new String[]{TAG_MAKE, TAG_MODEL};
case "Lens Model":
return new String[]{TAG_LENS_MAKE, TAG_LENS_MODEL, TAG_LENS_SPECIFICATION};
case "Serial Numbers":
return new String[]{TAG_BODY_SERIAL_NUMBER, TAG_LENS_SERIAL_NUMBER};
case "Software":
return new String[]{TAG_SOFTWARE};
default:
return new String[]{};
}
}

}
66 changes: 65 additions & 1 deletion app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,34 @@

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import androidx.annotation.NonNull;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import androidx.exifinterface.media.ExifInterface;

import com.google.gson.reflect.TypeToken;

import fr.free.nrw.commons.R;
import fr.free.nrw.commons.caching.CacheController;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.mwapi.CategoryApi;
import fr.free.nrw.commons.settings.Prefs;
import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;

Expand Down Expand Up @@ -66,7 +78,10 @@ void initFileDetails(@NonNull String filePath, ContentResolver contentResolver)
/**
* Processes filePath coordinates, either from EXIF data or user location
*/
GPSExtractor processFileCoordinates(SimilarImageInterface similarImageInterface) {
GPSExtractor processFileCoordinates(SimilarImageInterface similarImageInterface, Context context) {
// Redact EXIF data as indicated in preferences.
redactExifTags(exifInterface, getExifTagsToRedact(context));

Timber.d("Calling GPSExtractor");
imageObj = new GPSExtractor(exifInterface);
decimalCoords = imageObj.getCoords();
Expand All @@ -81,6 +96,55 @@ GPSExtractor processFileCoordinates(SimilarImageInterface similarImageInterface)
return imageObj;
}

/**
* Gets EXIF Tags from preferences to be redacted.
*
* @param context application context
* @return tags to be redacted
*/
private Set<String> getExifTagsToRedact(Context context) {
Type setType = new TypeToken<Set<String>>() {}.getType();
Set<String> prefManageEXIFTags = defaultKvStore.getJson(Prefs.MANAGED_EXIF_TAGS, setType);

Set<String> redactTags = new HashSet<>(Arrays.asList(
context.getResources().getStringArray(R.array.pref_exifTag_values)));
Timber.d(redactTags.toString());

if (prefManageEXIFTags != null) redactTags.removeAll(prefManageEXIFTags);

return redactTags;
}

/**
* Redacts EXIF metadata as indicated in preferences.
*
* @param exifInterface ExifInterface object
* @param redactTags tags to be redacted
*/
public static void redactExifTags(ExifInterface exifInterface, Set<String> redactTags) {
if(redactTags.isEmpty()) return;

Disposable disposable = Observable.fromIterable(redactTags)
.flatMap(tag -> Observable.fromArray(FileMetadataUtils.getTagsFromPref(tag)))
.forEach(tag -> {
Timber.d("Checking for tag: %s", tag);
String oldValue = exifInterface.getAttribute(tag);
if (oldValue != null && !oldValue.isEmpty()) {
Timber.d("Exif tag %s with value %s redacted.", tag, oldValue);
exifInterface.setAttribute(tag, null);
}
});
CompositeDisposable disposables = new CompositeDisposable();
disposables.add(disposable);
disposables.clear();

try {
exifInterface.saveAttributes();
} catch (IOException e) {
Timber.w("EXIF redaction failed: %s", e.toString());
}
}

/**
* Find other images around the same location that were taken within the last 20 sec
* @param similarImageInterface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private UploadItem getUploadItem(UploadableFile uploadableFile,
createdTimestampSource = dateTimeWithSource.getSource();
}
Timber.d("File created date is %d", fileCreatedDate);
GPSExtractor gpsExtractor = fileProcessor.processFileCoordinates(similarImageInterface);
GPSExtractor gpsExtractor = fileProcessor.processFileCoordinates(similarImageInterface, context);
return new UploadItem(uploadableFile.getContentUri(), Uri.parse(uploadableFile.getFilePath()), uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate, createdTimestampSource);
}

Expand Down
21 changes: 21 additions & 0 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,25 @@
<item>@string/license_pref_cc_by_sa_3_0</item>
<item>@string/license_pref_cc_by_sa_4_0</item>
</array>

<!--TODO add more EXIF tags-->
<array name="pref_exifTag_entries">
<item>@string/exif_tag_name_author</item>
<item>@string/exif_tag_name_copyright</item>
<item>@string/exif_tag_name_location</item>
<item>@string/exif_tag_name_cameraModel</item>
<item>@string/exif_tag_name_lensModel</item>
<item>@string/exif_tag_name_serialNumbers</item>
<item>@string/exif_tag_name_software</item>
</array>
<array name="pref_exifTag_values">
<item>@string/exif_tag_author</item>
<item>@string/exif_tag_copyright</item>
<item>@string/exif_tag_location</item>
<item>@string/exif_tag_cameraModel</item>
<item>@string/exif_tag_lensModel</item>
<item>@string/exif_tag_serialNumbers</item>
<item>@string/exif_tag_software</item>
</array>

</resources>
9 changes: 9 additions & 0 deletions app/src/main/res/values/keys.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,13 @@
<string name="license_pref_cc_by_sa_3_0" translatable="false">CC BY-SA 3.0</string>
<string name="license_pref_cc_by_4_0" translatable="false">CC BY 4.0</string>
<string name="license_pref_cc_by_sa_4_0" translatable="false">CC BY-SA 4.0</string>

<string name="exif_tag_author" translatable="false">Author</string>
<string name="exif_tag_copyright" translatable="false">Copyright</string>
<string name="exif_tag_location" translatable="false">Location</string>
<string name="exif_tag_cameraModel" translatable="false">Camera Model</string>
<string name="exif_tag_lensModel" translatable="false">Lens Model</string>
<string name="exif_tag_serialNumbers" translatable="false">Serial Numbers</string>
<string name="exif_tag_software" translatable="false">Software</string>

</resources>
13 changes: 13 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<string name="preference_category_appearance">Appearance</string>
<string name="preference_category_general">General</string>
<string name="preference_category_feedback">Feedback</string>
<string name="preference_category_privacy">Privacy</string>
<string name="preference_category_location">Location</string>
<string name="app_name">Commons</string>
<string name="bullet">&#8226; </string>
Expand Down Expand Up @@ -537,6 +538,18 @@ Upload your first media by tapping on the add button.</string>
<string name="welcome_dont_upload_content_description">Examples of images not to upload</string>
<string name="skip_image">SKIP THIS IMAGE</string>
<string name="download_failed_we_cannot_download_the_file_without_storage_permission">Download Failed!!. We cannot download the file without external storage permission.</string>

<string name="manage_exif_tags">Manage EXIF Tags</string>
<string name="manage_exif_tags_summary">Select which EXIF tags to keep in uploads</string>

<string name="exif_tag_name_author">Author</string>
<string name="exif_tag_name_copyright">Copyright</string>
<string name="exif_tag_name_location">Location</string>
<string name="exif_tag_name_cameraModel">Camera Model</string>
<string name="exif_tag_name_lensModel">Lens Model</string>
<string name="exif_tag_name_serialNumbers">Serial Numbers</string>
<string name="exif_tag_name_software">Software</string>

<string name="share_text">Upload photos to Wikimedia Commons on your phone Download the Commons app: %1$s</string>
<string name="share_via">Share app via...</string>
<string name="image_info">Image Info</string>
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/res/xml/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,18 @@

</fr.free.nrw.commons.ui.LongTitlePreferences.LongTitlePreferenceCategory>

<fr.free.nrw.commons.ui.LongTitlePreferences.LongTitlePreferenceCategory
android:title="@string/preference_category_privacy">

<fr.free.nrw.commons.ui.LongTitlePreferences.LongTitleMultiSelectListPreference
android:entries="@array/pref_exifTag_entries"
android:entryValues="@array/pref_exifTag_values"
android:key="manageExifTags"
android:title="@string/manage_exif_tags"
android:summary="@string/manage_exif_tags_summary"/>

</fr.free.nrw.commons.ui.LongTitlePreferences.LongTitlePreferenceCategory>

<!-- The key 'allowGps' was used before and has since been removed based on the discussion at #1599.
Do not reuse this key unless you revive the same feature with the changes mentioned at #1599.-->

Expand Down
Binary file added app/src/test/data/exif_redact_sample.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package fr.free.nrw.commons.upload

import androidx.exifinterface.media.ExifInterface.*
import junit.framework.Assert.assertTrue
import org.junit.Test
import java.util.*

/**
* Test cases for FileMetadataUtils
*/
class FileMetadataUtilsTest {

/**
* Test method to verify EXIF tags
*/
@Test
fun getTagsFromPref() {
val author = FileMetadataUtils.getTagsFromPref("Author")
val authorRef = arrayOf(TAG_ARTIST, TAG_CAMARA_OWNER_NAME);

assertTrue(Arrays.deepEquals(author, authorRef))
}
}
Loading