Reputation: 2914
I have issues with my app recently, when it is out of nowhere rejected by Google Play
because they found that I'm using background location. But in fact I'm not using this feature. I have only ACCESS_COARSE_LOCATION
and ACCESS_FINE_LOCATION
permissions and I'm using FusedLocationProviderClient
to get location in my app. This location is requested only by user action inside app, so if its in background, this is never called. I checked merged manifest feature and I tried to find if some of my imported libs are using background location permission, but I didn't find anything. Also I preventively added <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" tools:node="remove"/>
to my manifest to block any background location permission requests. I dont have any background services which are working with location at all. The only background service is FirebaseMessagingService
for push notifications.
Anyone have this problem recently?
UPDATE:
I checked merged manifest in my app and I couldn't find ACCESS_BACKGROUND_LOCATION permission there. But I found some services which could trigger background location but I'm not sure. They are part of Firebase Crashlytics and they are probably used to send data to Firebase and they could work in a background. But I don't think they are sending any location. Also they are part of firebase plugin which is from Google.
<service
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.JobInfoSchedulerService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<receiver
android:name="com.google.android.datatransport.runtime.scheduling.jobscheduling.AlarmManagerSchedulerBroadcastReceiver"
android:exported="false" />
UPDATE #2:
This is code I'm using to get location.
MainActivity:
/**
* Updating location every second/1 meter
*/
var currLocation: GpsLocation? = null
private var locationManager : LocationManager? = null
private fun initLocationManager() {
if (app.hasLocationPermission){
locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
changeLocationUpdaters(true)
}
private fun changeLocationUpdaters(isEnabled: Boolean){
if (ActivityCompat.checkSelfPermission(
this@MainActivity,
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(
this@MainActivity,
Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
locationManager?.apply{
if (isEnabled && app.hasLocationPermission){
requestLocationUpdates(LocationManager.GPS_PROVIDER, LOCATION_UPDATE_TIME_INTERVAL, LOCATION_UPDATE_DIST_INTERVAL, this@MainActivity)
requestLocationUpdates(LocationManager.NETWORK_PROVIDER, LOCATION_UPDATE_TIME_INTERVAL, LOCATION_UPDATE_DIST_INTERVAL, this@MainActivity)
} else {
removeUpdates(this@MainActivity)
}
}
} else {
return
}
}
Then removing location updaters when app is in background:
override fun onPause() {
super.onPause()
changeLocationUpdaters(false)
}
override fun onResume() {
super.onResume()
changeLocationUpdaters(true)
}
Then I use FusedLocationProvider
inside Fragment
to get more accurate location. Its used only by calling function so its not automated like previous one. Its used in GoogleMap classes and also in some onClick events inside app to return current location. There is no service or updater calling it.
private inner class LocationCb(val lp: FusedLocationProviderClient,
val onFailure: (()->Unit)? = null,
val onSuccess: (GpsLocation)->Unit)
: LocationCallback() {
init {
val lr = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 200
}
val lsr = LocationSettingsRequest.Builder().run {
addLocationRequest(lr)
build()
}
val check = LocationServices.getSettingsClient(activity!!).checkLocationSettings(lsr)
check.addOnCompleteListener {
try {
check.getResult(ApiException::class.java)
val task = lp.requestLocationUpdates(lr, this, Looper.getMainLooper())
task.addOnFailureListener {
onFailure?.invoke()
}
} catch (e: ApiException) {
when (e.statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED-> if(!locationResolutionAsked){
// Location settings are not satisfied. But could be fixed by showing the user a dialog.
try {
// Cast to a resolvable exception.
val re = e as ResolvableApiException
// Show the dialog by calling startResolutionForResult(), and check the result in onActivityResult().
re.startResolutionForResult(mainActivity, MainActivity.REQUEST_LOCATION_SETTINGS)
locationResolutionAsked = true
} catch (e: Exception) {
e.printStackTrace()
}
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE->{
App.warn("Location is not available")
onFailure?.invoke()
}
}
}
}
}
fun cancel(){
lp.removeLocationUpdates(this)
currLocCb = null
}
override fun onLocationResult(lr: LocationResult) {
cancel()
val ll = lr.lastLocation
onSuccess(GpsLocation(ll.longitude, ll.latitude))
}
}
This location provider is cancelled after result is returned so its one-time use only. But Ive added similar cancellation method inside onPause
and onStop
for Fragment
than it is in MainActivity
to make sure that its inactive when app is in background.
override fun onStop() {
super.onStop()
currLocCb?.cancel()
}
override fun onPause() {
super.onPause()
currLocCb?.cancel()
}
Upvotes: 8
Views: 4543
Reputation: 14670
Unfortunately, not all libraries publish a manifest that contains all necessary <uses-permission>
elements. That means, that simply checking your merged AndroidManifest.xml
won't help much - you will have to check documentation for each library to find out which permissions it really needs, or just add necessary permissions to your own AndroidManifest.xml
preemptively.
You also mentioned that your target SDK is 29
. So, according to the official documentation here, you have to set the permission in your AndroidManifest.xml
explicitly, if it's needed. Previously, it was granted automatically, if the app had foreground location access (basically, ACCESS_COARSE_LOCATION
and ACCESS_FINE_LOCATION
).
On Android 10 (API level 29) and higher, you must declare the ACCESS_BACKGROUND_LOCATION permission in your app's manifest in order to request background location access at runtime. On earlier versions of Android, when your app receives foreground location access, it automatically receives background location access as well.
So, for older versions, your app was granted ACCESS_BACKGROUND_LOCATION
automatically, because it was granted ACCESS_COARSE_LOCATION
and ACCESS_FINE_LOCATION
beforehand.
Additionally, even if you or any of your libraries do not set ACCESS_BACKGROUND_LOCATION
anywhere, the system will still consider that your app is using background location for any situation except:
An activity that belongs to your app is visible.
Your app is running a foreground service. When a foreground service is running, the system raises user awareness by showing a persistent notification. Your app retains access when it's placed in the background, such as when the user presses the Home button on their device or turns their device's display off.
What the latter means is that may have a library or libraries that need ACCESS_BACKGROUND_LOCATION
, but it's not present in their AndroidManifest.xml
for whatever reason. It used to work for API < 29
because your app was granted the permission automatically (due to foreground location permission).
Also, now, the system considers any usage of current location a background location if it's done outside of your visible Activity
or not in a Foreground Service
. So, make sure that you're not doing so in any part of your app.
Based on your updated question, you are requesting a current location within OnCompleteListener
by calling lp.requestLocationUpdates
:
...
check.addOnCompleteListener {
try {
check.getResult(ApiException::class.java)
val task = lp.requestLocationUpdates(lr, this, Looper.getMainLooper())
task.addOnFailureListener {
onFailure?.invoke()
}
...
This can be a problem (I cannot be sure because you don't show how the class is used within your app) because the app may go to the background before OnCompleteListener
completes, and so the location will be requested in the background.
As stated in the previous section, by doing so the system considers that you need a background location permissions to do so. So, you must unsubscribe your callback OnCompleteListener
if your app goes to background.
You could use another version of addOnCompleteListener
that also accept your Activity
instance as shown here
public Task addOnCompleteListener (Activity activity, OnCompleteListener listener)
In this case, the listener will be automatically removed during Activity.onStop()
.
Upvotes: 5
Reputation: 16729
First of all, remove completely words ACCESS_BACKGROUND_LOCATION
from your manifest. Even with tools:node="remove"
.
The second: if you haven't added ACCESS_BACKGROUND_LOCATION
manually it doesn't mean it is not there - some libraries may have added it for you. Instead of checking your project manifest file - check merged manifest - the usual path to it is: (it may be different in your case if you have flavors)
/project/module/build/intermediates/manifests/full/debug/AndroidManifest.xml
.
Check if there is ACCESS_BACKGROUND_LOCATION
permission there - if there is - this means that some library added it there. Manually check all the manifests of all the libraries to find out which one has added it there. When you find it - delete it.
If your project heavily depends on the target library - you have another solution - write a disclosure in the app and play store console about why do you need to use background location and show it before the location permission dialog with message that looks like:
We need access to your location in the background to ensure our app can function correctly.
Keep in mind that this message may be not enough descriptive - but testers from google will notify you if it is.
Either way, disclosure is the last chance solution...
If you have no ACCESS_BACKGROUND_LOCATION
and you do not use location in foreground service but only inside the app while it is running - write a letter to google support with all your arguments and ask them what exactly causes the rejection issue. Be polite and well-tempered - and it will be resolved. I have had similar issues in the past and contact with their release support always helped.
Upvotes: 0