如何检查目录路径已被授权并且不会抛出 EACCES 错误?

15

我有一个照片编辑安卓应用,用户可以选择结果照片的输出目录。问题是谷歌在KITKAT(安卓系统版本)中更改了SD卡写入权限,安卓KITKAT版本的设备不允许应用程序写入次级SD卡。现在,我需要检查用户选择的目录是否已授予权限并且不会抛出EACCES错误。我已经检查了canRead和canWrite,但这些都没有帮助。请告诉我如何检查所选目录是否不会引发EACCES。我的唯一解决方案是尝试在try catch中写入文件,但我希望有更好的方法。

[更新k3b 2016-09-19]

我在我的安卓4.4上尝试过这个方法,但没有成功。

Uri uri = Uri.fromFile(file);
int permissionCode = 
     context.checkCallingOrSelfUriPermission(uri,
     Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
if (permissionCode == PackageManager.PERMISSION_DENIED) {
   // on my android-4.4 i always get PERMISSION_DENIED even 
   // if i can overwrite the file
   return false;
}

@k3b:一个更简单、更可靠的解决方案是坚持使用已知的根目录。如果您尝试遍历已知安全目录之上的目录(例如,您尝试从“/”开始工作而不是坚持使用“Environment.getExternalStorageDirectory()”),那么您唯一可能得到无法使用的目录就是这种情况。如果您想支持可移动存储,请在Android 5.0+上使用Storage Access Framework和“ACTION_OPEN_DOCUMENT_TREE”(额外奖励:您将获得其他文档提供者的支持)。 - CommonsWare
在提供赏金后,没有新的答案,所以我认为目前没有解决方案 :-( - k3b
2个回答

3
try {
    Process p = new ProcessBuilder("ls", "-l", "-s", dir.getCanonicalPath()).start();

    String line;
    ArrayList<String> lineOut = new ArrayList<>();

    BufferedReader error = new BufferedReader(new InputStreamReader(p.getErrorStream()));
    while ((line = error.readLine()) != null) {
        Log.e(TAG, "ls error = "+line);
    }
    error.close();

    BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
    while ((line = input.readLine()) != null) {
        lineOut.add(line);
    }

    input.close();

    String[] strings = lineOut.toArray(new String[]{});
    List<FilesLS.FileEntry> fileEntries = FilesLS.processNewLines(strings);
    for(FilesLS.FileEntry file : fileEntries){
        Log.d(TAG, file.name +" = " + file.permissions);
    }

} catch (IOException e) {
    e.printStackTrace();
}

对于这个的一些编辑

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class FilesLS {
    /**
     * Entry type: File
     */
    public static final int TYPE_FILE = 0;
    /**
     * Entry type: Directory
     */
    public static final int TYPE_DIRECTORY = 1;
    /**
     * Entry type: Directory Link
     */
    public static final int TYPE_DIRECTORY_LINK = 2;
    /**
     * Entry type: Block
     */
    public static final int TYPE_BLOCK = 3;
    /**
     * Entry type: Character
     */
    public static final int TYPE_CHARACTER = 4;
    /**
     * Entry type: Link
     */
    public static final int TYPE_LINK = 5;
    /**
     * Entry type: Socket
     */
    public static final int TYPE_SOCKET = 6;
    /**
     * Entry type: FIFO
     */
    public static final int TYPE_FIFO = 7;
    /**
     * Entry type: Other
     */
    public static final int TYPE_OTHER = 8;
    /**
     * Device side file separator.
     */
    public static final String FILE_SEPARATOR = "/"; //$NON-NLS-1$
    /**
     * Regexp pattern to parse the result from ls.
     */
    private static Pattern sLsPattern = Pattern
        .compile("^([bcdlsp-][-r][-w][-xsS][-r][-w][-xsS][-r][-w][-xstST])\\s+(\\S+)\\s+   (\\S+)\\s+(\\d{4}-\\d\\d-\\d\\d)\\s+(\\d\\d:\\d\\d)\\s+(.*)$"); //$NON-NLS-1$  \s+([\d\s,]*)

    public static List<FileEntry> processNewLines(String[] lines) {
        List<FileEntry> listOfFiles = new ArrayList<FileEntry>();
        for (String line : lines) {
            // no need to handle empty lines.
            if (line.length() == 0) {
                continue;
            }
            // run the line through the regexp
            Matcher m = sLsPattern.matcher(line);
            if (m.matches() == false) {
                continue;
            }
            // get the name
            String name = m.group(6);
            // get the rest of the groups
            String permissions = m.group(1);
            String owner = m.group(2);
            String group = m.group(3);
            // String size = m.group(4);
            String date = m.group(4);
            String time = m.group(5);
            String info = null;
            // and the type
            int objectType = TYPE_OTHER;
            switch (permissions.charAt(0)) {
                case '-':
                    objectType = TYPE_FILE;
                    break;
                case 'b':
                    objectType = TYPE_BLOCK;
                    break;
                case 'c':
                    objectType = TYPE_CHARACTER;
                    break;
                case 'd':
                    objectType = TYPE_DIRECTORY;
                    break;
                case 'l':
                    objectType = TYPE_LINK;
                    break;
                case 's':
                    objectType = TYPE_SOCKET;
                    break;
                case 'p':
                    objectType = TYPE_FIFO;
                    break;
            }
            // now check what we may be linking to
            if (objectType == TYPE_LINK) {
                String[] segments = name.split("\\s->\\s"); //$NON-NLS-1$
                // we should have 2 segments
                if (segments.length == 2) {
                    // update the entry name to not contain the link
                    name = segments[0];
                    // and the link name
                    info = segments[1];
                    // now get the path to the link
                    String[] pathSegments = info.split(FILE_SEPARATOR);
                    if (pathSegments.length == 1) {
                        // the link is to something in the same directory,
                        // unless the link is ..
                        if ("..".equals(pathSegments[0])) { //$NON-NLS-1$
                            // set the type and we're done.
                            objectType = TYPE_DIRECTORY_LINK;
                        } else {
                            // either we found the object already
                            // or we'll find it later.
                        }
                    }
                }
                // add an arrow in front to specify it's a link.
                info = "-> " + info; //$NON-NLS-1$;
            }
            FileEntry entry = new FileEntry();
            entry.permissions = permissions;
            entry.name = name;
            // entry.size = size;
            entry.date = date;
            entry.time = time;
            entry.owner = owner;
            entry.group = group;
            if (objectType == TYPE_LINK) {
                entry.info = info;
            }
            listOfFiles.add(entry);
        }
        return listOfFiles;
    }

    public final static class FileEntry {
        String name;
        String info;
        String permissions;
        String size;
        String date;
        String time;
        String owner;
        String group;
        int type;
    }
}

4
这不是一个很好的解决方案。首先,启动一个单独的进程要比尝试所需操作并捕获失败要低效得多。其次,鉴于问题在于系统错误地报告文件以不存在的方式可访问,不完全清楚“ls”的输出是否会更准确,而不是报告同样不正确的信息。 - Chris Stratton

1
将您需要的权限添加到数组中:
private static final int REQUEST_CODE_PERMISSION = 2;
String[] mPermission = {
    Manifest.permission.INTERNET,
    Manifest.permission.CHANGE_WIFI_STATE,
    Manifest.permission.CHANGE_NETWORK_STATE,
    Manifest.permission.ACCESS_WIFI_STATE
};

将以下内容添加到onCreate或您想要的位置:
try {
    if (
        ActivityCompat.checkSelfPermission(this, mPermission[0])
            != MockPackageManager.PERMISSION_GRANTED ||
        ActivityCompat.checkSelfPermission(this, mPermission[1])
            != MockPackageManager.PERMISSION_GRANTED ||
        ActivityCompat.checkSelfPermission(this, mPermission[2])
            != MockPackageManager.PERMISSION_GRANTED ||
        ActivityCompat.checkSelfPermission(this, mPermission[3])
            != MockPackageManager.PERMISSION_GRANTED
    ) {
        Log.e("TAGTAG", "DENIED");
        ActivityCompat.requestPermissions(
            this, mPermission, REQUEST_CODE_PERMISSION
        );

        // 'Will execute recursively if any of the permissions was not granted.
    } else {
        Log.e("TAGTAG", "GRANTED");
    }
} catch (Exception e) {
    e.printStackTrace();
}

不要忘记在AndroidManifest.xml中声明权限。

问题是“如何询问我的应用程序是否具有特定文件或文件夹URL的写入权限”。我不知道您的文本如何回答这个问题。 - k3b
我写信给你,让你改变权限数组中你可能需要的权限。请阅读它。不要期望别人为你编写代码。 - rei koleci

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接