Своя оболочка вместо Android Auto: как Headswap превратился в автомобильный лаунчер

Проект Headswap начинался как мост между Android Auto и Baidu CarLife, а спустя месяц превратился в полноценную оболочку для автомобильной магнитолы. После первых 1000 километров на Honda Vezel автор добавил в версию 4.0 собственный рабочий стол, поддержку автомобильных версий 2ГИС и Яндекс Навигатора, голосовой вызов Алисы и запуск любых приложений телефона на экране головного устройства.
Идея появилась из практической необходимости: Android Auto хорошо решает задачу подключения смартфона к автомобилю, но жёстко ограничивает набор приложений и внешний вид интерфейса. Headswap должен был сохранить совместимость с CarLife, одновременно освободив пользователя от ограничений чужой витрины. Исходный код проекта распространяется под лицензией GPL-3.0.
От USB-моста к беспроводному подключению
Первая версия, выпущенная 8 октября, умела только передавать видео и звук Android Auto в CarLife по USB. Уже через несколько дней проект начали проверять на автомобилях Mazda, Volkswagen, Hyundai, Toyota и других марок, причём каждая магнитола по-своему реагировала на протоколы и обрывы соединения.
В версии 2.0 появился беспроводной CarLife. Оказалось, что это не один сетевой канал, а связка из шести TCP-соединений, Wi‑Fi Direct и Bluetooth RFCOMM для передачи параметров сети. Такой режим сделал проект пригодным для большего числа автомобилей, но одновременно выявил новые проблемы: некоторые Mazda неправильно обрабатывали паузы в Wi‑Fi-группе, а отдельные Volkswagen перегревались при одновременной беспроводной зарядке.
Тогда же в Headswap добавили выбор масштаба интерфейса от 100 до 280 dpi. Это было необходимо из-за большого разброса размеров экранов — от семидюймовых до 12-дюймовых магнитол. Позднее из приложения удалили службу специальных возможностей. Изначально она автоматически запускала сервер Android Auto на локальном порте 5277, но требовала нежелательных для многих пользователей разрешений. После отказа от неё сервер нужно было включить вручную один раз, а завершать соединения — корректно, через штатное прощание ByeBye.
Почему проверка сервера могла его сломать
Отдельной технической проблемой стала проверка доступности сервера Android Auto. Простое подключение к порту с немедленным закрытием соединения не подтверждало полноценную готовность сервера. Более того, после примерно пятидесяти таких попыток начинались тайм-ауты. Даже короткое рукопожатие с последующим разрывом могло привести к ECONNREFUSED на 27 секунд.
Решением стала короткая, но полностью завершённая сессия: Headswap открывал необходимые каналы, устанавливал видеофокус, дожидался AuthComplete и только затем отправлял ByeBye. Такой «пробник» не повреждал состояние сервера. Та же логика аккуратного завершения соединения позволила ему переживать многократные остановки и работать неделями без ручного перезапуска.
Прямой проброс видео вместо перекодирования
В версиях 1.0–2.x видеопоток Android Auto декодировался, проходил через OpenGL и снова кодировался в формате, подходящем для CarLife. Такой подход был надёжным, но создавал постоянную нагрузку на процессор смартфона: устройства нагревались, уходили в троттлинг, а на слабых моделях появлялись рывки.
В версии 3.0 автор перешёл к прямому пробросу H.264. Основной сложностью стали капризные декодеры автомобильных магнитол. На Honda после камеры заднего вида или перехода в штатное меню экран мог становиться чёрным, хотя сенсор и звук продолжали работать. Магнитола молча сбрасывала декодер и ждала следующий ключевой кадр, но Android Auto отправлял его редко — примерно раз в 69 секунд на обычной сцене и ещё реже на статичном изображении.
Чтобы решить проблему, Headswap начал самостоятельно формировать IDR-кадр из последнего изображения и добавлять его в поток примерно раз в две секунды. Кадр собирался из макроблоков I16×16, кодировался параллельно четырьмя слайсами, а параметр качества подбирался в диапазоне QP 18–40. Сначала дополнительный кадр увеличивал очередь магнитолы и вызывал задержку отклика на несколько секунд. После этого собственный IDR стал заменять два кадра Android Auto, что устранило накопление задержки.
Для широких экранов появилась обрезка через параметры SPS в заголовке H.264. Это позволило переложить операцию на декодер магнитолы и отказаться от лишнего перекодирования. На Honda пришлось учитывать особенности обработки смещения: интерфейс Android Auto размещался в левом верхнем углу, а поля переносились вправо и вниз.
Зачем понадобился собственный рабочий стол
Android Auto показывает только приложения, у которых есть автомобильная часть и подходящие категории. Сторонние видеоплееры, зеркалирование экрана и обычные приложения могут исчезнуть из интерфейса в зависимости от того, каким устройством представляется Headswap. Кроме того, Google определяет сетку, размеры плиток и расположение элементов, не оставляя возможности адаптировать интерфейс под конкретный экран.
Собственный лаунчер должен был устранить эти ограничения и одновременно сократить цепочку отказов. Вместо последовательной работы Android Auto и CarLife приложение выводило свой рабочий стол на экран магнитолы, сохраняя уже отлаженный видеоконвейер. Первый прототип появился после форка, который 30 октября сделал Ильдар Садыков. Через несколько дней ядро виртуального дисплея перенесли в основной проект.
Виртуальный дисплей без root
Технически оболочка строится вокруг VirtualDisplay, поверхность которого подключена к аппаратному H.264-энкодеру. На виртуальном дисплее запускается собственный лаунчер и другие приложения, а полученный поток проходит через тот же конвейер CarLife. Благодаря этому удалось повторно использовать наработки для нарезки данных, ограничения IDR, кропа через SPS и поддержания соединения во время пауз.
Обычному приложению Android при этом не хватает системных разрешений для запуска чужих активностей на таком дисплее, передачи касаний и вывода клавиатуры в нужном месте. MediaProjection позволяет захватывать изображение, но не даёт полноценно взаимодействовать с чужими приложениями. Служба специальных возможностей потребовала бы вернуть нежелательные разрешения, а root отсёк бы большую часть пользователей.
Выходом стала instrumentation, запускаемая от имени shell. На Android до версии 17 для этого существует штатный механизм, используемый автотестами. Инструмент запускается внутри процесса приложения, но получает права shell через UiAutomation.adoptShellPermissionIdentity(). В Headswap такой процесс держит доверенный дисплей и принимает команды от основной части приложения. Для активации используется команда am instrument, а доступ к каналу защищён токеном, который выдаёт само приложение.
С 3.0 до 4.1.1 проект прошёл путь всего за неделю: в пиковый день в репозиторий попало 59 коммитов. Объём Kotlin-кода вырос с 27,6 до 48,2 тысячи строк, ещё 16,9 тысячи строк заняли тесты. К моменту публикации приложение скачали более 10 тысяч раз, а число подписчиков проекта достигло 2200.


