Android timer when phone is idle - android

I am trying to create an Android app which plays a sound every few seconds. I want this to work even when the phone is idle. At the moment everything works fine even when the phone screen is off. But after about a minute, the timer stops working. As soon as the screen is turned back on, the missed sounds are played in quick succession. I struggle to find the right terms and concepts to properly find a solution with Google.
When I first encountered this issue, I made sure that my service was running in the background. As it seems, the service is also enabled in the background because everything works fine as long as the screen is not turned off.
Code for running the service in the background:
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
timer.scheduleAtFixedRate(TimeTask(), 0, 100);
return START_STICKY
}
private inner class TimeTask() : TimerTask() {
override fun run() {
sendBroadcast(Intent(TIMER_UPDATED))
}
}
Since this didn't work, I tried to make the service a foreground service. But this didn't work either. (I tried to do it as shown here)
Code for running the service in foreground:
private fun runInBackground() {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("service", "something")
} else {
""
}
val notification: Notification = NotificationCompat.Builder(this, channelId)
.setContentTitle("title")
.setContentText("text")
.setSmallIcon(R.drawable.alert_dark_frame)
.build()
startForeground(1, notification)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String{
val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE)
channel.lightColor = Color.BLUE
channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
(getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager)
.createNotificationChannel(channel)
return channelId
}
I read something about scheduling tasks in Android. And found the AlarmManager, but I don't think this would really work the way I want it to because I would have to schedule an alarm for every 100ms. The official doc also states that this shouldn't be used in that way and that "handlers" are more suited, but I struggle to understand how I could replace my current timer with such a handler. I have tried to implement something, but failed.
val updateHandler = Handler()
val runnable = Runnable {
// some action
}
updateHandler.looper(runnable, 100)

Finally solved my problem. Something that I didn't understand before was a "wakelock". It looks something like this:
val wakeLock: PowerManager.WakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ShuttleRun::DefaultWakeLock").apply {
acquire(30*60*1000L)
}
}
It basically just keeps the device awake, which doesn't mean that the screen is turned on, but rather that the service can run in the background without being disturbed.

Related

Microphone foreground service is almost always killed by certain operating systems

I simply cannot understand why my phone kills my foreground service.
Relevant code
In AndroidManifest:
<service
android:name=".recorder.RecorderService"
android:exported="false"
android:process=":Recorder"
android:foregroundServiceType="microphone">
</service>
In my service launcher
ContextCompat.startForegroundService(application, Intent(application, RecorderService::class.java))
Inside the actual service
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO)
val notification = buildRecordingNotification()
startServiceWithNotification(notification)
recordingJob = recordSounds.executeCancellable(Unit, serviceScope)
return START_STICKY
}
private fun buildRecordingNotification(): Notification {
val channelId = buildNotificationChannelId()
val builder = NotificationCompat.Builder(this, channelId)
return builder
.setSmallIcon(R.drawable.ic_stat_notify)
.setContentTitle("Recording...")
.setWhen(0)
.setChannelId(channelId)
.build()
}
private fun buildNotificationChannelId() =
NotificationChannelHelper.createChannel(this, NotificationChannelHelper.NOTIF_ID_RECORDER, NotificationManagerCompat.IMPORTANCE_LOW)
private fun startServiceWithNotification(notification: Notification) =
try {
startForeground(NOTIFICATION_ID, notification)
} catch (e: Exception) {
Log.e("RecorderService", "Failed to startForeground", e)
}
Inside my recording job
private suspend fun startRecorder() {
audioRecord = withContext(Dispatchers.Main) {
CustomAudioRecord().also { ar ->
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO)
ar.startRecording()
recordingSessionId = UUID.randomUUID().toString()
}
}
}
The problem
On OnePlus phones, the foreground service dies after around 30 minutes (so does the app) when the screen is closed and app put into background (easiest way to reproduce)
What I have tried
I tried making a lightweight process to host the service, in which I inject only the needed dependencies for the service to run. The whole process takes up around 120mb of ram, compared to nearly 700mb when the main process is running.
I checked dumpsys logs and got the following reading on my service:
Proc #25: prcp F/S/FGS --M t: 0 22173:***Recorder/u0a714 (fg-service)
Looks like it checks out as being a correctly created foreground service.
I tried using android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_AUDIO)
Didn't help.
I checked for wakelocks (tried using another one explicitly) and the Mic IN wakelock is active during my service.
A few notes
We have another foreground service for media playback, and it is usually not killed on OnePlus phones. It is built the same way.
On some devices the service runs all day all night without a problem.

