39
votes

Android 4.3 on Moto G, Android 4.4.2 on Nexus 7 2012, Android 4.4.2 on Nexus 5. Android Studio 0.4.

I don't want to receive regular location updates, I just want an accurate location when the user presses a button.

I have followed this example: https://developer.android.com/training/location/retrieve-current.html

In manifest file:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

I check that Play Services are available using GooglePlayServicesUtil.isGooglePlayServicesAvailable

In main activity:

//in activity onCreate method
mLocationClient = new LocationClient(this, this, this);

@Override
protected void onStart() {
    mLocationClient.connect();
    super.onStart();
}

@Override
protected void onStop() {
    mLocationClient.disconnect();
    super.onStop();
}

//in button onclick method    
mCurrentLocation = mLocationClient.getLastLocation();

I have no SIM card. If I enable Wifi then sometimes I get an accurate location. Other times mCurrentLocation is null.

If I disable Wifi then mCurrentLocation is always null.

I am testing outside in several locations always with a clear view of the sky. I waited three minutes in each location.

I never see the GPS icon appear on the Android notification bar at the top of the screen.

I have these location settings: enter image description here

A GPS Test app manages to use GPS successfully indoors on the same device with Wi-Fi disabled so GPS is working: enter image description here

Registering for location updates, as at https://developer.android.com/training/location/receive-location-updates.html, doesn't work either. Registered method never called.

What am I doing wrong?

12
post some code, it does take some time for GPS to get a proper lock tootyczj
@tyczj Just edited question to add code. I waited three minutes in each location. Neither time did the GPS icon appear in the Android notification bar.cja
Thanks! Can you also post the LocationRequest code you're using to register for location updates? Also, I assume that when you say you are turning on WiFi you also have an internet connection? You can also test the fused provider using my GPS Benchmark app here - play.google.com/store/apps/details?id=com.gpsbenchmark.androidSean Barbeau
GPS Benchmark looks like it'll be very usefulcja
do you really need both permissions? <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />Marian Paździoch

12 Answers

16
votes

I solved it. The problem was that "Let Google apps access your location" was turned off: enter image description here

When I turn it on I get GPS readings and when it's off I don't.

I had left it off for two reasons:

  1. I'm developing an app to be used to lots of devices at a company and I want minimum manual configuration to be necessary

  2. The screen says clearly "This setting affects Google apps only." I know that Play Services is Google software but I didn't think Google would expect an end user to understand that.

Then I got the Android 4.4.2 update and the location settings page has changed. It appears that I can have Google Location Reporting turned off and still get GPS readings from the fused location provider: enter image description here

So maybe Google realised that the setting was confusing and improved it. Either way, I'd have saved a lot of time if I'd got 4.4.2 a few days ago.

15
votes

The problem is with getLastLocation() because it uses a cached location. I had the same problem as I also tried to use this simple approach. Since, I have switched to listening to updates (and stopping after 1st successfull update automatically).

This is my code that works.

First, the check for availability in Application (not essential, can be in Activity and without keeping of result):

public class MainApp extends Application {
  public static enum PlayServices {
    NOT_CHECKED, AVAILABLE, UNAVAILABLE
  };
  public static PlayServices mPlayServices = PlayServices.NOT_CHECKED;

  @Override
  public void onCreate() {
    super.onCreate();

    if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS) {
      MainApp.mPlayServices = MainApp.PlayServices.AVAILABLE;
    }
  }
}

Then, on to the Activity:

