Merge "Refactor Geocoder Provider System APIs" into main
diff --git a/boot/hiddenapi/hiddenapi-unsupported.txt b/boot/hiddenapi/hiddenapi-unsupported.txt
index 26dc700..adcc3df 100644
--- a/boot/hiddenapi/hiddenapi-unsupported.txt
+++ b/boot/hiddenapi/hiddenapi-unsupported.txt
@@ -133,8 +133,6 @@
 Landroid/hardware/location/IContextHubService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/hardware/location/IContextHubService;
 Landroid/hardware/usb/IUsbManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/location/ICountryListener$Stub;-><init>()V
-Landroid/location/IGeocodeProvider$Stub;-><init>()V
-Landroid/location/IGeocodeProvider$Stub;->asInterface(Landroid/os/IBinder;)Landroid/location/IGeocodeProvider;
 Landroid/location/ILocationListener$Stub$Proxy;-><init>(Landroid/os/IBinder;)V
 Landroid/location/ILocationListener$Stub$Proxy;->mRemote:Landroid/os/IBinder;
 Landroid/location/ILocationListener$Stub;-><init>()V
diff --git a/location/api/current.txt b/location/api/current.txt
index 5ed8c3c..85e9f65 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -88,12 +88,12 @@
   public final class Geocoder {
     ctor public Geocoder(@NonNull android.content.Context);
     ctor public Geocoder(@NonNull android.content.Context, @NonNull java.util.Locale);
-    method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange int) throws java.io.IOException;
-    method public void getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange int, @NonNull android.location.Geocoder.GeocodeListener);
-    method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange int) throws java.io.IOException;
-    method public void getFromLocationName(@NonNull String, @IntRange int, @NonNull android.location.Geocoder.GeocodeListener);
-    method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double) throws java.io.IOException;
-    method public void getFromLocationName(@NonNull String, @IntRange int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @NonNull android.location.Geocoder.GeocodeListener);
+    method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange(from=1) int) throws java.io.IOException;
+    method public void getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange(from=1) int, @NonNull android.location.Geocoder.GeocodeListener);
+    method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange(from=1) int) throws java.io.IOException;
+    method public void getFromLocationName(@NonNull String, @IntRange(from=1) int, @NonNull android.location.Geocoder.GeocodeListener);
+    method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange(from=1) int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double) throws java.io.IOException;
+    method public void getFromLocationName(@NonNull String, @IntRange(from=1) int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @NonNull android.location.Geocoder.GeocodeListener);
     method public static boolean isPresent();
   }
 
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index b1cf96d..2e7a541 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -591,6 +591,36 @@
 
 package android.location.provider {
 
+  @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ForwardGeocodeRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getCallingAttributionTag();
+    method @NonNull public String getCallingPackage();
+    method public int getCallingUid();
+    method @NonNull public java.util.Locale getLocale();
+    method @NonNull public String getLocationName();
+    method @FloatRange(from=-90.0, to=90.0) public double getLowerLeftLatitude();
+    method @FloatRange(from=-180.0, to=180.0) public double getLowerLeftLongitude();
+    method @IntRange(from=1) public int getMaxResults();
+    method @FloatRange(from=-90.0, to=90.0) public double getUpperRightLatitude();
+    method @FloatRange(from=-180.0, to=180.0) public double getUpperRightLongitude();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.provider.ForwardGeocodeRequest> CREATOR;
+  }
+
+  public static final class ForwardGeocodeRequest.Builder {
+    ctor public ForwardGeocodeRequest.Builder(@NonNull String, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange(from=1) int, @NonNull java.util.Locale, int, @NonNull String);
+    method @NonNull public android.location.provider.ForwardGeocodeRequest build();
+    method @NonNull public android.location.provider.ForwardGeocodeRequest.Builder setCallingAttributionTag(@NonNull String);
+  }
+
+  @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public abstract class GeocodeProviderBase {
+    ctor public GeocodeProviderBase(@NonNull android.content.Context, @NonNull String);
+    method @NonNull public final android.os.IBinder getBinder();
+    method public abstract void onForwardGeocode(@NonNull android.location.provider.ForwardGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Exception>);
+    method public abstract void onReverseGeocode(@NonNull android.location.provider.ReverseGeocodeRequest, @NonNull android.os.OutcomeReceiver<java.util.List<android.location.Address>,java.lang.Exception>);
+    field public static final String ACTION_GEOCODE_PROVIDER = "com.android.location.service.GeocodeProvider";
+  }
+
   public abstract class LocationProviderBase {
     ctor public LocationProviderBase(@NonNull android.content.Context, @NonNull String, @NonNull android.location.provider.ProviderProperties);
     method @Nullable public final android.os.IBinder getBinder();
@@ -642,5 +672,24 @@
     method public void onProviderRequestChanged(@NonNull String, @NonNull android.location.provider.ProviderRequest);
   }
 
+  @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ReverseGeocodeRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getCallingAttributionTag();
+    method @NonNull public String getCallingPackage();
+    method public int getCallingUid();
+    method @FloatRange(from=-90.0, to=90.0) public double getLatitude();
+    method @NonNull public java.util.Locale getLocale();
+    method @FloatRange(from=-180.0, to=180.0) public double getLongitude();
+    method @IntRange(from=1) public int getMaxResults();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.provider.ReverseGeocodeRequest> CREATOR;
+  }
+
+  public static final class ReverseGeocodeRequest.Builder {
+    ctor public ReverseGeocodeRequest.Builder(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange(from=0) int, @NonNull java.util.Locale, int, @NonNull String);
+    method @NonNull public android.location.provider.ReverseGeocodeRequest build();
+    method @NonNull public android.location.provider.ReverseGeocodeRequest.Builder setCallingAttributionTag(@NonNull String);
+  }
+
 }
 
diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java
index a158344..cdde7c6 100644
--- a/location/java/android/location/Geocoder.java
+++ b/location/java/android/location/Geocoder.java
@@ -21,11 +21,13 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.location.provider.ForwardGeocodeRequest;
+import android.location.provider.IGeocodeCallback;
+import android.location.provider.ReverseGeocodeRequest;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
-import com.android.internal.util.Preconditions;
-
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
@@ -33,6 +35,7 @@
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * A class for handling geocoding and reverse geocoding. Geocoding is the process of transforming a
@@ -42,9 +45,12 @@
  * example one might contain the full street address of the closest building, while another might
  * contain only a city name and postal code.
  *
- * The Geocoder class requires a backend service that is not included in the core android framework.
- * The Geocoder query methods will return an empty list if there no backend service in the platform.
- * Use the isPresent() method to determine whether a Geocoder implementation exists.
+ * <p>Use the isPresent() method to determine whether a Geocoder implementation exists on the
+ * current device. If no implementation is present, any attempt to geocode will result in an error.
+ *
+ * <p>Geocoder implementations are only required to make a best effort to return results in the
+ * chosen locale. Note that geocoder implementations may return results in other locales if they
+ * have no information available for the chosen locale.
  *
  * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on
  * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful or
