• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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