3

I want to save an image taken with the camera from my app, directly to a usb stick plugged in my phone. Is there a way to do it without asking the user to choose the directory from SAF ? I want to use scoped storage as I'm targeting Android 11 and onwards.

There is no sd card connected to the devices.

I can store files on the usb stick on Android 10 with this code but not on Android 11

// I pass the image taken from the camera by parameter
    private void legacyToUsb(File image){
        File[] externalStorageVolumes = ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
        String path = null;
        for (File externalDir : externalStorageVolumes) {
            Log.d(TAG, "Found dir at : " + externalDir);
            path = externalDir.getPath();
        }
        if (path == null) {
            showToast("Failed to found mounted device");
            return;
        }
        String fileName = mDateFormat.format(System.currentTimeMillis()) + ".jpg";
        File destFile = new File(path, fileName);
        showToast("Going to save data to " + destFile.getPath());
        if (destFile == null)
            showToast("Failed to open file");
        FileOutputStream os;
        String msg = "Successfully saved to " + destFile.getPath();
        try{
            byte[] imageData = getBytesFromFile(image);
            os = new FileOutputStream(destFile);
            os.write(imageData);
        } catch (IOException e) {
            e.printStackTrace();
            msg = "Failed to save file : " + e.getMessage();
        }
        showToast(msg);
    }

However, on Android 11 that doesn't work (I cannot create the file inside the usb stick) because getExternalFilesDirs() doesn't give me the path to the usb otg.

ContextCompat.getExternalFilesDirs(<context>) returns:

Android 10

Found dir at : /storage/emulated/10/Android/data/com.example.MyApp/files
Found dir at : /storage/4406-15E5/Android/data/com.example.MyApp/files //I'm using this one

Android 11

Found dir at : /storage/emulated/0/Android/data/com.example.MyApp/files 

As far as I understand, scoped storage removed the possibility to access to the removable storage through those functions. So I'm trying this other way

    private void scopedToUsb(File image){
        ContentResolver resolver = getContentResolver();
        String fileName = mDateFormat.format(System.currentTimeMillis());

        ContentValues fileDetails = new ContentValues();
        fileDetails.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
        fileDetails.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        fileDetails.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
        fileDetails.put(MediaStore.Images.Media.IS_PENDING, 1);

        Set<String> volumeNames = MediaStore.getExternalVolumeNames(getApplicationContext());
        String storageVolume = null;
        // Get the first volume found here, it returns the hex with which the usb stick is identified
        // in my case 4406-15e5
        for (String volumeName : volumeNames){
            storageVolume = volumeName;
            Log.d(TAG, "Found volumeName : "+ volumeName);
            break;
        }
        // I have tried here MediaStore.VOLUME_EXTERNAL and MediaStore.VOLUME_EXTERNAL_PRIMARY
        // They both point to the same directory not in the usb stick
        Uri collection = MediaStore.Images.Media.getContentUri(storageVolume);
        Uri imageUri =  resolver.insert(collection, fileDetails);
        if(imageUri == null) {
            showToast("Failed to insert image row to ContentResolver");
            return;
        }
        Log.d(TAG, "Inserted, going to create ouput stream");
        OutputStream out = null;
        try {
            out = resolver.openOutputStream(imageUri);
            Bitmap bm = BitmapFactory.decodeFile(image.getAbsolutePath());
            bm.compress(Bitmap.CompressFormat.JPEG, 100, out);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
            showToast("Failed to sqve picture to Usb");
            return;
        }
        showToast("Successfully saved to " + collection.getPath());
    }

but I'm receiving an exception in this line

Uri imageUri =  resolver.insert(collection, fileDetails);

exception

