2
2
3
3
import android .annotation .SuppressLint ;
4
4
import android .content .ContentResolver ;
5
+ import android .content .Context ;
5
6
import android .net .Uri ;
6
7
import androidx .annotation .NonNull ;
7
8
8
9
import java .io .File ;
9
10
import java .io .IOException ;
11
+ import java .lang .reflect .Type ;
12
+ import java .util .Arrays ;
13
+ import java .util .HashSet ;
10
14
import java .util .List ;
15
+ import java .util .Set ;
11
16
12
17
import javax .inject .Inject ;
13
18
import javax .inject .Named ;
14
19
import javax .inject .Singleton ;
15
20
16
21
import androidx .exifinterface .media .ExifInterface ;
22
+
23
+ import com .google .gson .reflect .TypeToken ;
24
+
25
+ import fr .free .nrw .commons .R ;
17
26
import fr .free .nrw .commons .caching .CacheController ;
18
27
import fr .free .nrw .commons .kvstore .JsonKvStore ;
19
28
import fr .free .nrw .commons .mwapi .CategoryApi ;
29
+ import fr .free .nrw .commons .settings .Prefs ;
30
+ import io .reactivex .Observable ;
20
31
import io .reactivex .disposables .CompositeDisposable ;
32
+ import io .reactivex .disposables .Disposable ;
21
33
import io .reactivex .schedulers .Schedulers ;
22
34
import timber .log .Timber ;
23
35
@@ -66,7 +78,10 @@ void initFileDetails(@NonNull String filePath, ContentResolver contentResolver)
66
78
/**
67
79
* Processes filePath coordinates, either from EXIF data or user location
68
80
*/
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
+
70
85
Timber .d ("Calling GPSExtractor" );
71
86
imageObj = new GPSExtractor (exifInterface );
72
87
decimalCoords = imageObj .getCoords ();
@@ -81,6 +96,55 @@ GPSExtractor processFileCoordinates(SimilarImageInterface similarImageInterface)
81
96
return imageObj ;
82
97
}
83
98
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
+
84
148
/**
85
149
* Find other images around the same location that were taken within the last 20 sec
86
150
* @param similarImageInterface
0 commit comments