
О сколько нам открытий чудных
Готовят просвещенья дух
В данной статье пойдет речь о VoLTE, о Pixel 9, и о том, как включить VoLTE (и заодно VoWiFi) на данном устройстве.
Сначала предыстория
Был у меня Pixel 4a, работал нормально, меня полностью устраивал до определенного момента.
Момент этот - получение критического обновления, после которого емкость его батареи стала вдвое меньше.
У него и до этого заряда хватало на сутки, а после обновления и вовсе везде пришлось с зарядкой ходить. Так долго продолжаться не могло, выбрал Pixel 9.
Немного попользовавшись им, выяснилась проблема, а именно, периодически до меня не могли дозвониться, как, впрочем, и я.
Почему вызов может не состояться?
Для этого немного теории без подробностей на пальцах.
Представим сеть оператора.
В ней есть два ядра - ядро канальной коммутации (CS Core) и ядро пакетной коммутации (PS Core). Ядро канальной коммутации обеспечивает голосовую связь. Ядро пакетной коммутации предназначено для доступа к IP сетям и выхода в сеть интернет.
Cети второго поколения (2G) и третьего поколения (3G) подключаются к ядру канальной коммутации для осуществления голосовых вызовов. Также они подключаются и к ядру пакетной коммутации для выхода в сеть Internet.
Сеть четвертого поколения (4G) подключается только к ядру пакетной коммутации.

На этом месте возникает вопрос - как же тогда передается голос, если есть подключение только к ядру пакетной коммутации для выхода в интернет.
А происходит следующее - перед тем как принять вызов, устройство переходит в сеть предыдущего поколения с перерегистрацией в ядре канальной коммутации. Простыми словами - устройство падает из 4G в сеть предыдущего поколения 3G/2G. Этот процесс так и называется - CS Fallback.
Это можно наблюдать у себя на телефоне. Для этого:
Удостоверимся, что телефон находится в 4G
Делаем на него вызов
Наблюдаем как он переходит в 3G/2G
Начинается вызов
У кого-то из вас может такого не наблюдаться, и телефон так и остается в 4G при вызове.
Снова напрашивается вопрос - как тогда происходит вызов, ведь мы помним, что в 4G есть подключение только к ядру пакетной коммутации для выхода в интернет.
Ответом на этот вопрос является технология VoLTE, которая позволяет совершать голосовые вызовы через сеть 4G/LTE.
Для ее поддержки у оператора появилось ядро IMS. Телефон регистрируется в ядре IMS и после этого вызовы происходят в 4G без падения в сети предыдущего поколения.
Ядро IMS также используется и для технологии VoWiFi.

