Codementor Events

Enable background services and Jobs (or FCM) in Chinese ROMs

Published Aug 07, 2018


Every Problem has a work around and if it works fine and suits your need then its kinda solution for you


Lets discuss the problem statement first  :
While using Chinese ROMs ( Oxygen OS, MIUI etc ) when the apps are swiped from the recent app tray your app gets terminated ( similar to Force Stop). And due to this every task running in the background like Services, Jobs gets killed with the app. Even High priority FCM doesn’t see the daylight in Chinese ROMs.

This creates a lot of problems for startups and developers because they neither can execute anything in background nor they can send any notifications to engage the users.(Except apps like Whatsapp, Facebook ,Instagram, Google etc who are whitelisted by these Chinese ROMs and no one has any clue how to get your app whitelisted).

Real time problems  :

  1. You can’t fetch location of the user. So, no app would work properly if it depends on real-time location
  2. You can’t recevie the FCM high priority notifications 
  3. No time specific task/Job would execute if app is not in the tray and many more ….

What problem did I face ? :
My app work depends on Jobs for syncing data with the server at a particular time in the day and we push notifications to the device based on the task user performs or has completed it. 
It worked fine when tested with Pixel 2, Moto G5s(Stock ROMs) but it failed when tested on devices like OnePlus 5, OnePlus 5T, Xiaomi 3s, Redmi 2 etc. and considering Indian Smartphone market it wouldn’t be wrong to say these manufacturers dominate the market.
 Lets be clear, I’m not doing any heavy work on it, but periodicity was important, and that is why I couldn’t trust using a Job. Trust me, I tried GCM & FCM, and I found times where the Job wasn’t fired for hours. Also, running a Foreground Service for a recurring task it’s not a good idea; almost any user will feel uncomfortable with a fixed notification bar saying that your app is ‘always running’ and Android telling you (with another ongoing notification) that app ‘x’ is using the battery, even if the service is not doing any job


What did I try?
Just to make it work, I went through the internet like anything and got few ways to make background task happen(both are not under the control of the developer),

  1. User need to lock the app in the recent app tray list so that when he swipes all app from the tray your app doesn’t gets killed/swiped


Locked app in App’s tray

  1. Or user needs to enable auto start in settings for app to keep background service/Job running.By default it is off.To do this in some of the devices we can use,
