how to run location tracking service after screen is off? - android

location can't update when screen is off.
How to run location tracking service after screen is off ?
I start service using
val serviceClass = ControlLocationService::class.java
val intent = Intent(activity, serviceClass)
activity?.startService(intent)
In onLocationChanged method I try Log.e()
but not show latitude and longitude in logcat when screen is off
class ControlLocationService : Service() {
lateinit var locationRequest: LocationRequest
lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private lateinit var locationCallback:LocationCallback
override fun onBind(intent: Intent): IBinder? {
throw UnsupportedOperationException("Not yet implemented")
}
#SuppressLint("InvalidWakeLockTag")
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
//service is started
updateLocation()
return START_STICKY
}
#SuppressLint("MissingPermission")
private fun updateLocation() {
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
onLocationChanged(locationResult!!.lastLocation)
}
}
buildLocationRequest()
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
fusedLocationProviderClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper())
}
private fun onLocationChanged(location: Location) {
Log.e("...", location.latitude.toString()+location.longitude.toString())
}
private fun buildLocationRequest() {
locationRequest = LocationRequest()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = 3000
locationRequest.fastestInterval = 1000
//locationRequest.smallestDisplacement = 1f
}
}
Help me introduce about service management when the screen is off.
thank you.

You will need a wakelock and I would also make your service foreground. This will make android less likely to kill your app or service. Actually according to Background Location Limits its not said that you need a wakelock - only foreground service. By making your app work continously in the background, you will make it quickly drain your user battery - which is bad. Consider using passive location updates, those might happen few time in a hour - which might not be what you are after.
You might also want to read about doze mode which makes all non white-listed applications ignore wake locks while in screen off mode.
To add wake lock:
val mgr = service.getSystemService(Context.POWER_SERVICE) as PowerManager
val wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "myAppName:MyWakeLock")
wakeLock.acquire()
in onDestroy:
wakeLock.release()
Use below code to add notification to notification bar and make service foreground. You should add addServiceNotification(this) in your onStartCommand.
fun addServiceNotification(service: Service) {
val mgr = NotificationManagerCompat.from(service)
val title = "Caption"
val text = "Location"
if (Build.VERSION.SDK_INT >= 26) {
val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (notificationManager != null) {
var mChannel: NotificationChannel? = notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
if (mChannel == null) {
mChannel = NotificationChannel(NOTIFICATION_CHANNEL_ID, "myAppName", NotificationManager.IMPORTANCE_LOW)
notificationManager.createNotificationChannel(mChannel)
}
}
val notification = NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.app_icon)
.setTicker(title)
.setWhen(System.currentTimeMillis())
.setContentTitle(title)
.setContentText(text)
.setOnlyAlertOnce(true)
.setOngoing(true)
.build()
service.startForeground(LOCATION_NOTIFICATION_ID, notification)
mgr.cancel(LOCATION_NOTIFICATION_ID)
mgr.notify(LOCATION_NOTIFICATION_ID, notification)
}
}
// remove notification for location service
fun removeServiceNotification(service: Service) {
val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager?.cancel(LOCATION_NOTIFICATION_ID)
}
companion object {
val LOCATION_NOTIFICATION_ID = 1001
val NOTIFICATION_CHANNEL_ID = "myAppName"
}

Related

Foreground Service for sensor data stops after a small period of time

