天天看點

LOCAL_PRIVILEGED_MODULE 詳解(3)

2.2 ApplicationInfo中的 privateFlags 和 PRIVATE_FLAG_PRIVILEGED

看一下ApplicationInfo源代碼

/**
     * Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
     * {@hide}
     */
    public int privateFlags;
           
/**
     * Value for {@link #privateFlags}: set to {@code true} if the application
     * is permitted to hold privileged permissions.
     *
     * {@hide}
     */
    public static final int PRIVATE_FLAG_PRIVILEGED = <<;
           

首先看到@hide,可見不會出現在Android公開API中,也就是說,第三方App無緣使用。先來看看這個字段在Android系統中是如何被使用的。随便找例子:

在系統應用安裝器(PackageInstaller)中,packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

private boolean isInstallRequestFromUnknownSource(Intent intent) {
        String callerPackage = getCallingPackage();
        if (callerPackage != null && intent.getBooleanExtra(
                Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
            try {
                mSourceInfo = mPm.getApplicationInfo(callerPackage, );
                if (mSourceInfo != null) {
                    if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
                            != ) {
                        // Privileged apps are not considered an unknown source.
                        return false;
                    }
                }
            } catch (NameNotFoundException e) {
            }
        }

        return true;
    }
           

frameworks/base/services/core/java/com/android/server/firewall/SenderFilter.java

static boolean isPrivilegedApp(int callerUid, int callerPid) {
        if (callerUid == Process.SYSTEM_UID || callerUid ==  ||
                callerPid == Process.myPid() || callerPid == ) {
            return true;
        }

        IPackageManager pm = AppGlobals.getPackageManager();
        try {
            return (pm.getPrivateFlagsForUid(callerUid) & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
                    != ;
        } catch (RemoteException ex) {
            Slog.e(IntentFirewall.TAG, "Remote exception while retrieving uid flags",
                    ex);
        }

        return false;
    }
           

由上面這些代碼執行個體基本可以了解這個字段的用法。下面在PMS掃描/安裝應用的邏輯中找到處理這個字段的部分來分析。

我們知道,PMS是PackageManager的背景服務,先看看PMS側是如何實作PackageManager.getApplicationInfo()。

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java:

@Override
    public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
        if (!sUserManager.exists(userId)) return null;
        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get application info");
        // writer
        synchronized (mPackages) {
            PackageParser.Package p = mPackages.get(packageName);
            if (DEBUG_PACKAGE_INFO) Log.v(
                    TAG, "getApplicationInfo " + packageName
                    + ": " + p);
            if (p != null) {
                PackageSetting ps = mSettings.mPackages.get(packageName);
                if (ps == null) return null;
                // Note: isEnabledLP() does not apply here - always return info
                return PackageParser.generateApplicationInfo(
                        p, flags, ps.readUserState(userId), userId);
            }
            if ("android".equals(packageName)||"system".equals(packageName)) {
                return mAndroidApplication;
            }
            if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != ) {
                return generateApplicationInfoFromSettingsLPw(packageName, flags, userId);
            }
        }
        return null;
    }
           

從這段代碼中看到,擷取到一個ApplicationInfo需要三步:

第一步:從PMS的緩存mPackages中擷取對應包名的PackageParser.Package對象p。

第二步:從PMS的緩存mSettings.mPackages中擷取對應包名的PackageSetting對象ps。

第三步:以上兩步的緩存對象為參數,使用PackageParser.generateApplicationInfo()生成一個ApplicationInfo并傳回。

return PackageParser.generateApplicationInfo(
                        p, flags, ps.readUserState(userId), userId);
           

首先看緩存mPackages是怎麼來的。這是mPackages的定義。

// Keys are String (package name), values are Package.  This also serves
    // as the lock for the global state.  Methods that must be called with
    // this lock held have the prefix "LP".
    @GuardedBy("mPackages")
    final ArrayMap<String, PackageParser.Package> mPackages =
            new ArrayMap<String, PackageParser.Package>();
           

隻有一處會put value:

// Add the new setting to mPackages
            mPackages.put(pkg.applicationInfo.packageName, pkg);
           

