Важно (9.07.22)

Если картинки в постах не отображаются, зайдите в блог через прокси. РКН заблокировал поддомены blogger.com на которые загружались картинки.

суббота, 15 февраля 2014 г.

Получаем список доступных устройств хранения информации

Как и обещал в комментариях к заметке «Deployment Manager или куда ещё можно задеплоить файлы», покопался ещё немного в файловой системе (а точнее в Иерархии каталогов). Основой для этой статьи стал вопрос от Дмитрия Кузьменко, очень надеюсь, что смог ответить на него в необходимом объёме. Немного поговорим об OC Android, производителях устройств работающих под управлением этой операционки, а также я покажу несколько вариантов получения списка доступных устройств хранения информации.

Продолжение для Android 4.4.* тут: Android 4.4 и запись на внешнюю карту памяти...

Upd (17.02.14). На основе комментариев внесены правки в приложения.
Upd2 (17.02.14). Обнаружено ложное срабатывание 3-го варианта, особенность пофиксил. Немного улучшил поиск.

Upd (30.03.14). Третий способ работает на версиях Android ниже 4.3. Для версий выше 4.3 код не напишу, т.к. не имею устройств с такой версией Android для анализа новой структуры.
Upd (21.04.14). Проверил код на Delphi XE6

Upd3 (15.07.14). Обновление исходного кода, обнаружена не правильная работа (ошибка AV) на некоторых устройствах


Постараюсь изложить всю суть кратко, чтобы не получилось 10 страниц текста. 

Начнём с основ.
Операционная система Android основана на ядре Linux. В Unix-подобных операционных системах существует только один корневой каталог, а все остальные файлы и каталоги вложены в него. В большинстве UNIX-подобных систем съёмные диски, флеш-накопители и другие внешние устройства хранения данных монтируют в каталог /mnt, /mount или /media (в нашем случае это папка /mnt). UNIX-подобные операционные системы также позволяют автоматически монтировать диски при загрузке операционной системы.
На этом с основами всё. Идём дальше.

Определяем список доступных устройств хранения информации.
Сразу нужно сказать, что в Android API я не нашёл метода, позволяющего определить подобный список. Поэтому первое, что приходит в голову, это проверка возможных путей самостоятельно и тут появляется первое «НО». Всё было бы просто, если бы не производители устройств, которые так и хотят что-то изменить в Android, вот и в этом случае они постарались. Я поэкспериментировал и выяснил, что количество возможных вариантов путей достаточно большое и узнать их все почти невозможно, разве что начать собирать общую базу таких вариантов со всех владельцев Android устройств.

В моих экспериментах участвовали (моё только одно - SGS2 :):
  • Samsung Galaxy S Plus – Android 2.3.6
  • Samsung Galaxy S2 – Android 4.1.2
  • HTC Sensation Z710e – Android 4.0.3
  • HTC One X – Android 4.2.2
  • HTC Rhyme – Android 4.0.3

Теперь давайте посмотрим, какие пути доступны на этих устройствах, нам важны путь до внутренней карты (если есть), внешней карты (если есть) и usb устройств (если есть). 


Как видите пути везде разные.

Вариант #1.
Составляем массив возможных значений и в цикле проверяем каждое на доступность при помощи обычной проверки на существование папки (TDirectory.Exists), а также на пустоту папки (TDirectory.IsEmpty).
Я составил общий список для данных устройств и написал небольшое приложение для проверки.
Мой список (основан на 5-ти популярных устройствах):
  • /mnt/sdcard
  • /mnt/sdcard/external_sd
  • /mnt/extSdCard
  • /mnt/usb
  • /mnt/UsbDriveA
  • /mnt/UsbDriveB
  • /mnt/UsbDriveC
  • /mnt/UsbDriveD
  • /mnt/UsbDriveE
  • /mnt/UsbDriveF
Поискав подобные пути в интернете, нашёл ещё 3 возможных пункта:
  • /mnt/external_sd
  • /mnt/usb_storage
  • /mnt/external
Дополнение от Дмитрия Кузьменко:
Sony Xperia V - 4.1.2
  • /mnt/sdcard
  • /mnt/ext_card
  • /mnt/usbdisk
Ещё немного:
Sony Xperia Go - 4.1.2
  • /mnt/sdcard
  • /mnt/ext_card
  • /mnt/usbdisk
Samsung Galaxy S4 - 4.2.2
  • /mnt/sdcard
  • /mnt/extSdCard
  • /mnt/UsbDriveA
  • /mnt/UsbDriveB
  • /mnt/UsbDriveC
  • /mnt/UsbDriveD
  • /mnt/UsbDriveE
  • /mnt/UsbDriveF
Готовый массив вы найдёте в коде ниже.

Вы можете оставить в комментариях информацию о путях на ваших устройствах в таком виде:
Название устройства – версия Android
Прямой путь до внутренней памяти
Прямой путь до внешней карты памяти
Прямые пути до USB устройств
Все пути начинаются с папки /mnt/.

