0

I used to use the following code to get the address of a picture. And it worked well.

private static AddressVO getAddress(ExifInterface exifInterface, Context context){
    if (exifInterface == null){
        return null;
    }
    String latValue = exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
    String lonValue = exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
    String latRef = exifInterface.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
    String lonRef = exifInterface.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
    return AddressUtils.getAddress(context, latValue, latRef, lonValue, lonRef);
}

Recently, the code starts to fail, as I get "0/1,0/1,0/1" for latValue and lonValue, "" for latRef and lonRef, which makes my code no longer able to infer the geo location using latitude and longitude.

I believe the failure should have started since I upgraded my Android Studio to Flamingo and minSdkVersion from 28 to 31, because a lot of things were failing after that upgrade. I took a whole day to fix everything. Most problems were compatibility issues, and I think this one is also one of them, but I couldn't figure out a way to resolve it.

I don't think it will be the problem of my permissions because I do read the value but just it is just not correct. My photos are also all valid. I have retried the photos that used to work, and they no longer work now.

How should I fix it?

--- Update ---

This is how I get ExifInterface. Users can upload images from their gallery.

private static List<ConsumeRecordVO> buildRecords(Context context, ClipData clipData){
    List<ConsumeRecordVO> records = new ArrayList<>();
    int count = clipData.getItemCount();
    for (int i = 0; i < count; i++){
        Uri uri = clipData.getItemAt(i).getUri();
        String filename = ImageUtils.newImageFileName(i);
        if (!ImageUtils.saveImage(context, uri, filename)){
            continue;
        }
        ConsumeRecordVO record = new ConsumeRecordVO();
        RestaurantFoodVO food = new RestaurantFoodVO();
        food.setCover(filename);
        food.setImages(Lists.newArrayList(filename));
        record.setFoods(Lists.newArrayList(food));
        record.setEaters(new ArrayList<>());
        records.add(record);

        // the path of my test photo is /storage/emulated/0/DCIM/Camera/IMG_20230721_115842.jpg,
        // which is correct on my phone
        String path = FileUtils.getPath(context, uri);
        ExifInterface exifInterface;
        try {
            exifInterface = new ExifInterface(path);
        } catch (Exception e) {
            Log.w("buildRestaurantFromImages", "ExifInterface", e);
            exifInterface = null;
        }
        record.setConsumeTime(getConsumeTime(exifInterface, path));
        record.setAddress(getAddress(exifInterface, context));
    }
    return records;
}

--- Update2 ---

This is the FileUtils which converts uri content://media/external/images/media/1000007581 to path /storage/emulated/0/DCIM/Camera/IMG_20230724_125008.jpg.

/**
 * Get a file path from a Uri. This will get the the path for Storage Access
 * Framework Documents, as well as the _data field for the MediaStore and
 * other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @author paulburke
 */
public static String getPath(final Context context, final Uri uri) {
    // DocumentProvider
    if (DocumentsContract.isDocumentUri(context, uri)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }

            // TODO handle non-primary volumes
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.parseLong(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[] {
                    split[1]
            };

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

/**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @param selection (Optional) Filter used in the query.
 * @param selectionArgs (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
private static String getDataColumn(Context context, Uri uri, String selection,
                                   String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}


/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
private static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
private static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
private static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

This is how I open the gallery

public static void openGallery(Activity activity, View caller){
    GALLERY_CALLER = caller == null ? 0 : caller.getId();
    if (!PermissionUtils.checkAndRequestReadStoragePermission(activity)){
        Toast.makeText(activity, "no perm", Toast.LENGTH_SHORT).show();
        return;
    }
    Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
    activity.startActivityForResult(Intent.createChooser(intent, activity.getString(R.string.select_image)), ActivityCodeConstant.GALLERY);
}
Seaky Lone
  • 992
  • 1
  • 10
  • 29

1 Answers1

0

Since you're getting wrong values from the EXIF, especially after upgrading the minSdkVersion to 31, this could be one of the reasons:

The new photo picker on Android redacts location tags from EXIF. In fact, it seems to ignore the ACCESS_MEDIA_LOCATION permission too, as reported in this comment.

So, in case you are using ACTION_GET_CONTENT, ACTION_PICK_IMAGES or similar intents, Android will strip the location off from the images.

ACTION_GET_CONTENT may still open the system file picker on some devices, but only in case the GET_CONTENT takeover is not enabled on them. So, you will still face this issue on some devices. For me, switching to ACTION_OPEN_DOCUMENT worked as it opens the system file picker instead of the new photo picker.

  • Thanks, I believe it is the fucking problem of google. I will find a third-party image picker instead. – Seaky Lone Jul 27 '23 at 16:09
  • Is it the problem of photo picker? Because I used a third party photo picker, and I still get the wrong value for exif. – Seaky Lone Jul 27 '23 at 18:09
  • Android will remove the coordinates before any third-party photo picker (that uses ACTION_PICK_IMAGES or ACTION_GET_CONTENT to fetch images) reads them. There are two ways in which users can choose images on Android: using the photo picker, or using the traditional system file picker (or the DocumentsUI). As of now, the only way I know is to trigger the system file picker by using ACTION_OPEN_DOCUMENT or open it within the photo picker. – Ritika Pahwa Jul 28 '23 at 05:29
  • To confirm if it the same case for you, could you please tap 'options' (the three dots at the top right corner as shown in the GIF here: https://developer.android.com/training/data-storage/shared/photopicker) in your photo picker, choose Browse and then select the image? This should open the traditional file picker and EXIF values shouldn't be set as null. – Ritika Pahwa Jul 28 '23 at 05:33
  • Just checked the updated description. Thanks for adding the relevant code. Changing the intent to ACTION_OPEN_DOCUMENT with a mime filter for images instead of using ACTION_PICK from MediaStore here should work: Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); – Ritika Pahwa Jul 28 '23 at 06:38
  • For me, switching to `ACTION_OPEN_DOCUMENT` still doesn't work. – Seaky Lone Jul 28 '23 at 15:10
  • That's quite unfortunate :( – Ritika Pahwa Jul 29 '23 at 08:21