Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How was the source code of startService() modified to recognize if it was called from background?

Since about Android 9 it throws an IllegalStateException if startService() was called from the background. I see this exception many times in my developer console:

java.lang.IllegalStateException:
  at android.app.ContextImpl.startServiceCommon (ContextImpl.java:1666)
  at android.app.ContextImpl.startService (ContextImpl.java:1611)

In these cases, Google recommends to call startForegroundService() and within 5 seconds startForeground(), instead. See "Background execution limits".

Anyway, calling startService() from foreground is perfectly ok. Now, I wonder how exactly Android recognizes/decides that an app is in the foreground to not wrongly throwing an IllegalStateException?

I was starting to dig the source code of Android9/10 and comparing it with 8/7 to discover how startService() was modified to recognize if it was called from foreground/background. But I'm convinced that many developers before me did this already, and I would be happy if they'd give an answer.

like image 738
red symbol man Avatar asked Nov 16 '25 09:11

red symbol man


1 Answers

In AOSP10 (10.0.0_r25):

Server side:

in startServiceLocked from frameworks\base\services\core\java\com\android\server\am\ActiveServices.java:

        // Before going further -- if this app is not allowed to start services in the
        // background, then at this point we aren't going to let it period.
        final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
                r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
        if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
            Slog.w(TAG, "Background start not allowed: service "
                    + service + " to " + r.shortInstanceName
                    + " from pid=" + callingPid + " uid=" + callingUid
                    + " pkg=" + callingPackage + " startFg?=" + fgRequired);
            ......
            // This app knows it is in the new model where this operation is not
            // allowed, so tell it what has happened.
            UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(r.appInfo.uid);
            return new ComponentName("?", "app is in background uid " + uidRec);
        }

Then in client side:

in ContextImpl.java as your log:

            else if (cn.getPackageName().equals("?")) {
                throw new IllegalStateException(
                        "Not allowed to start service " + service + ": " + cn.getClassName());
            }
like image 130
jw_ Avatar answered Nov 19 '25 00:11

jw_