В начале было приложение. И было это приложение для цифровой подписи. Работало оно просто: подписываешь документы на смартфоне, не думая о каких-либо дополнительных токенах. Все упаковано в телефон — и сертификаты, и библиотеки. Процесс знакомый: авторизовался, загрузил файл, подписал.
Но вот однажды пользователи стали жаловаться, что не могут найти ранее подписанные документы. Бывало, пользователь подписал файл, долго к нему не возвращался, а спустя время уже не смог его отыскать. Переустановил приложение или авторизовался с другого устройства, и все файлы как в воду канули.
Меня зовут Георгий Левченко, я Android-разработчик в ПСБ. Начал я искать источник бед пользовательских. Оказалось, виной всему было то, что все файлы хранились в кэш-директории приложения. Понял я: чтобы файлы не пропадали, им нужен менеджмент для файлов. Отыскался у Android для таких целей готовый инструмент — Storage Access Framework (SAF), он служит для доступа к файлам через стандартный интерфейс без управления разрешениями вручную. Поведаю под катом, как я решал с его помощью эту задачу.
Почему именно Storage Access Framework?
Припомним, как менялась история взаимодействия с хранилищем в царстве Android разных версий.
Android 1.0 — начиная с этой версии (и до API 23 в версии 6.0) доступ к хранилищу был неограниченным. Достаточно было указать READ_EXTERNAL_STORAGE разрешение — и, установив приложение, можно было делать все, что заблагорассудится;
Android 6.0 — появление модели Runtime Permission;
Android 4.4 — здесь уже появилась первая версия Storage Access Framework, которая позволяла выбирать файлы без запроса разрешений. Однако с последующими версиями Android работа разработчиков с SAF все больше усложнялась;
Android 11 — в этой версии реализовали хранилище Scoped Storage. Из-за ряда особенностей Scoped Storage нам не подошел.
А в целом мы хотели, чтобы пользователь мог выбирать документ без лишних препятствий. Лучше всего подходило решение Storage Access Framework. На нем я и остановился.
Как хранилище Android файлы по папкам раскладывает?

Здесь можно выделить три составные части:
документ-провайдер — он дает возможность доступа к файлам;
клиентское приложение, которому требуется доступ;
picker — с его помощью пользователь может выбирать нужные файлы из хранилища.
SAF позволяет приложению получать доступ к файлам через диалоги выбора, работать с файлами и папками через ContentResolver, предоставлять временный доступ к конкретным файлам.
При этом у фреймворка есть плюсы, которые и делают его наиболее подходящим решением для такой задачи, какая была у меня:
не ограничивает доступ к файловой системе напрямую;
работает поверх существующей системы разрешений;
требует только явного действия пользователя для предоставления доступа;
может использоваться в сочетании с другими механизмами доступа к файлам.
Какие требования на плечи взвалили?
У нас были требования к процессу работы с файлами для пользователя.
Нужно было реализовать подобие работы с папкой загрузок на ПК: когда пользователь выделил определенную область для файлов и они там хранятся независимо от того, установлено ли еще приложение или его удалили.
Еще важна была возможность сохранять все в файле на SD-карту смартфона. Чтобы при переносе приложения на новое устройство можно было указать, что теперь на этом устройстве, после перепривязки аккаунта, файл следует забирать с SD-карты, а дальше остальные данные уже автоматически бы связывались.
С учетом этих и других требований я составил такой план:
запросить доступ в определенную папку;
сохранить полученный путь;
проверять его доступность перед работой;
по полученному пути работать с файлами.
То есть в picker нужно выбрать папку, сохранить ее — и так получить путь, куда потом будут сохраняться файлы. В дальнейшем приложение при работе с подписью будет проверять эти пути — смотреть, что находится в папке, и управлять файлами.
Что касается запроса доступа, то здесь можно выбрать три типа:
Создать новый файл ACTION_CREATE_DOCUMENT
Открыть документ или файл ACTION_OPEN_DOCUMENT
Предоставить доступ к содержимому каталога ACTION_OPEN_DOCUMENT_TREE (доступно в Android 5.0 уровня API 21 и выше).
Чтобы было наглядно, я покажу пример, как получать такие запросы доступа:

По сути все происходит через обычный activity launcher. И здесь же можно встретить еще один камень преткновения, подсвечу этот момент — вдруг с ним кто-то еще столкнется.
Обратите внимание: все полученные URI, с которыми мы будем получать данные файла, по умолчанию не persistable. То есть они будут доступны не очень долго. Эту проблему можно решить установкой persistable-флага, но злоупотреблять им тоже не стоит, а лучше через какое-то время его релизить, так как он влияет на производительность и/или на объем занимаемой памяти.
И здесь можно увидеть один из контрактов DocumentFile, через который мы получаем данные о файле.
API очень простой: всего лишь получить файл, данные о нем, его объем, метаинформацию и так далее.
Теперь покажу нюансы взаимодействия с файлами.

В этот фрагменте видно, как я кэшировал файлы перед подписью.
Здесь тоже важный момент: я отображаю эти файлы по URI — они предоставляются без MimeType, который отвечает за тип файлов (аудио, видео и так далее). Еще надо постараться не забывать указывать fileExtension. API здесь упрощенный, но не без подводных камней.
Где еще споткнуться можно?
Еще можно набрести на ошибку взаимодействия с External Storage. Скажу честно: я так до конца и не выяснил ее причину. Возможно, дело в реализации вендоров или в Google. Во всяком случае на момент разработки разговоров об этой ошибке на форумах и в Google было множество. И Google, несмотря на заявки, ее игнорировал.

Возникала эта ошибка External Storage нестабильно и в основном на смартфонах Samsung и еще нескольких китайских брендов холдинга BBK (vivo, Realme, OnePlus). Причем exception вылетал на com.android.externalstorage.
Дело было, скорее всего, в том, что у пользователей внешнее хранилище могло быть отключено, и тогда при получении URI и обращении по нему возникала ошибка. Из-за этого мог случиться непредвиденный краш.
Решить эту проблему можно легко: просто обернуть и сделать дополнительный алерт:

Вдобавок на форуме мне подсказали, что нужно делать переход в глубинный permission из настроек. Поскольку найти его непросто, посоветую сначала показывать пользователю alert: мол, такая-то ситуация, — а затем уже делать переход по этому пермишену. Вот как это может быть реализовано:

Что в сухом остатке…
Что же, API у Storage Access Framework довольно простой и бесхитростный. Не нужно выполнять дополнительные запросы в память — просто получаем URI и с ним работаем. Пользователи, которые слабо разбираются в работе смартфона, не будут пугаться доступов в память и записи в нее, а сейчас это особенно насущно, потому что люди волнуются из-за громких историй в СМИ.
Наконец пользователь получает понятный ему стандартный для Google picker, в котором наглядно видно, где и какой файл можно выбрать.
А вам доводилось работать с SAF? Какие трудности из-за плеча глядели, какими решениями их победить удалось? :) Прошу делиться в комментариях!
