Получить список путей всех устройств хранения, подключенных к Android-устройству

Я хочу получить список всех устройств хранения, которые подключены к Android-устройству.

Для внутреннего хранилища eg- (Хранение, в котором присутствуют все папки, такие как Загрузки, DCIM и т.д.), SD-карта и OTG-устройство.

Я знаю, что есть много сообщений StackOverflow, которые обсуждают эту тему, но ни одна из них не может служить моей цели, как указано выше.

Я могу получить внутреннее хранилище, вызвав Environment.getExternalStorageDirectory().getPath() которые возвращают путь к внутреннему хранилищу.

Любая помощь по этому поводу будет очень благодарна, поскольку нет стандартного AFAIK, с помощью которого можно восстановить список всех подключенных устройств хранения.

Кроме того, многие решения не работают на разных устройствах и версиях Android.

Ответы

Ответ 1

Вы можете создать один класс 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;
        }
    }
}

а затем вы можете использовать его для проверки SD-карты, или Usb или неизвестно в настоящее время подключено или нет с устройством

Таким образом вы можете получить подключенную SD-карту, USB и т.д.

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

Ответ 2

Мне повезло с

ContextCompat.getExternalFilesDirs

Это позволяет найти папки приложений на внешних дисках. Я еще не нашел более эффективного решения, чем это.

В моем случае использования я использую Environment.DIRECTORY_MOVIES Но если вам нужно, есть и другие определения, включая общие DIRECTORY_DOCUMENTS

Ответ 3

Это дополнение к ответу @Sagar о получении монтирования из /proc. Обратите внимание на использование /proc/self/mountinfo вместо /proc/mountinfo или /proc/mounts. Вы можете больше узнать о формате /proc/self/mountinfo в man 5 procfs. Хотя следующий код технически разбирает файлы, безопасно запускаться в основном потоке (потому что /proc - это файловая система в памяти).

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 retry once more
      parseMounts(d, false);
    } catch (IOException e) {
        throw new WrappedIOException(e);
    }
}

В этом ответе вы можете найти более полезную информацию (например, общий путь к бесполезной файловой системе). Обратите внимание, что форматы /proc/mounts и /proc/mountinfo отличаются друг от друга, позже были введены после того, как прежний улучшил формат, не нарушая обратной совместимости.

Приведенный выше код не является золотой пулей - на самом деле он ничего не говорит о отдельных файловых системах, просто их пути и имя файловой системы. Вы можете быть разумно уверены, что "vfat" и "ext4" являются полезными файловыми системами, а "procfs" бесполезен, но что-то вроде "плавкого предохранителя" останется загадочным. Вы можете увеличить вывод кода выше, используя android.os.storage.StorageManager чтобы получить более удобные имена файловой системы (например, "SD-карта"), когда они доступны (соответствие по пути монтирования). Вы также можете использовать StatF для получения свободного места на разделе - бесполезные виртуальные файловые системы обычно возвращают нулевое свободное пространство и нулевое свободное пространство при запросе. Наконец, если вы так склонны, вы можете рассмотреть варианты монтирования файловой системы при принятии решения о том, показывать ли файловую систему пользователю. Например, ro vs rw, - файловые системы только для чтения, как правило, будут намного менее полезны для ваших пользователей.


Когда я объясняю этот метод людям, они часто озабочены его надежностью... Будет ли он работать на случайном junkphone? Будет ли он оставаться доступным в будущих версиях ОС? Вот мой пример: этот метод по-прежнему лучше, чем многие рекомендации, основанные на рефлексии, - в худшем случае чтение из /proc/file вернет вам IOException. Это не приведет к сбою вашего приложения или не приведет к непредсказуемому поведению, как к хакерским атакам на основе анализа.

/proc - официальный Linux API, поддерживаемый разработчиками ядра Linux. Его невозможно удалить, указав различные варианты сборки ядра (например, это обязательная часть ядра ОС). Он доступен в течение многих лет и сохраняет лучшую обратную совместимость, чем большинство API Android. В частности, /proc/self/mountinfo была создана более 10 лет назад и будет доступна в большинстве существующих версий Android, кроме самых древних.

Разработчики Android официально не поддерживают API-интерфейсы, специфичные для Linux. Но они не уходят с дороги, чтобы сломать их. Некоторые из недавних изменений SELinux в post-Lollipop Android ограничили доступ к некоторым файлам в /proc/, потому что они позволяли приложениям скрытно отслеживать другие приложения. Эти изменения были специально сохранены /proc/self доступны, потому что /proc/self предназначен для отображения только собственной информации приложений (включая информацию о файловых системах, доступных для приложения).

Если Google когда-либо перейдет из Linux в Fuchensa или какой-нибудь другой доморощенный BSD-вил, вероятно, будут разорваны/proc/и другие API-интерфейсы Linux-specific. Мне все равно? На самом деле, нет.

Ответ 4

Начиная с уровня API 9, существует android.os.storage.StorageManager. Вызовите getStorageVolumes() (доступный с уровня API 24), чтобы получить список томов хранения. Как пишет док:

Верните список разделяемых/внешних томов хранилища, доступных текущему пользователю. Это включает как основное общее устройство хранения, так и любые подключенные внешние тома, включая SD-карты и USB-накопители.

Результатом является List<StorageVolume>. Теперь взгляните на android.os.storage.StorageVolume:

Информация об общем/внешнем томе хранения для конкретного пользователя.

Вы можете, например, получить видимое пользователем описание тома, вызвав getDescription(). См. createAccessIntent() как получить доступ.