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