這段代碼出現在掃描方法 scanPackageDirtyLI(),這個方法非常長,取一段相關代碼:

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
        final File scanFile = new File(pkg.codePath);
        if (pkg.applicationInfo.getCodePath() == null ||
                pkg.applicationInfo.getResourcePath() == null) {
            // Bail out. The resource and code paths haven't been set.
            throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                    "Code and resource paths haven't been set correctly");
        }

        if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != ) {
            pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
        } else {
            // Only allow system apps to be flagged as core apps.
            pkg.coreApp = false;
        }

        if ((parseFlags&PackageParser.PARSE_IS_PRIVILEGED) != ) {
            pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
        }
        ......
                // writer
        synchronized (mPackages) {
            // We don't expect installation to fail beyond this point

            // Add the new setting to mSettings
            mSettings.insertPackageSettingLPw(pkgSetting, pkg);
            // Add the new setting to mPackages
            mPackages.put(pkg.applicationInfo.packageName, pkg);
            ......
        }
  ......
  }
           

再來看看第三步中,生成ApplicationInfo的邏輯。

PackageParser.generateApplicationInfo():

public static ApplicationInfo generateApplicationInfo(Package p, int flags,
            PackageUserState state, int userId) {
        if (p == null) return null;
        if (!checkUseInstalledOrHidden(flags, state)) {
            return null;
        }
        if (!copyNeeded(flags, p, state, null, userId)
                && ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) == 
                        || state.enabled != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
            // In this case it is safe to directly modify the internal ApplicationInfo state:
            // - CompatibilityMode is global state, so will be the same for every call.
            // - We only come in to here if the app should reported as installed; this is the
            // default state, and we will do a copy otherwise.
            // - The enable state will always be reported the same for the application across
            // calls; the only exception is for the UNTIL_USED mode, and in that case we will
            // be doing a copy.
            updateApplicationInfo(p.applicationInfo, flags, state);
            return p.applicationInfo;
        }

        // Make shallow copy so we can store the metadata/libraries safely
        ApplicationInfo ai = new ApplicationInfo(p.applicationInfo);
        ai.uid = UserHandle.getUid(userId, ai.uid);
        ai.dataDir = Environment.getDataUserPackageDirectory(ai.volumeUuid, userId, ai.packageName)
                .getAbsolutePath();
        if ((flags & PackageManager.GET_META_DATA) != ) {
            ai.metaData = p.mAppMetaData;
        }
        if ((flags & PackageManager.GET_SHARED_LIBRARY_FILES) != ) {
            ai.sharedLibraryFiles = p.usesLibraryFiles;
        }
        if (state.stopped) {
            ai.flags |= ApplicationInfo.FLAG_STOPPED;
        } else {
            ai.flags &= ~ApplicationInfo.FLAG_STOPPED;
        }
        updateApplicationInfo(ai, flags, state);
        return ai;
    }
           
private static void updateApplicationInfo(ApplicationInfo ai, int flags,
            PackageUserState state) {
        // CompatibilityMode is global state.
        if (!sCompatibilityModeEnabled) {
            ai.disableCompatibilityMode();
        }
        if (state.installed) {
            ai.flags |= ApplicationInfo.FLAG_INSTALLED;
        } else {
            ai.flags &= ~ApplicationInfo.FLAG_INSTALLED;
        }
        if (state.hidden) {
            ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN;
        } else {
            ai.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HIDDEN;
        }
        if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
            ai.enabled = true;
        } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
            ai.enabled = (flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != ;
        } else if (state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED
                || state.enabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
            ai.enabled = false;
        }
        ai.enabledSetting = state.enabled;
    }
           

這段代碼并不複雜,是根據generateApplicationInfo(Package p, int flags, PackageUserState state, int userId)傳入的第一個參數p的成員變量applicationInfo,通過構造方法ApplicationInfo(ApplicationInfo orig)建立一個對象(這個構造方法會逐個字段淺拷貝orig到新的對象),然後通過updateApplicationInfo(ApplicationInfo ai, int flags,PackageUserState state) 完成一些字段的更新計算。在整個上述過程中,privateFlags字段的<<3位沒有變化,也就是說,其表征是否private app的字段位就是第一步中得到的PackageParser.Package對象p對應的字段位。

