Android Q has been officially released, and there is one privacy adjustment that will have a big impact on domestic apps. That’s what the title says: “Prohibit the Activity from being started in the background.” Indeed, it’s a good thing for users in all aspects.

  • Avoid interruptions (sudden pop-ups while playing games)
  • Pop-up ads for rogue apps
  • Rogue apps’ One-pixel survival ‘

It is this feature that makes it more difficult to keep the application alive, and as a user, I am in favor of banning it. As a developer, I also support banning. But for the sake of inquiry, I decided to look into it.

In addition, because I was the first time to see the source code, itself does not understand the internal structure, so more is through the keyword search to see some characteristics of the method or code, and for methods or attributes do not have a deep understanding, floating on the surface, so the content contains a large number of source code. The reading experience can be poor. Please forgive me.

Research Conclusions:

After source code analysis, basic can not directly bypass the inspection mechanism. However, you can consider masquerading through system services or tuning indirectly. The specific indirect bypass method has not been studied.

Research process:

When the Activity is refused to start in the background, the corresponding Log is generated, such as:

09-10 02:37:42.615  1967  2087 I ActivityTaskManager: START u0 {flg=0x10000004 cmp=com.lollipop.startactivitywhenbackground/.MainActivity (has extras)} from uid 10131

09-10 02:37:42.618  1967  2087 W ActivityTaskManager: Background activity start [callingPackage: com.lollipop.startactivitywhenbackground; callingUid: 10131; isCallingUidForeground: false; isCallingUidPersistentSystemProcess: false; realCallingUid: 1000; isRealCallingUidForeground: false; isRealCallingUidPersistentSystemProcess: true; originatingPendingIntent: PendingIntentRecord{583ac97 com.lollipop.startactivitywhenbackground startActivity}; isBgStartWhitelisted: false; intent: Intent { flg=0x10000004 cmp=com.lollipop.startactivitywhenbackground/.MainActivity (has extras) }; callerApp: null]
Copy the code

AndroidXref: AndroidXref: ActivityTaskManager: ActivityTaskManager: ActivityTaskManager: ActivityTaskManager: ActivityTaskManager: ActivityTaskManager: ActivityTaskManager Activitytaskmanager. Java is a wrapper class that implements activityTaskManager. Java.

    / * *@hide* /
    public static IActivityTaskManager getService(a) {
        return IActivityTaskManagerSingleton.get();
    }
    @UnsupportedAppUsage(trackingBug = 129726065)
    private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
            new Singleton<IActivityTaskManager>() {
                @Override
                protected IActivityTaskManager create(a) {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                    returnIActivityTaskManager.Stub.asInterface(b); }};Copy the code

And then, of course, give up and search through the Pie code to see where the service is initialized. In the end, found the SystemServer. Java, then find the concrete implementation class ActivityTaskManagerService. Java package path.

import com.android.server.wm.ActivityTaskManagerService;
Copy the code

Then, trace the package path would be found the real file path: ActivityTaskManagerService. Java.

From it, the following methods are found through the search of background keyword:

@Override
    public final int startActivities(IApplicationThread caller, String callingPackage,
            Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle bOptions,
            int userId) {
        final String reason = "startActivities";
        enforceNotIsolatedCaller(reason);
        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, reason);
        // TODO: Switch to user app stacks here.
        return getActivityStartController().startActivities(caller, -1.0, -1, callingPackage,
                intents, resolvedTypes, resultTo, SafeActivityOptions.fromBundle(bOptions), userId,
                reason, null /* originatingPendingIntent */.false /* allowBackgroundActivityStart */);
    }
    @Override
    public int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
                resultWho, requestCode, startFlags, profilerInfo, bOptions, userId,
                true /*validateIncomingUser*/);
    }
    int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId,
            boolean validateIncomingUser) {
        enforceNotIsolatedCaller("startActivityAsUser");
        userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
                Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");
        // TODO: Switch to user app stacks here.
        return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
                .setCaller(caller)
                .setCallingPackage(callingPackage)
                .setResolvedType(resolvedType)
                .setResultTo(resultTo)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setStartFlags(startFlags)
                .setProfilerInfo(profilerInfo)
                .setActivityOptions(bOptions)
                .setMayWait(userId)
                .execute();
    }
Copy the code

