Android 5 sd карта только для чтения. Будет ли запрос рекурсивно работать в выбранной папке? К чему это приводит

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

Итак, начнем с теории.

Терминология

Гугл нам говорит, что есть следующие понятия:
  1. Внутренняя (internal ) память - это часть встроенной в телефон карты памяти. При ее использовании по умолчанию папка приложения защищена от доступа других приложений (Using the Internal Storage).
  2. Внешняя (external ) память - это общее «внешнее хранилище», т.е. это может быть как часть встроенной памяти, так и удаляемое устройство. Обычно это часть встроенной памяти, как удаляемое устройство я видел в последний раз на андройде 2.2, где встроенная память была около 2Гб, и подключаемая память становилась внешней (Using the External Storage).
  3. Удаляемая (removable ) память - все хранилища, которые могут быть удалены из устройства без «хирургических» вмешательств.

До версии KitKat 4.4 API не предоставляло функционала для получения путей к внешней памяти. Начиная с этой версии (API 19) появилась функция public abstract File getExternalFilesDirs (String type), которая возвращает массив строк с путями к внутренней и внешней памяти. Но как же быть с нашей SD Card, которая вставлена в слот? Путь к ней мы опять не можем получить.

Результаты поиска

Чтобы ответить на поставленный вопрос я обратился к всезнающему гуглу. Но и он мне не дал четкого ответа. Было рассмотрено множество вариантов определения от использования стандартных функций, которые ведут к внешней памяти, но ничего общего с удаляемыми устройствами хранения данных они не имеют, до обработки правил монтирования устройств (Android же на ядре Linux работает). В последних случаях были использованы «зашитые» пути к папке с примонтироваными устройствами (в различных версиях эта директория разная). Не стоит забывать, что от версии к версии правила монтирования меняются.

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

Описание кода

Был создан класс MountDevice , который содержит в себе путь к устройству, тип устройства и некий хэш.
Типов устройств выделено два (внутреннюю память я не стал трогать, так как к ней доступ можно получить через API системы).

Public enum MountDeviceType { EXTERNAL_SD_CARD, REMOVABLE_SD_CARD }
И был создан класс StorageHelper , который и осуществляет поиск доступных карт памяти.

В классе StorageHelper реализовано два способа поиска - через системное окружение (Environment ) и с использованием утилиты Linux mount , а точнее результата ее выполнения.

Способ первый - Environment
При работе с окружением я использую стандартную функцию getExternalStorageDirectory() для получения информации о внешней памяти. Чтобы получить информацию о удаляемой памяти, я использую переменную окружения "SECONDARY_STORAGE ".

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

Функция fillDevicesEnvirement

String path = android.os.Environment.getExternalStorageDirectory() .getAbsolutePath(); if (!path.trim().isEmpty() && android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) { testAndAdd(path, MountDeviceType.EXTERNAL_SD_CARD); } // Получаем ремувабл String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE"); if (rawSecondaryStoragesStr != null && !rawSecondaryStoragesStr.isEmpty()) { // All Secondary SD-CARDs splited into array final String rawSecondaryStorages = rawSecondaryStoragesStr .split(File.pathSeparator); for (String rawSecondaryStorage: rawSecondaryStorages) { testAndAdd(rawSecondaryStorage, MountDeviceType.REMOVABLE_SD_CARD); } }


Вариант решения взят со stackoverflow . Ответ где-то там внизу.
Способ второй - mount
Так как у меня долго не получалось заставить систему мне сказать путь к удаляемой памяти, я решил искать в сторону примонтированных устройств. В системе есть файлы конфигурации, в которых описаны правила монтирования внешних устройств. Все бы хорошо, но на Android версии 4.* к этому файлу простым смертным доступа нет, поэтому рассматривать этот способ не буду.

Вернемся к утилите mount. При запуске без параметров команда возвращает список смонтированных файловых систем. Удаляемые устройства имеют обычно формат файловой системы FAT, то будем выделять строки, в которых есть характеристика "fat ". Внешняя память будет характеризоваться параметром "fuse ".

Примечание: при использовании такого способа не всегда корректно (скорее всего я что-то не учел) определяются типы смотнтированных устройств. Разницу замечал на разных версиях Android. Поэтому этот способ можно использовать как дополнительный.

Функция fillDevicesProcess

