Small Kotlin demo ACCESS_FINE_LOCATION - android

Improved version!
The following code shows how to ACCESS_FINE_LOCATION. While longer than my original code it is preferred because it stops GPS updates when the App is Paused or Stopped.
Due to issues with the Android Studio emulators it also includes a simple watchdog timer reporting the GPS Updates have stopped if no updates are received for approximately 10 seconds
const val PERMISSIONS_REQUEST_ACCESS_LOCATION = 99 // any positive value
class MainActivity : AppCompatActivity() {
private lateinit var tvCurrentTime : TextView
private lateinit var tvSpeed : TextView
private lateinit var tvBearing : TextView
private var myTimer: Timer = Timer()
private var sHourMinuteSecond : String = "00:00:00"
private lateinit var fusedLocationClient : FusedLocationProviderClient
private lateinit var locationCallback: LocationCallback
private var gpsWatchDog : Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvCurrentTime = findViewById<View>(R.id.show_time_textview) as TextView
tvSpeed = findViewById<View>(R.id.speed_textview) as TextView
tvBearing = findViewById<View>(R.id.bearing_textview) as TextView
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this#MainActivity) as FusedLocationProviderClient
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
processNewLocation(locationResult.lastLocation) // only process the lastLocation (may be others)
}
}
}
private fun checkPermissionsForGPSUpdates() {
if ((ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) ||
(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
// Android wants us to request both COARSE and FINE permissions
writeToLog("checkSelfPermission Permission Not Granted so requesting!")
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION), PERMISSIONS_REQUEST_ACCESS_LOCATION)
// During requestPermission OnPause() is called
// When user grants permission OnResume() is called which once again calls checkPermissionsForGPSUpdates() when GPS updates should commence
return
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
// only run if we have FINE location
writeToLog("ACCESS_FINE_LOCATION Permission Granted start GPS updates !")
val locationRequest: LocationRequest = LocationRequest.create().apply {
interval = 1000
fastestInterval = 500
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
try {
if (!fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()).isSuccessful) {
writeToLog("requestLocationUpdates() was unsuccessful") // Stackoverflow indicates this may actually be a google error
}
} catch (unlikely: SecurityException) {
writeToLog("Lost location permission. Could not request updates. $unlikely")
}
} else {
writeToLog("Permissions Not Granted so exit")
Toast.makeText(this#MainActivity, "App will not run without GPS" , Toast.LENGTH_LONG).show()
finish()
}
}
private fun updateTimeString() {
// Called by the timer DO NOT put code affecting the UI in here!!!
val systemTime = GregorianCalendar()
sHourMinuteSecond = SimpleDateFormat("HH:mm:ss").format(systemTime.timeInMillis)
// Use the runOnUiThread method to update the display
this.runOnUiThread(updateDisplay)
}
private val updateDisplay = Runnable {
// This method runs in the same thread as the User Interface
// due to continuous crashing of the emulator has been written to be short
tvCurrentTime.text = sHourMinuteSecond
if (++gpsWatchDog > 10) {
tvBearing.text = "No GPS"
}
}
private fun processNewLocation(latestLocation : Location) {
// reset the watchdog since we have received a GPS update
gpsWatchDog = 0
// do something with the latest location
// for now just show the GPS time
val sSpeed = SimpleDateFormat("HH:mm:ss").format(latestLocation.time)
tvSpeed.text = sSpeed
val sBearing = latestLocation.bearing.roundToInt().toString() // no decimal places
tvBearing.text = sBearing
}
override fun onResume() {
super.onResume()
writeToLog("Called onResume()")
// start the GPS updates (after checking user permission)
checkPermissionsForGPSUpdates()
val currentSystemTime = System.currentTimeMillis()
// start a timer to update the time
myTimer = Timer()
val delayToNextWholeSecond = ((currentSystemTime / 1000) + 1) * 1000 - currentSystemTime // Synchronise to whole seconds
myTimer.scheduleAtFixedRate(object : TimerTask() {
override fun run() {
updateTimeString()
}
}, delayToNextWholeSecond, 1000)
}
override fun onPause() {
super.onPause()
writeToLog("onPause()")
myTimer.cancel() // stop the existing timer
myTimer.purge()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
override fun onStop() {
super.onStop()
writeToLog("onStop()")
myTimer.cancel()
myTimer.purge()
fusedLocationClient.removeLocationUpdates(locationCallback)
}
private fun writeToLog(message : String) {
Log.d("SmallGPSDemo", message)
}
}
The original very short example is shown below only to show Android's new RequestPermission() to obtain permissions (and start updates)
class MainActivity : AppCompatActivity() {
private var debugTextView : TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
debugTextView = findViewById<View>(R.id.debug_textview) as TextView
// this checks permission and starts the location updates
requestPermission.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
private fun processNewLocation(latestLocation : Location) {
// do something with the location
debugTextView!!.text = SimpleDateFormat("hh:mm:ss.SSS").format(System.currentTimeMillis())
}
#SuppressLint("MissingPermission")
private val requestPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
// Permissions granted so start GPS updates
val locationRequest: LocationRequest = LocationRequest.create().apply {
interval = 1000
fastestInterval = 500
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
val locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
for (location in locationResult.locations) {
processNewLocation(location) // Settings should only give one location but ...
}
}
}
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this#MainActivity)
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
} else {
Log.d("DEBUG", "permission denied ")
Toast.makeText(this#MainActivity, "This activity will not run unless you allow GPS", Toast.LENGTH_LONG).show()
finish()
}
}
}
Reminder add location permissions to the AndroidManifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.reallysmallgpscode">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.ReallySmallGPSCode">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Hopefully others find the code useful

