I am creating an application which is inspired by another app in which the application collect my location even if the application is closed I am able to achieve the similar behaviour by creating a broadcast receiver which collect my location but In my application when I collect the location I get warning form the Android Setting that this app access location in the background but for the other app form playstore I have not see any such warning till this time I am using that app for a while now.
How can I achieve similar behaviour.
One thing which I have notice is that when that other app collect the location the android location icon show in the status bar but in case when I collect the location I can't see that. May be this gives some hint.
I even tried to star a foreground service while collection the location while the app is closed but can't start the service form the background.
I get this error:
Failed to start service: startForegroundService() not allowed due to mAllowStartForeground false: service com.kgJr.safecircle/.ui_util.services.LocationUpdateService
So how can I achieve the behaviour I am looking for.
I will be glad if someone can help me on this. Thank You !!!
I am trying the collect the location in the background while avoiding the warning from android default system that <This> app access location in the background like a similar app I am using which also collect the location in the background even if the application is closed and I wont get any such warning.
current code for fetching the location
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
fun getCurrentLocation(context: Context, callback: (Location?) -> Unit) {
if (!isLocationPermissionGranted(context)) {
callback(null)
return
}
val fusedLocationClient = getFusedLocationClient(context)
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
callback(location)
}.addOnFailureListener {
callback(null)
}
}
all the permissions
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<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" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
Ok this is the solution I have Implemented and its actually working super well.
so, the problem was I need to get location in the background but also I don't want to keep the app up and running all the time and also it should be efficient so, the it wont consume whole bunch of battery
so this is the solution:
so first you have to take some permission like for getting location in the background i.e
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
after this instead of having a single broadcast receiver I have two different type of broadcast receiver i.e
Activity Broadcast Receiver
BootReceiverRestarter
Only this two because I tried the wifi receiver and several other thing they did not worked that well.
<receiver android:name="com.kgjr.safecircle.broadcastReceiver.ActivityTransitionReceiver"
android:exported="true"
tools:ignore="ExportedReceiver" />
<receiver
android:name=".broadcastReceiver.BootReceiverRestarter"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
After this I also include Alarm manager why because in some case after some time like 1 or 2 day the activity receiver will stop catching the activity this the whole flow will break the alarm manager will ensure that we at the vary least collect the data at fixed interval and thus we can reinitialize the or check for the activity receiver is working fine or not also one important point is that we don't prescedule the whole alarm receiver in advance but we do something like this : After receiving the signal we scheduled for the next one.
Also for reciving the alarm and keeping the loop going we have to give some especific things I have experimented with other multiple options present but they did not worked that well so, this is the best I can come up with I have searched all over the internet but did not found a fully composed solution.
so, the below is the xml for the Alarm receiver:
<receiver
android:name=".broadcastReceiver.AlarmBootReceiverForLooper"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:name=".broadcastReceiver.AlarmReceiver"
android:exported="true"
tools:ignore="ExportedReceiver" />
<receiver
android:name=".broadcastReceiver.AlarmReceiverLooper"
android:exported="true"
tools:ignore="ExportedReceiver" />
<service
android:name=".service.AlarmForegroundService"
android:foregroundServiceType="location"
android:exported="false" />
<service
android:name=".service.AlarmForegroundServiceLooper"
android:foregroundServiceType="location"
android:exported="false" />
Also one cool thing about whole this is if for some reason the users phone shuts down the then restart the data collection will began again the user don't need to open the phone again
so the home screen once the app is loaded for the first time (after giving all the permission) I initialize all this things i.e :
LaunchedEffect(Unit) {
/**
* @Mark: always initialize the below method because it also creates the notification channel.
*/
LocationActivityManager.cancelPeriodicNotificationWorker(context)
LocationActivityManager.initializeNotificationAndWorker(context)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val permission = Manifest.permission.ACTIVITY_RECOGNITION
if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) {
delay(5000) // 5-sec delay
LocationActivityManager.startActivityRecognition(context)
} else {
Log.e("SafeCircle", "ACTIVITY_RECOGNITION permission not granted")
}
} else {
Log.d("SafeCircle", "ACTIVITY_RECOGNITION not required for SDK < 29")
}
}
here is how I intialize the things
object LocationActivityManager {
fun initializeNotificationAndWorker(context: Context) {
createNotificationChannel(context)
schedulePeriodicNotificationWorker(context) // For confirmation that location will be collect in 15 min max.
}
@RequiresPermission(Manifest.permission.ACTIVITY_RECOGNITION)
fun startActivityRecognition(context: Context) {
val activityRecognitionClient = ActivityRecognition.getClient(context)
val intent = Intent(context, ActivityTransitionReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
activityRecognitionClient.requestActivityUpdates(3000, pendingIntent) //kind of sating ask for update in every 3 sec...
.addOnSuccessListener {
Log.d("LocationActivityManager", "Activity updates requested successfully")
}
.addOnFailureListener {
Log.e("LocationActivityManager", "Failed to request activity updates", it)
}
}
private fun createNotificationChannel(context: Context) {
val channel = NotificationChannel(
UPDATE_LOCATION_CHANNEL_ID,
"Location Updates",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Used for background location update notifications"
}
val manager = context.getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
private fun schedulePeriodicNotificationWorker(context: Context) {
Log.d("WorkManager", "PeriodicNotificationWorker has been schedule")
val periodicWorkRequest = PeriodicWorkRequestBuilder<PeriodicLocationUpdater>(
15, TimeUnit.MINUTES
).addTag("PeriodicNotificationWorkerTag").build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"PeriodicNotificationWorkerTag",
ExistingPeriodicWorkPolicy.KEEP,
periodicWorkRequest
)
}
fun cancelPeriodicNotificationWorker(context: Context) {
Log.d("WorkManager", "PeriodicNotificationWorker has been cancelled.")
WorkManager.getInstance(context)
.cancelUniqueWork("PeriodicNotificationWorkerTag")
}
}
afte this you just need to create the bracast reciver and Alarm manager ie:
class ActivityTransitionReceiver : BroadcastReceiver() {
companion object {
private lateinit var sharedPreferenceManager: SharedPreferenceManager
}
private lateinit var notificationService: NotificationService
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.POST_NOTIFICATIONS])
override fun onReceive(context: Context, intent: Intent) {
//initializing the managers in the starting...
sharedPreferenceManager = SharedPreferenceManager(context)
notificationService = NotificationService(context)
if (MainApplication.getGoogleAuthUiClient().getSignedInUser()?.userId != null){
if (ActivityRecognitionResult.hasResult(intent)) {
val result = ActivityRecognitionResult.extractResult(intent)
val activity = result!!.mostProbableActivity
val type = getActivityType(activity.type)
val confidence = activity.confidence
Log.d("SafeCircle", "Detected activity: $type with confidence: $confidence")
try {
LocationUtils.getCurrentLocation(context) { location ->
location?.let {
updateLocation(context, type, location) {
}
}
}
} catch (e: SecurityException) {
e.printStackTrace()
Log.e("SafeCircle", "Missing location permission.")
}
}
}
}
}
you can create the function updateLocation() according to your need now.
this is the broadcast receiver of the case of device boot
class LocationBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
Log.d("LocationBroadcast", "Broadcast received: ${intent?.action}")
when (intent?.action) {
LocationManager.PROVIDERS_CHANGED_ACTION -> {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
val isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
Log.d("LocationBroadcast", "GPS: $isGpsEnabled, Network: $isNetworkEnabled")
}
Intent.ACTION_AIRPLANE_MODE_CHANGED -> {
Log.d("LocationBroadcast", "Airplane mode changed")
}
}
}
}
class BootReceiverRestarter : BroadcastReceiver() {
@RequiresPermission(Manifest.permission.ACTIVITY_RECOGNITION)
override fun onReceive(context: Context, intent: Intent?) {
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
Log.d("SafeCircle", "Boot completed - re-registering activity updates")
val activityRecognitionClient = ActivityRecognition.getClient(context)
val pendingIntent = PendingIntent.getBroadcast(
context,
0,
Intent(context, ActivityTransitionReceiver::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
activityRecognitionClient.requestActivityUpdates(3000, pendingIntent)
.addOnSuccessListener {
Log.d("SafeCircle", "Successfully re-registered activity updates")
}
.addOnFailureListener {
Log.e("SafeCircle", "Failed to re-register activity updates", it)
}
}
}
}
then this is the alarm broadcast receiver for the looping condition i.e
class AlarmReceiverLooper : BroadcastReceiver() {
companion object {
private lateinit var sharedPreferenceManager: SharedPreferenceManager
}
private lateinit var scheduler: AndroidAlarmSchedulerLooper
private lateinit var notificationService: NotificationService
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.POST_NOTIFICATIONS])
override fun onReceive(context: Context, intent: Intent) {
Log.d("SafeCircle", "AlarmReceiver triggered")
try {
BackgroundApiManagerUtil.uploadAllPendingData()
}catch (e: Exception){
e.printStackTrace()
}
scheduler = MainApplication.getScheduler()
sharedPreferenceManager = SharedPreferenceManager(context)
notificationService = NotificationService(context)
print("Looper Status: ${sharedPreferenceManager.getIsUpdateLocationApiCalledLooper()} ")
// if (sharedPreferenceManager.getIsUpdateLocationApiCalledLooper() == false && MainApplication.getGoogleAuthUiClient().getSignedInUser()?.userId != null) {
// val serviceIntent = Intent(context, AlarmForegroundServiceLooper::class.java).apply {
// putExtra("ActivityType", "N.A")
// }
// ContextCompat.startForegroundService(context, serviceIntent)
// }
LocationUtils.getCurrentLocation(context) { location ->
location?.let {
updateLocation(
context = context,
activityType = "N.A",
currentLocation = location,
onCompletion = {
scheduler.scheduleAlarm(300)
}
)
}
}
}
the below one is for single time use just comment this one line
// scheduler.scheduleAlarm(10)
all the other thing will remain the same
this is the boot receiver for Alarm Scheduler
class AlarmBootReceiverForLooper : BroadcastReceiver() {
companion object {
private lateinit var sharedPreferenceManager: SharedPreferenceManager
}
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
Log.d("SafeCircle", "Device booted. Attempting to reschedule alarm.")
sharedPreferenceManager = SharedPreferenceManager(context)
val scheduler = MainApplication.getScheduler()
scheduler.scheduleAlarm(timeInSec = 1)
sharedPreferenceManager.saveLooperEnabled(true)
Log.d("SafeCircle", "Alarm rescheduled after boot.")
}
}
}
this is the core implementation and the idea and thought process behind this
hope this answers will help.