public class MainActivity extends SherlockFragmentActivity implements
  GooglePlayServicesClient.ConnectionCallbacks,
  GooglePlayServicesClient.OnConnectionFailedListener, LocationListener {

In its onCreate():

if (MainApp.mPlayServices != MainApp.PlayServices.UNAVAILABLE) {
  mLocationClient = new LocationClient(this, this, this);

  mLocationRequest = LocationRequest.create();
  mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
  mLocationRequest.setInterval(5000);
  mLocationRequest.setNumUpdates(1);
  mLocationRequest.setFastestInterval(1000);

  mUpdatesRequested = false;
  MainApp.prefs.edit().putBoolean(MainApp.KEY_LOCATION_UPDATES_REQUESTED, mUpdatesRequested)
      .commit();
}

The rest of the MainActivity class:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  Log.d(this.getClass().getSimpleName(), "onActivityResult(" + requestCode + ", " + resultCode
      + ")");
  // Decide what to do based on the original request code
  switch (requestCode) {
    case MainApp.PLAY_CONNECTION_FAILURE_RESOLUTION_REQUEST:
      /*
       * If the result code is Activity.RESULT_OK, try
       * to connect again
       */
      switch (resultCode) {
        case Activity.RESULT_OK:
          // here we want to initiate location requests!
          mLocationClient = new LocationClient(this, this, this);

          break;
      }
      break;
  }
}

@Override
public void onConnected(Bundle dataBundle) {
  Log.d(this.getClass().getSimpleName(), "onConnected()");

  Log.d(this.getClass().getSimpleName(), "Google Play Services are available.");
  MainApp.mPlayServices = MainApp.PlayServices.AVAILABLE;

  if (!mUpdatesRequested) {

    LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

    boolean gps_enabled = false;
    try {
      gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
    } catch (Exception ex) {
    }

    boolean network_enabled = false;
    try {
      network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    } catch (Exception ex) {
    }

    // don't start listeners if no provider is enabled
    MainApp.locEnabled = gps_enabled || network_enabled;

    if (!MainApp.locEnabled) {
      // we have access to PlayServices, but user has disabled location visibility --> alert him
      alertLocationOff();
    } else {
      mLocationClient.requestLocationUpdates(mLocationRequest, this);
      mUpdatesRequested = true;
    }
  }
}

@Override
public void onDisconnected() {
  Log.d(this.getClass().getSimpleName(), "onDisconnected()");
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
  Log.d(this.getClass().getSimpleName(), "onConnectionFailed()");

  Log.d(this.getClass().getSimpleName(), "Google Play Services not available.");
  MainApp.mPlayServices = MainApp.PlayServices.UNAVAILABLE;

  /*
   * Google Play services can resolve some errors it detects.
   * If the error has a resolution, try sending an Intent to
   * start a Google Play services activity that can resolve
   * error.
   */
  if (connectionResult.hasResolution()) {
    try {
      // Start an Activity that tries to resolve the error
      connectionResult.startResolutionForResult(this,
          MainApp.PLAY_CONNECTION_FAILURE_RESOLUTION_REQUEST);
      /*
       * Thrown if Google Play services canceled the original
       * PendingIntent
       */
    } catch (IntentSender.SendIntentException e) {
      // Log the error
      e.printStackTrace();
    }
  } else {
    /*
     * If no resolution is available, display a dialog to the
     * user with the error.
     */
    GooglePlayServicesUtil.getErrorDialog(connectionResult.getErrorCode(), this, 0).show();
  }
}

@SuppressLint("NewApi")
@Override
public void onLocationChanged(Location location) {
  Log.d(this.getClass().getSimpleName(), "onLocationChanged(), location=" + location);

  if (location != null) {
    boolean present = true;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
      present = Geocoder.isPresent();
    }

    if (present) {
      (new ExtractLocationTask(this)).execute(location);
    } else {
      Log.e(this.getClass().getSimpleName(), "Geocoder not present");
      MainApp.mPlayServices = MainApp.PlayServices.UNAVAILABLE;
    }
  }
}


private class ExtractLocationTask extends AsyncTask<Location, Void, Boolean> {
  Context mContext;

  public ExtractLocationTask(Context context) {
    super();
    mContext = context;
  }

