I am developing an app which needs to geolocate the user. It works without problems in devices with GPS, such as my Marshmallow phone.
I also have a Marshmallow tablet, without GPS. In the settings pull down it still has a location icon to switch geolocation on and off. It correctly finds me on the map when I switch it on, so I would like to use the same method, whichever it is. It doesn't have a SIM card either, so it doesn't rely on the Network operator cell.
The app obviously crashes when I run it on my tablet, asking for authorisation to use the GPS sensor, which is not present.
I am seeking an example on how to use the tablet's location sensor, whatever that is. I did search a lot, but I didn't find anything relevant.
Or is it possible to modify the following class so to make it work if the GPS is not present? I believe all the relevant code is in the second constructor, the other classes are just used to format the town and postcode in the requested way.
public class GeolocationUtil implements LocationListener
{
// Public values to determine if first part of postcode has to be returned ( in countries
// where it's in two parts separated by space ) or full postcode
public static final boolean GET_FULL_POSTCODE = true;
public static final boolean GET_FIRST_PART_OF_POSTCODE = false;
private AppCompatActivity aca;
private TextView locationTV;
private GeolocationBean geolocation;
private boolean showFullPostcode;
/**
*
* Takes care of geolocation operations
*
* @param acaParam activity requesting geolocation
*
* @param locationTVParam text view which will be updated with the town and postcode
* of the device location
*
* @param geolocationParam bean which will be populated
*
*/
public GeolocationUtil ( AppCompatActivity acaParam, TextView locationTVParam,
GeolocationBean geolocationParam )
{
this ( acaParam, locationTVParam, geolocationParam, GET_FIRST_PART_OF_POSTCODE );
}
/**
*
* Takes care of geolocation operations
*
* @param acaParam activity requesting geolocation
*
* @param locationTVParam text view which will be updated with the town and postcode
* of the device location
*
* @param geolocationParam bean which will be populated
*
* @param showFullPostcodeParam parameter which lets users choose if the full postcode must be
* displayed, or only the first part. Only works for countries in
* which the postcode is made up by two parts separated by a space
*
*/
public GeolocationUtil ( AppCompatActivity acaParam, TextView locationTVParam,
GeolocationBean geolocationParam, boolean showFullPostcodeParam )
{
boolean gpsSensorAvailable;
aca = acaParam;
locationTV = locationTVParam;
geolocation = geolocationParam;
showFullPostcode = showFullPostcodeParam;
// Find out if device location is coarse or fine
gpsSensorAvailable = hasGPSDevice ( aca );
// Location variables which vary depending on presence or lack of GPS sensor.
// Default values for when it is not present, change if the device has it
String locationAccessType = Manifest.permission.ACCESS_COARSE_LOCATION;
String locationProviderType;
LocationManager locationManager = ( LocationManager ) aca.getSystemService ( Context.LOCATION_SERVICE );
// Check app has permission to access GPS sensor
if ( ActivityCompat.checkSelfPermission ( aca, Manifest.permission.ACCESS_FINE_LOCATION )
!= PackageManager.PERMISSION_GRANTED )
{
ActivityCompat.requestPermissions ( aca, new String[]
{ Manifest.permission.ACCESS_FINE_LOCATION }, 2 );
}
// If the sensor is switched off, open settings page to switch it on
if ( ! locationManager.isProviderEnabled ( LocationManager.GPS_PROVIDER ) )
{
showSettingsAlert ();
}
locationManager.requestLocationUpdates ( LocationManager.GPS_PROVIDER, 1000,
1, this );
}
/**
* Town / city and postcode of current location
*
* @param latitude latitude of required location and postcode
* @param longitude
* @return
*
*/
private String getTownAndPostcode ( double latitude, double longitude )
{
String townAndPostcode = "";
try
{
Geocoder geo = new Geocoder ( aca, Locale.getDefault () );
List< Address > addresses = geo.getFromLocation ( latitude, longitude, 1 );
if ( addresses.isEmpty () )
{
townAndPostcode = "@string/location_failed";
}
else
{
if ( addresses.size () > 0 )
{
Address address = addresses.get ( 0 );
// Town / city and first part of postcode if separated by space.
// To avoid having a slightly incorrect one, in the UK at least
if ( showFullPostcode )
{
townAndPostcode = address.getLocality () + " " + address.getPostalCode ();
}
else
{
townAndPostcode = address.getLocality () + " " + address.getPostalCode ().split ( "\\s+" )[ 0 ];
}
}
}
}
catch ( IOException ioe )
{
ioe.printStackTrace ();
}
return townAndPostcode;
}
// Determine if the device has a GPS sensor or not
private boolean hasGPSDevice ( Context context )
{
final LocationManager mgr = ( LocationManager ) context.getSystemService ( Context.LOCATION_SERVICE );
if ( mgr == null )
{
return false;
}
final List < String > providers = mgr.getAllProviders ();
{
if ( providers == null ) return false;
}
return providers.contains ( LocationManager.GPS_PROVIDER );
}
@Override
public void onLocationChanged ( Location location )
{
double latitude = geolocation.getLatitude ();
double longitude = geolocation.getLongitude ();
// Only retrieve location once
if ( location.getLatitude () != 0 || location.getLongitude () != 0 )
{
geolocation.setLatitude ( location.getLatitude () );
geolocation.setLongitude ( location.getLongitude () );
String townAndPostcode = getTownAndPostcode ( latitude, longitude );
locationTV.setText ( townAndPostcode );
geolocation.setTownAndPostcode ( townAndPostcode );
}
}
@Override
public void onProviderEnabled ( String provider ) { }
@Override
public void onStatusChanged ( String provider, int status, Bundle extras ) { }
@Override
public void onProviderDisabled ( String provider ) { }
public void showSettingsAlert ()
{
final Context context = aca.getApplicationContext ();
AlertDialog.Builder alertDialog = new AlertDialog.Builder ( aca );
// Setting Dialog Title
alertDialog.setTitle ( "Geolocation settings" );
// Setting Dialog Message
alertDialog.setMessage ( "Geolocation sensor is switched off. Click on settings to switch it on, then back to return to the app" );
// Setting Icon to Dialog
//alertDialog.setIcon(R.drawable.delete);
// On pressing Settings button
alertDialog.setPositiveButton ( "Settings", new DialogInterface.OnClickListener ()
{
public void onClick ( DialogInterface dialog, int which )
{
Intent intent = new Intent ( Settings.ACTION_LOCATION_SOURCE_SETTINGS );
intent.setFlags ( Intent.FLAG_ACTIVITY_NEW_TASK );
context.startActivity ( intent );
}
} );
// on pressing cancel button
alertDialog.setNegativeButton ( "Cancel", new DialogInterface.OnClickListener ()
{
public void onClick ( DialogInterface dialog, int which )
{
dialog.cancel ();
}
} );
// Showing Alert Message
alertDialog.show ();
}
}
Found the solution, should anybody find him / herself having the same problem.
First of all, I was using the Activity class for the interface and basic functionalities, and a geolocation util class to make the geolocation process available throughout the app. This is not possible because it is necessary to override an AppCompatActivity method ( onRequestPermissionsResult ) as well as LocationListener ones.
So it all goes in the Activity. This is it, without all the code that is required by my app only:
public class RegistrationPersonalDataActivity extends AppCompatActivity implements LocationListener
{
private GeolocationBean geolocation;
private String locationProvider;
private LocationManager locationManager;
/**
* Prepare menu items, load previous entered data if present
*
* @param savedInstanceState data for screen restoration
*
*/
@Override
protected void onCreate ( Bundle savedInstanceState )
{
super.onCreate ( savedInstanceState );
setContentView ( R.layout.activity_registration_personal_data );
locationManager = ( LocationManager ) getSystemService ( Context.LOCATION_SERVICE );
if ( deviceHasGpsSensor () )
{
locationProvider = LocationManager.GPS_PROVIDER;
}
else
{
locationProvider = LocationManager.NETWORK_PROVIDER;
}
}
/**
*
* if permission to access location ( GPS or network, according to availability ) is granted,
* location update listener is registered and checks if location provider is switched on
*
* @param requestCode the code of the request
*
* @param permissions an array of strings with the permission
*
* @param grantResults an array with the corresponding results
*
*/
@Override
public void onRequestPermissionsResult ( int requestCode, @NonNull String[] permissions, int[] grantResults )
{
for ( int i = 0; i < permissions.length; i++ )
{
String permission = permissions[i];
int grantResult = grantResults[i];
if ( permission.equals ( Manifest.permission.ACCESS_FINE_LOCATION ) && grantResult == 0 )
{
try
{
locationManager.requestLocationUpdates ( locationProvider, 1000,
1, this );
if ( ! locationManager.isProviderEnabled ( locationProvider ) )
{
showSettingsAlert ();
}
}
catch ( SecurityException se )
{
// It won't happen, this checked exception is mandatory for
// location updates, but it's inside a method that verifies it
}
}
}
}
/**
* triggers the geolocating process
*
* @param view "Locate me" button
*
*/
public void getGpsLocation ( View view )
{
locationTV.setText ( R.string.please_wait );
// Check app has permission to access GPS sensor
if ( ActivityCompat.checkSelfPermission ( this, Manifest.permission.ACCESS_FINE_LOCATION )
!= PackageManager.PERMISSION_GRANTED )
{
ActivityCompat.requestPermissions ( this, new String[]
{ Manifest.permission.ACCESS_FINE_LOCATION }, 2 );
}
}
// Goes through the steps required by the geoloation process
private void setLocationProcess ( GeolocationBean geolocationBean )
{
if ( geolocationBean.getLatitude () != 0 || geolocationBean.getLongitude () != 0 )
{
locationTV.setText ( geolocationBean.getTownAndPostcode () );
geolocation = geolocationBean;
}
}
// Opens a dialog asking the user to switch on the device location method if it already isn't
private void showSettingsAlert ()
{
final Context context = this.getApplicationContext ();
AlertDialog.Builder alertDialog = new AlertDialog.Builder ( RegistrationPersonalDataActivity.this );
// Title
alertDialog.setTitle ( "Geolocation settings" );
// Message
alertDialog.setMessage ( "Geolocation sensor is switched off. Click on settings to switch it on, then back to return to the app" );
// On pressing Settings button
alertDialog.setPositiveButton ( "Settings", new DialogInterface.OnClickListener ()
{
public void onClick ( DialogInterface dialog, int which )
{
Intent intent = new Intent ( Settings.ACTION_LOCATION_SOURCE_SETTINGS );
intent.setFlags ( Intent.FLAG_ACTIVITY_NEW_TASK );
context.startActivity ( intent );
}
} );
// on pressing cancel button
alertDialog.setNegativeButton ( "Cancel", new DialogInterface.OnClickListener ()
{
public void onClick ( DialogInterface dialog, int which )
{
dialog.cancel ();
}
} );
// Showing Alert Message
alertDialog.show ();
}
// Determine if the device has a GPS sensor or not
private boolean deviceHasGpsSensor ()
{
if ( locationManager == null )
{
return false;
}
final List< String > providers = locationManager.getAllProviders ();
{
if ( providers == null ) return false;
}
return providers.contains ( LocationManager.GPS_PROVIDER );
}
/**
*
* is called when the location changes or at regular intervals
*
* @param location the location with latitude and longitude
*
*/
@Override
public void onLocationChanged ( Location location )
{
double latitude = geolocation.getLatitude ();
double longitude = geolocation.getLongitude ();
geolocation.setLatitude ( location.getLatitude () );
geolocation.setLongitude ( location.getLongitude () );
String townAndPostcode = getTownAndPostcode ( latitude, longitude );
locationTV.setText ( townAndPostcode );
geolocation.setTownAndPostcode ( townAndPostcode );
}
/**
*
* is called when the location sensor is switched on
*
* @param provider the location provider
*
*/
@Override
public void onProviderEnabled ( String provider )
{
try
{
locationManager.requestLocationUpdates ( locationProvider, 1000,
1, this );
}
catch ( SecurityException se )
{
new ExceptionLoggingUtil ( se );
}
}
/**
*
* is called when the location sensor is switched on or off
*
* @param provider the location provider
*
* @param status the new status
*
* @param extras absolutely no idea
*
*/
@Override
public void onStatusChanged ( String provider, int status, Bundle extras ) { }
/**
*
* is called when the location sensor is switched off
*
* @param provider the location provider
*
*/
@Override
public void onProviderDisabled ( String provider ) { }
}