I am developing an android application where sensor data are recorded from the sensors, displayed in graphs and also stored in an SQLite database. I want the sensor data process to run when the app is not visible in the screen, so i designed a foreground service for that purpose. The service seems to be running ok for about 5 min and then the app stops recording sensor data.
I am trying to understand why this is occuring. The app in the emulator is not turned off, the screen is lit. I have declared my foreground service, (i call startForeground on onCreate() function of the service).
I am showcasing part of the code of the foreground service:
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
startMyOwnForeground()
else
startForeground(1, Notification())
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
initSensors()
registerListeners()
}
#RequiresApi(Build.VERSION_CODES.O)
private fun startMyOwnForeground() {
val chan = NotificationChannel(
"MyChannelId",
"My Foreground Service",
NotificationManager.IMPORTANCE_HIGH
)
val manager = (getSystemService(NOTIFICATION_SERVICE) as NotificationManager)!!
manager!!.createNotificationChannel(chan)
val notificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(
this, "MyChannelId"
)
val notification: Notification = notificationBuilder.setOngoing(true)
.setContentTitle("App is running on foreground")
.setPriority(NotificationManager.IMPORTANCE_HIGH)
.setSmallIcon(R.drawable.sym_def_app_icon)
.setCategory(Notification.CATEGORY_SERVICE)
.setChannelId("MyChannelId")
.build()
startForeground(1, notification)
}
And small excerpt from the sensor recording process:
private fun initSensors()
{
accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
accelometerSensorEventListener = object: SensorEventListener
{
override fun onSensorChanged(event: SensorEvent?) {
for (i in 0 until event!!.values.size)
{
accelerometerValues[i] = event.values[i]
}
//keep the time stamp for each event
if (!acceleratorTimeStampSet)
{
acceleratorTimeStampSet = true
initialAcceleratorTimeStamp = System.currentTimeMillis()
}
//set the timestamps
val timestampX: Long = System.currentTimeMillis() - initialAcceleratorTimeStamp
accelerometerValuesTimeStampCounter ++
//store the timestamps
accelerometerValuesTimeStamps.add(timestampX)
val accelSensorTriplets = ArrayList<Float>(3)
if (accelSensorTriplets.size == 0)
{
accelSensorTriplets.add(0, event.values[0])
accelSensorTriplets.add(1, event.values[1])
accelSensorTriplets.add(2, event.values[2])
}
else
{
accelSensorTriplets.set(0, event.values[0])
accelSensorTriplets.set(1, event.values[1])
accelSensorTriplets.set(2, event.values[2])
}
//add a dateTime Object value to the array
val dateTime = LocalDateTime.now()
if (!allTimeStampsAccel.contains(dateTime.format(DateTimeFormatter.ofPattern("YYYY-MM-d hh:mm:ss")))) {
allTimeStampsAccel.add(dateTime.format(DateTimeFormatter.ofPattern("YYYY-MM-d hh:mm:ss")))
if (!allTimeStamps.contains(dateTime.format(DateTimeFormatter.ofPattern("YYYY-MM-d hh:mm:ss")))) {
allTimeStamps.add(dateTime.format(DateTimeFormatter.ofPattern("YYYY-MM-d hh:mm:ss")))
}
var pair1 = Pair<Float, String>(
accelSensorTriplets.get(0),
allTimeStampsAccel.get(allTimeStampsAccel.size - 1)
)
var pair2 = Pair<Float, String>(
accelSensorTriplets.get(1),
allTimeStampsAccel.get(allTimeStampsAccel.size - 1)
)
var pair3 = Pair<Float, String>(
accelSensorTriplets.get(2),
allTimeStampsAccel.get(allTimeStampsAccel.size - 1)
)
var pairArrayList = ArrayList<Pair<Float, String>>(3)
pairArrayList.add(pair1)
pairArrayList.add(pair2)
pairArrayList.add(pair3)
accelerometerAllValuesForStore.add(pairArrayList)
Log.d(
"DATA",
"accel data with timestamp : ${allTimeStampsAccel.get(allTimeStampsAccel.size - 1)}"
)
}
//add the data for the accelerometer chart
Log.d("SQL" , "ACCEL ${allTimeStampsAccel.get(allTimeStampsAccel.size-1)}")
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
}
}
Any help on why this behaviour is occuring is appreciated.
Lampros
On a additional note, the onDestroy method of the service is immediatelly called witin the service execution...here is the code from the onStartCommand of the service:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
startMyOwnForeground()
else
startForeground(1, Notification()) }
// //Power management
// //Power management
val am = applicationContext.getSystemService(ALARM_SERVICE)
val pm = getSystemService(POWER_SERVICE) as PowerManager
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "myapp:mywakelocktag")
wl!!.acquire()
SetAlarm(applicationContext)
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
initSensors()
registerListeners()
return START_STICKY
}
Why does the onDestroy gets called instantly?

