Android 在Framework默認授予launcher runtime權限

Android版本: 8.1
問題描述: 要在launcher顯示通話記錄calllog 和未讀短信等內容,涉及運行時權限,導致必須申請運行時權限,這樣就會彈窗提示,客戶的需求當然是不許彈窗咯,第一次開機就要默認授權。


一,privapp-permissions-mediatek.xml無效了

記得以前做這種權限問題,都是直接在privapp-permissions權限白名單里加上對應權限,但是在當前項目不支持,找不到具體原因,猜測是簽名不同導致的,然而具體原因,在此只能打問號了。總之權限白名單失效了。

二,context.checkSelfPermission() 的流程

在前面白名單裏已經寫了,但是checkSelfPermission的時候還是返回了deny,於是我開始追尋checkSelfPermission的檢測過程了。

Context.java

    @PackageManager.PermissionResult
    public abstract int checkSelfPermission(@NonNull String permission);

checkSelfPermission是抽象方法,自然它的實際實現者就是ContextImpl了

ContextImpl.java

    @Override
    public int checkSelfPermission(String permission) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }

        return checkPermission(permission, Process.myPid(), Process.myUid());
    }
@Override
    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }
        final IActivityManager am = ActivityManager.getService();
        if (am == null) {
            // Well this is super awkward; we somehow don't have an active
            // ActivityManager instance. If we're testing a root or system
            // UID, then they totally have whatever permission this is.
            final int appId = UserHandle.getAppId(uid);
            if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
                Slog.w(TAG, "Missing ActivityManager; assuming " + uid + " holds " + permission);
                return PackageManager.PERMISSION_GRANTED;
            }
        }
        try {
            return am.checkPermission(permission, pid, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

這裏是通過IActivityManager接口來處理,當然實現IActivityManager接口的纔是實體類,ActivityManagerService extends IActivityManager.Stub

在ActivityManagerService 裏 checkPermission

    @Override
    public int checkPermission(String permission, int pid, int uid) {
        if (permission == null) {
            return PackageManager.PERMISSION_DENIED;
        }
        return checkComponentPermission(permission, pid, uid, -1, true);
    }
    int checkComponentPermission(String permission, int pid, int uid,
            int owningUid, boolean exported) {
        if (pid == MY_PID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        return ActivityManager.checkComponentPermission(permission, uid,
                owningUid, exported);
    }

接着進入了ActivityManager裏

/** @hide */
    public static int checkComponentPermission(String permission, int uid,
            int owningUid, boolean exported) {
        // Root, system server get to do everything.
        final int appId = UserHandle.getAppId(uid);
        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // Isolated processes don't get any permissions.
        if (UserHandle.isIsolated(uid)) {
            return PackageManager.PERMISSION_DENIED;
        }
        // If there is a uid that owns whatever is being accessed, it has
        // blanket access to it regardless of the permissions it requires.
        if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
            return PackageManager.PERMISSION_GRANTED;
        }
        // If the target is not exported, then nobody else can get to it.
        if (!exported) {
            /*
            RuntimeException here = new RuntimeException("here");
            here.fillInStackTrace();
            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
                    here);
            */
            return PackageManager.PERMISSION_DENIED;
        }
        if (permission == null) {
            return PackageManager.PERMISSION_GRANTED;
        }
        try {
            return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

前面都是判斷特殊情況,比如

  1. appId == Process.ROOT_UID 意思就是appId是屬於root 用戶就直接返回PERMISSION_GRANTED
  2. exported == false,意思就是說比如這個組件 contentprovider, 或者activity 設置了exported false, 就直接返回PERMISSION_DENIED

而其餘普通情況就是走進AppGlobals.getPackageManager() .checkUidPermission

這裏獲取到的是Packagemanager接口

    public static IPackageManager getPackageManager() {
        return ActivityThread.getPackageManager();
    }

實際對象是PackageManagerService

@Override
    public int checkUidPermission(String permName, int uid) {
        final int callingUid = Binder.getCallingUid();
        final int callingUserId = UserHandle.getUserId(callingUid);
        final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
        final boolean isUidInstantApp = getInstantAppPackageName(uid) != null;
        final int userId = UserHandle.getUserId(uid);
        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }
        /// M: CTA requirement - permission control  @{
        sCtaManager.reportPermRequestUsage(permName, uid);
        //@}
        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                if (obj instanceof SharedUserSetting) {
                    if (isCallerInstantApp) {
                        return PackageManager.PERMISSION_DENIED;
                    }
                } else if (obj instanceof PackageSetting) {
                    final PackageSetting ps = (PackageSetting) obj;
                    if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
                        return PackageManager.PERMISSION_DENIED;
                    }
                }
                final SettingBase settingBase = (SettingBase) obj;
                final PermissionsState permissionsState = settingBase.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    if (isUidInstantApp) {
                        BasePermission bp = mSettings.mPermissions.get(permName);
                        if (bp != null && bp.isInstant()) {
                            return PackageManager.PERMISSION_GRANTED;
                        }
                    } else {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }
            } else {
                ArraySet<String> perms = mSystemPermissions.get(uid);
                if (perms != null) {
                    if (perms.contains(permName)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                    if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
                            .contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
                        return PackageManager.PERMISSION_GRANTED;
                    }
                }
            }
        }

        return PackageManager.PERMISSION_DENIED;
    }

這個方法很重要,結果就是在這裏判斷的,
根據打印可知,mSettings.getUserIdLPr 返回對應於launcher id的object不是空,於是obj != null
而檢測結果顯示
permissionsState.hasPermission(permName, userId) == false
這就說明 ,在launcher的PermissionsState裏是不包含我們所要的運行權限的。

但是這個permissionsState是系統初始化的時候就對應好了的,也就是初始化的時候根本就沒有這個授予權限。

