How can I let users access the internal storage directory of my app?
Asked Answered
A

3

17

My app stores files in its internal storage directory (/Android/data/com.mycompany.myapp/files, as returned by getFilesDir()), and I would like to allow users to access those files directly from a file management app on their mobile device or the Android File Transfer desktop appplication.

The Storage Options developer guide says:

By default, files saved to the internal storage are private to your application and other applications cannot access them (nor can the user).

"By default" implies that I can change the default permissions to allow users to access these files, but I can't find any documentation of how to do that. Currently the com.mycompany.myapp directory is hidden when I browse the /Android/data directory with a file management app.

I'm currently saving file data like this:

String fileName = "myfile.jpg";
String filePath = getFilesDir() + File.separator + fileName;
FileOutputStream fileStream = new FileOutputStream(filePath);
fileData.writeTo(fileStream); // fileData is a ByteArrayOutputStream
fileStream.close();

I tried setting the permissions of the individual files to world-readable, but this didn't make the directory visible:

FileOutputStream fileStream = this.app.openFileOutput(fileName, Context.MODE_WORLD_READABLE);

I also checked the documentation for the AndroidManifest file and didn't see anything there. Does anyone know how I can do this?

Anethole answered 27/10, 2014 at 1:17 Comment(4)
I want to know the reason why do you want to store files in internal storage instead of external storage?Polivy
I would like the files to be tightly connected with the app, so that if the app is deleted the files are deleted with it. And in most cases users won't need to see the files, so keeping them in the com.mycompany.myapp directory seems cleanest. But in some cases it would be convenient for users to access these files directly. Particularly, adding a batch of files to the app might be easier as a drag-and-drop operation from their PC than it would be going through an app interface.Anethole
Internal storage can be accessed by the user if the user has root privileges.Polivy
Yes, but I don't want users to have to root their devices to access their files in this way. Other apps seem to be exposing the files to regular users and I would like to do that, too.Anethole
A
51

I took a closer look at the result of getFilesDir() vs getExternalFilesDir() and found that getFilesDir() returns /data/data/[packagename]/files while getExternalFilesDir() returns /Android/data/[packagename]/files. I thought the app files I was browsing in /Android/data were the internal storage directories, but now I see that those are actually the external storage directories.

If the internal storage directories are indeed never available to regular users, I wish the documentation said that instead of saying they are not available "by default." To me at least, saying "by default" implies that the default behavior can be changed.

Anyway, I tested and confirmed that if I delete my app, files saved to the getExternalFilesDir() are deleted automatically. So that meets my need for a storage location that is clearly connected with the app (uses an app-specific directory name and is deleted with the app) but is accessible to users for occasional manual file management.

Here's a comparison that might be helpful to someone else reading this:

  • getFilesDir() - creates an app-specific directory; hidden from users; deleted with the app
  • getExternalFilesDir() - creates an app-specific directory; accessible to users; deleted with the app
  • getExternalStoragePublicDirectory() - uses a shared directory (e.g., Music); accessible to users; remains when the app is deleted
Anethole answered 27/10, 2014 at 17:51 Comment(1)
Hey... Do you know why we can't access those files from Microsoft Windows when we connect the phone to USB port ? For getExternalStoragePublicDirectory(), I saved the picture in Environment.DIRECTORY_DCIM Camera folder .... but pictures taken from my app are not visible in Windows (even if I activate hidden files)Falter
T
12

I think you are getting confused by the developers documentation, I can't blame you, it's not the best documentation I've ever read. Anyway, Android provides two ways of saving files to the file system:

  1. Using the Internal Storage
  2. Using the External Storage

The Internal Storage is ALWAYS available and is ONLY accessible to your app, no other app in the system can access the files your app saved to this partition.

Now, I think what you need is the External Storage because it is "world-readable" and can be accessed by anyone and any any apps. The downside is that it may not be available since the user could mount it on a USB device - which can be removed by the user at anytime.

You shouldn't use neither MODE_WORLD_WRITEABLE nor MODE_WORLD_READABLE as per the documentation states, because it is very dangerous. Furthermore, these constants have been deprecated since API Level 17

