mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
5196: Fix in-app camera location loss (#5249)
Merging as this is a great improvement, additional issues/bugs can be filed as GitHub issues. * fix in-app camera location loss * fix failing unit tests * UploadMediaDetailFragmentUnitTest: modify testOnActivityResultAddLocationDialog to have null location * reintroduce removed variable * enable prePopulateCategoriesAndDepictionsBy for current user location * add relevant comment and fix failing test * modify dialog and disable location tag redaction from EXIF * modify in-app camera dialog flow and change location to inAppPictureLocation * change location to inAppPictureLocation * fix location flow * preferences.xml: remove redundant default value * inform users about location loss happening for first upload * FileProcessor.kt: remove commented-out code * prevent user location from getting attached to images with no EXIF location in normal and custom selector * handle onPermissionDenied for location permission * remove last location when the user turns the GPS off * disable photo picker and in app camera preferences in settings for logged-out users * remove debug statements and add toast inside runnables
This commit is contained in:
parent
1cab938d81
commit
5073ca08c3
21 changed files with 537 additions and 92 deletions
|
|
@ -6,6 +6,7 @@ import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.widget.Toast;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.filepicker.DefaultCallback;
|
import fr.free.nrw.commons.filepicker.DefaultCallback;
|
||||||
|
|
@ -13,8 +14,14 @@ import fr.free.nrw.commons.filepicker.FilePicker;
|
||||||
import fr.free.nrw.commons.filepicker.FilePicker.ImageSource;
|
import fr.free.nrw.commons.filepicker.FilePicker.ImageSource;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.location.LocationPermissionsHelper;
|
||||||
|
import fr.free.nrw.commons.location.LocationPermissionsHelper.Dialog;
|
||||||
|
import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback;
|
||||||
|
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.upload.UploadActivity;
|
import fr.free.nrw.commons.upload.UploadActivity;
|
||||||
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -28,7 +35,11 @@ public class ContributionController {
|
||||||
|
|
||||||
public static final String ACTION_INTERNAL_UPLOADS = "internalImageUploads";
|
public static final String ACTION_INTERNAL_UPLOADS = "internalImageUploads";
|
||||||
private final JsonKvStore defaultKvStore;
|
private final JsonKvStore defaultKvStore;
|
||||||
|
private LatLng locationBeforeImageCapture;
|
||||||
|
private boolean isInAppCameraUpload;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
LocationServiceManager locationManager;
|
||||||
@Inject
|
@Inject
|
||||||
public ContributionController(@Named("default_preferences") JsonKvStore defaultKvStore) {
|
public ContributionController(@Named("default_preferences") JsonKvStore defaultKvStore) {
|
||||||
this.defaultKvStore = defaultKvStore;
|
this.defaultKvStore = defaultKvStore;
|
||||||
|
|
@ -46,11 +57,94 @@ public class ContributionController {
|
||||||
|
|
||||||
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
() -> initiateCameraUpload(activity),
|
() -> {
|
||||||
|
if (defaultKvStore.getBoolean("inAppCameraFirstRun")) {
|
||||||
|
defaultKvStore.putBoolean("inAppCameraFirstRun", false);
|
||||||
|
askUserToAllowLocationAccess(activity);
|
||||||
|
} else if(defaultKvStore.getBoolean("inAppCameraLocationPref")) {
|
||||||
|
createDialogsAndHandleLocationPermissions(activity);
|
||||||
|
} else {
|
||||||
|
initiateCameraUpload(activity);
|
||||||
|
}
|
||||||
|
},
|
||||||
R.string.storage_permission_title,
|
R.string.storage_permission_title,
|
||||||
R.string.write_storage_permission_rationale);
|
R.string.write_storage_permission_rationale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks users to provide location access
|
||||||
|
*
|
||||||
|
* @param activity
|
||||||
|
*/
|
||||||
|
private void createDialogsAndHandleLocationPermissions(Activity activity) {
|
||||||
|
LocationPermissionsHelper.Dialog locationAccessDialog = new Dialog(
|
||||||
|
R.string.location_permission_title,
|
||||||
|
R.string.in_app_camera_location_permission_rationale
|
||||||
|
);
|
||||||
|
|
||||||
|
LocationPermissionsHelper.Dialog locationOffDialog = new Dialog(
|
||||||
|
R.string.ask_to_turn_location_on,
|
||||||
|
R.string.in_app_camera_needs_location
|
||||||
|
);
|
||||||
|
LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper(
|
||||||
|
activity, locationManager,
|
||||||
|
new LocationPermissionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onLocationPermissionDenied() {
|
||||||
|
initiateCameraUpload(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationPermissionGranted() {
|
||||||
|
initiateCameraUpload(activity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
locationPermissionsHelper.handleLocationPermissions(
|
||||||
|
locationAccessDialog,
|
||||||
|
locationOffDialog
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suggest user to attach location information with pictures.
|
||||||
|
* If the user selects "Yes", then:
|
||||||
|
*
|
||||||
|
* Location is taken from the EXIF if the default camera application
|
||||||
|
* does not redact location tags.
|
||||||
|
*
|
||||||
|
* Otherwise, if the EXIF metadata does not have location information,
|
||||||
|
* then location captured by the app is used
|
||||||
|
*
|
||||||
|
* @param activity
|
||||||
|
*/
|
||||||
|
private void askUserToAllowLocationAccess(Activity activity) {
|
||||||
|
DialogUtil.showAlertDialog(activity,
|
||||||
|
activity.getString(R.string.in_app_camera_location_permission_title),
|
||||||
|
activity.getString(R.string.in_app_camera_location_access_explanation),
|
||||||
|
activity.getString(R.string.option_allow),
|
||||||
|
activity.getString(R.string.option_dismiss),
|
||||||
|
()-> {
|
||||||
|
defaultKvStore.putBoolean("inAppCameraLocationPref", true);
|
||||||
|
createDialogsAndHandleLocationPermissions(activity);
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
defaultKvStore.putBoolean("inAppCameraLocationPref", false);
|
||||||
|
initiateCameraUpload(activity);
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if apps have access to location even after having individual access
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isLocationAccessToAppsTurnedOn() {
|
||||||
|
return (locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate gallery picker
|
* Initiate gallery picker
|
||||||
*/
|
*/
|
||||||
|
|
@ -66,9 +160,7 @@ public class ContributionController {
|
||||||
|
|
||||||
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
() -> {
|
() -> FilePicker.openCustomSelector(activity, 0),
|
||||||
FilePicker.openCustomSelector(activity, 0);
|
|
||||||
},
|
|
||||||
R.string.storage_permission_title,
|
R.string.storage_permission_title,
|
||||||
R.string.write_storage_permission_rationale);
|
R.string.write_storage_permission_rationale);
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +191,10 @@ public class ContributionController {
|
||||||
*/
|
*/
|
||||||
private void initiateCameraUpload(Activity activity) {
|
private void initiateCameraUpload(Activity activity) {
|
||||||
setPickerConfiguration(activity, false);
|
setPickerConfiguration(activity, false);
|
||||||
|
if (defaultKvStore.getBoolean("inAppCameraLocationPref", false)) {
|
||||||
|
locationBeforeImageCapture = locationManager.getLastLocation();
|
||||||
|
}
|
||||||
|
isInAppCameraUpload = true;
|
||||||
FilePicker.openCameraForImage(activity, 0);
|
FilePicker.openCameraForImage(activity, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,7 +230,8 @@ public class ContributionController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns intent to be passed to upload activity
|
* Returns intent to be passed to upload activity
|
||||||
* Attaches place object for nearby uploads
|
* Attaches place object for nearby uploads and
|
||||||
|
* location before image capture if in-app camera is used
|
||||||
*/
|
*/
|
||||||
private Intent handleImagesPicked(Context context,
|
private Intent handleImagesPicked(Context context,
|
||||||
List<UploadableFile> imagesFiles) {
|
List<UploadableFile> imagesFiles) {
|
||||||
|
|
@ -148,6 +245,17 @@ public class ContributionController {
|
||||||
shareIntent.putExtra(PLACE_OBJECT, place);
|
shareIntent.putExtra(PLACE_OBJECT, place);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (locationBeforeImageCapture != null) {
|
||||||
|
shareIntent.putExtra(
|
||||||
|
UploadActivity.LOCATION_BEFORE_IMAGE_CAPTURE,
|
||||||
|
locationBeforeImageCapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
shareIntent.putExtra(
|
||||||
|
UploadActivity.IN_APP_CAMERA_UPLOAD,
|
||||||
|
isInAppCameraUpload
|
||||||
|
);
|
||||||
|
isInAppCameraUpload = false; // reset the flag for next use
|
||||||
return shareIntent;
|
return shareIntent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@ public class MainActivity extends BaseActivity
|
||||||
|
|
||||||
if ((applicationKvStore.getBoolean("firstrun", true)) &&
|
if ((applicationKvStore.getBoolean("firstrun", true)) &&
|
||||||
(!applicationKvStore.getBoolean("login_skipped"))) {
|
(!applicationKvStore.getBoolean("login_skipped"))) {
|
||||||
|
defaultKvStore.putBoolean("inAppCameraFirstRun", true);
|
||||||
WelcomeActivity.startYourself(this);
|
WelcomeActivity.startYourself(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
package fr.free.nrw.commons.location;
|
||||||
|
|
||||||
|
import android.Manifest.permission;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
|
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to handle location permissions
|
||||||
|
*/
|
||||||
|
public class LocationPermissionsHelper {
|
||||||
|
Activity activity;
|
||||||
|
LocationServiceManager locationManager;
|
||||||
|
LocationPermissionCallback callback;
|
||||||
|
public LocationPermissionsHelper(Activity activity, LocationServiceManager locationManager,
|
||||||
|
LocationPermissionCallback callback) {
|
||||||
|
this.activity = activity;
|
||||||
|
this.locationManager = locationManager;
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
public static class Dialog {
|
||||||
|
int dialogTitleResource;
|
||||||
|
int dialogTextResource;
|
||||||
|
|
||||||
|
public Dialog(int dialogTitle, int dialogText) {
|
||||||
|
dialogTitleResource = dialogTitle;
|
||||||
|
dialogTextResource = dialogText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the entire location permissions flow
|
||||||
|
*
|
||||||
|
* @param locationAccessDialog
|
||||||
|
* @param locationOffDialog
|
||||||
|
*/
|
||||||
|
public void handleLocationPermissions(Dialog locationAccessDialog,
|
||||||
|
Dialog locationOffDialog) {
|
||||||
|
requestForLocationAccess(locationAccessDialog, locationOffDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask for location permission if the user agrees on attaching location with pictures
|
||||||
|
* and the app does not have the access to location
|
||||||
|
*
|
||||||
|
* @param locationAccessDialog
|
||||||
|
* @param locationOffDialog
|
||||||
|
*/
|
||||||
|
private void requestForLocationAccess(
|
||||||
|
Dialog locationAccessDialog,
|
||||||
|
Dialog locationOffDialog
|
||||||
|
) {
|
||||||
|
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||||
|
permission.ACCESS_FINE_LOCATION,
|
||||||
|
() -> {
|
||||||
|
if(!isLocationAccessToAppsTurnedOn()) {
|
||||||
|
showLocationOffDialog(locationOffDialog);
|
||||||
|
} else {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onLocationPermissionGranted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
if (callback != null) {
|
||||||
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
R.string.in_app_camera_location_permission_denied,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show();
|
||||||
|
callback.onLocationPermissionDenied();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
locationAccessDialog.dialogTitleResource,
|
||||||
|
locationAccessDialog.dialogTextResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if apps have access to location even after having individual access
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isLocationAccessToAppsTurnedOn() {
|
||||||
|
return (locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask user to grant location access to apps
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
private void showLocationOffDialog(Dialog locationOffDialog) {
|
||||||
|
DialogUtil
|
||||||
|
.showAlertDialog(activity,
|
||||||
|
activity.getString(locationOffDialog.dialogTitleResource),
|
||||||
|
activity.getString(locationOffDialog.dialogTextResource),
|
||||||
|
activity.getString(R.string.title_app_shortcut_setting),
|
||||||
|
activity.getString(R.string.cancel),
|
||||||
|
() -> openLocationSettings(),
|
||||||
|
() -> {
|
||||||
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
R.string.in_app_camera_location_unavailable,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show();
|
||||||
|
callback.onLocationPermissionDenied();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open location source settings so that apps with location access can access it
|
||||||
|
*
|
||||||
|
* TODO: modify it to fix https://github.com/commons-app/apps-android-commons/issues/5255
|
||||||
|
*/
|
||||||
|
|
||||||
|
private void openLocationSettings() {
|
||||||
|
final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||||
|
final PackageManager packageManager = activity.getPackageManager();
|
||||||
|
|
||||||
|
if (intent.resolveActivity(packageManager)!= null) {
|
||||||
|
activity.startActivity(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle onPermissionDenied within individual classes based on the requirements
|
||||||
|
*/
|
||||||
|
public interface LocationPermissionCallback {
|
||||||
|
void onLocationPermissionDenied();
|
||||||
|
void onLocationPermissionGranted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -188,9 +188,9 @@ public class UploadRepository {
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
|
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
|
||||||
SimilarImageInterface similarImageInterface) {
|
SimilarImageInterface similarImageInterface, LatLng inAppPictureLocation) {
|
||||||
return uploadModel.preProcessImage(uploadableFile, place,
|
return uploadModel.preProcessImage(uploadableFile, place,
|
||||||
similarImageInterface);
|
similarImageInterface, inAppPictureLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -199,8 +199,8 @@ public class UploadRepository {
|
||||||
* @param uploadItem
|
* @param uploadItem
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public Single<Integer> getImageQuality(UploadItem uploadItem) {
|
public Single<Integer> getImageQuality(UploadItem uploadItem, LatLng location) {
|
||||||
return uploadModel.getImageQuality(uploadItem);
|
return uploadModel.getImageQuality(uploadItem, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import android.widget.AdapterView.OnItemClickListener;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
import androidx.preference.MultiSelectListPreference;
|
import androidx.preference.MultiSelectListPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
@ -30,13 +31,15 @@ import androidx.recyclerview.widget.RecyclerView.Adapter;
|
||||||
import com.karumi.dexter.Dexter;
|
import com.karumi.dexter.Dexter;
|
||||||
import com.karumi.dexter.listener.PermissionGrantedResponse;
|
import com.karumi.dexter.listener.PermissionGrantedResponse;
|
||||||
import com.karumi.dexter.listener.single.BasePermissionListener;
|
import com.karumi.dexter.listener.single.BasePermissionListener;
|
||||||
import com.mapbox.mapboxsdk.Mapbox;
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.campaigns.CampaignView;
|
import fr.free.nrw.commons.campaigns.CampaignView;
|
||||||
import fr.free.nrw.commons.contributions.MainActivity;
|
import fr.free.nrw.commons.contributions.MainActivity;
|
||||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.location.LocationPermissionsHelper;
|
||||||
|
import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback;
|
||||||
|
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||||
import fr.free.nrw.commons.logging.CommonsLogSender;
|
import fr.free.nrw.commons.logging.CommonsLogSender;
|
||||||
import fr.free.nrw.commons.recentlanguages.Language;
|
import fr.free.nrw.commons.recentlanguages.Language;
|
||||||
import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter;
|
import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter;
|
||||||
|
|
@ -65,6 +68,9 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
@Inject
|
@Inject
|
||||||
RecentLanguagesDao recentLanguagesDao;
|
RecentLanguagesDao recentLanguagesDao;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
LocationServiceManager locationManager;
|
||||||
|
|
||||||
private ListPreference themeListPreference;
|
private ListPreference themeListPreference;
|
||||||
private Preference descriptionLanguageListPreference;
|
private Preference descriptionLanguageListPreference;
|
||||||
private Preference appUiLanguageListPreference;
|
private Preference appUiLanguageListPreference;
|
||||||
|
|
@ -97,6 +103,18 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Preference inAppCameraLocationPref = findPreference("inAppCameraLocationPref");
|
||||||
|
|
||||||
|
inAppCameraLocationPref.setOnPreferenceChangeListener(
|
||||||
|
(preference, newValue) -> {
|
||||||
|
boolean isInAppCameraLocationTurnedOn = (boolean) newValue;
|
||||||
|
if (isInAppCameraLocationTurnedOn) {
|
||||||
|
createDialogsAndHandleLocationPermissions(getActivity());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Gets current language code from shared preferences
|
// Gets current language code from shared preferences
|
||||||
String languageCode;
|
String languageCode;
|
||||||
|
|
||||||
|
|
@ -172,9 +190,45 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
findPreference("displayLocationPermissionForCardView").setEnabled(false);
|
findPreference("displayLocationPermissionForCardView").setEnabled(false);
|
||||||
findPreference(CampaignView.CAMPAIGNS_DEFAULT_PREFERENCE).setEnabled(false);
|
findPreference(CampaignView.CAMPAIGNS_DEFAULT_PREFERENCE).setEnabled(false);
|
||||||
findPreference("managed_exif_tags").setEnabled(false);
|
findPreference("managed_exif_tags").setEnabled(false);
|
||||||
|
findPreference("openDocumentPhotoPickerPref").setEnabled(false);
|
||||||
|
findPreference("inAppCameraLocationPref").setEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks users to provide location access
|
||||||
|
*
|
||||||
|
* @param activity
|
||||||
|
*/
|
||||||
|
private void createDialogsAndHandleLocationPermissions(Activity activity) {
|
||||||
|
LocationPermissionsHelper.Dialog locationAccessDialog = new LocationPermissionsHelper.Dialog(
|
||||||
|
R.string.location_permission_title,
|
||||||
|
R.string.in_app_camera_location_permission_rationale
|
||||||
|
);
|
||||||
|
|
||||||
|
LocationPermissionsHelper.Dialog locationOffDialog = new LocationPermissionsHelper.Dialog(
|
||||||
|
R.string.ask_to_turn_location_on,
|
||||||
|
R.string.in_app_camera_needs_location
|
||||||
|
);
|
||||||
|
|
||||||
|
LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper(
|
||||||
|
activity, locationManager, new LocationPermissionCallback() {
|
||||||
|
@Override
|
||||||
|
public void onLocationPermissionDenied() {
|
||||||
|
// dismiss the dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLocationPermissionGranted() {
|
||||||
|
// dismiss the dialog
|
||||||
|
}
|
||||||
|
});
|
||||||
|
locationPermissionsHelper.handleLocationPermissions(
|
||||||
|
locationAccessDialog,
|
||||||
|
locationOffDialog
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On some devices, the new Photo Picker with GET_CONTENT takeover
|
* On some devices, the new Photo Picker with GET_CONTENT takeover
|
||||||
* redacts location tags from EXIF metadata
|
* redacts location tags from EXIF metadata
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import android.net.Uri
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||||
|
import fr.free.nrw.commons.location.LatLng
|
||||||
import fr.free.nrw.commons.mwapi.CategoryApi
|
import fr.free.nrw.commons.mwapi.CategoryApi
|
||||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||||
import fr.free.nrw.commons.settings.Prefs
|
import fr.free.nrw.commons.settings.Prefs
|
||||||
|
|
@ -48,7 +49,8 @@ class FileProcessor @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Processes filePath coordinates, either from EXIF data or user location
|
* Processes filePath coordinates, either from EXIF data or user location
|
||||||
*/
|
*/
|
||||||
fun processFileCoordinates(similarImageInterface: SimilarImageInterface, filePath: String?)
|
fun processFileCoordinates(similarImageInterface: SimilarImageInterface,
|
||||||
|
filePath: String?, inAppPictureLocation: LatLng?)
|
||||||
: ImageCoordinates {
|
: ImageCoordinates {
|
||||||
val exifInterface: ExifInterface? = try {
|
val exifInterface: ExifInterface? = try {
|
||||||
ExifInterface(filePath!!)
|
ExifInterface(filePath!!)
|
||||||
|
|
@ -59,7 +61,7 @@ class FileProcessor @Inject constructor(
|
||||||
// Redact EXIF data as indicated in preferences.
|
// Redact EXIF data as indicated in preferences.
|
||||||
redactExifTags(exifInterface, getExifTagsToRedact())
|
redactExifTags(exifInterface, getExifTagsToRedact())
|
||||||
Timber.d("Calling GPSExtractor")
|
Timber.d("Calling GPSExtractor")
|
||||||
val originalImageCoordinates = ImageCoordinates(exifInterface)
|
val originalImageCoordinates = ImageCoordinates(exifInterface, inAppPictureLocation)
|
||||||
if (originalImageCoordinates.decimalCoords == null) {
|
if (originalImageCoordinates.decimalCoords == null) {
|
||||||
//Find other photos taken around the same time which has gps coordinates
|
//Find other photos taken around the same time which has gps coordinates
|
||||||
findOtherImages(
|
findOtherImages(
|
||||||
|
|
@ -156,11 +158,13 @@ class FileProcessor @Inject constructor(
|
||||||
|
|
||||||
private fun readImageCoordinates(file: File) =
|
private fun readImageCoordinates(file: File) =
|
||||||
try {
|
try {
|
||||||
ImageCoordinates(contentResolver.openInputStream(Uri.fromFile(file))!!)
|
/* Used null location as location for similar images captured before is not available
|
||||||
|
in case it is not present in the EXIF. */
|
||||||
|
ImageCoordinates(contentResolver.openInputStream(Uri.fromFile(file))!!, null)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
try {
|
try {
|
||||||
ImageCoordinates(file.absolutePath)
|
ImageCoordinates(file.absolutePath, null)
|
||||||
} catch (ex: IOException) {
|
} catch (ex: IOException) {
|
||||||
Timber.e(ex)
|
Timber.e(ex)
|
||||||
null
|
null
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
|
@ -64,11 +65,11 @@ public class FileUtils {
|
||||||
/**
|
/**
|
||||||
* Get Geolocation of filePath from input filePath path
|
* Get Geolocation of filePath from input filePath path
|
||||||
*/
|
*/
|
||||||
static String getGeolocationOfFile(String filePath) {
|
static String getGeolocationOfFile(String filePath, LatLng inAppPictureLocation) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ExifInterface exifInterface = new ExifInterface(filePath);
|
ExifInterface exifInterface = new ExifInterface(filePath);
|
||||||
ImageCoordinates imageObj = new ImageCoordinates(exifInterface);
|
ImageCoordinates imageObj = new ImageCoordinates(exifInterface, inAppPictureLocation);
|
||||||
if (imageObj.getDecimalCoords() != null) { // If image has geolocation information in its EXIF
|
if (imageObj.getDecimalCoords() != null) { // If image has geolocation information in its EXIF
|
||||||
return imageObj.getDecimalCoords();
|
return imageObj.getDecimalCoords();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
@ -37,8 +38,8 @@ public class FileUtilsWrapper {
|
||||||
return FileUtils.getFileInputStream(filePath);
|
return FileUtils.getFileInputStream(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getGeolocationOfFile(String filePath) {
|
public String getGeolocationOfFile(String filePath, LatLng inAppPictureLocation) {
|
||||||
return FileUtils.getGeolocationOfFile(filePath);
|
return FileUtils.getGeolocationOfFile(filePath, inAppPictureLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ import java.io.InputStream
|
||||||
/**
|
/**
|
||||||
* Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation
|
* Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation
|
||||||
* is uploaded, extract latitude and longitude from EXIF data of image.
|
* is uploaded, extract latitude and longitude from EXIF data of image.
|
||||||
|
* Otherwise, if current user location is available while using the in-app camera,
|
||||||
|
* use it to set image coordinates
|
||||||
*/
|
*/
|
||||||
class ImageCoordinates internal constructor(exif: ExifInterface?) {
|
class ImageCoordinates internal constructor(exif: ExifInterface?, inAppPictureLocation: LatLng?) {
|
||||||
var decLatitude = 0.0
|
var decLatitude = 0.0
|
||||||
var decLongitude = 0.0
|
var decLongitude = 0.0
|
||||||
var imageCoordsExists = false
|
var imageCoordsExists = false
|
||||||
|
|
@ -26,13 +28,13 @@ class ImageCoordinates internal constructor(exif: ExifInterface?) {
|
||||||
/**
|
/**
|
||||||
* Construct from a stream.
|
* Construct from a stream.
|
||||||
*/
|
*/
|
||||||
internal constructor(stream: InputStream) : this(ExifInterface(stream))
|
internal constructor(stream: InputStream, inAppPictureLocation: LatLng?) : this(ExifInterface(stream), inAppPictureLocation)
|
||||||
/**
|
/**
|
||||||
* Construct from the file path of the image.
|
* Construct from the file path of the image.
|
||||||
* @param path file path of the image
|
* @param path file path of the image
|
||||||
*/
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
internal constructor(path: String) : this(ExifInterface(path))
|
internal constructor(path: String, inAppPictureLocation: LatLng?) : this(ExifInterface(path), inAppPictureLocation)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
||||||
|
|
@ -61,6 +63,14 @@ class ImageCoordinates internal constructor(exif: ExifInterface?) {
|
||||||
//If image has EXIF data, extract image coords
|
//If image has EXIF data, extract image coords
|
||||||
imageCoordsExists = true
|
imageCoordsExists = true
|
||||||
Timber.d("EXIF data has location info")
|
Timber.d("EXIF data has location info")
|
||||||
|
} else if (inAppPictureLocation != null) {
|
||||||
|
decLatitude = inAppPictureLocation.latitude
|
||||||
|
decLongitude = inAppPictureLocation.longitude
|
||||||
|
if (!(decLatitude == 0.0 && decLongitude == 0.0)) {
|
||||||
|
decimalCoords = "$decLatitude|$decLongitude"
|
||||||
|
imageCoordsExists = true
|
||||||
|
Timber.d("Image coordinates recorded while using in-app camera")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.media.MediaClient;
|
import fr.free.nrw.commons.media.MediaClient;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
|
|
@ -46,7 +47,7 @@ public class ImageProcessingService {
|
||||||
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
||||||
* geolocation for image - check for valid title
|
* geolocation for image - check for valid title
|
||||||
*/
|
*/
|
||||||
Single<Integer> validateImage(UploadItem uploadItem) {
|
Single<Integer> validateImage(UploadItem uploadItem, LatLng inAppPictureLocation) {
|
||||||
int currentImageQuality = uploadItem.getImageQuality();
|
int currentImageQuality = uploadItem.getImageQuality();
|
||||||
Timber.d("Current image quality is %d", currentImageQuality);
|
Timber.d("Current image quality is %d", currentImageQuality);
|
||||||
if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
|
if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
|
||||||
|
|
@ -57,7 +58,7 @@ public class ImageProcessingService {
|
||||||
|
|
||||||
return Single.zip(
|
return Single.zip(
|
||||||
checkDuplicateImage(filePath),
|
checkDuplicateImage(filePath),
|
||||||
checkImageGeoLocation(uploadItem.getPlace(), filePath),
|
checkImageGeoLocation(uploadItem.getPlace(), filePath, inAppPictureLocation),
|
||||||
checkDarkImage(filePath),
|
checkDarkImage(filePath),
|
||||||
validateItemTitle(uploadItem),
|
validateItemTitle(uploadItem),
|
||||||
checkFBMD(filePath),
|
checkFBMD(filePath),
|
||||||
|
|
@ -148,13 +149,13 @@ public class ImageProcessingService {
|
||||||
* @param filePath file to be checked
|
* @param filePath file to be checked
|
||||||
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
|
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
|
||||||
*/
|
*/
|
||||||
private Single<Integer> checkImageGeoLocation(Place place, String filePath) {
|
private Single<Integer> checkImageGeoLocation(Place place, String filePath, LatLng inAppPictureLocation) {
|
||||||
Timber.d("Checking for image geolocation %s", filePath);
|
Timber.d("Checking for image geolocation %s", filePath);
|
||||||
if (place == null || StringUtils.isBlank(place.getWikiDataEntityId())) {
|
if (place == null || StringUtils.isBlank(place.getWikiDataEntityId())) {
|
||||||
return Single.just(ImageUtils.IMAGE_OK);
|
return Single.just(ImageUtils.IMAGE_OK);
|
||||||
}
|
}
|
||||||
return Single.fromCallable(() -> filePath)
|
return Single.fromCallable(() -> filePath)
|
||||||
.map(fileUtilsWrapper::getGeolocationOfFile)
|
.flatMap(path -> Single.just(fileUtilsWrapper.getGeolocationOfFile(path, inAppPictureLocation)))
|
||||||
.flatMap(geoLocation -> {
|
.flatMap(geoLocation -> {
|
||||||
if (StringUtils.isBlank(geoLocation)) {
|
if (StringUtils.isBlank(geoLocation)) {
|
||||||
return Single.just(ImageUtils.IMAGE_OK);
|
return Single.just(ImageUtils.IMAGE_OK);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.location.Location;
|
||||||
|
import android.location.LocationManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
@ -38,8 +40,12 @@ import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
import fr.free.nrw.commons.contributions.MainActivity;
|
import fr.free.nrw.commons.contributions.MainActivity;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.location.LocationPermissionsHelper;
|
||||||
|
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||||
import fr.free.nrw.commons.mwapi.UserClient;
|
import fr.free.nrw.commons.mwapi.UserClient;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
import fr.free.nrw.commons.theme.BaseActivity;
|
||||||
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
|
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
|
||||||
import fr.free.nrw.commons.upload.depicts.DepictsFragment;
|
import fr.free.nrw.commons.upload.depicts.DepictsFragment;
|
||||||
|
|
@ -57,6 +63,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
@ -74,6 +81,8 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
SessionManager sessionManager;
|
SessionManager sessionManager;
|
||||||
@Inject
|
@Inject
|
||||||
UserClient userClient;
|
UserClient userClient;
|
||||||
|
@Inject
|
||||||
|
LocationServiceManager locationManager;
|
||||||
|
|
||||||
|
|
||||||
@BindView(R.id.cv_container_top_card)
|
@BindView(R.id.cv_container_top_card)
|
||||||
|
|
@ -109,6 +118,9 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
private ThumbnailsAdapter thumbnailsAdapter;
|
private ThumbnailsAdapter thumbnailsAdapter;
|
||||||
|
|
||||||
private Place place;
|
private Place place;
|
||||||
|
private LatLng prevLocation;
|
||||||
|
private LatLng currLocation;
|
||||||
|
private boolean isInAppCameraUpload;
|
||||||
private List<UploadableFile> uploadableFiles = Collections.emptyList();
|
private List<UploadableFile> uploadableFiles = Collections.emptyList();
|
||||||
private int currentSelectedPosition = 0;
|
private int currentSelectedPosition = 0;
|
||||||
/*
|
/*
|
||||||
|
|
@ -117,6 +129,8 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
private boolean isMultipleFilesSelected = false;
|
private boolean isMultipleFilesSelected = false;
|
||||||
|
|
||||||
public static final String EXTRA_FILES = "commons_image_exta";
|
public static final String EXTRA_FILES = "commons_image_exta";
|
||||||
|
public static final String LOCATION_BEFORE_IMAGE_CAPTURE = "user_location_before_image_capture";
|
||||||
|
public static final String IN_APP_CAMERA_UPLOAD = "in_app_camera_upload";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores all nearby places found and related users response for
|
* Stores all nearby places found and related users response for
|
||||||
|
|
@ -148,6 +162,11 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
if (dpi<=321) {
|
if (dpi<=321) {
|
||||||
onRlContainerTitleClicked();
|
onRlContainerTitleClicked();
|
||||||
}
|
}
|
||||||
|
if (PermissionUtils.hasPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
|
||||||
|
locationManager.registerLocationManager();
|
||||||
|
}
|
||||||
|
locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
|
||||||
|
locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
|
|
@ -366,7 +385,29 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
fragments = new ArrayList<>();
|
fragments = new ArrayList<>();
|
||||||
for (UploadableFile uploadableFile : uploadableFiles) {
|
for (UploadableFile uploadableFile : uploadableFiles) {
|
||||||
UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment();
|
UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment();
|
||||||
uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, place);
|
|
||||||
|
LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper(
|
||||||
|
this, locationManager, null);
|
||||||
|
if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) {
|
||||||
|
currLocation = locationManager.getLastLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currLocation != null) {
|
||||||
|
float locationDifference = getLocationDifference(currLocation, prevLocation);
|
||||||
|
boolean isLocationTagUnchecked = isLocationTagUncheckedInTheSettings();
|
||||||
|
/* Remove location if the user has unchecked the Location EXIF tag in the
|
||||||
|
Manage EXIF Tags setting or turned "Record location for in-app shots" off.
|
||||||
|
Also, location information is discarded if the difference between
|
||||||
|
current location and location recorded just before capturing the image
|
||||||
|
is greater than 100 meters */
|
||||||
|
if (isLocationTagUnchecked || locationDifference > 100
|
||||||
|
|| !defaultKvStore.getBoolean("inAppCameraLocationPref")
|
||||||
|
|| !isInAppCameraUpload) {
|
||||||
|
currLocation = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, place, currLocation);
|
||||||
|
locationManager.unregisterLocationManager();
|
||||||
uploadMediaDetailFragment.setCallback(new UploadMediaDetailFragmentCallback() {
|
uploadMediaDetailFragment.setCallback(new UploadMediaDetailFragmentCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void deletePictureAtIndex(int index) {
|
public void deletePictureAtIndex(int index) {
|
||||||
|
|
@ -424,6 +465,39 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users may uncheck Location tag from the Manage EXIF tags setting any time.
|
||||||
|
* So, their location must not be shared in this case.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isLocationTagUncheckedInTheSettings() {
|
||||||
|
Set<String> prefExifTags = defaultKvStore.getStringSet(Prefs.MANAGED_EXIF_TAGS);
|
||||||
|
if (prefExifTags.contains(getString(R.string.exif_tag_location))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the difference between current location and
|
||||||
|
* location recorded before capturing the image
|
||||||
|
*
|
||||||
|
* @param currLocation
|
||||||
|
* @param prevLocation
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private float getLocationDifference(LatLng currLocation, LatLng prevLocation) {
|
||||||
|
if (prevLocation == null) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
float[] distance = new float[2];
|
||||||
|
Location.distanceBetween(
|
||||||
|
currLocation.getLatitude(), currLocation.getLongitude(),
|
||||||
|
prevLocation.getLatitude(), prevLocation.getLongitude(), distance);
|
||||||
|
return distance[0];
|
||||||
|
}
|
||||||
|
|
||||||
private void receiveExternalSharedItems() {
|
private void receiveExternalSharedItems() {
|
||||||
uploadableFiles = contributionController.handleExternalImagesPicked(this, getIntent());
|
uploadableFiles = contributionController.handleExternalImagesPicked(this, getIntent());
|
||||||
}
|
}
|
||||||
|
|
@ -438,6 +512,8 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
||||||
Timber.i("Received multiple upload %s", uploadableFiles.size());
|
Timber.i("Received multiple upload %s", uploadableFiles.size());
|
||||||
|
|
||||||
place = intent.getParcelableExtra(PLACE_OBJECT);
|
place = intent.getParcelableExtra(PLACE_OBJECT);
|
||||||
|
prevLocation = intent.getParcelableExtra(LOCATION_BEFORE_IMAGE_CAPTURE);
|
||||||
|
isInAppCameraUpload = intent.getBooleanExtra(IN_APP_CAMERA_UPLOAD, false);
|
||||||
resetDirectPrefs();
|
resetDirectPrefs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.upload.depicts.DepictsFragment;
|
import fr.free.nrw.commons.upload.depicts.DepictsFragment;
|
||||||
|
|
@ -86,18 +87,20 @@ public class UploadModel {
|
||||||
*/
|
*/
|
||||||
public Observable<UploadItem> preProcessImage(final UploadableFile uploadableFile,
|
public Observable<UploadItem> preProcessImage(final UploadableFile uploadableFile,
|
||||||
final Place place,
|
final Place place,
|
||||||
final SimilarImageInterface similarImageInterface) {
|
final SimilarImageInterface similarImageInterface,
|
||||||
|
LatLng inAppPictureLocation) {
|
||||||
return Observable.just(
|
return Observable.just(
|
||||||
createAndAddUploadItem(uploadableFile, place, similarImageInterface));
|
createAndAddUploadItem(uploadableFile, place, similarImageInterface, inAppPictureLocation));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Single<Integer> getImageQuality(final UploadItem uploadItem) {
|
public Single<Integer> getImageQuality(final UploadItem uploadItem, LatLng inAppPictureLocation) {
|
||||||
return imageProcessingService.validateImage(uploadItem);
|
return imageProcessingService.validateImage(uploadItem, inAppPictureLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile,
|
private UploadItem createAndAddUploadItem(final UploadableFile uploadableFile,
|
||||||
final Place place,
|
final Place place,
|
||||||
final SimilarImageInterface similarImageInterface) {
|
final SimilarImageInterface similarImageInterface,
|
||||||
|
LatLng inAppPictureLocation) {
|
||||||
final UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
|
final UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
|
||||||
.getFileCreatedDate(context);
|
.getFileCreatedDate(context);
|
||||||
long fileCreatedDate = -1;
|
long fileCreatedDate = -1;
|
||||||
|
|
@ -110,7 +113,8 @@ public class UploadModel {
|
||||||
}
|
}
|
||||||
Timber.d("File created date is %d", fileCreatedDate);
|
Timber.d("File created date is %d", fileCreatedDate);
|
||||||
final ImageCoordinates imageCoordinates = fileProcessor
|
final ImageCoordinates imageCoordinates = fileProcessor
|
||||||
.processFileCoordinates(similarImageInterface, uploadableFile.getFilePath());
|
.processFileCoordinates(similarImageInterface, uploadableFile.getFilePath(),
|
||||||
|
inAppPictureLocation);
|
||||||
final UploadItem uploadItem = new UploadItem(
|
final UploadItem uploadItem = new UploadItem(
|
||||||
Uri.parse(uploadableFile.getFilePath()),
|
Uri.parse(uploadableFile.getFilePath()),
|
||||||
uploadableFile.getMimeType(context), imageCoordinates, place, fileCreatedDate,
|
uploadableFile.getMimeType(context), imageCoordinates, place, fileCreatedDate,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ import fr.free.nrw.commons.LocationPicker.LocationPicker;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
|
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
|
|
@ -117,6 +119,11 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
*/
|
*/
|
||||||
private Place nearbyPlace;
|
private Place nearbyPlace;
|
||||||
private UploadItem uploadItem;
|
private UploadItem uploadItem;
|
||||||
|
/**
|
||||||
|
* inAppPictureLocation: use location recorded while using the in-app camera if
|
||||||
|
* device camera does not record it in the EXIF
|
||||||
|
*/
|
||||||
|
private LatLng inAppPictureLocation;
|
||||||
/**
|
/**
|
||||||
* editableUploadItem : Storing the upload item before going to update the coordinates
|
* editableUploadItem : Storing the upload item before going to update the coordinates
|
||||||
*/
|
*/
|
||||||
|
|
@ -133,9 +140,10 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setImageTobeUploaded(UploadableFile uploadableFile, Place place) {
|
public void setImageTobeUploaded(UploadableFile uploadableFile, Place place, LatLng inAppPictureLocation) {
|
||||||
this.uploadableFile = uploadableFile;
|
this.uploadableFile = uploadableFile;
|
||||||
this.place = place;
|
this.place = place;
|
||||||
|
this.inAppPictureLocation = inAppPictureLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
@ -160,7 +168,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
tooltip.setOnClickListener(
|
tooltip.setOnClickListener(
|
||||||
v -> showInfoAlert(R.string.media_detail_step_title, R.string.media_details_tooltip));
|
v -> showInfoAlert(R.string.media_detail_step_title, R.string.media_details_tooltip));
|
||||||
initPresenter();
|
initPresenter();
|
||||||
presenter.receiveImage(uploadableFile, place);
|
presenter.receiveImage(uploadableFile, place, inAppPictureLocation);
|
||||||
initRecyclerView();
|
initRecyclerView();
|
||||||
|
|
||||||
if (callback.getIndexInViewFlipper(this) == 0) {
|
if (callback.getIndexInViewFlipper(this) == 0) {
|
||||||
|
|
@ -222,7 +230,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
|
|
||||||
@OnClick(R.id.btn_next)
|
@OnClick(R.id.btn_next)
|
||||||
public void onNextButtonClicked() {
|
public void onNextButtonClicked() {
|
||||||
presenter.verifyImageQuality(callback.getIndexInViewFlipper(this));
|
presenter.verifyImageQuality(callback.getIndexInViewFlipper(this), inAppPictureLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.btn_previous)
|
@OnClick(R.id.btn_previous)
|
||||||
|
|
@ -448,6 +456,9 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
double defaultLongitude = -122.431297;
|
double defaultLongitude = -122.431297;
|
||||||
double defaultZoom = 16.0;
|
double defaultZoom = 16.0;
|
||||||
|
|
||||||
|
/* Retrieve image location from EXIF if present or
|
||||||
|
check if user has provided location while using the in-app camera.
|
||||||
|
Use location of last UploadItem if none of them is available */
|
||||||
if (uploadItem.getGpsCoords() != null && uploadItem.getGpsCoords()
|
if (uploadItem.getGpsCoords() != null && uploadItem.getGpsCoords()
|
||||||
.getDecLatitude() != 0.0 && uploadItem.getGpsCoords().getDecLongitude() != 0.0) {
|
.getDecLatitude() != 0.0 && uploadItem.getGpsCoords().getDecLongitude() != 0.0) {
|
||||||
defaultLatitude = uploadItem.getGpsCoords()
|
defaultLatitude = uploadItem.getGpsCoords()
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
|
|
||||||
import fr.free.nrw.commons.BasePresenter;
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.upload.ImageCoordinates;
|
import fr.free.nrw.commons.upload.ImageCoordinates;
|
||||||
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
||||||
|
|
@ -43,9 +44,9 @@ public interface UploadMediaDetailsContract {
|
||||||
|
|
||||||
interface UserActionListener extends BasePresenter<View> {
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
void receiveImage(UploadableFile uploadableFile, Place place);
|
void receiveImage(UploadableFile uploadableFile, Place place, LatLng inAppPictureLocation);
|
||||||
|
|
||||||
void verifyImageQuality(int uploadItemIndex);
|
void verifyImageQuality(int uploadItemIndex, LatLng inAppPictureLocation);
|
||||||
|
|
||||||
void copyTitleAndDescriptionToSubsequentMedia(int indexInViewFlipper);
|
void copyTitleAndDescriptionToSubsequentMedia(int indexInViewFlipper);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,11 +87,12 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
* @param place
|
* @param place
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void receiveImage(final UploadableFile uploadableFile, final Place place) {
|
public void receiveImage(final UploadableFile uploadableFile, final Place place,
|
||||||
|
LatLng inAppPictureLocation) {
|
||||||
view.showProgress(true);
|
view.showProgress(true);
|
||||||
compositeDisposable.add(
|
compositeDisposable.add(
|
||||||
repository
|
repository
|
||||||
.preProcessImage(uploadableFile, place, this)
|
.preProcessImage(uploadableFile, place, this, inAppPictureLocation)
|
||||||
.map(uploadItem -> {
|
.map(uploadItem -> {
|
||||||
if(place!=null && place.isMonument()){
|
if(place!=null && place.isMonument()){
|
||||||
if (place.location != null) {
|
if (place.location != null) {
|
||||||
|
|
@ -177,15 +178,15 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
* @param uploadItemIndex
|
* @param uploadItemIndex
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void verifyImageQuality(int uploadItemIndex) {
|
public void verifyImageQuality(int uploadItemIndex, LatLng inAppPictureLocation) {
|
||||||
final UploadItem uploadItem = repository.getUploads().get(uploadItemIndex);
|
final UploadItem uploadItem = repository.getUploads().get(uploadItemIndex);
|
||||||
|
|
||||||
if (uploadItem.getGpsCoords().getDecimalCoords() == null) {
|
if (uploadItem.getGpsCoords().getDecimalCoords() == null && inAppPictureLocation == null) {
|
||||||
final Runnable onSkipClicked = () -> {
|
final Runnable onSkipClicked = () -> {
|
||||||
view.showProgress(true);
|
view.showProgress(true);
|
||||||
compositeDisposable.add(
|
compositeDisposable.add(
|
||||||
repository
|
repository
|
||||||
.getImageQuality(uploadItem)
|
.getImageQuality(uploadItem, inAppPictureLocation)
|
||||||
.observeOn(mainThreadScheduler)
|
.observeOn(mainThreadScheduler)
|
||||||
.subscribe(imageResult -> {
|
.subscribe(imageResult -> {
|
||||||
view.showProgress(false);
|
view.showProgress(false);
|
||||||
|
|
@ -208,7 +209,7 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
||||||
view.showProgress(true);
|
view.showProgress(true);
|
||||||
compositeDisposable.add(
|
compositeDisposable.add(
|
||||||
repository
|
repository
|
||||||
.getImageQuality(uploadItem)
|
.getImageQuality(uploadItem, inAppPictureLocation)
|
||||||
.observeOn(mainThreadScheduler)
|
.observeOn(mainThreadScheduler)
|
||||||
.subscribe(imageResult -> {
|
.subscribe(imageResult -> {
|
||||||
view.showProgress(false);
|
view.showProgress(false);
|
||||||
|
|
|
||||||
|
|
@ -190,6 +190,8 @@
|
||||||
<string name="read_storage_permission_rationale">Required permission: Read external storage. App cannot access your gallery without this.</string>
|
<string name="read_storage_permission_rationale">Required permission: Read external storage. App cannot access your gallery without this.</string>
|
||||||
<string name="write_storage_permission_rationale">Required permission: Write external storage. App cannot access your camera/gallery without this.</string>
|
<string name="write_storage_permission_rationale">Required permission: Write external storage. App cannot access your camera/gallery without this.</string>
|
||||||
<string name="location_permission_title">Requesting Location Permission</string>
|
<string name="location_permission_title">Requesting Location Permission</string>
|
||||||
|
<string name="in_app_camera_location_permission_title">Record location for in-app shots</string>
|
||||||
|
<string name="in_app_camera_location_switch_pref_summary">Enable this to record location with in-app shots in case the device camera does not record it</string>
|
||||||
<string name="ok">OK</string>
|
<string name="ok">OK</string>
|
||||||
<string name="warning">Warning</string>
|
<string name="warning">Warning</string>
|
||||||
<string name="duplicate_file_name">Duplicate File Name found</string>
|
<string name="duplicate_file_name">Duplicate File Name found</string>
|
||||||
|
|
@ -440,6 +442,13 @@ Upload your first media by tapping on the add button.</string>
|
||||||
<string name="ends_on">Ends on:</string>
|
<string name="ends_on">Ends on:</string>
|
||||||
<string name="display_campaigns">Display campaigns</string>
|
<string name="display_campaigns">Display campaigns</string>
|
||||||
<string name="display_campaigns_explanation">See the ongoing campaigns</string>
|
<string name="display_campaigns_explanation">See the ongoing campaigns</string>
|
||||||
|
<string name="in_app_camera_location_access_explanation">Allow the app to fetch location in case the camera does not record it. Some device cameras do not record location. In such cases, letting the app fetch and attach location to it makes your contribution more useful. You may change this any time from the Settings</string>
|
||||||
|
<string name="option_allow">Allow</string>
|
||||||
|
<string name="option_dismiss">Dismiss</string>
|
||||||
|
<string name="in_app_camera_needs_location">Please turn on location access from the Settings and try again. \n\nNote: The upload may not have location if app is unable to retrieve location from device within a short interval.</string>
|
||||||
|
<string name="in_app_camera_location_permission_rationale">In-app camera needs location permission to attach it to your images in case location is not available in EXIF. Please allow the app to access your location and try again.\n\nNote: The upload may not have location if app is unable to retrieve location from device within a short interval.</string>
|
||||||
|
<string name="in_app_camera_location_permission_denied">The app would not record location along with in-shots due to lack of location permission</string>
|
||||||
|
<string name="in_app_camera_location_unavailable">The app would not record location along with in-shots as the GPS is turned off</string>
|
||||||
<string name="open_document_photo_picker_title">Use document based photo picker</string>
|
<string name="open_document_photo_picker_title">Use document based photo picker</string>
|
||||||
<string name="open_document_photo_picker_explanation">The new Android photo picker risks losing location information. Enable if you seem to be using it.</string>
|
<string name="open_document_photo_picker_explanation">The new Android photo picker risks losing location information. Enable if you seem to be using it.</string>
|
||||||
<string name="location_loss_warning">Turning this off could trigger the new Android photo picker. It risks losing location information.\n\nTap on \'Read more\' for more information.</string>
|
<string name="location_loss_warning">Turning this off could trigger the new Android photo picker. It risks losing location information.\n\nTap on \'Read more\' for more information.</string>
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,6 @@
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/preference_category_general">
|
android:title="@string/preference_category_general">
|
||||||
|
|
||||||
<SwitchPreference
|
|
||||||
android:defaultValue="true"
|
|
||||||
android:key="useExternalStorage"
|
|
||||||
app:singleLineTitle="false"
|
|
||||||
android:summary="@string/use_external_storage_summary"
|
|
||||||
android:title="@string/use_external_storage" />
|
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="appUiDefaultLanguagePref"
|
android:key="appUiDefaultLanguagePref"
|
||||||
app:useSimpleSummaryProvider="true"
|
app:useSimpleSummaryProvider="true"
|
||||||
|
|
@ -38,19 +31,6 @@
|
||||||
app:singleLineTitle="false"
|
app:singleLineTitle="false"
|
||||||
android:title="@string/default_description_language" />
|
android:title="@string/default_description_language" />
|
||||||
|
|
||||||
<SwitchPreference
|
|
||||||
android:key="useAuthorName"
|
|
||||||
app:singleLineTitle="false"
|
|
||||||
android:summary="@string/preference_author_name_toggle_summary"
|
|
||||||
android:title="@string/preference_author_name_toggle" />
|
|
||||||
|
|
||||||
<EditTextPreference
|
|
||||||
android:key="authorName"
|
|
||||||
app:singleLineTitle="false"
|
|
||||||
app:dependency="useAuthorName"
|
|
||||||
app:useSimpleSummaryProvider="true"
|
|
||||||
android:title="@string/preference_author_name" />
|
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="displayNearbyCardView"
|
android:key="displayNearbyCardView"
|
||||||
|
|
@ -71,11 +51,43 @@
|
||||||
android:summary="@string/display_campaigns_explanation"
|
android:summary="@string/display_campaigns_explanation"
|
||||||
android:title="@string/display_campaigns" />
|
android:title="@string/display_campaigns" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:title="Uploads">
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:key="useExternalStorage"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
android:summary="@string/use_external_storage_summary"
|
||||||
|
android:title="@string/use_external_storage" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:key="inAppCameraLocationPref"
|
||||||
|
android:title="@string/in_app_camera_location_permission_title"
|
||||||
|
android:summary="@string/in_app_camera_location_switch_pref_summary"/>
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="openDocumentPhotoPickerPref"
|
android:key="openDocumentPhotoPickerPref"
|
||||||
android:summary="@string/open_document_photo_picker_explanation"
|
android:summary="@string/open_document_photo_picker_explanation"
|
||||||
android:title="@string/open_document_photo_picker_title"/>
|
android:title="@string/open_document_photo_picker_title"/>
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:key="useAuthorName"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
android:summary="@string/preference_author_name_toggle_summary"
|
||||||
|
android:title="@string/preference_author_name_toggle" />
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="authorName"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
app:dependency="useAuthorName"
|
||||||
|
app:useSimpleSummaryProvider="true"
|
||||||
|
android:title="@string/preference_author_name" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|
@ -88,6 +100,7 @@
|
||||||
app:singleLineTitle="false"
|
app:singleLineTitle="false"
|
||||||
android:summary="@string/manage_exif_tags_summary"
|
android:summary="@string/manage_exif_tags_summary"
|
||||||
android:title="@string/manage_exif_tags" />
|
android:title="@string/manage_exif_tags" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<!-- The key 'allowGps' was used before and has since been removed based on the discussion at #1599.
|
<!-- The key 'allowGps' was used before and has since been removed based on the discussion at #1599.
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ class u {
|
||||||
internal var readEXIF: EXIFReader?=null
|
internal var readEXIF: EXIFReader?=null
|
||||||
@Mock
|
@Mock
|
||||||
internal var mediaClient: MediaClient? = null
|
internal var mediaClient: MediaClient? = null
|
||||||
|
@Mock
|
||||||
|
internal var location: LatLng? = null
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
var imageProcessingService: ImageProcessingService? = null
|
var imageProcessingService: ImageProcessingService? = null
|
||||||
|
|
@ -62,7 +64,7 @@ class u {
|
||||||
`when`(fileUtilsWrapper!!.getSHA1(any(FileInputStream::class.java)))
|
`when`(fileUtilsWrapper!!.getSHA1(any(FileInputStream::class.java)))
|
||||||
.thenReturn("fileSha")
|
.thenReturn("fileSha")
|
||||||
|
|
||||||
`when`(fileUtilsWrapper!!.getGeolocationOfFile(ArgumentMatchers.anyString()))
|
`when`(fileUtilsWrapper!!.getGeolocationOfFile(ArgumentMatchers.anyString(), any(LatLng::class.java)))
|
||||||
.thenReturn("latLng")
|
.thenReturn("latLng")
|
||||||
|
|
||||||
`when`(imageUtilsWrapper?.checkIfImageIsTooDark(ArgumentMatchers.anyString()))
|
`when`(imageUtilsWrapper?.checkIfImageIsTooDark(ArgumentMatchers.anyString()))
|
||||||
|
|
@ -88,7 +90,7 @@ class u {
|
||||||
@Test
|
@Test
|
||||||
fun validateImageForKeepImage() {
|
fun validateImageForKeepImage() {
|
||||||
`when`(uploadItem.imageQuality).thenReturn(ImageUtils.IMAGE_KEEP)
|
`when`(uploadItem.imageQuality).thenReturn(ImageUtils.IMAGE_KEEP)
|
||||||
val validateImage = imageProcessingService!!.validateImage(uploadItem)
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, location)
|
||||||
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
|
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -96,13 +98,13 @@ class u {
|
||||||
fun validateImageForDuplicateImage() {
|
fun validateImageForDuplicateImage() {
|
||||||
`when`(mediaClient!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
`when`(mediaClient!!.checkFileExistsUsingSha(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Single.just(true))
|
.thenReturn(Single.just(true))
|
||||||
val validateImage = imageProcessingService!!.validateImage(uploadItem)
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, location)
|
||||||
assertEquals(ImageUtils.IMAGE_DUPLICATE, validateImage.blockingGet())
|
assertEquals(ImageUtils.IMAGE_DUPLICATE, validateImage.blockingGet())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun validateImageForOkImage() {
|
fun validateImageForOkImage() {
|
||||||
val validateImage = imageProcessingService!!.validateImage(uploadItem)
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, location)
|
||||||
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
|
assertEquals(ImageUtils.IMAGE_OK, validateImage.blockingGet())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,7 +112,7 @@ class u {
|
||||||
fun validateImageForDarkImage() {
|
fun validateImageForDarkImage() {
|
||||||
`when`(imageUtilsWrapper?.checkIfImageIsTooDark(ArgumentMatchers.anyString()))
|
`when`(imageUtilsWrapper?.checkIfImageIsTooDark(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Single.just(ImageUtils.IMAGE_DARK))
|
.thenReturn(Single.just(ImageUtils.IMAGE_DARK))
|
||||||
val validateImage = imageProcessingService!!.validateImage(uploadItem)
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, location)
|
||||||
assertEquals(ImageUtils.IMAGE_DARK, validateImage.blockingGet())
|
assertEquals(ImageUtils.IMAGE_DARK, validateImage.blockingGet())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,7 +120,7 @@ class u {
|
||||||
fun validateImageForWrongGeoLocation() {
|
fun validateImageForWrongGeoLocation() {
|
||||||
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(ArgumentMatchers.anyString(), any(LatLng::class.java)))
|
`when`(imageUtilsWrapper!!.checkImageGeolocationIsDifferent(ArgumentMatchers.anyString(), any(LatLng::class.java)))
|
||||||
.thenReturn(Single.just(ImageUtils.IMAGE_GEOLOCATION_DIFFERENT))
|
.thenReturn(Single.just(ImageUtils.IMAGE_GEOLOCATION_DIFFERENT))
|
||||||
val validateImage = imageProcessingService!!.validateImage(uploadItem)
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, location)
|
||||||
assertEquals(ImageUtils.IMAGE_GEOLOCATION_DIFFERENT, validateImage.blockingGet())
|
assertEquals(ImageUtils.IMAGE_GEOLOCATION_DIFFERENT, validateImage.blockingGet())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,7 +128,7 @@ class u {
|
||||||
fun validateImageForFileNameExistsWithCheckTitleOn() {
|
fun validateImageForFileNameExistsWithCheckTitleOn() {
|
||||||
`when`(mediaClient?.checkPageExistsUsingTitle(ArgumentMatchers.anyString()))
|
`when`(mediaClient?.checkPageExistsUsingTitle(ArgumentMatchers.anyString()))
|
||||||
.thenReturn(Single.just(true))
|
.thenReturn(Single.just(true))
|
||||||
val validateImage = imageProcessingService!!.validateImage(uploadItem)
|
val validateImage = imageProcessingService!!.validateImage(uploadItem, location)
|
||||||
assertEquals(ImageUtils.FILE_NAME_EXISTS, validateImage.blockingGet())
|
assertEquals(ImageUtils.FILE_NAME_EXISTS, validateImage.blockingGet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ class UploadMediaPresenterTest {
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var place: Place
|
private lateinit var place: Place
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private var location: LatLng? = null
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var uploadItem: UploadItem
|
private lateinit var uploadItem: UploadItem
|
||||||
|
|
||||||
|
|
@ -88,10 +91,11 @@ class UploadMediaPresenterTest {
|
||||||
repository.preProcessImage(
|
repository.preProcessImage(
|
||||||
ArgumentMatchers.any(UploadableFile::class.java),
|
ArgumentMatchers.any(UploadableFile::class.java),
|
||||||
ArgumentMatchers.any(Place::class.java),
|
ArgumentMatchers.any(Place::class.java),
|
||||||
ArgumentMatchers.any(UploadMediaPresenter::class.java)
|
ArgumentMatchers.any(UploadMediaPresenter::class.java),
|
||||||
|
ArgumentMatchers.any(LatLng::class.java)
|
||||||
)
|
)
|
||||||
).thenReturn(testObservableUploadItem)
|
).thenReturn(testObservableUploadItem)
|
||||||
uploadMediaPresenter.receiveImage(uploadableFile, place)
|
uploadMediaPresenter.receiveImage(uploadableFile, place, location)
|
||||||
verify(view).showProgress(true)
|
verify(view).showProgress(true)
|
||||||
testScheduler.triggerActions()
|
testScheduler.triggerActions()
|
||||||
verify(view).onImageProcessed(
|
verify(view).onImageProcessed(
|
||||||
|
|
@ -107,14 +111,14 @@ class UploadMediaPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun verifyImageQualityTest() {
|
fun verifyImageQualityTest() {
|
||||||
whenever(repository.uploads).thenReturn(listOf(uploadItem))
|
whenever(repository.uploads).thenReturn(listOf(uploadItem))
|
||||||
whenever(repository.getImageQuality(uploadItem))
|
whenever(repository.getImageQuality(uploadItem, location))
|
||||||
.thenReturn(testSingleImageResult)
|
.thenReturn(testSingleImageResult)
|
||||||
whenever(uploadItem.imageQuality).thenReturn(0)
|
whenever(uploadItem.imageQuality).thenReturn(0)
|
||||||
whenever(uploadItem.gpsCoords)
|
whenever(uploadItem.gpsCoords)
|
||||||
.thenReturn(imageCoordinates)
|
.thenReturn(imageCoordinates)
|
||||||
whenever(uploadItem.gpsCoords.decimalCoords)
|
whenever(uploadItem.gpsCoords.decimalCoords)
|
||||||
.thenReturn("imageCoordinates")
|
.thenReturn("imageCoordinates")
|
||||||
uploadMediaPresenter.verifyImageQuality(0)
|
uploadMediaPresenter.verifyImageQuality(0, location)
|
||||||
verify(view).showProgress(true)
|
verify(view).showProgress(true)
|
||||||
testScheduler.triggerActions()
|
testScheduler.triggerActions()
|
||||||
verify(view).showProgress(false)
|
verify(view).showProgress(false)
|
||||||
|
|
@ -126,14 +130,14 @@ class UploadMediaPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `verify ImageQuality Test while coordinates equals to null`() {
|
fun `verify ImageQuality Test while coordinates equals to null`() {
|
||||||
whenever(repository.uploads).thenReturn(listOf(uploadItem))
|
whenever(repository.uploads).thenReturn(listOf(uploadItem))
|
||||||
whenever(repository.getImageQuality(uploadItem))
|
whenever(repository.getImageQuality(uploadItem, location))
|
||||||
.thenReturn(testSingleImageResult)
|
.thenReturn(testSingleImageResult)
|
||||||
whenever(uploadItem.imageQuality).thenReturn(0)
|
whenever(uploadItem.imageQuality).thenReturn(0)
|
||||||
whenever(uploadItem.gpsCoords)
|
whenever(uploadItem.gpsCoords)
|
||||||
.thenReturn(imageCoordinates)
|
.thenReturn(imageCoordinates)
|
||||||
whenever(uploadItem.gpsCoords.decimalCoords)
|
whenever(uploadItem.gpsCoords.decimalCoords)
|
||||||
.thenReturn(null)
|
.thenReturn(null)
|
||||||
uploadMediaPresenter.verifyImageQuality(0)
|
uploadMediaPresenter.verifyImageQuality(0, location)
|
||||||
testScheduler.triggerActions()
|
testScheduler.triggerActions()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,7 +171,7 @@ class UploadMediaPresenterTest {
|
||||||
val uploadMediaDetailList: ArrayList<UploadMediaDetail> = ArrayList()
|
val uploadMediaDetailList: ArrayList<UploadMediaDetail> = ArrayList()
|
||||||
uploadMediaDetailList.add(uploadMediaDetail)
|
uploadMediaDetailList.add(uploadMediaDetail)
|
||||||
uploadItem.setMediaDetails(uploadMediaDetailList)
|
uploadItem.setMediaDetails(uploadMediaDetailList)
|
||||||
Mockito.`when`(repository.getImageQuality(uploadItem)).then {
|
Mockito.`when`(repository.getImageQuality(uploadItem, location)).then {
|
||||||
verify(view).showProgress(true)
|
verify(view).showProgress(true)
|
||||||
testScheduler.triggerActions()
|
testScheduler.triggerActions()
|
||||||
verify(view).showProgress(true)
|
verify(view).showProgress(true)
|
||||||
|
|
@ -183,7 +187,7 @@ class UploadMediaPresenterTest {
|
||||||
uploadMediaDetail.captionText = "added caption"
|
uploadMediaDetail.captionText = "added caption"
|
||||||
uploadMediaDetail.languageCode = "eo"
|
uploadMediaDetail.languageCode = "eo"
|
||||||
uploadItem.setMediaDetails(Collections.singletonList(uploadMediaDetail))
|
uploadItem.setMediaDetails(Collections.singletonList(uploadMediaDetail))
|
||||||
Mockito.`when`(repository.getImageQuality(uploadItem)).then {
|
Mockito.`when`(repository.getImageQuality(uploadItem, location)).then {
|
||||||
verify(view).showProgress(true)
|
verify(view).showProgress(true)
|
||||||
testScheduler.triggerActions()
|
testScheduler.triggerActions()
|
||||||
verify(view).showProgress(true)
|
verify(view).showProgress(true)
|
||||||
|
|
@ -264,11 +268,12 @@ class UploadMediaPresenterTest {
|
||||||
repository.preProcessImage(
|
repository.preProcessImage(
|
||||||
ArgumentMatchers.any(UploadableFile::class.java),
|
ArgumentMatchers.any(UploadableFile::class.java),
|
||||||
ArgumentMatchers.any(Place::class.java),
|
ArgumentMatchers.any(Place::class.java),
|
||||||
ArgumentMatchers.any(UploadMediaPresenter::class.java)
|
ArgumentMatchers.any(UploadMediaPresenter::class.java),
|
||||||
|
ArgumentMatchers.any(LatLng::class.java)
|
||||||
)
|
)
|
||||||
).thenReturn(item)
|
).thenReturn(item)
|
||||||
|
|
||||||
uploadMediaPresenter.receiveImage(uploadableFile, germanyAsPlace)
|
uploadMediaPresenter.receiveImage(uploadableFile, germanyAsPlace, location)
|
||||||
verify(view).showProgress(true)
|
verify(view).showProgress(true)
|
||||||
testScheduler.triggerActions()
|
testScheduler.triggerActions()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,9 @@ class UploadRepositoryUnitTest {
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var place: Place
|
private lateinit var place: Place
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private var location: LatLng? = null
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var similarImageInterface: SimilarImageInterface
|
private lateinit var similarImageInterface: SimilarImageInterface
|
||||||
|
|
||||||
|
|
@ -175,16 +178,16 @@ class UploadRepositoryUnitTest {
|
||||||
@Test
|
@Test
|
||||||
fun testPreProcessImage() {
|
fun testPreProcessImage() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
repository.preProcessImage(uploadableFile, place, similarImageInterface),
|
repository.preProcessImage(uploadableFile, place, similarImageInterface, location),
|
||||||
uploadModel.preProcessImage(uploadableFile, place, similarImageInterface)
|
uploadModel.preProcessImage(uploadableFile, place, similarImageInterface, location)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testGetImageQuality() {
|
fun testGetImageQuality() {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
repository.getImageQuality(uploadItem),
|
repository.getImageQuality(uploadItem, location),
|
||||||
uploadModel.getImageQuality(uploadItem)
|
uploadModel.getImageQuality(uploadItem, location)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,9 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var place: Place
|
private lateinit var place: Place
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private var location: fr.free.nrw.commons.location.LatLng? = null
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var defaultKvStore: JsonKvStore
|
private lateinit var defaultKvStore: JsonKvStore
|
||||||
|
|
||||||
|
|
@ -172,7 +175,7 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testSetImageTobeUploaded() {
|
fun testSetImageTobeUploaded() {
|
||||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||||
fragment.setImageTobeUploaded(null, null)
|
fragment.setImageTobeUploaded(null, null, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -374,7 +377,7 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
`when`(latLng.longitude).thenReturn(0.0)
|
`when`(latLng.longitude).thenReturn(0.0)
|
||||||
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
|
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
|
||||||
fragment.onActivityResult(1211, Activity.RESULT_OK, intent)
|
fragment.onActivityResult(1211, Activity.RESULT_OK, intent)
|
||||||
Mockito.verify(presenter, Mockito.times(0)).verifyImageQuality(0)
|
Mockito.verify(presenter, Mockito.times(0)).verifyImageQuality(0, location)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -396,7 +399,7 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
`when`(latLng.longitude).thenReturn(0.0)
|
`when`(latLng.longitude).thenReturn(0.0)
|
||||||
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
|
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
|
||||||
fragment.onActivityResult(1211, Activity.RESULT_OK, intent)
|
fragment.onActivityResult(1211, Activity.RESULT_OK, intent)
|
||||||
Mockito.verify(presenter, Mockito.times(1)).verifyImageQuality(0)
|
Mockito.verify(presenter, Mockito.times(1)).verifyImageQuality(0, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue