关于“Android Q无法后台启动Activity”的初步调研结果

9,608 阅读6分钟

Android Q已经正式发布了,其中有一条隐私性调整,对于国内应用的影响很大。 那就是标题所说的 “禁止应用后台私自启动Activity”,确实,对于用户来说,不管哪个方面都是好事。

  • 可以避免当前页面操作被打断(打游戏时候的突然弹窗)
  • 流氓应用的弹出式广告
  • 流氓应用的“一像素保活”

就是这个功能,导致了应用保活更加困难,作为用户,我是支持禁止的。作为开发者,我也是支持禁止的。但是本着探究的目的,还是决定研究一下。

另外,由于我是第一次看这个源码,本身不太理解内部的结构,所以更多的是通过关键字搜索看一些有特征的方法或者代码,而对于方法或者属性没有比较深的理解,浮于表面,所以内容包含了大量的源码。阅读体验可能不佳。请见谅。

研究结论:

经过源码分析,基本无法直接绕过检查机制。但是可以考虑通过系统服务伪装或者间接调起。 具体间接绕过方式还没有研究结果。

研究过程:

在拒绝Activity后台启动时,会产生相应的Log,如:

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]

从log上看,Log是从ActivityTaskManager发出的,前往 AndroidXrefAndroid Pie 中查看(还没有Android Q的源码),发现没有搜索结果,应该是Android Q新增的。 只能前往Google Git寻找,由于没有搜索功能(我不太会用)只能 傻敷敷老实的挨个找,最终寻找到ActivityTaskManager.java,但是很可惜,只是一个包装类,内部实现是:

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

然后理所当然的就是放弃了,然后通过 Pie 的代码,来搜索,看看这个服务在哪里初始化的。 最终,找到了 SystemServer.java , 接着又在其中找到了具体实现类ActivityTaskManagerService.java的包路径。

import com.android.server.wm.ActivityTaskManagerService;

接着,循着这个包路径也就找到了文件的真实路径了:ActivityTaskManagerService.java

从中,通过background关键字搜索,找到了如下方法:

@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();
    }

上面的代码中,批量启动方法是直接显式设置allowBackgroundActivityStartfalse。而 startActivityAsUser却是没有设置,保持了缺省值。 getActivityStartController()的返回对象为:ActivityStartController.java。 它的 obtainStarter() 方法细节为:

    /**
     * @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);
    }

返回了一个 ActivityStarter.java 对象,而前面设置的参数,都保存在内部类 Request 中,其中的缺省值设置是:

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

默认情况下,是拒绝后台启动的。也就是说,用户启动的话,禁止在后台直接启动的。 具体的判断代码是在 shouldAbortBackgroundActivityStart 方法中,因为这个方法算是判断的核心方法了,因此贴了完整代码。

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;
    }

上面方法中的判断条件,就是允许后台启动 Activity 的全部条件了。 上面条件中,有3个是比较明显的。

// 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;
}

一个是前面显式设置的白名单属性allowBackgroundActivityStart, 另一个是应用的SYSTEM_ALERT_WINDOW权限,还有一个就是后台启动Activity的权限。

到了这里,基本上研究就算结束了,剩下的就是怎么去绕过,但是目前我没有想到办法绕过。 另外,在 ActivityManagerService.java 中也发现了一个权限:

    @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 = new Intent(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;
    }

虽然看到了这个权限的存在,但是目前还没有确定这个权限的具体影响。

以上就是本次初步研究的结果了,虽然看到了源码,但是却处于无从下手的状态。

如果看官有什么见解或者方案,欢迎评价。