Related

Location permission dialog not shown in android 12

I'm trying to apply a tutorial about request permission when the app start, but when I tested on my real device android 12, it's not show the permission dialog it show a snack bar that request from user to go to setting and grant it manually
Manifiest file
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- TODO: Step 1 add in permissions for fine location and background-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".HuntMainActivity"
android:label="#string/title_activity_hunt"
android:launchMode="singleInstance"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".GeofenceBroadcastReceiver"/>
</application>
</manifest>
this my code
class HuntMainActivity : AppCompatActivity() {
private lateinit var binding: ActivityHuntMainBinding
private lateinit var geofencingClient: GeofencingClient
private lateinit var viewModel: GeofenceViewModel
private val runningQOrLater = android.os.Build.VERSION.SDK_INT >=
android.os.Build.VERSION_CODES.Q
// A PendingIntent for the Broadcast Receiver that handles geofence transitions.
// TODO: Step 8 add in a pending intent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_hunt_main)
viewModel = ViewModelProvider(this, SavedStateViewModelFactory(this.application,
this)).get(GeofenceViewModel::class.java)
binding.viewmodel = viewModel
binding.lifecycleOwner = this
// TODO: Step 9 instantiate the geofencing client
// Create channel for notifications
createChannel(this )
}
override fun onStart() {
super.onStart()
checkPermissionsAndStartGeofencing()
}
/*
* When we get the result from asking the user to turn on device location, we call
* checkDeviceLocationSettingsAndStartGeofence again to make sure it's actually on, but
* we don't resolve the check to keep the user from seeing an endless loop.
*/
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// TODO: Step 7 add code to check that the user turned on their device location and ask
// again if they did not
}
/*
* When the user clicks on the notification, this method will be called, letting us know that
* the geofence has been triggered, and it's time to move to the next one in the treasure
* hunt.
*/
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val extras = intent?.extras
if(extras != null){
if(extras.containsKey(GeofencingConstants.EXTRA_GEOFENCE_INDEX)){
viewModel.updateHint(extras.getInt(GeofencingConstants.EXTRA_GEOFENCE_INDEX))
checkPermissionsAndStartGeofencing()
}
}
}
/*
* In all cases, we need to have the location permission. On Android 10+ (Q) we need to have
* the background permission as well.
*/
#SuppressLint("MissingSuperCall")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
Log.d(TAG, "onRequestPermissionResult")
if (
grantResults.isEmpty() ||
grantResults[LOCATION_PERMISSION_INDEX] == PackageManager.PERMISSION_DENIED ||
(requestCode == REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE &&
grantResults[BACKGROUND_LOCATION_PERMISSION_INDEX] ==
PackageManager.PERMISSION_DENIED))
{
Snackbar.make(
binding.activityMapsMain,
R.string.permission_denied_explanation,
Snackbar.LENGTH_INDEFINITE
)
.setAction(R.string.settings) {
startActivity(Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
flags = Intent.FLAG_ACTIVITY_NEW_TASK
})
}.show()
} else {
checkDeviceLocationSettingsAndStartGeofence()
}
}
/**
* This will also destroy any saved state in the associated ViewModel, so we remove the
* geofences here.
*/
override fun onDestroy() {
super.onDestroy()
removeGeofences()
}
/**
* Starts the permission check and Geofence process only if the Geofence associated with the
* current hint isn't yet active.
*/
private fun checkPermissionsAndStartGeofencing() {
if (viewModel.geofenceIsActive()) return
if (foregroundAndBackgroundLocationPermissionApproved()) {
checkDeviceLocationSettingsAndStartGeofence()
} else {
requestForegroundAndBackgroundLocationPermissions()
}
}
/*
* Uses the Location Client to check the current state of location settings, and gives the user
* the opportunity to turn on location services within our app.
*/
private fun checkDeviceLocationSettingsAndStartGeofence(resolve:Boolean = true) {
val locationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_LOW_POWER
}
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
val settingsClient = LocationServices.getSettingsClient(this)
val locationSettingsResponseTask =
settingsClient.checkLocationSettings(builder.build())
locationSettingsResponseTask.addOnFailureListener { exception ->
if (exception is ResolvableApiException && resolve){
try {
exception.startResolutionForResult(this#HuntMainActivity,
REQUEST_TURN_DEVICE_LOCATION_ON)
} catch (sendEx: IntentSender.SendIntentException) {
Log.d(TAG, "Error getting location settings resolution: " + sendEx.message)
}
} else {
Snackbar.make(
binding.activityMapsMain,
R.string.location_required_error, Snackbar.LENGTH_INDEFINITE
).setAction(android.R.string.ok) {
checkDeviceLocationSettingsAndStartGeofence()
}.show()
}
}
locationSettingsResponseTask.addOnCompleteListener {
if ( it.isSuccessful ) {
addGeofenceForClue()
}
}
}
/*
* Determines whether the app has the appropriate permissions across Android 10+ and all other
* Android versions.
*/
#TargetApi(29)
private fun foregroundAndBackgroundLocationPermissionApproved(): Boolean {
val foregroundLocationApproved = (
PackageManager.PERMISSION_GRANTED ==
ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION))
val backgroundPermissionApproved =
if (runningQOrLater) {
PackageManager.PERMISSION_GRANTED ==
ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
} else {
true
}
return foregroundLocationApproved && backgroundPermissionApproved
}
/*
* Requests ACCESS_FINE_LOCATION and (on Android 10+ (Q) ACCESS_BACKGROUND_LOCATION.
*/
#TargetApi(29 )
private fun requestForegroundAndBackgroundLocationPermissions() {
if (foregroundAndBackgroundLocationPermissionApproved())
return
var permissionsArray = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
val resultCode = when {
runningQOrLater -> {
permissionsArray += Manifest.permission.ACCESS_BACKGROUND_LOCATION
REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE
}
else -> REQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE
}
Log.d(TAG, "Request foreground only location permission")
ActivityCompat.requestPermissions(
this#HuntMainActivity,
permissionsArray,
resultCode
)
}
/*
* Adds a Geofence for the current clue if needed, and removes any existing Geofence. This
* method should be called after the user has granted the location permission. If there are
* no more geofences, we remove the geofence and let the viewmodel know that the ending hint
* is now "active."
*/
private fun addGeofenceForClue() {
// TODO: Step 10 add in code to add the geofence
}
/**
* Removes geofences. This method should be called after the user has granted the location
* permission.
*/
private fun removeGeofences() {
// TODO: Step 12 add in code to remove the geofences
}
companion object {
internal const val ACTION_GEOFENCE_EVENT =
"HuntMainActivity.treasureHunt.action.ACTION_GEOFENCE_EVENT"
}
}
private const val REQUEST_FOREGROUND_AND_BACKGROUND_PERMISSION_RESULT_CODE = 33
private const val REQUEST_FOREGROUND_ONLY_PERMISSIONS_REQUEST_CODE = 34
private const val REQUEST_TURN_DEVICE_LOCATION_ON = 29
private const val TAG = "HuntMainActivity"
private const val LOCATION_PERMISSION_INDEX = 0
private const val BACKGROUND_LOCATION_PERMISSION_INDEX = 1
I tested it on my real device, what i excopecting is showing the permission dialog when the app starts