val intent = Intent()
val manufacturer = android.os.Build.MANUFACTURER
when(manufacturer) {
    "xiaomi" ->   
             intent.component=
             ComponentName("com.miui.securitycenter",
             "com.miui.permcenter.autostart
             .AutoStartManagementActivity")
    "oppo" -> 
            intent.component =
            ComponentName("com.coloros.safecenter",
           "com.coloros.safecenter.permission.startup
            .StartupAppListActivity")
    "vivo" -> 
            intent.component = 
            ComponentName("com.vivo.permissionmanager",
            "com.vivo.permissionmanager.activity
            .BgStartUpManagerActivity")
}

val list = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
if (list.size > 0) {
    context.startActivity(intent)
}

But It is also not a full proof solution to our problem as these are modifications that are done at the OS level (Oxygen OS, MIUI etc) and just don’t take my word for it. I would like to quote one of Developer Advocate of Google :

With tech savvy users it might even work but don’t be surprised if you don’t get much far with this technique

So I came up with a work around to the problem and till now it worked fine for me on Modified OSes.


The Solution :

Or should I say: How to bypass Background Services restrictions in Android Oreo.Whatever, but it worked fine for me
So, after trying many methods and approaches, I started testing with AlarmManager and BroadcastReceiver , and this was the only thing that worked for me.(There were still few issues when the alarm manager didn’t fire) and with Android OREO, The startService() method now throws an IllegalStateExceptionwhen started form background.

So, What I am doing is to set the AlarmManager to fire a Broadcast every X minutes/hour. I configured the BroadcastReceiver in my AndroidManifest and inside the onReceive method I just fire a JobIntentService to do the work.

  1. Let’s declare our Receiver in the AndroidManifest.xml
<receiver android:name=".AlarmReceiver">
    <intent-filter>
        <action android:name="com.test.intent.action.ALARM" />
    </intent-filter>
</receiver>
  1. Make our JobIntentService do the background task.
class AlarmJobIntentService : JobIntentService() {

    override fun onHandleWork(intent: Intent) {
        /* your code here */ /* reset the alarm */ Util.showDebugLog("setAlarmCtx", "started Bottom")
        AlarmReceiver.setAlarm(false)
        stopSelf()
    }

    companion object {

        /* Give the Job a Unique Id */ private val JOB_ID = 1000

        fun enqueueWork(ctx: Context, intent: Intent) {
            JobIntentService.enqueueWork(ctx, AlarmReceiver::class.java, JOB_ID, intent)
        }
    }
}
  1. Create our BroadcastReceiver ,
class AlarmReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        /* enqueue the job */ AlarmJobIntentService.enqueueWork(context, intent)
    }

    companion object {

        val CUSTOM_INTENT = "com.test.intent.action.ALARM"

        val ctx = ApplicationClass.getAppContext()

        fun cancelAlarm() {
            val alarm = ctx.getSystemService(Context.ALARM_SERVICE)  
                        as AlarmManager

            /* cancel any pending alarm */ alarm.cancel(pendingIntent)
        }

        @RequiresApi(VERSION_CODES.M)
        fun setAlarm(force: Boolean) {
            cancelAlarm()
            val alarm = ctx.getSystemService(Context.ALARM_SERVICE) 
                        as AlarmManager
            // EVERY N MINUTES val delay = (1000 * 60 * N).toLong()
            var `when` = System.currentTimeMillis()
            if (!force) {
                `when` += delay
            }

            /* fire the broadcast */ val SDK_INT = Build.VERSION.SDK_INT
            when {
                SDK_INT < Build.VERSION_CODES.KITKAT -> alarm.set(AlarmManager.RTC_WAKEUP, `when`, pendingIntent)
                SDK_INT < Build.VERSION_CODES.M -> alarm.setExact(AlarmManager.RTC_WAKEUP, `when`, pendingIntent)
                else -> alarm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, `when`, pendingIntent)
            }
        }

        private val pendingIntent: PendingIntent
            get() {
                val alarmIntent = Intent(ctx, AlarmManagerTaskBroadCastReceiver::class.java)
                alarmIntent.action = CUSTOM_INTENT

                return PendingIntent.getBroadcast(ctx, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT)
            }
    }
}

That’s pretty much it. Now to call this in your application use,

AlarmReceiver.setAlarm(true); /* true will force the broadcast */

This will set an alarm that will broadcast the ‘ alarmIntent ’ (you can add data to this intent that you will receive in ‘ onHandleWork ’ from your Job.

The job will recreate the alarm (I chose this path so the job will run N minutes after finishing its task)

If you need to cancel the alarm, just call

AlarmReceiver.cancelAlarm();

Remember that Android cancels all the alarms when a device is rebooted. You can follow this to reset your alarm when the device boots:

Alarm Scheduling Documentation

Another thing to be aware of if that if the user Force Close your app for any reason, then your alarm will not fire anymore until you call setAlarm again.

PS: This works fine as far as I have tested on Chinese ROMs. This is not the recommended way but is a work-around to make your app work in background on modified OSes.

// Now You should use WorkManager to schedule jobs rather than using this solution.
 
If you like the article please show your support by clicking on clap fab. 
Let’s get in touch on Facebook, Twitter,LinkedIn, Github and Instagram.

Discover and read more posts from Himanshu Singh
get started
post commentsBe the first to share your opinion
Manas Jayanth
6 years ago

Going to try this out in the weekend! Great idea btw. Thanks for the post.

Show more replies