Поэтому, причины, по которым вызов может не состояться, следующие:
Не происходит падение в сети предыдущего поколения. Возможно, базовая станция, к которой подключается телефон, предназначена только для 4G, может что-то другое
На телефоне не включено использование VoLTE
Можно просниферить сеть для выяснения первой причины, однако, никак на базовую станцию повлиять не удастся. Поэтому остается разбираться со второй причиной, а именно, включить VoLTE на телефоне.
Как включить VoLTE?
Удобно когда эта опция доступна в настройках устройства. Как правило, эта настройка есть по следующему пути – Настройки –> Сеть и интернет –> SIM-карты –> Переходим в свою сим-карту и там, среди прочего, должна быть опция VoLTE.
К сожалению, на моем Pixel 9 этой опции не было.
Идем на Android портал и находим следующее.
В Android 6.0 и более поздних версиях предусмотрена возможность предоставления привилегированным приложениям конфигурацию платформы, специфичной для оператора.
Для этого надо создать приложение, объявить класс, который переопределяет android.service.carrier.CarrierService, и переопределить метод onLoadConfig, чтобы возвращать требуемые значения на основе переданного объекта service.carrier.CarrierIdentifier.
Подробнее можно прочитать тут - https://source.android.com/docs/core/connect/carrier
Казалось бы, все просто, но есть нюанс - это приложение должно быть подписано сертификатом, подпись которого совпадает с подписью на SIM-карте.
Ну что же, данный вариант не подходит по причине отсутствия требуемого сертификата.
Нужен другой вариант.
Как включить VoLTE по-другому?
На этом месте, полагаю, читатель скажет - рутани телефон и подредактируй файл build.prop. Не парься, делов-то на 5 минут.
Конечно и мне такая идея приходила, и не раз, но идея рутовать телефон мне не очень нравилась, поэтому есть продолжение.
Как сделать без рутования?
Ответ будет похож на ответ кэпа - для изменения настроек надо вызвать внутренний API.
Как вызвать внутренний API?
Android SDK, который предоставляется разработчикам, не содержит внутреннего API. Если же есть какие-то вызовы из публичного API к внутреннему, то они помечены аннотацией hide.
То есть, буквально, пакетов com.android.internal.telephony просто нет. Однако, телефон работает, поэтому, на самом деле они есть, но только на телефоне.
Получается, если хочется использовать подобные пакеты у себя в приложении, явным образом импортируя их, то надо делать кастомный SDK, который будет их содержать.
Задача эта вполне решаемая, но останавливаться подробно на ней я не буду (так как есть и другой вариант), только перечислю, что надо сделать:
Скачать framework.jar через adb с телефона
Распаковать скачанный framework.jar
Декомпилировать dex файлы из framework.jar
Взять android.jar из SDK и распаковать
Объединить их
Собрать объединенное как один android.jar
Положить вместо оригинального android.jar в SDK.
Как я уже выше написал, есть другой вариант, при котором не надо собирать свой кастомный SDK.
Думаю все уже догадались – использовать java reflection.
С этим разобрались, но это еще не все.
Вызвать внутренний API все равно не получится.
Как вызвать внутренний API по другому?
Для этого нужно понять, как приложение использует внутренний API.
Например, если приложение хочет получить идентификатор активной подписки, то следует вызвать SubscriptionManager#getActiveDataSubscriptionId().
На самом деле это процесс меж процессного взаимодействия (interprocess communication или IPC), процесса приложения и процесса системного сервера, просто фреймворк Android выполняет внутреннюю работу за нас.
Android использует Binder для выполнения этого типа IPC.
Binder позволяет серверной стороне узнать uid и pid клиентской стороны, чтобы системный сервер мог проверить, имеет ли приложение разрешение на выполнение операции.
Для преодоления этого будем использовать Shizuku. Для интересующихся, можно почитать тут - https://shizuku.rikka.app
В итоге получение идентификатора активной подписки будет выглядеть примерно следующим образом:
private int subscriptionId() throws Throwable {
Log.i(TAG, "Getting subscription id");
try {
Method getActiveDataSubscriptionIdMethod = getIsubStubClass().getMethod("getActiveDataSubscriptionId");
getActiveDataSubscriptionIdMethod.setAccessible(true);
Object id = getActiveDataSubscriptionIdMethod.invoke(sub());
Objects.requireNonNull(id);
return (int) id;
} catch (Throwable e) {
Log.e(TAG, "Error getting subscription id", e);
throw e;
}
}
@NonNull
private static Class<?> getIsubStubClass() throws ClassNotFoundException {
return Class.forName("com.android.internal.telephony.ISub$Stub");
}
@NonNull
private Object sub() {
return Objects.requireNonNull(interfaces.computeIfAbsent("isub", s -> {
try {
Method isubAsInterfaceMethod = getIsubStubClass().getMethod("asInterface", IBinder.class);
isubAsInterfaceMethod.setAccessible(true);
return (IInterface) isubAsInterfaceMethod.invoke(null, new ShizukuBinderWrapper(getServiceManager(s)));
} catch (Throwable e) {
throw new IllegalStateException(e);
}
}
));
}
@NonNull
private static IBinder getServiceManager(String sn) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Class<?> serviceManagerClass = Class.forName("android.os.ServiceManager");
Method getServiceMethod = serviceManagerClass.getMethod("getService", String.class);
getServiceMethod.setAccessible(true);
return (IBinder) Objects.requireNonNull(getServiceMethod.invoke(null, sn));
}
Теперь дело за малым - создаем приложение.
В заключение
Тут каждый сам для себя выбирает, что ему ближе - свое родное собственноручно сделанное, или скачать https://play.google.com/store/apps/details?id=ru.dwerty.android.pims
В любом случае, если вы будете применять данный подход, перед запуском приложения необходимо установить и запустить Shizuku.
После включения VoLTE и VoWiFi в настройках SIM-карты на вашем смартфоне появятся опции "VoLTE" и "Звонки по Wi-Fi".


При обновлении Android настройки будут сброшены, поэтому для использования VoLTE и VoWiFi вам понадобится снова их включить.