0

I want to continuously get location updates in the background using service even app is paused I tried much but when the app is paused it won't get any of the user's locations.

Here is my service.java

package com.app.testservices;


import android.app.AlarmManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.os.IBinder;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;

public class MyService extends Service {
    private static final long UPDATE_INTERVAL_IN_MILLISECONDS = 3000;
    private FusedLocationProviderClient mFusedLocationClient;
    private LocationRequest locationRequest;
    private LocationSettingsRequest locationSettingsRequest;
    Notification notification;
    NotificationCompat.Builder builder;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        prepareNotification(intent);
        startLocationUpdates();
        return START_STICKY;
    }

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


    //Location Callback
    private LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            super.onLocationResult(locationResult);
            Location currentLocation = locationResult.getLastLocation();
            Log.d("Locations", currentLocation.getLatitude() + "," + currentLocation.getLongitude());
            //Share/Publish Location
            builder.setContentText(currentLocation.getLatitude() + "," + currentLocation.getLongitude());
            Notification nm = builder.build();
            startForeground(2, nm);
        }
    };

    private void startLocationUpdates() {
        mFusedLocationClient.requestLocationUpdates(this.locationRequest,
                this.locationCallback, Looper.getMainLooper());
    }

    public void prepareNotification(Intent intent) {
        String input = intent.getStringExtra("input");
        Intent notificationIntent = new Intent(this, MyService.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,
                0, notificationIntent, 0);
        builder = new NotificationCompat.Builder(this, MyChannel.CHANNEL_ID)
                .setContentTitle("My notification")
                .setContentText(input)
                .setSmallIcon(R.drawable.ic_android_black_24dp)
                .setContentIntent(pendingIntent);
        notification = builder.build();
        startForeground(1, notification);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction("restartservice");
        broadcastIntent.setClass(this, Restarter.class);
        this.sendBroadcast(broadcastIntent);
    }

    private void initData() {
        locationRequest = LocationRequest.create();
        locationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        locationRequest.setNumUpdates(Integer.MAX_VALUE);
        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(getApplicationContext());

    }

    @Override
    public void onTaskRemoved(Intent rootIntent){
        Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass());
        restartServiceIntent.setPackage(getPackageName());

        PendingIntent restartServicePendingIntent = PendingIntent.getService(getApplicationContext(), 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT);
        AlarmManager alarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
        alarmService.set(
                AlarmManager.ELAPSED_REALTIME,
                SystemClock.elapsedRealtime() + 10,
                restartServicePendingIntent);

        super.onTaskRemoved(rootIntent);
    }
}

Channel.java

package com.app.testservices;

import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.os.Build;

public class MyChannel extends Application {
    public static final String CHANNEL_ID = "serviceChannel";

    @Override
    public void onCreate() {
        super.onCreate();
        createNotificationChannel();
    }
    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel serviceChannel = new NotificationChannel(
                    CHANNEL_ID,
                    "Notification service",
                    NotificationManager.IMPORTANCE_DEFAULT
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            manager.createNotificationChannel(serviceChannel);
        }
    }
}

I also tried to write a restarter to restart the service to fetch the location Restarter.java

package com.app.testservices;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;

public class Restarter extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i("Broadcast Listened", "Service tried to stop");
        Toast.makeText(context, "Service restarted", Toast.LENGTH_SHORT).show();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(new Intent(context, MyService.class));
        } else {
            context.startService(new Intent(context, MyService.class));
        }
    }
}

Here is my MainActivity.java

package com.app.testservices;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Looper;
import android.provider.Settings;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import com.google.android.gms.common.api.ResolvableApiException;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.LocationSettingsRequest;
import com.google.android.gms.location.LocationSettingsResponse;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

