android Q sdcard权限详解

作者: 十八砖 | 来源:发表于2019-10-09 18:30 被阅读0次

    默认情况下,如果应用以 Android Q 为目标平台,则在访问外部存储设备中的文件时会进入过滤视图。应用可以使用 Context.getExternalFilesDir() 将专用于自己的文件存储在特定于自己的目录中。

    1. 临时停用分区存储行为:

    • 以 Android 9(API 级别 28)或更低版本为目标平台。
    • 如果您以 Android Q 为目标平台,请在应用的清单文件中将 requestLegacyExternalStorage 的值设为 true。
    <manifest ... >
        <!-- This attribute is "false" by default on apps targeting Android Q. -->
        <application android:requestLegacyExternalStorage="true" ... >
        ...
        </application>
    </manifest>
    

    2. 如何实现隔离存储:

    2.1 ApplicationInfo新增PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE标记

    PackageParser.java:

    if (sa.getBoolean(
            R.styleable.AndroidManifestApplication_requestLegacyExternalStorage,
            owner.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
        ai.privateFlags |= ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE;
    }
    

    ApplicationInfo.java:

    public boolean hasRequestedLegacyExternalStorage() {
        return (privateFlags & PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE) != 0;
    }
    

    2.2 grantRuntimePermission()重新挂载视图
    apk启动时默认挂载runtime/default视图,grantRuntimePermission()时如果是READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE,则会获取挂载模式重新挂载对应视图。

    PermissionManagerService.java:

        private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
                int callingUid, final int userId, PermissionCallback callback) {
            ......
            if (READ_EXTERNAL_STORAGE.equals(permName)
                    || WRITE_EXTERNAL_STORAGE.equals(permName)) {
                final long token = Binder.clearCallingIdentity();
                try {
                    if (mUserManagerInt.isUserInitialized(userId)) {
                        StorageManagerInternal storageManagerInternal = LocalServices.getService(
                                StorageManagerInternal.class);
                        storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
                    }
                } finally {
                    Binder.restoreCallingIdentity(token);
                }
            }
    
        }
    

    获取挂载模式这块android10有修改,没有设置Legacy标志的话,总是获取default挂载模式,没有读写权限。
    android 10会设置属性[persist.sys.isolated_storage]: [true],因此走到if(ENABLE_ISOLATED_STORAGE)中的getMountMode()。

    public static boolean hasIsolatedStorage() {
        //[persist.sys.isolated_storage]: [true]
        //[sys.isolated_storage_snapshot]: [true]
        return SystemProperties.getBoolean("sys.isolated_storage_snapshot",
                SystemProperties.getBoolean("persist.sys.isolated_storage", true));
    }
    
    private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage();
    
    public int getExternalStorageMountMode(int uid, String packageName) {
    +    //android 10新增逻辑
    +    if (ENABLE_ISOLATED_STORAGE) {
    +        return getMountMode(uid, packageName);
    +    }
        ......
        int mountMode = Integer.MAX_VALUE;
        for (ExternalStorageMountPolicy policy : mPolicies) {
            final int policyMode = policy.getMountMode(uid, packageName);
            if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
                return Zygote.MOUNT_EXTERNAL_NONE;
            }
            mountMode = Math.min(mountMode, policyMode);
        }
        if (mountMode == Integer.MAX_VALUE) {
            return Zygote.MOUNT_EXTERNAL_NONE;
        }
        return mountMode;
    }
    

    正常模式下hasLegacy=false,走到if判断的DEFAULT分支;legacy模式hasLegacy=true,与之前保持一致,有write权限就走到WRITE模式分支。

    private int getMountModeInternal(int uid, String packageName) {
        try {
            ......
            final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
                    uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE);
            final boolean hasWrite = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
                    uid, packageName, WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE);
            ......
            final boolean hasLegacy = mIAppOpsService.checkOperation(OP_LEGACY_STORAGE,
                    uid, packageName) == MODE_ALLOWED;
            if (hasLegacy && hasWrite) {
                return Zygote.MOUNT_EXTERNAL_WRITE;
            } else if (hasLegacy && hasRead) {
                return Zygote.MOUNT_EXTERNAL_READ;
            } else {
                return Zygote.MOUNT_EXTERNAL_DEFAULT;
            }
        } catch (RemoteException e) {
            // Should not happen
        }
        return Zygote.MOUNT_EXTERNAL_NONE;
    }
    

    2.3 Legacy Storage属性对权限的影响
    安装apk时,就会根据requestLegacyExternalStorage属性来对ops state进行设置,修改OP_LEGACY_STORAGE的默认状态。

    <manifest ... >
        <application android:requestLegacyExternalStorage="true" ... >
        </application>
    </manifest>
    
    //Q 正常模式
    LEGACY_STORAGE: mode=ignore
    //Q legacy模式
    LEGACY_STORAGE: mode=allow
    

    PermissionPolicyService启动时首先进行权限变化监听:

    public void onStart() {
        permManagerInternal.addOnRuntimePermissionStateChangedListener(
        this::synchronizePackagePermissionsAndAppOpsAsyncForUser);
    }
    
    private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName,
            @UserIdInt int changedUserId) {
        if (isStarted(changedUserId)) {
            synchronized (mLock) {
                if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
                    FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                            PermissionPolicyService
                                    ::synchronizePackagePermissionsAndAppOpsForUser,
                            this, packageName, changedUserId));
                }
                ......
            }
        }
    }
    

    APK安装时,会根据requestLegacyExternalStorage属性来通知storage权限变化,调用关系如下:

    //调用关系:
    1.PackageManagerService.java:
        installPackagesLI()
            commitPackagesLocked()
                updateSettingsLI()
                    updateSettingsInternalLI()
                        2.PermissionManagerService.java:
                            mPermissionManager.updatePermissions()
                                restorePermissionState()
    
    //关键代码:
    private void restorePermissionState(@NonNull PackageParser.Package pkg, boolean replace,
            @Nullable String packageOfInterest, @Nullable PermissionCallback callback) {
    ......
            //判断requestLegacyExternalStorage属性
            updatedUserIds = checkIfLegacyStorageOpsNeedToBeUpdated(pkg, replace, updatedUserIds);
    ......
        for (int userId : updatedUserIds) {
            notifyRuntimePermissionStateChanged(pkg.packageName, userId);
        }
    }
    
    

    最终调用到PermissionPolicyService的监听函数synchronizePackagePermissionsAndAppOpsForUser(),进行默认权限获取和设置。
    当apk安装时,声明了requestLegacyExternalStorage="true"属性,并且声明了READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE,那么addOpIfRestricted()就会将LEGACY_STORAGE设置为allow模式。

    //调用关系:
    synchronizePackagePermissionsAndAppOpsForUser():
        synchroniser.addPackage()
            addOpIfRestricted()//LEGACY_STORAGE加入到mOpsToAllow
        synchroniser.syncPackages()
            setUidModeAllowed()
                setUidMode()//设置LEGACY_STORAGE为allow
    
    //关键代码:
    private void addOpIfRestricted(@NonNull PermissionInfo permissionInfo,
            @NonNull PackageInfo pkg) {
    ......
            //forPermission()会根据requestLegacyExternalStorage的值进行返回
            final SoftRestrictedPermissionPolicy policy =
                    SoftRestrictedPermissionPolicy.forPermission(mContext, pkg.applicationInfo,
                            mContext.getUser(), permission);
    
            final int op = policy.resolveAppOp();
            if (op != OP_NONE) {
                switch (policy.getDesiredOpMode()) {
                    case MODE_DEFAULT:
                        mOpsToDefault.add(new OpToChange(uid, pkg.packageName, op));
                        break;
                    case MODE_ALLOWED:
                        //在声明READ_EXTERNAL_STORAGE权限下,会将LEGACY_STORAGE加入到mOpsToAllow
                        if (policy.shouldSetAppOpIfNotDefault()) {
                            mOpsToAllow.add(new OpToChange(uid, pkg.packageName, op));
                        } else {
                            mOpsToAllowIfDefault.add(
                                    new OpToChange(uid, pkg.packageName, op));
                        }
                        break;
    ......
    }
    
    public static @NonNull SoftRestrictedPermissionPolicy forPermission(@NonNull Context context,
            @Nullable ApplicationInfo appInfo, @Nullable UserHandle user,
            @NonNull String permission) {
        switch (permission) {
            case READ_EXTERNAL_STORAGE: {
                if (appInfo != null) {
                    boolean hasAnyRequestedLegacyExternalStorage =
                            appInfo.hasRequestedLegacyExternalStorage();
    
                    hasRequestedLegacyExternalStorage = hasAnyRequestedLegacyExternalStorage;
                }
    
                return new SoftRestrictedPermissionPolicy() {
                    @Override
                    public int getDesiredOpMode() {
                        if (applyRestriction) {
                            return MODE_DEFAULT;
                        } else if (hasRequestedLegacyExternalStorage) {
                            //声明了requestLegacyExternalStorage就返回allow
                            return MODE_ALLOWED;
                        } else {
                            return MODE_IGNORED;
                        }
                    }
    
                    @Override
                    public boolean shouldSetAppOpIfNotDefault() {
                        return getDesiredOpMode() != MODE_IGNORED;
                    }
                };
            }
    

    3. sdcard路径权限说明:

    • rwx:421,umask默认为八进制022(----w--w-)
    • /mnt/runtime/default的gid为1015,也就是sdcard_rw;mask 为6,八进制006,group sdcard_rw可读写,也就是other没有rw权限
    • /mnt/runtime/read的gid为9997,也就是everybody;mask 为23,八进制027,group everybody可读、不可写,other没有读写执行权限
    • /mnt/runtime/write的gid为9997,也就是everybody;mask 为7,八进制007,group everybody可读写,other没有读写可执行权限
    /data/media on /mnt/runtime/default/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=1015,multiuser,mask=6,derive_gid,default_normal)
    /data/media on /mnt/runtime/read/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=23,derive_gid,default_normal)
    /data/media on /mnt/runtime/write/emulated type sdcardfs (rw,nosuid,nodev,noexec,noatime,fsuid=1023,fsgid=1023,gid=9997,multiuser,mask=7,derive_gid,default_normal)
    

    /mnt/runtime/default:

    drwxrwx--x 3 root sdcard_rw 4096 2018-12-18 03:41 Android
    drwxrwx--x 3 root sdcard_rw 4096 2018-12-18 06:11 DCIM
    

    /mnt/runtime/read:

    drwxr-x--- 3 root everybody 4096 2018-12-18 03:41 Android
    drwxr-x--- 3 root everybody 4096 2018-12-18 06:11 DCIM
    

    /mnt/runtime/write:

    drwxrwx--- 3 root everybody 4096 2018-12-18 03:41 Android
    drwxrwx--- 3 root everybody 4096 2018-12-18 06:11 DCIM
    

    /sdcard/Android/data:

    drwxrwx--- 4 u0_a64 everybody 4096 2018-12-18 06:11 com.android.camera2
    drwxrwx--- 3 u0_a15 everybody 4096 2018-12-18 03:41 com.google.android.gms
    drwxrwx--- 4 u0_a84 everybody 4096 2018-12-18 03:41 com.google.android.youtube
    

    4. sdcard文件存储示例:

    4.1 getExternalFilesDir()随卸载而删除

    ///storage/emulated/0/Android/data/com.xx.xx/files
    File file = File(context.getExternalFilesDir(null), "test.txt");
    

    4.2 媒体文件
    媒体文件使用MediaStore操作,卸载后不会删除。
    访问其他应用生成的照片、视频、音频,需要READ_EXTERNAL_STORAGE权限。

    4.3 存储访问框架(SAF)
    访问其他应用创建的文件,例如"Download"目录,必须使用存储访问框架,用户通过框架选择特定文件。

    4.4 照片中的位置信息
    需要ACCESS_MEDIA_LOCATION权限,才能获取元数据中的位置信息。

    <permission android:name="android.permission.ACCESS_MEDIA_LOCATION"
        android:permissionGroup="android.permission-group.UNDEFINED"
        android:label="@string/permlab_mediaLocation"
        android:description="@string/permdesc_mediaLocation"
        android:protectionLevel="dangerous" />
    

    相关文章

      网友评论

        本文标题:android Q sdcard权限详解

        本文链接:https://www.haomeiwen.com/subject/fsogpctx.html