Display nearest address for Geotagged files

Bug: 64298047
Test: MediaViewTest.java
Change-Id: I114b5fb57d86cfc2c5992c6f248437e0f2c4db1b
diff --git a/res/values/inspector_strings.xml b/res/values/inspector_strings.xml
index 8a426e3..d02ae0c 100644
--- a/res/values/inspector_strings.xml
+++ b/res/values/inspector_strings.xml
@@ -83,6 +83,8 @@
     <string name="metadata_composer">Composer</string>
     <!--The album the file is in, Note this is probably MP3 ID3 tags-->
     <string name="metadata_album">Album</string>
+    <!-- The address nearest to where a photo was taken -->
+    <string name="metadata_address">Location</string>
 
     <!-- String label for developer/debug file details, specifying which stream types are available. -->
     <string name="debug_stream_types">Stream types</string>
diff --git a/src/com/android/documentsui/inspector/MediaView.java b/src/com/android/documentsui/inspector/MediaView.java
index 8fc179e..b2ae758 100644
--- a/src/com/android/documentsui/inspector/MediaView.java
+++ b/src/com/android/documentsui/inspector/MediaView.java
@@ -17,8 +17,11 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.location.Address;
+import android.location.Geocoder;
 import android.media.ExifInterface;
 import android.media.MediaMetadata;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.support.annotation.VisibleForTesting;
@@ -31,6 +34,9 @@
 import com.android.documentsui.inspector.InspectorController.MediaDisplay;
 import com.android.documentsui.inspector.InspectorController.TableDisplay;
 
+import java.io.IOException;
+import java.util.function.Consumer;
+
 import javax.annotation.Nullable;
 
 /**
@@ -41,6 +47,7 @@
 public class MediaView extends TableView implements MediaDisplay {
 
     private final Resources mResources;
+    private final Context mContext;
 
     public MediaView(Context context) {
         this(context, null);
@@ -52,6 +59,7 @@
 
     public MediaView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        mContext = context;
         mResources = context.getResources();
     }
 
@@ -61,7 +69,7 @@
 
         Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF);
         if (exif != null) {
-            showExifData(this, mResources, doc, exif, geoClickListener);
+            showExifData(this, mResources, doc, exif, geoClickListener, this::getAddress);
         }
 
         Bundle video = metadata.getBundle(Shared.METADATA_KEY_VIDEO);
@@ -126,7 +134,8 @@
             Resources resources,
             DocumentInfo doc,
             Bundle tags,
-            @Nullable Runnable geoClickListener) {
+            @Nullable Runnable geoClickListener,
+            Consumer<float[]> geoAddressFetcher) {
 
         addDimensionsRow(table, resources, tags);
 
@@ -135,11 +144,6 @@
             table.put(R.string.metadata_date_time, date);
         }
 
-        if (MetadataUtils.hasExifGpsFields(tags)) {
-            float[] coords = MetadataUtils.getExifGpsCoords(tags);
-            showCoordiantes(table, resources, coords, geoClickListener);
-        }
-
         if (tags.containsKey(ExifInterface.TAG_GPS_ALTITUDE)) {
             double altitude = tags.getDouble(ExifInterface.TAG_GPS_ALTITUDE);
             table.put(R.string.metadata_altitude, String.valueOf(altitude));
@@ -178,6 +182,11 @@
                     String.format(resources.getString(R.string.metadata_iso_format), iso));
         }
 
+        if (MetadataUtils.hasExifGpsFields(tags)) {
+            float[] coords = MetadataUtils.getExifGpsCoords(tags);
+            showCoordiantes(table, resources, coords, geoClickListener);
+            geoAddressFetcher.accept(coords);
+        }
     }
 
     private static void showCoordiantes(
@@ -202,6 +211,53 @@
     }
 
     /**
+     * Attempts to retrieve an approximate address and displays the address if it can find one.
+     * @param coords the coordinates that gets an address.
+     */
+    private void getAddress(float[] coords) {
+        new AsyncTask<Float, Void, Address>() {
+            @Override
+            protected Address doInBackground(Float... coords) {
+                assert (coords.length == 2);
+                Geocoder geocoder = new Geocoder(mContext);
+                try {
+                    Address address = geocoder.getFromLocation(coords[0], // latitude
+                            coords[1], // longitude
+                            1 // amount of results returned
+                    ).get(0);
+                    return address;
+                } catch (IOException e) {
+                    return null;
+                }
+            }
+            @Override
+            protected void onPostExecute(@Nullable Address address) {
+                if (address != null) {
+                    TableDisplay table = MediaView.this;
+                    if (address.getMaxAddressLineIndex() >= 0) {
+                        String formattedAddress;
+                        StringBuilder addressBuilder = new StringBuilder("");
+                        addressBuilder.append(address.getAddressLine(0));
+                        for (int i = 1; i < address.getMaxAddressLineIndex(); i++) {
+                            addressBuilder.append("\n");
+                            addressBuilder.append(address.getAddressLine(i));
+                        }
+                        formattedAddress = addressBuilder.toString();
+                        table.put(R.string.metadata_address, formattedAddress);
+                    } else if (address.getLocality() != null) {
+                        table.put(R.string.metadata_address, address.getLocality());
+                    } else if (address.getSubAdminArea() != null) {
+                        table.put(R.string.metadata_address, address.getSubAdminArea());
+                    } else if (address.getAdminArea() != null) {
+                        table.put(R.string.metadata_address, address.getAdminArea());
+                    } else if (address.getCountryName() != null) {
+                        table.put(R.string.metadata_address, address.getCountryName());
+                    }                }
+            }
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, coords[0], coords[1]);
+    }
+
+    /**
      * @param speed a value n, where shutter speed equals 1/(2^n)
      * @return a String containing either a fraction that displays 1 over a positive integer, or a
      * double rounded to one decimal, depending on if 1/(2^n) is less than or greater than 1,
diff --git a/src/com/android/documentsui/inspector/TableView.java b/src/com/android/documentsui/inspector/TableView.java
index b112980..41a3165 100644
--- a/src/com/android/documentsui/inspector/TableView.java
+++ b/src/com/android/documentsui/inspector/TableView.java
@@ -83,15 +83,16 @@
     }
 
     /**
-     * Puts or updates an value in the table view.
+     * Puts or updates a value in the table view.
      */
     @Override
     public void put(@StringRes int keyId, CharSequence value) {
         put(mRes.getString(keyId), value);
     }
 
