16

I want to get the list of all Storage Devices which are connected to the Android device.

For eg- Internal Storage(Storage in which all the folders such as Downloads, DCIM etc are present), SD Card and OTG device.

I Know there are a lot of StackOverflow posts which discuss this topic but none of them could serve my purpose as stated above.

I am able to get the Internal Storage by calling Environment.getExternalStorageDirectory().getPath() which return the path to the Internal Storage.

Any help on this would be really grateful as there is no standard AFAIK using which the list of all the connected Storage devices can be retrieved.

Also, many solutions do not work on different devices and Android version.

Rahulrr2602
  • 701
  • 1
  • 13
  • 34

4 Answers4

5

You can create one class EnvironmentSDCardCheck

package com.example.storagecheck;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.os.EnvironmentCompat;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class EnvironmentSDCardCheck {
    private static final String TAG = "EnvironmentSDCardCheck";

    public final static String TYPE_PRIMARY = "primär";
    public final static String TYPE_INTERNAL = "intern";
    public final static String TYPE_SD = "MicroSD";
    public final static String TYPE_USB = "USB";
    public final static String TYPE_UNKNOWN = "unbekannt";

    public final static String WRITE_NONE = "none";
    public final static String WRITE_READONLY = "readonly";
    public final static String WRITE_APPONLY = "apponly";
    public final static String WRITE_FULL = "readwrite";

    private static Device[] devices, externalstorage, storage;
    private static BroadcastReceiver receiver;
    private static boolean useReceiver = true;
    private static String userDir;

    public static Device[] getDevices(Context context) {
        if (devices == null) initDevices(context);
        return devices;
    }

    public static Device[] getExternalStorage(Context context) {
        if (devices == null) initDevices(context);
        return externalstorage;
    }

    public static Device[] getStorage(Context context) {
        if (devices == null) initDevices(context);
        return storage;
    }

    public static IntentFilter getRescanIntentFilter() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_MEDIA_BAD_REMOVAL); 
        filter.addAction(Intent.ACTION_MEDIA_MOUNTED); 
        filter.addAction(Intent.ACTION_MEDIA_REMOVED); 
        filter.addAction(Intent.ACTION_MEDIA_SHARED); 
        filter.addDataScheme("file");
        return filter;
    }

    public static void setUseReceiver(Context context, boolean use) {
        if (use && receiver == null) {
            receiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    Log.i(TAG, "Storage " + intent.getAction() + "-" + intent.getData());
                    initDevices(context);
                }
            };
            context.registerReceiver(receiver, getRescanIntentFilter());
        } else if (!use && receiver != null) {
            context.unregisterReceiver(receiver);
            receiver = null;
        }
        useReceiver = use;
    }

    public static void initDevices(Context context) {
        if (userDir == null) userDir = "/Android/data/" + context.getPackageName();
        setUseReceiver(context, useReceiver);
        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        Class c = sm.getClass();
        Object[] vols;
        try {
            Method m = c.getMethod("getVolumeList", null);
            vols = (Object[]) m.invoke(sm, null); // android.os.Storage.StorageVolume
            Device[] temp = new Device[vols.length];
            for (int i = 0; i < vols.length; i++) temp[i] = new Device(vols[i]);
            Device primary = null;
            for (Device d : temp) if (d.mPrimary) primary = d;
            if (primary == null) for (Device d : temp)
                if (!d.mRemovable) {
                    d.mPrimary = true;
                    primary = d;
                    break;
                }
            if (primary == null) {
                primary = temp[0];
                primary.mPrimary = true;
            }

            File[] files = ContextCompat.getExternalFilesDirs(context, null);
            File[] caches = ContextCompat.getExternalCacheDirs(context);
            for (Device d : temp) {
                if (files != null) for (File f : files)
                    if (f != null && f.getAbsolutePath().startsWith(d.getAbsolutePath()))
                        d.mFiles = f;
                if (caches != null) for (File f : caches)
                    if (f != null && f.getAbsolutePath().startsWith(d.getAbsolutePath()))
                        d.mCache = f;
            }

            ArrayList<Device> tempDev = new ArrayList<Device>(10);
            ArrayList<Device> tempStor = new ArrayList<Device>(10);
            ArrayList<Device> tempExt = new ArrayList<Device>(10);
            for (Device d : temp) {
                tempDev.add(d);
                if (d.isAvailable()) {
                    tempExt.add(d);
                    tempStor.add(d);
                }
            }

            Device internal = new Device(context);
            tempStor.add(0, internal); // bei Storage-Alternativen immer
            if (!primary.mEmulated) tempDev.add(0, internal); // bei Devices nur wenn zusätzlich

            devices = tempDev.toArray(new Device[tempDev.size()]);
            storage = tempStor.toArray(new Device[tempStor.size()]);
            externalstorage = tempExt.toArray(new Device[tempExt.size()]);
        } catch (Exception e) {
            // Fallback auf normale Android-Funktionen
        }

    }

    public static class Device extends File {
        String mUserLabel, mUuid, mState, mWriteState, mType;
        boolean mPrimary, mRemovable, mEmulated, mAllowMassStorage;
        long mMaxFileSize;
        File mFiles, mCache;

        Device(Context context) {
            super(Environment.getDataDirectory().getAbsolutePath());
            mState = Environment.MEDIA_MOUNTED;
            mFiles = context.getFilesDir();
            mCache = context.getCacheDir();
            mType = TYPE_INTERNAL;
            mWriteState = WRITE_APPONLY;
        }

        @SuppressWarnings("NullArgumentToVariableArgMethod")
        Device(Object storage) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            super((String) storage.getClass().getMethod("getPath", null).invoke(storage, null));
            for (Method m : storage.getClass().getMethods()) {
                if (m.getName().equals("getUserLabel") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
                    mUserLabel = (String) m.invoke(storage, null); // ab Android 4.4
                if (m.getName().equals("getUuid") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
                    mUuid = (String) m.invoke(storage, null); // ab Android 4.4
                if (m.getName().equals("getState") && m.getParameterTypes().length == 0 && m.getReturnType() == String.class)
                    mState = (String) m.invoke(storage, null); // ab Android 4.4
                if (m.getName().equals("isRemovable") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
                    mRemovable = (Boolean) m.invoke(storage, null); // ab Android 4.0
                if (m.getName().equals("isPrimary") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
                    mPrimary = (Boolean) m.invoke(storage, null); // ab Android 4.2
                if (m.getName().equals("isEmulated") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
                    mEmulated = (Boolean) m.invoke(storage, null); // ab Android 4.0
                if (m.getName().equals("allowMassStorage") && m.getParameterTypes().length == 0 && m.getReturnType() == boolean.class)
                    mAllowMassStorage = (Boolean) m.invoke(storage, null); // ab Android 4.0
                if (m.getName().equals("getMaxFileSize") && m.getParameterTypes().length == 0 && m.getReturnType() == long.class)
                    mMaxFileSize = (Long) m.invoke(storage, null); // ab Android 4.0
                // getDescription (ab 4.1 mit context) liefert keine sinnvollen Werte
                // getPathFile (ab 4.2) liefert keine sinnvollen Werte
                // getMtpReserveSpace (ab 4.0) für diese Zwecke unwichtig
                // getStorageId (ab 4.0) für diese Zwecke unwichtig
            }
            if (mState == null) mState = getState();

            if (mPrimary)
                mType = TYPE_PRIMARY;
            else {
                String n = getAbsolutePath().toLowerCase();
                if (n.indexOf("sd") > 0)
                    mType = TYPE_SD;
                else if (n.indexOf("usb") > 0)
                    mType = TYPE_USB;
                else
                    mType = TYPE_UNKNOWN + " " + getAbsolutePath();
            }
        }

        public String getType() {
            return mType;
        }

        public String getAccess() {
            if (mWriteState == null) {
                try {
                    mWriteState = WRITE_NONE;
                    File[] root = listFiles();
                    if (root == null || root.length == 0)
                        throw new IOException("root empty/unreadable");
                    mWriteState = WRITE_READONLY;
                    File t = File.createTempFile("jow", null, getFilesDir());
                    //noinspection ResultOfMethodCallIgnored
                    t.delete();
                    mWriteState = WRITE_APPONLY;
                    t = File.createTempFile("jow", null, this);
                    //noinspection ResultOfMethodCallIgnored
                    t.delete();
                    mWriteState = WRITE_FULL;
                } catch (IOException ignore) {
                    Log.v(TAG, "test " + getAbsolutePath() + " ->" + mWriteState + "<- " + ignore.getMessage());
                }
            }
            return mWriteState;
        }

        public boolean isAvailable() {
            String s = getState();
            return (
                    Environment.MEDIA_MOUNTED.equals(s) ||
                            Environment.MEDIA_MOUNTED_READ_ONLY.equals(s)
            );
            // MEDIA_SHARED: als USB freigegeben; bitte Handy auf MTP umstellen
        }

        public String getState() {
            if (mRemovable || mState == null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                    // Android 5.0? Da gibts was neues
                    mState = Environment.getExternalStorageState(this);
                else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
                    // Android 4.4? Dann dort nachfragen
                    mState = Environment.getStorageState(this);
                else if (canRead() && getTotalSpace() > 0)
                    // lesbar und Größe vorhanden => gibt es
                    mState = Environment.MEDIA_MOUNTED;
                else if (mState == null || Environment.MEDIA_MOUNTED.equals(mState))
                    // nicht lesbar, keine Größe aber noch MOUNTED || oder ungesetzt => UNKNOWN
                    mState = EnvironmentCompat.MEDIA_UNKNOWN;
            }
            return mState;
        }

        public File getFilesDir() {
            if (mFiles == null) {
                mFiles = new File(this, userDir + "/files");
                if (!mFiles.isDirectory())
                    //noinspection ResultOfMethodCallIgnored
                    mFiles.mkdirs();
            }
            return mFiles;
        }

        public File getCacheDir() {
            if (mCache == null) {
                mCache = new File(this, userDir + "/cache");
                if (!mCache.isDirectory())
                    //noinspection ResultOfMethodCallIgnored
                    mCache.mkdirs();
            }
            return mCache;
        }

        public boolean isPrimary() {
            return mPrimary;
        }

        public boolean isRemovable() {
            return mRemovable;
        }
        public boolean isEmulated() {
            return mEmulated;
        }

        public boolean isAllowMassStorage() {
            return mAllowMassStorage;
        }

        public long getMaxFileSize() {
            return mMaxFileSize;
        }

        public String getUserLabel() {
            return mUserLabel;
        }

        public String getUuid() {
            return mUuid;
        }
    }
}

and then you can use it to check sd card or Usb or unknown is presently connected or not with device

This way you can get the connected sd card, usb, etc.

private boolean checkSdCardPermission() {
    boolean flag = false;
    try {
        EnvironmentSDCard.Device[] devices = EnvironmentSDCard.getExternalStorage(MainActivity.this);
        for (EnvironmentSDCard.Device d : devices) {
            if (d.getType().equals(EnvironmentSDCard.TYPE_SD) || d.getType().contains(EnvironmentSDCard.TYPE_UNKNOWN) || d.getType().contains(EnvironmentSDCard.TYPE_USB)) {
                flag = d.isAvailable();
                if (flag)
                    break;
            }
        }
    } catch (Exception e) {
    }
    return flag;
}
Amjad Khan
  • 1,309
  • 15
  • 32
  • Thanks will try and get back. – Rahulrr2602 Mar 30 '18 at 06:43
  • That's a lot of reflection calls… If device vendor does not properly register a storage disk in `Environment`, they are unlikely to register it in StorageManager either, so I don't think, that this method is productive. Do you have examples of devices, when it actually made differences on older Android versions? – user1643723 Mar 30 '18 at 07:43
  • Thanks for the answer. Your answer works perfectly. Will wait for some other answers, if no answer works will accept it as the correct answer and award the bounty. – Rahulrr2602 Mar 30 '18 at 16:17
  • Okay, No Problem you may try the different solution also :) – Amjad Khan Mar 31 '18 at 03:43
  • Thanks. Have awarded the bounty and accepted your answer as the correct answer. Thanks once again. – Rahulrr2602 Mar 31 '18 at 11:07
2

I have had some luck with

ContextCompat.getExternalFilesDirs

This allows the application folders on external drives to be found. I haven't yet found a working better solution than this.

In my use case I am using Environment.DIRECTORY_MOVIES But if you need, there are other definitions, including the generic DIRECTORY_DOCUMENTS

mksteve
  • 12,614
  • 3
  • 28
  • 50
  • Thanks for the answer. What should I pass in the place of second argument `ContextCompat.getExternalFilesDir(this," ? ")` – Rahulrr2602 Mar 27 '18 at 10:50
1

Since API level 9 there is android.os.storage.StorageManager. Call getStorageVolumes() (available since API level 24) to get a list of storage volumes. As the doc puts it:

Return the list of shared/external storage volumes available to the current user. This includes both the primary shared storage device and any attached external volumes including SD cards and USB drives.

The result is List<StorageVolume>. Now, take a look at android.os.storage.StorageVolume:

Information about a shared/external storage volume for a specific user.

You can for example get a user-visible description of the volume by calling getDescription(). See createAccessIntent() how to get access.

  • Is this method available to older Android versions publicly? – Rahulrr2602 Mar 30 '18 at 06:40
  • API level 9 means Android 2.3 (Gingerbread), which was released December 6, 2010 (more than seven years ago) –  Mar 30 '18 at 07:17
  • 1
    StorageManager was introduced in API 9, but `getStorageVolumes` did not become public API until API 24. In addition, it does not list anything, that haven't been specifically listed by device vendor. In other words, if your device does not list some storage location in public API, you are back to square one. – user1643723 Mar 30 '18 at 07:41
1

This is an addition to @Sagar's answer about getting mounts from /proc. Note the use of /proc/self/mountinfo instead of /proc/mountinfo or /proc/mounts. You can read more about format of /proc/self/mountinfo in man 5 procfs. While the following code technically parses files, it is safe to run on the main thread (because /proc is in-memory filesystem).

private static final int SANE_SIZE_LIMIT = 200 * 1024 * 1024;

// some hashmap for mapping long values to objects
// personally I am using HPPC maps, the HashMap class is for simplicity
public final HashMap<String> mountMap = new HashMap<>();

public void parse() {
    mountMap.clear();

    CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();

    parseMounts(decoder, true);
}

private int measure(FileChannel fc) throws IOException {
    final ByteBuffer buffer = ByteBuffer.allocate(1024 * 4);

    int totalRead = 0, lastRead;

    do {
        buffer.clear();

        lastRead = fc.read(buffer);

        totalRead += lastRead;

        if (totalRead > SANE_SIZE_LIMIT) {
            throw new IOException("/proc/ file appears to be too big!!");
        }
    } while (lastRead != -1);

    fc.position(0);

    return totalRead;
}

private void parseMounts(CharsetDecoder d, boolean force) {
  File file = new File("/proc/self/mountinfo");

  int mode = ParcelFileDescriptor.MODE_READ_ONLY;

  try (ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, mode));
    FileChannel fc = new FileInputStream(pfd.getFileDescriptor()).getChannel()) {

    // Measure size of file before reading from it.
    // Virtual files in /proc/ appear to be zero-sized (because
    // their contents are dynamic), but we want to attempt
    // reading it in single read() call to avoid inconsistencies
    final int totalRead = measure(fc);

    try (FileInputStream fis = new FileInputStream(pfd.getFileDescriptor());
         Reader r = Channels.newReader(fis.getChannel(), d, totalRead);
         Scanner scanner = new Scanner(r)) {
      while (scanner.hasNextLine()) {
        scanner.nextInt();
        scanner.nextInt();

        final String[] mm = scanner.next().split(":");

        final int major = Integer.parseInt(mm[0]);
        final int minor = Integer.parseInt(mm[1]);

        final long dev_t = makedev(major, minor);

        final String source = scanner.next();

        // ignore bind-mounts for now
        if ("/".equals(source)) {
          final String location = scanner.next();

          // skip optional parts
          scanner.skip("(.+ -)");

          // type of file system (such as ext4)
          // most useful filesystems can be distinguished by type
          // but "fuse" is problematic (because most Android
          // distributions implement dynamic permissions on
          // external SD card via custom FUSE filesystem).
          // To make matters worse, that SD card FUSE filesystem is
          // often mounted in several places at once. The code below
          // will throw away duplicate mounts by placing them in
          // HashMap, keyed by uniqie filesystem type ID,
          // but you can do it more cleverly be checking whether
          // a mountpoint directory is accessible (via File#list).
          // You can throw away rest of useless filesystems (such as
          // /mnt/secure/asec) by permission checks and blacklisting
          // well-known paths.
          final String fsType = scanner.next().intern();

          final String subject = scanner.next().intern();

          String created = location + subject + fsType;

          String prev = mountMap.put(dev_t, created);

          if (prev != null) {
            created.next = prev;
          }
        }

        scanner.nextLine();
      }

      return;
    } catch (NumberFormatException | NoSuchElementException nse) {
      // oops.. either a new row type was introduced (not a big deal)
      // or the file changed below our feet (because someone has mounted
      // something). Let's retry once more
      parseMounts(d, false);
    } catch (IOException e) {
        throw new WrappedIOException(e);
    }
}

You can find more useful information (such as common path to useless filesystem), in this answer. Note, that formats of /proc/mounts and /proc/mountinfo are different, later was introduced after former to improve upon it's format without breaking backwards compatibility.

The code above is not golden bullet — it does not really tell you anything about individual filesystems, just their paths and filesystem name. You can be reasonable confident, that "vfat" and "ext4" are useful filesystems, and "procfs" is useless, but something like "fuse" is going to remain mysterious. You can augment output of code above by using android.os.storage.StorageManager to get more user-friendly filesystem names (like "SD Card") when they are available (match by mount paths). You can also use StatFs to obtain available free space on partition — useless virtual filesystems typically will return zero free space and zero available space when queried. Finally, if you are so inclined, you can consider filesystem mount options when deciding whether to show filesystem to user. E.g. ro vs rw, — read-only filesystem mounts typically will be a lot less useful for your users.


When I explain this method to people, they are often concerned with it's robustness… Will it work on some random junkphone? Will it remain available in future OS versions? Here is my take on it: this method is still better than many reflection-based advises, — in the worst case, reading from /proc/ file will return you IOException. It will not crash your app or result in unpredictable behavior like some reflection-based hacks.

/proc filesystem is official Linux API, maintained by Linux kernel developers. It is not possible to remove it by specifying different kernel build options (e.g. it is a mandatory part of OS kernel). It has been available for many years and retains better backwards compatibility than most Android APIs. In particular /proc/self/mountinfo was created over 10 years ago and will be available in most existing Android versions except most ancient.

Android developers do not officially support Linux-specific APIs. But they don't go out of their way to break them either. Some of recent SELinux changes in post-Lollipop Android have restricted access to some files in /proc/, — because they allowed applications to covertly spy on other applications. Those changes specifically kept /proc/self accessible, because /proc/self is designed to expose only applications own information (including information about file systems, available to the application).

If Google ever transitions from Linux to Fuchensa or some other homegrown BSD fork, the /proc/ and other Linux-specifc APIs will probably break. Do I care? Not really.

user1643723
  • 4,109
  • 1
  • 24
  • 48
  • Tried your method but I am getting some errors in Android Studio. Some are the `Mount` class is not found, `throwawayBuffer.get()` is shown as error, `pfd.getFileDescriptor()` is shown as error, `scanner.hasNextLine()` is shown as error, etc errors are shown. Can you please have a look and help. – Rahulrr2602 Mar 30 '18 at 16:05
  • @Rahulrr2602 I have removed references to `throwawayBuffer` and `Mount`, — those are just some ByteBuffer a simple POJO to store mountpoint information respectively. This isn't really a complete ready-to-use library — I am expect you to adapt the code to your environment. – user1643723 Apr 02 '18 at 02:12