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

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

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

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

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

研究结论:

通过源码分析,基本没法直接绕过检查机制。可是能够考虑经过系统服务假装或者间接调起。 具体间接绕过方式尚未研究结果。app

研究过程:

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

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的包路径。ui

import com.android.server.wm.ActivityTaskManagerService;
复制代码

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

从中,经过background关键字搜索,找到了以下方法:google

@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() 方法细节为:spa

/** * @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;
    }
复制代码

虽然看到了这个权限的存在,可是目前尚未肯定这个权限的具体影响。

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

若是看官有什么看法或者方案,欢迎评价。

相关文章
相关标签/搜索