Budius
Budius

Reputation: 39846

How to detect process foreground for Android O

On our application there's a service that is normally started during Application.OnCreate (directly calling context.startService) and also later on via AlarmManager (refactor is in progress to migrate some of its work to JobScheduler).

Our application also have a BroadcastReceiver that gets launched with its direct intent.

Given the new limitations in Android Oreo (https://developer.android.com/about/versions/oreo/android-8.0-changes.html) we're having an issue as follows:

this leads to crash with "IllegalStateException: Not allowed to start service Intent".

I'm aware of the new recommended ways of launching a Service as answered by CommonsWare here https://stackoverflow.com/a/44505719/906362, but for this specific case, I simply want to have if(process in foreground) { startService }. I'm currently using the following method and it seems to work:

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static boolean isProcessInForeground_V21(Context context) {
        ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
        List<ActivityManager.AppTask> tasks = am.getAppTasks();
        return tasks.size() > 0;
    }

But I can't find the exact checks Android Oreo is doing (I got as far as here https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ContextImpl.java on the startServiceCommon method, but from there requireForeground flag seems to go to some native implementation)

So my question:

For the specific purpose of Android Oreo new limitations, how to check if my process is foreground before calling startService?

Upvotes: 5

Views: 977

Answers (1)

TWiStErRob
TWiStErRob

Reputation: 46480

To continue your investigation: (TL;DR: see between horizontal lines at the bottom)


Disclaimer, I don't know too much about Android internals, I just like digging in the source code.
Note: you can also navigate the code in Android Studio if you jump to file instead of class: enter image description here or searching for text in Project and Libraries. enter image description here


IActivityManager is defined by AIDL, that's why there are no sources for it: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/app/IActivityManager.aidl#145

Based on how AIDL needs to be implemented I found that ActivityManagerService extends IActivityManager.Stub (God bless Google indexing).

Note I also found this, which might be an interesting read if you're really interested how things work internally. https://programmer.group/android-9.0-source-app-startup-process.html

ActivityManagerService sources reveal that in Oreo startService is forwarded to ActiveServices which is located in the same package.

Assuming we're looking for an exception like this:

java.lang.IllegalStateException: Not allowed to start service Intent {...}: app is in background uid UidRecord{af72e61 u0a229 CAC bg:+3m52s273ms idle procs:1 seq(0,0,0)}

we have to continue down the rabbit hole: requireForeground gets assigned to fgRequired parameter and the message is here. The condition to allow this depends on the start mode returned by ActivityManagerService.getAppStartModeLocked(packageTargetSdk = 26 or greater, disabledOnly = false, forcedStandby = false).

There are 4 start modes:

  • APP_START_MODE_NORMAL (needs to be different than this, i.e. !=)
  • APP_START_MODE_DELAYED (this is ok, i.e. return null)
  • APP_START_MODE_DELAYED_RIGID
  • APP_START_MODE_DISABLED

Ephemeral apps will immediately return APP_START_MODE_DISABLED, but assuming this is a normal app, we end up in appServicesRestrictedInBackgroundLocked. Note: this is where some of the whitelist mentioned in https://stackoverflow.com/a/46445436/253468 is decided. Since all branches but last return APP_START_MODE_NORMAL, this redirects to appRestrictedInBackgroundLocked where we find our most likely suspect:

    int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
        // Apps that target O+ are always subject to background check
        if (packageTargetSdk >= Build.VERSION_CODES.O) {
            return ActivityManager.APP_START_MODE_DELAYED_RIGID;
        }

So the reason for denial is simply targeting O. I think the final answer to your question of how the OS decides if your app is foreground or background is this condition in getAppStartModeLocked

UidRecord uidRec = mActiveUids.get(uid);
if (uidRec == null || alwaysRestrict || uidRec.idle) {

My guess is that a missing record means it's not running (but then how is it starting a service?!), and idle means it's backgrounded. Notice that in my exception message the UidRecord is saying that it's idle and has been backgrounded for 3m52s.

I peeked into your getAppTasks and it's based on TaskRecord.effectiveUid, so I'm guessing that's quite close to listing UidRecords for your app.


Not sure if this helps, but I'll post it anyway, so if anyone wants to investigate more, they have more info.

Upvotes: 0

Related Questions