How to solve problem with gps permissions on Android versions 12?

GPS service is not enabled on Android versions 12 and above.
Everything worked correctly on earlier versions
I have read this documentation,
but I'm not sure if I have a problem with android:foregroundServiceType since I basically don't use it in the application.
So here is some of my code AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
And code of my vm:
private fun enableGps(activity: FragmentActivity) {
val manager = activity.getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER) && hasGPSDevice(manager)) {
locationManager.enableGps(activity)
}
}
And here is the implementation of the method itself:
override fun enableGps(activity: FragmentActivity) {
if (googleApiClient == null) {
googleApiClient = GoogleApiClient.Builder(context)
.addApi(LocationServices.API)
.addConnectionCallbacks(object : GoogleApiClient.ConnectionCallbacks {
override fun onConnected(bundle: Bundle?) {}
override fun onConnectionSuspended(i: Int) {
googleApiClient?.connect()
}
})
.addOnConnectionFailedListener { connectionResult ->
Timber.e("${connectionResult.errorCode}")
}.build()
googleApiClient?.connect()
}
val locationRequest = LocationRequest.create()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = 10000
locationRequest.fastestInterval = 5000
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
builder.setAlwaysShow(true)
googleApiClient?.let {
val result: PendingResult<LocationSettingsResult> =
LocationServices.SettingsApi.checkLocationSettings(it, builder.build())
result.setResultCallback { result ->
val status: Status = result.status
when (status.statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try {
// Show the dialog by calling startResolutionForResult(),
status.startResolutionForResult(
activity,
REQUEST_LOCATION
)
} catch (e: IntentSender.SendIntentException) {
Timber.e(e)
}
LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> {
activity.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
}
}
}
}
}
UPD
Here is full code of my DeviceLocationManager, maybe I 'm requesting my permissions somehow wrong here:
https://gist.github.com/mnewlive/0c0a0c1f7ccb26fe58fd6b0fa5dd1dda
I rewrote the following method because it was deprecated:
override fun isLocationProviderActive(): Boolean {
val providerInfo = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.LOCATION_PROVIDERS_ALLOWED
)
return providerInfo?.isNotEmpty() == true
}
to
override fun isLocationStateEnabled(): Boolean {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return LocationManagerCompat.isLocationEnabled(locationManager)
}