Running a foreground service in android kotlin to show notifications every minute through out the day using handler and runnable

Running a foreground service in android kotlin to show notifications every minute through out the day using handler and runnable .Get the notification when i run the below code but after sometime the notifications stops. If the phone is in locked state and if i unlock it ,the notification starts coming but again stops after sometime. Please help me with this.
class MedTimerService : Service() {
private val FOREGROUND_NOTIFY_ID = 5051
private val TIME_GAP_FOR_NEXT_MEDICATION = 24 * 60 * 60 * 1000
private var morningTime = 0L
private var afternoonTime = 0L
private var eveningTime = 0L
private var nightTime = 0L
private val minuteHandler: Handler = Handler()
private val morningHandler: Handler = Handler()
private val afternoonHandler: Handler = Handler()
private val eveningHandler: Handler = Handler()
private val nightHandler: Handler = Handler()
private val teleconsultReminder: Handler = Handler()
#RequiresApi(Build.VERSION_CODES.O)
private val minuteRunnable: Runnable = Runnable {
doTheProcess()
}
#RequiresApi(Build.VERSION_CODES.O)
private fun doTheProcess() {
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath + "/Wekare/data/handlerForEveryMinute").mkdirs()
FL.init(
FLConfig.Builder(this#MedTimerService)
.logger(null) // customise how to hook up with logcat
.defaultTag("HandlerForEveryMinute Tag") // customise default tag
.minLevel(FLConst.Level.V) // customise minimum logging level
.logToFile(true) // enable logging to file
.dir(File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath + "/Wekare/data/handlerForEveryMinute")) // customise directory to hold log files
.retentionPolicy(FLConst.RetentionPolicy.FILE_COUNT) // customise retention strategy
.maxFileCount(FLConst.DEFAULT_MAX_FILE_COUNT) // customise how many log files to keep if retention by file count
.maxTotalSize(FLConst.DEFAULT_MAX_TOTAL_SIZE) // customise how much space log files can occupy if retention by total size
.build()
)
FL.setEnabled(true)
showMedsReminderNotification("HandlerForEveryMinute",DateUtils.getEpochDateTimeFromDate(System.currentTimeMillis()))
FL.d(
"HandlerForEveryMinute",
"minute handler Current Time ${DateUtils.getEpochDateTimeFromDate(System.currentTimeMillis())}"
)
minuteHandler.removeCallbacksAndMessages(minuteRunnable)
minuteHandler.postDelayed(minuteRunnable, 60 * 1000L)
}
override fun onCreate() {
super.onCreate()
}
#RequiresApi(Build.VERSION_CODES.O)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// FL.d("onStartCommand")
if (intent != null) {
val action = intent.action
// TimberLogger.i("FOREGROUND-START-ID", startId.toString())
//TimberLogger.i("FOREGROUND-ACTION", action.toString())
return if (action == "START") {
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath + "/Wekare/data/medicationTimerService").mkdirs()
FL.init(
FLConfig.Builder(this#MedTimerService)
.logger(null) // customise how to hook up with logcat
.defaultTag("MedicationTimerService Tag") // customise default tag
.minLevel(FLConst.Level.V) // customise minimum logging level
.logToFile(true) // enable logging to file
.dir(File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath + "/Wekare/data/medicationTimerService")) // customise directory to hold log files
.retentionPolicy(FLConst.RetentionPolicy.FILE_COUNT) // customise retention strategy
.maxFileCount(FLConst.DEFAULT_MAX_FILE_COUNT) // customise how many log files to keep if retention by file count
.maxTotalSize(FLConst.DEFAULT_MAX_TOTAL_SIZE) // customise how much space log files can occupy if retention by total size
.build()
)
FL.setEnabled(true)
FL.d("started MedicationTimerService")
// processTimings(Injector.provideWeKareRepository().reminderTimeInfo)
minuteHandler.postDelayed(minuteRunnable, 60 * 1000L)
startForegroundService()
showMedsReminderNotification("MedicationTimerService",DateUtils.getEpochDateTimeFromDate(System.currentTimeMillis()))
START_STICKY
} else {
minuteHandler.removeCallbacksAndMessages(null)
stopForeground(true)
stopSelfResult(startId)
FL.d("stopped MedicationTimerService")
STOP_FOREGROUND_REMOVE
}
} else {
return super.onStartCommand(intent, flags, startId)
}
}
#RequiresApi(Build.VERSION_CODES.O)
private fun startForegroundService() {
val notification = buildForegroundNotification()
notification.flags = Notification.FLAG_FOREGROUND_SERVICE.let {
notification.flags = notification.flags or it; notification.flags
}
startForeground(
FOREGROUND_NOTIFY_ID,
notification
)
FL.d("start foreground service")
File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath + "/Wekare/data/logs5").mkdirs()
FL.init(
FLConfig.Builder(WeKareApplication.application.applicationContext)
.logger(null) // customise how to hook up with logcat
.defaultTag("Background Tag") // customise default tag
.minLevel(FLConst.Level.V) // customise minimum logging level
.logToFile(true) // enable logging to file
.dir(File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).absolutePath + "/Wekare/data/logs5")) // customise directory to hold log files
// .formatter(FileFormatter()) // customise log format and file name
.retentionPolicy(FLConst.RetentionPolicy.FILE_COUNT) // customise retention strategy
.maxFileCount(FLConst.DEFAULT_MAX_FILE_COUNT) // customise how many log files to keep if retention by file count
.maxTotalSize(FLConst.DEFAULT_MAX_TOTAL_SIZE) // customise how much space log files can occupy if retention by total size
.build()
)
FL.setEnabled(true)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun buildForegroundNotification(): Notification {
// return getDefaultNotification()
return getCustomNotification()
}
#RequiresApi(Build.VERSION_CODES.O)
private fun getCustomNotification(): Notification {
val notificationCompatBuilder =
NotificationCompat.Builder(
applicationContext,
WeKareConstants.NOTIFICATION_CHANNEL_ID_MED
)
val notificationManager = notificationManager()
createNotificationChannel(
notificationCompatBuilder,
notificationManager,
WeKareConstants.NOTIFICATION_CHANNEL_ID_MED,
WeKareConstants.NOTIFICATION_CHANNEL_NAME_MED,
NotificationManager.IMPORTANCE_LOW
)
val notificationLayout = RemoteViews(packageName, R.layout.notification_meds_layout)
val resultPendingIntent: PendingIntent? = getResultPIntent()
notificationCompatBuilder
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(resultPendingIntent)
// .setStyle(NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(notificationLayout)
.setOngoing(true)
.setAutoCancel(true)
return notificationCompatBuilder.build()
FL.d("custom notification")
}
private fun notificationManager(): NotificationManager {
return applicationContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
}
private fun getResultPIntent(): PendingIntent? {
val resultIntent = Intent(applicationContext, MainActivity::class.java)
resultIntent.putExtra(WeKareConstants.IS_FROM_NOTIFICATION, true)
resultIntent.putExtra(WeKareConstants.IS_FROM_THERAPY, false)
resultIntent.putExtra(WeKareConstants.IS_MEDICATION_REMINDER, true)
return TaskStackBuilder.create(applicationContext).run {
addNextIntentWithParentStack(resultIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
#RequiresApi(Build.VERSION_CODES.O)
private fun showMedsReminderNotification(title: String, message: String) {
val notificationCompatBuilder =
NotificationCompat.Builder(
applicationContext,
WeKareConstants.NOTIFICATION_CHANNEL_IMPORTANT_ID
)
val notificationManager =
notificationManager()
createNotificationChannel(
notificationCompatBuilder,
notificationManager,
WeKareConstants.NOTIFICATION_CHANNEL_IMPORTANT_ID,
WeKareConstants.NOTIFICATION_CHANNEL_IMPORTANT_NAME,
NotificationManager.IMPORTANCE_HIGH
)
val bitmap = applicationContext.vectorToBitmap(R.drawable.ic_launcher_background)
val resultPendingIntent: PendingIntent? = getResultPIntent()
notificationCompatBuilder
.setLargeIcon(bitmap).setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle(title)
.setContentText(message)
.setContentIntent(resultPendingIntent)
.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setColor(Color.parseColor("#f06d35"))
.setStyle(NotificationCompat.BigTextStyle().bigText(message))
.setAutoCancel(true)
notificationManager.notify(
System.currentTimeMillis().toInt(),
notificationCompatBuilder.build()
)
FL.d("Posted Medication Notification to the system ")
}

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")
}
}
}
}