public class MainActivity extends AppCompatActivity {
    private EditText etInput;
    protected static final int REQUEST_CHECK_SETTINGS = 0x1;
    FusedLocationProviderClient mFusedLocationClient;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        etInput = findViewById(R.id.et_input_text);
        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{
                    Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION
            }, 1);

            return;
        }
    }

    public void startService(View view) {
        String input = etInput.getText().toString();
        Intent serviceIntent = new Intent(this, MyService.class);
        serviceIntent.putExtra("input", input);
        startService(serviceIntent);
    }

    public void stopService(View view) {
        Intent serviceIntent = new Intent(this, MyService.class);
        stopService(serviceIntent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {


        // If request is cancelled, the result arrays are empty.
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            mFusedLocationClient = LocationServices.getFusedLocationProviderClient(MainActivity.this);
                    if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                        mFusedLocationClient.getLastLocation().addOnCompleteListener(new OnCompleteListener<Location>() {
                            @Override
                            public void onComplete(@NonNull Task<Location> task) {
                                Location location = task.getResult();
                                LocationCallback mLocationCallback = new LocationCallback() {

                                    @Override
                                    public void onLocationResult(LocationResult locationResult) {
                                        Location mLastLocation = locationResult.getLastLocation();
                                    }
                                };
                                if (location == null) {
                                    LocationRequest mLocationRequest = new LocationRequest();
                                    mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
                                    mLocationRequest.setInterval(5);
                                    mLocationRequest.setFastestInterval(0);
                                    mLocationRequest.setNumUpdates(2);

                                    mFusedLocationClient = LocationServices.getFusedLocationProviderClient(MainActivity.this);
                                    if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                                        // TODO: Consider calling
                                        //    ActivityCompat#requestPermissions
                                        ActivityCompat.requestPermissions(MainActivity.this, new String[]{
                                                Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION
                                        }, 1);

                                        return;
                                    }
                                    mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper());

                                } else {
                                    System.out.println("Location: " + location);

                                }
                            }
                        });
                    }

        }
    }

    public void enableLocationSettings() {
        LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            LocationRequest locationRequest = LocationRequest.create()
                    .setInterval(5)
                    .setFastestInterval(0)
                    .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

            LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().addLocationRequest(locationRequest);
            LocationServices.getSettingsClient(this).checkLocationSettings(builder.build())
                    .addOnSuccessListener(this, (LocationSettingsResponse response) -> {
                        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(MainActivity.this);
                                if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                                    mFusedLocationClient.getLastLocation().addOnCompleteListener(task -> {
                                        Location location = task.getResult();
                                        LocationCallback mLocationCallback = new LocationCallback() {

                                            @Override
                                            public void onLocationResult(LocationResult locationResult) {
                                                Location mLastLocation = locationResult.getLastLocation();
                                            }
                                        };
                                        if (location == null) {
                                            LocationRequest mLocationRequest = new LocationRequest();
                                            mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
                                            mLocationRequest.setInterval(5);
                                            mLocationRequest.setFastestInterval(0);
                                            mLocationRequest.setNumUpdates(2);

                                            mFusedLocationClient = LocationServices.getFusedLocationProviderClient(MainActivity.this);
                                            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                                                // TODO: Consider calling
                                                //    ActivityCompat#requestPermissions
                                                ActivityCompat.requestPermissions(MainActivity.this, new String[]{
                                                        Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION
                                                }, 1);

                                                return;
                                            }
                                            mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper());

                                        } else {
                                            System.out.println("Location: " + location);

                                        }
                                    });
                                }

                            }).addOnFailureListener(this, ex -> {
                if (ex instanceof ResolvableApiException) {
                    // Location settings are NOT satisfied,  but this can be fixed  by showing the user a dialog.
                    try {
                        startActivityForResult(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 1);
                        ResolvableApiException resolvable = (ResolvableApiException) ex;
                        resolvable.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS);
                    } catch (IntentSender.SendIntentException sendEx) {
                        // Ignore the error.
                    }
                }
            });
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (REQUEST_CHECK_SETTINGS == requestCode) {
            if (Activity.RESULT_OK == resultCode) {
            }
        }
    }
}

Any help or guidance will be appreciated. Moreover, I also tried Android How to get user location continuously even your app is killed this but it didn't work for me.

a_local_nobody
  • 7,947
  • 5
  • 29
  • 51

2 Answers2

0

You need to keep a foreground service always running. You need to start it when the app is in the foreground and then keep it running all the time even when the app is in the background.

kingston
  • 11,053
  • 14
  • 62
  • 116
  • Can you elaborate it with some piece of code? – Ghazanfar Ateeb Mar 07 '22 at 09:30
  • the thing that you do here `startForeground(2, nm);` needs to be done immediately and you should not stop the service if you want to keep the location updates – kingston Mar 07 '22 at 09:31
  • So, I must not start a new foreground with it at `startForeground(2, nm);`? – Ghazanfar Ateeb Mar 07 '22 at 09:33
  • You should start a foreground service once and keep it running all the time. You can update the notification if you want – kingston Mar 07 '22 at 09:34
  • After searching I found this Note: The Google Play store has updated its policy concerning device location, restricting background location access to apps that need it for their core functionality and meet related policy requirements. Adopting these best practices doesn't guarantee Google Play approves your app's usage of location in the background. – Ghazanfar Ateeb Mar 07 '22 at 10:46
  • That doesn't have to do with your issue. Eventually it would be an issue if you want to publish the app and Google decides that it is not justifiable the fact that you are requesting the location update. That happens if you are doing only for Ads for example – kingston Mar 07 '22 at 11:41
  • Actually, I am working on a vehicle tracking app so the user can see continuous location. Therefore, I want a continuous location as my background service even my app is killed or dozed off. – Ghazanfar Ateeb Mar 07 '22 at 16:43
  • After one day of trying I came to know that my service is running but it don't fetch location when app is paused – Ghazanfar Ateeb Mar 08 '22 at 05:05
  • Did you give the permission to fetch the location in the background? – kingston Mar 08 '22 at 17:05
  • Yes permissions and location access are provided. – Ghazanfar Ateeb Mar 08 '22 at 17:41
  • 1
    From the code you posted I don't see you are asking for ACCESS_BACKGROUND_LOCATION. BTW you don't need all that `Restarter` thing – kingston Mar 09 '22 at 12:06
  • Well ACCESS_BACKGROUND_LOCATION was in `manifest.xml` though I also tried to request permission for ACCESS_BACKGROUND_LOCATION then no permission popped up. – Ghazanfar Ateeb Mar 09 '22 at 15:49
  • You need to ask the normal permission first and when that one is granted you can ask for the background one. If you want to publish the app on Google Play Store you also need to show a rationale screen to explain why you need these permissions. – kingston Mar 09 '22 at 16:04
  • OK. I'll try this and will let you know. BTW thanks for this much guidance. As for publishing This will not be published on Google Play Store – Ghazanfar Ateeb Mar 09 '22 at 16:07
