Android Runtime permission研究分析

前言:

帶著問(wèn)題研究問(wèn)題,如下分析就是按照這種思路進(jìn)行的分析。

背景:

運(yùn)行時(shí)權(quán)限是Android 6.0 變更中尤為突出的一點(diǎn)。6以下的手機(jī)在安裝時(shí)候提示權(quán)限列表,用戶全部同意后才能安裝程序;6以后的手機(jī)直接安裝,在運(yùn)行過(guò)程中動(dòng)態(tài)的向用戶申請(qǐng)所需權(quán)限,另外用戶可以在設(shè)置界面對(duì)程序的各個(gè)權(quán)限進(jìn)行管理。

Android 中的權(quán)限可以分為三類:

普通權(quán)限(Normal Permissions)

危險(xiǎn)權(quán)限(Dangerous Permissions)

特殊權(quán)限(Special Permissions)

普通權(quán)限不涉及用戶隱私,在AndroidManifest.xml中聲明即獲??;危險(xiǎn)權(quán)限涉及用戶的隱私,在用戶授權(quán)之后方能使用。另外,Google 對(duì)危險(xiǎn)權(quán)限進(jìn)行了分組,當(dāng)某一個(gè)組中的某一個(gè)權(quán)限被授權(quán)之后應(yīng)用同時(shí)就獲取到了整個(gè)組的所有權(quán)限。而且,調(diào)用 API 申請(qǐng)權(quán)限時(shí)系統(tǒng)將向用戶顯示一個(gè)標(biāo)準(zhǔn)對(duì)話框,該對(duì)話框上的提示權(quán)限說(shuō)明是針對(duì)整個(gè)組的說(shuō)明,應(yīng)用無(wú)法配置或更改此對(duì)話框。

問(wèn)題:

1.怎么默認(rèn)就給予了apk相應(yīng)的權(quán)限?

案例一:怎么默認(rèn)就給予了apk相應(yīng)的權(quán)限?

條件:

Android 8.1系統(tǒng)源碼分析

過(guò)程:

我們了解到PackageManagerService(簡(jiǎn)稱:PMS)管理著所有跟package相關(guān)的工作,例如安裝、卸載應(yīng)用等,所以,應(yīng)該大概從PMS入手。

我們系統(tǒng)開(kāi)機(jī)的過(guò)程,init進(jìn)程拉起Zygote進(jìn)程,Zygote進(jìn)程拉起了system_server進(jìn)程,而system_server初始化Binder通信服務(wù),這里包含PMS。

代碼:

framework/base/services/java/com/android/server/SystemServer.java

public static void main(String[] args) {

        new SystemServer().run();

}

private void run() {

······

startBootstrapServices();

/// M: For mtk systemserver

sMtkSystemServerIns.startMtkBootstrapServices();

startCoreServices();

/// M: for mtk other service.

sMtkSystemServerIns.startMtkCoreServices();

startOtherServices();

······

}

private void startBootstrapServices() {

······

traceBeginAndSlog("StartPackageManagerService");

mPackageManagerService = PackageManagerService.main(mSystemContext, installer,

                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);

mFirstBoot = mPackageManagerService.isFirstBoot();

······

}

private void startOtherServices() {

      ······

        traceBeginAndSlog("MakePackageManagerServiceReady");

        try {

            mPackageManagerService.systemReady();

        } catch (Throwable e) {

            reportWtf("making Package Manager Service ready", e);

        }

        traceEnd();

        ······

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

public void systemReady() {

······

    // If we upgraded grant all default permissions before kicking off.

    for (int userId : grantPermissionsUserIds) {

        mDefaultPermissionPolicy.grantDefaultPermissions(userId);

    }

······

}


frameworks/base/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java

public void grantDefaultPermissions(int userId) {

        **// 方案一:只要強(qiáng)制把FEATURE_EMBEDDED 打開(kāi),這樣默認(rèn)授權(quán)了所有apk的權(quán)限**

        if (mService.hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {

            grantAllRuntimePermissions(userId);

        } else {

            // 系統(tǒng)組件和priv-app下面的默認(rèn)授權(quán)

            grantPermissionsToSysComponentsAndPrivApps(userId);

            //系統(tǒng)特殊處理的apk

            grantDefaultSystemHandlerPermissions(userId);

            grantDefaultPermissionExceptions(userId);

        }

}

方案二:

第一部分:

// 1.isSysComponentOrPersistentPlatformSignedPrivAppLPr

//    a.priv-app b.apk為persist屬性 c.apk簽名一致

// 2.doesPackageSupportRuntimePermission

//    apk的版本大于LOLLIPOP_MR1

// 3.pkg.requestedPermissions.isEmpty()

//    沒(méi)有權(quán)限的apk

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

            }

        }

    }

    private boolean isSysComponentOrPersistentPlatformSignedPrivAppLPr(PackageParser.Package pkg) {

        if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {

            return true;

        }

        if (!pkg.isPrivilegedApp()) {//priv-app

            return false;

        }

        PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);

        if (sysPkg != null && sysPkg.pkg != null) {//特別說(shuō)明,app的AndroidManifest.xml中application可以設(shè)置persist屬性

            if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {

                return false;

            }

        } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {//persistent app

            return false;

        }

        return PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,

                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;//apk簽名一致

    } 

