Reputation: 39846
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
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:
or searching for text in Project and Libraries.
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:
!=
)return null
)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 UidRecord
s 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