The above code, batch startup method is explicitly set directly allowBackgroundActivityStart to false. StartActivityAsUser is unset and keeps the default value. GetActivityStartController () return objects for: ActivityStartController. Java. The details of its obtainStarter() method are:

    / * * *@return A starter to configure and execute starting an activity. It is valid until after
     *         {@link ActivityStarter#execute} is invoked. At that point, the starter should be
     *         considered invalid and no longer modified or used.
     */
    ActivityStarter obtainStarter(Intent intent, String reason) {
        return mFactory.obtain().setIntent(intent).setReason(reason);
    }
Copy the code

An activitystarter.java object is returned, and the previous parameters are stored in the internal Request class, where the default Settings are:

/** * Ensure constructed request matches reset instance. */
        Request() {
            reset();
        }
        /** * Sets values back to the initial state, clearing any held references. */
        void reset(a) {... allowBackgroundActivityStart =false;
        }
Copy the code

Background startup is rejected by default. That is to say, if the user starts, it is forbidden to start directly in the background. Code is specific judgment on shouldAbortBackgroundActivityStart method, by this method is the core method of judgment, so put up the complete code.

boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
            final String callingPackage, int realCallingUid, int realCallingPid,
            WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
            boolean allowBackgroundActivityStart, Intent intent) {
        // don't abort for the most important UIDs
        final int callingAppId = UserHandle.getAppId(callingUid);
        if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID
                || callingAppId == Process.NFC_UID) {
            return false;
        }
        // don't abort if the callingUid has a visible window or is a persistent system process
        final int callingUidProcState = mService.getUidState(callingUid);
        final boolean callingUidHasAnyVisibleWindow =
                mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
        final boolean isCallingUidForeground = callingUidHasAnyVisibleWindow
                || callingUidProcState == ActivityManager.PROCESS_STATE_TOP
                || callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
        final boolean isCallingUidPersistentSystemProcess =
                callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
        if (callingUidHasAnyVisibleWindow || isCallingUidPersistentSystemProcess) {
            return false;
        }
        // take realCallingUid into consideration
        final int realCallingUidProcState = (callingUid == realCallingUid)
                ? callingUidProcState
                : mService.getUidState(realCallingUid);
        final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid)
                ? callingUidHasAnyVisibleWindow
                : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(realCallingUid);
        final boolean isRealCallingUidForeground = (callingUid == realCallingUid)
                ? isCallingUidForeground
                : realCallingUidHasAnyVisibleWindow
                        || realCallingUidProcState == ActivityManager.PROCESS_STATE_TOP;
        final int realCallingAppId = UserHandle.getAppId(realCallingUid);
        final boolean isRealCallingUidPersistentSystemProcess = (callingUid == realCallingUid)
                ? isCallingUidPersistentSystemProcess
                : (realCallingAppId == Process.SYSTEM_UID)
                        || realCallingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
        if(realCallingUid ! = callingUid) {// don't abort if the realCallingUid has a visible window
            if (realCallingUidHasAnyVisibleWindow) {
                return false;
            }
            // if the realCallingUid is a persistent system process, abort if the IntentSender
            // wasn't whitelisted to start an activity
            if (isRealCallingUidPersistentSystemProcess && allowBackgroundActivityStart) {
                return false;
            }
            // don't abort if the realCallingUid is an associated companion app
            if (mService.isAssociatedCompanionApp(UserHandle.getUserId(realCallingUid),
                    realCallingUid)) {
                return false; }}// don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
        if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
                == PERMISSION_GRANTED) {
            return false;
        }
        // don't abort if the caller has the same uid as the recents component
        if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) {
            return false;
        }
        // don't abort if the callingUid is the device owner
        if (mService.isDeviceOwner(callingUid)) {
            return false;
        }
        // don't abort if the callingUid has companion device
        final int callingUserId = UserHandle.getUserId(callingUid);
        if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
            return false;
        }
        // If we don't have callerApp at this point, no caller was provided to startActivity().
        // That's the case for PendingIntent-based starts, since the creator's process might not be
        // up and alive. If that's the case, we retrieve the WindowProcessController for the send()
        // caller, so that we can make the decision based on its foreground/whitelisted state.
        int callerAppUid = callingUid;
        if (callerApp == null) {
            callerApp = mService.getProcessController(realCallingPid, realCallingUid);
            callerAppUid = realCallingUid;
        }
        // don't abort if the callerApp or other processes of that uid are whitelisted in any way
        if(callerApp ! =null) {
            // first check the original calling process
            if (callerApp.areBackgroundActivityStartsAllowed()) {
                return false;
            }
            // only if that one wasn't whitelisted, check the other ones
            final ArraySet<WindowProcessController> uidProcesses =
                    mService.mProcessMap.getProcesses(callerAppUid);
            if(uidProcesses ! =null) {
                for (int i = uidProcesses.size() - 1; i >= 0; i--) {
                    final WindowProcessController proc = uidProcesses.valueAt(i);
                    if(proc ! = callerApp && proc.areBackgroundActivityStartsAllowed()) {return false; }}}}// don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
        if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
            Slog.w(TAG, "Background activity start for " + callingPackage
                    + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
            return false;
        }
        // anything that has fallen through would currently be aborted
        Slog.w(TAG, "Background activity start [callingPackage: " + callingPackage
                + "; callingUid: " + callingUid
                + "; isCallingUidForeground: " + isCallingUidForeground
                + "; isCallingUidPersistentSystemProcess: " + isCallingUidPersistentSystemProcess
                + "; realCallingUid: " + realCallingUid
                + "; isRealCallingUidForeground: " + isRealCallingUidForeground
                + "; isRealCallingUidPersistentSystemProcess: "
                + isRealCallingUidPersistentSystemProcess
                + "; originatingPendingIntent: " + originatingPendingIntent
                + "; isBgStartWhitelisted: " + allowBackgroundActivityStart
                + "; intent: " + intent
                + "; callerApp: " + callerApp
                + "]");
        // log aborted activity start to TRON
        if(mService.isActivityStartsLoggingEnabled()) { mSupervisor.getActivityMetricsLogger().logAbortedBgActivityStart(intent, callerApp, callingUid, callingPackage, callingUidProcState, callingUidHasAnyVisibleWindow, realCallingUid, realCallingUidProcState, realCallingUidHasAnyVisibleWindow, (originatingPendingIntent ! =null));
        }
        return true;
    }
