1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.location; 18 19 import android.annotation.FloatRange; 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.location.provider.ForwardGeocodeRequest; 25 import android.location.provider.IGeocodeCallback; 26 import android.location.provider.ReverseGeocodeRequest; 27 import android.os.Process; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 31 import java.io.IOException; 32 import java.util.Collections; 33 import java.util.List; 34 import java.util.Locale; 35 import java.util.Objects; 36 import java.util.concurrent.CountDownLatch; 37 import java.util.concurrent.TimeUnit; 38 import java.util.concurrent.TimeoutException; 39 40 /** 41 * A class for handling geocoding and reverse geocoding. Geocoding is the process of transforming a 42 * street address or other description of a location into a (latitude, longitude) coordinate. 43 * Reverse geocoding is the process of transforming a (latitude, longitude) coordinate into a 44 * (partial) address. The amount of detail in a reverse geocoded location description may vary, for 45 * example one might contain the full street address of the closest building, while another might 46 * contain only a city name and postal code. 47 * 48 * <p>Use the isPresent() method to determine whether a Geocoder implementation exists on the 49 * current device. If no implementation is present, any attempt to geocode will result in an error. 50 * 51 * <p>Geocoder implementations are only required to make a best effort to return results in the 52 * chosen locale. Note that geocoder implementations may return results in other locales if they 53 * have no information available for the chosen locale. 54 * 55 * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on 56 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful or 57 * correct. Do not use this API for any safety-critical or regulatory compliance purpose. 58 */ 59 public final class Geocoder { 60 61 /** 62 * A listener for asynchronous geocoding results. Only one of the methods will ever be invoked 63 * per geocoding attempt. There are no guarantees on how long it will take for a method to be 64 * invoked, nor any guarantees on the format or availability of error information. 65 */ 66 public interface GeocodeListener { 67 /** Invoked when geocoding completes successfully. May return an empty list. */ onGeocode(@onNull List<Address> addresses)68 void onGeocode(@NonNull List<Address> addresses); 69 70 /** Invoked when geocoding fails, with an optional error message. */ onError(@ullable String errorMessage)71 default void onError(@Nullable String errorMessage) {} 72 } 73 74 private static final long TIMEOUT_MS = 15000; 75 76 private final Context mContext; 77 private final Locale mLocale; 78 private final ILocationManager mService; 79 80 /** 81 * Returns true if there is a geocoder implementation present on the device that may return 82 * results. If true, there is still no guarantee that any individual geocoding attempt will 83 * succeed. 84 */ isPresent()85 public static boolean isPresent() { 86 ILocationManager lm = ILocationManager.Stub.asInterface( 87 ServiceManager.getService(Context.LOCATION_SERVICE)); 88 if (lm == null) { 89 return false; 90 } 91 try { 92 return lm.isGeocodeAvailable(); 93 } catch (RemoteException e) { 94 throw e.rethrowFromSystemServer(); 95 } 96 } 97 98 /** Constructs a Geocoder localized for {@link Locale#getDefault()}. */ Geocoder(@onNull Context context)99 public Geocoder(@NonNull Context context) { 100 this(context, Locale.getDefault()); 101 } 102 103 /** 104 * Constructs a Geocoder localized for the given locale. Note that geocoder implementations will 105 * only make a best effort to return results in the given locale, and there is no guarantee that 106 * returned results will be in the specific locale. 107 */ Geocoder(@onNull Context context, @NonNull Locale locale)108 public Geocoder(@NonNull Context context, @NonNull Locale locale) { 109 mContext = Objects.requireNonNull(context); 110 mLocale = Objects.requireNonNull(locale); 111 mService = ILocationManager.Stub.asInterface( 112 ServiceManager.getService(Context.LOCATION_SERVICE)); 113 } 114 115 /** 116 * Returns an array of Addresses that attempt to describe the area immediately surrounding the 117 * given latitude and longitude. The returned addresses should be localized for the locale 118 * provided to this class's constructor. 119 * 120 * <p class="warning"><strong>Warning:</strong> Geocoding services may provide no guarantees on 121 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 122 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 123 * purposes. 124 * 125 * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for 126 * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this 127 * API. If that is not possible, this should be run on a background thread to avoid blocking 128 * other operations. 129 * 130 * @param latitude the latitude a point for the search 131 * @param longitude the longitude a point for the search 132 * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended 133 * @return a list of Address objects. Returns null or empty list if no matches were found or 134 * there is no backend service available. 135 * @throws IllegalArgumentException if latitude or longitude is invalid 136 * @throws IOException if there is a failure 137 * @deprecated Use {@link #getFromLocation(double, double, int, GeocodeListener)} instead to 138 * avoid blocking a thread waiting for results. 139 */ 140 @Deprecated getFromLocation( @loatRangefrom = -90D, to = 90D) double latitude, @FloatRange(from = -180D, to = 180D) double longitude, @IntRange(from = 1) int maxResults)141 public @Nullable List<Address> getFromLocation( 142 @FloatRange(from = -90D, to = 90D) double latitude, 143 @FloatRange(from = -180D, to = 180D) double longitude, 144 @IntRange(from = 1) int maxResults) 145 throws IOException { 146 SynchronousGeocoder listener = new SynchronousGeocoder(); 147 getFromLocation(latitude, longitude, maxResults, listener); 148 return listener.getResults(); 149 } 150 151 /** 152 * Provides an array of Addresses that attempt to describe the area immediately surrounding the 153 * given latitude and longitude. The returned addresses should be localized for the locale 154 * provided to this class's constructor. 155 * 156 * <p class="warning"><strong>Warning:</strong> Geocoding services may provide no guarantees on 157 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 158 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 159 * purposes. 160 * 161 * @param latitude the latitude a point for the search 162 * @param longitude the longitude a point for the search 163 * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended 164 * @param listener a listener for receiving results 165 * @throws IllegalArgumentException if latitude or longitude is invalid 166 */ getFromLocation( @loatRangefrom = -90D, to = 90D) double latitude, @FloatRange(from = -180D, to = 180D) double longitude, @IntRange(from = 1) int maxResults, @NonNull GeocodeListener listener)167 public void getFromLocation( 168 @FloatRange(from = -90D, to = 90D) double latitude, 169 @FloatRange(from = -180D, to = 180D) double longitude, 170 @IntRange(from = 1) int maxResults, 171 @NonNull GeocodeListener listener) { 172 ReverseGeocodeRequest.Builder b = 173 new ReverseGeocodeRequest.Builder( 174 latitude, 175 longitude, 176 maxResults, 177 mLocale, 178 Process.myUid(), 179 mContext.getPackageName()); 180 if (mContext.getAttributionTag() != null) { 181 b.setCallingAttributionTag(mContext.getAttributionTag()); 182 } 183 try { 184 mService.reverseGeocode(b.build(), new GeocodeCallbackImpl(listener)); 185 } catch (RemoteException e) { 186 throw e.rethrowFromSystemServer(); 187 } 188 } 189 190 /** 191 * Returns an array of Addresses that attempt to describe the named location, which may be a 192 * place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain 193 * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be 194 * localized for the locale provided to this class's constructor. 195 * 196 * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on 197 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 198 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 199 * purposes. 200 * 201 * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for 202 * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this 203 * API. If that is not possible, this should be run on a background thread to avoid blocking 204 * other operations. 205 * 206 * @param locationName a user-supplied description of a location 207 * @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended 208 * @return a list of Address objects. Returns null or empty list if no matches were found or 209 * there is no backend service available. 210 * @throws IllegalArgumentException if locationName is null 211 * @throws IOException if there is a failure 212 * @deprecated Use {@link #getFromLocationName(String, int, GeocodeListener)} instead to avoid 213 * blocking a thread waiting for results. 214 */ 215 @Deprecated getFromLocationName( @onNull String locationName, @IntRange(from = 1) int maxResults)216 public @Nullable List<Address> getFromLocationName( 217 @NonNull String locationName, @IntRange(from = 1) int maxResults) throws IOException { 218 return getFromLocationName(locationName, maxResults, 0, 0, 0, 0); 219 } 220 221 /** 222 * Provides an array of Addresses that attempt to describe the named location, which may be a 223 * place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain 224 * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be 225 * localized for the locale provided to this class's constructor. 226 * 227 * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on 228 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 229 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 230 * purposes. 231 * 232 * @param locationName a user-supplied description of a location 233 * @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended 234 * @param listener a listener for receiving results 235 * @throws IllegalArgumentException if locationName is null 236 */ getFromLocationName( @onNull String locationName, @IntRange(from = 1) int maxResults, @NonNull GeocodeListener listener)237 public void getFromLocationName( 238 @NonNull String locationName, 239 @IntRange(from = 1) int maxResults, 240 @NonNull GeocodeListener listener) { 241 getFromLocationName(locationName, maxResults, 0, 0, 0, 0, listener); 242 } 243 244 /** 245 * Returns an array of Addresses that attempt to describe the named location, which may be a 246 * place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain 247 * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be 248 * localized for the locale provided to this class's constructor. 249 * 250 * <p>You may specify a bounding box for the search results by including the latitude and 251 * longitude of the lower left point and upper right point of the box. 252 * 253 * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on 254 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 255 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 256 * purposes. 257 * 258 * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for 259 * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this 260 * API. If that is not possible, this should be run on a background thread to avoid blocking 261 * other operations. 262 * 263 * @param locationName a user-supplied description of a location 264 * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended 265 * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box 266 * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box 267 * @param upperRightLatitude the latitude of the upper right corner of the bounding box 268 * @param upperRightLongitude the longitude of the upper right corner of the bounding box 269 * @return a list of Address objects. Returns null or empty list if no matches were found or 270 * there is no backend service available. 271 * @throws IllegalArgumentException if locationName is null 272 * @throws IllegalArgumentException if any latitude or longitude is invalid 273 * @throws IOException if there is a failure 274 * @deprecated Use {@link #getFromLocationName(String, int, double, double, double, double, 275 * GeocodeListener)} instead to avoid blocking a thread waiting for results. 276 */ 277 @Deprecated getFromLocationName( @onNull String locationName, @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)278 public @Nullable List<Address> getFromLocationName( 279 @NonNull String locationName, 280 @IntRange(from = 1) int maxResults, 281 @FloatRange(from = -90D, to = 90D) double lowerLeftLatitude, 282 @FloatRange(from = -180D, to = 180D) double lowerLeftLongitude, 283 @FloatRange(from = -90D, to = 90D) double upperRightLatitude, 284 @FloatRange(from = -180D, to = 180D) double upperRightLongitude) 285 throws IOException { 286 SynchronousGeocoder listener = new SynchronousGeocoder(); 287 getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, 288 upperRightLatitude, upperRightLongitude, listener); 289 return listener.getResults(); 290 } 291 292 /** 293 * Returns an array of Addresses that attempt to describe the named location, which may be a 294 * place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain 295 * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be 296 * localized for the locale provided to this class's constructor. 297 * 298 * <p>You may specify a bounding box for the search results by including the latitude and 299 * longitude of the lower left point and upper right point of the box. 300 * 301 * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on 302 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 303 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 304 * purposes. 305 * 306 * @param locationName a user-supplied description of a location 307 * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended 308 * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box 309 * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box 310 * @param upperRightLatitude the latitude of the upper right corner of the bounding box 311 * @param upperRightLongitude the longitude of the upper right corner of the bounding box 312 * @param listener a listener for receiving results 313 * @throws IllegalArgumentException if locationName is null 314 * @throws IllegalArgumentException if any latitude or longitude is invalid 315 */ getFromLocationName( @onNull String locationName, @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)316 public void getFromLocationName( 317 @NonNull String locationName, 318 @IntRange(from = 1) int maxResults, 319 @FloatRange(from = -90D, to = 90D) double lowerLeftLatitude, 320 @FloatRange(from = -180D, to = 180D) double lowerLeftLongitude, 321 @FloatRange(from = -90D, to = 90D) double upperRightLatitude, 322 @FloatRange(from = -180D, to = 180D) double upperRightLongitude, 323 @NonNull GeocodeListener listener) { 324 ForwardGeocodeRequest.Builder b = 325 new ForwardGeocodeRequest.Builder( 326 locationName, 327 lowerLeftLatitude, 328 lowerLeftLongitude, 329 upperRightLatitude, 330 upperRightLongitude, 331 maxResults, 332 mLocale, 333 Process.myUid(), 334 mContext.getPackageName()); 335 if (mContext.getAttributionTag() != null) { 336 b.setCallingAttributionTag(mContext.getAttributionTag()); 337 } 338 try { 339 mService.forwardGeocode(b.build(), new GeocodeCallbackImpl(listener)); 340 } catch (RemoteException e) { 341 throw e.rethrowFromSystemServer(); 342 } 343 } 344 345 private static class GeocodeCallbackImpl extends IGeocodeCallback.Stub { 346 347 @Nullable private GeocodeListener mListener; 348 GeocodeCallbackImpl(GeocodeListener listener)349 GeocodeCallbackImpl(GeocodeListener listener) { 350 mListener = Objects.requireNonNull(listener); 351 } 352 353 @Override onError(@ullable String error)354 public void onError(@Nullable String error) { 355 if (mListener == null) { 356 return; 357 } 358 359 mListener.onError(error); 360 mListener = null; 361 } 362 363 @Override onResults(List<Address> addresses)364 public void onResults(List<Address> addresses) { 365 if (mListener == null) { 366 return; 367 } 368 369 mListener.onGeocode(addresses); 370 mListener = null; 371 } 372 } 373 374 private static class SynchronousGeocoder implements GeocodeListener { 375 private final CountDownLatch mLatch = new CountDownLatch(1); 376 377 private String mError = null; 378 private List<Address> mResults = Collections.emptyList(); 379 SynchronousGeocoder()380 SynchronousGeocoder() {} 381 382 @Override onGeocode(@onNull List<Address> addresses)383 public void onGeocode(@NonNull List<Address> addresses) { 384 mResults = addresses; 385 mLatch.countDown(); 386 } 387 388 @Override onError(@ullable String error)389 public void onError(@Nullable String error) { 390 mError = error; 391 mLatch.countDown(); 392 } 393 getResults()394 public List<Address> getResults() throws IOException { 395 try { 396 if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 397 throw new IOException(new TimeoutException()); 398 } 399 } catch (InterruptedException e) { 400 Thread.currentThread().interrupt(); 401 } 402 403 if (mError != null) { 404 throw new IOException(mError); 405 } else { 406 return mResults; 407 } 408 } 409 } 410 } 411