Recommendation

If you need to make your files public, then save them to the External Storage. You will need to declare a permission in your manifest file to avoid your app from crashing everytime it access the External Storage...

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

because the External Storage might not be available you will need to determine the state of it before you perform any operation, otherwise your app will crash...

public enum StorageState{
  NOT_AVAILABLE, WRITEABLE, READ_ONLY
}

public StorageState getExternalStorageState() {
    StorageState result = StorageState.NOT_AVAILABLE;
    String state = Environment.getExternalStorageState();

    if (Environment.MEDIA_MOUNTED.equals(state)) {
        return StorageState.WRITEABLE;
    }
    else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
        return StorageState.READ_ONLY;
    }

    return result;
}

There is more information on the documentation and things that you should be aware of. For example, you can give your app ownership of these files so that when your app is uninstalled the system can automatically delete these files. For more info please refer to the documentation on the Android Developers Site

Ted answered 27/10, 2014 at 1:57 Comment(5)
The internal/external designations are definitely confusing ("external" storage on the device's built-in memory vs. "secondary external" storage on a removable memory card), but I think I get that. The internal storage is at /Android/data/com.mycompany.myapp, right? It seems that other apps are making this visible to users. For example, I have the Kindle app installed, and I can browse the /Android/data/com.amazon.kindle/files directory from the ES File Explorer app or from Android File Transfer on my Mac.Anethole
@Anethole mate, I think you are getting even more confused. The Kindle files you are viewing in your folder are NOT private they are "world readable". Furthermore, you don't even need to use something like Android File Transfer to read these files in a Windows machine if you connect it as MTP. So, basically, you don't really get it, otherwise you wouldn't make that comment...MODE_PRIVATE means ONLY visible to the owning app, simply. Keep browsing over your apps internal storage you'll realize that many of them are emptyTed
@Anethole what exactly you still don't understand?Ted
My app is saving its files in data/com.mycompany.myapp/files, which does not appear to a regular (not rooted) user browsing the file system. Some other apps' directories do appear and users can browse their files. How can I make my app work that way?Anethole
I just tried saving to the getExternalFilesDir() location instead of the getFilesDir() location, and that created a browsable data/com.mycompany.myapp/files directory. So it looks like those directories are the external storage of various apps, not the internal storage. Is that what you were trying to tell me?Anethole
H
0

add this to manifest

then add this in your activity:

private static final String RequieredPermission = Manifest.permission.READ_EXTERNAL_STORAGE;

then call HandlePermission() where your require to check for permission, usually in Oncreate method of an activity. These functions are then required:

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Log.d("amir", "onRequestPermissionsResult: called");
        switch (requestCode) {
            case REQUEST_PERMISSION_READING_STATE:
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(MainActivity.this, "Permission Granted!", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(MainActivity.this, "Permission Denied!", Toast.LENGTH_SHORT).show();
                }
        }

    }

    private void HandlePermission() {
        int permissionCheck = ContextCompat.checkSelfPermission(
                this, RequieredPermission);
        if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    RequieredPermission)) {
                showExplanationaboutPermission("Permission Needed", "Rationale", RequieredPermission, REQUEST_PERMISSION_READING_STATE);
            } else {
                requestPermission(RequieredPermission, REQUEST_PERMISSION_READING_STATE);
            }
        } else {
            Toast.makeText(MainActivity.this, "Permission (already) Granted!", Toast.LENGTH_SHORT).show();
        }
    }

    private void showExplanationaboutPermission(String title,
                                                String message,
                                                final String permission,
                                                final int permissionRequestCode) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle(title)
                .setMessage(message)
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        requestPermission(permission, permissionRequestCode);
                    }
                });
        builder.create().show();
    }

    private void requestPermission(String permissionName, int permissionRequestCode) {
        ActivityCompat.requestPermissions(this,
                new String[]{permissionName}, permissionRequestCode);
    }
Hubbell answered 6/7, 2019 at 23:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.