Copy the code

The criteria in the above method are all that allow the background to start the Activity. Three of the above conditions are obvious.

// if the realCallingUid is a persistent system process, abort if the IntentSender
// wasn't whitelisted to start an activity
if (isRealCallingUidPersistentSystemProcess && allowBackgroundActivityStart) {
    return false;
}
// don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission
if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) {
    Slog.w(TAG, "Background activity start for " + callingPackage
        + " allowed because SYSTEM_ALERT_WINDOW permission is granted.");
    return false;
}
// don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission
if (mService.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
	== PERMISSION_GRANTED) {
	return false;
}
Copy the code

One is explicitly set in front of the white list properties allowBackgroundActivityStart, another is the application of SYSTEM_ALERT_WINDOW permissions, one is the background to start the Activity.

At this point, the research is basically over, and the only thing left is how to bypass, but I haven’t figured out a way to bypass at present. In addition, in ActivityManagerService. In Java also found a permissions:

    @GuardedBy("this")
    final int broadcastIntentLocked(ProcessRecord callerApp,
            String callerPackage, Intent intent, String resolvedType,
            IIntentReceiver resultTo, int resultCode, String resultData,
            Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
            boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
            int realCallingPid, int userId, boolean allowBackgroundActivityStarts) {
        intent = newIntent(intent); .if(bOptions ! =null) {
            if (brOptions.allowsBackgroundActivityStarts()) {
                // See if the caller is allowed to do this. Note we are checking against
                // the actual real caller (not whoever provided the operation as say a
                // PendingIntent), because that who is actually supplied the arguments.
                if (checkComponentPermission(
                        android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
                        realCallingPid, realCallingUid, -1.true)
                        != PackageManager.PERMISSION_GRANTED) {
                    String msg = "Permission Denial: " + intent.getAction()
                            + " broadcast from " + callerPackage + " (pid=" + callingPid
                            + ", uid=" + callingUid + ")"
                            + " requires "
                            + android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
                    Slog.w(TAG, msg);
                    throw new SecurityException(msg);
                } else {
                    allowBackgroundActivityStarts = true; }}}...return ActivityManager.BROADCAST_SUCCESS;
    }
Copy the code

Although the existence of this permission has been seen, the specific impact of this permission has not yet been determined.

Above is the result of this preliminary study, although see the source code, but in a state of no way to start.

If you have any ideas or plans, please comment.