Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Job Scheduler not starting jobs in locked/direct boot on Android 12

I am working on making my alarm app compatible with a soon to be released Android 12 and lately I stumbled upon a problem. It seems that since Android 12 update, jobs scheduled in Job Scheduler during locked/direct boot are not started until after the device is unlocked. I have reviewed changes made in Android 12 and I don't find any that would apply to my situation and I found out about it during random tests.

It is a major problem, as I need to reschedule alarms for my app on devices reboot and can't wait for the user to unlock the device first, as a reboot may happen automatically overnight, leaving user without a scheduled alarm in the morning.

When running below test app (targeting Android 11), results are as below:

  • Android 11 and lower: job is scheduled and started immediately both during locked and regular boot.
  • Android 12: work as above during regular boot, but in locked boot job is scheduled immediately, but starts only after device is unlocked.

Any suggestions on how to work with this, beside running whole job manually, for example with a use of a WakeLock? Or maybe someone knows what changes of Android 12 are actually in effect here?

Here's a simple test class for observing this situation:

class TestScheduler : JobService() {
companion object {
    fun addScheduledJob(context: Context){
        JobInfo.Builder(1111, ComponentName(context, TestScheduler::class.java))
            .setOverrideDeadline(TimeUnit.SECONDS.toMillis(1))
            .build().let {
                val result = (context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler).schedule(it)
                when(result){
                    JobScheduler.RESULT_SUCCESS -> "success"
                    else -> "failure"
                }.also {
                    Log.d("MyTAG", "jobScheduled: $it")
                }
            }
    }
}
override fun onStartJob(params: JobParameters?): Boolean {
    Log.d("MyTAG", "onStartJob: ${params?.jobId}")
    jobFinished(params, false)
    return true
}

override fun onStopJob(params: JobParameters?): Boolean {
    Log.d("MyTAG", "onStopJob: ${params?.jobId}")
    return true
}
}

Job is scheduled from a boot receiver:

class OnBootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
    TestScheduler.addScheduleJob(context)
}
}

Both clasess are directBootAware (manifest's content):

<receiver
    android:name=".OnBootReceiver"
    android:directBootAware="true"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
    </intent-filter>
</receiver>

<service
    android:name=".TestScheduler"
    android:directBootAware="true"
    android:permission="android.permission.BIND_JOB_SERVICE" />
like image 287
Koger Avatar asked Nov 27 '22 13:11

Koger


1 Answers

I am having the same issue, and believe I have tracked down the change to the AOSP build which appear to cause the problem:

Looking at the android-12 history, there was a change to JobSchedulerService.java to prevent jobs from running when the at the lock screen.

Merge "Wait for unlock to start jobs." into sc-dev

    @Override
    public void onUserUnlocked(@NonNull TargetUser user) {
        synchronized (mLock) {
            // Note that the user has started after its unlocked instead of when the user
            // actually starts because the storage won't be decrypted until unlock.
            mStartedUsers = ArrayUtils.appendInt(mStartedUsers, user.getUserIdentifier());
        }
        // Let's kick any outstanding jobs for this user.
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    }

And that change was later backed out:

Partial revert "Wait for unlock to start jobs."

    @Override
    public void onUserStarting(@NonNull TargetUser user) {
        synchronized (mLock) {
            mStartedUsers = ArrayUtils.appendInt(mStartedUsers, user.getUserIdentifier());
        }
        // The user is starting but credential encrypted storage is still locked.
        // Only direct-boot-aware jobs can safely run.
        // Let's kick off any eligible jobs for this user.
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    }
    
    @Override
    public void onUserUnlocked(@NonNull TargetUser user) {
        // The user is fully unlocked and credential encrypted storage is now decrypted.
        // Direct-boot-UNaware jobs can now safely run.
        // Let's kick off any outstanding jobs for this user.
        mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
    }

I have back-ported this change to to my source and it appears to have resolved the issue.

For those with the option to take a newer AOSP release, android-12.1.0_26 has this change present.

like image 172
Simon Williams Avatar answered Dec 10 '22 02:12

Simon Williams