Prevent RuntimeException when calling setDataSource

When retrieving EXIF data from HEIF files, ExifInterface calls
MediaMetadataRetriever.setDataSource, but it throws a
RuntimeException for API 26 and below because support for parsing
HEIF files was added in API 27. Also, support for retrieving EXIF
data from HEIF files was added in API 28 (ag/2799160), so trying
to get it from API 27 is useless as well.

This CL changes the implementation to call getHeifAttributes only
for API 28 and above and print a UnsupportedOperationException for
API 27 and below to provide more information to developers.

Bug: 172025296
Test: Run ./gradlew :exifinterface:exifinterface:connectedCheck

Change-Id: I1c6af5f38cafa56806ddfe8487083cdb59432087
(cherry picked from commit 62a9bd6f83f9476fbfe97ae4d88caa37520b7787)
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index 7e1ec2e..3a0edbb 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -99,20 +99,20 @@
             "jpeg_with_datetime_tag_primary_format.jpg";
     private static final String JPEG_WITH_DATETIME_TAG_SECONDARY_FORMAT =
             "jpeg_with_datetime_tag_secondary_format.jpg";
-    private static final String HEIC_WITH_EXIF = "heic_with_exif.heic";
+    private static final String HEIF_WITH_EXIF = "heif_with_exif.heic";
     private static final int[] IMAGE_RESOURCES = new int[] {
             R.raw.jpeg_with_exif_byte_order_ii, R.raw.jpeg_with_exif_byte_order_mm,
             R.raw.dng_with_exif_with_xmp, R.raw.jpeg_with_exif_with_xmp,
             R.raw.png_with_exif_byte_order_ii, R.raw.png_without_exif, R.raw.webp_with_exif,
             R.raw.webp_with_anim_without_exif, R.raw.webp_without_exif,
             R.raw.webp_lossless_without_exif, R.raw.jpeg_with_datetime_tag_primary_format,
-            R.raw.jpeg_with_datetime_tag_secondary_format, R.raw.heic_with_exif};
+            R.raw.jpeg_with_datetime_tag_secondary_format, R.raw.heif_with_exif};
     private static final String[] IMAGE_FILENAMES = new String[] {
             JPEG_WITH_EXIF_BYTE_ORDER_II, JPEG_WITH_EXIF_BYTE_ORDER_MM, DNG_WITH_EXIF_WITH_XMP,
             JPEG_WITH_EXIF_WITH_XMP, PNG_WITH_EXIF_BYTE_ORDER_II, PNG_WITHOUT_EXIF,
             WEBP_WITH_EXIF, WEBP_WITHOUT_EXIF_WITH_ANIM_DATA, WEBP_WITHOUT_EXIF,
             WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING, JPEG_WITH_DATETIME_TAG_PRIMARY_FORMAT,
-            JPEG_WITH_DATETIME_TAG_SECONDARY_FORMAT, HEIC_WITH_EXIF};
+            JPEG_WITH_DATETIME_TAG_SECONDARY_FORMAT, HEIF_WITH_EXIF};
 
     private static final int USER_READ_WRITE = 0600;
     private static final String TEST_TEMP_FILE_NAME = "testImage";
@@ -459,15 +459,21 @@
     }
 
     /**
-     * .heic file is a container for HEIF format images, which ExifInterface supports.
+     * Support for retrieving EXIF from HEIF was added in SDK 28.
      */
     @Test
     @LargeTest
