7 package com.google.appinventor.components.runtime;
22 import android.content.Context;
23 import android.location.Address;
24 import android.location.Criteria;
25 import android.location.Geocoder;
26 import android.location.Location;
27 import android.location.LocationListener;
28 import android.location.LocationManager;
29 import android.location.LocationProvider;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.util.Log;
33 import android.Manifest;
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.HashSet;
38 import java.util.List;
58 @DesignerComponent(version = YaVersion.LOCATIONSENSOR_COMPONENT_VERSION,
59 description =
"Non-visible component providing location information, " +
60 "including longitude, latitude, altitude (if supported by the device), " +
61 "speed (if supported by the device), " +
62 "and address. This can also perform \"geocoding\", converting a given " +
63 "address (not necessarily the current one) to a latitude (with the " +
64 "<code>LatitudeFromAddress</code> method) and a longitude (with the " +
65 "<code>LongitudeFromAddress</code> method).</p>\n" +
66 "<p>In order to function, the component must have its " +
67 "<code>Enabled</code> property set to True, and the device must have " +
68 "location sensing enabled through wireless networks or GPS " +
69 "satellites (if outdoors).</p>\n" +
70 "Location information might not be immediately available when an app starts. You'll have to wait a short time for " +
71 "a location provider to be found and used, or wait for the LocationChanged event",
72 category = ComponentCategory.SENSORS,
74 iconName =
"images/locationSensor.png")
76 @UsesPermissions(permissionNames =
77 "android.permission.ACCESS_FINE_LOCATION," +
78 "android.permission.ACCESS_COARSE_LOCATION," +
79 "android.permission.ACCESS_MOCK_LOCATION," +
80 "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS")
95 private class MyLocationListener
implements LocationListener {
100 public void onLocationChanged(
final Location location) {
101 lastLocation = location;
102 longitude = location.getLongitude();
103 latitude = location.getLatitude();
104 speed = location.getSpeed();
107 if (location.hasAltitude()) {
109 altitude = location.getAltitude();
114 if (longitude != UNKNOWN_VALUE || latitude != UNKNOWN_VALUE) {
115 hasLocationData =
true;
116 final double argLatitude = latitude;
117 final double argLongitude = longitude;
118 final double argAltitude = altitude;
119 final float argSpeed = speed;
120 androidUIHandler.post(
new Runnable() {
123 LocationChanged(argLatitude, argLongitude, argAltitude, argSpeed);
125 listener.onLocationChanged(location);
133 public void onProviderDisabled(String provider) {
134 StatusChanged(provider,
"Disabled");
137 RefreshProvider(
"onProviderDisabled");
142 public void onProviderEnabled(String provider) {
143 StatusChanged(provider,
"Enabled");
144 RefreshProvider(
"onProviderEnabled");
148 public void onStatusChanged(String provider,
int status, Bundle extras) {
151 case LocationProvider.TEMPORARILY_UNAVAILABLE:
152 StatusChanged(provider,
"TEMPORARILY_UNAVAILABLE");
154 case LocationProvider.OUT_OF_SERVICE:
157 StatusChanged(provider,
"OUT_OF_SERVICE");
159 if (provider.equals(providerName)) {
161 RefreshProvider(
"onStatusChanged");
164 case LocationProvider.AVAILABLE:
167 StatusChanged(provider,
"AVAILABLE");
168 if (!provider.equals(providerName) &&
169 !allProviders.contains(provider)) {
170 RefreshProvider(
"onStatusChanged");
183 public static final int UNKNOWN_VALUE = 0;
186 private final Criteria locationCriteria;
187 private final Handler handler;
188 private final LocationManager locationManager;
190 private final Set<LocationSensorListener> listeners =
new HashSet<LocationSensorListener>();
192 private boolean providerLocked =
false;
193 private String providerName;
196 private boolean initialized =
false;
198 private int timeInterval;
199 private int distanceInterval;
201 private MyLocationListener myLocationListener;
203 private LocationProvider locationProvider;
204 private boolean listening =
false;
210 private List<String> allProviders;
213 private Location lastLocation;
214 private double longitude = UNKNOWN_VALUE;
215 private double latitude = UNKNOWN_VALUE;
216 private double altitude = UNKNOWN_VALUE;
217 private float speed = UNKNOWN_VALUE;
218 private boolean hasLocationData =
false;
219 private boolean hasAltitude =
false;
222 private final Handler androidUIHandler =
new Handler();
225 private Geocoder geocoder;
228 private boolean enabled =
true;
230 private boolean havePermission =
false;
231 private static final String LOG_TAG =
LocationSensor.class.getSimpleName();
239 this(container,
true);
249 super(container.
$form());
250 this.enabled = enabled;
251 handler =
new Handler();
253 form.registerForOnResume(
this);
254 form.registerForOnStop(
this);
257 timeInterval = 60000;
258 distanceInterval = 5;
261 Context context = container.
$context();
262 geocoder =
new Geocoder(context);
263 locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
264 locationCriteria =
new Criteria();
265 myLocationListener =
new MyLocationListener();
266 allProviders =
new ArrayList<String>();
271 @SuppressWarnings({
"unused"})
283 @
SimpleEvent(description =
"Indicates that a new location has been detected.")
284 public
void LocationChanged(
double latitude,
double longitude,
double altitude,
float speed) {
306 public String ProviderName() {
307 if (providerName ==
null) {
308 return "NO PROVIDER";
325 this.providerName = providerName;
326 if (!empty(providerName) && startProvider(providerName)) {
329 RefreshProvider(
"ProviderName");
334 public
boolean ProviderLocked() {
335 return providerLocked;
353 providerLocked = lock;
357 defaultValue =
"60000")
363 if (interval < 0 || interval > 1000000)
366 timeInterval = interval;
370 RefreshProvider(
"TimeInterval");
374 listener.onTimeIntervalChanged(timeInterval);
389 description =
"Determines the minimum time interval, in milliseconds, that the sensor will try " +
390 "to use for sending out location updates. However, location updates will only be received " +
391 "when the location of the phone actually changes, and use of the specified time interval " +
392 "is not guaranteed. For example, if 1000 is used as the time interval, location updates will " +
393 "never be fired sooner than 1000ms, but they may be fired anytime after.",
395 public
int TimeInterval() {
406 if (interval < 0 || interval > 1000)
409 distanceInterval = interval;
413 RefreshProvider(
"DistanceInterval");
417 listener.onDistanceIntervalChanged(distanceInterval);
432 description =
"Determines the minimum distance interval, in meters, that the sensor will try " +
433 "to use for sending out location updates. For example, if this is set to 5, then the sensor will " +
434 "fire a LocationChanged event only after 5 meters have been traversed. However, the sensor does " +
435 "not guarantee that an update will be received at exactly the distance interval. It may take more " +
436 "than 5 meters to fire an event, for instance.",
438 public
int DistanceInterval() {
439 return distanceInterval;
447 public
boolean HasLongitudeLatitude() {
448 return hasLocationData && enabled;
455 public
boolean HasAltitude() {
456 return hasAltitude && enabled;
463 public
boolean HasAccuracy() {
464 return Accuracy() != UNKNOWN_VALUE && enabled;
473 public
double Longitude() {
483 public
double Latitude() {
498 description =
"The most recently available altitude value, in meters. If no value is "
499 +
"available, 0 will be returned.")
500 public
double Altitude() {
515 description =
"The most recent measure of accuracy, in meters. If no value is available, "
516 +
"0 will be returned.")
517 public
double Accuracy() {
518 if (lastLocation !=
null && lastLocation.hasAccuracy()) {
519 return lastLocation.getAccuracy();
520 }
else if (locationProvider !=
null) {
521 return locationProvider.getAccuracy();
523 return UNKNOWN_VALUE;
532 public
boolean Enabled() {
543 defaultValue =
"True")
546 this.enabled = enabled;
553 RefreshProvider(
"Enabled");
567 description =
"Provides a textual representation of the current address or \"No address "
569 public String CurrentAddress() {
570 if (hasLocationData &&
571 latitude <= 90 && latitude >= -90 &&
572 longitude <= 180 || longitude >= -180) {
574 List<Address> addresses = geocoder.getFromLocation(latitude, longitude, 1);
575 if (addresses !=
null && addresses.size() == 1) {
576 Address address = addresses.get(0);
577 if (address !=
null) {
578 StringBuilder sb =
new StringBuilder();
579 for (
int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
580 sb.append(address.getAddressLine(i));
583 return sb.toString();
587 }
catch (Exception e) {
591 if (e instanceof IllegalArgumentException
592 || e instanceof IOException
593 || e instanceof IndexOutOfBoundsException ) {
594 Log.e(LOG_TAG,
"Exception thrown by getting current address " + e.getMessage());
598 "Unexpected exception thrown by getting current address " + e.getMessage());
602 return "No address available";
612 @
SimpleFunction(description =
"Derives latitude of given address")
613 public
double LatitudeFromAddress(String locationName) {
615 List<Address> addressObjs = geocoder.getFromLocationName(locationName, 1);
616 Log.i(LOG_TAG,
"latitude addressObjs size is " + addressObjs.size() +
" for " + locationName);
617 if ( (addressObjs ==
null) || (addressObjs.size() == 0) ){
618 throw new IOException(
"");
620 return addressObjs.get(0).getLatitude();
621 }
catch (IOException e) {
622 form.dispatchErrorOccurredEvent(
this,
"LatitudeFromAddress",
635 @
SimpleFunction(description =
"Derives longitude of given address")
636 public
double LongitudeFromAddress(String locationName) {
638 List<Address> addressObjs = geocoder.getFromLocationName(locationName, 1);
639 Log.i(LOG_TAG,
"longitude addressObjs size is " + addressObjs.size() +
" for " + locationName);
640 if ( (addressObjs ==
null) || (addressObjs.size() == 0) ){
641 throw new IOException(
"");
643 return addressObjs.get(0).getLongitude();
644 }
catch (IOException e) {
645 form.dispatchErrorOccurredEvent(
this,
"LongitudeFromAddress",
656 public List<String> AvailableProviders () {
671 if (!initialized)
return;
674 if (!havePermission) {
676 androidUIHandler.post(
new Runnable() {
682 public void HandlePermissionResponse(String permission,
boolean granted) {
684 me.havePermission =
true;
686 Log.d(LOG_TAG,
"Permission Granted");
688 me.havePermission =
false;
697 if (providerLocked && !empty(providerName)) {
698 listening = startProvider(providerName);
701 allProviders = locationManager.getProviders(
true);
702 String bProviderName = locationManager.getBestProvider(locationCriteria,
true);
703 if (bProviderName !=
null && !bProviderName.equals(allProviders.get(0))) {
704 allProviders.add(0, bProviderName);
707 for (String providerN : allProviders) {
708 listening = startProvider(providerN);
710 if (!providerLocked) {
711 providerName = providerN;
721 private boolean startProvider(
final String providerName) {
722 this.providerName = providerName;
723 LocationProvider tLocationProvider = locationManager.getProvider(providerName);
724 if (tLocationProvider ==
null) {
725 Log.d(LOG_TAG,
"getProvider(" + providerName +
") returned null");
729 locationProvider = tLocationProvider;
730 locationManager.requestLocationUpdates(providerName, timeInterval,
731 distanceInterval, myLocationListener);
743 private void stopListening() {
745 locationManager.removeUpdates(myLocationListener);
746 locationProvider =
null;
757 RefreshProvider(
"onResume");
777 listeners.add(listener);
781 listeners.remove(listener);
785 private boolean empty(String s) {
786 return s ==
null || s.length() == 0;