ОБНОВЛЕНИЕ: мой первоначальный вопрос может вводить в заблуждение, поэтому я хочу перефразировать его: я хочу пройти через дерево иерархии с устройства, подключенного к MTP, через Android Storage Access Framework. Я не могу этого добиться, потому что получаю SecurityException
, что подузел не является потомком своего родительского узла. Есть ли способ обойти эту проблему? Или это известная проблема? Спасибо.
Я пишу приложение для Android, которое пытается просматривать и получать доступ к документам через дерево иерархии с использованием Android Storage Access Framework (SAF) через MtpDocumentsProvider
. Я более или менее следую примеру кода, описанному в https://github.com/googlesamples/android-DirectorySelection о том, как запустить средство выбора SAF из моего приложения, выбрать источник данных MTP, а затем в onActivityResult
использовать возвращенный Uri
для перемещения по иерархии. К сожалению, это не работает, потому что как только я получаю доступ к подпапке и пытаюсь пройти по ней, я всегда получаю SecurityException
, что document xx is not a descendant of yy
Итак, мой вопрос: используя MtpDocumentProvider
, как я могу успешно пройти через дерево иерархии из моего приложения и избежать этого исключения?
Чтобы быть конкретным, в моем приложении сначала я вызываю следующий метод для запуска средства выбора SAF:
private void launchStoragePicker() {
Intent browseIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
browseIntent.addFlags(
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
| Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
| Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
);
startActivityForResult(browseIntent, REQUEST_CODE_OPEN_DIRECTORY);
}
Затем запускается средство выбора Android SAF, и я вижу, что мое подключенное устройство распознается как источник данных MTP. Я выбираю указанный источник данных и получаю Uri
из своего onActivityResult
:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_OPEN_DIRECTORY && resultCode == Activity.RESULT_OK) {
traverseDirectoryEntries(data.getData()); // getData() returns the root uri node
}
}
Затем, используя возвращенный Uri
, я вызываю DocumentsContract.buildChildDocumentsUriUsingTree
, чтобы получить Uri
, который затем могу использовать для запроса и доступа к иерархии дерева:
void traverseDirectoryEntries(Uri rootUri) {
ContentResolver contentResolver = getActivity().getContentResolver();
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, DocumentsContract.getTreeDocumentId(rootUri));
// Keep track of our directory hierarchy
List<Uri> dirNodes = new LinkedList<>();
dirNodes.add(childrenUri);
while(!dirNodes.isEmpty()) {
childrenUri = dirNodes.remove(0); // get the item from top
Log.d(TAG, "node uri: ", childrenUri);
Cursor c = contentResolver.query(childrenUri, new String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE}, null, null, null);
try {
while (c.moveToNext()) {
final String docId = c.getString(0);
final String name = c.getString(1);
final String mime = c.getString(2);
Log.d(TAG, "docId: " + id + ", name: " + name + ", mime: " + mime);
if(isDirectory(mime)) {
final Uri newNode = DocumentsContract.buildChildDocumentsUriUsingTree(rootUri, docId);
dirNodes.add(newNode);
}
}
} finally {
closeQuietly(c);
}
}
}
// Util method to check if the mime type is a directory
private static boolean isDirectory(String mimeType) {
return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
}
// Util method to close a closeable
private static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (RuntimeException re) {
throw re;
} catch (Exception ignore) {
// ignore exception
}
}
}
Первая итерация внешнего цикла while завершилась успешно: вызов query
вернул мне допустимый Cursor
для прохождения. Проблема заключается во второй итерации: когда я пытаюсь запросить Uri
, который оказывается подузлом rootUri
, я получаю SecurityException
о том, что документ xx не является потомком yy.
D / MyApp (19241): узел uri: content: //com.android.mtp.documents/tree/2/document/2/children D / MyApp (19241): docId: 4, имя: DCIM, mime: vnd. android.document / directory D / MyApp (19241): uri узла: content: //com.android.mtp.documents/tree/2/document/4/children E / DatabaseUtils (20944): запись исключения в посылку E / DatabaseUtils (20944): java.lang.SecurityException: документ 4 не является потомком 2
Может ли кто-нибудь дать представление о том, что я делаю неправильно? Если я использую другого поставщика источника данных, например, из внешнего хранилища (например, SD-карту, подключенную через стандартный USB-считыватель OTG), все работает нормально.
Дополнительная информация: я использую это на Nexus 6P, Android 7.1.1, а моему приложению minSdkVersion
- 19.
and I see my connected device recognized as the MTP data source.
. Вы можете подробнее рассказать об этом? MTP? Вы подключили устройство к своему Android-устройству? Как? Что за устройство? - person greenapps   schedule 12.12.2016if(isDirectory(mime))
. Вы не разместили код для этой функции. И вы этого не объяснили. - person greenapps   schedule 12.12.2016closeQuietly(childCursor);
childCursor? - person greenapps   schedule 12.12.2016isDirectory
иcloseQuietly
- это просто вспомогательные методы. Я добавил код в свой отредактированный пост.rootUri
- этоUri
, возвращенный изonActivityResult
. Я сделал ошибку копирования / вставки, которую исправил. - person spring.ace   schedule 12.12.2016and determined I can access the contents via MTP
. Вы можете подробнее рассказать об этом? Что видит пользователь, благодаря которому он знает, что соединение является MTP? Или программист? Я не понимаю, что вы используете этот код провайдера mtp из своей ссылки, поскольку ваше приложение для Android не является поставщиком. Камера будет провайдером. Никогда не видел камеры, которая общается по мтп. Для меня это все в новинку. - person greenapps   schedule 12.12.2016and its trying to communicate via MTP
. Откуда вы знаете? Я спрашивал об этом раньше! Какая марка / тип камеры? Я тоже спрашивал об этом раньше. - person greenapps   schedule 13.12.2016ACTION_OPEN_DOCUMENT_TREE
, и в результате я вижу устройство, подключенное к камере, в средстве выбора. Я не делаю ничего особенного. - person spring.ace   schedule 13.12.2016