9

I have created a service that tracks the device's location as it moves. The service is started in by an Activity that binds to it, and in this activity there is a "Start Tracking" button. When this button is pressed, I need the service to start in the foreground, so that it stores the Locations that the device has moved to, even if the Activity that binds to it is closed, or the app is minimised.

I understand that for the Service to be in the foreground, a notification must be displayed. I have attempted to do so, but I cannot get the notification to show or the Service to work in the foreground when the activity is destroyed.

It appears that notifications have changed in Oreo due to the notification channels, but I cannot figure out what needs to be done differently. The device I am testing this on is on 8.0.0.

Here is mys service:

public class LocationTrackerService extends Service {

    private LocationListener locationListener;
    private LocationManager locationManager;
    private IBinder binder = new LocalBinder();
    private boolean isTracking;
    private ArrayList<Location> trackedWaypoints;
    private String bestProvider;
    private Timer timer;
    private Distance distance;

    @SuppressLint("MissingPermission")
    @Override
    public void onCreate() {
        super.onCreate();

        locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
        Criteria criteria = new Criteria();
        bestProvider = locationManager.getBestProvider(criteria, true);

        isTracking = false;

        locationListener = new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                Intent intent = new Intent("location_update");
                intent.putExtra("latitude", location.getLatitude());
                intent.putExtra("longitude", location.getLongitude());
                sendBroadcast(intent);
                if (isTracking) {
                    if (trackedWaypoints.size() > 1) {
                        distance.add(trackedWaypoints.get(trackedWaypoints.size() - 1).distanceTo(location));
                    }
                    trackedWaypoints.add(location);
                }
            }

            @Override
            public void onStatusChanged(String s, int i, Bundle bundle) { }

            @Override
            public void onProviderEnabled(String s) { }

            @Override
            public void onProviderDisabled(String s) {
                Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);
            }
        };

        locationManager.requestLocationUpdates(bestProvider, 0, 0, locationListener);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (locationManager != null) {
            locationManager.removeUpdates(locationListener);
        }
    }

    public void startTracking() {
        trackedWaypoints = new ArrayList<Location>();
        timer = new Timer();
        distance = new Distance();
        timer.start();
        isTracking = true;
        startInForeground();
    }

    private void startInForeground() {
        Intent notificationIntent = new Intent(this, WorkoutActivity.class);
        PendingIntent pendingIntent =
                PendingIntent.getActivity(this, 0, notificationIntent, 0);

        Notification notification =
                new Notification.Builder(this)
                        .setContentTitle("TEST")
                        .setContentText("HELLO")
                        .setSmallIcon(R.drawable.ic_directions_run_black_24dp)
                        .setContentIntent(pendingIntent)
                        .setTicker("TICKER")
                        .build();

        startForeground(101, notification);
    }

    public void stopTracking() {
        isTracking = false;
        stopForeground(true);
    }

    public boolean isTracking() {
        return isTracking;
    }

    public ArrayList<Location> getTrackedWaypoints() {
        return trackedWaypoints;
    }

    public Timer getTime() {
        timer.update();
        return timer;
    }

    public Distance getDistance() {
        return distance;
    }

    public int getSteps() {
        return 0;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    public class LocalBinder extends Binder {
        public LocationTrackerService getLocationTrackerInstance() {
            return LocationTrackerService.this;
        }
    }
}
KOB
  • 4,084
  • 9
  • 44
  • 88
  • 1
    You do not appear to be calling `startTracking()`. – CommonsWare Dec 02 '17 at 18:29
  • It is called from the activity when the button is pressed. – KOB Dec 02 '17 at 18:30
  • 1
    If your `targetSdkVersion` is 26 or higher, you will need to define a notification channel (if you didn't define it previously) and use that channel ID in the builder. You might also consider switching to `NotificationCompat.Builder`. – CommonsWare Dec 02 '17 at 18:36
  • Defining he notification channel is where I am lost. Do I just set it as one o the integers set for notification channel? – KOB Dec 02 '17 at 18:38
  • See the answer that mac229 posted. You would then use the two-parameter `Builder` constructor and pass `PRIMARY_CHANNEL_ID` in, along with the `Context`. See also [these lines](https://github.com/commonsguy/cw-omnibus/blob/v8.8/Notifications/Foreground/app/src/main/java/com/commonsware/android/foredown/Downloader.java#L49-L56) and [these lines](https://github.com/commonsguy/cw-omnibus/blob/v8.8/Notifications/Foreground/app/src/main/java/com/commonsware/android/foredown/Downloader.java#L139-L150) from one of my sample apps. – CommonsWare Dec 02 '17 at 18:42
  • Ok great, that is working. However, when I close the activity that binds to and starts the service after it has then been set to the foreground, it stops the service and removes the foreground notification. Here is the fragment in the activity that starts the service: https://pastebin.com/CxDyxwwp – KOB Dec 02 '17 at 18:52
  • Well, you're calling `stopService()`. That stops the service. – CommonsWare Dec 02 '17 at 18:53

2 Answers2

19

Try to change your startInForeground() method with this code:

private void startInForeground() {
        Intent notificationIntent = new Intent(this, WorkoutActivity.class);
        PendingIntent pendingIntent=PendingIntent.getActivity(this,0,notificationIntent,0);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this,NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(R.drawable.shsl_notification)
                .setContentTitle("TEST")
                .setContentText("HELLO")
                .setTicker("TICKER") 
                .setContentIntent(pendingIntent);
        Notification notification=builder.build();
        if(Build.VERSION.SDK_INT>=26) {
            NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
            channel.setDescription(NOTIFICATION_CHANNEL_DESC);
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
        }
        startForeground(NOTIFICATION_ID, notification);
}

So, when your Android version is Oreo (or higher) notification channel will be created, otherwise not.

colens
  • 467
  • 3
  • 12
  • 3
    thanks it worked , minor update to the code , instead of using 26 you can use Build.VERSION_CODES.O which will increase the readability of your code . – A.Alqadomi Jan 01 '18 at 10:40
  • From which library are you using "NotificationCompat.Builder" ? The variants I found only accept one parameter (Context). – j3App Jul 13 '18 at 08:18
  • Just found the answer. You need to set ***ALL*** google support libs to minimum 26.1.0 – j3App Jul 13 '18 at 09:01
2

Before show notification you have to create notification channel:

private void createNotificationChannel() {
    if (Build.VERSION_CODES.O <= Build.VERSION.SDK_INT) {
        NotificationChannel notificationChannel =
                new NotificationChannel(PRIMARY_CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
        notificationManager.createNotificationChannel(notificationChannel);
    }
}

and then change create notification builder

new Notification.Builder(context, PRIMARY_CHANNEL_ID)
mac229
  • 4,319
  • 5
  • 18
  • 24