javaandroidandroid-studiofusedlocationproviderclient

How to access location in background Android 12?


I am making an application that fetch last known location when user clicked power key two times. I am using foreground service to register the broadcast receiver and for location I am using fusedlocationproviderclient. The user can fetch the location while app is in background. My problem is I am only able to fetch location for one time only. Second time location is null. How can I solve it?

Note: It is working fine in android 7 version.

Service:

public class ScreenOnOffBackgroundService extends Service {
private ScreenOnOffReceiver screenOnOffReceiver = null;

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

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    createNotificationChannel();
    Intent mainIntent = new Intent(this, DashboardActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this,0,mainIntent, 0);
    Notification notification = new NotificationCompat.Builder(this,"safetyId")
            .setContentTitle("Safety Service")
            .setContentText("Press power key to send alert")
            .setSmallIcon(R.drawable.android)
            .setContentIntent(pendingIntent)
            .build();
    startForeground(1,notification);
    return START_STICKY;
}

@Override
public void onCreate() {
    super.onCreate();
    // Create an IntentFilter instance.
    IntentFilter intentFilter = new IntentFilter();
    // Add network connectivity change action.
    intentFilter.addAction("android.intent.action.SCREEN_ON");
    // Set broadcast receiver priority.
    intentFilter.setPriority(100);
    screenOnOffReceiver = new ScreenOnOffReceiver();
    HandlerThread broadcastHandlerThread = new HandlerThread("SafetyThread");
    broadcastHandlerThread.start();
    Looper looper = broadcastHandlerThread.getLooper();
    Handler broadcastHandler = new Handler(looper);
    registerReceiver(screenOnOffReceiver,intentFilter,null,broadcastHandler);
}

private void createNotificationChannel() {

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ){
        NotificationChannel channel = new NotificationChannel(
                "safetyId",
                "Safety Service",
                NotificationManager.IMPORTANCE_DEFAULT
        );
        NotificationManager manager = getSystemService(NotificationManager.class);
        manager.createNotificationChannel(channel);
    }
}


@Override
public void onDestroy() {

    stopForeground(true);
    stopSelf();
    // Unregister screenOnOffReceiver when destroy.
    if(screenOnOffReceiver!=null)
    {
        unregisterReceiver(screenOnOffReceiver);
    }
}
}

Broadcast Receiver:

public class ScreenOnOffReceiver extends BroadcastReceiver {

private int count = 0;

@Override
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    if (Intent.ACTION_SCREEN_ON.equals(action) || Intent.ACTION_SCREEN_OFF.equals(action)) {
        count++;
        if (count == 2) {
            count = 0;
            DashboardActivity.getInstance().getLastLocation();
        }
    }
}
}

Dashboard Activity:

public class DashboardActivity extends AppCompatActivity {
public Button btnAlert;
private static DashboardActivity instance;
private final static int REQUEST_CODE = 123;
private FusedLocationProviderClient locationProviderClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_dashboard);
    instance = this;
    registerService();
    btnAlert = findViewById(R.id.btnAlert);
    locationProviderClient = LocationServices.getFusedLocationProviderClient(this);
    btnAlert.setOnClickListener(view -> getLastLocation());
}

public static DashboardActivity getInstance(){
    return instance;
}

public void registerService(){
    if(!foregroundServiceRunning()){
        Intent backgroundService = new Intent(this, ScreenOnOffBackgroundService.class);
        ContextCompat.startForegroundService(this,backgroundService);
    }
}

public boolean foregroundServiceRunning(){
    ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    for(ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)){
        if(ScreenOnOffBackgroundService.class.getName().equals(service.service.getClassName())){
            return true;
        }
    }
    return false;
}

@SuppressLint("MissingPermission")
public void getLastLocation() {
    // check if permissions are given
    if (checkPermissions()) {
        // check if location is enabled
        if (isLocationEnabled()) {

            locationProviderClient.getLastLocation().addOnCompleteListener(task -> {
                Location location = task.getResult();
                if (location == null) {
                    requestNewLocationData();
                } else {
                    Toast.makeText(this, "Latitude: "+location.getLatitude(), Toast.LENGTH_LONG).show();
                }
            });
        } else {
            Toast.makeText(this, "Please turn on your location", Toast.LENGTH_LONG).show();
            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
            startActivity(intent);
        }
    } else {
        requestPermissions();
    }
}


private boolean checkPermissions() {
    return ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}

private void requestPermissions() {

    String[] permissionArray = new String[]{
            android.Manifest.permission.ACCESS_COARSE_LOCATION,
            android.Manifest.permission.ACCESS_FINE_LOCATION,};
    ActivityCompat.requestPermissions(this, permissionArray, REQUEST_CODE);
}

@SuppressLint("MissingPermission")
private void requestNewLocationData() {

    LocationRequest mLocationRequest = LocationRequest.create()
            .setInterval(100)
            .setFastestInterval(3000)
            .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
            .setMaxWaitTime(100).setNumUpdates(1);

    locationProviderClient = LocationServices.getFusedLocationProviderClient(this);
    locationProviderClient.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.myLooper());
}

private final LocationCallback mLocationCallback = new LocationCallback() {

    @Override
    public void onLocationResult(LocationResult locationResult) {
        Location mLastLocation = locationResult.getLastLocation();
    }
};

private boolean isLocationEnabled() {
    LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
}

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

    if (requestCode == REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            getLastLocation();
        }
    }

    if (requestCode == 0) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            notifyPolice();
        }
    }
}

}


Solution

  • First off- DashboardActivity.getInstance()- NEVER DO THIS. You're assuming there's exactly 1 instance of an activity at all times. That's wrong. There can be 0. There can be 2. You can't make an activity a singleton. In addition, this always creates a memory leak. NEVER store an Activity in a static variable, this is always a memory leak. It's also (due to the first fact) wrong because it can point to the wrong instance of an Activity.

    Secondly- you're using getLastLocation. Don't do that. That function is meant for optimization and will usually return null (I know the docs say the opposite. The docs there have always been wrong.) If you need the location, request it instead. getLastLocation should only be used to get a quick result optimisticly before requesting location updates.

    Thirdly- your call to requestNewLocation is passing in a location handler that does nothing. It isn't even updating mLastLocation because that's a method level variable and not a class one.

    Fourthly- did you read the new rules for background location processing in ANdroid 12? https://proandroiddev.com/android-12-privacy-changes-for-location-55ffd8c016fd

    Those are just the major mistakes, not smaller things that make me go "huh?". With the best of intentions here- this code needs major refactoring. It feels like someone who didn't really understand Android cobbled it together from a dozen examples he didn't understand. If I were you I'd start by simplifying the concept- do one thing, understand it, and then do another. You're overreaching your abilities at the moment doing this all at once, and you'll be better off learning one thing at a time.