diff --git a/.travis.yml b/.travis.yml index dcbf8137e..ab511cecc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,3 +54,6 @@ before_install: - if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "master" ]; then openssl aes-256-cbc -K $encrypted_38ac1a5053f6_key -iv $encrypted_38ac1a5053f6_iv -in play.p12.enc -out play.p12 -d; fi +notifications: + webhooks: + - https://wiki-commons.zulipchat.com/api/v1/external/travis?api_key=kn4a8YKNqHCBYp7EW2k463txMj35vReq&stream=travis-ci diff --git a/app/build.gradle b/app/build.gradle index 1b7029e27..fac1ab9ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -60,7 +60,8 @@ dependencies { testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION" testImplementation "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION" testImplementation 'junit:junit:4.12' - testImplementation 'org.robolectric:robolectric:3.7.1' + testImplementation 'org.robolectric:robolectric:4.3' + testImplementation 'androidx.test:core:1.2.0' testImplementation 'com.nhaarman:mockito-kotlin:1.5.0' testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0' diff --git a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java index f66ae6527..23bf7e3fc 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java @@ -70,7 +70,7 @@ public class NetworkingModule { @Provides @Singleton public OkHttpJsonApiClient provideOkHttpJsonApiClient(OkHttpClient okHttpClient, - @Named("tools_force") HttpUrl toolsForgeUrl, + @Named("tools_forge") HttpUrl toolsForgeUrl, @Named("default_preferences") JsonKvStore defaultKvStore, Gson gson) { return new OkHttpJsonApiClient(okHttpClient, @@ -91,7 +91,7 @@ public class NetworkingModule { } @Provides - @Named("tools_force") + @Named("tools_forge") @NonNull @SuppressWarnings("ConstantConditions") public HttpUrl provideToolsForgeUrl() { diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java index 6f13f7e49..a2c9e81d8 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java @@ -1,7 +1,9 @@ package fr.free.nrw.commons.mwapi; +import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -10,6 +12,7 @@ import org.wikipedia.dataclient.mwapi.MwQueryPage; import org.wikipedia.dataclient.mwapi.MwQueryResponse; import org.wikipedia.dataclient.mwapi.RecentChange; import org.wikipedia.util.DateUtil; + import java.io.IOException; import java.lang.reflect.Type; import java.util.ArrayList; @@ -33,6 +36,7 @@ import fr.free.nrw.commons.nearby.model.NearbyResponse; import fr.free.nrw.commons.nearby.model.NearbyResultItem; import fr.free.nrw.commons.upload.FileUtils; import fr.free.nrw.commons.utils.CommonsDateUtil; +import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.wikidata.model.GetWikidataEditCountResponse; import io.reactivex.Observable; import io.reactivex.Single; @@ -84,6 +88,11 @@ public class OkHttpJsonApiClient { urlBuilder .addPathSegments("/uploadsbyuser.py") .addQueryParameter("user", userName); + + if (ConfigUtils.isBetaFlavour()) { + urlBuilder.addQueryParameter("labs", "commonswiki"); + } + Request request = new Request.Builder() .url(urlBuilder.build()) .build(); @@ -91,7 +100,9 @@ public class OkHttpJsonApiClient { return Single.fromCallable(() -> { Response response = okHttpClient.newCall(request).execute(); if (response != null && response.isSuccessful()) { - return Integer.parseInt(response.body().string().trim()); + if(!TextUtils.isEmpty(response.body().string().trim())){ + return Integer.parseInt(response.body().string().trim()); + } } return 0; }); @@ -103,6 +114,11 @@ public class OkHttpJsonApiClient { urlBuilder .addPathSegments("/wikidataedits.py") .addQueryParameter("user", userName); + + if (ConfigUtils.isBetaFlavour()) { + urlBuilder.addQueryParameter("labs", "commonswiki"); + } + Request request = new Request.Builder() .url(urlBuilder.build()) .build(); @@ -131,7 +147,7 @@ public class OkHttpJsonApiClient { */ public Single getAchievements(String userName) { final String fetchAchievementUrlTemplate = - wikiMediaToolforgeUrl + "/feedback.py"; + wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki" : "/feedback.py"); return Single.fromCallable(() -> { String url = String.format( Locale.ENGLISH, diff --git a/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java index d059c862e..9a40acc55 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java +++ b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.java @@ -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"; diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java index cf0e8aaee..6d527c91d 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java @@ -4,6 +4,7 @@ import android.Manifest; 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; @@ -14,6 +15,11 @@ import com.karumi.dexter.Dexter; 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; @@ -59,6 +65,14 @@ public class SettingsFragment extends PreferenceFragment { 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)); diff --git a/app/src/main/java/fr/free/nrw/commons/ui/LongTitlePreferences/LongTitleMultiSelectListPreference.java b/app/src/main/java/fr/free/nrw/commons/ui/LongTitlePreferences/LongTitleMultiSelectListPreference.java new file mode 100644 index 000000000..9c5f327ac --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/ui/LongTitlePreferences/LongTitleMultiSelectListPreference.java @@ -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); + } + } + +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileMetadataUtils.java b/app/src/main/java/fr/free/nrw/commons/upload/FileMetadataUtils.java new file mode 100644 index 000000000..70dec35a4 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileMetadataUtils.java @@ -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 { + + /** + * 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[]{}; + } + } + +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.java b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.java index 16fc5712a..1d4497586 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.java @@ -2,22 +2,34 @@ package fr.free.nrw.commons.upload; 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; @@ -66,7 +78,10 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse { /** * 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(); @@ -81,6 +96,55 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse { return imageObj; } + /** + * Gets EXIF Tags from preferences to be redacted. + * + * @param context application context + * @return tags to be redacted + */ + private Set getExifTagsToRedact(Context context) { + Type setType = new TypeToken>() {}.getType(); + Set prefManageEXIFTags = defaultKvStore.getJson(Prefs.MANAGED_EXIF_TAGS, setType); + + Set 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 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 diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java index afd59f4ca..c94f9f496 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java @@ -109,7 +109,7 @@ public class UploadModel { 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); } diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index f6876812f..acf6d08e2 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1,5 +1,6 @@ + + @string/exif_tag_name_author + @string/exif_tag_name_copyright + @string/exif_tag_name_location + @string/exif_tag_name_cameraModel + @string/exif_tag_name_lensModel + @string/exif_tag_name_serialNumbers + @string/exif_tag_name_software + + + @string/exif_tag_author + @string/exif_tag_copyright + @string/exif_tag_location + @string/exif_tag_cameraModel + @string/exif_tag_lensModel + @string/exif_tag_serialNumbers + @string/exif_tag_software + + \ No newline at end of file diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index a2bb6860d..60d0b566a 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -5,4 +5,13 @@ CC BY-SA 3.0 CC BY 4.0 CC BY-SA 4.0 + + Author + Copyright + Location + Camera Model + Lens Model + Serial Numbers + Software + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 15b04a61e..276f9e2c1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ Appearance General Feedback + Privacy Location Commons @@ -537,6 +538,18 @@ Upload your first media by tapping on the add button. Examples of images not to upload SKIP THIS IMAGE Download Failed!!. We cannot download the file without external storage permission. + + Manage EXIF Tags + Select which EXIF tags to keep in uploads + + Author + Copyright + Location + Camera Model + Lens Model + Serial Numbers + Software + Upload photos to Wikimedia Commons on your phone Download the Commons app: %1$s Share app via... Image Info diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 21df4c045..9ab4d5f5f 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -59,6 +59,18 @@ + + + + + + diff --git a/app/src/test/data/exif_redact_sample.jpg b/app/src/test/data/exif_redact_sample.jpg new file mode 100644 index 000000000..177e42aad Binary files /dev/null and b/app/src/test/data/exif_redact_sample.jpg differ diff --git a/app/src/test/kotlin/fr/free/nrw/commons/MediaDataExtractorTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/MediaDataExtractorTest.kt index 839d7f22c..cf87f9cd6 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/MediaDataExtractorTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/MediaDataExtractorTest.kt @@ -4,7 +4,7 @@ import fr.free.nrw.commons.mwapi.MediaResult import fr.free.nrw.commons.mwapi.MediaWikiApi import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import io.reactivex.Single -import junit.framework.Assert.assertTrue +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers diff --git a/app/src/test/kotlin/fr/free/nrw/commons/MediaTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/MediaTest.kt index f75c34568..df1adf81a 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/MediaTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/MediaTest.kt @@ -7,7 +7,7 @@ import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = intArrayOf(21), application = TestCommonsApplication::class) +@Config(sdk = [21], application = TestCommonsApplication::class) class MediaTest { @Test fun displayTitleShouldStripExtension() { diff --git a/app/src/test/kotlin/fr/free/nrw/commons/NearbyControllerTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/NearbyControllerTest.kt index 1123a55ec..12c06ee9e 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/NearbyControllerTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/NearbyControllerTest.kt @@ -2,15 +2,15 @@ package fr.free.nrw.commons import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.nearby.NearbyController.loadAttractionsFromLocationToBaseMarkerOptions +import androidx.test.core.app.ApplicationProvider import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class) +@Config(sdk = [21], application = TestCommonsApplication::class) class NearbyControllerTest { @Test @@ -18,7 +18,7 @@ class NearbyControllerTest { val location = LatLng(0.0, 0.0, 0f) val options = loadAttractionsFromLocationToBaseMarkerOptions( - location, null, RuntimeEnvironment.application, null) + location, null, ApplicationProvider.getApplicationContext(), null) assertEquals(0, options.size.toLong()) } @@ -28,7 +28,7 @@ class NearbyControllerTest { val location = LatLng(0.0, 0.0, 0f) val options = loadAttractionsFromLocationToBaseMarkerOptions( - location, emptyList(), RuntimeEnvironment.application, emptyList()) + location, emptyList(), ApplicationProvider.getApplicationContext(), emptyList()) assertEquals(0, options.size.toLong()) } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookMarkLocationDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookMarkLocationDaoTest.kt index a1c984fda..4d168c1d3 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookMarkLocationDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/locations/BookMarkLocationDaoTest.kt @@ -24,7 +24,7 @@ import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class) +@Config(sdk = [21], application = TestCommonsApplication::class) class BookMarkLocationDaoTest { private val columns = arrayOf(COLUMN_NAME, COLUMN_DESCRIPTION, diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPictureDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPictureDaoTest.kt index 3710c0c5c..b38722f88 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPictureDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPictureDaoTest.kt @@ -21,7 +21,7 @@ import org.junit.Before import org.junit.Test @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class) +@Config(sdk = [21], application = TestCommonsApplication::class) class BookmarkPictureDaoTest { private val columns = arrayOf(COLUMN_MEDIA_NAME, COLUMN_CREATOR) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt index b3870c60f..9ee2e75de 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt @@ -21,7 +21,7 @@ import org.robolectric.annotation.Config import java.util.* @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class) +@Config(sdk = [21], application = TestCommonsApplication::class) class CategoryDaoTest { private val columns = arrayOf(COLUMN_ID, COLUMN_NAME, COLUMN_LAST_USED, COLUMN_TIMES_USED) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionDaoTest.kt index 31e66197d..adbb50ff8 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionDaoTest.kt @@ -23,7 +23,7 @@ import org.robolectric.annotation.Config import java.util.* @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class) +@Config(sdk = [21], application = TestCommonsApplication::class) class ContributionDaoTest { private val localUri = "http://example.com/" private val client: ContentProviderClient = mock() diff --git a/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt index 0aad6ae44..46d7dfbfc 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt @@ -7,7 +7,7 @@ import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.mwapi.MediaWikiApi import fr.free.nrw.commons.notification.NotificationHelper import fr.free.nrw.commons.utils.ViewUtilWrapper -import junit.framework.Assert.* +import org.junit.Assert.* import org.junit.Before import org.junit.Test import org.mockito.InjectMocks diff --git a/app/src/test/kotlin/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDaoTest.kt index d764c8da7..a6dbfc50e 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDaoTest.kt @@ -21,7 +21,7 @@ import org.robolectric.annotation.Config import java.util.* @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class) +@Config(sdk = [21], application = TestCommonsApplication::class) class RecentSearchesDaoTest { private val columns = arrayOf(COLUMN_ID, COLUMN_NAME, COLUMN_LAST_USED) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/modifications/ModifierSequenceDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/modifications/ModifierSequenceDaoTest.kt index bf0ddf772..3187d48ff 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/modifications/ModifierSequenceDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/modifications/ModifierSequenceDaoTest.kt @@ -20,7 +20,7 @@ import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = [21], application = TestCommonsApplication::class) +@Config(sdk = [21], application = TestCommonsApplication::class) class ModifierSequenceDaoTest { private val mediaUrl = "http://example.com/" diff --git a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.kt index 04f20f7b9..907b07c74 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/ApacheHttpClientMediaWikiApiTest.kt @@ -1,8 +1,8 @@ package fr.free.nrw.commons.mwapi import android.os.Build +import androidx.test.core.app.ApplicationProvider import com.google.gson.Gson -import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.kvstore.JsonKvStore import fr.free.nrw.commons.utils.ConfigUtils @@ -17,14 +17,13 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock import org.robolectric.RobolectricTestRunner -import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config import org.wikipedia.util.DateUtil import java.net.URLDecoder import java.util.* @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = intArrayOf(21), application = TestCommonsApplication::class) +@Config(sdk = [21], application = TestCommonsApplication::class) class ApacheHttpClientMediaWikiApiTest { private lateinit var testObject: ApacheHttpClientMediaWikiApi @@ -39,7 +38,7 @@ class ApacheHttpClientMediaWikiApiTest { wikidataServer = MockWebServer() okHttpClient = OkHttpClient() sharedPreferences = mock(JsonKvStore::class.java) - testObject = ApacheHttpClientMediaWikiApi(RuntimeEnvironment.application, "http://" + server.hostName + ":" + server.port + "/", "http://" + wikidataServer.hostName + ":" + wikidataServer.port + "/", sharedPreferences, Gson()) + testObject = ApacheHttpClientMediaWikiApi(ApplicationProvider.getApplicationContext(), "http://" + server.hostName + ":" + server.port + "/", "http://" + wikidataServer.hostName + ":" + wikidataServer.port + "/", sharedPreferences, Gson()) } @After @@ -319,7 +318,7 @@ class ApacheHttpClientMediaWikiApiTest { private fun assertBasicRequestParameters(server: MockWebServer, method: String): RecordedRequest = server.takeRequest().let { assertEquals("/", it.requestUrl.encodedPath()) assertEquals(method, it.method) - assertEquals("Commons/${ConfigUtils.getVersionNameWithSha(RuntimeEnvironment.application)} (https://mediawiki.org/wiki/Apps/Commons) Android/${Build.VERSION.RELEASE}", + assertEquals("Commons/${ConfigUtils.getVersionNameWithSha(ApplicationProvider.getApplicationContext())} (https://mediawiki.org/wiki/Apps/Commons) Android/${Build.VERSION.RELEASE}", it.getHeader("User-Agent")) if ("POST" == method) { assertEquals("application/x-www-form-urlencoded", it.getHeader("Content-Type")) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/OkHttpJsonApiClientTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/OkHttpJsonApiClientTest.kt index 604095f60..e9766e5ee 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/mwapi/OkHttpJsonApiClientTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/mwapi/OkHttpJsonApiClientTest.kt @@ -29,7 +29,7 @@ import kotlin.random.Random * Mock web server based tests for ok http json api client */ @RunWith(RobolectricTestRunner::class) -@Config(constants = BuildConfig::class, sdk = [23], application = TestCommonsApplication::class) +@Config(sdk = [23], application = TestCommonsApplication::class) class OkHttpJsonApiClientTest { private lateinit var testObject: OkHttpJsonApiClient diff --git a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt index 42e59797a..a878fb5ae 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/review/ReviewHelperTest.kt @@ -4,7 +4,7 @@ import fr.free.nrw.commons.Media import fr.free.nrw.commons.mwapi.MediaWikiApi import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import io.reactivex.Single -import junit.framework.Assert.assertTrue +import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.ArgumentMatchers diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/FileMetadataUtilsTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/FileMetadataUtilsTest.kt new file mode 100644 index 000000000..6dde026a9 --- /dev/null +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/FileMetadataUtilsTest.kt @@ -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)) + } +} \ No newline at end of file diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/FileProcessorTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/FileProcessorTest.kt index 3d5803a1c..98f6a2e22 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/FileProcessorTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/FileProcessorTest.kt @@ -1,6 +1,7 @@ package fr.free.nrw.commons.upload import android.content.SharedPreferences +import androidx.exifinterface.media.ExifInterface import fr.free.nrw.commons.caching.CacheController import fr.free.nrw.commons.mwapi.CategoryApi import org.junit.Before @@ -11,6 +12,9 @@ import org.mockito.MockitoAnnotations import javax.inject.Inject import javax.inject.Named +import java.io.FileInputStream +import java.io.FileOutputStream + class FileProcessorTest { @Mock @@ -35,4 +39,51 @@ class FileProcessorTest { fun processFileCoordinates() { } + + /** + * Test method to verify redaction Exif metadata + */ + @Test + fun redactExifTags() { + /* + val filePathRef: String? = "src/test/data/exif_redact_sample.jpg" + val filePathTmp: String? = "" + System.getProperty("java.io.tmpdir") + "exif_redact_sample_tmp.jpg" + + val inStream = FileInputStream(filePathRef) + val outStream = FileOutputStream(filePathTmp) + val inChannel = inStream.getChannel() + val outChannel = outStream.getChannel() + inChannel.transferTo(0, inChannel.size(), outChannel) + inStream.close() + outStream.close() + + val redactTags = mutableSetOf("Author", "Copyright", "Location", "Camera Model", + "Lens Model", "Serial Numbers", "Software") + + val exifInterface : ExifInterface? = ExifInterface(filePathTmp.toString()) + + var nonEmptyTag = false + for (redactTag in redactTags) { + for (tag in FileMetadataUtils.getTagsFromPref(redactTag)) { + val tagValue = exifInterface?.getAttribute(tag) + if(tagValue != null) { + nonEmptyTag = true + break + } + } + if (nonEmptyTag) break + } + // all tags are empty, can't test redaction + assert(nonEmptyTag) + + FileProcessor.redactExifTags(exifInterface, redactTags) + + for (redactTag in redactTags) { + for (tag in FileMetadataUtils.getTagsFromPref(redactTag)) { + val oldValue = exifInterface?.getAttribute(tag) + assert(oldValue == null) + } + } + */ + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 4bf0b1e18..fc38b3a14 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,6 +15,7 @@ #Thu Mar 01 15:28:48 IST 2018 org.gradle.jvmargs=-Xmx1536M android.enableBuildCache=true +android.enableUnitTestBinaryResources=true KOTLIN_VERSION=1.3.21 BUTTERKNIFE_VERSION=10.1.0