Android FusedLocationClient with PendingIntent not receiving location update when app is in background

I'm using FusedLocationClient and broadcast receiver to get location in background, i enabled background location background permission and locations (FINE and COARSE) permission. i receive location update when app in foreground but when moving app in background i don't receive any update.
AndroidManifest.xml
<receiver
android:name=".LocationUpdatesBroadcastReceiver"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="com.test.gpstracking.LocationUpdatesBroadcastReceiver.ACTION_PROCESS_UPDATES" />
</intent-filter>
</receiver>
LocationManager Class
class MyLocationManager private constructor(private val context: Context) {
// The Fused Location Provider provides access to location APIs.
private val fusedLocationClient: FusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(context)
private val TAG = "MyLocationManager"
// Stores parameters for requests to the FusedLocationProviderApi.
private val locationRequest: LocationRequest = LocationRequest().apply {
interval = TimeUnit.SECONDS.toMillis(4)
fastestInterval = TimeUnit.SECONDS.toMillis(4)
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
/**
* Creates default PendingIntent for location changes.
*
* Note: We use a BroadcastReceiver because on API level 26 and above (Oreo+), Android places
* limits on Services.
*/
private val locationUpdatePendingIntent: PendingIntent by lazy {
var intent = Intent(context, LocationUpdatesBroadcastReceiver::class.java)
intent.action = LocationUpdatesBroadcastReceiver.ACTION_PROCESS_UPDATES
PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
}
#Throws(SecurityException::class)
#MainThread
fun startLocationUpdates() {
Log.d(TAG, "startLocationUpdates()")
if (!context.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) return
try {
Log.e(TAG, "receiving location updates")
// If the PendingIntent is the same as the last request (which it always is), this
// request will replace any requestLocationUpdates() called before.
fusedLocationClient.requestLocationUpdates(locationRequest, locationUpdatePendingIntent)
} catch (permissionRevoked: SecurityException) {
Log.e(TAG, "not receiving location updates")
// Exception only occurs if the user revokes the FINE location permission before
// requestLocationUpdates() is finished executing (very rare).
Log.d(TAG, "Location permission revoked; details: $permissionRevoked")
throw permissionRevoked
}
}
companion object {
#Volatile
private var INSTANCE: MyLocationManager? = null
fun getInstance(context: Context): MyLocationManager {
return INSTANCE ?: synchronized(this) {
INSTANCE ?: MyLocationManager(context).also { INSTANCE = it }
}
}
}
}
My broadcast receiver
class LocationUpdatesBroadcastReceiver : BroadcastReceiver() {
private val TAG = "LocationUpdatesBroadcastReceiver"
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == ACTION_PROCESS_UPDATES) {
// Checks for location availability changes.
LocationAvailability.extractLocationAvailability(intent)?.let { locationAvailability ->
if (!locationAvailability.isLocationAvailable) {
Log.d(TAG, "Location services are no longer available!")
}
}
LocationResult.extractResult(intent)?.let { locationResult ->
locationResult.lastLocation
val location = locationResult.lastLocation
if (location != null) {
Toast.makeText(context, location.toString(), Toast.LENGTH_SHORT).show()
}
}
}
}
PS : I'm following the android source code of LocationUpdatesBackgroundKotlin from google repo on github -> link
Android does not allow any kind of background service after Android Oreo, Check out this link will guide you.
Access Location in Background