Или скопируйте и вышлите мне файл "/etc/vold.fstab" в комменты (не забудьте указать модель устройства и версию Android'а). Пожалуйста, указывайте также все папки, которые есть в директории /mnt/, это очень важно. 

Приложение:

Код:
uses
  System.IOUtils;

const
  pathmnt: Array[0..14] of String = ('/mnt/sdcard', '/mnt/sdcard/external_sd',
  '/mnt/extSdCard', '/mnt/usb', '/mnt/UsbDriveA', '/mnt/UsbDriveB',
  '/mnt/UsbDriveC', '/mnt/UsbDriveD', '/mnt/UsbDriveE', '/mnt/UsbDriveF',
  '/mnt/external_sd', '/mnt/usb_storage', '/mnt/external', '/mnt/ext_card',
  '/mnt/usbdisk');

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  Memo1.Lines.Clear;
  Memo2.Lines.Clear;
  for i := 0 to Length(pathmnt) - 1 do begin
    if TDirectory.Exists(pathmnt[i]) AND not TDirectory.IsEmpty(pathmnt[i]) then
    begin
      Memo1.Lines.Add(pathmnt[i]);
      Memo2.Lines.Add('True');
    end
    else
    begin
      Memo1.Lines.Add(pathmnt[i]);
      Memo2.Lines.Add('False');
    end;
  end;
end;

Исходный код: Скачать с Google Drive

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

Есть такой файлик «vold.fstab», лежит тут «/etc/»(«/system/etc/»). Это один из конфигурационных файлов в UNIX-подобных системах, который содержит информацию о различных файловых системах и устройствах хранения информации. Описывает, как диск (раздел) будет использоваться или как будет интегрирован в систему.

И есть файл «mounts», лежит тут «/proc/». Содержит информацию обо всех точках монтирования, используемых в устройстве. Важно понимать, что если карта памяти не упоминается в данном файле, то она не подключена. Это означает, что файл постоянно обновляется.

Пошагово:
  1. Читаем файл «vold.fstab»
  2. Составляем список устройств хранения информации (половина дела сделали)
  3. Проверяем каждое устройство на доступность, при помощи чтения файла «mounts» и поиска в нём нужной строки.
  4. Составляем список реально доступных устройств хранения информации

В этом варианте пока не будет кода, т.к. по какой-то неведомой мне причине, у меня не получается прочитать файл «mounts». В связи с этим предлагаю третий вариант.


Вариант #3.
Объединяем первый и второй варианты. Список будем брать из файла «vold.fstab», а проверять своими силами, не открывая при этом файл «mounts». Зачем каждый раз мучать два файла, когда можно работать только с одним.

Пошагово:
  1. Читаем файл «vold.fstab»
  2. Составляем список устройств хранения информации (половина дела сделали)
  3. Теперь в цикле пробегаемся по списку и проверяем каждый путь с помощью «TDirectory.Exists» и «TDirectory.IsEmpty»
  4. Составляем список реально доступных устройств хранения информации
Update-17.02.14: Благодаря Дмитрию Кузьменко и общим усилиям выяснили, что файл «/etc/vold.fstab» в Sony Xperia V немного отличается от остальных, поэтому были внесены изменения в исходный код приложения. Всё проверили, работает! :) Исходники везде обновил.

Update2-17.02.14: В файле "/etc/vold.fstab" иногда встречаются (на некоторых устройствах) закомментированные строчки, которые очень похожи на искомые в моём коде, в связи с этим происходило ложное срабатывание. Эту особенность я пофиксил в новой версии кода.
Также пофиксил поиск нужных строчек, теперь он более универсален!

В общем, теперь код должен нормально отрабатывать на разных устройствах. Пробуйте, отписывайтесь в комменты, прикладывайте свой файл "/etc/vold.fstab" (не забывайте указывать модель устройства и версию Android).

Update3 - 15.07.14: 
Читатель блога Sergey Yakimenko, обнаружил не правильную работу (чтение файла) третьего варианта на устройствах Huawei Ascend P6 и Reellex TAB-07, о чём сообщил в комментариях.

После личной переписки и некоторого анализа, я выяснил, что за проблемы мешали работе.

Обнаруженные и исправленные проблемы:

  1. Пробелы в начале каждой найденной строчке. Исправлено при помощи "Trim"
  2. В найденных строчках используется табуляция вместо пробелов. Исправлено заменой символов табуляции на пробелы.

Код обновлён. Сергею большое спасибо.


Приложение:


Код:
uses
  System.StrUtils, System.IOUtils;

procedure TForm1.Button1Click(Sender: TObject);
var
  OpenFileVold: TStringList;
  i: Integer;
  pathtemp: TStringDynArray;
begin

  OpenFileVold := TStringList.Create;

  try

    // Читаем файл
    OpenFileVold.LoadFromFile('/etc/vold.fstab');

    for i := 0 to OpenFileVold.Count - 1 do
    begin

      if (Pos('dev_mount', OpenFileVold.Strings[i]) > 0) OR
        (Pos('fuse_mount', OpenFileVold.Strings[i]) > 0) then
      begin

        // Update(15.07.14)
        // 1 - Удаляем пробелы в начале и в конце строке (избавляемся от AV)
        // 2 - Заменяем символы табуляции на пробелы
        pathtemp := SplitString(StringReplace(Trim(OpenFileVold.Strings[i]), #9,
          ' ', [rfReplaceAll, rfIgnoreCase]), ' ');

        // Проверяем первый символ строки на совпадение с символом "#" или "##",
        // чтобы не было ложных срабатываний
        if (pathtemp[0][0] <> '#') AND (pathtemp[0][0] <> '##') then
        begin

          if TDirectory.Exists(pathtemp[2]) AND
            not TDirectory.IsEmpty(pathtemp[2]) then
          begin
            Memo1.Lines.Add(pathtemp[2]);
          end;

        end;

      end;

    end;

  finally
    OpenFileVold.Free; // FreeAndNil(SourceFile);
  end;

end;

Исходный код: Скачать с Google Drive

Итог: Научились получать список доступных устройств хранения информации (я использовал бы третий вариант). Текста (без кода и картинок) вышло на 3 страницы, скорее всего, заскучать не успели :)

Всем спасибо за внимание и удачной разработки.

p.s. Дополнения приветствуются.
p.s.2. Потратил немало времени на одни только эксперименты и сравнение файлов vold.fstab, mounts, с разных устройств...
p.s.3. В следующей статье, я покажу, как избавиться от проблемы обновления базы данных при обновлении приложения вручную.