Search code examples
androidgeolocation

Geolocation of Android devices without GPS sensor, such as tablets


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 ();
    }
}

Solution

  • 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 ) { }
    }