5196: Fix location stripped from EXIF metadata (#5227)

* MainActivity: add ACCESS_MEDIA_LOCATION permission check to retain location info in EXIF metadata

* remove redundant permission check and optimise imports

* FilePicker: switch to ACTION_OPEN_DOCUMENT intent for opening image files

* add a comment explaining the change

* implement GET_CONTENT photo picker toggle switch

* add location loss warning pop up

* SettingsFragment: modify the comment about GET_CONTENT takeover for more clarity
This commit is contained in:
Ritika Pahwa 2023-06-15 06:35:55 +05:30 committed by GitHub
parent 4d71c305f2
commit 9a0f35c681
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 20 deletions

View file

@ -3,12 +3,9 @@ package fr.free.nrw.commons.contributions;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT; import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
import android.Manifest; import android.Manifest;
import android.Manifest.permission;
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.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
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;
@ -70,15 +67,6 @@ public class ContributionController {
PermissionUtils.checkPermissionsAndPerformAction(activity, PermissionUtils.checkPermissionsAndPerformAction(activity,
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
() -> { () -> {
if (VERSION.SDK_INT >= VERSION_CODES.Q) {
PermissionUtils.checkPermissionsAndPerformAction(
activity,
permission.ACCESS_MEDIA_LOCATION,
() -> {},
R.string.media_location_permission_denied,
R.string.add_location_manually
);
}
FilePicker.openCustomSelector(activity, 0); FilePicker.openCustomSelector(activity, 0);
}, },
R.string.storage_permission_title, R.string.storage_permission_title,
@ -91,7 +79,8 @@ public class ContributionController {
*/ */
private void initiateGalleryUpload(final Activity activity, final boolean allowMultipleUploads) { private void initiateGalleryUpload(final Activity activity, final boolean allowMultipleUploads) {
setPickerConfiguration(activity, allowMultipleUploads); setPickerConfiguration(activity, allowMultipleUploads);
FilePicker.openGallery(activity, 0); boolean isGetContentPickerPreferred = defaultKvStore.getBoolean("getContentPhotoPickerPref");
FilePicker.openGallery(activity, 0, isGetContentPickerPreferred);
} }
/** /**

View file

@ -5,7 +5,6 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build.VERSION; import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES; import android.os.Build.VERSION_CODES;
import android.os.Bundle; import android.os.Bundle;
@ -152,6 +151,20 @@ public class MainActivity extends BaseActivity
} }
} }
setUpPager(); setUpPager();
/**
* Ask the user for media location access just after login
* so that location in the EXIF metadata of the images shared by the user
* is retained on devices running Android 10 or above
*/
if (VERSION.SDK_INT >= VERSION_CODES.Q) {
PermissionUtils.checkPermissionsAndPerformAction(
this,
permission.ACCESS_MEDIA_LOCATION,
() -> {},
R.string.media_location_permission_denied,
R.string.add_location_manually
);
}
} }
} }

View file

@ -46,10 +46,11 @@ public class FilePicker implements Constants {
return uri; return uri;
} }
private static Intent createGalleryIntent(@NonNull Context context, int type) { private static Intent createGalleryIntent(@NonNull Context context, int type,
boolean isGetContentPickerPreferred) {
// storing picked image type to shared preferences // storing picked image type to shared preferences
storeType(context, type); storeType(context, type);
return plainGalleryPickerIntent() return plainGalleryPickerIntent(isGetContentPickerPreferred)
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery()); .putExtra(Intent.EXTRA_ALLOW_MULTIPLE, configuration(context).allowsMultiplePickingInGallery());
} }
@ -105,8 +106,8 @@ public class FilePicker implements Constants {
* *
* @param type Custom type of your choice, which will be returned with the images * @param type Custom type of your choice, which will be returned with the images
*/ */
public static void openGallery(Activity activity, int type) { public static void openGallery(Activity activity, int type, boolean isGetContentPickerPreferred) {
Intent intent = createGalleryIntent(activity, type); Intent intent = createGalleryIntent(activity, type, isGetContentPickerPreferred);
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY); activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
} }
@ -200,8 +201,40 @@ public class FilePicker implements Constants {
return data == null || (data.getData() == null && data.getClipData() == null); return data == null || (data.getData() == null && data.getClipData() == null);
} }
private static Intent plainGalleryPickerIntent() { private static Intent plainGalleryPickerIntent(boolean isGetContentPickerPreferred) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); /**
* Asking for ACCESS_MEDIA_LOCATION at runtime solved the location-loss issue
* in the custom selector in Contributions fragment.
* Detailed discussion: https://github.com/commons-app/apps-android-commons/issues/5015
*
* This permission check, however, was insufficient to fix location-loss in
* the regular selector in Contributions fragment and Nearby fragment,
* especially on some devices running Android 13 that use the new Photo Picker by default.
*
* New Photo Picker: https://developer.android.com/training/data-storage/shared/photopicker
*
* The new Photo Picker introduced by Android redacts location tags from EXIF metadata.
* Reported on the Google Issue Tracker: https://issuetracker.google.com/issues/243294058
* Status: Won't fix (Intended behaviour)
*
* Switched intent from ACTION_GET_CONTENT to ACTION_OPEN_DOCUMENT
* (based on user's preference) as:
*
* ACTION_GET_CONTENT opens the 'best application' for choosing that kind of data
* The best application is the new Photo Picker that redacts the location tags
*
* ACTION_OPEN_DOCUMENT, however, displays the various DocumentsProvider instances
* installed on the device, letting the user interactively navigate through them.
*
* So, this allows us to use the traditional file picker that does not redact location tags from EXIF.
*
*/
Intent intent;
if (isGetContentPickerPreferred) {
intent = new Intent(Intent.ACTION_GET_CONTENT);
} else {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
}
intent.setType("image/*"); intent.setType("image/*");
return intent; return intent;
} }

