• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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 com.cooliris.media;
18 
19 import java.io.BufferedInputStream;
20 import java.io.BufferedOutputStream;
21 import java.io.ByteArrayInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.DataInputStream;
24 import java.io.DataOutputStream;
25 import java.io.IOException;
26 import java.util.List;
27 import java.util.Locale;
28 
29 import android.content.Context;
30 import android.location.Address;
31 import android.location.Criteria;
32 import android.location.Geocoder;
33 import android.location.Location;
34 import android.location.LocationManager;
35 import android.os.Process;
36 
37 public final class ReverseGeocoder extends Thread {
38     private static final int MAX_COUNTRY_NAME_LENGTH = 8;
39     // If two points are within 20 miles of each other, use
40     // "Around Palo Alto, CA" or "Around Mountain View, CA".
41     // instead of directly jumping to the next level and saying
42     // "California, US".
43     private static final int MAX_LOCALITY_MILE_RANGE = 20;
44     private static final Deque<MediaSet> sQueue = new Deque<MediaSet>();
45     private static final DiskCache sGeoCache = new DiskCache("geocoder-cache");
46     private static final String TAG = "ReverseGeocoder";
47     private static Criteria LOCATION_CRITERIA = new Criteria();
48     private static Address sCurrentAddress; // last known address
49 
50     static {
51         LOCATION_CRITERIA.setAccuracy(Criteria.ACCURACY_COARSE);
52         LOCATION_CRITERIA.setPowerRequirement(Criteria.NO_REQUIREMENT);
53         LOCATION_CRITERIA.setBearingRequired(false);
54         LOCATION_CRITERIA.setSpeedRequired(false);
55         LOCATION_CRITERIA.setAltitudeRequired(false);
56     }
57 
58     private Geocoder mGeocoder;
59     private final Context mContext;
60 
ReverseGeocoder(Context context)61     public ReverseGeocoder(Context context) {
62         super(TAG);
63         mContext = context;
64         start();
65     }
66 
enqueue(MediaSet set)67     public void enqueue(MediaSet set) {
68         Deque<MediaSet> inQueue = sQueue;
69         synchronized (inQueue) {
70             inQueue.addFirst(set);
71             inQueue.notify();
72         }
73     }
74 
75     @Override
run()76     public void run() {
77         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
78         Deque<MediaSet> queue = sQueue;
79         mGeocoder = new Geocoder(mContext);
80         queue.clear();
81         try {
82             for (;;) {
83                 // Wait for the next request.
84                 MediaSet set;
85                 synchronized (queue) {
86                     while ((set = queue.pollFirst()) == null) {
87                         queue.wait();
88                     }
89                 }
90                 // Process the request.
91                 process(set);
92             }
93         } catch (InterruptedException e) {
94             // Terminate the thread.
95         }
96     }
97 
flushCache()98     public void flushCache() {
99         sGeoCache.flush();
100     }
101 
shutdown()102     public void shutdown() {
103         flushCache();
104         this.interrupt();
105     }
106 
process(final MediaSet set)107     private boolean process(final MediaSet set) {
108         if (!set.mLatLongDetermined) {
109             // No latitude, longitude information available.
110             set.mReverseGeocodedLocationComputed = true;
111             return false;
112         }
113         set.mReverseGeocodedLocation = computeMostGranularCommonLocation(set);
114         set.mReverseGeocodedLocationComputed = true;
115         return true;
116     }
117 
computeMostGranularCommonLocation(final MediaSet set)118     protected String computeMostGranularCommonLocation(final MediaSet set) {
119         // The overall min and max latitudes and longitudes of the set.
120         double setMinLatitude = set.mMinLatLatitude;
121         double setMinLongitude = set.mMinLatLongitude;
122         double setMaxLatitude = set.mMaxLatLatitude;
123         double setMaxLongitude = set.mMaxLatLongitude;
124         if (Math.abs(set.mMaxLatLatitude - set.mMinLatLatitude) < Math.abs(set.mMaxLonLongitude - set.mMinLonLongitude)) {
125             setMinLatitude = set.mMinLonLatitude;
126             setMinLongitude = set.mMinLonLongitude;
127             setMaxLatitude = set.mMaxLonLatitude;
128             setMaxLongitude = set.mMaxLonLongitude;
129         }
130         Address addr1 = lookupAddress(setMinLatitude, setMinLongitude);
131         Address addr2 = lookupAddress(setMaxLatitude, setMaxLongitude);
132         if (addr1 == null)
133             addr1 = addr2;
134         if (addr2 == null)
135             addr2 = addr1;
136         if (addr1 == null || addr2 == null) {
137             return null;
138         }
139 
140         // Get current location, we decide the granularity of the string based
141         // on this.
142         LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
143         Location location = null;
144         List<String> providers = locationManager.getAllProviders();
145         for (int i = 0; i < providers.size(); ++i) {
146             String provider = providers.get(i);
147             location = (provider != null) ? locationManager.getLastKnownLocation(provider) : null;
148             if (location != null)
149                 break;
150         }
151         String currentCity = "";
152         String currentAdminArea = "";
153         String currentCountry = Locale.getDefault().getCountry();
154         if (location != null) {
155             Address currentAddress = lookupAddress(location.getLatitude(), location.getLongitude());
156             if (currentAddress == null) {
157                 currentAddress = sCurrentAddress;
158             } else {
159                 sCurrentAddress = currentAddress;
160             }
161             if (currentAddress != null && currentAddress.getCountryCode() != null) {
162                 currentCity = checkNull(currentAddress.getLocality());
163                 currentCountry = checkNull(currentAddress.getCountryCode());
164                 currentAdminArea = checkNull(currentAddress.getAdminArea());
165             }
166         }
167 
168         String closestCommonLocation = null;
169         String addr1Locality = checkNull(addr1.getLocality());
170         String addr2Locality = checkNull(addr2.getLocality());
171         String addr1AdminArea = checkNull(addr1.getAdminArea());
172         String addr2AdminArea = checkNull(addr2.getAdminArea());
173         String addr1CountryCode = checkNull(addr1.getCountryCode());
174         String addr2CountryCode = checkNull(addr2.getCountryCode());
175 
176         if (currentCity.equals(addr1Locality) && currentCity.equals(addr2Locality)) {
177             String otherCity = currentCity;
178             if (currentCity.equals(addr1Locality)) {
179                 otherCity = addr2Locality;
180                 if (otherCity.length() == 0) {
181                     otherCity = addr2AdminArea;
182                     if (!currentCountry.equals(addr2CountryCode)) {
183                         otherCity += " " + addr2CountryCode;
184                     }
185                 }
186                 addr2Locality = addr1Locality;
187                 addr2AdminArea = addr1AdminArea;
188                 addr2CountryCode = addr1CountryCode;
189             } else {
190                 otherCity = addr1Locality;
191                 if (otherCity.length() == 0) {
192                     otherCity = addr1AdminArea + " " + addr1CountryCode;
193                     ;
194                     if (!currentCountry.equals(addr1CountryCode)) {
195                         otherCity += " " + addr1CountryCode;
196                     }
197                 }
198                 addr1Locality = addr2Locality;
199                 addr1AdminArea = addr2AdminArea;
200                 addr1CountryCode = addr2CountryCode;
201             }
202             closestCommonLocation = valueIfEqual(addr1.getAddressLine(0), addr2.getAddressLine(0));
203             if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
204                 if (!currentCity.equals(otherCity)) {
205                     closestCommonLocation += " - " + otherCity;
206                 }
207                 return closestCommonLocation;
208             }
209 
210             // Compare thoroughfare (street address) next.
211             closestCommonLocation = valueIfEqual(addr1.getThoroughfare(), addr2.getThoroughfare());
212             if (closestCommonLocation != null && !("null".equals(closestCommonLocation))) {
213                 return closestCommonLocation;
214             }
215         }
216 
217         // Compare the locality.
218         closestCommonLocation = valueIfEqual(addr1Locality, addr2Locality);
219         if (closestCommonLocation != null && !("".equals(closestCommonLocation))) {
220             String adminArea = addr1AdminArea;
221             String countryCode = addr1CountryCode;
222             if (adminArea != null && adminArea.length() > 0) {
223                 if (!countryCode.equals(currentCountry)) {
224                     closestCommonLocation += ", " + adminArea + " " + countryCode;
225                 } else {
226                     closestCommonLocation += ", " + adminArea;
227                 }
228             }
229             return closestCommonLocation;
230         }
231 
232         // If the admin area is the same as the current location, we hide it and
233         // instead show the city name.
234         if (currentAdminArea.equals(addr1AdminArea) && currentAdminArea.equals(addr2AdminArea)) {
235             if ("".equals(addr1Locality)) {
236                 addr1Locality = addr2Locality;
237             }
238             if ("".equals(addr2Locality)) {
239                 addr2Locality = addr1Locality;
240             }
241             if (!"".equals(addr1Locality)) {
242                 if (addr1Locality.equals(addr2Locality)) {
243                     closestCommonLocation = addr1Locality + ", " + currentAdminArea;
244                 } else {
245                     closestCommonLocation = addr1Locality + " - " + addr2Locality;
246                 }
247                 return closestCommonLocation;
248             }
249         }
250 
251         // Just choose one of the localities if within a MAX_LOCALITY_MILE_RANGE
252         // mile radius.
253         int distance = (int) LocationMediaFilter.toMile(LocationMediaFilter.distanceBetween(setMinLatitude, setMinLongitude,
254                 setMaxLatitude, setMaxLongitude));
255         if (distance < MAX_LOCALITY_MILE_RANGE) {
256             // Try each of the points and just return the first one to have a
257             // valid address.
258             closestCommonLocation = getLocalityAdminForAddress(addr1, true);
259             if (closestCommonLocation != null) {
260                 return closestCommonLocation;
261             }
262             closestCommonLocation = getLocalityAdminForAddress(addr2, true);
263             if (closestCommonLocation != null) {
264                 return closestCommonLocation;
265             }
266         }
267 
268         // Check the administrative area.
269         closestCommonLocation = valueIfEqual(addr1AdminArea, addr2AdminArea);
270         if (closestCommonLocation != null && !("".equals(closestCommonLocation))) {
271             String countryCode = addr1CountryCode;
272             if (!countryCode.equals(currentCountry)) {
273                 if (countryCode != null && countryCode.length() > 0) {
274                     closestCommonLocation += " " + countryCode;
275                 }
276             }
277             return closestCommonLocation;
278         }
279 
280         // Check the country codes.
281         closestCommonLocation = valueIfEqual(addr1CountryCode, addr2CountryCode);
282         if (closestCommonLocation != null && !("".equals(closestCommonLocation))) {
283             return closestCommonLocation;
284         }
285         // There is no intersection, let's choose a nicer name.
286         String addr1Country = addr1.getCountryName();
287         String addr2Country = addr2.getCountryName();
288         if (addr1Country == null)
289             addr1Country = addr1CountryCode;
290         if (addr2Country == null)
291             addr2Country = addr2CountryCode;
292         if (addr1Country == null || addr2Country == null)
293             return null;
294         if (addr1Country.length() > MAX_COUNTRY_NAME_LENGTH || addr2Country.length() > MAX_COUNTRY_NAME_LENGTH) {
295             closestCommonLocation = addr1CountryCode + " - " + addr2CountryCode;
296         } else {
297             closestCommonLocation = addr1Country + " - " + addr2Country;
298         }
299         return closestCommonLocation;
300     }
301 
checkNull(String locality)302     private String checkNull(String locality) {
303         if (locality == null)
304             return "";
305         if (locality.equals("null"))
306             return "";
307         return locality;
308     }
309 
getReverseGeocodedLocation(final double latitude, final double longitude, final int desiredNumDetails)310     protected String getReverseGeocodedLocation(final double latitude, final double longitude, final int desiredNumDetails) {
311         String location = null;
312         int numDetails = 0;
313         try {
314             Address addr = lookupAddress(latitude, longitude);
315 
316             if (addr != null) {
317                 // Look at the first line of the address, thorough fare and
318                 // feature
319                 // name in order and pick one.
320                 location = addr.getAddressLine(0);
321                 if (location != null && !("null".equals(location))) {
322                     numDetails++;
323                 } else {
324                     location = addr.getThoroughfare();
325                     if (location != null && !("null".equals(location))) {
326                         numDetails++;
327                     } else {
328                         location = addr.getFeatureName();
329                         if (location != null && !("null".equals(location))) {
330                             numDetails++;
331                         }
332                     }
333                 }
334 
335                 if (numDetails == desiredNumDetails) {
336                     return location;
337                 }
338 
339                 String locality = addr.getLocality();
340                 if (locality != null && !("null".equals(locality))) {
341                     if (location != null && location.length() > 0) {
342                         location += ", " + locality;
343                     } else {
344                         location = locality;
345                     }
346                     numDetails++;
347                 }
348 
349                 if (numDetails == desiredNumDetails) {
350                     return location;
351                 }
352 
353                 String adminArea = addr.getAdminArea();
354                 if (adminArea != null && !("null".equals(adminArea))) {
355                     if (location != null && location.length() > 0) {
356                         location += ", " + adminArea;
357                     } else {
358                         location = adminArea;
359                     }
360                     numDetails++;
361                 }
362 
363                 if (numDetails == desiredNumDetails) {
364                     return location;
365                 }
366 
367                 String countryCode = addr.getCountryCode();
368                 if (countryCode != null && !("null".equals(countryCode))) {
369                     if (location != null && location.length() > 0) {
370                         location += ", " + countryCode;
371                     } else {
372                         location = addr.getCountryName();
373                     }
374                 }
375             }
376 
377             return location;
378         } catch (Exception e) {
379             return null;
380         }
381     }
382 
getLocalityAdminForAddress(final Address addr, final boolean approxLocation)383     private String getLocalityAdminForAddress(final Address addr, final boolean approxLocation) {
384         if (addr == null)
385             return "";
386         String localityAdminStr = addr.getLocality();
387         if (localityAdminStr != null && !("null".equals(localityAdminStr))) {
388             if (approxLocation) {
389                 // TODO: Uncomment these lines as soon as we may translations
390                 // for Res.string.around.
391                 // localityAdminStr =
392                 // mContext.getResources().getString(Res.string.around) + " " +
393                 // localityAdminStr;
394             }
395             String adminArea = addr.getAdminArea();
396             if (adminArea != null && adminArea.length() > 0) {
397                 localityAdminStr += ", " + adminArea;
398             }
399             return localityAdminStr;
400         }
401         return null;
402     }
403 
lookupAddress(final double latitude, final double longitude)404     private Address lookupAddress(final double latitude, final double longitude) {
405         try {
406             long locationKey = (long) (((latitude + LocationMediaFilter.LAT_MAX) * 2 * LocationMediaFilter.LAT_MAX + (longitude + LocationMediaFilter.LON_MAX)) * LocationMediaFilter.EARTH_RADIUS_METERS);
407             byte[] cachedLocation = sGeoCache.get(locationKey, 0);
408             Address address = null;
409             if (cachedLocation == null || cachedLocation.length == 0) {
410                 try {
411                     List<Address> addresses = mGeocoder.getFromLocation(latitude, longitude, 1);
412                     if (!addresses.isEmpty()) {
413                         address = addresses.get(0);
414                         ByteArrayOutputStream bos = new ByteArrayOutputStream();
415                         DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(bos, 256));
416                         Locale locale = address.getLocale();
417                         Utils.writeUTF(dos, locale.getLanguage());
418                         Utils.writeUTF(dos, locale.getCountry());
419                         Utils.writeUTF(dos, locale.getVariant());
420 
421                         Utils.writeUTF(dos, address.getThoroughfare());
422                         int numAddressLines = address.getMaxAddressLineIndex();
423                         dos.writeInt(numAddressLines);
424                         for (int i = 0; i < numAddressLines; ++i) {
425                             Utils.writeUTF(dos, address.getAddressLine(i));
426                         }
427                         Utils.writeUTF(dos, address.getFeatureName());
428                         Utils.writeUTF(dos, address.getLocality());
429                         Utils.writeUTF(dos, address.getAdminArea());
430                         Utils.writeUTF(dos, address.getSubAdminArea());
431 
432                         Utils.writeUTF(dos, address.getCountryName());
433                         Utils.writeUTF(dos, address.getCountryCode());
434                         Utils.writeUTF(dos, address.getPostalCode());
435                         Utils.writeUTF(dos, address.getPhone());
436                         Utils.writeUTF(dos, address.getUrl());
437 
438                         dos.flush();
439                         sGeoCache.put(locationKey, bos.toByteArray(), 0);
440                         dos.close();
441                     }
442                 } finally {
443 
444                 }
445             } else {
446                 // Parsing the address from the byte stream.
447                 DataInputStream dis = new DataInputStream(new BufferedInputStream(new ByteArrayInputStream(cachedLocation), 256));
448                 String language = Utils.readUTF(dis);
449                 String country = Utils.readUTF(dis);
450                 String variant = Utils.readUTF(dis);
451                 Locale locale = null;
452                 if (language != null) {
453                     if (country == null) {
454                         locale = new Locale(language);
455                     } else if (variant == null) {
456                         locale = new Locale(language, country);
457                     } else {
458                         locale = new Locale(language, country, variant);
459                     }
460                 }
461                 if (!locale.getLanguage().equals(Locale.getDefault().getLanguage())) {
462                     sGeoCache.delete(locationKey);
463                     dis.close();
464                     return lookupAddress(latitude, longitude);
465                 }
466                 address = new Address(locale);
467 
468                 address.setThoroughfare(Utils.readUTF(dis));
469                 int numAddressLines = dis.readInt();
470                 for (int i = 0; i < numAddressLines; ++i) {
471                     address.setAddressLine(i, Utils.readUTF(dis));
472                 }
473                 address.setFeatureName(Utils.readUTF(dis));
474                 address.setLocality(Utils.readUTF(dis));
475                 address.setAdminArea(Utils.readUTF(dis));
476                 address.setSubAdminArea(Utils.readUTF(dis));
477 
478                 address.setCountryName(Utils.readUTF(dis));
479                 address.setCountryCode(Utils.readUTF(dis));
480                 address.setPostalCode(Utils.readUTF(dis));
481                 address.setPhone(Utils.readUTF(dis));
482                 address.setUrl(Utils.readUTF(dis));
483                 dis.close();
484             }
485             return address;
486         } catch (Exception e) {
487             // Ignore.
488         }
489         return null;
490     }
491 
valueIfEqual(String a, String b)492     private String valueIfEqual(String a, String b) {
493         return (a != null && b != null && a.equalsIgnoreCase(b)) ? a : null;
494     }
495 }
496