Android M權限管理

Android的權限管理越來越完善,但是牽涉的內容也是更多了:從4.4的AppOps到6.0的Runtime Permission,Google還是爲之做了不少努力。

AppOps簡介:

Android 4.4加入的權限管理:用戶在安裝應用時,會彈窗列舉申請的權限,用戶授權才能正常安裝,因此只要安裝了的應用就會獲取所有權限。部分三方OEM廠商會將安裝授予的權限改爲詢問,提高安全性。因爲是安裝授予的權限,我們暫且把AppOps管理的權限稱爲“安裝權限”
雖然AppOps在4.4沒有對外開放,但仍是後續版本權限管理的基礎。

Runtime Permission簡介:

Android 6.0啓用的權限管理:在應用運行時,需要動態的申請權限,而不僅僅是在Manifest中申明就行了。依據其特性,我們稱之爲“運行時權限”。6.0中對於sdk >= 23的應用,安裝權限都是默認賦予的。

運行時權限與安裝權限是相輔相成的,只有同時具備兩種權限,應用才能正常使用對應功能。需要指出的是:運行時權限僅針對api >= 23的應用,對於api < 23的應用,仍沿用AppOps的規則。

權限的檢查:

在Activity中檢查應用是否有權限,ContextWrapper.java:

@Override
public int checkSelfPermission(String permission) {
   return mBase.checkSelfPermission(permission);
}

其具體實現在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");
        }

        try {
            return ActivityManagerNative.getDefault().checkPermission(
                    permission, pid, uid);
        } catch (RemoteException e) {
            return PackageManager.PERMISSION_DENIED;
        }
    }

走入了ActivityManagerNative.java中,ActivityManagerNative.getDefault()獲取到的是一個IActivityManager對象,這實際上是ActivityManagerService的一個遠程代理,具體實現在ActivityManagerService.java:

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

如果是MY_PID,直接賦予權限,否則調用ActivityManager.java的checkComponentPermission方法:

 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) {
            // Should never happen, but if it does... deny!
            Slog.e(TAG, "PackageManager is dead?!?", e);
        }
        return PackageManager.PERMISSION_DENIED;
    }

ROOT_UID和SYSTEM_UID也是直接賦予權限的。在上面代碼中owningUid是設置爲-1的,因此最終會走到AppGlobals.getPackageManager()
.checkUidPermission(permission, uid),通過PackageManager來查詢權限,其具體實現在PackageManagerService.java

public int checkUidPermission(String permName, int uid) {
        final int userId = UserHandle.getUserId(uid);

        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }

        synchronized (mPackages) {
        //依據uid查詢,獲取一個應用的狀態對象,判斷該應用是否具有該權限
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                final SettingBase ps = (SettingBase) obj;
                final PermissionsState permissionsState = ps.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    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;
                }
            } 
            //此處處理系統應用的狀態,延伸下去發現是從xml文件讀取的默認權限。
            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;
    }

至此運行時權限的查詢流程走完了,我們發現運行時權限最終是落腳於PackageManagerService中的,這裏纔是運行時權限的核心。至於運行時權限的設置、持久化是如何進行,在下文權限的申請中討論,當然也是在PackageManagerService中。

權限的申請:

動態權限的申請用requestPermissions來進行,從Activity作爲起點:

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
        if (mHasCurrentPermissionsRequest) {
            Log.w(TAG, "Can reqeust only one set of permissions at a time");
            // Dispatch the callback with empty arrays which means a cancellation.
            onRequestPermissionsResult(requestCode, new String[0], new int[0]);
            return;
        }
        Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
        startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
        mHasCurrentPermissionsRequest = true;
    }

Intent的獲取是通過PackageManager來獲取的,用的是startActivityForResult方法來啓動權限申請對話框,便於返回結果:

public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
        if (ArrayUtils.isEmpty(permissions)) {
           throw new NullPointerException("permission cannot be null or empty");
        }
        Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS);
        intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
        intent.setPackage(getPermissionControllerPackageName());
        return intent;
    }

intent的Action爲ACTION_REQUEST_PERMISSIONS,隱式的調用GrantPermissionsActivity來進行授權。UI部分在GrantPermissionsViewHandler中處理,具體授權仍在GrantPermissionActivity中,具體授權代碼如下:

