15

I have a photo editing android app that users can choose the output directory of the the result photos. Problem is Google made a change on sdcard write permission with the KITKAT version and devices with Android KITKAT version won't allow apps to write secondary sdcards. Now I need to check if the choosen directory by user has granted the permission and won't throw EACCES error. I am already checking canRead and canWrite but these won't help. Could you please tell me how can I check if the choosen directory won't throw EACCES. My only solution is trying to write a file in a try catch, however I am hoping there is better way to do it.

[update k3b 2016-09-19]

i tried this on my android-4.4 but without success

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;
}
esote
  • 831
  • 12
  • 25
user65721
  • 2,833
  • 3
  • 19
  • 28
  • @k3b: A simpler, more reliable solution is to stick to known roots. The only way you can get a directory that you cannot use is if you try traversing above known-safe directories (e.g., you try working from `/` instead of sticking to `Environment.getExternalStorageDirectory()`). If you want to support removable storage, use the Storage Access Framework with `ACTION_OPEN_DOCUMENT_TREE` on Android 5.0+ (bonus: you get support for other document providers "for free"). – CommonsWare Sep 19 '16 at 22:17
  • After offering a bount there where no new answers so i assume that there is currently no solution :-( – k3b Oct 06 '16 at 12:14

2 Answers2

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

And some edits to this class

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;
    }
}
JumperBot_
  • 551
  • 1
  • 6
  • 19
user486312
  • 116
  • 1
  • 8
  • 4
    This is not a very good solution. First, launching a distinct process is going to be far more inefficient than simply trying the desired operation and catching failure. Next, given that the problem is that the system is falsely reporting the file as accessible in ways it is not, it is not entirely clear that the output of `ls` is going to be any more accurate, and not also report the same incorrect information. – Chris Stratton Aug 08 '14 at 15:57
1

Add the permission(s) you need to the array:

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

Add this to onCreate or where you want it to be:

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

Don't forget to declare the permissions in AndroidManifest.xml.

Saeed Zhiany
  • 2,051
  • 9
  • 30
  • 41
rei koleci
  • 68
  • 1
  • 7
  • The question was "how to ask if my app has write permissions for a certain file or folder url". I dont so how your text answers this. – k3b Sep 23 '16 at 08:48
  • I wrote you to change in the array of permissions what permissions you might need. Read it. Dont except sm to write the code for you. – rei koleci Sep 23 '16 at 08:51