Can I make my background service not stop or pause in kotlin? (Android Studio)

I am making a stopwatch app and would like my stopwatch to continue after 1 minute of the app being closed or the phone being turned off. I was wondering if there was any way for me to make my timer service continue working after the the app is paused. In the background service I am currently using the timer function stop exactly 1 minute after the app is closed. I have been searching the internet for solutions which have pointed me towards foreground services but I am very inexperienced and cannot figure out how to make them work.
Here is my timer service (I got this from Code With Cal on youtube)
class TimerService : Service() {
override fun onBind(p0: Intent?): IBinder? = null
private val timer = Timer()
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val time = intent.getDoubleExtra(TIME_EXTRA, 0.0)
timer.scheduleAtFixedRate(TimeTask(time), 0, 1000)
return START_NOT_STICKY
}
override fun onDestroy() {
timer.cancel()
super.onDestroy()
}
private inner class TimeTask(private var time: Double) : TimerTask() {
override fun run() {
val intent = Intent(TIMER_UPDATED)
time++
intent.putExtra(TIME_EXTRA, time)
sendBroadcast(intent)
}
}
companion object {
const val TIMER_UPDATED = "timerUpdated"
const val TIME_EXTRA = "timeExtra"
}
Can I make this into a service that continues on when the app is closed? Thank you so much (Note: This is my first app and I am very inexperienced. If there is any more code you need, please tell me, thanks!)
As Mike M. Answered in the comments:
"Nope, you can't do that, but you don't really need/want to, anyway. All you really need to know whenever your app is setting up the stopwatch is the starting time of the current timing session, and the time it is right now. If you save that starting time somewhere persistent – e.g., in SharedPreferences – then you just need to retrieve that value and do some simple arithmetic to get the stopwatch re-set correctly."
-Thanks so much (-Casper)

Unable to cancel periodic work request running foreground

I have got a periodic work request running foreground. Using the following version of WorkManager
androidx.work:work-runtime-ktx:2.5.0-alpha03
Here is the work request.
val workRequest = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
setConstraints(constraints).build()
And on the worker class, I set it to foreground.
class MyWorker(context, params) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
setForeground(createForegroundInfo())
//rest of the logic
}
private void createForegroundInfo() {
val cancelIntent = WorkManager.getInstance(context).createCancelPendingIntent(id) //work request id
val notification = NotificationCompat.Builder(context, channelId)
.setContentTitle("Test Title")
.addAction(actionIconRes, "Cancel", cancelIntent).build()
return ForegroundInfo(notificationId, notification)
}
}
When I run it, I can see the notification coming up with cancel action. When clicked, the notification disappears momentarily and appears again. Nothing happens on further clicks on cancel action. There is no cancel signalled at all either. What could potentially be a stopper? Any clue?
I think I found the problem. I had nested suspending functions which were causing the Worker to cancel completely. Now that I have changed those functions, it's all working now.

How to make a service that running in the background even if the app's already destroyed?