try { Runtime runtime = Runtime.getRuntime(); proc = runtime.exec("mount"); try { is = proc.getInputStream(); isr = new InputStreamReader(is); br = new BufferedReader(isr); while ((line = br.readLine()) != null) { if (line.contains("secure")) continue; if (line.contains("asec")) continue; if (line.contains("fat")) {// TF card String columns = line.split(" "); if (columns != null && columns.length > 1) { testAndAdd(columns, MountDeviceType.REMOVABLE_SD_CARD); } } else if (line.contains("fuse")) {// internal(External) // storage String columns = line.split(" "); if (columns != null && columns.length > 1) { // mount = mount.concat(columns + "\n"); testAndAdd(columns, MountDeviceType.EXTERNAL_SD_CARD); } } } } finally { ... } } catch (Exception e) { ... }

Исходный код всего класса расположен еще нигде не расположен. На днях постараюсь разместить на gitHub.

Кто еще какими способами пользуется?

Теги:

  • android
  • разработка под android
  • sd card
Добавить метки

Проблема

Сообщение направляет вас посетить два веб-сайта:

Это выглядит как внутренний пример (возможно, позже будет показан в демонстрациях API), но довольно сложно понять, что происходит.

Это официальная документация по новому API, но в нем недостаточно информации о том, как ее использовать.

Вот что он вам говорит:

Если вам действительно нужен полный доступ ко всему поддереву документов, начните с запуска ACTION_OPEN_DOCUMENT_TREE, чтобы пользователь мог выбрать каталог. Затем передайте полученный getData() в fromTreeUri (Context, Uri), чтобы начать работу с выбранным пользователем деревом.

При навигации по дереву экземпляров DocumentFile вы всегда можете использовать getUri(), чтобы получить Uri, представляющий базовый документ для этот объект для использования с openInputStream (Uri) и т.д.

Чтобы упростить код на устройствах, работающих под управлением KITKAT или ранее, вы можете используйте fromFile (Файл), который эмулирует поведение DocumentProvider.

Вопросы

У меня есть несколько вопросов о новом API:

2 ответов

Множество хороших вопросов, дайте понять.:)

Как вы его используете?

Здесь отличный учебник для взаимодействия с платформой доступа к хранилищу в KitKat:

Взаимодействие с новыми API в Lollipop очень похоже. Чтобы предложить пользователю выбрать дерево каталогов, вы можете запустить такое намерение следующим образом:

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

Затем в вашем onActivityResult() вы можете передать выбранный пользователем Uri в новый вспомогательный класс DocumentFile. Вот краткий пример, в котором перечислены файлы в выбранном каталоге, а затем создается новый файл:

Public void onActivityResult(int requestCode, int resultCode, Intent resultData) { if (resultCode == RESULT_OK) { Uri treeUri = resultData.getData(); DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri); // List all existing files inside picked directory for (DocumentFile file: pickedDir.listFiles()) { Log.d(TAG, "Found file " + file.getName() + " with size " + file.length()); } // Create a new file and write into it DocumentFile newFile = pickedDir.createFile("text/plain", "My Novel"); OutputStream out = getContentResolver().openOutputStream(newFile.getUri()); out.write("A long time ago...".getBytes()); out.close(); } }

Если вы хотите получить доступ к этому Uri из собственного кода, вы можете вызвать ContentResolver.openFileDescriptor() , а затем использовать ParcelFileDescriptor.getFd() или detachFd() для получения традиционного целочисленного дескриптора файла POSIX.

Как вы можете проверить доступ к файлам/папкам?

По умолчанию Uris, возвращаемый с помощью возможностей Storage Access Framework, не сохраняется при перезагрузках. Платформа "предлагает" возможность сохранять разрешение, но вам все равно нужно "взять" разрешение, если вы этого хотите. В нашем примере выше вы бы назвали:

GetContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

Вы всегда можете выяснить, какие постоянные гранты для вашего приложения доступны через API ContentResolver.getPersistedUriPermissions() . Если вам больше не нужен доступ к сохраненному Uri, вы можете освободить его с помощью ContentResolver.releasePersistableUriPermission() .

Доступно ли это на KitKat?

Нет, мы не можем ретроактивно добавлять новые функции к более старым версиям платформы.

Могу ли я увидеть, какие приложения имеют доступ к файлам/папкам?

В настоящее время нет пользовательского интерфейса, который показывает это, но вы можете найти информацию в разделе "Предоставленные разрешения Uri" вывода adb shell dumpsys activity providers .

Что произойдет, если приложение установлено для нескольких пользователей на одном устройстве?

Разрешения на разрешение Uri изолированы для каждого пользователя, как и все другие функции многопользовательской платформы. То есть, одно и то же приложение, работающее под двумя разными пользователями, не имеет наложенных или общих разрешений Uri.

Можно ли отозвать разрешения?

Подпрограмма DocumentProvider может аннулировать разрешение в любое время, например, когда удаляется облачный документ. Наиболее распространенный способ обнаружения этих отозванных разрешений - это когда они исчезают из ContentResolver.getPersistedUriPermissions() , упомянутых выше.

Разрешения также отменяется всякий раз, когда данные приложения очищаются для любого приложения, участвующего в гранте.

Будет ли запрос рекурсивно работать в выбранной папке?

Yep, намерение ACTION_OPEN_DOCUMENT_TREE дает вам рекурсивный доступ к существующим и вновь созданным файлам и каталогам.

Предоставляет ли это несколько вариантов?

Да, с KitKat поддерживался множественный выбор, и вы можете разрешить его, установив EXTRA_ALLOW_MULTIPLE при запуске вашего намерения ACTION_OPEN_DOCUMENT . Вы можете использовать Intent.setType() или EXTRA_MIME_TYPES , чтобы сузить типы файлов, которые можно выбрать:

Есть ли способ эмулятора попробовать новый API?

Да, основное устройство хранения данных должно появиться в сборщике, даже на эмуляторе. Если ваше приложение использует платформу доступа к хранилищу для доступа к разделяемому хранилищу, вам больше не нужны разрешения READ/WRITE_EXTERNAL_STORAGE и вы можете удалить их или использовать функцию android:maxSdkVersion , чтобы запрашивать их только на более ранних версиях платформы.

Что происходит, когда пользователь заменяет SD-карту другим?

Когда задействуются физические носители, UUID (такой как серийный номер FAT) основного носителя всегда записывается в возвращаемый Uri. Система использует это для подключения к мультимедиа, который был выбран пользователем, даже если пользователь меняет носитель между несколькими слотами.

Если пользователь поменяется на второй карте, вам необходимо запросить доступ к новой карте. Поскольку система запоминает гранты на основе UUID, вы будете продолжать иметь ранее предоставленный доступ к исходной карте, если пользователь повторно вставляет ее позже.

В моем проекте Android в Github, приведенном ниже, вы можете найти рабочий код, который позволяет писать на extSdCard на Android 5. Предполагается, что пользователь дает доступ ко всей SD-карте, а затем позволяет писать всюду на этой карте. (Если вы хотите иметь доступ только к одиночным файлам, все становится проще.)

Snipplets главного кода

Запуск платформы доступа к хранилищу:

@TargetApi(Build.VERSION_CODES.LOLLIPOP) private void triggerStorageAccessFramework() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); startActivityForResult(intent, REQUEST_CODE_STORAGE_ACCESS); }

Обработка ответа из платформы доступа к хранилищу:

@TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public final void onActivityResult(final int requestCode, final int resultCode, final Intent resultData) { if (requestCode == SettingsFragment.REQUEST_CODE_STORAGE_ACCESS) { Uri treeUri = null; if (resultCode == Activity.RESULT_OK) { // Get Uri from Storage Access Framework. treeUri = resultData.getData(); // Persist URI in shared preference so that you can use it later. // Use your own framework here instead of PreferenceUtil. PreferenceUtil.setSharedPreferenceUri(R.string.key_internal_uri_extsdcard, treeUri); // Persist access permissions. final int takeFlags = resultData.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); getActivity().getContentResolver().takePersistableUriPermission(treeUri, takeFlags); } } }

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

DocumentFile targetDocument = getDocumentFile(file, false); OutputStream outStream = Application.getAppContext(). getContentResolver().openOutputStream(targetDocument.getUri());

Используются следующие вспомогательные методы:

Public static DocumentFile getDocumentFile(final File file, final boolean isDirectory) { String baseFolder = getExtSdCardFolder(file); if (baseFolder == null) { return null; } String relativePath = null; try { String fullPath = file.getCanonicalPath(); relativePath = fullPath.substring(baseFolder.length() + 1); } catch (IOException e) { return null; } Uri treeUri = PreferenceUtil.getSharedPreferenceUri(R.string.key_internal_uri_extsdcard); if (treeUri == null) { return null; } // start with root of SD card and then parse through document tree. DocumentFile document = DocumentFile.fromTreeUri(Application.getAppContext(), treeUri); String parts = relativePath.split("\\/"); for (int i = 0; i < parts.length; i++) { DocumentFile nextDocument = document.findFile(parts[i]); if (nextDocument == null) { if ((i < parts.length - 1) || isDirectory) { nextDocument = document.createDirectory(parts[i]); } else { nextDocument = document.createFile("image", parts[i]); } } document = nextDocument; } return document; } public static String getExtSdCardFolder(final File file) { String extSdPaths = getExtSdCardPaths(); try { for (int i = 0; i < extSdPaths.length; i++) { if (file.getCanonicalPath().startsWith(extSdPaths[i])) { return extSdPaths[i]; } } } catch (IOException e) { return null; } return null; } /** * Get a list of external SD card paths. (Kitkat or higher.) * * @return A list of external SD card paths. */ @TargetApi(Build.VERSION_CODES.KITKAT) private static String getExtSdCardPaths() { List paths = new ArrayList<>(); for (File file: Application.getAppContext().getExternalFilesDirs("external")) { if (file != null && !file.equals(Application.getAppContext().getExternalFilesDir("external"))) { int index = file.getAbsolutePath().lastIndexOf("/Android/data"); if (index < 0) { Log.w(Application.TAG, "Unexpected external file dir: " + file.getAbsolutePath()); } else { String path = file.getAbsolutePath().substring(0, index); try { path = new File(path).getCanonicalPath(); } catch (IOException e) { // Keep non-canonical path. } paths.add(path); } } } return paths.toArray(new String); } /** * Retrieve the application context. * * @return The (statically stored) application context */ public static Context getAppContext() { return Application.mApplication.getApplicationContext(); }

Давайте поговорим о том, как снять защиту записи с SD-карты памяти на Android. Многие люди сталкиваются с этой проблемой, когда пытаются скопировать или переместить файлы на SD карту. В этой статье вы найдете несколько способов как снять защиту записи Android.

Как снять защиту записи с SD-карты Android

Если при попытке скопировать файлы или форматировать SD-карту вы получаете ошибку, как на изображении выше, тогда знайте, это не ваша проблема. Более того, карта памяти не повреждена и не заражена вирусом, просто на ней установлена защита от записи. Без лишней суматохи мы рассмотрим несколько методов как снять защиту записи Android.

Как снять защиту записи Android c SD-карты с помощью Regedit

Большинство проблем и ошибок легко решаются с помощью реестра. Также мы можем использовать его, чтобы снять защиту записи Android.


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

Как снять защиту записи Android c SD-карты с помощью Diskpart

  1. Вставьте SD-карту памяти в компьютер и запустите командую строку. Вы можете сделать это, нажав клавишу WIN+R и написав «CMD», или просто ввести «Командная строка» в меню Пуска.
  2. В окне командной строки вы должны ввести следующие команды по порядку.

  • diskpart
  • list disk
  • select disk x (где Х – это номер вашей SD карты)
  • attributes disk clear readonly
  • clean
  • create partition primary
  • format fs=fat32

Если и этот способ не помог снять защиту записи Android, не отчаивайтесь, у нас есть еще парочка решений этой проблемы.

Изменить разрешение

  1. Вставьте SD карту памяти в компьютер. Щелкните правой кнопкой мыши на карту памяти и выберите Свойства. Затем во вкладке Безопасность вы найдете имя вашей учетной записи.
  2. Измените разрешение для SD-карты на «Чтение/запись», «Чтение/Выполнение» или «Полный контроль».

Этот способ позволит снять защиту записи Android.

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

Надеемся, вам удалось снять защиту записи Android. Если у вас остались вопросы, пожалуйста, напишите в комментариях ниже.

С обновлением до пользователи получили ряд ограничений на использование SD карт. Решить эту проблему до недавних пор можно было при помощи кастомной прошивки или отката к более ранней версии ОС. Теперь же былые возможности можно вернуть и на Android 4.4 KitKat . О том, как это сделать, читайте далее.

Прежде всего, вам потребуется получить root-права для своего Android устройства. Второе важное условие – это приложение SDFix . Скачать приложение на Андроид бесплатно можно прямиком с . Для этого воспользуйтесь ссылкой, указанной далее.

Установив SDFix на свой Android , вы в несколько тапов восстановите былые возможности. Принцип работы приложения заключается в том, что SDFix добавляет Android UNIX группу "media_rw" к WRITE_EXTERNAL_STORAGE , таким образом, используемый в Android 4.4 KitKat XML файл меняется на platform.xml . При этом создается резервная копия изначальной настройки platform.xml.original-pre-sdfix , которую можно использовать для восстановления ограничений. Чтобы вернуть систему к стоковому состоянию, необходимо воспользоваться проводником с поддержкой root-доступа и заменить platform.xml на platform.xml.original-pre-sdfix.

Отдельно необходимо отметить, что приложения, которые используют SD, получат более низкий уровень безопасности. Также важным является тот факт, что SDFix приводит к перманентным изменениям, а это означает, что после восстановления старых полномочий приложение можно удалить.

Свершилось! На планшет, а именно, на Asus MeMO Pad 7, который я на замену своему честному трудяге, прилетело обновление до Android 5.0.1. Теперь у меня есть устройство с Леденцом, или, как пишут в интернете, с Лолипопой.

Обнова прилетела несколько неожиданно. Если честно, я ждал в апреле новую прошивку на телефон (Asus ZenFone 5) - ее, по крайней мере, обещали. Про планшет же никто ничего не говорил и тут - на тебе, получите и распишитесь.

Это, на самом деле, обескуражило меня (в хорошем, конечно, смысле), еще и потому, что планшет я приобрел в декабре, и на нем стояла версия 4.3. То есть, это второй существенный апдейт операционной системы (на самом деле, обнов было больше, но номер версии Android не менялся). Предыдущий производитель моих устройств - Samsung - не мог похвастаться такой скорострельностью, хотя, тоже дважды обновлял операционку: с Android 3.2 до Android 4.1.2 (через 4.0.1). Только вот ждать этих новых версий приходилось значительно дольше.

Что ж, да здравствуют сюрпризы! Получив уведомление о выходе новой версии прошивки, я тут же скачал ее, а это больше 700 метров, и запустил обновление. Так как качал я не через домашнюю сеть , а через мобильную, то времени на все про все потребовалось чуть больше: от начала загрузки до завершения обновления прошло около часа. Само обновление длилось где-то минут двадцать, но, потенциально, может занимать и больше времени - потому, что после установки операционки следует процесс обновления установленных программ. У меня счетчик досчитал до 205. Но вот, процесс завершился и настало время посмотреть, что же изменилось.

Первое, что бросилось в глаза - более утонченная графика при разблокировке устройства. Второе - рука в области уведомлений. Пару дней я ее терпел, надеясь на то, что случайно где-нибудь увижу что-нибудь, связанное с ней. Не увидел. Тогда стал искать целенаправленно и нашел разъяснение по ней на 4pda . Оказывается, оповещения поделили на важные и не очень, и есть возможность указать, какие оповещения вы хотите получать. Если выставить опцию Оповещать всегда , то рука пропадает, если опцию Только важные оповещения , то рука появляется. Если честно, руку я убирал несколько раз, но, почему-то, она стабильно возвращается. Почему? Пока не знаю.

Вот, собственно, сама процедура по "убиранию" этой руки:

Метод, приведенный выше, совершенно не сложен. Но можно добиться результата еще быстрее и проще: нажать на какую-то из двух кнопок управления громкостью - на экране появится окошко, позволяющее изменять уровни громкости для различных компонентов системы, а также, внимание, управлять Режимами оповещения . Правда, почему-то, эти самые Режимы оповещения выводятся в таком быстром варианте не всегда. Закономерности я пока не нашел, но, если честно, не больно-то и искал.

Официальных обновлений Андроид (для нашего региона) я не нашел на официальных сайтах.

На форумах я нашел информацию, что Google выпустила версию прошивки для России в январе 2015. Но очень медленно внедряется на аппараты клиентов.

Таким образом, я могу подождать, пока на мой аппарат не поступит сообщение о том, что есть обновление По. После этого я смогу обновить Андроид.