java.lang.IllegalStateException: Failed to create directory: /mnt/media_rw/4406-15E5/Pictures/.pending-1621349186-2021-05-11-16-46-26-671.jpg
        at android.os.Parcel.createExceptionOrNull(Parcel.java:2384)
        at android.os.Parcel.createException(Parcel.java:2360)
        at android.os.Parcel.readException(Parcel.java:2343)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:190)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
        at android.content.ContentProviderProxy.insert(ContentProviderNative.java:549)
        at android.content.ContentResolver.insert(ContentResolver.java:2177)
        at android.content.ContentResolver.insert(ContentResolver.java:2138)
        at com.example.MyApp.MainActivity.scopedToUsb(MainActivity.java:368)
        at com.example.MyApp.MainActivity.saveToUsb(MainActivity.java:294)
        at com.example.MyApp.MainActivity.access$000(MainActivity.java:72)
        at com.example.MyApp.MainActivity$1.onImageSaved(MainActivity.java:273)
        at androidx.camera.core.ImageCapture$2.onImageSaved(ImageCapture.java:844)
        at androidx.camera.core.ImageSaver.lambda$postSuccess$1$ImageSaver(ImageSaver.java:308)
        at androidx.camera.core.-$$Lambda$ImageSaver$29vxg6qyjKwPRXgWrIwgYVInWKE.run(Unknown Source:4)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

Is it possible ? Am I missing any permission? should I mandatory use Storage Acess Framework? Thanks

afvmil
  • 362
  • 3
  • 11
  • `on Android 11 this doesn't work as I cannot get the file inside the usb ` What do you mean? I cannot follow you. – blackapps May 11 '21 at 18:03
  • Sorry. I ment `On Android 10, I can do this` Why cant you do that on 11 i wanted to ask . You did not tell – blackapps May 11 '21 at 20:21
  • when I use my `legacyToUsb()` function with an Android 11 phone it stores the image at `/storage/emulated/0/Android/data/com.example.MyApp/files` which does not correspond to the USB stick. I think it was not clear on the question so I modified the phrase a bit. Thanks ! – afvmil May 12 '21 at 08:23
  • You still gave no explanation for `However, on Android 11 that doesn't work` . So the code above that sentence does not work on Android 11. Ok. But why not? You still did not tell. Now i ask it for the third time: Wy does that code not work on Android 11? And why does it work on Android 10? – blackapps May 12 '21 at 08:26
  • `I cannot get the file inside the usb ` A very unclear sentence. Do you mean: `I cannot create a file on the usb-stick.`? Please be clear. – blackapps May 12 '21 at 08:29
  • `Apparently ContextCompat.getExternalFilesDirs() returns the path to /storage/emulated/0/Android/data/com.example.MyApp/files which is located internally on the phone ` Well if it does that on Android 10 and 11 then what is the difference? And that function returns an array. So please tell something about that array for Android 10 and about that array for Android11. – blackapps May 12 '21 at 08:32
  • `And that function returns an array. So please tell something about that array for Android 10 and about that array for Android 11.` What is the difference? And do the used devices both have a micro sd card inserted? – blackapps May 12 '21 at 08:33
  • `for (File externalDir : externalStorageVolumes) { Log.d(TAG, "Found dir at : " + externalDir); }` Please tell what is logged on an Android 10 device. After that tell what is logged on an Android 11 device. Put all in your post there where you explain that it does not work for Android 11. – blackapps May 12 '21 at 08:37
  • You stilll did not tell if your Android devices have micro sd cards inserted. (Your devices! I am not talkiing about your usb stick as you did). – blackapps May 12 '21 at 09:11
  • But... Now you you had a good look at the logs you could tell us why your first approach does not work for Android 11. – blackapps May 12 '21 at 09:12
  • `However, on Android 11 that doesn't work as I cannot see the file inside the usb stick after calling the legacyToUsb() function. ` But... that is not the reason. Better tell the real reason. I'm asking that since my first comment. – blackapps May 12 '21 at 09:14
  • `getExternalFilesDirs() doesn't give me the path to the removable storage.` Well.. yes and no. It would give the path to a removable micro sd card (mostly as second item) if you had inserted one in your device. On Android 10 it would often also give path to a usb otg stick. But on Android 11 the latter not anymore. And that is your problem and has little to do with you -wrong- code but only with how getExternalFilesDir() behaves. And that you should have reported from the first sentence of your post. – blackapps May 12 '21 at 09:57
  • ok thank you, I couldn't see it before. So my problem is that `getExternalFilesDirs()` does not return the path to the usb otg on it, which is the same you had [here](https://stackoverflow.com/a/66351432/13154650). you propose using SAF, but is there any way to access the usb stick without it? – afvmil May 12 '21 at 10:19

0 Answers0