0

"I am trying to get the location in the background but it is showing this error. I am unable to get the location anyway. Notification not working. How should my doMystuff() function look like in the main activity? Can someone please provide how can I register the broadcast receiver in manifest and how to implement the code with onButton Click." UPDATE[2]: "Here I have just written the run-time permission to be accessed. Now on button click "startButton" in the doStuff function should I write startService or startForegroundService?" "MAIN ACTIVITY"

package com.example.locationrunandall;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    private Button butt1, butt2;
    public static final int MY_PERMISSIONS_REQUEST_LOCATION = 99;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textview);
        butt1 = findViewById(R.id.butt1);
        butt2 = findViewById(R.id.butt2);


    }



    public void startButton(View view) {
        fetchCode();
    }

    private void fetchCode() {
        if (ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {

            // Permission is not granted
            if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                    Manifest.permission.ACCESS_FINE_LOCATION)) {
                // Show an explanation to the user *asynchronously* -- don't block
                new AlertDialog.Builder(this)
                        .setTitle("Required Location")
                        .setMessage("You have to get this permission to access the feature.")
                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                ActivityCompat.requestPermissions(MainActivity.this,
                                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                                        MY_PERMISSIONS_REQUEST_LOCATION);
                            }
                        })
                        .setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        }).create().show();
            } else {
                // No explanation needed; request the permission
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        MY_PERMISSIONS_REQUEST_LOCATION);

            }
        } else {
            // Permission has already been granted
            doMyStuff();
        }
    }

    private void doMyStuff() {
        Toast.makeText(this, "Working", Toast.LENGTH_SHORT).show();
        Intent intent = new Intent(this,ForeService.class);
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
            startForegroundService(intent);
        }else{
            startService(intent);
        }
    }   
    public void stopButton(View view) {
        Intent intent = new Intent(this,ForeService.class);
        stopService(intent);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            if(requestCode==MY_PERMISSIONS_REQUEST_LOCATION){
                if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){
                    Intent gpsOptionsIntent = new Intent(
                            android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                    startActivity(gpsOptionsIntent);
                }else
                {
                }
            }
    }
}
  • Are you able to see the pinned notification for the service when app is removed from the background? If not its likely that the service is killed when the activity is killed. You can override `OnDestroy()` in both `MainActivity` and `LocationUpdatesService` and add some logs to check whats happening. – Arun May 28 '19 at 08:02
  • Yes, the notification also went away when the app is removed from the background. – Hirok Burman May 28 '19 at 09:17
  • Then the service is killed when the app is removed from background. It might be because you are binding the service and activity. Try removing the bind/unbind mechanism and run the service independently. If its still getting killed then try `return START_STICKY;' in your `onStartCommand' or restarting the service again in `onDestroy` or `onTaskRemoved`. You can check the answers [here](https://stackoverflow.com/questions/9696861/how-can-we-prevent-a-service-from-being-killed-by-os) for more help on keeping your service active. – Arun May 28 '19 at 10:11
  • @Arun Sir, please can you check my new implementation and tell me what am I doing wrong? – Hirok Burman May 28 '19 at 18:02
  • i have posted an answer with working code. You can use that – Arun May 29 '19 at 06:53

2 Answers2

2

You should use ForegroundService instead of Service. Call startForegroundService() when you want to start it.

Next in this new ForegroundService in onCreate method call startForeground(). With startForeground you have to pass a notification to inform the user that service is running.

Moreover I've found that some devices like HUAWEI have a feature called “power-intensive app monitor“. It kills every app that runs in the background for a long time unless user gives special permissions to it. The path to do this: Settings -> Security & privacy -> Location services -> recent location requests: YOUR APP NAME -> Battery -> uncheck Power-intensive prompt, App launch: Manage manually: check all three positions: Auto-launch, secondary launch, run in background.

I don’t know is there a way to do this programmatically. I think the best way is to create a sort of help activity and explain the user what to do if application won’t work.

MarkWalczak
  • 1,532
  • 3
  • 16
  • 24
0

Service code to get Location update every 10 seconds