  @Override
  protected Boolean doInBackground(Location... params) {
    Log.d(getClass().getSimpleName(), "ExtractLocationTask.onPreExecute()");

    boolean found = false;
    try {
      Geocoder geoCoder_local = new Geocoder(mContext, Locale.getDefault());
      Geocoder geoCoder_en = new Geocoder(mContext, Locale.ENGLISH);

      List<Address> addresses_local = geoCoder_local.getFromLocation(params[0].getLatitude(),
          params[0].getLongitude(), 10);
      List<Address> addresses_en = geoCoder_en.getFromLocation(params[0].getLatitude(),
          params[0].getLongitude(), 10);

      if (addresses_local != null && addresses_local.size() > 0) {

        // do what you want with location info here

        // based on mLocationRequest.setNumUpdates(1), no need to call
        // removeLocationUpdates()

        MainApp.locEnabled = true;

        mUpdatesRequested = false;
        MainApp.prefs.edit()
            .putBoolean(MainApp.KEY_LOCATION_UPDATES_REQUESTED, mUpdatesRequested).commit();

        found = true;
      }
    } catch (IOException e) {
      Log.e(this.getClass().getSimpleName(), "Exception: ", e);
    }

    return found;
  }

  @Override
  protected void onPostExecute(Boolean found) {
    Log.d(getClass().getSimpleName(), "ExtractLocationTask.onPostExecute()");

    if (found) {
      // update UI etc.
    } else if (!mUpdatesReRequested) {
      mLocationClient.requestLocationUpdates(mLocationRequest, (LocationListener) mContext);
      mUpdatesRequested = true;
      mUpdatesReRequested = true;
    }
  }
}

I hope this will help you get it to work!

4
votes

Location provider won't wake up GPS until some of clients ask (subscribe) for high precision location (as described in examples given by other users). GPS test app doesn't use location provider but uses old "direct" way of obtaining location.

Also there is expiry mechanism, which removes information about last location after some time if it is believed to be stale.

Summing up all above it is really possible that LP(Location Provider) has nothing to give you.

2
votes

I think your are testing your application in Indoor , it doesn't works..

and code flow..

public void setUpLocationClientIfNeeded() {
        createLocReq();
        if (mLocationClient == null) {
            mLocationClient = new LocationClient(getApplicationContext(), this, // ConnectionCallbacks
                    this); // OnConnectionFailedListener
        }
    }

    private void createLocReq() {
        if (mLocationRequest == null) {
            // Create a new global location parameters object
            mLocationRequest = LocationRequest.create();
            // Set the update interval
            mLocationRequest.setInterval(LocationServices.UPDATE_INTERVAL_IN_MILLISECONDS);
            // Use high accuracy
            mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
            // Set the interval ceiling to one minute
            mLocationRequest.setFastestInterval(LocationServices.FAST_INTERVAL_CEILING_IN_MILLISECONDS);

            mLocationClient = new LocationClient(getApplicationContext(), this, this);
            mLocationClient.connect();
        }
    }

    public void updateLocation(Location location) {
        if (lastTrackedLat == 0.0) {
            lastTrackedLat = location.getLatitude();
        }
        if (lastTrackedLng == 0.0) {
            lastTrackedLng = location.getLongitude();
        }

        currentLat = location.getLatitude();
        currentLng = location.getLongitude();
    }

    @Override
    public void onLocationChanged(Location location) {
        if (location != null) {
            this.location = location;
            updateLocation(location);
        }
    }

    public Location getLocation() {
        // mLocationClient.getLastLocation();
        return location;
    }
1
votes

According to the docs

This method provides a simplified way to get location. It is particularly well suited for applications that do not require an accurate location and that do not want to maintain extra logic for location updates.

so it may or may not return a highly accurate location.

GPS can take a while to lock on so calling getLastLocation may or may not return a location

you are better off requesting location updates then after you get a location just stop requesting location updates.

Also looking at you code you provided are you waiting for the LocationClient to connect before trying to get a location? That will certainly give you a null location since it is not connected to get the location yet.

what you should be doing is in your onConnected get the last location there, example