@Override
    public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
        if (isObscuredTouch()) {
            showOverlayDialog();
            finish();
            return;
        }
        GroupState groupState = mRequestGrantPermissionGroups.get(name);
        if (groupState.mGroup != null) {
            if (granted) {
                groupState.mGroup.grantRuntimePermissions(doNotAskAgain);
                groupState.mState = GroupState.STATE_ALLOWED;
            } else {
                groupState.mGroup.revokeRuntimePermissions(doNotAskAgain);
                groupState.mState = GroupState.STATE_DENIED;
            }
            updateGrantResults(groupState.mGroup);
        }
        if (!showNextPermissionGroupGrantRequest()) {
            setResultAndFinish();
        }
    }

我們看到grantRuntimePermissions是AppPermissionGroup的方法,這是PackageInstaller中的一個類,在6.0中運行時權限也都是PackageInstaller這個系統應用在管理。從名字我們也可以看出,這個應用也負責應用的安裝、卸載。

public boolean grantRuntimePermissions(boolean fixedByTheUser) {
        final boolean isSharedUser = mPackageInfo.sharedUserId != null;
        final int uid = mPackageInfo.applicationInfo.uid;

        // We toggle permissions only to apps that support runtime
        // permissions, otherwise we toggle the app op corresponding
        // to the permission if the permission is granted to the app.
        for (Permission permission : mPermissions.values()) {
            if (mAppSupportsRuntimePermissions) {
                // Do not touch permissions fixed by the system.
                if (permission.isSystemFixed()) {
                    return false;
                }

                // Ensure the permission app op enabled before the permission grant.
                if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
                    permission.setAppOpAllowed(true);
                    mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                }

                // Grant the permission if needed.
                if (!permission.isGranted()) {
                    permission.setGranted(true);
                    mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                            permission.getName(), mUserHandle);
                }

                // Update the permission flags.
                if (!fixedByTheUser) {
                    // Now the apps can ask for the permission as the user
                    // no longer has it fixed in a denied state.
                    if (permission.isUserFixed() || permission.isUserSet()) {
                        permission.setUserFixed(false);
                        permission.setUserSet(true);
                        mPackageManager.updatePermissionFlags(permission.getName(),
                                mPackageInfo.packageName,
                                PackageManager.FLAG_PERMISSION_USER_FIXED
                                        | PackageManager.FLAG_PERMISSION_USER_SET,
                                0, mUserHandle);
                    }
                }
            } else {
                // Legacy apps cannot have a not granted permission but just in case.
                // Also if the permissions has no corresponding app op, then it is a
                // third-party one and we do not offer toggling of such permissions.
                if (!permission.isGranted() || !permission.hasAppOp()) {
                    continue;
                }

                if (!permission.isAppOpAllowed()) {
                    permission.setAppOpAllowed(true);
                    // It this is a shared user we want to enable the app op for all
                    // packages in the shared user to match the behavior of this
                    // shared user having a runtime permission.
                    if (isSharedUser) {
                        // Enable the app op.
                        String[] packageNames = mPackageManager.getPackagesForUid(uid);
                        for (String packageName : packageNames) {
                            mAppOps.setUidMode(permission.getAppOp(), uid,
                                    AppOpsManager.MODE_ALLOWED);
                        }
                    } else {
                        // Enable the app op.
                        mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                    }

                    // Mark that the permission should not be be granted on upgrade
                    // when the app begins supporting runtime permissions.
                    if (permission.shouldRevokeOnUpgrade()) {
                        permission.setRevokeOnUpgrade(false);
                        mPackageManager.updatePermissionFlags(permission.getName(),
                                mPackageInfo.packageName,
                                PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE,
                                0, mUserHandle);
                    }

                    // Legacy apps do not know that they have to retry access to a
                    // resource due to changes in runtime permissions (app ops in this
                    // case). Therefore, we restart them on app op change, so they
                    // can pick up the change.
                    mActivityManager.killUid(uid, KILL_REASON_APP_OP_CHANGE);
                }
            }
        }

        return true;
    }

permissionsState.grantRuntimePermission
與checkUidPermission中的permissionsState.hasPermission相呼應。
mSettings.writeRuntimePermissionsForUserLPr(userId, false)進行持久化,將結果寫入runtime-permissions.xml。

未完待續。。。

參考:
http://blog.csdn.net/lewif/article/details/49124757
http://mobile.51cto.com/android-526905.htm

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