I want to make a feature to notify user when there's data changes in my firebase database. The problem is when the app is destroyed, then the service automatically destroyed. After that when the data in database change, my app won't notify the user.
Here's some snippet code for my apps.
Service :
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "DogNotificationService created")
val phone = intent?.extras?.getString("PHONE")
if(isFirstTime) {
val firebaseDatabase = FirebaseDatabase.getInstance()
notificationReference = firebaseDatabase.getReference("walker/$phone/notification")
val coroutineScope = CoroutineScope(Job() + Dispatchers.IO)
coroutineScope.launch {
launchListener()
}
isFirstTime = false
}
return super.onStartCommand(intent, flags, startId)
}
fun launchListener() {
valueEventListener =
notificationReference.addValueEventListener(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
Log.w(TAG, "Read failed: " + p0.message)
}
override fun onDataChange(p0: DataSnapshot) {
val notificationData = p0.getValue(String::class.java)
if(count > 0) {
Log.d(TAG, notificationData)
sendNotification(notificationData!!)
}
count++
}
})
}
When I start the service :
private fun startNotificationService() {
val intent = Intent(context, DogNotificationService::class.java)
intent.putExtra("PHONE", "081293312313")
Log.d(TAG, "Start notification service")
activity?.startService(intent)
}
If any idea to do this approach, please help.
I was working in an app to keep track of the users to allow them record their tracks and found out that services can be killed at any moment if the android system requires free memory. Even if your service has wakelocks or is running in foreground.
The solution I found was to use alarms with a foreground service, if you schedule alarms this alarms will be fired whether your app is still executing or not. That way my app could get the device position even though the system had killed the app due to lack of resources. It's the only solution I found that works in this scenario. An alarm that wakes up the service.
The idea came to me in some google i/o when they said that if you really need your app to continue no matter what you should use alarms instead of services.
Besides that, if you need the app to be awaked constantly then use exact alarms as the inexact ones in some devices they might be fired 5 minutes later if the time that they should be fired is too near to the current time.

How can I run a service the would have access to my database and could run operations on it, but won't stuff up the UI thread

I am building an app the needs to go through a collection of photos stored locally, which I import to a room database, and try to detect for each if it contains faces or not.
I've got everything sorted, my only issue is how to run this operation, which could take a while, in a service that wouldn't stuff up the UI thread.
At first I wanted to use a JobIntentService but couldn't because I was unable to observeForever on a background thread, and couldn't use a simple observer because I have no lifecycleOwner to give to the Observer.
I've ended up using just a service, as soon as the operation starts my UI is pretty much stuck and if I try to do anything the app crashes.
I tried maybe IntentService but I can't use the observer in onHandleIntent because it's a worker thread and it doesn't let me, and when I run the operations under onStartCommand then it's just the same thing.
I feel like I am stuck with the architecture of this thing, I'd appreciate any ideas. Thank you.
This is my service:
class DetectJobIntentService : Service() {
private val TAG = "DetectJobIntentServi22"
lateinit var repo: PhotoRepository
lateinit var observer : Observer<MutableList<Photo>>
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val options = FirebaseVisionFaceDetectorOptions.Builder()
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ACCURATE)
.setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
.setMinFaceSize(0.15f)
.build()
val detector = FirebaseVision.getInstance()
.getVisionFaceDetector(options)
repo = PhotoRepository(application)
observer = Observer {
for (file in it) {
val image = FirebaseVisionImage.fromFilePath(application, Uri.parse(file.uri))
AsyncTask.execute {
detector.detectInImage(image).addOnSuccessListener { list ->
if (list.isNotEmpty()) {
file.hasFaces = 1
repo.update(file)
} else {
file.hasFaces = 2
repo.update(file)
}
}
}
}
}
repo.getAllPhotos().observeForever(observer)
val notificationIntent= Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this,
0, notificationIntent, 0)
val notification = NotificationCompat.Builder(this, getString(tech.levanter.anyvision.R.string.channel_id))
.setContentTitle("Detecting faces..")
.setContentText("64 photos detected")
.setSmallIcon(tech.levanter.anyvision.R.drawable.ic_face)
.setContentIntent(pendingIntent)
.build()
startForeground(1, notification)
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
repo.getAllPhotos().removeObserver(observer)
}
}
Seeing your code is in Kotlin, I'll advice you try out Kotlin Coroutines. This would enable you dispatch expensive operations i.e. querying databases, making network requests/calls off to other threads thereby not blocking the UIThread. Coroutines help you avoid the hassle of callbacks. Also, Google just deprecated the AsyncTask API in favour of Coroutines as the way to go for multi-threading purposes.

Categories

Resources