Android 5.0 DocumentFile из дерева URI

Хорошо, я искал и искал, и никто не получил мой точный ответ, или я пропустил его. Мои пользователи выбирают каталог по:

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, READ_REQUEST_CODE);

В своей деятельности я хочу уловить реальный путь, который кажется невозможным.

protected void onActivityResult(int requestCode, int resultCode, Intent intent){
    super.onActivityResult(requestCode, resultCode, intent);
    if (resultCode == RESULT_OK) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
            //Marshmallow 

        } else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){
            //Set directory as default in preferences
            Uri treeUri = intent.getData();
            //grant write permissions
            getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            //File myFile = new File(uri.getPath()); 
            DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);

Папка, которую я выбрал, находится по адресу:

Device storage/test/

Я попробовал все следующие способы получить точное имя пути, но безрезультатно.

File myFile = new File (uri.getPath());
//returns: /tree/1AF6-3708:test

treeUri.getPath();
//returns: /tree/1AF6-3708:test/

pickedDir.getName()
//returns: test

pickedDir.getParentFile()
//returns: null

В основном мне нужно включить /tree/1AF6-3708: в /storage/emulated/0/ или как там каждое устройство называет это место хранения. Все остальные доступные варианты возврата /tree/1AF6-37u08: также.

Есть две причины, по которым я хочу сделать это таким образом.

1) В моем приложении я сохраняю расположение файла в качестве общего предпочтения, поскольку оно зависит от пользователя. У меня есть довольно много данных, которые будут загружены и сохранены, и я хочу, чтобы пользователь мог разместить их там, где они хотят, особенно если у них есть дополнительное место для хранения. Я устанавливаю значение по умолчанию, но мне нужна универсальность, а не выделенное расположение:

Device storage/Android/data/com.app.name/

2) В 5.0 я хочу разрешить пользователю получать права на чтение / запись для этой папки, и это, кажется, единственный способ сделать это. Если я могу получить разрешения на чтение / запись из строки, которая исправит эту проблему.

Все решения, которые мне удалось найти, относятся к Mediastore, что мне точно не помогает. Я должен что-то упустить, или я должен был застеклить это. Любая помощь будет оценена. Благодарю.

6 ответов

Это даст вам фактический путь к выбранной папке (при условии, что выбранная папка находится во внешнем хранилище или на внешней SD-карте)

Uri treeUri = data.getData();
String path = FileUtil.getFullPathFromTreeUri(treeUri,this); 

где FileUtil - следующий класс

public final class FileUtil {
    static String TAG="TAG";
    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 = getVolumePath(getVolumeIdFromTreeUri(treeUri),con);
        if (volumePath == null) return File.separator;
        if (volumePath.endsWith(File.separator))
            volumePath = volumePath.substring(0, volumePath.length() - 1);

        String documentPath = 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;
    }


    @SuppressLint("ObsoleteSdkInt")
    private static String getVolumePath(final String volumeId, Context context) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
        try {
            StorageManager mStorageManager =
                    (StorageManager) context.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 && 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;
    }
}

Это дополнение к ответу @Anonymous для Android 11.

