22
33import android .annotation .SuppressLint ;
44import android .content .ContentResolver ;
5+ import android .content .Context ;
56import android .net .Uri ;
67import androidx .annotation .NonNull ;
78
89import java .io .File ;
910import java .io .IOException ;
11+ import java .lang .reflect .Type ;
12+ import java .util .Arrays ;
13+ import java .util .HashSet ;
1014import java .util .List ;
15+ import java .util .Set ;
1116
1217import javax .inject .Inject ;
1318import javax .inject .Named ;
1419import javax .inject .Singleton ;
1520
1621import androidx .exifinterface .media .ExifInterface ;
22+
23+ import com .google .gson .reflect .TypeToken ;
24+
25+ import fr .free .nrw .commons .R ;
1726import fr .free .nrw .commons .caching .CacheController ;
1827import fr .free .nrw .commons .kvstore .JsonKvStore ;
1928import fr .free .nrw .commons .mwapi .CategoryApi ;
29+ import fr .free .nrw .commons .settings .Prefs ;
30+ import io .reactivex .Observable ;
2031import io .reactivex .disposables .CompositeDisposable ;
32+ import io .reactivex .disposables .Disposable ;
2133import io .reactivex .schedulers .Schedulers ;
2234import timber .log .Timber ;
2335
@@ -66,7 +78,10 @@ void initFileDetails(@NonNull String filePath, ContentResolver contentResolver)
6678 /**
6779 * Processes filePath coordinates, either from EXIF data or user location
6880 */
69- GPSExtractor processFileCoordinates (SimilarImageInterface similarImageInterface ) {
81+ GPSExtractor processFileCoordinates (SimilarImageInterface similarImageInterface , Context context ) {
82+ // Redact EXIF data as indicated in preferences.
83+ redactExifTags (exifInterface , getExifTagsToRedact (context ));
84+
7085 Timber .d ("Calling GPSExtractor" );
7186 imageObj = new GPSExtractor (exifInterface );
7287 decimalCoords = imageObj .getCoords ();
@@ -81,6 +96,55 @@ GPSExtractor processFileCoordinates(SimilarImageInterface similarImageInterface)
8196 return imageObj ;
8297 }
8398
99+ /**
100+ * Gets EXIF Tags from preferences to be redacted.
101+ *
102+ * @param context application context
103+ * @return tags to be redacted
104+ */
105+ private Set <String > getExifTagsToRedact (Context context ) {
106+ Type setType = new TypeToken <Set <String >>() {}.getType ();
107+ Set <String > prefManageEXIFTags = defaultKvStore .getJson (Prefs .MANAGED_EXIF_TAGS , setType );
108+
109+ Set <String > redactTags = new HashSet <>(Arrays .asList (
110+ context .getResources ().getStringArray (R .array .pref_exifTag_values )));
111+ Timber .d (redactTags .toString ());
112+
113+ if (prefManageEXIFTags != null ) redactTags .removeAll (prefManageEXIFTags );
114+
115+ return redactTags ;
116+ }
117+
118+ /**
119+ * Redacts EXIF metadata as indicated in preferences.
120+ *
121+ * @param exifInterface ExifInterface object
122+ * @param redactTags tags to be redacted
123+ */
124+ public static void redactExifTags (ExifInterface exifInterface , Set <String > redactTags ) {
125+ if (redactTags .isEmpty ()) return ;
126+
127+ Disposable disposable = Observable .fromIterable (redactTags )
128+ .flatMap (tag -> Observable .fromArray (FileMetadataUtils .getTagsFromPref (tag )))
129+ .forEach (tag -> {
130+ Timber .d ("Checking for tag: %s" , tag );
131+ String oldValue = exifInterface .getAttribute (tag );
132+ if (oldValue != null && !oldValue .isEmpty ()) {
133+ Timber .d ("Exif tag %s with value %s redacted." , tag , oldValue );
134+ exifInterface .setAttribute (tag , null );
135+ }
136+ });
137+ CompositeDisposable disposables = new CompositeDisposable ();
138+ disposables .add (disposable );
139+ disposables .clear ();
140+
141+ try {
142+ exifInterface .saveAttributes ();
143+ } catch (IOException e ) {
144+ Timber .w ("EXIF redaction failed: %s" , e .toString ());
145+ }
146+ }
147+
84148 /**
85149 * Find other images around the same location that were taken within the last 20 sec
86150 * @param similarImageInterface
0 commit comments