Android 8.0权限管理源码分析

权限的目的是保护用户的隐私。应用访问敏感数据,例如通讯录和SMS,还有系统特性,如摄像头,都需要申请权限;根据权限的类型,系统会自动赋予,或者让用户决定是否给予权限;

权限等级可以分为四个等级:
protectionLevel
(1)Normal
权限被声明为Normal级别,任何应用都可以申请,在安装应用时,不会直接提示给用户,点击全部才会展示。
(2)Dangerous
权限被声明为Dangerous级别,任何应用都可以申请,在安装应用时,会直接提示给用户。
(3)Signature
权限被声明为Signature级别,只有和该apk(定义了这个权限的apk)用相同的私钥签名的应用才可以申请该权限。
frameworks/base/core/res/AndroidManifest.xml声明的权限为Signature级别,那么只有Android官方使用相同私钥签名的应用才可以申请该权限。
(4)SignatureOrSystem
权限被声明为SignatureOrSystem级别,有两种应用可以申请该权限。
1)和该apk(定义了这个权限的apk)用相同的私钥签名的应用
2)在/system/app目录下的应用

对于APP targetSdkVersion >= 23, 在应用安装的时候不会显示应用需要的权限; 应需要在使用时动态申请,并且用户可以选择拒绝授权访问这些权限,已授予过的权限,用户也可以去APP设置页面去关闭授权;
杜宇targetSdkVersion <23 , 会在安装时候显示应用需要的所有权限;如果用户允许,就会授予应用所有权限,如果不允许,就会停止安装;
AppOpsManager Target < 23; 由AppOpsService处理,持久化到appops.xml;
Runtime-permission Target >= 23;由PackageManagerService 处理;持久化到runtime-permission.xml

另外权限不仅是系统特性, 还可以用于限制调用者;如Activtiy 中如果有android:permission,那么只有这个caller拥有这个权限,才可以启动这个Activity;
这个权限会在,startActivity(),startActivityForResult的时候检测,如果没有会抛出SecurityException;

普通权限

image.png

权限组

系统根据权限用途又定义了权限组,每个权限都可属于一个权限组,每个权限组可以包含多个权限。例如联系人权限组,包含读取联系人、修改联系人和获取账户三个权限。

  • 如果应用申请访问一个危险权限,而此应用目前没有对应的权限组内的任何权限,系统会弹窗提示用户要访问的权限组(注意不是权限)。例如无论你申请READ_CONTACTS还是WRITE_CONTACTS,都是提示应用需要访问联系人信息。
  • 如果用户申请访问一个危险权限,而应用已经授权同权限组的其他权限,则系统会直接授权,不会再与用户有交互。例如应用已经请求并授予了READ_CONTACTS权限,那么当应用申请WRITE_CONTACTS时,系统会立即授予该权限。下面为危险权限和权限组:
image.png

ADB工具

配合runtime permission, 也新增了一些相关的adb 命令:

  1. 查看所有的dangerous permissions:
              adb shell pm list permissions –g –d
    
  2. 安装app并且对所有列在app manifest文件下的所有permission给予授权:
              adb install -g <path_to_apk>
    
  3. 授权给某个app某个permission:
             adb pm grant <package_name> <permission_name>
    
  4. 撤销授权:
              adb pm revoke <package_name> <permission_name>
    

Setting APP中对应用权限管理:

菜单:
应用和通知:AppAndNotificationDashboardFragment.java;
应用信息:ManageApplications.java,列出所有APP;
具体应用详细界面:InstalledAppDetails.java;
点击"权限",进入


image.png

Packageinstaller APP 权限修改界面

详细列出应用需要的权限
ManagePermissionsActivity.java
AppPermissionsFragment.java:
管理应用权限类:
AppPermissions.java:
构造函数:


image.png

通过packageInof 获得应用所有权限,在封装成permissiongroup;

image.png

在AppPremissionsFragment中显示Group


image.png

