關於「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;
    }
複製代碼

雖然看到了這個權限的存在,可是目前尚未肯定這個權限的具體影響。

以上就是本次初步研究的結果了,雖然看到了源碼,可是卻處於無從下手的狀態。

若是看官有什麼看法或者方案,歡迎評價。

相關文章
相關標籤/搜索