Форсирование режима автомобильного дока (с помощью root)

в 12:36, , рубрики: Android NDK, Разработка под android, метки:

Форсирование режима автомобильного дока (с помощью root)

Введение

Не так давно вышедший Galaxy S3, помимо прочих нововведений в области аксессуаров, получил новый, ужасно скучный автомобильный док. В отличие от предыдущих аппаратов линейки (S, S2, Note), этот был сделан «универсальным», и потому не имел специального резистора, заставляющего телефон переключаться в автомобильный режим. А я, как человек, пользовавшийся в своё время полноценной док-станцией для первого SGS, уже привык, что при установке в док у меня включается режим громкой связи и появляется альбомная ориентация в TouchWiz. Но в новом доке такого достичь было нельзя…

Однако, решение было найдено, и потребовало лишь одной NFC-метки, рута, а также нескольких часов поисков и программирования.

Поиск решения

Первое, что я сделал, ещё когда у меня не было этого «универсального» дока — это разобрал док-станцию от первого SGS. Результат был достаточно близок к желаемому, хотя выглядел довольно забавно:

Форсирование режима автомобильного дока (с помощью root)

На картинке видно, что в области уведомлений красуется «автомобильная» иконка — так вот, она не имеет никакого отношения к Driving Mode (который S-Voice), это именно уведомление от самого Android.

Однако, и сам SGS, и его док пришлось отдать, а потом ко мне приехал новый док, и вопрос переключения режима встал уже в полный рост. Очевидное решение заключалось в использовании NFC-метки для включения автомобильного режима, но как этого добиться? В Google Play есть приложения, которые якобы включают Car Mode, но по факту они используют стандартный метод UiModeManager, который всего лишь запускает Car Home, а столь желаемые мной громкая связь и альбомная ориентация не включаются.

Форсирование режима автомобильного дока (с помощью root)

В документации по Android есть стандартный метод определения того, находится ли устройство в док-станции:

registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)).getIntExtra(Intent.EXTRA_DOCK_STATE, -1);

а также упомянут метод, который это состояние устанавливает:

sendStickyBroadcast((new Intent(Intent.ACTION_DOCK_EVENT)).putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_CAR));

однако, в реальной программе он работать не будет, т.к. конкретно это событие требует специальных прав (ведь оно, по сути, генерируется USB-контроллером). Но даже имея root, как это можно сделать?

Есть простое и достаточно известное решение:

su -c /system/bin/am broadcast -a android.intent.action.DOCK_EVENT --ei android.intent.extra.DOCK_STATE 2

которое, увы, неполно. Да, этот вызов успешно включит полноценный автомобильный режим, однако из-за того, что это обычный broadcast, а не «sticky» (утилита am не умеет этого), вышеупомянутый метод определения состояния док-станции работать не будет, поэтому придётся либо заводить в приложении две кнопки («включить» и «выключить»), либо найти способ послать полноценное системное сообщение.

Немного покопавшись в исходниках Android, можно найти класс DockObserver, который как раз занимается отправкой этого сообщения. Изучив его, легко видеть, что на самом деле он делает чуть-чуть больше, чем просто оповещает о смене режима (например, воспроизводит звук вставки в док), а также то, что делает это он при поступлении особого события (UEvent). То есть, если научиться такое событие создавать, то можно сымитировать не только следствие, но и причину!

Ну и, делается это довольно простым кодом с использованием NDK:

char event[] = "ACTION=changeDEVPATH=/devices/virtual/switch/dockSUBSYSTEM=switchSWITCH_NAME=stateSWITCH_STATE=2";
int sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);

if (sock != -1) {
	struct sockaddr_nl snl;
	struct iovec iov = { event, sizeof(event) };
	struct msghdr msg = { &snl, sizeof(snl), &iov, 1, NULL, 0, 0 };

	memset(&snl, 0, sizeof(struct sockaddr_nl));
	snl.nl_family = AF_NETLINK;
	snl.nl_pid = getpid();
	snl.nl_groups = -1;

	sendmsg(sock, &msg, 0);
	close(sock);
}

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

Заключение

Итак, само приложение доступно в Google Play.
При запуске оно переключает телефон в автомобильный режим, при повторном запуске — выходит из него.
Если повесить его запуск на NFC-метку, можно получить тот самый полноценный автомобильный док.

Ну и, все исходники можно найти на GitHub.

Автор: ginkage

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js