0

I am trying to create a new File in SD Card for Android 5.0 and above. So first I am making the user grant the permission through SAF. This is how I am check if the selected Directory is SD Card or Not.

public static boolean wrong_directory_selected(Uri uri, Context con)
    {

        final File uri_path=new File(FileUtil.getFullPathFromTreeUri(uri,con));
        if(uri_path.getName().toLowerCase().equals(new File("SD CARD PATH").getName().toLowerCase()))
        {

            return false;
        }
        return  true;
    }

And then this is how I am Trying to Create a new File.

DocumentFile move = DocumentFile(new File("path)).createFile(mime,"name); // But I am getting java.lang.NullPointerException 

Below are the methods which I am using to get the DocumentFile for the Directory to which the file has to be Created.

public static DocumentFile DocumentFile(final File file)
{

    DocumentFile rootDocFile = DocumentFile.fromTreeUri(con, permission().getUri());

    String[] parts = (file.getPath()).split("\\/");

    for (int i = 3; i < parts.length; i++)
    {

        rootDocFile = rootDocFile.findFile(parts[i]);

    }
    return rootDocFile;
}

public static UriPermission permission()
{
    for (UriPermission permissionUri : con.getContentResolver().getPersistedUriPermissions())
    {
        final File uri_path = new File(FileUtil.getFullPathFromTreeUri(permissionUri.getUri(), con));

        if (uri_path.getName().toLowerCase().equals(new File("SD_CARD_PATH").getName().toLowerCase()))
        {
            return permissionUri;

        }

    }

    return null;
}

The code is working fine most of the time but sometime I am getting java.lang.NullPointerException.

Any Help would be Grateful.

EDIT: This is my FileUtil class

public final class FileUtil {


    private static final String PRIMARY_VOLUME_NAME = "primary";




    @Nullable
    public static String getFullPathFromTreeUri(@Nullable final Uri treeUri, Context con) 
    {
        if (treeUri == null) 
        {
            return null;
        }
        String volumePath = FileUtil.getVolumePath(FileUtil.getVolumeIdFromTreeUri(treeUri),con);
        if (volumePath == null)
        {
            return File.separator;
        }
        if (volumePath.endsWith(File.separator))
        {
            volumePath = volumePath.substring(0, volumePath.length() - 1);
        }

        String documentPath = FileUtil.getDocumentPathFromTreeUri(treeUri);
        if (documentPath.endsWith(File.separator)) 
        {
            documentPath = documentPath.substring(0, documentPath.length() - 1);
        }

        if (documentPath.length() > 0)
        {
            if (documentPath.startsWith(File.separator)) 
            {
                return volumePath + documentPath;
            }
            else {
                return volumePath + File.separator + documentPath;
            }
        }
        else
        {
            return volumePath;
        }
    }


    private static String getVolumePath(final String volumeId, Context con)
    {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) 
        {
            return null;
        }

        try {
            StorageManager mStorageManager =
                    (StorageManager) con.getSystemService(Context.STORAGE_SERVICE);

            Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");

            Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
            Method getUuid = storageVolumeClazz.getMethod("getUuid");
            Method getPath = storageVolumeClazz.getMethod("getPath");
            Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
            Object result = getVolumeList.invoke(mStorageManager);

            final int length = Array.getLength(result);
            for (int i = 0; i < length; i++) 
            {
                Object storageVolumeElement = Array.get(result, i);
                String uuid = (String) getUuid.invoke(storageVolumeElement);
                Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);

                // primary volume?
                if (primary && PRIMARY_VOLUME_NAME.equals(volumeId)) 
                {
                    return (String) getPath.invoke(storageVolumeElement);
                }

                // other volumes?
                if (uuid != null) 
                {
                    if (uuid.equals(volumeId)) 
                    {
                        return (String) getPath.invoke(storageVolumeElement);
                    }
                }
            }

            // not found.
            return null;
        }
        catch (Exception ex) 
        {
            return null;
        }
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static String getVolumeIdFromTreeUri(final Uri treeUri) 
    {
        final String docId = DocumentsContract.getTreeDocumentId(treeUri);
        final String[] split = docId.split(":");

        if (split.length > 0)
        {
            return split[0];
        }
        else
        {
            return null;
        }
    }


    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private static String getDocumentPathFromTreeUri(final Uri treeUri) 
    {
        final String docId = DocumentsContract.getTreeDocumentId(treeUri);
        final String[] split = docId.split(":");
        if ((split.length >= 2) && (split[1] != null))
        {
            return split[1];
        }
        else 
        {
            return File.separator;
        }
    }


}

EDIT 2 :

