суббота, 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" на почту infocean @ gmail.com или в комменты (не забудьте указать модель устройства и версию 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. В следующей статье, я покажу, как избавиться от проблемы обновления базы данных при обновлении приложения вручную.

23 комментария:

  1. Этот комментарий был удален автором.

    ОтветитьУдалить
    Ответы
    1. Этот комментарий был удален автором.

      Удалить
  2. я коммент удалил, а то не того. Запускал именно третий вариант.
    1. после нажатия кнопки в мемо ничего не попадает. т.е. if не срабатывает.
    2. посмотрел, что вообще в этом файле выдается
    /storage/sdcard1
    /storage/sdcard0
    и так далее.

    Файл сейчас пришлю.

    ОтветитьУдалить
  3. Этот комментарий был удален автором.

    ОтветитьУдалить
  4. Спасибо за ваши труды!
    у Nexus 5 (cyanogenmod) нет файла vold.fstab (карта подключена /mnt/sdcard)
    'Cannot open file "/etc/vold.fstab". No such file or directory'
    Версия android 4.4.2 версия ядра 3.4

    ОтветитьУдалить
    Ответы
    1. В статье указано, что способ будет работать на версиях до 4.3. Девайса с Android версией больше 4.3 у меня нет, так бы я написал код и для этих версий (если это вообще возможно). Прошивать свой аппарат на CM пока желания нет.

      Удалить
  5. Добрый день Андрей, на huawei ascend p6 не работает. Вылетает с ошибкой AV

    ОтветитьУдалить
    Ответы
    1. Здравствуйте, Сергей.
      К сожалению, я не знаю с чем это может быть связано, но давайте попробуем выяснить.
      - Какая версия Android используется?
      - Пришлите мне файл /etc/vold.fstab, на почту или файлообменник (например rghost.ru).
      - Также было бы неплохо узнать список папок из директории «/mnt/».
      - Версия среды разработки.
      - логи (если есть возможность)

      Удалить
  6. Сейчас попробую разобраться на чем именно вылетает (использую хе6, пробовал так же на устройстве rellex tab, падает так же, хотя этот файл читается манагером). Однако смотрю что самое вкусное будет использовать список из папки /mnt/ и давать пользов возможность самому указывать какую из перечисленых карт использовать. Хотя не комильфо получается, но надежно. А что можете сказать по-поводу определения свободного места на устройстве/карте?

    Нашел еще одно интересное на счет новой версии:

    You do not have read access to /system/etc/vold.fstab on this device, apparently. There is no requirement that this file be readable on all devices by ordinary SDK apps, and Android 4.4.x has been progressively tightening the security on access to the filesystem.

    ОтветитьУдалить
    Ответы
    1. В начале статьи я писал, что данный метод не будет работать на Android 4.4. Поэтому я просил вас указать всю информацию по пунктам.
      Для того чтобы узнать свободное место, можно использовать Android API.

      Удалить
    2. Про 4.4 я знаю, я на 4.2 и 4.1 играюсь.
      Спасибо

      Удалить
    3. Хмм… тогда остаются только логи и отладка.
      Как выясните в чём проблема, отпишитесь, пожалуйста, интересно, в чём проблема.

      Удалить
    4. вот тут валится if TDirectory.Exists(pathtemp[2])

      Удалить
    5. В таком случае мне необходима вся информация из пунктов, которые я перечислил.(прислать можно сюда infocean @ gmail.com)

      Если по какой-то причине не хотите присылать её мне, то проверьте файл /etc/vold.fstab, возможно он организован у вас по-другому.

      Нормальная запись выглядит примерно так:
      dev_mount sdcard /storage/sdcard0 11 /devices/platform/dw_mmc/mmc_host/mmc0/mmc0 encryptable_nonremovable

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

      Удалить
  7. Для определения корневой директории я использую системную папку МУЗЫКА

    ------------------------------
    var

    len: integer;

    music, SDCard, put: string;

    begin

    music:=System.IOUtils.TPath.GetSharedMusicPath;
    // получили адрес папки МУЗЫКА


    len:=Length(music);
    // получили длину этого адреса


    SDCard:=Copy(music, 0, len-5);
    // отрезаем от полученного адреса слово MUSIC,
    // и получаем адрес корневой директории (с последним слешем)
    // лично у меня /storage/SdCard0/


    put:= (SDCard+'Download/my_program/');
    // тут уже конструируем собственные пути...

    end;

    ОтветитьУдалить
    Ответы
    1. Ваш способ был бы удобен, если бы все устройства работали одинаково с файловой системой.
      Но это не так, ваш способ, на одних устройствах будет получать путь карты памяти, а на других внутренней памяти. Т.е. он не подходит для ситуации, когда вам необходимо хранить информацию исключительно на внешней карте памяти.

      Удалить
    2. Андрей, Вы совершенно правы.
      Я использовал этот способ ранее, когда не знал о существовании файлика vold.fstab, и вообще, должен сказать, что Ваши наработки невероятно продвинули мою производительность.

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

      Удалить
    3. Fly 4404 spark - Android 4.2.2
      /storage/SdCard0/
      /storage/SdCard1/

      содержимое папки /mnt
      asec
      cd-rom
      obb
      sdcard
      sdcard2
      secure
      shell

      Удалить
  8. Андрей, могу поделится кодом отправки смс на Delphi XE8. Если Вам это интересно

    ОтветитьУдалить
  9. Здравствуйте Андрей.
    У меня Lenovo A860.Версия Андроида 4.4.2. По третьему варианту нет файла vold.fstab (карта подключена /mnt/sdcard)
    'Cannot open file "/etc/vold.fstab". No such file or directory'.
    Как у Андрея Митьянова.
    С Уважением. Олег.

    ОтветитьУдалить
  10. Здравствуйте Андрей.
    У меня Lenovo A860.Версия Андроида 4.4.2. По второму варианту True только на /mnt/sdcard/
    С Уважением. Олег.

    ОтветитьУдалить