AppPermissionGroup.java
针对targetSdkVersion是否高于23做了不同处理,targetSdkVersion<23的所有的权限都在packages.xml中,grante一直是true,无法被跟新;如果targetSdkVersion>=23支持动态权限管理,那就更新动态权限,并将其持久化到runtime-permission.xml中,并更新其granted值,如果targetSdkVersion<23 ,也不是动态管理,那就只更新AppOps,这是4.3引入的老的动态权限管理模型,不过这里主要是将权限持久化到appops.xml中,更新权限后30分钟才会持久化到appops.xml中,不过对于其granted的值是没有做任何更新的,仅仅是更新了packages.xml中的flag,这个flag可以配合appops.xml标识是否被授权(对于targetSdkVersion<23的适用。

禁止权限

revokeRuntimePermissionsrevoke(false)
----------mPackageManager.revokeRuntimePermission更新权限
----------mPackageManager.updatePermissionFlag更新permissionflag


image.png

对于targetSdkVersion < 23的应用


image.png

允许权限

grantRuntimePermissions(false)
----------mPackageManager.grantRuntimePermission
1 判断是否可以动态权限管理,targetSdkVersion> = 23
2 更新AppOps服务
3 调用packageManager更新其runtime-permission.xml 中granted值
4 更新permissionflag

image.png

image.png

如果不是动态权限,
1 更新AppOps 服务
2 更新permissionflag


image.png

image.png

PackageManagerService-- grantRuntimePermission

1 PackageManagerService中通过mPackage得到目标pkg ;
2 通过mSettings.mPermissions获取到目标权限bp ;
3 通过pkg得到一个PermissionsState对象permissionsState;
4 使用permissionsState对象来对pkg的bp进行赋予权限的操作;
1 通过ensurePermissionData方法获取一个PermissionData对象pd;
2 使用pd来赋予权限
5 持久化到runtime-permission.xml

    private void grantRuntimePermission(String packageName, String name, final int userId,
            boolean overridePolicy) {
        if (!sUserManager.exists(userId)) {
            Log.e(TAG, "No such user:" + userId);
            return;
        }
        final int callingUid = Binder.getCallingUid();

        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
                "grantRuntimePermission");

        enforceCrossUserPermission(callingUid, userId,
                true /* requireFullPermission */, true /* checkShell */,
                "grantRuntimePermission");

        final int uid;
        final PackageSetting ps;

        synchronized (mPackages) {
//1 通过mPackage得到目标pkg
            final PackageParser.Package pkg = mPackages.get(packageName);
            if (pkg == null) {
                throw new IllegalArgumentException("Unknown package: " + packageName);
            }
// 2通过mSettings.mPermissions获取到目标权限bp ;
            final BasePermission bp = mSettings.mPermissions.get(name);
            if (bp == null) {
                throw new IllegalArgumentException("Unknown permission: " + name);
            }
            ps = (PackageSetting) pkg.mExtras;
            if (ps == null
                    || filterAppAccessLPr(ps, callingUid, userId)) {
                throw new IllegalArgumentException("Unknown package: " + packageName);
            }
            //app将该permission 注册到AndroidManifest中
            //并且该permission 是Runtime 或者是development permission
            enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);

            // If a permission review is required for legacy apps we represent
            // their permissions as always granted runtime ones since we need
            // to keep the review required permission flag per user while an
            // install permission's state is shared across all users.
            if (mPermissionReviewRequired
                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
                    && bp.isRuntime()) {
                return;
            }

            uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
//3 通过pkg得到一个PermissionsState对象permissionsState;

            final PermissionsState permissionsState = ps.getPermissionsState();

            final int flags = permissionsState.getPermissionFlags(name, userId);
            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
                throw new SecurityException("Cannot grant system fixed permission "
                        + name + " for package " + packageName);
            }
            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
                throw new SecurityException("Cannot grant policy fixed permission "
                        + name + " for package " + packageName);
            }

            if (bp.isDevelopment()) {
                // Development permissions must be handled specially, since they are not
                // normal runtime permissions.  For now they apply to all users.
                if (permissionsState.grantInstallPermission(bp) !=
                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
                    scheduleWriteSettingsLocked();
                }
                return;
            }

            if (ps.getInstantApp(userId) && !bp.isInstant()) {
                throw new SecurityException("Cannot grant non-ephemeral permission"
                        + name + " for package " + packageName);
            }

            if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
                return;
            }