public void onConnected(Bundle connectionHint) {
    Location location = mLocationClient.getLastLocation();
}

as it says in that example onConnected is Called by Location Services when the request to connect the client finishes successfully. At this point, you can request the current location or start periodic updates

1
votes

All you need to do is to add a priority property to the request object like this.

public void onConnected(Bundle arg0)
{
    locationrequest = LocationRequest.create();
    locationrequest.setInterval(1000);
    locationrequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    locationclient.requestLocationUpdates(locationrequest, this);
}

Maybe use a boolean variable to let the user have options to force GPS like

boolean forceGPS=false;
.
.
.//make the user choose to change the value of the boolean
.
.
public void onConnected(Bundle arg0)
    {
        locationrequest = LocationRequest.create();
        locationrequest.setInterval(1000);
        if(forceGPS==true)
        {
        locationrequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        }
        locationclient.requestLocationUpdates(locationrequest, this);
    }
1
votes

Without a sim card the coarse location provider has no way to know the coarse position, unless it is able to find a WiFi network that has been mapped by Google.

Requesting the last known location may result in a outdated location, and as such is rather useless. I guess this is the position that was recorded the last time some app requested a location update.

I use the following code to get a recent location:

    Criteria criteria = new Criteria();
    criteria.setAccuracy(Criteria.ACCURACY_COARSE);
    criteria.setAltitudeRequired(false);
    criteria.setSpeedRequired(false);
    criteria.setBearingRequired(false);
    criteria.setCostAllowed(false);

    final LocationManager manager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);

    ....
    LocationListener listener = new LocationListener() {
        @Override
        public void onLocationChanged(Location lastKnownLocation) {
                     ....
        }
        // rest of interface
     }


     manager.requestSingleUpdate(criteria, listener, null);

The last call ensures that we request the current location, not the location it was able to find an unknown amount of time before.

You might try to change it to Criteria.ACCURACY_FINE in order to get the GPS fired up. Be aware that if the GPS didn't have a fix for quite some while it may take more than several minutes before it is actually capable of getting a fix. I'd expect in the mean time that you'd see the GPS icon indicating it is waiting for a fix.

1
votes

What's in the OnConnected method?

In this method you should create the LocationRequest object with PRIORITY_HIGH_ACCURACY priority.

@Override
public void onConnected(Bundle dataBundle) {
    mLocationRequest = LocationRequest.create();
    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
    mLocationClient.requestLocationUpdates(mLocationRequest, fusedListener);
}
0
votes

Try this:

public final static int MINACCURACY = 50;

private LocationManager lm;
private LocationListener listener;

private void foundLoc(double x, double y) {
    // do something with x,y
    Log.v("DEBUG", x + "," + y);
}


public void findMe(View v) {
    lm = (LocationManager) getSystemService(LOCATION_SERVICE);
    listener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            if(location.getAccuracy() < MINACCURACY) {
                foundLoc(location.getLatitude(), location.getLongitude());
                lm.removeUpdates(listener);
            }
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
        }

        @Override
        public void onProviderEnabled(String provider) {
        }

        @Override
        public void onProviderDisabled(String provider) {
        }
    };
    lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 100, 0, listener);
}

MINACCURACY is in meters. This way, on button press (which calls the findMe method), the GPS is enabled, your location is found with a minimum accuracy, the method foundLoc gets the data and the listener terminates (which, in turn, disables GPS).

0
votes

There are two things going on here that are causing you to sometimes get null, and sometimes get a location.

First, you're creating a new instance of the LocationClient in the onClick method, which is not the same instance you're calling connect() on in onStart(). This will create unpredictable behavior where sometimes the client will have enough time to connect before returning a result from LocationClient.getLastLocation(), and sometimes it won't.

Second, you should guard the call to LocationClient.getLastLocation() with a call to LocationClient.isConnected(). It says the following in the LocationClient.isConnected() docs: https://developer.android.com/reference/com/google/android/gms/location/LocationClient.html#isConnected()

