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 = Objects.requireNonNull(ILocationManager.Stub.asInterface( 87 ServiceManager.getService(Context.LOCATION_SERVICE))); 88 try { 89 return lm.isGeocodeAvailable(); 90 } catch (RemoteException e) { 91 throw e.rethrowFromSystemServer(); 92 } 93 } 94 95 /** Constructs a Geocoder localized for {@link Locale#getDefault()}. */ Geocoder(@onNull Context context)96 public Geocoder(@NonNull Context context) { 97 this(context, Locale.getDefault()); 98 } 99 100 /** 101 * Constructs a Geocoder localized for the given locale. Note that geocoder implementations will 102 * only make a best effort to return results in the given locale, and there is no guarantee that 103 * returned results will be in the specific locale. 104 */ Geocoder(@onNull Context context, @NonNull Locale locale)105 public Geocoder(@NonNull Context context, @NonNull Locale locale) { 106 mContext = Objects.requireNonNull(context); 107 mLocale = Objects.requireNonNull(locale); 108 mService = ILocationManager.Stub.asInterface( 109 ServiceManager.getService(Context.LOCATION_SERVICE)); 110 } 111 112 /** 113 * Returns an array of Addresses that attempt to describe the area immediately surrounding the 114 * given latitude and longitude. The returned addresses should be localized for the locale 115 * provided to this class's constructor. 116 * 117 * <p class="warning"><strong>Warning:</strong> Geocoding services may provide no guarantees on 118 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 119 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 120 * purposes. 121 * 122 * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for 123 * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this 124 * API. If that is not possible, this should be run on a background thread to avoid blocking 125 * other operations. 126 * 127 * @param latitude the latitude a point for the search 128 * @param longitude the longitude a point for the search 129 * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended 130 * @return a list of Address objects. Returns null or empty list if no matches were found or 131 * there is no backend service available. 132 * @throws IllegalArgumentException if latitude or longitude is invalid 133 * @throws IOException if there is a failure 134 * @deprecated Use {@link #getFromLocation(double, double, int, GeocodeListener)} instead to 135 * avoid blocking a thread waiting for results. 136 */ 137 @Deprecated getFromLocation( @loatRangefrom = -90D, to = 90D) double latitude, @FloatRange(from = -180D, to = 180D) double longitude, @IntRange(from = 1) int maxResults)138 public @Nullable List<Address> getFromLocation( 139 @FloatRange(from = -90D, to = 90D) double latitude, 140 @FloatRange(from = -180D, to = 180D) double longitude, 141 @IntRange(from = 1) int maxResults) 142 throws IOException { 143 SynchronousGeocoder listener = new SynchronousGeocoder(); 144 getFromLocation(latitude, longitude, maxResults, listener); 145 return listener.getResults(); 146 } 147 148 /** 149 * Provides an array of Addresses that attempt to describe the area immediately surrounding the 150 * given latitude and longitude. The returned addresses should be localized for the locale 151 * provided to this class's constructor. 152 * 153 * <p class="warning"><strong>Warning:</strong> Geocoding services may provide no guarantees on 154 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 155 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 156 * purposes. 157 * 158 * @param latitude the latitude a point for the search 159 * @param longitude the longitude a point for the search 160 * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended 161 * @param listener a listener for receiving results 162 * @throws IllegalArgumentException if latitude or longitude is invalid 163 */ getFromLocation( @loatRangefrom = -90D, to = 90D) double latitude, @FloatRange(from = -180D, to = 180D) double longitude, @IntRange(from = 1) int maxResults, @NonNull GeocodeListener listener)164 public void getFromLocation( 165 @FloatRange(from = -90D, to = 90D) double latitude, 166 @FloatRange(from = -180D, to = 180D) double longitude, 167 @IntRange(from = 1) int maxResults, 168 @NonNull GeocodeListener listener) { 169 ReverseGeocodeRequest.Builder b = 170 new ReverseGeocodeRequest.Builder( 171 latitude, 172 longitude, 173 maxResults, 174 mLocale, 175 Process.myUid(), 176 mContext.getPackageName()); 177 if (mContext.getAttributionTag() != null) { 178 b.setCallingAttributionTag(mContext.getAttributionTag()); 179 } 180 try { 181 mService.reverseGeocode(b.build(), new GeocodeCallbackImpl(listener)); 182 } catch (RemoteException e) { 183 throw e.rethrowFromSystemServer(); 184 } 185 } 186 187 /** 188 * Returns an array of Addresses that attempt to describe the named location, which may be a 189 * place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain 190 * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be 191 * localized for the locale provided to this class's constructor. 192 * 193 * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on 194 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 195 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 196 * purposes. 197 * 198 * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for 199 * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this 200 * API. If that is not possible, this should be run on a background thread to avoid blocking 201 * other operations. 202 * 203 * @param locationName a user-supplied description of a location 204 * @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended 205 * @return a list of Address objects. Returns null or empty list if no matches were found or 206 * there is no backend service available. 207 * @throws IllegalArgumentException if locationName is null 208 * @throws IOException if there is a failure 209 * @deprecated Use {@link #getFromLocationName(String, int, GeocodeListener)} instead to avoid 210 * blocking a thread waiting for results. 211 */ 212 @Deprecated getFromLocationName( @onNull String locationName, @IntRange(from = 1) int maxResults)213 public @Nullable List<Address> getFromLocationName( 214 @NonNull String locationName, @IntRange(from = 1) int maxResults) throws IOException { 215 return getFromLocationName(locationName, maxResults, 0, 0, 0, 0); 216 } 217 218 /** 219 * Provides an array of Addresses that attempt to describe the named location, which may be a 220 * place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain 221 * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be 222 * localized for the locale provided to this class's constructor. 223 * 224 * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on 225 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 226 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 227 * purposes. 228 * 229 * @param locationName a user-supplied description of a location 230 * @param maxResults max number of results to return. Smaller numbers (1 to 5) are recommended 231 * @param listener a listener for receiving results 232 * @throws IllegalArgumentException if locationName is null 233 */ getFromLocationName( @onNull String locationName, @IntRange(from = 1) int maxResults, @NonNull GeocodeListener listener)234 public void getFromLocationName( 235 @NonNull String locationName, 236 @IntRange(from = 1) int maxResults, 237 @NonNull GeocodeListener listener) { 238 getFromLocationName(locationName, maxResults, 0, 0, 0, 0, listener); 239 } 240 241 /** 242 * Returns an array of Addresses that attempt to describe the named location, which may be a 243 * place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain 244 * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be 245 * localized for the locale provided to this class's constructor. 246 * 247 * <p>You may specify a bounding box for the search results by including the latitude and 248 * longitude of the lower left point and upper right point of the box. 249 * 250 * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on 251 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 252 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 253 * purposes. 254 * 255 * <p class="warning"><strong>Warning:</strong> This API may hit the network, and may block for 256 * excessive amounts of time. It's strongly encouraged to use the asynchronous version of this 257 * API. If that is not possible, this should be run on a background thread to avoid blocking 258 * other operations. 259 * 260 * @param locationName a user-supplied description of a location 261 * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended 262 * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box 263 * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box 264 * @param upperRightLatitude the latitude of the upper right corner of the bounding box 265 * @param upperRightLongitude the longitude of the upper right corner of the bounding box 266 * @return a list of Address objects. Returns null or empty list if no matches were found or 267 * there is no backend service available. 268 * @throws IllegalArgumentException if locationName is null 269 * @throws IllegalArgumentException if any latitude or longitude is invalid 270 * @throws IOException if there is a failure 271 * @deprecated Use {@link #getFromLocationName(String, int, double, double, double, double, 272 * GeocodeListener)} instead to avoid blocking a thread waiting for results. 273 */ 274 @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)275 public @Nullable List<Address> getFromLocationName( 276 @NonNull String locationName, 277 @IntRange(from = 1) int maxResults, 278 @FloatRange(from = -90D, to = 90D) double lowerLeftLatitude, 279 @FloatRange(from = -180D, to = 180D) double lowerLeftLongitude, 280 @FloatRange(from = -90D, to = 90D) double upperRightLatitude, 281 @FloatRange(from = -180D, to = 180D) double upperRightLongitude) 282 throws IOException { 283 SynchronousGeocoder listener = new SynchronousGeocoder(); 284 getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, 285 upperRightLatitude, upperRightLongitude, listener); 286 return listener.getResults(); 287 } 288 289 /** 290 * Returns an array of Addresses that attempt to describe the named location, which may be a 291 * place name such as "Dalvik, Iceland", an address such as "1600 Amphitheatre Parkway, Mountain 292 * View, CA", an airport code such as "SFO", and so forth. The returned addresses should be 293 * localized for the locale provided to this class's constructor. 294 * 295 * <p>You may specify a bounding box for the search results by including the latitude and 296 * longitude of the lower left point and upper right point of the box. 297 * 298 * <p class="note"><strong>Warning:</strong> Geocoding services may provide no guarantees on 299 * availability or accuracy. Results are a best guess, and are not guaranteed to be meaningful 300 * or correct. Do <b>NOT</b> use this API for any safety-critical or regulatory compliance 301 * purposes. 302 * 303 * @param locationName a user-supplied description of a location 304 * @param maxResults max number of addresses to return. Smaller numbers (1 to 5) are recommended 305 * @param lowerLeftLatitude the latitude of the lower left corner of the bounding box 306 * @param lowerLeftLongitude the longitude of the lower left corner of the bounding box 307 * @param upperRightLatitude the latitude of the upper right corner of the bounding box 308 * @param upperRightLongitude the longitude of the upper right corner of the bounding box 309 * @param listener a listener for receiving results 310 * @throws IllegalArgumentException if locationName is null 311 * @throws IllegalArgumentException if any latitude or longitude is invalid 312 */ 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)313 public void getFromLocationName( 314 @NonNull String locationName, 315 @IntRange(from = 1) int maxResults, 316 @FloatRange(from = -90D, to = 90D) double lowerLeftLatitude, 317 @FloatRange(from = -180D, to = 180D) double lowerLeftLongitude, 318 @FloatRange(from = -90D, to = 90D) double upperRightLatitude, 319 @FloatRange(from = -180D, to = 180D) double upperRightLongitude, 320 @NonNull GeocodeListener listener) { 321 ForwardGeocodeRequest.Builder b = 322 new ForwardGeocodeRequest.Builder( 323 locationName, 324 lowerLeftLatitude, 325 lowerLeftLongitude, 326 upperRightLatitude, 327 upperRightLongitude, 328 maxResults, 329 mLocale, 330 Process.myUid(), 331 mContext.getPackageName()); 332 if (mContext.getAttributionTag() != null) { 333 b.setCallingAttributionTag(mContext.getAttributionTag()); 334 } 335 try { 336 mService.forwardGeocode(b.build(), new GeocodeCallbackImpl(listener)); 337 } catch (RemoteException e) { 338 throw e.rethrowFromSystemServer(); 339 } 340 } 341 342 private static class GeocodeCallbackImpl extends IGeocodeCallback.Stub { 343 344 @Nullable private GeocodeListener mListener; 345 GeocodeCallbackImpl(GeocodeListener listener)346 GeocodeCallbackImpl(GeocodeListener listener) { 347 mListener = Objects.requireNonNull(listener); 348 } 349 350 @Override onError(@ullable String error)351 public void onError(@Nullable String error) { 352 if (mListener == null) { 353 return; 354 } 355 356 mListener.onError(error); 357 mListener = null; 358 } 359 360 @Override onResults(List<Address> addresses)361 public void onResults(List<Address> addresses) { 362 if (mListener == null) { 363 return; 364 } 365 366 mListener.onGeocode(addresses); 367 mListener = null; 368 } 369 } 370 371 private static class SynchronousGeocoder implements GeocodeListener { 372 private final CountDownLatch mLatch = new CountDownLatch(1); 373 374 private String mError = null; 375 private List<Address> mResults = Collections.emptyList(); 376 SynchronousGeocoder()377 SynchronousGeocoder() {} 378 379 @Override onGeocode(@onNull List<Address> addresses)380 public void onGeocode(@NonNull List<Address> addresses) { 381 mResults = addresses; 382 mLatch.countDown(); 383 } 384 385 @Override onError(@ullable String error)386 public void onError(@Nullable String error) { 387 mError = error; 388 mLatch.countDown(); 389 } 390 getResults()391 public List<Address> getResults() throws IOException { 392 try { 393 if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 394 throw new IOException(new TimeoutException()); 395 } 396 } catch (InterruptedException e) { 397 Thread.currentThread().interrupt(); 398 } 399 400 if (mError != null) { 401 throw new IOException(mError); 402 } else { 403 return mResults; 404 } 405 } 406 } 407 } 408