@TargetApi(Build.VERSION_CODES.R)
    private static String getVolumePath_SDK30(final String volumeId, Context context) {
        try {
            StorageManager mStorageManager =
                    (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
            if (mStorageManager == null) return null;
            List<StorageVolume> storageVolumes = mStorageManager.getStorageVolumes();
            for (StorageVolume storageVolume : storageVolumes) {
                String uuid = storageVolume.getUuid();
                Boolean primary = storageVolume.isPrimary();
                if (primary == null) return null;
                // primary volume?
                if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
                    return storageVolume.getDirectory().getPath();
                // other volumes?
                if (uuid != null && uuid.equals(volumeId))
                    return storageVolume.getDirectory().getPath();
            }
            // not found.
            return null;
        } catch (Exception ex) {
            return null;
        }
    }

В своей деятельности я хочу уловить реальный путь, который кажется невозможным.

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

У меня есть довольно много данных, которые будут загружены и сохранены, и я хочу, чтобы пользователь мог разместить их там, где они хотят

Затем используйте API-интерфейсы Storage Access Framework вместо того, чтобы думать, что документы / деревья, которые вы получаете из Storage Access Framework, всегда локальны. Или не использовать ACTION_OPEN_DOCUMENT_TREE,

В 5.0 я хочу разрешить пользователю получать права на чтение / запись для этой папки

Это обрабатывается поставщиком хранилища как часть взаимодействия пользователя с этим поставщиком хранилища. Вы не вовлечены.

      public static String findFullPath(String path) {
        String actualResult="";
        path=path.substring(5);
        int index=0;
        StringBuilder result = new StringBuilder("/storage");
        for (int i = 0; i < path.length(); i++) {
            if (path.charAt(i) != ':') {
                result.append(path.charAt(i));
            } else {
                index = ++i;
                result.append('/');
                break;
            }
        }
        for (int i = index; i < path.length(); i++) {
            result.append(path.charAt(i));
        }
        if (result.substring(9, 16).equalsIgnoreCase("primary")) {
            actualResult = result.substring(0, 8) + "/emulated/0/" + result.substring(17);
        } else {
            actualResult = result.toString();
        }
        return actualResult;
    }

Я пытался добавить каталог сохранения по умолчанию до того, или пользователь не выберет пользовательский каталог с помощью пользовательского интерфейса SAF на экране настроек моего приложения. Пользователи могут пропускать выбор папки, и приложение может зависнуть. Чтобы добавить папку по умолчанию в память устройства, вы должны

    DocumentFile saveDir = null;
    saveDir = DocumentFile.fromFile(Environment.getExternalStorageDirectory());
    String uriString = saveDir.getUri().toString();

    List<UriPermission> perms = getContentResolver().getPersistedUriPermissions();
    for (UriPermission p : perms) {
        if (p.getUri().toString().equals(uriString) && p.isWritePermission()) {
            canWrite = true;
            break;
        }
    }
    // Permitted to create a direct child of parent directory
    DocumentFile newDir = null;
    if (canWrite) {
         newDir = saveDir.createDirectory("MyFolder");
    }

    if (newDir != null && newDir.exists()) {
        return newDir;
    }

Этот фрагмент создаст каталог в основной памяти устройства и предоставит разрешения на чтение / запись для этой папки и подпапок. Вы не можете напрямую создать иерархию MyFolder/MySubFolder, вам следует снова создать другой каталог.

Вы можете проверить, есть ли у этого каталога разрешение на запись, насколько я видел на 3 устройствах, он возвращает true, если он создан с использованием DocumentFileвместо File учебный класс. Это простой способ создания и предоставления прав на запись для Android >= 5.0 без использования ACTION_OPEN_DOCUMENT_TREE

public static String findFullPath(String path) {
    String actualResult="";
    path=path.substring(5);
    int index=0;
    StringBuilder result = new StringBuilder("/storage");
    for (int i = 0; i < path.length(); i++) {
        if (path.charAt(i) != ':') {
            result.append(path.charAt(i));
        } else {
            index = ++i;
            result.append('/');
            break;
        }
    }
    for (int i = index; i < path.length(); i++) {
        result.append(path.charAt(i));
    }
    if (result.substring(9, 16).equalsIgnoreCase("primary")) {
        actualResult = result.substring(0, 8) + "/emulated/0/" + result.substring(17);
    } else {
        actualResult = result.toString();
    }
    return actualResult;
}

эта функция дает мне абсолютный путь от дерева uri. это решение работает на большинстве устройств, которые я тестировал на более чем 1000 устройствах. он также дает нам правильный абсолютный путь к папке, которая содержится на карте памяти или OTG.

How it Works?

в основном у большинства устройств путь начинается с префикса /Storage/. а средняя часть пути содержит имя смонтированной точки, например / emulated / 0 / для внутреннего хранилища, или некоторую строку, например /C0V54440/ etc (просто пример). и последний сегмент - это путь от корня хранилища до папки типа / movie / piratesofthecarribian

Итак, путь, который мы построили из:- /tree/primary:movie/piratesofthecarribian, выглядит так:- /storage/emulated/0/movie/piratesofthecarribian

Вы можете найти больше информации о моем репозитории на github. посетите там, чтобы получить код Android о том, как преобразовать древовидный uri в фактический абсолютный путь.

gihub ( https://github.com/chetanborase/TreeUritoAbsolutePath)

Другие вопросы по тегам