三,從系統初始化找爲什麼沒有自動授權

在系統初始化完成後PackageManagerService自然也要調用systemReady;

PackageManagerService–systemReady()

    @Override
    public void systemReady() {
		...
        // If we upgraded grant all default permissions before kicking off.
        for (int userId : grantPermissionsUserIds) {
            mDefaultPermissionPolicy.grantDefaultPermissions(userId);
        }
        ...
   }

這裏有個默認授權的地方。

    public void grantDefaultPermissions(int userId) {
        if (mService.hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
            grantAllRuntimePermissions(userId);
        } else {
            grantPermissionsToSysComponentsAndPrivApps(userId);
            grantDefaultSystemHandlerPermissions(userId);
            grantDefaultPermissionExceptions(userId);
        }
    }

這裏PackageManager.FEATURE_EMBEDDED是沒有支持的,接着走下面三個方法,重點在第一個方法裏。

grantPermissionsToSysComponentsAndPrivApps
看這方法名不就是授權給系統組件和特權app嗎? 但是爲什麼launcher明明是priv app裏的,還是沒有權限?

    private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
        Log.i(TAG, "Granting permissions to platform components for user " + userId);

        synchronized (mService.mPackages) {
            for (PackageParser.Package pkg : mService.mPackages.values()) {
                if (!isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg)
                        || !doesPackageSupportRuntimePermissions(pkg)
                        || pkg.requestedPermissions.isEmpty()) {
                    continue;
                }
                grantRuntimePermissionsForPackageLocked(userId, pkg);
            }
        }
    }

遍歷系統packages, 做了三個判斷,只有通過判斷才能走下去

  1. isSysComponentOrPersistentPlatformSignedPrivAppLPr
  2. doesPackageSupportRuntimePermissions
  3. pkg.requestedPermissions.isEmpty()

這裏第一個方法就是判斷該組件是不是系統組件或者是Persistent的系統簽名的app

private boolean isSysComponentOrPersistentPlatformSignedPrivAppLPr(PackageParser.Package pkg) {
        if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
            return true;
        }
        if (!pkg.isPrivilegedApp()) {
            return false;
        }
        PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
        if (sysPkg != null && sysPkg.pkg != null) {
            if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
                return false;
            }
        } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
            return false;
        }
        return PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,
                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
    }

對laucnher來說pkg.isPrivilegedApp()肯定是true ,但是這裏的ApplicationInfo.FLAG_PERSISTENT,launcher是沒有這個屬性的,然後後面就是系統簽名匹配,pkg.mSignatures,
也就是LOCAL_CERTIFICATE := platform ,launcher也是沒有設置的。所以這裏返回肯定是false。

然後第二項,檢查package 的targetSdkVersion是不是大於22,也就是android 6.0

    private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
        return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
    }

第三項,pkg.requestedPermissions.isEmpty()
權限列表是否爲空,

根據條件判斷,這裏必然三項檢查返回必須都是false才能走下去,也就是 必須是 系統簽名且FLAG_PERSISTENT的app 且 targetsdk>22 且 permissions 不爲空纔會往下走,否則continue跳到下一個package

只有滿足條件的app
才能走後面的grantRuntimePermissionsForPackageLocked

    private void grantRuntimePermissionsForPackageLocked(int userId, PackageParser.Package pkg) {
        Set<String> permissions = new ArraySet<>();
        for (String permission :  pkg.requestedPermissions) {
            BasePermission bp = mService.mSettings.mPermissions.get(permission);
            if (bp != null && bp.isRuntime()) {
                permissions.add(permission);
            }
        }
        if (!permissions.isEmpty()) {
            grantRuntimePermissionsLPw(pkg, permissions, true, userId);
        }
    }
    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
            boolean systemFixed, int userId) {
        grantRuntimePermissionsLPw(pkg, permissions, systemFixed, false, userId);
    }

接下來所做的就是授權了

    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
            boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
		......
		mService.grantRuntimePermission(pkg.packageName, permission, userId);
		......
		省略
private void grantRuntimePermission(String packageName, String name, final int userId,
            boolean overridePolicy) {
        ......
		final int result = permissionsState.grantRuntimePermission(bp, userId);
		......
}

也就是前面提到的permissionsState裏給與對應權限,

    public int grantRuntimePermission(BasePermission permission, int userId) {
        enforceValidUserId(userId);
        if (userId == UserHandle.USER_ALL) {
            return PERMISSION_OPERATION_FAILURE;
        }
        return grantPermission(permission, userId);
    }

流程就是這樣,後面不看了,對於launcher的問題,是出在前面的判斷裏,
打印知道,在launcher包做判斷檢查是否系統組件的時候返回了false,這就導致系統初始化的時候並不會自動授權launcher的運行權限。
而關鍵的地方

(pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0

PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,
                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH

這兩項必須滿足,所以要在 <application 標籤裏添加

android:persistent="true"

以及在makefile裏添加

LOCAL_CERTIFICATE := platform

只有這樣纔會滿足前面的條件。

重新編譯,launcher 再檢查權限,就是返回已經授權通話相關權限了。解決了默認runtime授權。

int result = mcontext.checkSelfPermission("android.permission.READ_PHONE_STATE")
result = PackageManager.PERMISSION_GRANTED

其實對於開發來說,還有更粗暴的方式,就是直接在前面判斷launcher包名返回true,不過這樣實在太粗暴了

if (pkg.packageName.contains("launcher3")) {
            return true;
}

總結:

由於運行權限的問題,導致好幾個地方不能正確獲取權限,而又不允許彈窗申請,所以必須默認授權,這就一定要走源碼,通過走一下權限檢查流程,加深了我對權限這一塊的理解。以後再有這種問題,就直接上了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章