View file

@ -42,6 +42,7 @@ import fr.free.nrw.commons.recentlanguages.Language;
import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter; import fr.free.nrw.commons.recentlanguages.RecentLanguagesAdapter;
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao; import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
import fr.free.nrw.commons.upload.LanguagesAdapter; import fr.free.nrw.commons.upload.LanguagesAdapter;
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.HashMap; import java.util.HashMap;
@ -71,6 +72,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
private TextView recentLanguagesTextView; private TextView recentLanguagesTextView;
private View separator; private View separator;
private ListView languageHistoryListView; private ListView languageHistoryListView;
private static final String GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content";
@Override @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@ -150,6 +152,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
checkPermissionsAndSendLogs(); checkPermissionsAndSendLogs();
return true; return true;
}); });
Preference getContentPickerPreference = findPreference("getContentPhotoPickerPref");
getContentPickerPreference.setOnPreferenceChangeListener(
(preference, newValue) -> {
boolean isGetContentPickerTurnedOn = (boolean) newValue;
if (isGetContentPickerTurnedOn) {
showLocationLossWarning();
}
return true;
}
);
// Disable some settings when not logged in. // Disable some settings when not logged in.
if (defaultKvStore.getBoolean("login_skipped", false)) { if (defaultKvStore.getBoolean("login_skipped", false)) {
findPreference("useExternalStorage").setEnabled(false); findPreference("useExternalStorage").setEnabled(false);
@ -162,6 +175,26 @@ public class SettingsFragment extends PreferenceFragmentCompat {
} }
} }
/**
* On some devices, the new Photo Picker with GET_CONTENT takeover
* redacts location tags from EXIF metadata
*
* Show warning to the user when ACTION_GET_CONTENT intent is enabled
*/
private void showLocationLossWarning() {
DialogUtil.showAlertDialog(
getActivity(),
null,
getString(R.string.location_loss_warning),
getString(R.string.ok),
getString(R.string.read_help_link),
() -> {},
() -> Utils.handleWebUrl(requireContext(), Uri.parse(GET_CONTENT_PICKER_HELP_URL)),
null,
true
);
}
@Override @Override
protected Adapter onCreateAdapter(final PreferenceScreen preferenceScreen) { protected Adapter onCreateAdapter(final PreferenceScreen preferenceScreen) {
return new PreferenceGroupAdapter(preferenceScreen) { return new PreferenceGroupAdapter(preferenceScreen) {

View file

@ -440,6 +440,9 @@ 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="get_content_photo_picker_title">Use GET_CONTENT photo picker</string>
<string name="get_content_photo_picker_explanation">Disable if your pictures get uploaded without location</string>
<string name="location_loss_warning">Please make sure that this new Android picker does not strip location from your pictures.</string>
<string name="nearby_campaign_dismiss_message">You won\'t see the campaigns anymore. However, you can re-enable this notification in Settings if you wish.</string> <string name="nearby_campaign_dismiss_message">You won\'t see the campaigns anymore. However, you can re-enable this notification in Settings if you wish.</string>
<string name="this_function_needs_network_connection">This function requires network connection, please check your connection settings.</string> <string name="this_function_needs_network_connection">This function requires network connection, please check your connection settings.</string>

View file

@ -70,6 +70,12 @@
app:singleLineTitle="false" app:singleLineTitle="false"
android:summary="@string/display_campaigns_explanation" android:summary="@string/display_campaigns_explanation"
android:title="@string/display_campaigns" /> android:title="@string/display_campaigns" />
<SwitchPreference
android:defaultValue="false"
android:key="getContentPhotoPickerPref"
android:summary="@string/get_content_photo_picker_explanation"
android:title="@string/get_content_photo_picker_title"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory