Home > Blockchain >  Can't access location after app is killed
Can't access location after app is killed

Time:09-27

I'm working on android app that should collect user geolocation (if the user agrees ofc ) every 15 minutes. I'm familiar with concept of WorkManger and I'm using it for executing PeriodicWork. Also, main reason I'm using it is that it works after device reboot.

I'm using LocationManager instead of Fused because one of my requirements is possible distribution outside of Play Store.

The problem is: when app is killed (recent items -> swipe) worker still executes LocationWork, but provider returns always null. I've heard about background location limitations so I've tried to move this functionality to foreground service. Unfortunately, it didn't change anything.

My code:

class GeoLocationWorker(context: Context, workerParameters: WorkerParameters) :
    Worker(context, workerParameters) {

    private var requestQueue: RequestQueue = Volley.newRequestQueue(applicationContext)

    override fun doWork(): Result {

        Log.i("GLW", "before sleep")
        Thread.sleep(5000)
        Log.i("GLW", "after sleep")
        Log.i("GLW", "location lookup start")

        applicationContext.startForegroundService(Intent(applicationContext, GeoService::class.java))
        return Result.success()

    }
}
class GeoService : Service() {
    override fun onBind(p0: Intent?): IBinder? {
        stopForeground(true)
        return null
    }

    override fun onCreate() {
        super.onCreate()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startForeground()
        return START_STICKY
    }

    private fun startForeground() {
        val channelId =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                createNotificationChannel("my_service", "My Background Service")
            } else {
                ""
            }

        val notificationBuilder = NotificationCompat.Builder(this, channelId)
        val notification = notificationBuilder.setOngoing(true)
            .setSmallIcon(R.mipmap.ic_launcher)
            .setPriority(PRIORITY_MIN)
            .setCategory(Notification.CATEGORY_SERVICE)
            .build()
        startForeground(101, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION)
        val locationManager =
            applicationContext.getSystemService(LOCATION_SERVICE) as LocationManager

        /* no checking for permissions, because this code is executed only after successful request
            for ACCESS_LOCATION_*
         */

        locationManager.getCurrentLocation(
            LocationManager.GPS_PROVIDER,
            null,
            applicationContext.mainExecutor,
            {
                if (it == null) {
                    Log.e("location", "location == null")
                } else {
                    Log.i("location", it.toString())
                }
                stopForeground(true)
            })
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private fun createNotificationChannel(channelId: String, channelName: String): String {
        val chan = NotificationChannel(
            channelId,
            channelName, NotificationManager.IMPORTANCE_NONE
        )
        chan.lightColor = Color.BLUE
        chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
        val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        service.createNotificationChannel(chan)
        return channelId
    }
}

Manifest:

 <service
            android:name="a.b.c.d.GeoService"
            android:foregroundServiceType="location" />

Requesting permissions:


class PermissionActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportActionBar?.hide()
        setContentView(R.layout.activity_permission)
    }

    @SuppressLint("InlinedApi")
    fun askForPermissions(view: View) {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(
                android.Manifest.permission.ACCESS_COARSE_LOCATION,
                android.Manifest.permission.ACCESS_FINE_LOCATION
            ),
            1
        )
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        for (i in permissions.indices) {
            if (permissions[i] == android.Manifest.permission.ACCESS_FINE_LOCATION
                && grantResults[i] == PackageManager.PERMISSION_GRANTED
            ) {

                val request =
                    PeriodicWorkRequestBuilder<GeoLocationWorker>(15, TimeUnit.MINUTES)
                        .build()

                WorkManager
                    .getInstance(this)
                    .enqueueUniquePeriodicWork(
                        WorkerConfig.LOCATION_WORKER,
                        ExistingPeriodicWorkPolicy.KEEP,
                        request
                    )
            }
        }
        finish()
        startActivity(Intent(this, HomeActivity::class.java))
    }
}

CodePudding user response:

From android 10 and above , you need to request ACCESS_BACKGROUND_LOCATION to get the location while app is in background - Request Location Permission

AndroidManifest.xml

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

Update your location code:

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
             // requesting background location
       BottomNavigationActivity.this.requestPermissions(
          new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
                     Manifest.permission.ACCESS_FINE_LOCATION,
                     Manifest.permission.ACCESS_BACKGROUND_LOCATION},
                      MY_PERMISSION_LOCATION);

  } else {
       MyActivity.this.requestPermissions(
          new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
                     Manifest.permission.ACCESS_FINE_LOCATION},
                     MY_PERMISSION_LOCATION);
  }

CodePudding user response:

I've managed to find the solution. Nitish's comments helped me a lot but main problem was something I missed in documentation:

Caution: If your app targets Android 11 (API level 30) or higher, the system enforces this best practice. If you request a foreground location permission and the background location permission at the same time, the system ignores the request and doesn't grant your app either permission.

Working permission request code:


class PermissionActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportActionBar?.hide()
        setContentView(R.layout.activity_permission)
    }

    fun askForPermissions(view: View) {


            ActivityCompat.requestPermissions(
                this,
                arrayOf(
                    android.Manifest.permission.ACCESS_COARSE_LOCATION,
                    android.Manifest.permission.ACCESS_FINE_LOCATION
                ),
                1
            )

    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if (requestCode == 1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(
                    android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
                ),
                2
            )
            return
        }

        for (i in permissions.indices) {
            if (permissions[i] == android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
                && grantResults[i] == PackageManager.PERMISSION_GRANTED
            ) {

                val request =
                    PeriodicWorkRequestBuilder<GeoLocationWorker>(15, TimeUnit.MINUTES)
                        .build()

                WorkManager
                    .getInstance(this)
                    .enqueueUniquePeriodicWork(
                        WorkerConfig.LOCATION_WORKER,
                        ExistingPeriodicWorkPolicy.REPLACE,
                        request
                    )
            }
        }
        finish()
        startActivity(Intent(this, HomeActivity::class.java))
    }
}

  • Related