0

You should use a Foreground Service with a partial wakelock, in order to prevent the phone from sleeping. Here below an example of Foreground Service Class that implements a Wakelock:

public class ICService extends Service {
    
    private static final int ID = 1;                        // The id of the notification
    
    private NotificationCompat.Builder builder;
    private NotificationManager mNotificationManager;
    private PowerManager.WakeLock wakeLock;                 // PARTIAL_WAKELOCK
    
    /**
     * Returns the instance of the service
     */
    public class LocalBinder extends Binder {
        public ICService getServiceInstance(){
            return ICService.this;
        }
    }
    private final IBinder mBinder = new LocalBinder();      // IBinder
    
    @Override
    public void onCreate() {
        super.onCreate();
        // PARTIAL_WAKELOCK
        PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"INSERT_YOUR_APP_NAME:wakelock");
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        startForeground(ID, getNotification());
        return START_NOT_STICKY;
    }
    
    @SuppressLint("WakelockTimeout")
    @Override
    public IBinder onBind(Intent intent) {
        if (wakeLock != null && !wakeLock.isHeld()) {
            wakeLock.acquire();
        }
        return mBinder;
    }
    
    @Override
    public void onDestroy() {
        // PARTIAL_WAKELOCK
        if (wakeLock != null && wakeLock.isHeld()) {
            wakeLock.release();
        }
        super.onDestroy();
    }

    private Notification getNotification() {
        final String CHANNEL_ID = "YOUR_SERVICE_CHANNEL";

        builder = new NotificationCompat.Builder(this, CHANNEL_ID);
        //builder.setSmallIcon(R.drawable.ic_notification_24dp)
        builder.setSmallIcon(R.mipmap.YOUR_RESOURCE_ICON)
            .setColor(getResources().getColor(R.color.colorPrimaryLight))
            .setContentTitle(getString(R.string.app_name))
            .setShowWhen(false)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .setCategory(NotificationCompat.CATEGORY_SERVICE)
            .setOngoing(true)
            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
            .setContentText(composeContentText());

        final Intent startIntent = new Intent(getApplicationContext(), ICActivity.class);
        startIntent.setAction(Intent.ACTION_MAIN);
        startIntent.addCategory(Intent.CATEGORY_LAUNCHER);
        startIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 1, startIntent, 0);
        builder.setContentIntent(contentIntent);
        return builder.build();
    }
}

Obviously you still have also to disable battery optimization for your app. You can browse a real working example of this service on a GPS Logging app here.

Don't forget to declare your service type as "location" into your AndroidManifest.xml, in order to allow your application to receive the GPS updates also after a momentary signal loss when running in background, and to add the FOREGROUND_SERVICE and WAKE_LOCK permissions:

In your manifest you should declare:

    ...
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    ...
    <!-- Recommended for Android 9 (API level 28) and lower. -->
    <!-- Required for Android 10 (API level 29) and higher. -->
    <service
        android:name="MyGPSService"
        android:foregroundServiceType="location" ... >
    </service>
    ...

As a note, if the location requests start when the app is in the foreground, you don't need to request the android.permission.ACCESS_BACKGROUND_LOCATION permission to continue to receive locations in background, also in case of momentary signal loss.
the android.permission.ACCESS_FINE_LOCATION (and, if you target API31, also android.permission.ACCESS_COARSE_LOCATION) is enough for your use.

Graziano
  • 313
  • 1
  • 4
  • 11
  • Sorry for the late reply, as I was unwell, though, I tried everything even I tried your code but it didn't work for me. Actually, the service was running all the time but it stops getting location after sometimes I don't know why. – Ghazanfar Ateeb Mar 20 '22 at 15:10
  • It could be due to Android system. Try to set the power saving of your phone to "Performance" instead of "Power saving". – Graziano Mar 24 '22 at 13:00