第二部分:

private void grantDefaultSystemHandlerPermissions(int userId) {

······

//可以參考方法內(nèi)部對(duì)各種apk的權(quán)限處理

//或者直接參考grantAllRuntimePermissions(userId);中對(duì)apk的權(quán)限處理

······

}

第三部分:

private void grantDefaultPermissionExceptions(int userId) {
    ······
    if (mGrantExceptions == null) {
        //讀取的是system/etc/default-permissions下面的xml文件
        //也就是說(shuō),我們只要把想要獲取動(dòng)態(tài)權(quán)限的apk,按照既定格式
        //放到此目錄下面,也是可以讓apk默認(rèn)給予權(quán)限的
        mGrantExceptions = readDefaultPermissionExceptionsLPw();
    }
    ······
    for (int i = 0; i < exceptionCount; i++) {
          String packageName = mGrantExceptions.keyAt(i);
          //從這里繼續(xù)研究跟蹤下去可知:這是system app條件
          PackageParser.Package pkg = getSystemPackageLPr(packageName);
          ······
    }
    ······
 }

詳細(xì)分析如下:
 private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
            readDefaultPermissionExceptionsLPw() {
       File[] files = getDefaultPermissionFiles();//獲取的目錄
       ·······
       try (
                InputStream str = new BufferedInputStream(new FileInputStream(file))
            ) {
                XmlPullParser parser = Xml.newPullParser();
                parser.setInput(str, null);
                parse(parser, grantExceptions);//既定格式
            } catch (XmlPullParserException | IOException e) {
                Slog.w(TAG, "Error reading default permissions file " + file, e);
            }
        ······
  }
  
private File[] getDefaultPermissionFiles() {
        ArrayList<File> ret = new ArrayList<File>();
        File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
        if (dir.isDirectory() && dir.canRead()) {
            Collections.addAll(ret, dir.listFiles());
        }
        dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
        if (dir.isDirectory() && dir.canRead()) {
            Collections.addAll(ret, dir.listFiles());
        }
        return ret.isEmpty() ? null : ret.toArray(new File[0]);
 }          
 
 private void parse(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
            outGrantExceptions) throws IOException, XmlPullParserException {
        //簡(jiǎn)單說(shuō)明格式
        //<exceptions>
        //<exception name="com.qiyi.video.speaker">
        //<item name="android.permission.READ_PHONE_STATE" fixed="true" />
        //<item name="android.permission.WRITE_EXTERNAL_STORAGE" fixed="true" />
        //</exception>
        //</exceptions> 
        //重點(diǎn)說(shuō)明:fixed的意思:如果為true代表其狀態(tài)跟誰(shuí)系統(tǒng)狀態(tài)而定,如果為false,代表強(qiáng)制默認(rèn)打開(kāi)
        //介紹處:PackageManager.FLAG_PERMISSION_SYSTEM_FIXED/PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
 }
 
 private PackageParser.Package getSystemPackageLPr(String packageName) {
        PackageParser.Package pkg = getPackageLPr(packageName);
        if (pkg != null && pkg.isSystemApp()) {//代表滿足isSystemApp的apk都是可以這么做,例如:system/app system/priv-app vendor/app vendor/priv-app
            return !isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg) ? pkg : null;
        }
        return null;
}

總結(jié):系統(tǒng)默認(rèn)了3種實(shí)現(xiàn)方式默認(rèn)給apk授權(quán)權(quán)限

補(bǔ)充說(shuō)明:
問(wèn)題點(diǎn):不管是系統(tǒng)默認(rèn)操作還是我們手動(dòng)操作,最后的動(dòng)態(tài)權(quán)限是否保存在文件中?

關(guān)鍵部分:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

public void updatePermissionFlags(String name, String packageName, int flagMask,
            int flagValues, int userId) {
 ······
mSettings.writeRuntimePermissionsForUserLPr(userId, false);
······
}

frameworks/base/services/core/java/com/android/server/pm/Settings.java

public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {
        if (sync) {
            mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);
        } else {
            mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
        }
}

public void writePermissionsForUserAsyncLPr(int userId) {
    ······
    if (mWriteScheduled.get(userId)) {
        ······
         Message message = mHandler.obtainMessage(userId);
        mHandler.sendMessageDelayed(message, writeDelayMillis);
      } else {
       mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis);
       Message message = mHandler.obtainMessage(userId);
       mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
       mWriteScheduled.put(userId, true);
      }
}

private final class MyHandler extends Handler {
            public MyHandler() {
                super(BackgroundThread.getHandler().getLooper());
            }

            @Override
            public void handleMessage(Message message) {
                final int userId = message.what;
                Runnable callback = (Runnable) message.obj;
                writePermissionsSync(userId);
                if (callback != null) {
                    callback.run();
                }
            }
}

private void writePermissionsSync(int userId) {
//寫入的是這個(gè)文件:/data/system/users/0/runtime-permissions.xml
}