Android Doze mode kills foregreound MediaPlayer service

I've been researching awhile about how to keep active a constantly running audio playback in the background (online radio). For last I made a foreground service for it and its works for the most phones, but not on Samsung Android P and above... (as this article show in the "Foreground service limitations" section: https://proandroiddev.com/android-foreground-service-restrictions-d3baa93b2f70)
I heard that there is a advanced tool for audio playback called ExoPlayer. Could this lib help me out?
I'v been tried these solutions:
ping google.com every 2 sec
request battery optimization ignoring
set wake lock for mediplayer with: setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK) (still in use)
Starting the service:
viewModel.isMusicControlServiceNeedToStart.observe(this, Observer {
if (it) {
val intent = Intent(this, MusicControlForegroundServiceImpl::class.java).apply { action = ACTION_SHOW_MUSIC_CONTROL }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(intent) else startService(intent)
} else {
stopService(Intent(this, MusicControlForegroundServiceImpl::class.java))
}
})
The service itself:
class MusicControlForegroundServiceImpl : Service(), KoinComponent {
private val notificationManager: NotificationManager by inject()
private val radioManager: RadioManager by inject()
private val context: Context by inject()
private val preferences: Preferences by inject()
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.action == ACTION_SHOW_MUSIC_CONTROL) {
val lastSelectedRadio = preferences.getJSON(Globals.LAST_SELECTED_RADIO_KEY, Radio::class.java)
?: return START_NOT_STICKY
val notification = notificationManager.createMediaControlNotificationIfNeeded(context, lastSelectedRadio)
startForeground(1, notification)
}
return START_NOT_STICKY
}
override fun onTaskRemoved(rootIntent: Intent?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(rootIntent) else startService(rootIntent)
super.onTaskRemoved(rootIntent)
}
override fun onDestroy() {
if (!notificationManager.musicControlServiceRestart) radioManager.release()
synchronized(MUSIC_CONTROL_SERVICE_LOCK) { notificationManager.musicControlServiceRestart = false }
synchronized(MEDIA_PLAYER_LOCK) { radioManager.lastPlayedMediaUrl = null }
stopForeground(true)
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
}
The notification creation:
override fun createMediaControlNotificationIfNeeded(context: Context, selectedRadio: Radio): Notification {
val resultIntent = Intent(context, RadioDetailActivity::class.java)
val resultPendingIntent: PendingIntent? = TaskStackBuilder.create(context).run {
addNextIntentWithParentStack(resultIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
val playIntent = Intent(context, NotificationReceiver::class.java).apply {
putExtra(MusicState::class.java.name, MusicState.PLAY)
}
val pauseIntent = Intent(context, NotificationReceiver::class.java).apply {
putExtra(MusicState::class.java.name, MusicState.PAUSE)
}
val notificationManager = NotificationManagerCompat.from(context)
#Suppress("DEPRECATION") val builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification_icon)
.setContentTitle(selectedRadio.name)
.setDefaults(0)
.setOngoing(true)
.setNotificationSilent()
.addAction(
R.drawable.ic_notification_pause,
context.getString(R.string.pause),
PendingIntent.getBroadcast(context, 1, pauseIntent, 0)
)
.addAction(
R.drawable.ic_notification_play,
context.getString(R.string.play),
PendingIntent.getBroadcast(context, 2, playIntent, 0)
)
.setStyle(
androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(
MediaSessionCompat(
context,
RadioDetailActivity::class.java.name
).sessionToken
)
)
.setContentIntent(resultPendingIntent)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
context.getString(R.string.app_name),
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel)
builder.setChannelId(CHANNEL_ID)
}
return builder.build()
}
If you need any other resources please let me know and help if you can! I'm struggling with this problem for weeks now.. :(
UPDATE
Now I throw my media control notification in every 2 minutes to update previous, so the app can survive like 30 minutes on the affected phone, but still not a working solution...