public class LocationService extends Service implements com.google.android.gms.location.LocationListener,
        GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener{

    private GoogleApiClient mGoogleApiClient;
    private LocationRequest mLocationRequest;

    private String mChannelId = "my_service";
    private int mNotificationId = 1;
    private NotificationCompat.Builder mNotifBuilder = new NotificationCompat.Builder(this, mChannelId);
    private Notification mNotif;
    private NotificationManager mNotificationManager;
    private boolean mRestartService = true;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void initGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .addApi(LocationServices.API)
                .build();
        mGoogleApiClient.connect();
    }

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

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null && intent.getAction() != null) {
            String mAction = intent.getAction();
            if(mAction.equals("start_service")){
                startForegroundService();
            }else if(mAction.equals("stop_service")){
                stopForegroundService();
            }
        }
        return START_NOT_STICKY;
    }

    @Override
    public void onConnected(@Nullable Bundle bundle) {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(10000);
        mLocationRequest.setFastestInterval(5000);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);

        try {
            LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {

    }

    @Override
    public void onLocationChanged(Location location) {
        Toast.makeText(this, "Location Updated", Toast.LENGTH_SHORT).show();
        //Broadcast Location
    }

    private void createNotificationChannel(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            String description = "Foreground Service Channel";
            String name = "My Background Channel";
            int importance = NotificationManager.IMPORTANCE_HIGH;
            NotificationChannel channel = new NotificationChannel(mChannelId, name, importance);
            channel.setDescription(description);
            mNotificationManager.createNotificationChannel(channel);
        }
    }

    private void startForegroundService() {
        //Initializing Notifications and intents
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

        createNotificationChannel();

        mNotifBuilder.setContentText("Notification Content")
                .setContentTitle("Notification Title")
                .setSmallIcon(R.drawable.ic_notify)
                .setContentIntent(pendingIntent)
                .setOngoing(true)
                .setAutoCancel(true);

        mNotif = mNotifBuilder.build();
        startForeground(mNotificationId, mNotif);
    }

    private void stopForegroundService() {
        mRestartService = false;
        if (mGoogleApiClient != null && mGoogleApiClient.isConnected()) {
            LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
        }
        stopForeground(true);
    }

    /*@Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        Toast.makeText(this, "Task Removed", Toast.LENGTH_SHORT).show();
        if (mRestartService){
            Intent intent = new Intent(this, RestartService.class);
            intent.setAction("restart_service");
            sendBroadcast(intent);
        }
    }*/
}

Then in your Activity add a button or a toggle button to start and stop the service. You can also do it with a button in the Notification.

private void startService() {
        Intent intent = new Intent(MainActivity.this, LocationService.class);
        intent.setAction("start_service");
        startService(intent);
    }

private void stopService() {
    Intent intent = new Intent(MainActivity.this, LocationService.class);
    intent.setAction("stop_service");
    startService(intent);
}

The service should run even the app is killed. In some devices like xiaomi, OPPO, VIVO...etc it might get killed regardless of what you do in the service. In those you can try to restart the service again with a broadcast to start the service when it is killed.

public class RestartService extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if(action.equals("restart_service")){
            Intent intent1 = new Intent(context, LocationService.class);
            intent.setAction("start_service");
            context.startService(intent1);
        }
    }
}

You can add it in onTaskRemoved() if the service is killed when the activity is removed from the background. You can add it in onDestroy() if it is killed sometime after the activity is removed from the background

public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        Toast.makeText(this, "Task Removed", Toast.LENGTH_SHORT).show();
        if (mRestartService){
            Intent intent = new Intent(this, RestartService.class);
            intent.setAction("restart_service");
            sendBroadcast(intent);
        }
    }

Even with this the service might be killed in some mobiles. In those cases you need to ask the user to exclude the service from battery optimizations and also allow other exceptions for it to run freely.

Arun
  • 321
  • 2
  • 8
  • sir, I have used your code and I have added the updated MainActivity above. – Hirok Burman May 29 '19 at 07:27
  • Sir what should I write in the doMyStuff function to get the current location and how do I know if location update is working or not? – Hirok Burman May 29 '19 at 07:28
  • and how can I update my textview in mainActivity from location that I sm getting in the background. – Hirok Burman May 29 '19 at 07:30
  • @HirokBurman Everytime location is updated, the `onLocationChanged()` method will be called and you can use the updated location. I have set it to update location every 10 seconds, you can change it in the `LocationRequest` if you want. Your previous implementation had a Broadcast mechanism to send data to activity. You can use that to update the textview in your activity. – Arun May 29 '19 at 07:39
  • `` Add the service in the android manifest – Arun May 29 '19 at 07:41
  • and what should I do in the doMyStuff function. Do I have to call startService()? – Hirok Burman May 29 '19 at 07:43
  • but in build version.O do I have to use startForegroundService(): – Hirok Burman May 29 '19 at 07:43
  • Isn't fusedlocationApi depreciated? – Hirok Burman May 29 '19 at 07:58
  • I am still using an older play-services version in my gradle, so i dont have any warning. If you are getting a warning, then you can switch to `FusedLocationProviderClient`. [Here](https://developer.android.com/training/location#documentation) is the documentation for the same. – Arun May 29 '19 at 08:33
  • @HirokBurman In oreo and above you can use `startForegroundService()`, but `startService()` should still work. Also if FusedLocationApi works, you can ignore the warning for now till your service is able to run without getting killed. – Arun May 29 '19 at 08:45