- PVSM.RU - https://www.pvsm.ru -
Здравствуй Хабр!
В течение всей недели я размышлял о том, стоит ли писать о том что обещал или же по просьбе некоторых читателей дать разъяснения и объяснения по поводу некоторых, на мой взгляд базовых, вещей.
Не хотелось бы, но придется некоторых из вас расстроить. Писать самому — займет слишком много времени и сил, «копипастить» я не люблю больше всего, а делать из статьи кучу ссылок на разные ресурсы — будет моветон.
Например, описывать как происходит загрузка Android в данной статье будет не уместно. Если вы знаете принцип инициализации аппаратного обеспечения вашего компьютера в BIOSе, а затем его загрузка через ядро системы, то Android в этом плане ничем не отличается. Разница лишь в процессорной архитектуре. Структура файловой системы? Ну господа, это же чистой воды UNIX система, и писать где, что и как хранится — абсурдно! Править build.prop — это тюнинг системы. Да, это модификация, но большую часть этих параметров можно сделать сторонними приложениями, при чем удобными для пользования, например System Tuner [1].
Понять самому принципы системы Android заняло у меня пару месяцев, столько же займет времени и писать статьи, чтобы осветить все базовые вещи. Так что давайте лучше будем писать о конкретных примерах как разбирать Dalvik код и создавать на телефоне удобства «пользования».
И так, поехали! Сегодня я расскажу как я реализовал функционал автоматической запись телефонных разговоров родными средствами.
Законодательство некоторых стран запрещает производить запись телефонных разговоров техническим средствами. Например в США запрещается записывать личные телефонные разговоры без предварительного согласия сторон. А на нашем пост-советском пространстве разрешено записывать беседу, в которой вы являетесь одной из сторон, без предупреждения других участников беседы. В том же самом Китае запись просто напросто приветствуется и «стучать» на соседа — идеологический принцип. И это касается не только записей разговоров. Пользование интернетом, отправка СМС сообщений, пользование соц сетями, алгоритмы шифрования, телефоны с двумя SIM картами и многое другое также индивидуально регламентируются законодательством разных стран. А теперь представьте, что вы владелец бизнеса и экспортируете свою IT продукцию по всему миру. Разумеется, свое программное обеспечение вы будете «писать» одной веткой, а не несколькими, но вот конфигурации будут отличаться от региона к региону. Для стран СНГ одно, для Европы — другое. Также и поступают производители телефонов. Операционная система Android дорабатывается только одной группой программистов и в основную ветку, а для каждого региона при компиляции финальных релизов используются специфичные конфигурационные файлы. Говорю это с уверенностью, так как работал в свое время у одного из вендоров.
В доказательство этому есть хорошее руководство [2] как делать портирование прошивки на чужое устройство.
Читая дизассемблированый JAVA код Phone.apk я случайно наткнулся на занимательный флаг. Интересного, но скрытого от нас функционала на самом деле очень много. Буквально сегодня обнаружил, что на моем телефоне есть параметры для японского оператора KDDI [3]. Специально сформированное СМС сообщение от провайдера может заставить мой телефон истошно издавать звуки и вибрировать несколько минут в случае землетрясения или цунами. Но вернемся к нашему флагу.
public static final boolean IS_INCALL_RECORDING_ENABLE = false;
Занимательно, подумал я. Если есть такой флаг, значит он где-то используется. Но вот где — не понятно. Тем не менее, я предположил, что кнопка записи звонка должна появляться во время звонка. Дело за малым! Я изменил FALSE на TRUE, перезаписал патченный Phone.apk, позвонил на домашний телефон, поднял трубку и увидел кнопку «Начать запись». А ведь раньше ее не было!!!
[4]
Еще раз расскажу как делать подобные вещи для усвоения материала:
java -Xmx512m -jar baksmali.jar -a <API LEVEL> -d <FRAMEWORK DIR> -o Phone -x Phone.jar
<API LEVEL> — это API вашей версии Android. Для JB — это 16
<FRAMEWORK DIR> — папка, где находятся все фреймворки прошивки.
.field public static final IS_INCALL_RECORDING_ENABLE:Z = false
на
.field public static final IS_INCALL_RECORDING_ENABLE:Z = true
java -Xmx512m -jar smali.jar -a 16 Phone -o classes.dex Протестировав запись нескольких звонков, я обнаружил, что телефон записывает разговоры стандартным встроенным диктофоном и при чем в очень и очень хорошем качестве. Название файлов формируется автоматически и содержит номер или имя собеседника, а также дату и длительность звонка. Формат сохранения файлов также можно настроить в родном приложении. А зачем вы спросите мне вообще нужны эти записи звонков?
Ну, во-первых, очень удобно. Позвонила супруга и наговорила список продуктов для покупки, а записать некуда, тут-то и поможет диктофон.
Во-вторых, по телефону мне приходится разговаривать с клиентами и заказчиками и порой нужная и важная информация может ускользнуть или быть не расслышана. Или, допустим, состоялся важный разговор, который надо прослушать и проанализировать и принять своевременные решения иди действия вместе с партнером по бизнесу или кем-то еще.
В-третьих, очень удобная доказательная база в работе и быту.
Вы также можете спросить а почему бы не пользоваться сторонними приложениями, коих полно в открытом и бесплатном доступе?
Всем разработчикам под Android известно, что в системе полно различных стандартных широковещательных сообщений. Что бы ни произошло в системе, любое приложение его может получить, если реализовать «своего» получателя такого сообщения. Можно сделать свои широковещательные сообщения, только получатель этого сообщения будет само приложение или все приложении, которые сделали вы сами и они это сообщение как-то обрабатывают. Я такое в практике видел в GoDialer и GoSMSPro.
Таким же образом работают и сторонние приложения записи звонков. Как только прошло сообщение, что был установлен звонок, включается запись. Как только звонок прекратился, запись останавливается и буфер записывается в файл.
Моя задача состояла в том, чтобы найти то место, где формируется это самое сообщение или обрабатывается и принудительно начинать запись звонка без лишних телодвижений. Ведь часто бывает, что или забываешь включать или просто-напросто не успеваешь. Как вообще искать нужное место в тонне кода прошивки — тема следующей статьи, а пока перейдем сразу к «нашему месту».
Обработчик, а точнее два, оказались в файле comandroidphoneCallNotifier.java
Декомпилированный код (здесь показана только часть кода) из Dalvik в Java оказался следующим:
private void onCallConnected(AsyncResult paramAsyncResult)
{
Connection localConnection = (Connection)paramAsyncResult.result;
String str = ((IfConnection)localConnection).getDialString();
VLog.d("onCallConnected() dialed number:" + str);
removeMessages(120000);
removeMessages(120001);
this.mIsEccNeedRetry = false;
this.mEccIsSwitchingForRetrying = false;
// много много кода
}
и
private void onDisconnect(AsyncResult paramAsyncResult)
{
Phone.State localState = this.mCM.getState();
if (CallNotifier.VDBG)
super.log("onDisconnect()... CallManager state: " + this.mCM.getState());
VLog.d(this, "onDisconnect()");
removeMessages(120000);
removeMessages(120001);
// много много кода
}
Задача усложняется, по сравнению с прежней статьей. Если в прошлый раз нам надо было исправить простую функцию, то здесь мы ее исправить не сможем, так как у нас нет исходного кода, чтобы его переписать. Здесь нам нужно вживить свой код.
Об этом кратко написано здесь [7] и здесь [8]. Если говорить на еще более простым языком — байт код виртуальной машины основан на регистрах (выделенная область памяти для оперирования переменными) и множества инструкций и операторов. Смысл и принцип работы довольно прост: вы записываете в регистр какое-то значение и затем совершаете над ним операции, результат операции возвращаете туда, от куда за действиями обращались. Более подробно обо всех операторах и инструкциях можно подсмотреть здесь: Bytecode for the Dalvik VM [9].
Для того чтобы что-то вписать, нам надо знать что туда вписывать. Слизать код можно с обработчика кнопки «Начать запись».
Найти где хранится обработчик даже начинающим программистам для Android это составит труда. Мы потом еще вернемся к тому что и как искать в будущих статьях. Суть первых статей объяснить принципы.
При нажатии кнопки, срабатывает следующий код:
VoiceRecorderHelper localVoiceRecorderHelper = VoiceRecorderHelper.getInstance();
if (!localVoiceRecorderHelper.isRecording())
{
localVoiceRecorderHelper.start();
}
То есть чтобы автоматизировать запись всех звонков, надо этот код добавить в обработчик onCallConnected.
Dalvik код этой записи выглядит как
invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;
move-result-object v1
invoke-virtual/range {v1 .. v1}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z
move-result v2
const/4 v3, 0x0
if-ne v3, v2, :cond_a9
invoke-virtual/range {v1 .. v1}, Lcom/android/phone/util/VoiceRecorderHelper;->start()Z
:cond_a9
Разберем код построчно:
Возвращаемся к нашему onCallConnected. Его dalvik код выглядит следующим образом:
.method private onCallConnected(Landroid/os/AsyncResult;)V
.registers 8
.parameter "r"
.prologue
.line 2302
iget-object v0, p1, Landroid/os/AsyncResult;->result:Ljava/lang/Object;
check-cast v0, Lcom/android/internal/telephony/Connection;
.local v0, c:Lcom/android/internal/telephony/Connection;
move-object v2, v0
Давайте и этот код разберем, чтобы было понятно что к чему относится
.registers 8 — количество регистров памяти, необходимое и используемое для данной функцииparameter "r" — название параметра, которое было использовано в исходном коде. Нас оно редко интересует.prologue — начало алгоритма функции.line 2302 — номер строки в исходном коде. Это только для отладки.iget-object v0, p1, Landroid/os/AsyncResult;->result:Ljava/lang/Object; следующая строка соответствует (Connection)paramAsyncResult.result; check-cast v0, Lcom/android/internal/telephony/Connection; соответствует Connection .local v0, c:Lcom/android/internal/telephony/Connection; соответствует localConnection move-object v2, v0 — клонирование локальной переменной v0 в регистр v2Разбирать код не так уж и сложно, если обращаться к описанию и сравнивать с Java кодом.
Казалось бы, нам нужно всего лишь скопировать код из обработчика нажатия кнопки и вставить в начало нашего обработчика звонка и готово. Не тут то было. Иногда это работает, но большей частью нет. Дело в том, что регистры, куда мы записываем данные, могут использоваться в дальнейшем коде программы и если в начале мы возьмем неправильный регистр и запишем в него что-то, то по ходу исполнения программы могут вылезть ошибки и поломаться весь алгоритм. Наш случай прост в том, что мы вписываем в начало функции и можем использовать любые регистры, которые не инициализированы в начале, потому как они будут перезаписаны в дальнейшем. Но часто код приходится вживлять где-то в середине программы и с регистрами нужно быть осторожным. Об этом тоже в будущих статьях.
Наши первые две строки вживляемого кода имеют следующее:
invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;
move-result-object v1
Самый простой способ искать подходящие номера регистра — это поискать в самом методе какие виды данных записываются в необходимые номера регистров. Если поискать в коде, то обнаружим, что move-result-object у нас записывается и в v2 и v 3.
Соответственно все наши v1 во вживляемом кода заменим на v2 или v3
Проделав все операции по замене номеров регистров во вживляемом кода получаем следующую картину:
invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;
move-result-object v3
invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z
move-result v4
const/4 v5, 0x0
if-ne v5, v4, :cond_27
invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->start()Z
:cond_27
Стоит отметить, что маркер cond_a9 мы изменили на cond_27. Дело в том, что маркер cond_a9 уже имелся в том файле, куда мы вживляли код и второй раз такой маркер не может быть использован. Номер маркера — шестнадцатиричный код и может быть любым, главное уникальным.
Теперь в исходном файле заменяем строку .line 2302 на наш вживляемый код и получаем
.method private onCallConnected(Landroid/os/AsyncResult;)V
.registers 8
.parameter "r"
.prologue
invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;
move-result-object v3
invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z
move-result v4
const/4 v5, 0x0
if-ne v5, v4, :cond_27
invoke-virtual/range {v3 .. v3}, Lcom/android/phone/util/VoiceRecorderHelper;->start()Z
:cond_27
.line 2302
iget-object v0, p1, Landroid/os/AsyncResult;->result:Ljava/lang/Object;
check-cast v0, Lcom/android/internal/telephony/Connection;
.local v0, c:Lcom/android/internal/telephony/Connection;
move-object v2, v0
Осталось теперь собрать наш код с помощью команды java -Xmx512m -jar smali.jar -a 16 Phone -o classes.dex, заменить в Phone.apk и протестировать.
В Java варианте наша работа стала выглядеть следующим образом:
private void onCallConnected(AsyncResult paramAsyncResult)
{
VoiceRecorderHelper localVoiceRecorderHelper = VoiceRecorderHelper.getInstance();
if (!localVoiceRecorderHelper.isRecording())
{
localVoiceRecorderHelper.start();
}
Connection localConnection = (Connection)paramAsyncResult.result;
String str = ((IfConnection)localConnection).getDialString();
VLog.d("onCallConnected() dialed number:" + str);
removeMessages(120000);
removeMessages(120001);
Все хорошо, все работает, но единственная проблема, после завершения разговора, запись звонка продолжается бесконечно.
Для этого нам надо прописать подобный код и в начале функции (метода) onDisconnect, только с обратной логикой.
VoiceRecorderHelper localVoiceRecorderHelper = VoiceRecorderHelper.getInstance();
if (localVoiceRecorderHelper.isRecording())
{
localVoiceRecorderHelper.stop();
}
По аналогии с началом записи, мы пишем небольшую процедуру, заменяя номера регистров
.method private onDisconnect(Landroid/os/AsyncResult;)V
.registers 41
.parameter "r"
.prologue
invoke-static {}, Lcom/android/phone/util/VoiceRecorderHelper;->getInstance()Lcom/android/phone/util/VoiceRecorderHelper;
move-result-object v34
invoke-virtual/range {v34 .. v34}, Lcom/android/phone/util/VoiceRecorderHelper;->isRecording()Z
move-result v4
if-eqz v4, :cond_33
invoke-virtual/range {v34 .. v34}, Lcom/android/phone/util/VoiceRecorderHelper;->stop()Z
.line 2487
:cond_33
собираем наши изменения, заменяем в телефоне и вуаля — все работает так как должно.
Уверен, данный материал по сравнению с предыдущей статьей оказался в несколько раз сложнее и запутанней. Какие-то регистры, операторы, модификаторы… Похоже на бред. Я сам в первый раз когда увидел Dalvik — ужаснулся, закрыл страничку и не открывал ее в течение полу года. Когда прижало к стенке, в течение двух недель быстро разобрался что к чему и как это реализовать на практике. Что радует, за всю практику ни разу не получал кирпич.
Не для рекламы ради, хочу посоветовать два ресурса, на которых можно почерпнуть много информации:
Русскоязычный [10] и Англоязычный [11]
На обоих ресурсах я присутствую с тем же ником.
А пока до следующей статьи, надеюсь через неделю.
Автор: Falseclock
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android/36130
Ссылки в тексте:
[1] System Tuner: https://play.google.com/store/apps/details?id=ccc71.pmw&hl=ru
[2] руководство: http://forum.xda-developers.com/showthread.php?t=1222746
[3] KDDI: http://ru.wikipedia.org/wiki/KDDI
[4] Image: http://radikal.ru/F/i051.radikal.ru/1306/2c/d0f87003c5d2.jpg.html
[5] smali: https://code.google.com/p/smali/downloads/detail?name=smali
[6] backsmali: https://code.google.com/p/smali/downloads/detail?name=baksmali
[7] здесь: http://ru.wikipedia.org/wiki/Dalvik_virtual_machine
[8] здесь: http://android-shark.ru/virtualnaya-mashina-dalvik/
[9] Bytecode for the Dalvik VM: http://s.android.com/tech/dalvik/dalvik-bytecode.html
[10] Русскоязычный: http://4pda.ru/forum/index.php?showforum=281
[11] Англоязычный: http://forum.xda-developers.com/index.php
[12] Источник: http://habrahabr.ru/post/182640/
Нажмите здесь для печати.