外置sd卡相关功能的时候遇到了在9.0上外置SD卡写入权限的问题,在 9.0 之前的平台,申请了 WRITE_MEDIA_STORAGE 的权限后,平台签名的应用就可以通过 java.io.File 接口写入外置 SD 卡了。但之后,想要写入外置 SD 卡,就需要像第三方应用一样,使用 DocumentFile 的接口,
可以阅读 API 文档 存储访问框架 和 使用作用域目录访问 。

android 9加入权限和动态申请都无法对外置SD卡创建文件夹

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

如果是APP开发,可以做兼容 , 以下是对DocumentFile 类调用的封装

贴代码

 

package com.mw.test;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private static final String TAG = "MainActivity-Test";
    String rootPath = "";  //外置 SD 卡路径
    private List<String> unPermissionList = new ArrayList<String>(); //申请未得到授权的权限列表
    private final int RequestCode = 100;//权限请求码
    private AlertDialog mPermissionDialog;
    private String mPackName ;  //获取 a'p'k 包名
    private String[] permissionList = new String[]{    //申请的权限列表
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
    };

    private Button mButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        checkPermission();
        mPackName = getPackageName();
        rootPath = getStoragePath(this,true);  //获取 可以插拔的 sd 卡  路径
        Log.i(TAG," rootPath: " + rootPath);
        if (DocumentsUtils.checkWritableRootPath(this, rootPath)) {   //检查sd卡路径是否有 权限 没有显示dialog
            showOpenDocumentTree();
        }


        mButton = (Button) findViewById(R.id.creatDir);
        mButton.setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.creatDir:
                Log.i(TAG,"点击");
               try {
                    writeDataToSD();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }


    private void showOpenDocumentTree() {
        Intent intent = null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            StorageManager sm = getSystemService(StorageManager.class);

            StorageVolume volume = sm.getStorageVolume(new File(rootPath));

            if (volume != null) {
                intent = volume.createAccessIntent(null);
            }
        }

        if (intent == null) {
            intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        }
        startActivityForResult(intent, DocumentsUtils.OPEN_DOCUMENT_TREE_CODE);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case DocumentsUtils.OPEN_DOCUMENT_TREE_CODE:
                if (data != null && data.getData() != null) {
                    Uri uri = data.getData();
                    DocumentsUtils.saveTreeUri(this, rootPath, uri);
                }
                break;
            default:
                break;
        }
    }

    //权限判断和申请
    public void checkPermission() {
        unPermissionList.clear();//清空申请的没有通过的权限
        //逐个判断是否还有未通过的权限
        for (int i = 0; i < permissionList.length; i++) {
            if (ContextCompat.checkSelfPermission(this, permissionList[i]) !=
                    PackageManager.PERMISSION_GRANTED) {
                unPermissionList.add(permissionList[i]);//添加还未授予的权限到unPermissionList中
            }
        }

        //有权限没有通过,需要申请
        if (unPermissionList.size() > 0) {
            ActivityCompat.requestPermissions( this,permissionList, 100);
            Log.i(TAG, "check 有权限未通过");
        } else {
            //权限已经都通过了,可以将程序继续打开了
            Log.i(TAG, "check 权限都已经申请通过");
        }
    }

    /**
     * 5.请求权限后回调的方法
     *
     * @param requestCode  是我们自己定义的权限请求码
     * @param permissions  是我们请求的权限名称数组
     * @param grantResults 是我们在弹出页面后是否允许权限的标识数组,数组的长度对应的是权限
     *                     名称数组的长度,数组的数据0表示允许权限,-1表示我们点击了禁止权限
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                           int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        Log.i(TAG,"申请结果反馈");
        boolean hasPermissionDismiss = false;
        if (100 == requestCode) {
            for (int i = 0; i < grantResults.length; i++) {
                if (grantResults[i] == -1) {
                    hasPermissionDismiss = true; //有权限没有通过
                    Log.i(TAG,"有权限没有被通过");
                    break;
                }
            }
        }
        if (hasPermissionDismiss) {//如果有没有被允许的权限
            showPermissionDialog();
        } else {
            //权限已经都通过了,可以将程序继续打开了
            Log.i(TAG, "onRequestPermissionsResult 权限都已经申请通过");
        }
    }


    /**
     * 不再提示权限时的展示对话框
     */

    private void showPermissionDialog() {


        Log.i(TAG,"mPackName: " + mPackName);
        if (mPermissionDialog == null) {
            mPermissionDialog = new AlertDialog.Builder(this)
                    .setMessage("已禁用权限,请手动授予")
                    .setPositiveButton("设置", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            cancelPermissionDialog();

                            Uri packageURI = Uri.parse("package:" + mPackName);     //去设置里面设置
                            Intent intent = new Intent(Settings.
                                    ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                            startActivity(intent);
                        }
                    })
                    .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            //关闭页面或者做其他操作
                            cancelPermissionDialog();
                        }
                    })
                    .create();
        }
        mPermissionDialog.show();


    }

    private void cancelPermissionDialog() {
        mPermissionDialog.cancel();
    }




    /**
     * 通过反射调用获取内置存储和外置sd卡根路径(通用)
     *
     * @param mContext    上下文
     * @param is_removale 是否可移除,false返回内部存储路径,true返回外置SD卡路径
     * @return
     */
    private static String getStoragePath(Context mContext, boolean is_removale) {
        String path = "";
        //使用getSystemService(String)检索一个StorageManager用于访问系统存储功能。
        StorageManager mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
        Class<?> storageVolumeClazz = null;
        try {
            storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
            Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
            Method getPath = storageVolumeClazz.getMethod("getPath");
            Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
            Object result = getVolumeList.invoke(mStorageManager);

            for (int i = 0; i < Array.getLength(result); i++) {
                Object storageVolumeElement = Array.get(result, i);
                path = (String) getPath.invoke(storageVolumeElement);
                boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);
                if (is_removale == removable) {
                    return path;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return path;
    }


    public  void writeDataToSD() throws IOException {
        //写文件的例子 文件不存在,会创建
        String  str = "just a test\n";
        String strRead = "";

        String  sdkOut = getStoragePath(this,true);  //获取 可以插拔的 sd 卡  路径
        Log.i(TAG,"sdkOut:"+sdkOut);
        String  filePath = sdkOut + "/test";
        Log.i(TAG," sdkOut: " + filePath);
        File file = new File(filePath);
        if (!file.exists()){
            if(DocumentsUtils.mkdirs(this,file)){
                Log.i(TAG,"创建文件夹:" + filePath);
            }else{
                Log.i(TAG,"创建文件夹失败:" + filePath);
            }

        }

        String  fileWritePath = filePath + "/test.txt";
        File fileWrite = new File(fileWritePath);


        Log.i(TAG,"  准备写入" );
        try {
            OutputStream outputStream = DocumentsUtils.getOutputStream(this,fileWrite);  //获取输出流
            //  OutputStream outputStream = new FileOutputStream(fileWrite);
            outputStream.write(str.getBytes());
            outputStream.close();
            Log.i(TAG," 写入成功" );
            Toast.makeText(this,"路径:" + fileWritePath + "成功",Toast.LENGTH_SHORT ).show();
        } catch (IOException e) {
            e.printStackTrace();
            Log.i(TAG," 写入失败" );
            Toast.makeText(this,"路径:" + fileWritePath + "失败",Toast.LENGTH_SHORT ).show();
        }



    }

}
package com.test.multicamera.utils;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.DocumentsContract;
import android.util.Log;

import androidx.documentfile.provider.DocumentFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;


public class DocumentsUtils {

    private static final String TAG = DocumentsUtils.class.getSimpleName();

    public static final int OPEN_DOCUMENT_TREE_CODE = 8000;

    private static List<String> sExtSdCardPaths = new ArrayList<>();

    private DocumentsUtils() {

    }

    public static void cleanCache() {
        sExtSdCardPaths.clear();
    }

    /**
     * Get a list of external SD card paths. (Kitkat or higher.)
     *
     * @return A list of external SD card paths.
     */
    @TargetApi(Build.VERSION_CODES.KITKAT)
    private static String[] getExtSdCardPaths(Context context) {
        if (sExtSdCardPaths.size() > 0) {
            return sExtSdCardPaths.toArray(new String[0]);
        }
        for (File file : context.getExternalFilesDirs("external")) {
            if (file != null && !file.equals(context.getExternalFilesDir("external"))) {
                int index = file.getAbsolutePath().lastIndexOf("/Android/data");
                if (index < 0) {
                    Log.w(TAG, "Unexpected external file dir: " + file.getAbsolutePath());
                } else {
                    String path = file.getAbsolutePath().substring(0, index);
                    try {
                        path = new File(path).getCanonicalPath();
                    } catch (IOException e) {
                        // Keep non-canonical path.
                    }
                    sExtSdCardPaths.add(path);
                }
            }
        }
        if (sExtSdCardPaths.isEmpty()) sExtSdCardPaths.add("/storage/sdcard1");
        return sExtSdCardPaths.toArray(new String[0]);
    }

    /**
     * Determine the main folder of the external SD card containing the given file.
     *
     * @param file the file.
     * @return The main folder of the external SD card containing this file, if the file is on an SD
     * card. Otherwise,
     * null is returned.
     */
    @TargetApi(Build.VERSION_CODES.KITKAT)
    private static String getExtSdCardFolder(final File file, Context context) {
        String[] extSdPaths = getExtSdCardPaths(context);
        try {
            for (int i = 0; i < extSdPaths.length; i++) {
                if (file.getCanonicalPath().startsWith(extSdPaths[i])) {
                    return extSdPaths[i];
                }
            }
        } catch (IOException e) {
            return null;
        }
        return null;
    }

    /**
     * Determine if a file is on external sd card. (Kitkat or higher.)
     *
     * @param file The file.
     * @return true if on external sd card.
     */
    @TargetApi(Build.VERSION_CODES.KITKAT)
    public static boolean isOnExtSdCard(final File file, Context c) {
        return getExtSdCardFolder(file, c) != null;
    }

    /**
     * Get a DocumentFile corresponding to the given file (for writing on ExtSdCard on Android 5).
     * If the file is not
     * existing, it is created.
     *
     * @param file        The file.
     * @param isDirectory flag indicating if the file should be a directory.
     * @return The DocumentFile
     */
    public static DocumentFile getDocumentFile(final File file, final boolean isDirectory,
                                               Context context) {

        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
            return DocumentFile.fromFile(file);
        }

        String baseFolder = getExtSdCardFolder(file, context);
        Log.i(TAG," baseFolder " + baseFolder);
        boolean originalDirectory = false;
        if (baseFolder == null) {
            return null;
        }

        String relativePath = null;
        try {
            String fullPath = file.getCanonicalPath();
            if (!baseFolder.equals(fullPath)) {
                relativePath = fullPath.substring(baseFolder.length() + 1);
            } else {
                originalDirectory = true;
            }
        } catch (IOException e) {
            return null;
        } catch (Exception f) {
            originalDirectory = true;
            //continue
        }
        String as = PreferenceManager.getDefaultSharedPreferences(context).getString(baseFolder,
                null);

        Uri treeUri = null;
        if (as != null) treeUri = Uri.parse(as);
        if (treeUri == null) {
            return null;
        }

        // start with root of SD card and then parse through document tree.
        DocumentFile document = DocumentFile.fromTreeUri(context, treeUri);
        if (originalDirectory) return document;
        String[] parts = relativePath.split("/");
        for (int i = 0; i < parts.length; i++) {
            DocumentFile nextDocument = document.findFile(parts[i]);

            if (nextDocument == null) {
                if ((i < parts.length - 1) || isDirectory) {
                    nextDocument = document.createDirectory(parts[i]);
                } else {
                    nextDocument = document.createFile("image", parts[i]);
                }
            }
            document = nextDocument;
        }

        return document;
    }

    public static boolean mkdirs(Context context, File dir) {
        boolean res = dir.mkdirs();
        if (!res) {
            if (DocumentsUtils.isOnExtSdCard(dir, context)) {
                DocumentFile documentFile = DocumentsUtils.getDocumentFile(dir, true, context);
                res = documentFile != null && documentFile.canWrite();
            }
        }
        return res;
    }

    public static boolean delete(Context context, File file) {
        boolean ret = file.delete();
        if (!ret && DocumentsUtils.isOnExtSdCard(file, context)) {
            DocumentFile f = DocumentsUtils.getDocumentFile(file, false, context);
            if (f != null) {
                ret = f.delete();
            }
        }
        return ret;
    }

    public static boolean canWrite(File file) {
        boolean res = file.exists() && file.canWrite();

        if (!res && !file.exists()) {
            try {
                if (!file.isDirectory()) {
                    res = file.createNewFile() && file.delete();
                } else {
                    res = file.mkdirs() && file.delete();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return res;
    }

    public static boolean canWrite(Context context, File file) {
        boolean res = canWrite(file);

        if (!res && DocumentsUtils.isOnExtSdCard(file, context)) {
            DocumentFile documentFile = DocumentsUtils.getDocumentFile(file, true, context);
            res = documentFile != null && documentFile.canWrite();
        }
        return res;
    }

    public static boolean renameTo(Context context, File src, File dest) {
        boolean res = src.renameTo(dest);

        if (!res && isOnExtSdCard(dest, context)) {
            DocumentFile srcDoc;
            if (isOnExtSdCard(src, context)) {
                srcDoc = getDocumentFile(src, false, context);
            } else {
                srcDoc = DocumentFile.fromFile(src);
            }
            DocumentFile destDoc = getDocumentFile(dest.getParentFile(), true, context);
            if (srcDoc != null && destDoc != null) {
                try {
                    if (src.getParent().equals(dest.getParent())) {
                        res = srcDoc.renameTo(dest.getName());
                    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        res = DocumentsContract.moveDocument(context.getContentResolver(),
                                srcDoc.getUri(),
                                srcDoc.getParentFile().getUri(),
                                destDoc.getUri()) != null;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        return res;
    }

    public static InputStream getInputStream(Context context, File destFile) {
        InputStream in = null;
        try {
            if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) {
                DocumentFile file = DocumentsUtils.getDocumentFile(destFile, false, context);
                if (file != null && file.canWrite()) {
                    in = context.getContentResolver().openInputStream(file.getUri());
                }
            } else {
                in = new FileInputStream(destFile);

            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return in;
    }

    public static OutputStream getOutputStream(Context context, File destFile) {
        OutputStream out = null;
        try {
            if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) {
                DocumentFile file = DocumentsUtils.getDocumentFile(destFile, false, context);
                if (file != null && file.canWrite()) {
                    out = context.getContentResolver().openOutputStream(file.getUri());
                }
            } else {
                out = new FileOutputStream(destFile);

            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return out;
    }

    public static boolean saveTreeUri(Context context, String rootPath, Uri uri) {
        DocumentFile file = DocumentFile.fromTreeUri(context, uri);
        if (file != null && file.canWrite()) {
            SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context);
            perf.edit().putString(rootPath, uri.toString()).apply();
            Log.e(TAG, "save uri" + rootPath);
            return true;
        } else {
            Log.e(TAG, "no write permission: " + rootPath);
        }
        return false;
    }

    public static boolean checkWritableRootPath(Context context, String rootPath) {
        File root = new File(rootPath);
        if (!root.canWrite()) {

            if (DocumentsUtils.isOnExtSdCard(root, context)) {
                Log.i(TAG,"isOnExtSdCard");
                DocumentFile documentFile = DocumentsUtils.getDocumentFile(root, true, context);
                return documentFile == null || !documentFile.canWrite();
            } else {
                Log.i(TAG," get perf");
                SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context);

                String documentUri = perf.getString(rootPath, "");

                if (documentUri == null || documentUri.isEmpty()) {
                    return true;
                } else {
                    DocumentFile file = DocumentFile.fromTreeUri(context, Uri.parse(documentUri));
                    return !(file != null && file.canWrite());
                }
            }
        }
        return false;
    }
}

 

6 android sd卡权限 安卓9 sd卡权限_6 android sd卡权限

 

6 android sd卡权限 安卓9 sd卡权限_6 android sd卡权限_02

 

 

 

 

对于系统定制开发,为了不想改所有的APP做兼容,所以直接修改系统文件

 

Android 从6.0 开始引入了Runtime permission,应用对于storage 进行读取、存储的时候,需要注册、申请对应的权限。
Android 8.0中对于sdcard 读写只需要申请权限即可使用,可以在Android 9.0 中同样的应用执行同样的步骤,却提示了Permission denied。


APP应用外置SD卡读写失败selinux造成的报avc权限拒绝,

我这边试着关闭selinux 没有成功,只好再想其它办法

adb logcat | grep avc
 01-01 02:21:28.269  6995  6995 W zsj.multicamera: type=1400 audit(0.0:44): avc: denied { setattr } for name="base.vdex" dev="mmcblk0p56" ino=3885 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=file permissive=001-01 00:05:57.110  4047  4047 W tel.multicamera: type=1400 audit(0.0:24): avc: denied { unlink } for name="base.apk.arm64.flock" dev="mmcblk0p56" ino=2151 scontext=u:r:system_app:s0 tcontext=u:object_r:apk_data_file:s0 tclass=file permissive=0

修改系统文件-以下两个地址的内容要一致

system\sepolicy\private\system_app.te
 system\sepolicy\prebuilts\api\28.0\private\system_app.te

直接新增了好几样权限(也可以根接日志avc 提示来增加)

allow system_app apk_data_file:file {create write unlink setattr};

修改完成后,make 系统,然后只用推送相应的包就可以了

adb push SC200Y\out\target\product\msm8953_64\system\etc\selinux /system/etc/ 
adb reboot

重启后 重试 还是不行

// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#define LOG_TAG "sdcard"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/fuse.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/mount.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>

#include <cutils/fs.h>
#include <cutils/multiuser.h>
#include <cutils/properties.h>

#include <libminijail.h>
#include <scoped_minijail.h>

#include <private/android_filesystem_config.h>

#define PROP_SDCARDFS_DEVICE "ro.sys.sdcardfs"
#define PROP_SDCARDFS_USER "persist.sys.sdcardfs"

static bool supports_esdfs(void) {
    std::string filesystems;
    if (!android::base::ReadFileToString("/proc/filesystems", &filesystems)) {
        PLOG(ERROR) << "Could not read /proc/filesystems";
        return false;
    }
    for (const auto& fs : android::base::Split(filesystems, "\n")) {
        if (fs.find("esdfs") != std::string::npos) return true;
    }
    return false;
}

static bool should_use_sdcardfs(void) {
    char property[PROPERTY_VALUE_MAX];

    // Allow user to have a strong opinion about state
    property_get(PROP_SDCARDFS_USER, property, "");
    if (!strcmp(property, "force_on")) {
        LOG(WARNING) << "User explicitly enabled sdcardfs";
        return true;
    } else if (!strcmp(property, "force_off")) {
        LOG(WARNING) << "User explicitly disabled sdcardfs";
        return !supports_esdfs();
    }

    // Fall back to device opinion about state
    if (property_get_bool(PROP_SDCARDFS_DEVICE, true)) {
        LOG(WARNING) << "Device explicitly enabled sdcardfs";
        return true;
    } else {
        LOG(WARNING) << "Device explicitly disabled sdcardfs";
        return !supports_esdfs();
    }
}

// NOTE: This is a vestigial program that simply exists to mount the in-kernel
// sdcardfs filesystem.  The older FUSE-based design that used to live here has
// been completely removed to avoid confusion.

/* Supplementary groups to execute with. */
static const gid_t kGroups[1] = { AID_PACKAGE_INFO };

static void drop_privs(uid_t uid, gid_t gid) {
    ScopedMinijail j(minijail_new());
    minijail_set_supplementary_gids(j.get(), arraysize(kGroups), kGroups);
    minijail_change_gid(j.get(), gid);
    minijail_change_uid(j.get(), uid);
    /* minijail_enter() will abort if priv-dropping fails. */
    minijail_enter(j.get());
}

static bool sdcardfs_setup(const std::string& source_path, const std::string& dest_path,
                           uid_t fsuid, gid_t fsgid, bool multi_user, userid_t userid, gid_t gid,
                           mode_t mask, bool derive_gid, bool default_normal, bool use_esdfs) {
    // Try several attempts, each time with one less option, to gracefully
    // handle older kernels that aren't updated yet.
    for (int i = 0; i < 4; i++) {
        std::string new_opts;
        if (multi_user && i < 3) new_opts += "multiuser,";
        if (derive_gid && i < 2) new_opts += "derive_gid,";
        if (default_normal && i < 1) new_opts += "default_normal,";

        auto opts = android::base::StringPrintf("fsuid=%d,fsgid=%d,%smask=%d,userid=%d,gid=%d",
                                                fsuid, fsgid, new_opts.c_str(), mask, userid, gid);
        if (mount(source_path.c_str(), dest_path.c_str(), use_esdfs ? "esdfs" : "sdcardfs",
                  MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) == -1) {
            PLOG(WARNING) << "Failed to mount sdcardfs with options " << opts;
        } else {
            return true;
        }
    }

    return false;
}

static bool sdcardfs_setup_bind_remount(const std::string& source_path, const std::string& dest_path,
                                        gid_t gid, mode_t mask) {
    std::string opts = android::base::StringPrintf("mask=%d,gid=%d", mask, gid);

    if (mount(source_path.c_str(), dest_path.c_str(), nullptr,
            MS_BIND, nullptr) != 0) {
        PLOG(ERROR) << "failed to bind mount sdcardfs filesystem";
        return false;
    }

    if (mount(source_path.c_str(), dest_path.c_str(), "none",
            MS_REMOUNT | MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME, opts.c_str()) != 0) {
        PLOG(ERROR) << "failed to mount sdcardfs filesystem";
        if (umount2(dest_path.c_str(), MNT_DETACH))
            PLOG(WARNING) << "Failed to unmount bind";
        return false;
    }

    return true;
}

static bool sdcardfs_setup_secondary(const std::string& default_path, const std::string& source_path,
                                     const std::string& dest_path, uid_t fsuid, gid_t fsgid,
                                     bool multi_user, userid_t userid, gid_t gid, mode_t mask,
                                     bool derive_gid, bool default_normal, bool use_esdfs) {
    if (use_esdfs) {
        return sdcardfs_setup(source_path, dest_path, fsuid, fsgid, multi_user, userid, gid, mask,
                              derive_gid, default_normal, use_esdfs);
    } else {
        return sdcardfs_setup_bind_remount(default_path, dest_path, gid, mask);
    }
}

static void run_sdcardfs(const std::string& source_path, const std::string& label, uid_t uid,
                         gid_t gid, userid_t userid, bool multi_user, bool full_write,
                         bool derive_gid, bool default_normal, bool use_esdfs) {
    std::string dest_path_default = "/mnt/runtime/default/" + label;
    std::string dest_path_read = "/mnt/runtime/read/" + label;
    std::string dest_path_write = "/mnt/runtime/write/" + label;

    umask(0);
   //alex 2021-03-20 modify sdcard avc denied  [full_write set true and   multi_user set false;
    full_write =true;
    multi_user = false;
    if (multi_user) {
        // Multi-user storage is fully isolated per user, so "other"
        // permissions are completely masked off.
        if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid,
                            AID_SDCARD_RW, 0006, derive_gid, default_normal, use_esdfs) ||
            !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_read, uid, gid,
                                      multi_user, userid, AID_EVERYBODY, 0027, derive_gid,
                                      default_normal, use_esdfs) ||
            !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_write, uid, gid,
                                      multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0027,
                                      derive_gid, default_normal, use_esdfs)) {
            LOG(FATAL) << "failed to sdcardfs_setup";
        }
    } else {
        // Physical storage is readable by all users on device, but
        // the Android directories are masked off to a single user
        // deep inside attr_from_stat().
        if (!sdcardfs_setup(source_path, dest_path_default, uid, gid, multi_user, userid,
                            AID_SDCARD_RW, 0007, derive_gid, default_normal, use_esdfs) ||
            !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_read, uid, gid,
                                      multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0022,
                                      derive_gid, default_normal, use_esdfs) ||
            !sdcardfs_setup_secondary(dest_path_default, source_path, dest_path_write, uid, gid,
                                      multi_user, userid, AID_EVERYBODY, full_write ? 0007 : 0022,
                                      derive_gid, default_normal, use_esdfs)) {
            LOG(FATAL) << "failed to sdcardfs_setup";
        }
    }

    // Will abort if priv-dropping fails.
    drop_privs(uid, gid);

    if (multi_user) {
        std::string obb_path = source_path + "/obb";
        fs_prepare_dir(obb_path.c_str(), 0775, uid, gid);
    }

    exit(0);
}

static int usage() {
    LOG(ERROR) << "usage: sdcard [OPTIONS] <source_path> <label>"
               << "    -u: specify UID to run as"
               << "    -g: specify GID to run as"
               << "    -U: specify user ID that owns device"
               << "    -m: source_path is multi-user"
               << "    -w: runtime write mount has full write access"
               << "    -P  preserve owners on the lower file system";
    return 1;
}

int main(int argc, char **argv) {
    const char *source_path = NULL;
    const char *label = NULL;
    uid_t uid = 0;
    gid_t gid = 0;
    userid_t userid = 0;
    bool multi_user = false;
    bool full_write = false;
    bool derive_gid = false;
    bool default_normal = false;
    int i;
    struct rlimit rlim;
    int fs_version;

    setenv("ANDROID_LOG_TAGS", "*:v", 1);
    android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));

    int opt;
    while ((opt = getopt(argc, argv, "u:g:U:mwGi")) != -1) {
        switch (opt) {
            case 'u':
                uid = strtoul(optarg, NULL, 10);
                break;
            case 'g':
                gid = strtoul(optarg, NULL, 10);
                break;
            case 'U':
                userid = strtoul(optarg, NULL, 10);
                break;
            case 'm':
                multi_user = true;
                break;
            case 'w':
                full_write = true;
                break;
            case 'G':
                derive_gid = true;
                break;
            case 'i':
                default_normal = true;
                break;
            case '?':
            default:
                return usage();
        }
    }

    for (i = optind; i < argc; i++) {
        char* arg = argv[i];
        if (!source_path) {
            source_path = arg;
        } else if (!label) {
            label = arg;
        } else {
            LOG(ERROR) << "too many arguments";
            return usage();
        }
    }

    if (!source_path) {
        LOG(ERROR) << "no source path specified";
        return usage();
    }
    if (!label) {
        LOG(ERROR) << "no label specified";
        return usage();
    }
    if (!uid || !gid) {
        LOG(ERROR) << "uid and gid must be nonzero";
        return usage();
    }

    rlim.rlim_cur = 8192;
    rlim.rlim_max = 8192;
    if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) {
        PLOG(ERROR) << "setting RLIMIT_NOFILE failed";
    }

    while ((fs_read_atomic_int("/data/.layout_version", &fs_version) == -1) || (fs_version < 3)) {
        LOG(ERROR) << "installd fs upgrade not yet complete; waiting...";
        sleep(1);
    }

    run_sdcardfs(source_path, label, uid, gid, userid, multi_user, full_write, derive_gid,
                 default_normal, !should_use_sdcardfs());
    return 1;
}

标红的为被修改的部分

6 android sd卡权限 安卓9 sd卡权限_sd卡_03

 

只需要sdcard打包

mmm system/core/sdcard/


只需要推送

adb push  SC200Y\out\target\product\msm8953_64\system\bin\sdcard /system/bin


然后杀进程 或 重启系统

ps -A |grep sdcard

然后就可以正常读写了