-    public void testHeicFile() throws Throwable {
-        // TODO: Reading HEIC file for SDK < 28 throws an exception. Revisit once issue is solved.
-        //  (b/172025296)
-        if (Build.VERSION.SDK_INT > 27) {
-            readFromFilesWithExif(HEIC_WITH_EXIF, R.array.heic_with_exif);
+    public void testHeifFile() throws Throwable {
+        if (Build.VERSION.SDK_INT >= 28) {
+            readFromFilesWithExif(HEIF_WITH_EXIF, R.array.heif_with_exif);
+        } else {
+            // Make sure that an exception is not thrown and that image length/width tag values
+            // return default values, not the actual values.
+            File imageFile = getFileFromExternalDir(HEIF_WITH_EXIF);
+            ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+            String defaultTagValue = "0";
+            assertEquals(defaultTagValue, exif.getAttribute(ExifInterface.TAG_IMAGE_LENGTH));
+            assertEquals(defaultTagValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
         }
     }
 
diff --git a/exifinterface/exifinterface/src/androidTest/res/raw/heic_with_exif.heic b/exifinterface/exifinterface/src/androidTest/res/raw/heif_with_exif.heic
similarity index 100%
rename from exifinterface/exifinterface/src/androidTest/res/raw/heic_with_exif.heic
rename to exifinterface/exifinterface/src/androidTest/res/raw/heif_with_exif.heic
Binary files differ
diff --git a/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml b/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
index b3438c9..f2bd02f 100644
--- a/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
+++ b/exifinterface/exifinterface/src/androidTest/res/values/arrays.xml
@@ -367,7 +367,7 @@
         <item>0</item>
         <item>0</item>
     </array>
-    <array name="heic_with_exif">
+    <array name="heif_with_exif">
         <!--Whether thumbnail exists-->
         <item>false</item>
         <item>0</item>
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index 4099177..04a3912 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -4629,9 +4629,6 @@
                         getRawAttributes(inputStream);
                         break;
                     }
-                    default: {
-                        break;
-                    }
                 }
             } else {
                 getStandaloneAttributes(inputStream);
@@ -4639,7 +4636,7 @@
             // Set thumbnail image offset and length
             inputStream.seek(mOffsetToExifData);
             setThumbnailData(inputStream);
-        } catch (IOException e) {
+        } catch (IOException | UnsupportedOperationException e) {
             // Ignore exceptions in order to keep the compatibility with the old versions of
             // ExifInterface.
             if (DEBUG) {
@@ -5842,10 +5839,12 @@
         }
     }
 
+    // Support for getting MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET and
+    // MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH was added SDK 28.
     private void getHeifAttributes(final ByteOrderedDataInputStream in) throws IOException {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        try {
-            if (Build.VERSION.SDK_INT >= 23) {
+        if (Build.VERSION.SDK_INT >= 28) {
+            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+            try {
                 retriever.setDataSource(new MediaDataSource() {
                     long mPosition;
 
@@ -5898,110 +5897,105 @@
                         return -1;
                     }
                 });
-            } else {
-                if (mSeekableFileDescriptor != null) {
-                    retriever.setDataSource(mSeekableFileDescriptor);
-                } else if (mFilename != null) {
-                    retriever.setDataSource(mFilename);
-                } else {
-                    return;
-                }
-            }
 
-            String exifOffsetStr = retriever.extractMetadata(
-                    MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET);
-            String exifLengthStr = retriever.extractMetadata(
-                    MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH);
-            String hasImage = retriever.extractMetadata(
-                    MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
-            String hasVideo = retriever.extractMetadata(
-                    MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
+                String exifOffsetStr = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET);
+                String exifLengthStr = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH);
+                String hasImage = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
+                String hasVideo = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
 
-            String width = null;
-            String height = null;
-            String rotation = null;
-            final String metadataValueYes = "yes";
-            // If the file has both image and video, prefer image info over video info.
-            // App querying ExifInterface is most likely using the bitmap path which
-            // picks the image first.
-            if (metadataValueYes.equals(hasImage)) {
-                width = retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
-                height = retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
-                rotation = retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
-            } else if (metadataValueYes.equals(hasVideo)) {
-                width = retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
-                height = retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
-                rotation = retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
-            }
-
-            if (width != null) {
-                mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
-                        ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
-            }
-
-            if (height != null) {
-                mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
-                        ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
-            }
-
-            if (rotation != null) {
-                int orientation = ExifInterface.ORIENTATION_NORMAL;
-
-                // all rotation angles in CW
-                switch (Integer.parseInt(rotation)) {
-                    case 90:
-                        orientation = ExifInterface.ORIENTATION_ROTATE_90;
-                        break;
-                    case 180:
-                        orientation = ExifInterface.ORIENTATION_ROTATE_180;
-                        break;
-                    case 270:
-                        orientation = ExifInterface.ORIENTATION_ROTATE_270;
-                        break;
+                String width = null;
+                String height = null;
+                String rotation = null;
+                final String metadataValueYes = "yes";
+                // If the file has both image and video, prefer image info over video info.
+                // App querying ExifInterface is most likely using the bitmap path which
+                // picks the image first.
+                if (metadataValueYes.equals(hasImage)) {
+                    width = retriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH);
+                    height = retriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT);
+                    rotation = retriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION);
+                } else if (metadataValueYes.equals(hasVideo)) {
+                    width = retriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
+                    height = retriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+                    rotation = retriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
                 }
 
-                mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
-                        ExifAttribute.createUShort(orientation, mExifByteOrder));
-            }
-
-            if (exifOffsetStr != null && exifLengthStr != null) {
-                int offset = Integer.parseInt(exifOffsetStr);
-                int length = Integer.parseInt(exifLengthStr);
-                if (length <= 6) {
-                    throw new IOException("Invalid exif length");
-                }
-                in.seek(offset);
-                byte[] identifier = new byte[6];
-                if (in.read(identifier) != 6) {
-                    throw new IOException("Can't read identifier");
-                }
-                offset += 6;
-                length -= 6;
-                if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
-                    throw new IOException("Invalid identifier");
+                if (width != null) {
+                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_WIDTH,
+                            ExifAttribute.createUShort(Integer.parseInt(width), mExifByteOrder));
                 }
 
