Add asynchronous geocoding API

Geocoding is unsupported, but the old APIs encourage bad behavior and
force clients to waste a thread blocking for results. This often causes
ANRs in a variety of applications. Provider an asynchronous API so
clients can avoid ANRs.

Bug: 197585308
Test: presubmits
Change-Id: I6fdd90d467f01892ce34371c2161721bd5976ca9
This commit is contained in:
Soonil Nagarkar
2021-08-27 17:57:55 -07:00
parent 60a7931da6
commit 03e0b89362
3 changed files with 249 additions and 100 deletions

View File

@@ -19606,14 +19606,22 @@ package android.location {
}
public final class Geocoder {
ctor public Geocoder(android.content.Context, java.util.Locale);
ctor public Geocoder(android.content.Context);
method public java.util.List<android.location.Address> getFromLocation(double, double, int) throws java.io.IOException;
method public java.util.List<android.location.Address> getFromLocationName(String, int) throws java.io.IOException;
method public java.util.List<android.location.Address> getFromLocationName(String, int, double, double, double, double) throws java.io.IOException;
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 public static boolean isPresent();
}
public static interface Geocoder.GeocodeListener {
method public default void onError(@Nullable String);
method public void onGeocode(@NonNull java.util.List<android.location.Address>);
}
public final class GnssAntennaInfo implements android.os.Parcelable {
method public int describeContents();
method @FloatRange(from=0.0f) public double getCarrierFrequencyMHz();

View File

@@ -16,8 +16,11 @@
package android.location;
import android.annotation.FloatRange;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -27,24 +30,21 @@ import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* A class for handling geocoding and reverse geocoding. Geocoding is
* the process of transforming a street address or other description
* of a location into a (latitude, longitude) coordinate. Reverse
* geocoding is the process of transforming a (latitude, longitude)
* coordinate into a (partial) address. The amount of detail in a
* reverse geocoded location description may vary, for example one
* might contain the full street address of the closest building, while
* another might contain only a city name and postal code.
* A class for handling geocoding and reverse geocoding. Geocoding is the process of transforming a
* street address or other description of a location into a (latitude, longitude) coordinate.
* Reverse geocoding is the process of transforming a (latitude, longitude) coordinate into a
* (partial) address. The amount of detail in a reverse geocoded location description may vary, for
* 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.
* 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 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,20 +52,26 @@ import java.util.concurrent.TimeUnit;
*/
public final class Geocoder {
/** A listener for asynchronous geocoding results. */
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. */
default void onError(@Nullable String errorMessage) {}
}
private static final long TIMEOUT_MS = 60000;
private final GeocoderParams mParams;
private final ILocationManager mService;
/**
* Returns true if the Geocoder methods getFromLocation and
* getFromLocationName are implemented. Lack of network
* connectivity may still cause these methods to return null or
* empty lists.
* 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.
*/
public static boolean isPresent() {
IBinder b = ServiceManager.getService(Context.LOCATION_SERVICE);
ILocationManager lm = ILocationManager.Stub.asInterface(b);
ILocationManager lm = Objects.requireNonNull(ILocationManager.Stub.asInterface(
ServiceManager.getService(Context.LOCATION_SERVICE)));
try {
return lm.geocoderIsPresent();
} catch (RemoteException e) {
@@ -74,40 +80,35 @@ public final class Geocoder {
}
/**
* Constructs a Geocoder whose responses will be localized for the
* given Locale.
*
* @param context the Context of the calling Activity
* @param locale the desired Locale for the query results
*
* @throws NullPointerException if Locale is null
* Constructs a Geocoder localized for the default locale.
*/
public Geocoder(Context context, Locale locale) {
public Geocoder(@NonNull Context context) {
this(context, Locale.getDefault());
}
/**
* Constructs a Geocoder localized for the given locale.
*/
public Geocoder(@NonNull Context context, @NonNull Locale locale) {
mParams = new GeocoderParams(context, locale);
mService = ILocationManager.Stub.asInterface(
ServiceManager.getService(Context.LOCATION_SERVICE));
}
/**
* Constructs a Geocoder whose responses will be localized for the
* default system Locale.
*
* @param context the Context of the calling Activity
*/
public Geocoder(Context context) {
this(context, Locale.getDefault());
}
/**
* Returns an array of Addresses that attempt to describe the area immediately surrounding the
* given latitude and longitude. The returned addresses should be localized for the locale
* provided to this class's constructor. Results may be obtained by means of a network lookup
* and this method may take some time to return, and so should not be called on the main thread.
* provided to this class's constructor.
*
* <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on
* <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.
* purposes.</p>
*
* <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>
*
* @param latitude the latitude a point for the search
* @param longitude the longitude a point for the search
@@ -116,22 +117,51 @@ public final class Geocoder {
* @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 is
* less than -90 or greater than 90
* @throws IllegalArgumentException if longitude is
* less than -180 or greater than 180
* @throws IOException if the network is unavailable or any other
* I/O problem occurs
* @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.
*/
public List<Address> getFromLocation(double latitude, double longitude, int maxResults)
@Deprecated
public @Nullable List<Address> getFromLocation(
@FloatRange(from = -90D, to = 90D) double latitude,
@FloatRange(from = -180D, to = 180D)double longitude,
@IntRange int maxResults)
throws IOException {
SynchronousGeocoder listener = new SynchronousGeocoder();
getFromLocation(latitude, longitude, maxResults, listener);
return listener.getResults();
}
/**
* Provides an array of Addresses that attempt to describe the area immediately surrounding the
* given latitude and longitude. The returned addresses should be localized for the locale
* provided to this class's constructor.
*
* <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>
*
* @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,
@NonNull GeocodeListener listener) {
Preconditions.checkArgumentInRange(latitude, -90.0, 90.0, "latitude");
Preconditions.checkArgumentInRange(longitude, -180.0, 180.0, "longitude");
try {
GeocodeListener listener = new GeocodeListener();
mService.getFromLocation(latitude, longitude, maxResults, mParams, listener);
return listener.getResults();
mService.getFromLocation(latitude, longitude, maxResults, mParams,
new GeocoderImpl(listener));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -141,14 +171,17 @@ public final class Geocoder {
* Returns an array of Addresses that attempt to describe the named location, which may be a
* place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain
* 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. Results may be obtained by
* means of a network lookup and this method may take some time to return, and so should not be
* called on the main thread.
* localized for the locale provided to this class's constructor.
*
* <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.
* purposes.</p>
*
* <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>
*
* @param locationName a user-supplied description of a location
* @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended
@@ -157,20 +190,47 @@ public final class Geocoder {
* found or there is no backend service available.
*
* @throws IllegalArgumentException if locationName is null
* @throws IOException if the network is unavailable or any other
* I/O problem occurs
* @throws IOException if there is a failure
*
* @deprecated Use {@link #getFromLocationName(String, int, GeocodeListener)} instead to avoid
* blocking a thread waiting for results.
*/
public List<Address> getFromLocationName(String locationName, int maxResults) throws IOException {
@Deprecated
public @Nullable List<Address> getFromLocationName(
@NonNull String locationName,
@IntRange int maxResults) throws IOException {
return getFromLocationName(locationName, maxResults, 0, 0, 0, 0);
}
/**
* Provides an array of Addresses that attempt to describe the named location, which may be a
* place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain
* 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 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>
*
* @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,
@NonNull GeocodeListener listener) {
getFromLocationName(locationName, maxResults, 0, 0, 0, 0, listener);
}
/**
* Returns an array of Addresses that attempt to describe the named location, which may be a
* place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain
* 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. Results may be obtained by
* means of a network lookup and this method may take some time to return, and so should not be
* called on the main thread.
* 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
* longitude of the lower left point and upper right point of the box.
@@ -178,29 +238,78 @@ public final class Geocoder {
* <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.
* purposes.</p>
*
* @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
* <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>
*
* @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.
*
* @throws IllegalArgumentException if locationName is null
* @throws IllegalArgumentException if any latitude is
* less than -90 or greater than 90
* @throws IllegalArgumentException if any longitude is
* less than -180 or greater than 180
* @throws IOException if the network is unavailable or any other
* I/O problem occurs
* @throws IllegalArgumentException if any latitude or longitude is invalid
* @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.
*/
public List<Address> getFromLocationName(String locationName, int maxResults,
double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude,
double upperRightLongitude) throws IOException {
@Deprecated
public @Nullable List<Address> getFromLocationName(
@NonNull String locationName,
@IntRange 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 {
SynchronousGeocoder listener = new SynchronousGeocoder();
getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude,
upperRightLatitude, upperRightLongitude, listener);
return listener.getResults();
}
/**
* Returns an array of Addresses that attempt to describe the named location, which may be a
* place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain
* 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
* 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>
*
* @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
*
* @throws IllegalArgumentException if locationName is null
* @throws IllegalArgumentException if any latitude or longitude is invalid
*/
public void getFromLocationName(
@NonNull String locationName,
@IntRange 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");
@@ -209,27 +318,59 @@ public final class Geocoder {
"upperRightLongitude");
try {
GeocodeListener listener = new GeocodeListener();
mService.getFromLocationName(locationName, lowerLeftLatitude, lowerLeftLongitude,
upperRightLatitude, upperRightLongitude, maxResults, mParams, listener);
return listener.getResults();
upperRightLatitude, upperRightLongitude, maxResults, mParams,
new GeocoderImpl(listener));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private static class GeocodeListener extends IGeocodeListener.Stub {
private static class GeocoderImpl extends IGeocodeListener.Stub {
private GeocodeListener mListener;
GeocoderImpl(GeocodeListener listener) {
mListener = Objects.requireNonNull(listener);
}
@Override
public void onResults(String error, List<Address> addresses) throws RemoteException {
if (mListener == null) {
return;
}
GeocodeListener listener = mListener;
mListener = null;
if (error != null) {
listener.onError(error);
} else {
if (addresses == null) {
addresses = Collections.emptyList();
}
listener.onGeocode(addresses);
}
}
}
private static class SynchronousGeocoder implements GeocodeListener {
private final CountDownLatch mLatch = new CountDownLatch(1);
private String mError = null;
private List<Address> mResults = Collections.emptyList();
GeocodeListener() {}
SynchronousGeocoder() {}
@Override
public void onResults(String error, List<Address> results) {
mError = error;
mResults = results;
public void onGeocode(List<Address> addresses) {
mResults = addresses;
mLatch.countDown();
}
@Override
public void onError(String errorMessage) {
mError = errorMessage;
mLatch.countDown();
}

View File

@@ -95,11 +95,11 @@ public class GeocoderParams implements Parcelable {
new Parcelable.Creator<GeocoderParams>() {
public GeocoderParams createFromParcel(Parcel in) {
int uid = in.readInt();
String packageName = in.readString();
String attributionTag = in.readString();
String language = in.readString();
String country = in.readString();
String variant = in.readString();
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));
@@ -116,10 +116,10 @@ public class GeocoderParams implements Parcelable {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mUid);
parcel.writeString(mPackageName);
parcel.writeString(mAttributionTag);
parcel.writeString(mLocale.getLanguage());
parcel.writeString(mLocale.getCountry());
parcel.writeString(mLocale.getVariant());
parcel.writeString8(mPackageName);
parcel.writeString8(mAttributionTag);
parcel.writeString8(mLocale.getLanguage());
parcel.writeString8(mLocale.getCountry());
parcel.writeString8(mLocale.getVariant());
}
}