Android ForegroundService for background location

From Android 11 I learned there are some restrictions related to background location, but from the documentation is not very clear to me if this affects a ForegroundService which has the foregroundServiceType="location" declared in the AndroidManifest.xml file.
This part of the documentation is confusing for me:
"If your app starts a foreground service while the app is running in
the foreground ("while-in-use"), the service has the following access
restrictions:
If the user has granted the ACCESS_BACKGROUND_LOCATION permission to
your app, the service can access location all the time. Otherwise, if
the user has granted the ACCESS_FINE_LOCATION or
ACCESS_COARSE_LOCATION permission to your app, the service has access
to location only while the app is running in the foreground (also
known as "while-in-use access to location")."
So, if I need background location access is it safe to use only the ForegroundService with type "location" for Android 11 or it is still mandatory to add the ACCESS_BACKGROUND_LOCATION permission?
NOTE: I created a sample project with ForegroundService declared with type "location" for target SDK 30 and seems to work without the background location permission (I receive the location updates every 2 seconds while in background) and this is why I am confused about this. I run the app on Pixel 4 with Android 11.
This is the sample project:
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.locationforegroundservice">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.LocationForegroundService">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".LocationService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="location"/>
</application>
</manifest>
LocationService
class LocationService : Service() {
private var context: Context? = null
private var settingsClient: SettingsClient? = null
private var locationSettingsRequest: LocationSettingsRequest? = null
private var locationManager: LocationManager? = null
private var locationRequest: LocationRequest? = null
private var notificationManager: NotificationManager? = null
private var fusedLocationClient: FusedLocationProviderClient? = null
private val binder: IBinder = LocalBinder()
private var locationCallback: LocationCallback? = null
private var location: Location? = null
override fun onBind(intent: Intent?): IBinder {
// Called when a client (MainActivity in case of this sample) comes to the foreground
// and binds with this service. The service should cease to be a foreground service
// when that happens.
Log.i(TAG, "in onBind()")
return binder
}
override fun onCreate() {
super.onCreate()
context = this
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
createLocationRequest()
locationCallback = object : LocationCallback() {
#RequiresApi(Build.VERSION_CODES.O)
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
for (location in locationResult.locations) {
onNewLocation(location)
}
}
}
val handlerThread = HandlerThread(TAG)
handlerThread.start()
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager?
// Android O requires a Notification Channel.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name: CharSequence = "service"
val mChannel = NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT)
// Set the Notification Channel for the Notification Manager.
notificationManager?.createNotificationChannel(mChannel)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(TAG, "Service started")
val startedFromNotification =
intent?.getBooleanExtra(EXTRA_STARTED_FROM_NOTIFICATION, false)
// We got here because the user decided to remove location updates from the notification.
if (startedFromNotification == true) {
removeLocationUpdates()
stopSelf()
}
// Tells the system to not try to recreate the service after it has been killed.
return START_NOT_STICKY
}
/**
* Returns the [NotificationCompat] used as part of the foreground service.
*/
private val notification: Notification
private get() {
val intent = Intent(this, LocationService::class.java)
// Extra to help us figure out if we arrived in onStartCommand via the notification or not.
intent.putExtra(EXTRA_STARTED_FROM_NOTIFICATION, true)
// The PendingIntent that leads to a call to onStartCommand() in this service.
val servicePendingIntent =
PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
// The PendingIntent to launch activity.
val activityPendingIntent =
PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), 0)
val builder = NotificationCompat.Builder(this)
.addAction(R.drawable.ic_delete, "title", activityPendingIntent)
.addAction(R.drawable.ic_delete, "remove", servicePendingIntent)
.setContentTitle("location title").setOngoing(true)
.setPriority(Notification.PRIORITY_HIGH).setSmallIcon(R.drawable.btn_dialog)
.setWhen(System.currentTimeMillis())
// Set the Channel ID for Android O.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CHANNEL_ID) // Channel ID
}
return builder.build()
}
/**
* Makes a request for location updates. Note that in this sample we merely log the
* [SecurityException].
*/
fun requestLocationUpdates() {
Log.i(TAG, "Requesting location updates")
startForeground(NOTIFICATION_ID, notification)
try {
fusedLocationClient?.requestLocationUpdates(locationRequest, locationCallback, null)
} catch (unlikely: SecurityException) {
Log.e(TAG, "Lost location permission. Could not request updates. $unlikely")
}
}
#RequiresApi(Build.VERSION_CODES.O)
private fun onNewLocation(location: Location) {
Log.i(TAG, "New location ${LocalDateTime.now()}: $location")
this.location = location
// Notify anyone listening for broadcasts about the new location.
val intent = Intent(ACTION_BROADCAST)
intent.putExtra(EXTRA_LOCATION, location)
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
// Update notification content if running as a foreground service.
if (serviceIsRunningInForeground(this)) {
notificationManager?.notify(NOTIFICATION_ID, notification)
}
}
/**
* Sets the location request parameters.
*/
private fun createLocationRequest() {
locationManager = context?.getSystemService(LOCATION_SERVICE) as LocationManager
settingsClient = LocationServices.getSettingsClient(context)
locationRequest = LocationRequest.create()
locationRequest?.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest?.interval = 1000
locationRequest?.fastestInterval = 1000
val builder = LocationSettingsRequest.Builder().addLocationRequest(locationRequest)
locationSettingsRequest = builder.build()
builder.setAlwaysShow(true) //this is the key ingredient
}
/**
* Removes location updates. Note that in this sample we merely log the
* [SecurityException].
*/
fun removeLocationUpdates() {
Log.i(TAG, "Removing location updates")
try {
fusedLocationClient?.removeLocationUpdates(locationCallback)
stopSelf()
} catch (unlikely: SecurityException) {
Log.e(TAG, "Lost location permission. Could not remove updates. $unlikely")
}
}
/**
* Class used for the client Binder. Since this service runs in the same process as its
* clients, we don't need to deal with IPC.
*/
inner class LocalBinder : Binder() {
val service: LocationService
get() = this#LocationService
}
/**
* Returns true if this is a foreground service.
*
* #param context The [Context].
*/
fun serviceIsRunningInForeground(context: Context): Boolean {
val manager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Int.MAX_VALUE)) {
if (javaClass.name == service.service.className) {
if (service.foreground) {
return true
}
}
}
return false
}
companion object {
private const val PACKAGE_NAME = "com.example.locationforegroundservice"
private val TAG = "TEST"
/**
* The name of the channel for notifications.
*/
private const val CHANNEL_ID = "channel_01"
const val ACTION_BROADCAST = PACKAGE_NAME + ".broadcast"
const val EXTRA_LOCATION = PACKAGE_NAME + ".location"
private const val EXTRA_STARTED_FROM_NOTIFICATION =
PACKAGE_NAME + ".started_from_notification"
/**
* The desired interval for location updates. Inexact. Updates may be more or less frequent.
*/
private const val UPDATE_INTERVAL_IN_MILLISECONDS: Long = 1000
/**
* The fastest rate for active location updates. Updates will never be more frequent
* than this value.
*/
private const val FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS =
UPDATE_INTERVAL_IN_MILLISECONDS / 2
/**
* The identifier for the notification displayed for the foreground service.
*/
private const val NOTIFICATION_ID = 12345678
}
MainActivity
class MainActivity : AppCompatActivity() {
private val TAG = "TEST"
private val FOREGROUND_LOCATION_CODE = 2
// The BroadcastReceiver used to listen from broadcasts from the service.
private var myReceiver: MyReceiver? = null
// A reference to the service used to get location updates.
private var mService: LocationService? = null
// Monitors the state of the connection to the service.
private val mServiceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
val binder: LocationService.LocalBinder = service as LocationService.LocalBinder
mService = binder.service
}
override fun onServiceDisconnected(name: ComponentName) {
mService = null
}
}
#RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
checkForegroundLocationPermission()
myReceiver = MyReceiver()
myReceiver?.let {
LocalBroadcastManager.getInstance(this)
.registerReceiver(it, IntentFilter(LocationService.ACTION_BROADCAST))
}
findViewById<Button>(R.id.start).setOnClickListener { view ->
Snackbar.make(view, "Start listening...", Snackbar.LENGTH_LONG).show()
Log.d("TEST", "Start listening...")
mService?.requestLocationUpdates();
}
findViewById<Button>(R.id.stop).setOnClickListener { view ->
Snackbar.make(view, "Stop listening...", Snackbar.LENGTH_LONG).show()
Log.d("TEST", "Stop listening...")
mService?.removeLocationUpdates()
}
}
override fun onStart() {
super.onStart()
// Bind to the service. If the service is in foreground mode, this signals to the service
// that since this activity is in the foreground, the service can exit foreground mode.
// Bind to the service. If the service is in foreground mode, this signals to the service
// that since this activity is in the foreground, the service can exit foreground mode.
Intent(this, LocationService::class.java).also {
bindService(it, mServiceConnection, BIND_AUTO_CREATE)
}
}
override fun onResume() {
super.onResume()
Log.d(TAG, "onResume")
}
override fun onStop() {
Log.d(TAG, "onStop")
super.onStop()
}
#RequiresApi(Build.VERSION_CODES.M)
private fun checkForegroundLocationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// Check if permission is not granted
Log.d(TAG, "Permission for foreground location is not granted")
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
FOREGROUND_LOCATION_CODE)
} else {
// Permission is already granted, do your magic here!
Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
}
}
#RequiresApi(Build.VERSION_CODES.Q)
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray) {
when (requestCode) {
FOREGROUND_LOCATION_CODE -> {
Log.d(TAG, "onRequestPermissionsResult -> FOREGROUND_LOCATION_CODE")
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Foreground Permission granted", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this, "Foreground Permission denied", Toast.LENGTH_SHORT).show()
}
return
}
}
}
private class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val location: Location? = intent.getParcelableExtra(LocationService.EXTRA_LOCATION)
if (location != null) {
Log.d("TEST", "Location = $location")
}
}
}
}