-                // TODO: Need to handle potential OutOfMemoryError
-                byte[] bytes = new byte[length];
-                if (in.read(bytes) != length) {
-                    throw new IOException("Can't read exif");
+                if (height != null) {
+                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_IMAGE_LENGTH,
+                            ExifAttribute.createUShort(Integer.parseInt(height), mExifByteOrder));
                 }
-                // Save offset to EXIF data for handling thumbnail and attribute offsets.
-                mOffsetToExifData = offset;
-                readExifSegment(bytes, IFD_TYPE_PRIMARY);
-            }
 
-            if (DEBUG) {
-                Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
+                if (rotation != null) {
+                    int orientation = ExifInterface.ORIENTATION_NORMAL;
+
+                    // all rotation angles in CW
+                    switch (Integer.parseInt(rotation)) {
+                        case 90:
+                            orientation = ExifInterface.ORIENTATION_ROTATE_90;
+                            break;
+                        case 180:
+                            orientation = ExifInterface.ORIENTATION_ROTATE_180;
+                            break;
+                        case 270:
+                            orientation = ExifInterface.ORIENTATION_ROTATE_270;
+                            break;
+                    }
+
+                    mAttributes[IFD_TYPE_PRIMARY].put(TAG_ORIENTATION,
+                            ExifAttribute.createUShort(orientation, mExifByteOrder));
+                }
+
+                if (exifOffsetStr != null && exifLengthStr != null) {
+                    int offset = Integer.parseInt(exifOffsetStr);
+                    int length = Integer.parseInt(exifLengthStr);
+                    if (length <= 6) {
+                        throw new IOException("Invalid exif length");
+                    }
+                    in.seek(offset);
+                    byte[] identifier = new byte[6];
+                    if (in.read(identifier) != 6) {
+                        throw new IOException("Can't read identifier");
+                    }
+                    offset += 6;
+                    length -= 6;
+                    if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) {
+                        throw new IOException("Invalid identifier");
+                    }
+
+                    // TODO: Need to handle potential OutOfMemoryError
+                    byte[] bytes = new byte[length];
+                    if (in.read(bytes) != length) {
+                        throw new IOException("Can't read exif");
+                    }
+                    // Save offset to EXIF data for handling thumbnail and attribute offsets.
+                    mOffsetToExifData = offset;
+                    readExifSegment(bytes, IFD_TYPE_PRIMARY);
+                }
+
+                if (DEBUG) {
+                    Log.d(TAG, "Heif meta: " + width + "x" + height + ", rotation " + rotation);
+                }
+            } finally {
+                retriever.release();
             }
-        } finally {
-            retriever.release();
+        } else {
+            throw new UnsupportedOperationException("Reading EXIF from HEIF files "
+                    + "is supported from SDK 28 and above");
         }
     }