Checks if the client is currently connected to the service, so that requests to other methods will succeed. Applications should guard client actions caused by the user with a call to this method.

Since the user is triggering the onClick() code by tapping on the button, you should call this method before trying to get the last location.

So, your code should look like this:

LocationClient mLocationClient;
Location mCurrentLocation;

@Override
protected void onCreate() {
    ...
    mLocationClient = new LocationClient(this, this, this);
    ...
}

@Override
protected void onStart() {
    mLocationClient.connect();
    super.onStart();
}

@Override
protected void onStop() {
    mLocationClient.disconnect();
    super.onStop();
}

public void onClick(View v) {
    ...
    if (mLocationClient.isConnected()) {
        // You should get a valid location here
        mCurrentLocation = mLocationClient.getLastLocation();
    }
}

This should give the LocationClient a long enough time to connect and give you a valid location, which should hopefully include GPS data.

0
votes

Since no one has shared this link, I found this to be the most helpful documentation http://developer.android.com/guide/topics/location/strategies.html

"You might expect that the most recent location fix is the most accurate. However, because the accuracy of a location fix varies, the most recent fix is not always the best. You should include logic for choosing location fixes based on several criteria. The criteria also varies depending on the use-cases of the application and field testing."

Your best bet is to keep a location listener going as long as the activity is in the foreground and select the most accurate cached location when the button is pressed. You may need to show a spinner or something and disable the button while it waits for an accurate measurement to show up.

0
votes

I prefer to use Service that implements LocationListener to get current near-accurate location. I normally take following steps:

  1. Initialize LocationManager and call requestLocationUpdates for both GPS_PROVIDER and NETWORK_PROVIDER in onCreate()
  2. Call getLastKnownLocation for both GPS_PROVIDER and NETWORK_PROVIDER and use getTime() to know last newer location.
  3. Broadcast last location (if it's <30 sec old) (optional)
  4. In the meantime, when onLocationChanged is called I compare newly updated location with last best location (if any) and check accuracy level.
  5. If accuracy delta < MIN_ACCURACY (user variable), use it as best location
  6. Broadcast current best location
  7. Must remove LocationManager locationManager.removeUpdates(this);
  8. Call stopSelf(); (you can also stop service from activity's onDestroy)

EDIT:

OK...Above method was using Location API, below I have coded using Fused Provider (GooglePlayServices). I have tested on my Nexus 5 (Android 4.4.2), NO SIM, NO WIFI...and I'm getting results.

Edit 2: I have also tested on Android 2.3.3 LG Optimus (No SIM & Wifi) and it took about 5 min to get lock (using Fused Provided), but I was getting location icon instantly.

public class HomeActivity extends FragmentActivity implements
        ActionBar.OnNavigationListener,
        GooglePlayServicesClient.ConnectionCallbacks,
        GooglePlayServicesClient.OnConnectionFailedListener, LocationListener {

    private LocationClient locationClient;
    private LocationRequest locationRequest;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_home);
            // ...
            int resp = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        if (resp == ConnectionResult.SUCCESS) {
            locationClient = new LocationClient(this, this, this);
            locationClient.connect();
        } else {
            Toast.makeText(this, "Google Play Service Error " + resp,
                    Toast.LENGTH_LONG).show();

        }
    }

    @Override
    public void onConnected(Bundle connectionHint) {
        if (locationClient != null && locationClient.isConnected()) {

            locationRequest = LocationRequest.create();
            locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
            locationRequest.setInterval(100);
            locationClient.requestLocationUpdates(locationRequest, this);

        }
    }

    @Override
    public void onLocationChanged(Location location) {

        try {
            if (location != null) {
                LatLng gps = new LatLng(location.getLatitude(), location.getLongitude());
                Log.v("JD", "My Location: " + gps.toString());
            }
        } catch (Exception e) {
            Log.d("JD",
                    "onLocationChanged", e);

        }       
    }
}