案例二:檢查權(quán)限和默認(rèn)授予權(quán)限是怎么關(guān)聯(lián)起來(lái)的?

關(guān)鍵點(diǎn):
Context.checkSelfPermission
Activity.requestPermissions
Activity.onRequestPermissionsResult
Activity.shouldShowRequestPermissionRationale

從Activity.requestPermissions入手分析
Activity.java -->AMS-->PackageInstaller(GrantPermissionsActivity.java)-->PMS:updatePermissionFlags

關(guān)鍵代碼:
PackageManagerService.java
public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
   return getPackageInfoInternal(packageName, PackageManager.VERSION_CODE_HIGHEST,
                flags, Binder.getCallingUid(), userId);
}

 private PackageInfo getPackageInfoInternal(String packageName, int versionCode,
            int flags, int filterCallingUid, int userId) {
 //從這段方法可以知道,最終的數(shù)據(jù)來(lái)自mSettings
 //因?yàn)榛旧螾ackageSetting來(lái)自mSettings
 //所以,初始化mSettings很重要
 //這里涉及PMS構(gòu)造方法中的這句語(yǔ)句:mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));
 //把mSettings的各種權(quán)限狀態(tài)都初始化了,特別注意boolean readLPw(@NonNull List<UserInfo> users)中的
 //mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);這是runtime權(quán)限初始化的地方
 ·····
 }
 
 private PackageInfo generatePackageInfo(PackageSetting ps, int flags, int userId) {
       //重點(diǎn)在PermissionsState
       //這里的初始化的動(dòng)作都在PMS中
       ······
       final PermissionsState permissionsState = ps.getPermissionsState();

        // Compute GIDs only if requested
        final int[] gids = (flags & PackageManager.GET_GIDS) == 0
                ? EMPTY_INT_ARRAY : permissionsState.computeGids(userId);
        // Compute granted permissions only if package has requested permissions
        final Set<String> permissions = ArrayUtils.isEmpty(p.requestedPermissions)
                ? Collections.<String>emptySet() : permissionsState.getPermissions(userId);
         ······
         PackageInfo packageInfo = PackageParser.generatePackageInfo(p, gids, flags,
                ps.firstInstallTime, ps.lastUpdateTime, permissions, state, userId);
         ······
 }



從Context.checkSelfPermission入手分析

ContextImpl.java
public int checkSelfPermission(String permission) {
        if (permission == null) {
            throw new IllegalArgumentException("permission is null");
        }
        return checkPermission(permission, Process.myPid(), Process.myUid());
}

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

ActivityManagerService.java
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.java
public static int checkComponentPermission(String permission, int uid,
            int owningUid, boolean exported) {
    ·······
    return AppGlobals.getPackageManager()
                    .checkUidPermission(permission, uid);
}

PackageManagerService.java
public int checkUidPermission(String permName, int uid){
    //關(guān)鍵點(diǎn):mSettings、PermissionsState
    ``````
    Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
    ``````  
    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;
                    }
                }  
}
注:
Tips
a.權(quán)限涉及到的模塊AMS、PMS、PackageInstaller(安裝器)。動(dòng)態(tài)權(quán)限的對(duì)話框也是安裝器展示
b.PackageManager的實(shí)現(xiàn)類為ApplicationPackageManager

adb快速查看

查看系統(tǒng)所擁有的危險(xiǎn)權(quán)限

adb shell pm list permissions -d -g 查看危險(xiǎn)權(quán)限指令

查看某一個(gè)apk所擁有的權(quán)限

adb shell pm dump 包名 | findstr permission
例如:adb shell pm dump com.smart.launcher | findstr permission

參考學(xué)習(xí)

https://blog.csdn.net/sinat_20059415/article/details/80369872
https://blog.csdn.net/zchy198799/article/details/89341974
https://blog.csdn.net/u013553529/article/details/53167072
https://blog.csdn.net/csdnxialei/article/details/93628890

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom閱讀 3,184評(píng)論 0 3
  • 記錄一次序列化引起的問(wèn)題解決辦法查看已編譯類序列化值 本文主要內(nèi)容: 1:怎么查看已經(jīng)編譯的類的序列化(Seria...
    凱哥Java閱讀 316評(píng)論 0 1
  • Documentation/kbuild/kconfig-language.txt 主目錄下的Kconfig文件,...
    3e1094b2ef7b閱讀 641評(píng)論 0 0
  • 前幾天迷上了一部超級(jí)帶感的電視劇《路西法》,講述的是上帝之子撒旦因叛逆之心被貶入地獄工作后,由天使變成了魔鬼。永...
    肉丸配燒酒閱讀 1,621評(píng)論 0 0
  • 小小的城市變化紛繁 而你依舊矗立在路邊 我偶然走過(guò)你的身旁 多少往事在心底浮現(xiàn) 一段空想中的愛(ài)戀 寫滿了長(zhǎng)長(zhǎng)的信件...
    der青銅騎士閱讀 246評(píng)論 0 5

友情鏈接更多精彩內(nèi)容