App crashes while getting location through ForegroundService

I've implemented the exact same example of LocationUpdateForeground Service as provided in these location-samples. The problem is when I run the provided project, it runs but as I'm working with Kotlin and these projects are in Java, my app crashes and givers null pointer exception on mService!!.removeLocationUpdates() but I've tried copying it the exact same without any changes and it still crashes.
I am stuck on getting background location as most of them don't work and this one works exactly even in the Android Q.
The error comes because the onServiceConnected() never gets called in which the mService is being initialized.
Manifest.xml
<service
android:name="com.app.writer.LocationUpdatesService"
android:enabled="true"
android:exported="true"
android:foregroundServiceType="location"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
MainActivity
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
var prevMenuItem: MenuItem? = null
private lateinit var tinyDB: TinyDB
private lateinit var myApi: INodeJS
private lateinit var mySnackbar: Snackbar
private var compositeDisposable = CompositeDisposable()
private var mServiceBinder: LocationUpdatesService.LocalBinder? = null
private val TAG = MainActivity::class.java.simpleName
// Used in checking for runtime permissions.
private val REQUEST_PERMISSIONS_REQUEST_CODE = 34
// The BroadcastReceiver used to listen from broadcasts from the service.
private var myReceiver: MainActivity.MyReceiver? =
null
// A reference to the service used to get location updates.
private var mService: LocationUpdatesService? = null
// Tracks the bound state of the service.
private var mBound = false
private val mServiceConnection: ServiceConnection = object :
ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
mServiceBinder = service as LocationUpdatesService.LocalBinder
mService = mServiceBinder!!.service
mBound = true
}
override fun onServiceDisconnected(name: ComponentName) {
mService = null
mBound = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.decorView.systemUiVisibility = (
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
tinyDB = TinyDB(this) //Easy way to save data in SharedPreferences
myReceiver = MyReceiver()
// Check that the user hasn't revoked permissions by going to Settings.
if (Utils.requestingLocationUpdates(this)) {
if (!checkPermissions()) {
requestPermissions()
}
}
}
override fun onDestroy() {
super.onDestroy()
}
override fun onStop() {
if (mBound) {
// Unbind from the service. This signals to the service that this activity is no longer
// in the foreground, and the service can respond by promoting itself to a foreground
// service.
unbindService(mServiceConnection)
mBound = false
}
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this)
super.onStop()
}
override fun onStart() {
super.onStart()
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this)
Writer.setOnClickListener{
if (!checkPermissions()) {
requestPermissions()
} else {
if (tinyDB.getBoolean("Writer")) {
mySnackbar.setText("Kavach Disabled!!!").show()
} else {
mService!!.requestLocationUpdates()
}
}
}
// Restore the state of the buttons when the activity (re)launches.
setButtonsState(Utils.requestingLocationUpdates(this))
// Bind to the service. If the service is in foreground mode, this signals to the service
// that since this activity is in the foreground, the service can exit foreground mode.
applicationContext.bindService(
Intent(this, LocationUpdatesService::class.java), mServiceConnection,
Context.BIND_AUTO_CREATE
)
}
override fun onResume() {
super.onResume()
LocalBroadcastManager.getInstance(this).registerReceiver(
myReceiver!!,
IntentFilter(LocationUpdatesService.ACTION_BROADCAST)
)
}
override fun onPause() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(myReceiver!!)
super.onPause()
}
/**
* Returns the current state of the permissions needed.
*/
private fun checkPermissions(): Boolean {
return PackageManager.PERMISSION_GRANTED == ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
}
private fun requestPermissions() {
val shouldProvideRationale =
ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
// Provide an additional rationale to the user. This would happen if the user denied the
// request previously, but didn't check the "Don't ask again" checkbox.
if (shouldProvideRationale) {
Log.i(
TAG,
"Displaying permission rationale to provide additional context."
)
mySnackbar.setText("Location permission is needed for core functionality")
.setAction("Ok", View.OnClickListener { // Request permission
ActivityCompat.requestPermissions(
this#MainActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_PERMISSIONS_REQUEST_CODE
)
})
.show()
} else {
Log.i(TAG, "Requesting permission")
// Request permission. It's possible this can be auto answered if device policy
// sets the permission in a given state or the user denied the permission
// previously and checked "Never ask again".
ActivityCompat.requestPermissions(
this#MainActivity,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_PERMISSIONS_REQUEST_CODE
)
}
}
/**
* Callback received when a permissions request has been completed.
*/
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String?>,
grantResults: IntArray
) {
Log.i(TAG, "onRequestPermissionResult")
if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
if (grantResults.isEmpty()) {
// If user interaction was interrupted, the permission request is cancelled and you
// receive empty arrays.
Log.i(TAG, "User interaction was cancelled.")
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission was granted.
mService!!.requestLocationUpdates()
} else {
// Permission denied.
setButtonsState(false)
mySnackbar.setText("Permission Denied, can't work without it")
.setAction(
R.string.settings
) { // Build intent that displays the App settings screen.
val intent = Intent()
intent.action =
android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts(
"package",
BuildConfig.APPLICATION_ID, null
)
intent.data = uri
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)
}
.show()
}
}
}
/**
* Receiver for broadcasts sent by [LocationUpdatesService].
*/
private class MyReceiver : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
val location =
intent.getParcelableExtra<Location>(LocationUpdatesService.EXTRA_LOCATION)
if (location != null) {
Toast.makeText(
context, Utils.getLocationText(location),
Toast.LENGTH_SHORT
).show()
}
}
}
override fun onSharedPreferenceChanged(
sharedPreferences: SharedPreferences,
s: String
) {
// Update the buttons state depending on whether location updates are being requested.
if (s == Utils.KEY_REQUESTING_LOCATION_UPDATES) {
setButtonsState(
sharedPreferences.getBoolean(
Utils.KEY_REQUESTING_LOCATION_UPDATES,
false
)
)
}
}
private fun setButtonsState(requestingLocationUpdates: Boolean) {
if (!requestingLocationUpdates) {
mService!!.removeLocationUpdates()
} else {
mService!!.requestLocationUpdates()
}
}
}
I know what a nullpointer exception is, but here I'm stuck and also, it happen because of onStart.
replace your service declaration with
<service android:enabled="true" android:name="com.app.writer.LocationUpdatesService"/>

Categories

Resources