0

I am trying to write an app that tracks location in background and sends data to a server -- to monitor where my family members are, for example.
Currently I am playing with https://github.com/android/location-samples, particularly with LocationUpdatesBackgroundKotlin that seems to be the best way to receive location updates, but

after receiving about 8-10 location updates in background, the gps icon on the status bar dies without anyway notifying the application (here is the android/phone info, but I want the app to be compatible to Android 5.1).
I want to somehow know is receiving location updates alive or not and re-start it if it's dead (restarting receiving updates with fusedLocationClient.requestLocationUpdates on line 105 of MyLocationManager helps receiving further updates, but I have to monitor the status by eye).

Is there any way out, or a more reliable approach? Thanks.
P.S. Have been writing for android for a week.

Alexey Burdin
  • 122
  • 2
  • 9

2 Answers2

2

In order to get location constantly from the application, you have to use the foreground service in which you can initialize the location manager and get the location update constantly as per the parameters that have been set. Also, make sure you have background location permission given as it is the requirement after API level 29. Following is the very basic flow of how it can be achieved. Make sure to start this service after taking the location permission:

public class MyCustomService extends Service implements
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    private GoogleApiClient mGoogleApiClient;
    private PowerManager.WakeLock mWakeLock;
    private LocationRequest mLocationRequest;
    private boolean mInProgress;

    private Boolean servicesAvailable = false;

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

    private static final int UPDATE_INTERVAL_IN_SECONDS = 120;
    private static final int MILLISECONDS_PER_SECOND = 1000;
    public static final long UPDATE_INTERVAL = MILLISECONDS_PER_SECOND * UPDATE_INTERVAL_IN_SECONDS;
    private static final int FASTEST_INTERVAL_IN_SECONDS = 60;
    public static final long FASTEST_INTERVAL = MILLISECONDS_PER_SECOND * FASTEST_INTERVAL_IN_SECONDS;


    @Override
    public void onCreate() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService();
        }

        mInProgress = false;
        mLocationRequest = LocationRequest.create();
        mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
        mLocationRequest.setInterval(UPDATE_INTERVAL);
        mLocationRequest.setFastestInterval(FASTEST_INTERVAL);
        mLocationRequest.setSmallestDisplacement(100);
        servicesAvailable = servicesConnected();

        /*
         * Create a new location client, using the enclosing class to
         * handle callbacks.
         */
        setUpLocationClientIfNeeded();
        super.onCreate();

    }


    private void setUpLocationClientIfNeeded() {
        if (mGoogleApiClient == null)
            buildGoogleApiClient();
    }

    /*
     * Create a new location client, using the enclosing class to
     * handle callbacks.
     */
    protected synchronized void buildGoogleApiClient() {
        this.mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
    }

    private boolean servicesConnected() {

        // Check that Google Play services is available
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);

        // If Google Play services is available
        if (ConnectionResult.SUCCESS == resultCode) {

            return true;
        } else {

            return false;
        }
    }

    /* Used to build and start foreground service. */
    private void startForegroundService() {
        Intent notificationIntent = new Intent(this, HomeActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        String CHANNEL_ID = "1";
        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setSmallIcon(R.drawable.noti_icon)
                .setPriority(Notification.PRIORITY_LOW)
                .setOngoing(true)
                .setAutoCancel(false)
                .setContentTitle("ServiceTitle")
                .setContentText("Service Reason text")
                .setTicker("TICKER")
                .setChannelId(CHANNEL_ID)
                .setVibrate(new long[]{0L})
                .setContentIntent(pendingIntent);
        Notification notification = builder.build();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "NOTIFICATION_CHANNEL_NAME", NotificationManager.IMPORTANCE_HIGH);
            channel.setDescription("NOTIFICATION_CHANNEL_DESC");
            channel.enableVibration(false);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
            notificationManager.createNotificationChannel(channel);
        }
        startForeground(123, notification);
    }

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

        this.mInProgress = false;

        if (this.servicesAvailable && this.mGoogleApiClient != null) {
            this.mGoogleApiClient.unregisterConnectionCallbacks(this);
            this.mGoogleApiClient.unregisterConnectionFailedListener(this);
            this.mGoogleApiClient.disconnect();
            // Destroy the current location client
            this.mGoogleApiClient = null;
        }

        if (this.mWakeLock != null) {
            this.mWakeLock.release();
            this.mWakeLock = null;
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        super.onStartCommand(intent, flags, startId);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService();
        }
        setUpLocationClientIfNeeded();
        if (!mGoogleApiClient.isConnected() || !mGoogleApiClient.isConnecting() && !mInProgress) {
            mInProgress = true;
            mGoogleApiClient.connect();
        }
        return START_STICKY;

    }


    @Override
    public void onConnected(@Nullable Bundle bundle) {
        Intent intent = new Intent(this, LocationReceiver.class);
        PendingIntent pendingIntent = PendingIntent
                .getBroadcast(this, 54321, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        if (this.mGoogleApiClient != null)
            LocationServices.FusedLocationApi.requestLocationUpdates(this.mGoogleApiClient,
                    mLocationRequest, pendingIntent);
    }

    @Override
    public void onConnectionSuspended(int i) {
        // Turn off the request flag
        mInProgress = false;
        // Destroy the current location client
        mGoogleApiClient = null;
    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        mInProgress = false;

        /*
         * 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()) {

            // If no resolution is available, display an error dialog
        } else {

        }
    }
}

Here is the Location receiver class which you need to register in the Androidmanifest file also

public class LocationReceiver extends BroadcastReceiver {

    private String TAG = "LOCATION RECEIVER";

    private LocationResult mLocationResult;
    private Context context;
    Location mLastLocation;
    

    @Override
    public void onReceive(Context context, Intent intent) {
        // Need to check and grab the Intent's extras like so
        this.context = context;
        if (LocationResult.hasResult(intent)) {
            this.mLocationResult = LocationResult.extractResult(intent);
            if (mLocationResult.getLocations().get(0).getAccuracy() < 100) {

                // DO WHATEVER YOU WANT WITH LOCATION
            }
        }
    }
}

Permission that is required:

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

Note: Some of the methods like FusedLocationApi and isGooglePlayServicesAvailable in above code is deprecated

Rudrik Patel
  • 676
  • 6
  • 13
  • I don't think it's the solution for two reasons: first, the foreground service runs in the main thread so it's not very different from main activity. second: you're saying that official google examples are wrong, it's believeable but unlikely. I'll give it a try. Thanks. – Alexey Burdin Oct 09 '20 at 07:59
  • As per the Google guidelines you can not fetch the location in the background without user's consent so for that you have to use foreground service otherwise it won't work. And I am not saying anything about the official Google example so don't take it on that context. I have used foreground service to fetch the constant location in my application also and it works just fine as long as you follow the Google's privacy policy guidelines. – Rudrik Patel Oct 09 '20 at 08:08
  • Okay. 1. I'm not saying anything about "without user's consent", there's a permission to retrieve locations in background (but android 8 simply doesn't have it, so I did cut it off from the code while testing) 2. I want to fetch changing location, e.g. where a bus is, not a constant one. 3. Please consider a possibility to share a part of working code if it's convinient. Thanks. – Alexey Burdin Oct 09 '20 at 08:16
  • @AlexeyBurdin Provided a basic code from which you can have a general idea of how it can be achieved. Hope this helps. – Rudrik Patel Oct 09 '20 at 10:49
  • Thank you this helped me alot. but you forgot to mention the service class in manifest file. which would be And to start the service from other main activity would be startService(new Intent(this,MyCustomService.class)); – Malick Usman Dec 25 '21 at 15:02
0

It comes out for Android 5 Lollipop it's better (much more stable) to use third-party library for bacground location receiving, namely io.nlopez.smartlocation.SmartLocation from 'io.nlopez.smartlocation:library:3.3.3', as decribed here, along with a foreground service, as Rudrik Patel mentioned. Works like a charm.

Alexey Burdin
  • 122
  • 2
  • 9