Periodic Task in Background

I'm currently building an app that starts a session by sending a request to the backend. After that, the app has to send a heartbeat request every 4.5 minutes. If the app does not send a request to the backend after 4.5 minutes since the last successful request, the session will get terminated. The heartbeat request can be sent earlier which also means that the next heartbeat has to be sent 4.5 minutes after that request.
Once the user has started the session, he should be able to put the app to the background to use the device for other things.
I'm struggling with coming up with a solution that works with the background restrictions (doze mode, etc).
I'm currently running the app with a foreground service. But the requests stop after a couple of minutes if I don't use the device actively. I tried the WorkManager and the AlarmManager. But the requests keep getting delayed.
I played around with REQUEST_IGNORE_BATTERY_OPTIMIZATIONS and this seems to work but I don't want to use that approach since Google seems to really dislike apps using this permission.
I created a test app to play around with different approaches. Maybe I'm doing something completely wrong?
Service:
class MainService : Service() {
private lateinit var wakeLock: PowerManager.WakeLock
override fun onBind(intent: Intent?): IBinder? = null
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= 26) {
val appName = getString(R.string.app_name)
val channelName = "$appName channel name"
val channelImportance = NotificationManager.IMPORTANCE_HIGH
val channelDescription = "$appName channel description"
createNotificationChannel(this, NOTIFICATION_CHANNEL_ID, channelName, channelImportance, channelDescription)
}
val notification = createOngoingNotification(this, NOTIFICATION_REQUEST_CODE, R.mipmap.ic_launcher_round, "Content Text")
startForeground(1000, notification)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp::MyWakelockTag").apply {
acquire(1 * 60 * 60 * 1000L)
}
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
intent?.action?.let {
if (it == "Heartbeat") {
val v = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
v.vibrate(VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE))
}
}
}
setNext()
return START_STICKY
}
private fun setNext() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val intent = Intent(applicationContext, MainService::class.java)
intent.action = "Heartbeat"
val pendingIntent = PendingIntent.getService(applicationContext, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5 * 1000, pendingIntent)
}
}
override fun onDestroy() {
stopForeground(true)
wakeLock.release()
super.onDestroy()
}
companion object {
const val REQUEST_CODE = 101
const val NOTIFICATION_REQUEST_CODE = 100
const val NOTIFICATION_CHANNEL_ID = "notification_channel_id"
fun createOngoingNotification(context: Context, requestCode: Int, icon: Int, text: String): Notification {
val contentIntent = Intent(context, MainActivity::class.java)
.setAction(Intent.ACTION_MAIN)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
val contentPendingIntent = PendingIntent.getActivity(context, requestCode, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT)
return NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setOngoing(true)
.setSmallIcon(icon)
.setContentTitle("Test Notification")
.setContentText(text)
.setContentIntent(contentPendingIntent)
.build()
}
#RequiresApi(api = 26)
fun createNotificationChannel(context: Context,
id: String, name: String, importance: Int,
description: String) {
val channel = NotificationChannel(id, name, importance)
channel.description = description
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}
I think that foreground service may be stopped as you are not executing anything inside it after you set the alarm. Foreground service keeps running if you are using it, so for making it work periodically i would put an observable that emits a value every 5 seconds for example. You only need the one emmited after 4.5 minutes but that will keep the foreground service active until you need it. Using rxjava:
Observable.intervalRange( 0 , 54, 0, 5, TimeUnit.SECONDS )
.doOnNext{
//You may not need this
}
.doOnComplete {
//heartbeat
//start this observable again for other 4.5 minutes
}
You will emit a value every 5 seconds 54 times. 54x5 = 270 seconds (what is 4.5 minutes)

Categories

Resources