@@ -52,45 +58,53 @@
  */
 public final class Geocoder {
 
-    /** A listener for asynchronous geocoding results. */
+    /**
+     * A listener for asynchronous geocoding results. Only one of the methods will ever be invoked
+     * per geocoding attempt. There are no guarantees on how long it will take for a method to be
+     * invoked, nor any guarantees on the format or availability of error information.
+     */
     public interface GeocodeListener {
         /** Invoked when geocoding completes successfully. May return an empty list. */
         void onGeocode(@NonNull List<Address> addresses);
-        /** Invoked when geocoding fails, with a brief error message. */
+
+        /** Invoked when geocoding fails, with an optional error message. */
         default void onError(@Nullable String errorMessage) {}
     }
 
-    private static final long TIMEOUT_MS = 60000;
+    private static final long TIMEOUT_MS = 15000;
 
-    private final GeocoderParams mParams;
+    private final Context mContext;
+    private final Locale mLocale;
     private final ILocationManager mService;
 
     /**
-     * Returns true if there is a geocoder implementation present that may return results. If true,
-     * there is still no guarantee that any individual geocoding attempt will succeed.
+     * Returns true if there is a geocoder implementation present on the device that may return
+     * results. If true, there is still no guarantee that any individual geocoding attempt will
+     * succeed.
      */
     public static boolean isPresent() {
         ILocationManager lm = Objects.requireNonNull(ILocationManager.Stub.asInterface(
                 ServiceManager.getService(Context.LOCATION_SERVICE)));
         try {
-            return lm.geocoderIsPresent();
+            return lm.isGeocodeAvailable();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
-    /**
-     * Constructs a Geocoder localized for the default locale.
-     */
+    /** Constructs a Geocoder localized for {@link Locale#getDefault()}. */
     public Geocoder(@NonNull Context context) {
         this(context, Locale.getDefault());
     }
 
     /**
-     * Constructs a Geocoder localized for the given locale.
+     * Constructs a Geocoder localized for the given locale. Note that geocoder implementations will
+     * only make a best effort to return results in the given locale, and there is no guarantee that
+     * returned results will be in the specific locale.
      */
     public Geocoder(@NonNull Context context, @NonNull Locale locale) {
-        mParams = new GeocoderParams(context, locale);
+        mContext = Objects.requireNonNull(context);
+        mLocale = Objects.requireNonNull(locale);
         mService = ILocationManager.Stub.asInterface(
                 ServiceManager.getService(Context.LOCATION_SERVICE));
     }
@@ -103,31 +117,28 @@
      * <p class="warning"><strong>Warning:</strong> Geocoding services may provide no guarantees on
      * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful
      * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance
-     * purposes.</p>
+     * purposes.
      *
      * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for
-     * excessive amounts of time, up to 60 seconds or more. It's strongly encouraged to use the
-     * asynchronous version of this API. If that is not possible, this should be run on a background
-     * thread to avoid blocking other operations.</p>
+     * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this
+     * API. If that is not possible, this should be run on a background thread to avoid blocking
+     * other operations.
      *
      * @param latitude the latitude a point for the search
      * @param longitude the longitude a point for the search
      * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended
-     *
-     * @return a list of Address objects. Returns null or empty list if no matches were
-     * found or there is no backend service available.
-     *
+     * @return a list of Address objects. Returns null or empty list if no matches were found or
+     *     there is no backend service available.
      * @throws IllegalArgumentException if latitude or longitude is invalid
      * @throws IOException if there is a failure
-     *
      * @deprecated Use {@link #getFromLocation(double, double, int, GeocodeListener)} instead to
-     * avoid blocking a thread waiting for results.
+     *     avoid blocking a thread waiting for results.
      */
     @Deprecated
     public @Nullable List<Address> getFromLocation(
             @FloatRange(from = -90D, to = 90D) double latitude,
-            @FloatRange(from = -180D, to = 180D)double longitude,
-            @IntRange int maxResults)
+            @FloatRange(from = -180D, to = 180D) double longitude,
+            @IntRange(from = 1) int maxResults)
             throws IOException {
         SynchronousGeocoder listener = new SynchronousGeocoder();
         getFromLocation(latitude, longitude, maxResults, listener);
@@ -142,26 +153,32 @@
      * <p class="warning"><strong>Warning:</strong> Geocoding services may provide no guarantees on
      * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful
      * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance
-     * purposes.</p>
+     * purposes.
      *
      * @param latitude the latitude a point for the search
      * @param longitude the longitude a point for the search
      * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended
      * @param listener a listener for receiving results
-     *
      * @throws IllegalArgumentException if latitude or longitude is invalid
      */
     public void getFromLocation(
             @FloatRange(from = -90D, to = 90D) double latitude,
             @FloatRange(from = -180D, to = 180D) double longitude,
-            @IntRange int maxResults,
+            @IntRange(from = 1) int maxResults,
             @NonNull GeocodeListener listener) {
-        Preconditions.checkArgumentInRange(latitude, -90.0, 90.0, "latitude");
-        Preconditions.checkArgumentInRange(longitude, -180.0, 180.0, "longitude");
-
+        ReverseGeocodeRequest.Builder b =
+                new ReverseGeocodeRequest.Builder(
+                        latitude,
+                        longitude,
+                        maxResults,
+                        mLocale,
+                        Process.myUid(),
+                        mContext.getPackageName());
+        if (mContext.getAttributionTag() != null) {
+            b.setCallingAttributionTag(mContext.getAttributionTag());
+        }
         try {
-            mService.getFromLocation(latitude, longitude, maxResults, mParams,
-                    new GeocoderImpl(listener));
+            mService.reverseGeocode(b.build(), new GeocodeCallbackImpl(listener));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -176,29 +193,25 @@
      * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on
      * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful
      * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance
-     * purposes.</p>
+     * purposes.
      *
      * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for
-     * excessive amounts of time, up to 60 seconds or more. It's strongly encouraged to use the
-     * asynchronous version of this API. If that is not possible, this should be run on a background
-     * thread to avoid blocking other operations.</p>
+     * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this
+     * API. If that is not possible, this should be run on a background thread to avoid blocking
+     * other operations.
      *
      * @param locationName a user-supplied description of a location
      * @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended
-     *
-     * @return a list of Address objects. Returns null or empty list if no matches were
-     * found or there is no backend service available.
-     *
+     * @return a list of Address objects. Returns null or empty list if no matches were found or
+     *     there is no backend service available.
      * @throws IllegalArgumentException if locationName is null
      * @throws IOException if there is a failure
-     *
      * @deprecated Use {@link #getFromLocationName(String, int, GeocodeListener)} instead to avoid
-     * blocking a thread waiting for results.
+     *     blocking a thread waiting for results.
      */
     @Deprecated
     public @Nullable List<Address> getFromLocationName(
-            @NonNull String locationName,
-            @IntRange int maxResults) throws IOException {
+            @NonNull String locationName, @IntRange(from = 1) int maxResults) throws IOException {
         return getFromLocationName(locationName, maxResults, 0, 0, 0, 0);
     }
 
@@ -211,17 +224,16 @@
      * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on
      * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful
      * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance
-     * purposes.</p>
+     * purposes.
      *
      * @param locationName a user-supplied description of a location
      * @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended
      * @param listener a listener for receiving results
-     *
      * @throws IllegalArgumentException if locationName is null
      */
     public void getFromLocationName(
             @NonNull String locationName,
-            @IntRange int maxResults,
+            @IntRange(from = 1) int maxResults,
             @NonNull GeocodeListener listener) {
         getFromLocationName(locationName, maxResults, 0, 0, 0, 0, listener);
     }
@@ -232,45 +244,42 @@
      * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be
      * localized for the locale provided to this class's constructor.
      *
-     * <p> You may specify a bounding box for the search results by including the latitude and
+     * <p>You may specify a bounding box for the search results by including the latitude and
      * longitude of the lower left point and upper right point of the box.
      *
      * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on
      * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful
      * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance
-     * purposes.</p>
+     * purposes.
      *
      * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for
-     * excessive amounts of time, up to 60 seconds or more. It's strongly encouraged to use the
-     * asynchronous version of this API. If that is not possible, this should be run on a background
-     * thread to avoid blocking other operations.</p>
+     * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this
+     * API. If that is not possible, this should be run on a background thread to avoid blocking
+     * other operations.
      *
-     * @param locationName        a user-supplied description of a location
-     * @param maxResults          max number of addresses to return. Smaller numbers (1 to 5) are
-     *                            recommended
-     * @param lowerLeftLatitude   the latitude of the lower left corner of the bounding box
-     * @param lowerLeftLongitude  the longitude of the lower left corner of the bounding box
-     * @param upperRightLatitude  the latitude of the upper right corner of the bounding box
+     * @param locationName a user-supplied description of a location
+     * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended
+     * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box
+     * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box
+     * @param upperRightLatitude the latitude of the upper right corner of the bounding box
      * @param upperRightLongitude the longitude of the upper right corner of the bounding box
-     *
-     * @return a list of Address objects. Returns null or empty list if no matches were
-     * found or there is no backend service available.
-     *
+     * @return a list of Address objects. Returns null or empty list if no matches were found or
+     *     there is no backend service available.
      * @throws IllegalArgumentException if locationName is null
      * @throws IllegalArgumentException if any latitude or longitude is invalid
-     * @throws IOException              if there is a failure
-     *
+     * @throws IOException if there is a failure
      * @deprecated Use {@link #getFromLocationName(String, int, double, double, double, double,
-     * GeocodeListener)} instead to avoid blocking a thread waiting for results.
+     *     GeocodeListener)} instead to avoid blocking a thread waiting for results.
      */
     @Deprecated
     public @Nullable List<Address> getFromLocationName(
             @NonNull String locationName,
-            @IntRange int maxResults,
+            @IntRange(from = 1) int maxResults,
             @FloatRange(from = -90D, to = 90D) double lowerLeftLatitude,
             @FloatRange(from = -180D, to = 180D) double lowerLeftLongitude,
             @FloatRange(from = -90D, to = 90D) double upperRightLatitude,
-            @FloatRange(from = -180D, to = 180D) double upperRightLongitude) throws IOException {
+            @FloatRange(from = -180D, to = 180D) double upperRightLongitude)
+            throws IOException {
         SynchronousGeocoder listener = new SynchronousGeocoder();
         getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude,
                 upperRightLatitude, upperRightLongitude, listener);
@@ -283,75 +292,79 @@
      * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be
      * localized for the locale provided to this class's constructor.
      *
-     * <p> You may specify a bounding box for the search results by including the latitude and
+     * <p>You may specify a bounding box for the search results by including the latitude and
      * longitude of the lower left point and upper right point of the box.
      *
      * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on
      * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful
      * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance
-     * purposes.</p>
+     * purposes.
      *
-     * @param locationName        a user-supplied description of a location
-     * @param maxResults          max number of addresses to return. Smaller numbers (1 to 5) are
-     *                            recommended
-     * @param lowerLeftLatitude   the latitude of the lower left corner of the bounding box
-     * @param lowerLeftLongitude  the longitude of the lower left corner of the bounding box
-     * @param upperRightLatitude  the latitude of the upper right corner of the bounding box
+     * @param locationName a user-supplied description of a location
+     * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended
+     * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box
+     * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box
+     * @param upperRightLatitude the latitude of the upper right corner of the bounding box
      * @param upperRightLongitude the longitude of the upper right corner of the bounding box
-     * @param listener            a listener for receiving results
-     *
+     * @param listener a listener for receiving results
      * @throws IllegalArgumentException if locationName is null
      * @throws IllegalArgumentException if any latitude or longitude is invalid
      */
     public void getFromLocationName(
             @NonNull String locationName,
-            @IntRange int maxResults,
+            @IntRange(from = 1) int maxResults,
             @FloatRange(from = -90D, to = 90D) double lowerLeftLatitude,
             @FloatRange(from = -180D, to = 180D) double lowerLeftLongitude,
             @FloatRange(from = -90D, to = 90D) double upperRightLatitude,
             @FloatRange(from = -180D, to = 180D) double upperRightLongitude,
             @NonNull GeocodeListener listener) {
-        Preconditions.checkArgument(locationName != null);
-        Preconditions.checkArgumentInRange(lowerLeftLatitude, -90.0, 90.0, "lowerLeftLatitude");
-        Preconditions.checkArgumentInRange(lowerLeftLongitude, -180.0, 180.0, "lowerLeftLongitude");
-        Preconditions.checkArgumentInRange(upperRightLatitude, -90.0, 90.0, "upperRightLatitude");
-        Preconditions.checkArgumentInRange(upperRightLongitude, -180.0, 180.0,
-                "upperRightLongitude");
-
+        ForwardGeocodeRequest.Builder b =
+                new ForwardGeocodeRequest.Builder(
+                        locationName,
+                        lowerLeftLatitude,
+                        lowerLeftLongitude,
+                        upperRightLatitude,
+                        upperRightLongitude,
+                        maxResults,
+                        mLocale,
+                        Process.myUid(),
+                        mContext.getPackageName());
+        if (mContext.getAttributionTag() != null) {
+            b.setCallingAttributionTag(mContext.getAttributionTag());
+        }
         try {
-            mService.getFromLocationName(locationName, lowerLeftLatitude, lowerLeftLongitude,
-                    upperRightLatitude, upperRightLongitude, maxResults, mParams,
-                    new GeocoderImpl(listener));
+            mService.forwardGeocode(b.build(), new GeocodeCallbackImpl(listener));
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
-    private static class GeocoderImpl extends IGeocodeListener.Stub {
+    private static class GeocodeCallbackImpl extends IGeocodeCallback.Stub {
 
-        private GeocodeListener mListener;
+        @Nullable private GeocodeListener mListener;
 
-        GeocoderImpl(GeocodeListener listener) {
+        GeocodeCallbackImpl(GeocodeListener listener) {
             mListener = Objects.requireNonNull(listener);
         }
 
         @Override
-        public void onResults(String error, List<Address> addresses) throws RemoteException {
+        public void onError(@Nullable String error) {
             if (mListener == null) {
                 return;
             }
 
-            GeocodeListener listener = mListener;
+            mListener.onError(error);
             mListener = null;
+        }
 
-            if (error != null) {
-                listener.onError(error);
-            } else {
-                if (addresses == null) {
-                    addresses = Collections.emptyList();
-                }
-                listener.onGeocode(addresses);
+        @Override
+        public void onResults(List<Address> addresses) {
+            if (mListener == null) {
+                return;
             }
+
+            mListener.onGeocode(addresses);
+            mListener = null;
         }
     }
 
@@ -364,21 +377,21 @@
         SynchronousGeocoder() {}
 
         @Override
-        public void onGeocode(List<Address> addresses) {
+        public void onGeocode(@NonNull List<Address> addresses) {
             mResults = addresses;
             mLatch.countDown();
         }
 
         @Override
-        public void onError(String errorMessage) {
-            mError = errorMessage;
+        public void onError(@Nullable String error) {
+            mError = error;
             mLatch.countDown();
         }
 
         public List<Address> getResults() throws IOException {
             try {
                 if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-                    mError = "Service not Available";
+                    throw new IOException(new TimeoutException());
                 }
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
diff --git a/location/java/android/location/IGeocodeProvider.aidl b/location/java/android/location/IGeocodeProvider.aidl
deleted file mode 100644
index e661ca6..0000000
--- a/location/java/android/location/IGeocodeProvider.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location;
-
-import android.location.Address;
-import android.location.IGeocodeListener;
-import android.location.GeocoderParams;
-
-/**
- * An interface for location providers implementing the Geocoder services.
- *
- * {@hide}
- */
-interface IGeocodeProvider {
-
-    oneway void getFromLocation(double latitude, double longitude, int maxResults, in GeocoderParams params, in IGeocodeListener listener);
-    oneway void getFromLocationName(String locationName, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude,
-        double upperRightLongitude, int maxResults, in GeocoderParams params, in IGeocodeListener listener);
-}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 72761ef..c96c118 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -19,13 +19,11 @@
 import android.app.PendingIntent;
 import android.location.Address;
 import android.location.Criteria;
-import android.location.GeocoderParams;
 import android.location.Geofence;
 import android.location.GnssAntennaInfo;
 import android.location.GnssCapabilities;
 import android.location.GnssMeasurementCorrections;
 import android.location.GnssMeasurementRequest;
-import android.location.IGeocodeListener;
 import android.location.IGnssAntennaInfoListener;
 import android.location.IGnssMeasurementsListener;
 import android.location.IGnssStatusListener;
@@ -37,8 +35,11 @@
 import android.location.Location;
 import android.location.LocationRequest;
 import android.location.LocationTime;
+import android.location.provider.ForwardGeocodeRequest;
+import android.location.provider.IGeocodeCallback;
 import android.location.provider.IProviderRequestListener;
 import android.location.provider.ProviderProperties;
+import android.location.provider.ReverseGeocodeRequest;
 import android.os.Bundle;
 import android.os.ICancellationSignal;
 import android.os.PackageTagsList;
@@ -68,13 +69,9 @@
     void requestGeofence(in Geofence geofence, in PendingIntent intent, String packageName, String attributionTag);
     void removeGeofence(in PendingIntent intent);
 
-    boolean geocoderIsPresent();
-    void getFromLocation(double latitude, double longitude, int maxResults,
-        in GeocoderParams params, in IGeocodeListener listener);
-    void getFromLocationName(String locationName,
-        double lowerLeftLatitude, double lowerLeftLongitude,
-        double upperRightLatitude, double upperRightLongitude, int maxResults,
-        in GeocoderParams params, in IGeocodeListener listener);
+    boolean isGeocodeAvailable();
+    void reverseGeocode(in ReverseGeocodeRequest request, in IGeocodeCallback callback);
+    void forwardGeocode(in ForwardGeocodeRequest request, in IGeocodeCallback callback);
 
     GnssCapabilities getGnssCapabilities();
     int getGnssYearOfHardware();
diff --git a/location/java/android/location/flags/location.aconfig b/location/java/android/location/flags/location.aconfig
index 0fa641b..156be38 100644
--- a/location/java/android/location/flags/location.aconfig
+++ b/location/java/android/location/flags/location.aconfig
@@ -1,6 +1,13 @@
 package: "android.location.flags"
 
 flag {
+    name: "new_geocoder"
+    namespace: "location"
+    description: "Flag for new Geocoder APIs"
+    bug: "229872126"
+}
+
+flag {
     name: "location_bypass"
     namespace: "location"
     description: "Enable location bypass appops behavior"
diff --git a/location/java/android/location/GeocoderParams.aidl b/location/java/android/location/provider/ForwardGeocodeRequest.aidl
similarity index 74%
copy from location/java/android/location/GeocoderParams.aidl
copy to location/java/android/location/provider/ForwardGeocodeRequest.aidl
index 2484e20..acd6190 100644
--- a/location/java/android/location/GeocoderParams.aidl
+++ b/location/java/android/location/provider/ForwardGeocodeRequest.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2010, The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package android.location;
+package android.location.provider;
 
-parcelable GeocoderParams;
+parcelable ForwardGeocodeRequest;
diff --git a/location/java/android/location/provider/ForwardGeocodeRequest.java b/location/java/android/location/provider/ForwardGeocodeRequest.java
new file mode 100644
index 0000000..8f227b1
--- /dev/null
+++ b/location/java/android/location/provider/ForwardGeocodeRequest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import static java.lang.Math.max;
+
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.location.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Forward geocode (ie from address to lat/lng) provider request.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_NEW_GEOCODER)
+@SystemApi
+public final class ForwardGeocodeRequest implements Parcelable {
+
+    private final String mLocationName;
+    private final double mLowerLeftLatitude;
+    private final double mLowerLeftLongitude;
+    private final double mUpperRightLatitude;
+    private final double mUpperRightLongitude;
+    private final int mMaxResults;
+    private final Locale mLocale;
+    private final int mCallingUid;
+    private final String mCallingPackage;
+    @Nullable private final String mCallingAttributionTag;
+
+    private ForwardGeocodeRequest(
+            @NonNull String locationName,
+            double lowerLeftLatitude,
+            double lowerLeftLongitude,
+            double upperRightLatitude,
+            double upperRightLongitude,
+            int maxResults,
+            @NonNull Locale locale,
+            int callingUid,
+            @NonNull String callingPackage,
+            @Nullable String callingAttributionTag) {
+        Preconditions.checkArgument(locationName != null, "locationName must not be null");
+        Preconditions.checkArgumentInRange(lowerLeftLatitude, -90.0, 90.0, "lowerLeftLatitude");
+        Preconditions.checkArgumentInRange(lowerLeftLongitude, -180.0, 180.0, "lowerLeftLongitude");
+        Preconditions.checkArgumentInRange(upperRightLatitude, -90.0, 90.0, "upperRightLatitude");
+        Preconditions.checkArgumentInRange(
+                upperRightLongitude, -180.0, 180.0, "upperRightLongitude");
+
+        mLocationName = locationName;
+        mLowerLeftLatitude = lowerLeftLatitude;
+        mLowerLeftLongitude = lowerLeftLongitude;
+        mUpperRightLatitude = upperRightLatitude;
+        mUpperRightLongitude = upperRightLongitude;
+        mMaxResults = max(maxResults, 1);
+        mLocale = Objects.requireNonNull(locale);
+
+        mCallingUid = callingUid;
+        mCallingPackage = Objects.requireNonNull(callingPackage);
+        mCallingAttributionTag = callingAttributionTag;
+    }
+
+    /**
+     * The location name to be forward geocoded. An arbitrary user string that could have any value.
+     */
+    @NonNull
+    public String getLocationName() {
+        return mLocationName;
+    }
+
+    /** The lower left latitude of the bounding box that should constrain forward geocoding. */
+    @FloatRange(from = -90.0, to = 90.0)
+    public double getLowerLeftLatitude() {
+        return mLowerLeftLatitude;
+    }
+
+    /** The lower left longitude of the bounding box that should constrain forward geocoding. */
+    @FloatRange(from = -180.0, to = 180.0)
+    public double getLowerLeftLongitude() {
+        return mLowerLeftLongitude;
+    }
+
+    /** The upper right latitude of the bounding box that should constrain forward geocoding. */
+    @FloatRange(from = -90.0, to = 90.0)
+    public double getUpperRightLatitude() {
+        return mUpperRightLatitude;
+    }
+
+    /** The upper right longitude of the bounding box that should constrain forward geocoding. */
+    @FloatRange(from = -180.0, to = 180.0)
+    public double getUpperRightLongitude() {
+        return mUpperRightLongitude;
+    }
+
+    /** The maximum number of forward geocoding results that should be returned. */
+    @IntRange(from = 1)
+    public int getMaxResults() {
+        return mMaxResults;
+    }
+
+    /** The locale that results should be localized to (best effort). */
+    @NonNull
+    public Locale getLocale() {
+        return mLocale;
+    }
+
+    /** The UID of the caller this geocoding request is happening on behalf of. */
+    public int getCallingUid() {
+        return mCallingUid;
+    }
+
+    /** The package of the caller this geocoding request is happening on behalf of. */
+    @NonNull
+    public String getCallingPackage() {
+        return mCallingPackage;
+    }
+
+    /** The attribution tag of the caller this geocoding request is happening on behalf of. */
+    @Nullable
+    public String getCallingAttributionTag() {
+        return mCallingAttributionTag;
+    }
+
+    public static final @NonNull Creator<ForwardGeocodeRequest> CREATOR =
+            new Creator<>() {
+                @Override
+                public ForwardGeocodeRequest createFromParcel(Parcel in) {
+                    return new ForwardGeocodeRequest(
+                            Objects.requireNonNull(in.readString8()),
+                            in.readDouble(),
+                            in.readDouble(),
+                            in.readDouble(),
+                            in.readDouble(),
+                            in.readInt(),
+                            new Locale(in.readString8(), in.readString8(), in.readString8()),
+                            in.readInt(),
+                            Objects.requireNonNull(in.readString8()),
+                            in.readString8());
+                }
+
+                @Override
+                public ForwardGeocodeRequest[] newArray(int size) {
+                    return new ForwardGeocodeRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeString8(mLocationName);
+        parcel.writeDouble(mLowerLeftLatitude);
+        parcel.writeDouble(mLowerLeftLongitude);
+        parcel.writeDouble(mUpperRightLatitude);
+        parcel.writeDouble(mUpperRightLongitude);
+        parcel.writeInt(mMaxResults);
+        parcel.writeString8(mLocale.getLanguage());
+        parcel.writeString8(mLocale.getCountry());
+        parcel.writeString8(mLocale.getVariant());
+        parcel.writeInt(mCallingUid);
+        parcel.writeString8(mCallingPackage);
+        parcel.writeString8(mCallingAttributionTag);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (object instanceof ForwardGeocodeRequest that) {
+            return mLowerLeftLatitude == that.mLowerLeftLatitude
+                    && mLowerLeftLongitude == that.mLowerLeftLongitude
+                    && mUpperRightLatitude == that.mUpperRightLatitude
+                    && mUpperRightLongitude == that.mUpperRightLongitude
+                    && mMaxResults == that.mMaxResults
+                    && mCallingUid == that.mCallingUid
+                    && mLocale.equals(that.mLocale)
+                    && mCallingPackage.equals(that.mCallingPackage)
+                    && mLocationName.equals(that.mLocationName)
+                    && Objects.equals(mCallingAttributionTag, that.mCallingAttributionTag);
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mLocationName,
+                mLowerLeftLatitude,
+                mLowerLeftLongitude,
+                mUpperRightLatitude,
+                mUpperRightLongitude,
+                mMaxResults,
+                mLocale,
+                mCallingUid,
+                mCallingPackage,
+                mCallingAttributionTag);
+    }
+
+    /** A Builder for {@link ReverseGeocodeRequest}s. */
+    public static final class Builder {
+
+        private final String mLocationName;
+        private final double mLowerLeftLatitude;
+        private final double mLowerLeftLongitude;
+        private final double mUpperRightLatitude;
+        private final double mUpperRightLongitude;
+        private final int mMaxResults;
+        private final Locale mLocale;
+
+        private final int mCallingUid;
+        private final String mCallingPackage;
+        @Nullable private String mCallingAttributionTag;
+
+        /** Creates a new Builder instance with the given parameters. */
+        public Builder(
+                @NonNull String locationName,
+                @FloatRange(from = -90.0, to = 90.0) double lowerLeftLatitude,
+                @FloatRange(from = -180.0, to = 180.0) double lowerLeftLongitude,
+                @FloatRange(from = -90.0, to = 90.0) double upperRightLatitude,
+                @FloatRange(from = -180.0, to = 180.0) double upperRightLongitude,
+                @IntRange(from = 1) int maxResults,
+                @NonNull Locale locale,
+                int callingUid,
+                @NonNull String callingPackage) {
+            mLocationName = locationName;
+            mLowerLeftLatitude = lowerLeftLatitude;
+            mLowerLeftLongitude = lowerLeftLongitude;
+            mUpperRightLatitude = upperRightLatitude;
+            mUpperRightLongitude = upperRightLongitude;
+            mMaxResults = maxResults;
+            mLocale = locale;
+            mCallingUid = callingUid;
+            mCallingPackage = callingPackage;
+            mCallingAttributionTag = null;
+        }
+
+        /** Sets the attribution tag. */
+        @NonNull
+        public Builder setCallingAttributionTag(@NonNull String attributionTag) {
+            mCallingAttributionTag = attributionTag;
+            return this;
+        }
+
+        /** Builds a {@link ForwardGeocodeRequest}. */
+        @NonNull
+        public ForwardGeocodeRequest build() {
+            return new ForwardGeocodeRequest(
+                    mLocationName,
+                    mLowerLeftLatitude,
+                    mLowerLeftLongitude,
+                    mUpperRightLatitude,
+                    mUpperRightLongitude,
+                    mMaxResults,
+                    mLocale,
+                    mCallingUid,
+                    mCallingPackage,
+                    mCallingAttributionTag);
+        }
+    }
+}
diff --git a/location/java/android/location/provider/GeocodeProviderBase.java b/location/java/android/location/provider/GeocodeProviderBase.java
new file mode 100644
index 0000000..e2c48b9
--- /dev/null
+++ b/location/java/android/location/provider/GeocodeProviderBase.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Address;
+import android.location.flags.Flags;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Base class for geocode providers outside the system server.
+ *
+ * <p>Geocode providers should be wrapped in a non-exported service which returns the result of
+ * {@link #getBinder()} from the service's {@link android.app.Service#onBind(Intent)} method. The
+ * service should not be exported so that components other than the system server cannot bind to it.
+ * Alternatively, the service may be guarded by a permission that only system server can obtain. The
+ * service may specify metadata on its capabilities:
+ *
+ * <ul>
+ *   <li>"serviceVersion": An integer version code to help tie break if multiple services are
+ *       capable of implementing the geocode provider. All else equal, the service with the highest
+ *       version code will be chosen. Assumed to be 0 if not specified.
+ *   <li>"serviceIsMultiuser": A boolean property, indicating if the service wishes to take
+ *       responsibility for handling changes to the current user on the device. If true, the service
+ *       will always be bound from the system user. If false, the service will always be bound from
+ *       the current user. If the current user changes, the old binding will be released, and a new
+ *       binding established under the new user. Assumed to be false if not specified.
+ * </ul>
+ *
+ * <p>The service should have an intent filter in place for the geocode provider as specified by the
+ * constant in this class.
+ *
+ * <p>Geocode providers are identified by their UID / package name / attribution tag. Based on this
+ * identity, geocode providers may be given some special privileges.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_NEW_GEOCODER)
+@SystemApi
+public abstract class GeocodeProviderBase {
+
+    /**
+     * The action the wrapping service should have in its intent filter to implement the geocode
+     * provider.
+     */
+    @SuppressLint("ActionValue")
+    public static final String ACTION_GEOCODE_PROVIDER =
+            "com.android.location.service.GeocodeProvider";
+
+    final String mTag;
+    @Nullable final String mAttributionTag;
+    final IBinder mBinder;
+
+    /**
+     * Subclasses should pass in a context and an arbitrary tag that may be used for logcat logging
+     * of errors, and thus should uniquely identify the class.
+     */
+    public GeocodeProviderBase(@NonNull Context context, @NonNull String tag) {
+        mTag = tag;
+        mAttributionTag = context.getAttributionTag();
+        mBinder = new Service();
+    }
+
+    /**
+     * Returns the IBinder instance that should be returned from the {@link
+     * android.app.Service#onBind(Intent)} method of the wrapping service.
+     */
+    @NonNull
+    public final IBinder getBinder() {
+        return mBinder;
+    }
+
+    /**
+     * Requests forward geocoding of the given arguments. The given callback must be invoked once.
+     */
+    public abstract void onForwardGeocode(
+            @NonNull ForwardGeocodeRequest request,
+            @NonNull OutcomeReceiver<List<Address>, Exception> callback);
+
+    /**
+     * Requests reverse geocoding of the given arguments. The given callback must be invoked once.
+     */
+    public abstract void onReverseGeocode(
+            @NonNull ReverseGeocodeRequest request,
+            @NonNull OutcomeReceiver<List<Address>, Exception> callback);
+
+    private class Service extends IGeocodeProvider.Stub {
+        @Override
+        public void forwardGeocode(ForwardGeocodeRequest request, IGeocodeCallback callback) {
+            try {
+                onForwardGeocode(request, new SingleUseCallback(callback));
+            } catch (RuntimeException e) {
+                // exceptions on one-way binder threads are dropped - move to a different thread
+                Log.w(mTag, e);
+                new Handler(Looper.getMainLooper())
+                        .post(
+                                () -> {
+                                    throw new AssertionError(e);
+                                });
+            }
+        }
+
+        @Override
+        public void reverseGeocode(ReverseGeocodeRequest request, IGeocodeCallback callback) {
+            try {
+                onReverseGeocode(request, new SingleUseCallback(callback));
+            } catch (RuntimeException e) {
+                // exceptions on one-way binder threads are dropped - move to a different thread
+                Log.w(mTag, e);
+                new Handler(Looper.getMainLooper())
+                        .post(
+                                () -> {
+                                    throw new AssertionError(e);
+                                });
+            }
+        }
+    }
+
+    private static class SingleUseCallback implements OutcomeReceiver<List<Address>, Exception> {
+
+        private final AtomicReference<IGeocodeCallback> mCallback;
+
+        SingleUseCallback(IGeocodeCallback callback) {
+            mCallback = new AtomicReference<>(callback);
+        }
+
+        @Override
+        public void onError(Exception e) {
+            try {
+                Objects.requireNonNull(mCallback.getAndSet(null)).onError(e.toString());
+            } catch (RemoteException r) {
+                throw r.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public void onResult(List<Address> results) {
+            try {
+                Objects.requireNonNull(mCallback.getAndSet(null)).onResults(results);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/location/java/android/location/IGeocodeListener.aidl b/location/java/android/location/provider/IGeocodeCallback.aidl
similarity index 76%
rename from location/java/android/location/IGeocodeListener.aidl
rename to location/java/android/location/provider/IGeocodeCallback.aidl
index 8e10411..cf52713 100644
--- a/location/java/android/location/IGeocodeListener.aidl
+++ b/location/java/android/location/provider/IGeocodeCallback.aidl
@@ -14,16 +14,15 @@
  * limitations under the License.
  */
 
-package android.location;
+package android.location.provider;
 
 import android.location.Address;
 
 /**
- * An interface for returning geocode results.
- *
- * {@hide}
+ * Binder interface for geocoding callbacks.
+ * @hide
  */
-interface IGeocodeListener {
-
-    oneway void onResults(String error, in List<Address> results);
+oneway interface IGeocodeCallback {
+    void onError(String error);
+    void onResults(in List<Address> results);
 }
diff --git a/location/java/android/location/provider/IGeocodeProvider.aidl b/location/java/android/location/provider/IGeocodeProvider.aidl
new file mode 100644
index 0000000..9217438
--- /dev/null
+++ b/location/java/android/location/provider/IGeocodeProvider.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import android.location.provider.IGeocodeCallback;
+import android.location.provider.ForwardGeocodeRequest;
+import android.location.provider.ReverseGeocodeRequest;
+
+/**
+ * Binder interface for services that implement geocode providers. Do not implement this directly,
+ * extend {@link GeocodeProviderBase} instead.
+ * @hide
+ */
+oneway interface IGeocodeProvider {
+    void forwardGeocode(in ForwardGeocodeRequest request, in IGeocodeCallback callback);
+    void reverseGeocode(in ReverseGeocodeRequest request, in IGeocodeCallback callback);
+}
diff --git a/location/java/android/location/GeocoderParams.aidl b/location/java/android/location/provider/ReverseGeocodeRequest.aidl
similarity index 75%
rename from location/java/android/location/GeocoderParams.aidl
rename to location/java/android/location/provider/ReverseGeocodeRequest.aidl
index 2484e20..015757a 100644
--- a/location/java/android/location/GeocoderParams.aidl
+++ b/location/java/android/location/provider/ReverseGeocodeRequest.aidl
@@ -1,11 +1,10 @@
 /*
- * Copyright (C) 2010, The Android Open Source Project
- *
+ * Copyright (C) 2024
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +13,6 @@
  * limitations under the License.
  */
 
-package android.location;
+package android.location.provider;
 
-parcelable GeocoderParams;
+parcelable ReverseGeocodeRequest;
diff --git a/location/java/android/location/provider/ReverseGeocodeRequest.java b/location/java/android/location/provider/ReverseGeocodeRequest.java
new file mode 100644
index 0000000..57c9047
--- /dev/null
+++ b/location/java/android/location/provider/ReverseGeocodeRequest.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.provider;
+
+import static java.lang.Math.max;
+
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.location.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Reverse geocode (ie from lat/lng to address) provider request.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_NEW_GEOCODER)
+@SystemApi
+public final class ReverseGeocodeRequest implements Parcelable {
+
+    private final double mLatitude;
+    private final double mLongitude;
+    private final int mMaxResults;
+    private final Locale mLocale;
+
+    private final int mCallingUid;
+    private final String mCallingPackage;
+    @Nullable private final String mCallingAttributionTag;
+
+    private ReverseGeocodeRequest(
+            double latitude,
+            double longitude,
+            int maxResults,
+            Locale locale,
+            int callingUid,
+            String callingPackage,
+            @Nullable String callingAttributionTag) {
+        Preconditions.checkArgumentInRange(latitude, -90.0, 90.0, "latitude");
+        Preconditions.checkArgumentInRange(longitude, -180.0, 180.0, "longitude");
+
+        mLatitude = latitude;
+        mLongitude = longitude;
+        mMaxResults = max(maxResults, 1);
+        mLocale = Objects.requireNonNull(locale);
+
+        mCallingUid = callingUid;
+        mCallingPackage = Objects.requireNonNull(callingPackage);
+        mCallingAttributionTag = callingAttributionTag;
+    }
+
+    /** The latitude of the point to be reverse geocoded. */
+    @FloatRange(from = -90.0, to = 90.0)
+    public double getLatitude() {
+        return mLatitude;
+    }
+
+    /** The longitude of the point to be reverse geocoded. */
+    @FloatRange(from = -180.0, to = 180.0)
+    public double getLongitude() {
+        return mLongitude;
+    }
+
+    /** The maximum number of reverse geocoding results that should be returned. */
+    @IntRange(from = 1)
+    public int getMaxResults() {
+        return mMaxResults;
+    }
+
+    /** The locale that results should be localized to (best effort). */
+    @NonNull
+    public Locale getLocale() {
+        return mLocale;
+    }
+
+    /** The UID of the caller this geocoding request is happening on behalf of. */
+    public int getCallingUid() {
+        return mCallingUid;
+    }
+
+    /** The package of the caller this geocoding request is happening on behalf of. */
+    @NonNull
+    public String getCallingPackage() {
+        return mCallingPackage;
+    }
+
+    /** The attribution tag of the caller this geocoding request is happening on behalf of. */
+    @Nullable
+    public String getCallingAttributionTag() {
+        return mCallingAttributionTag;
+    }
+
+    public static final @NonNull Creator<ReverseGeocodeRequest> CREATOR =
+            new Creator<>() {
+                @Override
+                public ReverseGeocodeRequest createFromParcel(Parcel in) {
+                    return new ReverseGeocodeRequest(
+                            in.readDouble(),
+                            in.readDouble(),
+                            in.readInt(),
+                            new Locale(in.readString8(), in.readString8(), in.readString8()),
+                            in.readInt(),
+                            Objects.requireNonNull(in.readString8()),
+                            in.readString8());
+                }
+
+                @Override
+                public ReverseGeocodeRequest[] newArray(int size) {
+                    return new ReverseGeocodeRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeDouble(mLatitude);
+        parcel.writeDouble(mLongitude);
+        parcel.writeInt(mMaxResults);
+        parcel.writeString8(mLocale.getLanguage());
+        parcel.writeString8(mLocale.getCountry());
+        parcel.writeString8(mLocale.getVariant());
+        parcel.writeInt(mCallingUid);
+        parcel.writeString8(mCallingPackage);
+        parcel.writeString8(mCallingAttributionTag);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (object instanceof ReverseGeocodeRequest that) {
+            return mLatitude == that.mLatitude
+                    && mLongitude == that.mLongitude
+                    && mMaxResults == that.mMaxResults
+                    && mCallingUid == that.mCallingUid
+                    && mLocale.equals(that.mLocale)
+                    && mCallingPackage.equals(that.mCallingPackage)
+                    && Objects.equals(mCallingAttributionTag, that.mCallingAttributionTag);
+        }
+
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mLatitude,
+                mLongitude,
+                mMaxResults,
+                mLocale,
+                mCallingUid,
+                mCallingPackage,
+                mCallingAttributionTag);
+    }
+
+    /** A Builder for {@link ReverseGeocodeRequest}s. */
+    public static final class Builder {
+
+        private final double mLatitude;
+        private final double mLongitude;
+        private final int mMaxResults;
+        private final Locale mLocale;
+
+        private final int mCallingUid;
+        private final String mCallingPackage;
+        @Nullable private String mCallingAttributionTag;
+
+        /** Creates a new Builder instance with the given parameters. */
+        public Builder(
+                @FloatRange(from = -90.0, to = 90.0) double latitude,
+                @FloatRange(from = -180.0, to = 180.0) double longitude,
+                @IntRange(from = 0) int maxResults,
+                @NonNull Locale locale,
+                int callingUid,
+                @NonNull String callingPackage) {
+            mLatitude = latitude;
+            mLongitude = longitude;
+            mMaxResults = maxResults;
+            mLocale = locale;
+            mCallingUid = callingUid;
+            mCallingPackage = callingPackage;
+            mCallingAttributionTag = null;
+        }
+
+        /** Sets the attribution tag. */
+        @NonNull
+        public Builder setCallingAttributionTag(@NonNull String attributionTag) {
+            mCallingAttributionTag = attributionTag;
+            return this;
+        }
+
+        /** Builds a {@link ReverseGeocodeRequest}. */
+        @NonNull
+        public ReverseGeocodeRequest build() {
+            return new ReverseGeocodeRequest(
+                    mLatitude,
+                    mLongitude,
+                    mMaxResults,
+                    mLocale,
+                    mCallingUid,
+                    mCallingPackage,
+                    mCallingAttributionTag);
+        }
+    }
+}
diff --git a/location/lib/Android.bp b/location/lib/Android.bp
index c9be579..b10019a 100644
--- a/location/lib/Android.bp
+++ b/location/lib/Android.bp
@@ -30,6 +30,7 @@
         "androidx.annotation_annotation",
     ],
     api_packages: [
+        "android.location",
         "com.android.location.provider",
         "com.android.location.timezone.provider",
     ],
diff --git a/location/lib/README.txt b/location/lib/README.txt
index 400a7dd..ae51872 100644
--- a/location/lib/README.txt
+++ b/location/lib/README.txt
@@ -1,30 +1,12 @@
 This library (com.android.location.provider.jar) is a shared java library
-containing classes required by unbundled location providers.
+containing classes required by unbundled providers. The library was created
+as a way of exposing API classes outside of the public API before SystemApi
+was possible. Now that SystemApi exists, no new classes should ever be added
+to this library, and all classes in this library should eventually be
+deprecated and new SystemApi replacements offered.
 
---- Rules of this library ---
-o This library is effectively a PUBLIC API for unbundled location providers
-  that may be distributed outside the system image. So it MUST BE API STABLE.
-  You can add but not remove. The rules are the same as for the
-  public platform SDK API.
-o This library can see and instantiate internal platform classes (such as
-  ProviderRequest.java), but it must not expose them in any public method
-  (or by extending them via inheritance). This would break clients of the
-  library because they cannot see the internal platform classes.
-
-This library is distributed in the system image, and loaded as
-a shared library. So you can change the implementation, but not
-the interface. In this way it is like framework.jar.
-
---- Why does this library exists? ---
-
-Unbundled location providers (such as the NetworkLocationProvider)
-can not use internal platform classes.
-
-So ideally all of these classes would be part of the public platform SDK API,
-but that doesn't seem like a great idea when only applications with a special
-signature can implement this API.
-
-The compromise is this library.
-
-It wraps internal platform classes (like ProviderRequest) with a stable
-API that does not leak the internal classes.
+Whether or not classes in this library can ever be removed must be answered on
+a case by case basis. Most of the classes are usually referenced by Google Play
+services (in which case references can be removed from that code base), but
+these classes may also be referenced by OEM code, which must be considered
+before any removal.
diff --git a/location/lib/api/system-current.txt b/location/lib/api/system-current.txt
index 7046abd..75e6bb4 100644
--- a/location/lib/api/system-current.txt
+++ b/location/lib/api/system-current.txt
@@ -1,4 +1,21 @@
 // Signature format: 2.0
+package android.location {
+
+  @Deprecated public class GeocoderParams implements android.os.Parcelable {
+    ctor @Deprecated public GeocoderParams(android.content.Context);
+    ctor @Deprecated public GeocoderParams(android.content.Context, java.util.Locale);
+    ctor @Deprecated public GeocoderParams(int, String, @Nullable String, java.util.Locale);
+    method @Deprecated public int describeContents();
+    method @Deprecated @Nullable public String getClientAttributionTag();
+    method @Deprecated @NonNull public String getClientPackage();
+    method @Deprecated public int getClientUid();
+    method @Deprecated @NonNull public java.util.Locale getLocale();
+    method @Deprecated public void writeToParcel(android.os.Parcel, int);
+    field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.location.GeocoderParams> CREATOR;
+  }
+
+}
+
 package com.android.location.provider {
 
   @Deprecated public final class FusedLocationHardware {
@@ -25,6 +42,13 @@
     method @Deprecated public void onStatusChanged(int);
   }
 
+  @Deprecated public abstract class GeocodeProvider {
+    ctor @Deprecated public GeocodeProvider();
+    method @Deprecated public android.os.IBinder getBinder();
+    method @Deprecated public abstract String onGetFromLocation(double, double, int, android.location.GeocoderParams, java.util.List<android.location.Address>);
+    method @Deprecated public abstract String onGetFromLocationName(String, double, double, double, double, int, android.location.GeocoderParams, java.util.List<android.location.Address>);
+  }
+
   @Deprecated public class GmsFusedBatchOptions {
     ctor @Deprecated public GmsFusedBatchOptions();
     method @Deprecated public int getFlags();
diff --git a/core/java/android/location/GeocoderParams.java b/location/lib/java/android/location/GeocoderParams.java
similarity index 65%
rename from core/java/android/location/GeocoderParams.java
rename to location/lib/java/android/location/GeocoderParams.java
index 3ea6364..780ccf4 100644
--- a/core/java/android/location/GeocoderParams.java
+++ b/location/lib/java/android/location/GeocoderParams.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -28,15 +28,15 @@
 import java.util.Objects;
 
 /**
- * This class contains extra parameters to pass to an IGeocodeProvider
- * implementation from the Geocoder class.  Currently this contains the
- * language, country and variant information from the Geocoder's locale
- * as well as the Geocoder client's package name for geocoder server
- * logging.  This information is kept in a separate class to allow for
- * future expansion of the IGeocodeProvider interface.
+ * This class was originally shipped out-of-band from the normal API processes as a separate drop
+ * before SystemApi existed. Now that SystemApi does exist, this class has been retroactively
+ * published through SystemApi.
  *
+ * @deprecated Do not use.
  * @hide
  */
+@Deprecated
+@SystemApi
 public class GeocoderParams implements Parcelable {
 
     private final int mUid;
@@ -52,7 +52,8 @@
         this(Process.myUid(), context.getPackageName(), context.getAttributionTag(), locale);
     }
 
-    private GeocoderParams(int uid, String packageName, String attributionTag, Locale locale) {
+    public GeocoderParams(
+            int uid, String packageName, @Nullable String attributionTag, Locale locale) {
         mUid = uid;
         mPackageName = Objects.requireNonNull(packageName);
         mAttributionTag = attributionTag;
@@ -62,7 +63,6 @@
     /**
      * Returns the client UID.
      */
-    @UnsupportedAppUsage
     public int getClientUid() {
         return mUid;
     }
@@ -70,7 +70,6 @@
     /**
      * Returns the client package name.
      */
-    @UnsupportedAppUsage
     public @NonNull String getClientPackage() {
         return mPackageName;
     }
@@ -78,7 +77,6 @@
     /**
      * Returns the client attribution tag.
      */
-    @UnsupportedAppUsage
     public @Nullable String getClientAttributionTag() {
         return mAttributionTag;
     }
@@ -86,34 +84,38 @@
     /**
      * Returns the locale.
      */
-    @UnsupportedAppUsage
     public @NonNull Locale getLocale() {
         return mLocale;
     }
 
     public static final @NonNull Parcelable.Creator<GeocoderParams> CREATOR =
-        new Parcelable.Creator<GeocoderParams>() {
-            public GeocoderParams createFromParcel(Parcel in) {
-                int uid = in.readInt();
-                String packageName = in.readString8();
-                String attributionTag = in.readString8();
-                String language = in.readString8();
-                String country = in.readString8();
-                String variant = in.readString8();
+            new Parcelable.Creator<>() {
+                public GeocoderParams createFromParcel(Parcel in) {
+                    int uid = in.readInt();
+                    String packageName = in.readString8();
+                    String attributionTag = in.readString8();
+                    String language = in.readString8();
+                    String country = in.readString8();
+                    String variant = in.readString8();
 
-                return new GeocoderParams(uid, packageName, attributionTag,
-                        new Locale(language, country, variant));
-            }
+                    return new GeocoderParams(
+                            uid,
+                            packageName,
+                            attributionTag,
+                            new Locale(language, country, variant));
+                }
 
-            public GeocoderParams[] newArray(int size) {
-                return new GeocoderParams[size];
-            }
-        };
+                public GeocoderParams[] newArray(int size) {
+                    return new GeocoderParams[size];
+                }
+            };
 
+    @Override
     public int describeContents() {
         return 0;
     }
 
+    @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(mUid);
         parcel.writeString8(mPackageName);
diff --git a/location/lib/java/com/android/location/provider/GeocodeProvider.java b/location/lib/java/com/android/location/provider/GeocodeProvider.java
index 05d7935..45077ba 100644
--- a/location/lib/java/com/android/location/provider/GeocodeProvider.java
+++ b/location/lib/java/com/android/location/provider/GeocodeProvider.java
@@ -16,10 +16,13 @@
 
 package com.android.location.provider;
 
+import android.annotation.SystemApi;
 import android.location.Address;
 import android.location.GeocoderParams;
-import android.location.IGeocodeListener;
-import android.location.IGeocodeProvider;
+import android.location.provider.ForwardGeocodeRequest;
+import android.location.provider.IGeocodeCallback;
+import android.location.provider.IGeocodeProvider;
+import android.location.provider.ReverseGeocodeRequest;
 import android.os.IBinder;
 import android.os.RemoteException;
 
@@ -27,47 +30,74 @@
 import java.util.List;
 
 /**
- * Base class for geocode providers implemented as unbundled services.
+ * This class was originally shipped out-of-band from the normal API processes as a separate drop
+ * before SystemApi existed. Now that SystemApi does exist, this class has been retroactively
+ * published through SystemApi.
  *
- * <p>Geocode providers can be implemented as services and return the result of
- * {@link GeocodeProvider#getBinder()} in its getBinder() method.
- *
- * <p>IMPORTANT: This class is effectively a public API for unbundled
- * applications, and must remain API stable. See README.txt in the root
- * of this package for more information.
+ * @deprecated Use {@link android.location.provider.GeocodeProviderBase} instead.
  * @hide
  */
+@Deprecated
+@SystemApi
 public abstract class GeocodeProvider {
 
-    private IGeocodeProvider.Stub mProvider = new IGeocodeProvider.Stub() {
-        @Override
-        public void getFromLocation(double latitude, double longitude, int maxResults,
-                GeocoderParams params, IGeocodeListener listener) {
-            List<Address> results = new ArrayList<>();
-            String error = onGetFromLocation(latitude, longitude, maxResults, params, results);
-            try {
-                listener.onResults(error, results);
-            } catch (RemoteException e) {
-                // ignore
-            }
-        }
+    private final IGeocodeProvider.Stub mProvider =
+            new IGeocodeProvider.Stub() {
+                @Override
+                public void reverseGeocode(
+                        ReverseGeocodeRequest request, IGeocodeCallback callback) {
+                    List<Address> results = new ArrayList<>();
+                    String error =
+                            onGetFromLocation(
+                                    request.getLatitude(),
+                                    request.getLongitude(),
+                                    request.getMaxResults(),
+                                    new GeocoderParams(
+                                            request.getCallingUid(),
+                                            request.getCallingPackage(),
+                                            request.getCallingAttributionTag(),
+                                            request.getLocale()),
+                                    results);
+                    try {
+                        if (error != null) {
+                            callback.onError(error);
+                        } else {
+                            callback.onResults(results);
+                        }
+                    } catch (RemoteException e) {
+                        // ignore
+                    }
+                }
 
-        @Override
-        public void getFromLocationName(String locationName,
-                double lowerLeftLatitude, double lowerLeftLongitude,
-                double upperRightLatitude, double upperRightLongitude, int maxResults,
-                GeocoderParams params, IGeocodeListener listener) {
-            List<Address> results = new ArrayList<>();
-            String error = onGetFromLocationName(locationName, lowerLeftLatitude,
-                    lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
-                    maxResults, params, results);
-            try {
-                listener.onResults(error, results);
-            } catch (RemoteException e) {
-                // ignore
-            }
-        }
-    };
+                @Override
+                public void forwardGeocode(
+                        ForwardGeocodeRequest request, IGeocodeCallback callback) {
+                    List<Address> results = new ArrayList<>();
+                    String error =
+                            onGetFromLocationName(
+                                    request.getLocationName(),
+                                    request.getLowerLeftLatitude(),
+                                    request.getLowerLeftLongitude(),
+                                    request.getUpperRightLatitude(),
+                                    request.getUpperRightLongitude(),
+                                    request.getMaxResults(),
+                                    new GeocoderParams(
+                                            request.getCallingUid(),
+                                            request.getCallingPackage(),
+                                            request.getCallingAttributionTag(),
+                                            request.getLocale()),
+                                    results);
+                    try {
+                        if (error != null) {
+                            callback.onError(error);
+                        } else {
+                            callback.onResults(results);
+                        }
+                    } catch (RemoteException e) {
+                        // ignore
+                    }
+                }
+            };
 
     /**
      * This method is overridden to implement the
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
deleted file mode 100644
index ac42646..0000000
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.location;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.location.GeocoderParams;
-import android.location.IGeocodeListener;
-import android.location.IGeocodeProvider;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-import com.android.server.servicewatcher.CurrentUserServiceSupplier;
-import com.android.server.servicewatcher.ServiceWatcher;
-
-import java.util.Collections;
-
-/**
- * Proxy for IGeocodeProvider implementations.
- *
- * @hide
- */
-public class GeocoderProxy {
-
-    private static final String SERVICE_ACTION = "com.android.location.service.GeocodeProvider";
-
-    /**
-     * Creates and registers this proxy. If no suitable service is available for the proxy, returns
-     * null.
-     */
-    @Nullable
-    public static GeocoderProxy createAndRegister(Context context) {
-        GeocoderProxy proxy = new GeocoderProxy(context);
-        if (proxy.register()) {
-            return proxy;
-        } else {
-            return null;
-        }
-    }
-
-    private final ServiceWatcher mServiceWatcher;
-
-    private GeocoderProxy(Context context) {
-        mServiceWatcher = ServiceWatcher.create(context, "GeocoderProxy",
-                CurrentUserServiceSupplier.createFromConfig(context, SERVICE_ACTION,
-                        com.android.internal.R.bool.config_enableGeocoderOverlay,
-                        com.android.internal.R.string.config_geocoderProviderPackageName),
-                null);
-    }
-
-    private boolean register() {
-        boolean resolves = mServiceWatcher.checkServiceResolves();
-        if (resolves) {
-            mServiceWatcher.register();
-        }
-        return resolves;
-    }
-
-    /**
-     * Geocodes stuff.
-     */
-    public void getFromLocation(double latitude, double longitude, int maxResults,
-            GeocoderParams params, IGeocodeListener listener) {
-        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
-            @Override
-            public void run(IBinder binder) throws RemoteException {
-                IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
-                provider.getFromLocation(latitude, longitude, maxResults, params, listener);
-            }
-
-            @Override
-            public void onError(Throwable t) {
-                try {
-                    listener.onResults(t.toString(), Collections.emptyList());
-                } catch (RemoteException e) {
-                    // ignore
-                }
-            }
-        });
-    }
-
-    /**
-     * Geocodes stuff.
-     */
-    public void getFromLocationName(String locationName,
-            double lowerLeftLatitude, double lowerLeftLongitude,
-            double upperRightLatitude, double upperRightLongitude, int maxResults,
-            GeocoderParams params, IGeocodeListener listener) {
-        mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
-            @Override
-            public void run(IBinder binder) throws RemoteException {
-                IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
-                provider.getFromLocationName(locationName, lowerLeftLatitude,
-                        lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
-                        maxResults, params, listener);
-            }
-
-            @Override
-            public void onError(Throwable t) {
-                try {
-                    listener.onResults(t.toString(), Collections.emptyList());
-                } catch (RemoteException e) {
-                    // ignore
-                }
-            }
-        });
-    }
-}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 323cdc5..c5f3855 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -52,13 +52,11 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.location.Criteria;
-import android.location.GeocoderParams;
 import android.location.Geofence;
 import android.location.GnssAntennaInfo;
 import android.location.GnssCapabilities;
 import android.location.GnssMeasurementCorrections;
 import android.location.GnssMeasurementRequest;
-import android.location.IGeocodeListener;
 import android.location.IGnssAntennaInfoListener;
 import android.location.IGnssMeasurementsListener;
 import android.location.IGnssNavigationMessageListener;
@@ -75,8 +73,11 @@
 import android.location.LocationProvider;
 import android.location.LocationRequest;
 import android.location.LocationTime;
+import android.location.provider.ForwardGeocodeRequest;
+import android.location.provider.IGeocodeCallback;
 import android.location.provider.IProviderRequestListener;
 import android.location.provider.ProviderProperties;
+import android.location.provider.ReverseGeocodeRequest;
 import android.location.util.identity.CallerIdentity;
 import android.os.Binder;
 import android.os.Build;
@@ -141,6 +142,7 @@
 import com.android.server.location.provider.PassiveLocationProvider;
 import com.android.server.location.provider.PassiveLocationProviderManager;
 import com.android.server.location.provider.StationaryThrottlingLocationProvider;
+import com.android.server.location.provider.proxy.ProxyGeocodeProvider;
 import com.android.server.location.provider.proxy.ProxyLocationProvider;
 import com.android.server.location.settings.LocationSettings;
 import com.android.server.location.settings.LocationUserSettings;
@@ -253,7 +255,7 @@
 
     private final GeofenceManager mGeofenceManager;
     private volatile @Nullable GnssManagerService mGnssManagerService = null;
-    private GeocoderProxy mGeocodeProvider;
+    private ProxyGeocodeProvider mGeocodeProvider;
 
     private final Object mDeprecatedGnssBatchingLock = new Object();
     @GuardedBy("mDeprecatedGnssBatchingLock")
@@ -493,7 +495,7 @@
         }
 
         // bind to geocoder provider
-        mGeocodeProvider = GeocoderProxy.createAndRegister(mContext);
+        mGeocodeProvider = ProxyGeocodeProvider.createAndRegister(mContext);
         if (mGeocodeProvider == null) {
             Log.e(TAG, "no geocoder provider found");
         }
@@ -1363,23 +1365,22 @@
     }
 
     @Override
-    public boolean geocoderIsPresent() {
+    public boolean isGeocodeAvailable() {
         return mGeocodeProvider != null;
     }
 
     @Override
-    public void getFromLocation(double latitude, double longitude, int maxResults,
-            GeocoderParams params, IGeocodeListener listener) {
-        // validate identity
-        CallerIdentity identity = CallerIdentity.fromBinder(mContext, params.getClientPackage(),
-                params.getClientAttributionTag());
-        Preconditions.checkArgument(identity.getUid() == params.getClientUid());
+    public void reverseGeocode(ReverseGeocodeRequest request, IGeocodeCallback callback) {
+        CallerIdentity identity =
+                CallerIdentity.fromBinder(
+                        mContext, request.getCallingPackage(), request.getCallingAttributionTag());
+        Preconditions.checkArgument(identity.getUid() == request.getCallingUid());
 
         if (mGeocodeProvider != null) {
-            mGeocodeProvider.getFromLocation(latitude, longitude, maxResults, params, listener);
+            mGeocodeProvider.reverseGeocode(request, callback);
         } else {
             try {
-                listener.onResults(null, Collections.emptyList());
+                callback.onError(null);
             } catch (RemoteException e) {
                 // ignore
             }
@@ -1387,22 +1388,17 @@
     }
 
     @Override
-    public void getFromLocationName(String locationName,
-            double lowerLeftLatitude, double lowerLeftLongitude,
-            double upperRightLatitude, double upperRightLongitude, int maxResults,
-            GeocoderParams params, IGeocodeListener listener) {
-        // validate identity
-        CallerIdentity identity = CallerIdentity.fromBinder(mContext, params.getClientPackage(),
-                params.getClientAttributionTag());
-        Preconditions.checkArgument(identity.getUid() == params.getClientUid());
+    public void forwardGeocode(ForwardGeocodeRequest request, IGeocodeCallback callback) {
+        CallerIdentity identity =
+                CallerIdentity.fromBinder(
+                        mContext, request.getCallingPackage(), request.getCallingAttributionTag());
+        Preconditions.checkArgument(identity.getUid() == request.getCallingUid());
 
         if (mGeocodeProvider != null) {
-            mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude,
-                    lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
-                    maxResults, params, listener);
+            mGeocodeProvider.forwardGeocode(request, callback);
         } else {
             try {
-                listener.onResults(null, Collections.emptyList());
+                callback.onError(null);
             } catch (RemoteException e) {
                 // ignore
             }
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyGeocodeProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyGeocodeProvider.java
new file mode 100644
index 0000000..ac945f1
--- /dev/null
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyGeocodeProvider.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.provider.proxy;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.provider.ForwardGeocodeRequest;
+import android.location.provider.GeocodeProviderBase;
+import android.location.provider.IGeocodeCallback;
+import android.location.provider.IGeocodeProvider;
+import android.location.provider.ReverseGeocodeRequest;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.server.servicewatcher.CurrentUserServiceSupplier;
+import com.android.server.servicewatcher.ServiceWatcher;
+
+/** Proxy for IGeocodeProvider implementations. */
+public class ProxyGeocodeProvider {
+
+    /**
+     * Creates and registers this proxy. If no suitable service is available for the proxy, returns
+     * null.
+     */
+    @Nullable
+    public static ProxyGeocodeProvider createAndRegister(Context context) {
+        ProxyGeocodeProvider proxy = new ProxyGeocodeProvider(context);
+        if (proxy.register()) {
+            return proxy;
+        } else {
+            return null;
+        }
+    }
+
+    private final ServiceWatcher mServiceWatcher;
+
+    private ProxyGeocodeProvider(Context context) {
+        mServiceWatcher =
+                ServiceWatcher.create(
+                        context,
+                        "GeocoderProxy",
+                        CurrentUserServiceSupplier.createFromConfig(
+                                context,
+                                GeocodeProviderBase.ACTION_GEOCODE_PROVIDER,
+                                com.android.internal.R.bool.config_enableGeocoderOverlay,
+                                com.android.internal.R.string.config_geocoderProviderPackageName),
+                        null);
+    }
+
+    private boolean register() {
+        boolean resolves = mServiceWatcher.checkServiceResolves();
+        if (resolves) {
+            mServiceWatcher.register();
+        }
+        return resolves;
+    }
+
+    /** Reverse geocodes. */
+    public void reverseGeocode(ReverseGeocodeRequest request, IGeocodeCallback callback) {
+        mServiceWatcher.runOnBinder(
+                new ServiceWatcher.BinderOperation() {
+                    @Override
+                    public void run(IBinder binder) throws RemoteException {
+                        IGeocodeProvider.Stub.asInterface(binder).reverseGeocode(request, callback);
+                    }
+
+                    @Override
+                    public void onError(Throwable t) {
+                        try {
+                            callback.onError(t.toString());
+                        } catch (RemoteException e) {
+                            // ignore
+                        }
+                    }
+                });
+    }
+
+    /** Forward geocodes. */
+    public void forwardGeocode(ForwardGeocodeRequest request, IGeocodeCallback callback) {
+        mServiceWatcher.runOnBinder(
+                new ServiceWatcher.BinderOperation() {
+                    @Override
+                    public void run(IBinder binder) throws RemoteException {
+                        IGeocodeProvider.Stub.asInterface(binder).forwardGeocode(request, callback);
+                    }
+
+                    @Override
+                    public void onError(Throwable t) {
+                        try {
+                            callback.onError(t.toString());
+                        } catch (RemoteException e) {
+                            // ignore
+                        }
+                    }
+                });
+    }
+}