The Path in which the file has to be created is fine and I have also checked the Permission URI and even that is not null.

The Values are

The path where the file has to be created- /storage/external_SD

Permission Uri- content://com.android.externalstorage.documents/tree/6634-3765%3A

EDIT 3:

I am using this library to find the SD Card path.

Rahulrr2602
  • 701
  • 1
  • 13
  • 34
  • I have no idea what `FileUtil` is, other than it is buggy, since you cannot get a `File` from a `Uri` (except by creating a file and copying the content identified by the `Uri` into it, and I doubt that is what `FileUtil` is doing). – CommonsWare Jul 30 '17 at 19:13
  • @CommonsWare I have Edited the Question and added my `FileUtil` Class can you please have a look. – Rahulrr2602 Jul 30 '17 at 19:18
  • 2
    You are making a variety of assumptions about the implementation of Android that may change between Android OS versions, including changes made by device manufacturers and custom ROM developers. – CommonsWare Jul 30 '17 at 19:26
  • @CommonsWare Is there any Reliable way which could help me know that if the user has chosen right Directory i.e. SD Card Directory. Also I have tested the Code with three Different manufacture it seems to work fine with them. – Rahulrr2602 Jul 30 '17 at 19:29
  • 2
    "Is there any Reliable way which could help me know that if the user has chosen right Directory i.e. SD Card Directory" -- you should not care where the user chooses to store their content. If they want to use your app with Google Drive or some other storage provider, that is their decision to make, not yours. "Also I have tested the Code with three Different manufacture it seems to work fine with them" -- there are nearly 10,000 device models, from many more than three manufacturers. – CommonsWare Jul 30 '17 at 19:30
  • @CommonsWare I am taking permission for the whole SD Card and then making the user select the path where the file will be created. It is not possible for me to make the user choose the path using the SAF every time. I have one more query . What will happen to the permission if the user Changes its SD Card to a new one. – Rahulrr2602 Jul 30 '17 at 19:34
  • "I am taking permission for the whole SD Card and then making the user select the path where the file will be created" -- use `ACTION_CREATE_DOCUMENT` instead. You avoid all this awful code and make your app much more flexible by supporting any storage provider. "What will happen to the permission if the user Changes its SD Card to a new one" -- I have no idea, sorry. – CommonsWare Jul 30 '17 at 19:42
  • @CommonsWare Thank You very much for your help. Just one last doubt. There are apps like ES File Explorer using which users can Create File to Any path in SD Card, they ask for permission only once. And also they ask permission again if the SD card has been changed by the user. How do they manage to Keep a track of which SD card has been Granted the permission. – Rahulrr2602 Jul 30 '17 at 19:52
  • You would have to ask them. – CommonsWare Jul 30 '17 at 19:55
  • @CommonsWare Thank You very much. – Rahulrr2602 Jul 30 '17 at 19:57
  • 1
    @Rahulrr2602 "What will happen to the permission if the user Changes its SD Card to a new one" — if you are talking about the directory-specific permission, granted by picking a directory in file picker, that permission is attached to the tree Uri, which contains sd-card partion id. So that permission is effectively stapled to physical SD card and will be lost if user changes or re-formats the card. – user1643723 Jul 31 '17 at 02:10
  • @user1643723 Thank You very much. Will the permission be lost if the user selects the Main SD Card directory and then again changes its SD Card to a new one. Also can you please also help me with how to find out that the Directory chosen to grant permission is the main SD Card directory or not. – Rahulrr2602 Jul 31 '17 at 08:50

1 Answers1

1

Continue from this answer now that you have the DocumentFile (which is a directory to create a file inside it) through the loop just use myDocumentFile.createFile(...) to create a new file on your desired directory.

// creating the file
DocumentFile documentFileNewFile = documentFileGoal.createFile(myMimeType,
myNewFileName);

Then stream to it

outputStream = getContentResolver().openOutputStream(documentFileNewFile.getUri());
inputStream = new FileInputStream(myInputFile);

...
   if (outputStream != null) {
       byte[] buffer = new byte[1024];
       int read;
       while ((read = inputStream.read(buffer)) != -1) {
           outputStream.write(buffer, 0, read);
       }
      }
...
...
...
} finally {
         if (inputStream != null)
             inputStream.close();
         if (outputStream != null) {
             outputStream.flush();
             outputStream.close();
           }
         }  

Edite
Prevent findFile on a null DocumentFile by checking the value of rootDocFile on each loop. (happens when the user selects a wrong path instead of the sd-card)