//4 使用permissionsState对象来对pkg的bp进行赋予权限的操作;

            final int result = permissionsState.grantRuntimePermission(bp, userId);
            switch (result) {
                case PermissionsState.PERMISSION_OPERATION_FAILURE: {
                    return;
                }

                case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
                    final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
                        }
                    });
                }
                break;
            }

            if (bp.isRuntime()) {
                logPermissionGranted(mContext, name, packageName);
            }

            mOnPermissionChangeListeners.onPermissionsChanged(uid);
//5 持久化到runtime-permission.xml

            // Not critical if that is lost - app has to request again.
            mSettings.writeRuntimePermissionsForUserLPr(userId, false);
        }

        // Only need to do this if user is initialized. Otherwise it's a new user
        // and there are no processes running as the user yet and there's no need
        // to make an expensive call to remount processes for the changed permissions.
        if (READ_EXTERNAL_STORAGE.equals(name)
                || WRITE_EXTERNAL_STORAGE.equals(name)) {
            final long token = Binder.clearCallingIdentity();
            try {
                if (sUserManager.isInitialized(userId)) {
                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
                            StorageManagerInternal.class);
                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
                }
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }

权限检测 checkUidPermission

1 通过UID 获得应用的SettingBase;
2 通过SettingBase 获得PermissionsState
3 更具permissionsState 判断是否具有权限

    public int checkUidPermission(String permName, int uid) {
        final int callingUid = Binder.getCallingUid();
        final int callingUserId = UserHandle.getUserId(caSllingUid);
        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;
        }
        //permName.equals();
        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;
    }

录音权限判断

ServiceUtilities.cpp

1 packagemanager--- checkPermission
2 AppOpsManager--- noteOp

bool recordingAllowed(const String16& opPackageName, pid_t pid, uid_t uid) {
    // we're always OK.
    if (getpid_cached == IPCThreadState::self()->getCallingPid()) return true;

    static const String16 sRecordAudio("android.permission.RECORD_AUDIO");

    // We specify a pid and uid here as mediaserver (aka MediaRecorder or StageFrightRecorder)
    // may open a record track on behalf of a client.  Note that pid may be a tid.
    // IMPORTANT: Don't use PermissionCache - a runtime permission and may change.
    const bool ok = checkPermission(sRecordAudio, pid, uid);
    if (!ok) {
        ALOGE("Request requires android.permission.RECORD_AUDIO");
        return false;
    }

    // To permit command-line native tests
    if (uid == AID_ROOT) return true;

    String16 checkedOpPackageName = opPackageName;

    // In some cases the calling code has no access to the package it runs under.
    // For example, code using the wilhelm framework's OpenSL-ES APIs. In this
    // case we will get the packages for the calling UID and pick the first one
    // for attributing the app op. This will work correctly for runtime permissions
    // as for legacy apps we will toggle the app op for all packages in the UID.
    // The caveat is that the operation may be attributed to the wrong package and
    // stats based on app ops may be slightly off.
    if (checkedOpPackageName.size() <= 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder = sm->getService(String16("permission"));
        if (binder == 0) {
            ALOGE("Cannot get permission service");
            return false;
        }

        sp<IPermissionController> permCtrl = interface_cast<IPermissionController>(binder);
        Vector<String16> packages;

        permCtrl->getPackagesForUid(uid, packages);

        if (packages.isEmpty()) {
            ALOGE("No packages for calling UID");
            return false;
        }
        checkedOpPackageName = packages[0];
    }

    AppOpsManager appOps;
    if (appOps.noteOp(AppOpsManager::OP_RECORD_AUDIO, uid, checkedOpPackageName)
            != AppOpsManager::MODE_ALLOWED) {
        ALOGE("Request denied by app op OP_RECORD_AUDIO");
        return false;
    }

    return true;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容