+
     /**
-     * Puts or updates an value in the table view.
+     * Puts or updates a value in the table view.
      */
     protected KeyValueRow put(CharSequence key, CharSequence value) {
         KeyValueRow row = mRows.get(key);
diff --git a/tests/unit/com/android/documentsui/inspector/MediaViewTest.java b/tests/unit/com/android/documentsui/inspector/MediaViewTest.java
index 224ae30..7cf7b21 100644
--- a/tests/unit/com/android/documentsui/inspector/MediaViewTest.java
+++ b/tests/unit/com/android/documentsui/inspector/MediaViewTest.java
@@ -31,6 +31,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.function.Consumer;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MediaViewTest {
@@ -38,6 +40,10 @@
     private TestResources mResources;
     private TestTable mTable;
     private Bundle mMetadata;
+    private Consumer<float[]> mGeo = (float[] coords) -> {
+        mTable.put(R.string.metadata_address, "1234 Street Street\n"
+                + "City, State, 56789");
+    };
 
     @Before
     public void setUp() {
@@ -63,7 +69,7 @@
         mResources.strings.put(R.string.metadata_focal_format, "%.2f mm");
         mResources.strings.put(R.string.metadata_iso_format, "ISO %d");
         Bundle exif = mMetadata.getBundle(DocumentsContract.METADATA_EXIF);
-        MediaView.showExifData(mTable, mResources, TestEnv.FILE_JPG, exif, null);
+        MediaView.showExifData(mTable, mResources, TestEnv.FILE_JPG, exif, null, mGeo);
 
         mTable.assertHasRow(R.string.metadata_dimensions, "3840 x 2160, 8.3MP");
         mTable.assertHasRow(R.string.metadata_date_time, "Jan 01, 1970, 12:16 AM");
@@ -74,6 +80,8 @@
         mTable.assertHasRow(R.string.metadata_aperture, "f/2.0");
         mTable.assertHasRow(R.string.metadata_iso_speed_ratings, "ISO 120");
         mTable.assertHasRow(R.string.metadata_focal_length, "4.27 mm");
+        mTable.assertHasRow(R.string.metadata_address, "1234 Street Street\n"
+                + "City, State, 56789");
     }
 
     /**
@@ -87,7 +95,7 @@
         data.putDouble(ExifInterface.TAG_GPS_LATITUDE, 37.7749);
 
         mMetadata.putBundle(DocumentsContract.METADATA_EXIF, data);
-        MediaView.showExifData(mTable, mResources, TestEnv.FILE_JPG, mMetadata, null);
+        MediaView.showExifData(mTable, mResources, TestEnv.FILE_JPG, mMetadata, null, mGeo);
         mTable.assertEmpty();
     }
 
@@ -102,7 +110,7 @@
         data.putInt(ExifInterface.TAG_IMAGE_WIDTH, 3840);
 
         mMetadata.putBundle(DocumentsContract.METADATA_EXIF, data);
-        MediaView.showExifData(mTable, mResources, TestEnv.FILE_JPG, mMetadata, null);
+        MediaView.showExifData(mTable, mResources, TestEnv.FILE_JPG, mMetadata, null, mGeo);
         mTable.assertEmpty();
     }
 
@@ -142,4 +150,17 @@
 
         mTable.assertHasRow(R.string.metadata_duration, "6:01:00");
     }
+
+    @Test
+    public void testGetAddress() throws Exception {
+        Consumer<float[]> badAddress = (float[] coords) -> {
+        };
+        Bundle data = new Bundle();
+        data.putInt(ExifInterface.TAG_IMAGE_WIDTH, 3840);
+        data.putInt(ExifInterface.TAG_IMAGE_LENGTH, 2160);
+
+        mMetadata.getBundle(DocumentsContract.METADATA_EXIF);
+        MediaView.showExifData(mTable, mResources, TestEnv.FILE_JPG, mMetadata, null, badAddress);
+        mTable.assertNotInTable(R.string.metadata_address);
+    }
 }
diff --git a/tests/unit/com/android/documentsui/inspector/TestTable.java b/tests/unit/com/android/documentsui/inspector/TestTable.java
index c3665e4..f1bd0b7 100644
--- a/tests/unit/com/android/documentsui/inspector/TestTable.java
+++ b/tests/unit/com/android/documentsui/inspector/TestTable.java
@@ -17,6 +17,7 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
 import android.view.View.OnClickListener;
@@ -42,6 +43,10 @@
         assertEquals(expected, mRows.get(keyId));
     }
 
+    public void assertNotInTable (int keyId) {
+        assertNull(mRows.get(keyId));
+    }
+
     @Override
     public void put(int keyId, CharSequence value) {
         mRows.put(keyId, value);