基于以上分析,我們隻要找到掃描過程生成的緩存PackageParser.Package對象中對應的privateFlags的<<3位是如何指派即可。前文提到過,添加緩存PackageParser.Package對象mPackages.put()隻有在scan方法中被調用。省略無關的邏輯分支,細化上述調用關系如下:

(1)public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) ->

(2)private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) ->

(3)private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException ->

(4)private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException ->

(5)private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags, int scanFlags, long currentTime, UserHandle user) throws PackageManagerException ->

(6)mPackages.put()

依次在上述過程中分析相關代碼段:

(1)掃描system/priv-app下的apk檔案

// Collected privileged system packages.
            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
            scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, );
           

此外,掃描framework resource也會被這樣處理,一般而言,其對應ROM中 system/framework/framework-res.apk

File frameworkDir = new File(Environment.getRootDirectory(), "framework");
            ......
            // Find base frameworks (resource packages without code).
            scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
                    | PackageParser.PARSE_IS_SYSTEM_DIR
                    | PackageParser.PARSE_IS_PRIVILEGED,
                    scanFlags | SCAN_NO_DEX, );
           

傳給(2)的參數 int parseFlags,隻有上述兩種情況中,PackageParser.PARSE_IS_PRIVILEGED标志位會置為1,其餘為0。

(2)參數 int parseFlags,不修改PackageParser.PARSE_IS_PRIVILEGED标志位,作為同名參數傳給(3)。

(3)參數 int parseFlags,作為同名參數傳給(4)。其間有如下邏輯,如果發現通過諸如OTA更新新的package仍然在/system/priv-app目錄下,則參數int parseFlags中PackageParser.PARSE_IS_PRIVILEGED标志位為置為1。此處暫時存疑,并沒有發現parseFlags重置PackageParser.PARSE_IS_PRIVILEGED标志位為置為0的邏輯,也就是說如果OTA更新的package不在/system/priv-app目錄下了,是如何實作的修改?不影響主流程分析。

if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != ) {
            // If new package is not located in "/system/priv-app" (e.g. due to an OTA),
            // it needs to drop FLAG_PRIVILEGED.
            if (locationIsPrivileged(scanFile)) {
                updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
            } else {
                updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
            }
            ......
        }
           
if (updatedPkg != null) {
            // An updated system app will not have the PARSE_IS_SYSTEM flag set
            // initially
            parseFlags |= PackageParser.PARSE_IS_SYSTEM;

            // An updated privileged app will not have the PARSE_IS_PRIVILEGED
            // flag set initially
            if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != ) {
                parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
            }
        }
           

(4)參數int parseFlags作為同名參數透傳給(5)。

(5)根據傳入的參數int parseFlags,對傳入的參數PackageParser.Package pkg相關的字段applicationInfo.privateFlags設定标志位<<3

if ((parseFlags&PackageParser.PARSE_IS_PRIVILEGED) != ) {
            pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
        }
           

将pkg放入緩存

// writer
        synchronized (mPackages) {
            // We don't expect installation to fail beyond this point

            // Add the new setting to mSettings
            mSettings.insertPackageSettingLPw(pkgSetting, pkg);
            // Add the new setting to mPackages
            mPackages.put(pkg.applicationInfo.packageName, pkg);
            // Make sure we don't accidentally delete its data.
            ......
        }
           

總結:

(1)使用LOCAL_PRIVILEGED_MODULE設定為true編譯的app,即ROM中的system/priv-app/下的app,通過PackageManager拿到的ApplicationInfo,其privateFlags字段<<3标志位為1,即ApplicationInfo.PRIVATE_FLAG_PRIVILEGED。也就是說,LOCAL_PRIVILEGED_MODULE為true編譯的app即所謂privileged app(特權app)。

(2)除此之外,ROM中的system/framework/framework-res.apk,也具有上述特征。

LOCAL_PRIVILEGED_MODULE 詳解(1)

LOCAL_PRIVILEGED_MODULE 詳解(2)

LOCAL_PRIVILEGED_MODULE 詳解(3)

LOCAL_PRIVILEGED_MODULE 詳解(4)

LOCAL_PRIVILEGED_MODULE 詳解(5)