for (int i = 3; i < parts.length; i++)
{
    if (rootDocFile != null) {
        rootDocFile = rootDocFile.findFile(parts[i]);
    }
}
Eftekhari
  • 1,001
  • 1
  • 19
  • 37
  • Please explain why you down vote. I will help as far as I could go :) – Eftekhari Jul 31 '17 at 08:45
  • I am getting `java.lang.NullPointerException` while creating a new File. I think that the `DocumentFile` method is returning null which means that I am wrong somewhere because of which the DocumentFile is null. – Rahulrr2602 Jul 31 '17 at 08:57
  • If you want to create a new file on the `sd-card` you need to provide your desired directory's path to the loop. This way you have a `DocumanetFile` that is a directory. Then create your file inside it. – Eftekhari Jul 31 '17 at 09:03
  • I am providing the path and also it is working fine most of the times but some times the `DocumentFile` returned by the `DocumentFile` method is null. – Rahulrr2602 Jul 31 '17 at 09:10
  • You should never end up having a null `DocumentFile` while you have to ask the user to provide you another `sd-card`'s path. Keep the provided `sd-card`'s path inside a list only when the loop found your rootDocFile successfully and then `setPersistableUriPermission` with the `TreeUri` you've got from the `onActivityResult`. – Eftekhari Jul 31 '17 at 10:15
  • And to find out what is really happening on some devices just use `FireBase` and get the values on the catch. See if something weird is happening about the `path` that the user is provided or maybe you should `synchronize` some of your busy methods related to the `AsyncTask` that is doing your job. – Eftekhari Jul 31 '17 at 10:19
  • I am pretty sure that the permission has been granted where the file has to be Created. And I am also checking if the permission has been granted for that particular SD Card or not. Can you please explain this "maybe you should synchronize some of your busy methods related to the AsyncTask that is doing your job" – Rahulrr2602 Jul 31 '17 at 10:27
  • Methods that are not `thread-safe` should be called synchronously if they are getting called out of the `main-thread`. Please provide the value of the file, permission().getUri(), rootDocFile before loop and rootDocFile after loop on the method you get the `DocumentFile` when you face a null `DocumentFile`. Then we have something to talk about. – Eftekhari Jul 31 '17 at 11:16
  • Thank You very much. Shall soon update you with these values. – Rahulrr2602 Jul 31 '17 at 12:10
  • Can you please have a look again. I have updated the question with values. Also these have been taken from an LG K4 phone. I am using a try catch to catch the error while creating the file. And when error occurred I copied the values to my data base. – Rahulrr2602 Aug 23 '17 at 13:06
  • @Rahulrr2602 Check the edit and let me know the result – Eftekhari Aug 24 '17 at 18:09
  • Thank for replying again. I am not letting the user select any other path other than SD Card. The User can only choose any folder in SD Card. – Rahulrr2602 Aug 24 '17 at 18:19
  • How do you make sure that the user selects the `sd-card`? – Eftekhari Aug 24 '17 at 19:07
  • I have edited the code and mentioned the library I am using to Find the SD Card path. I am starting the selection from SD Card path only. – Rahulrr2602 Aug 24 '17 at 19:22
  • Also the `DocumentFile` method sometimes returns null for an image which is present in SD Card. – Rahulrr2602 Aug 24 '17 at 19:23
  • That's an old library and I'm not sure that will work on Android versions bigger than 5. "Supported Versions: Android 2.3 to Android 5.0 This library is targeted for Android Developers (Not for end users)." – Eftekhari Aug 24 '17 at 19:55
  • OK. Will try some other method of finding out SD Card path. Also will check if that paths exists or not and update it to my Data Base. And get back with the values. – Rahulrr2602 Aug 24 '17 at 19:58
  • Can you please suggest some effective method of finding SD Card path. – Rahulrr2602 Aug 24 '17 at 19:59
  • Instead of that library just check the `rootDocFile` and if you get a null value, no matter what the reason is, just ask for the selection of the `sd-card` again. – Eftekhari Aug 24 '17 at 19:59
  • Thank You very much. OK. Will also try that. Can the error be caused because I am looping the `DocumentFile` method many times from an `Async Task`? Will soon get back with updated info. – Rahulrr2602 Aug 24 '17 at 20:02
  • The most probable way of getting a null value for the `rootDocFile` is when the user selects a wrong path as the expected `sd-card`. – Eftekhari Aug 24 '17 at 20:05
  • With my experience, `DocumentFile` is thread-safe (don't worry about the consistency). – Eftekhari Aug 24 '17 at 20:06
  • Once again Thank You. Will surely try your suggestions and will get back with the results soon. – Rahulrr2602 Aug 24 '17 at 20:10