Пишемо свій Dialer для Android і працюємо з дзвінками на низькому рівні

  1. Зміст статті В Android існує програмний інтерфейс до низкорівневому апаратного стека телефону. З...
  2. Приймаємо перший дзвінок
  3. Приймаємо другий дзвінок
  4. Поклади трубку!
  5. INFO
  6. Липкий список Google
  7. INFO
  8. телефонуємо
  9. Ода маніфесту
  10. висновок

Зміст статті

В Android існує програмний інтерфейс до низкорівневому апаратного стека телефону. З його допомогою можна написати програму для набору номера або обробляти вхідний дзвінок - наприклад, негласно включити запис з мікрофону або ініціювати відправку поточних координат . Словом, цікавих штук можна придумати масу.

Однією з найпопулярніших програм часів Symbian і «Нокії» був так званий чорний список дзвінків, що дозволяє захистити тонку натуру власника телефону від небажаних абонентів. І хоча сьогодні подібна функціональність інтегрована в деякі прошивки смартфонів, часто такі можливості зводяться лише до банального перманентного «бану» контакту в адресній книзі. У дослідницьких цілях розглянемо, як подібний механізм реалізується на практиці. Будемо вважати, що ти давно читаєш рубрику «Кодінг», живеш в Android Studio і лаєшся виключно на Java.

Типовий чорний список

А де у нього кнопочки?

Яким би не було додаток, офіційним або негласним (тільки для особистого користування з метою дослідження, природно), однаково погано, якщо воно буде падати через відсутність на пристрої телефонних функцій (Wi-Fi-планшет). Тому перше, що варто зробити, - перевірити такі:

PackageManager pm = getPackageManager (); boolean isTelephonySupported = pm.hasSystemFeature (PackageManager.FEATURE_TELEPHONY); boolean isGSMSupported = pm.hasSystemFeature (PackageManager.FEATURE_TELEPHONY_GSM);

Як бачиш, ми скористалися методом hasSystemFeature з об'єкта PackageManager, вказавши константу FEATURE_TELEPHONY як параметр. Крім того, має сенс додатково перевірити підтримку GSM-модуля константою FEATURE_TELEPHONY_GSM.

Якщо обидві константи брехливі, то ми помилилися пристроєм, нічого не поробиш. У цьому випадку варто завершити роботу додатка, а на виході попросити користувача змінити девайс;).

Приймаємо перший дзвінок

За допомогою класу PhoneStateListener в Android'е відстежується стан телефону, але лише в тому випадку, якщо додаток запросило повноваження READ_PHONE_STATE в своєму маніфесті:

<Uses-permission android: name = "android.permission.READ_PHONE_STATE" />

Далі необхідно перевизначити і зареєструвати метод onCallStateChanged в реалізації PhoneStateListener, щоб отримувати повідомлення про зміну стану телефонного виклику. Готова реалізація представлена ​​нижче:

PhoneStateListener stateListener = new PhoneStateListener () {public void onCallStateChanged (int state, String incomingNumber) {switch (state) {case TelephonyManager.CALL_STATE_IDLE: break; case TelephonyManager.CALL_STATE_OFFHOOK: break; case TelephonyManager.CALL_STATE_RINGING: doMagicWork (incomingNumber); // Поступив дзвінок з номера incomingNumber break; }}}; ... TelephonyManager.listen (stateListener, PhoneStateListener.LISTEN_CALL_STATE); // Розміщуємо в onCreate активності

Коли надходить дзвінок, цілочисельний параметр state приймає значення CALL_STATE_RINGING, що призводить до виклику нашої бойової (або мирної) навантаження у вигляді функції doMagicWork.

Коли надходить дзвінок, цілочисельний параметр state приймає значення CALL_STATE_RINGING, що призводить до виклику нашої бойової (або мирної) навантаження у вигляді функції doMagicWork

Вхідний дзвінок

У природі цей варіант прийому телефонного дзвінка використовується трохи рідше, ніж ніколи. Справа в тому, що в момент дзвінка програма має працювати на передньому плані, - таке своєрідне використання придумати складно (хіба тільки в налагоджувальних цілях), тому рухаємося далі.

Приймаємо другий дзвінок

Коли стан телефону змінюється (наприклад, в результаті прийому дзвінка), об'єкт TelephonyManager починає транслювати намір (Intent) з дією ACTION_PHONE_STATE_CHANGED.

Наміри - міжпрограмний фреймворк для обміну повідомленнями. Наміри широко використовуються в Android для запуску / зупинки активностей і сервісів, трансляції повідомлень по всій системі, неявного виклику активностей, сервісів і широкомовних приймачів.

Широкомовні приймачі - компоненти, за допомогою яких програма може відстежувати наміри і реагувати на будь-які отримані дії. Приймачі реалізують подієву модель взаємодії додатків і системи. Більш докладно тема створення широковещательного приймача розглянута в статті « Хакерський Cron на Android ».

Як і в попередньому випадку, додаток повинен отримати дозвіл READ_PHONE_STATE в маніфесті:

<Uses-permission android: name = "android.permission.READ_PHONE_STATE" />

Там же реєструється і широкомовний приймач, здатний відстежувати трансляцію наміри:

<Receiver android: name = "PhoneStateChangedReceiver"> <intent-filter> <action android: name = "android.intent.action.PHONE_STATE" /> </ intent-filter> </ receiver>

При такому підході ми завжди можемо отримувати інформацію про вхідні дзвінки, навіть якщо додаток в даний момент не запущено.

Намір, що повідомляє про зміну стану телефону, буде містити два параметри: EXTRA_STATE_RINGING - ознака вхідного дзвінка і EXTRA_INCOMING_NUMBER - телефонний номер абонента.

public class PhoneStateChangedReceiver extends BroadcastReceiver {@Override public void onReceive (Context context, Intent intent) {String phoneState = intent.getStringExtra (TelephonyManager.EXTRA_STATE); if (phoneState.equals (TelephonyManager.EXTRA_STATE_RINGING)) {String incomingNumber = intent.getStringExtra (TelephonyManager.EXTRA_INCOMING_NUMBER); doMagicWork (incomingNumber); // Поступив дзвінок з номера incomingNumber}}}

Такий підхід і слід використовувати на практиці.

Поклади трубку!

Отже, телефон весело дзвонить, номер вхідного визначено, наш широкомовний приймач спрацював. Що далі?

Якщо розглядати варіант чорного списку або ж бота, що виконує команди ззовні, то непогано б навчитися вішати трубку, не привертаючи уваги користувача. Апаратний стек телефону дуже схожий на нульове кільце (ring 0) в Windows, в тому сенсі, що теж є низькорівневий системний компонент. Тому не існує стандартного способу до нього дістатися (особливо якщо у тебе нерутованний апарат).

Як варіант, можна спробувати використовувати мову опису інтерфейсів (Android Interface Definition Language, AIDL) для забезпечення взаємодії між процесами між компонентами системи.

Для цього необхідно додати в проект файл-інтерфейс ITelephony.aidl такого вигляду:

package com.android.internal.telephony; interface ITelephony {boolean endCall (); void answerRingingCall (); void silenceRinger (); }

Наступний код підхопить інтерфейс і, використовуючи рефлексію, «покладе» трубку:

import java.lang.reflect.Method; import com.android.internal.telephony.ITelephony; ... TelephonyManager telephony = (TelephonyManager) context.getSystemService (Context.TELEPHONY_SERVICE); try {Class c = Class.forName (telephony.getClass (). getName ()); Method m = c.getDeclaredMethod ( "getITelephony"); m.setAccessible (true); telephonyService = (ITelephony) m.invoke (telephony); telephonyService.endCall (); } Catch (Exception e) {e.printStackTrace (); }

Щоб це господарство запрацювало, додаток повинен отримати ще один дозвіл в маніфесті:

<Uses-permission android: name = "android.permission.MODIFY_PHONE_STATE" />

Через це застосувати подібний спосіб на пристроях з Android 2.3 і вище не вийде, так як починаючи з Gingerbread даний дозвіл вважається системним і спроба його використовувати призведе до падіння програми:

Neither user 10031 nor current process has android.permission.MODIFY_PHONE_STATE

Але ж в Google Play повно додатків, що реалізують чорний список! Як же вони працюють? Умовно їх можна розділити на дві групи (крім тих, хто чесно юзает AIDL): фальшивки і ... милиці. Перші всього лише імітують роботу, періодично показуючи в шторці статистику «заблокованих» дзвінків (і СМС). Натомість вони вимагають доступ в інтернет, скачують кілотонни реклами, яку крутять по приводу і без. Розрахунок тут будується на тому, що користувач не відразу виявить обман і свою порцію банерів гарантовано отримає (гомеопатія в чистому вигляді). Такі програми навряд чи відповідають рубриці «Кодінг», тому ми їх пропускаємо.

Додатки другої групи намагаються обірвати дзвінок нетривіальними способами - наприклад, прикидаючись користувачем і натискаючи кнопки:

public static void answerPhoneHeadsethook (Context context) {// «Натискаємо» і «відпускаємо» кнопку на гарнітурі Intent buttonDown = new Intent (Intent.ACTION_MEDIA_BUTTON); buttonDown.putExtra (Intent.EXTRA_KEY_EVENT, new KeyEvent (KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK)); context.sendOrderedBroadcast (buttonDown, "android.permission.CALL_PRIVILEGED"); Intent buttonUp = new Intent (Intent.ACTION_MEDIA_BUTTON); buttonUp.putExtra (Intent.EXTRA_KEY_EVENT, new KeyEvent (KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK)); context.sendOrderedBroadcast (buttonUp, "android.permission.CALL_PRIVILEGED"); }

Оригінальним, але цілком робочим може бути метод зменшення гучності небажаного дзвінка до нуля:

AudioManager audioManager = (AudioManager) context.getSystemService (Context.AUDIO_SERVICE); int ringerMode = audioManager.getRingerMode (); audioManager.setRingerMode (AudioManager.RINGER_MODE_SILENT);

Використовуючи об'єкт AudioManager, ми спочатку отримуємо поточний звуковий профіль getRingerMode (), а потім встановлюємо безшумний режим AudioManager.RINGER_MODE_SILENT.

Після того як дзвінок припиниться (поточний стан зміниться на EXTRA_STATE_IDLE), відновлюємо вихідний режим:

audioManager.setRingerMode (ringerMode);

Але навіть в цьому випадку не обійтися без спеціальних дозволів:

<Uses-permission android: name = "android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android: name = "android.permission.WRITE_SETTINGS" /> <uses-permission android: name = "android.permission.WRITE_SECURE_SETTINGS" / >

Тут ми не блокуємо номер як такої, швидше за просто не піднімаємо трубку, проте такий підхід не вимагає ніяких «викрутасів».

Зустрічаються додатки, які з перемінним успіхом намагаються отримати доступ до кнопок на екрані вхідного дзвінка, як, наприклад, тут .

Зустрічаються додатки, які з перемінним успіхом намагаються отримати доступ до кнопок на екрані вхідного дзвінка, як, наприклад,   тут

INFO

До даного питання можна віднести задачу виведення інформації поверх активності вхідного дзвінка (як варіант - повна візуальна заміна вікна для маскування), але з міркувань безпеки Android не дозволяє створювати власні активності для цих цілей. Проте це не поширюється на системні вікна. цікава стаття по темі.

Як показує практика, багато «хакі», непогано функціонують на одних пристроях, на інших в кращому випадку не працюють, а в гіршому - руйнують додаток під час вхідного дзвінка. Відповідно, оцінки подібних додатків скачуть від одиниці ( «Нічого не працює, поверніть гроші!») До п'яти ( «Користуюся вже двадцять років, все влаштовує!»).

Так працює чи ні?

На цьому можна було б поставити жирну крапку, якби раптово «корпорація добра» не зробила хід слоном.

Липкий список Google

В Android 7.0 Nougat (API 24) з'явився клас BlockedNumberContract - той самий чорний список, але вже не у вигляді компонента прошивки смартфона, а в якості повноцінного об'єкта ОЗ. Всі дзвінки (а також СМС і електронні листи) від відправників з цього списку будуть автоматично відхилені системою.

BlockedNumberContract є стандартний контент-провайдер, працювати з яким можуть, по-перше, системні програми, по-друге, додатки для СМС і телефонії, задані як зі стандартними програмами (Default App). Властивість «за замовчуванням» повинен встановити сам користувач - це одна з парадигм безпеки Android починаючи з версії 4.4. Для телефонії вказане властивість наділяє код правом не тільки обробляти вхідні та вихідні дзвінки, але і змінювати базу даних (наприклад, видаляти окремі дзвінки з логів). Тому, до речі, варто дуже насторожено ставитися до тих додатків, навіть з Google Play, які намагаються отримати прапор «за замовчуванням» і при цьому мають необмежений доступ в інтернет, - ймовірність зливу інформації досить висока.

INFO

Більш детальну інформацію про додатки «за замовчуванням» можна почерпнути з цієї статті .

Робота з BlockedNumberContract нагадує взаємодію з базою даних: використовуються впізнавані методи вставки, видалення і, зрозуміло, вибірки записів.

Контент-провайдер - поділюване постійне сховище (як правило, база даних SQLite), яке містить інформацію, що відноситься до додатка, і керує нею. Переважний спосіб обміну даними між різними програмами.

Щоб забанити номер телефону, викликаємо стандартний метод getContentResolver (). Insert:

ContentValues ​​values ​​= new ContentValues ​​(); values.put (BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890"); Uri uri = getContentResolver (). Insert (BlockedNumbers.CONTENT_URI, values);

Незважаючи на назву, стовпчик COLUMN_ORIGINAL_NUMBER може містити не тільки номер телефону, але і електронну адресу:

values.put (BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "[email protected]");

Видалити номер з бана так само просто:

ContentValues ​​values ​​= new ContentValues ​​(); values.put (BlockedNumbers.COLUMN_ORIGINAL_NUMBER, "1234567890"); Uri uri = getContentResolver (). Insert (BlockedNumbers.CONTENT_URI, values); getContentResolver (). delete (uri, null, null);

Для перевірки, не внесений чи номер в чорний список, передбачений метод isBlocked (Context, String).

Нарешті, щоб отримати всіх відкинутих одним махом:

Cursor c = getContentResolver (). Query (BlockedNumbers.CONTENT_URI, new String [] {BlockedNumbers.COLUMN_ID, BlockedNumbers.COLUMN_ORIGINAL_NUMBER, BlockedNumbers.COLUMN_E164_NUMBER}, null, null, null);

Таким чином, розглянуті в попередньому розділі трюки поступово зійдуть нанівець. Інше питання, як швидко це станеться. Частка Android 7 поки не перевищує навіть інженерної похибки.

Частка Android 7 поки не перевищує навіть інженерної похибки

Фрагментація Android (березень 2017 року)

телефонуємо

Зателефонувати в Android'е можна двома принципово різними способами. Перший, і найпростіший, - викликати стандартну активність, передавши їй як параметр номер для набору:

Intent call = new Intent (Intent.ACTION_DIAL, Uri.parse ( "tel: 8495-123-45-56")); startActivity (call);

Тут використовується ініціює дзвінок намір Intent.ACTION_DIAL, а номер передається у вигляді шляху URI з обов'язковим зазначенням протоколу tel. На екрані смартфона користувач побачить звичне вікно з заведеним номером.

На екрані смартфона користувач побачить звичне вікно з заведеним номером

Стандартна «звонилка»

Стандартна активність для дозвону дозволяє змінити номер безпосередньо перед виконанням дзвінка, тому ніяких дозволів в маніфесті додатка не потрібно.

Другий варіант - перехоплення намірів, які обслуговуються стандартним додатком, і виклик своєї активності. В цьому випадку отрисовка екранних цифрових кнопок і пошук по контактам (і це далеко не повний перелік) лягає на плечі програміста.

Крім того, оскільки в цьому випадку потрібен дозвіл:

<Uses-permission android: name = "android.permission.CALL_PHONE" />

починаючи з Android 4.4 додаток відмовиться працювати, якщо воно не буде вибрано за замовчуванням, а користувач навряд чи просто так змінить знайому «звонилку».

Skype ненав'язливо намагається стати телефоном

Як бачиш, Google непогано захистила свій телефонний компонент, і шкідливий, таємно що дзвонять на короткі платні номери, в природі (поки що?) Не спостерігається.

Ода маніфесту

Якщо ти уважно читаєш рубрику «Кодінг», то напевно помітив, що та чи інша потенційно небезпечне діяння в Android вимагає однозначного дозволу. Незважаючи на наявні уразливості (коли в останній раз до тебе прилітали патчі?) В різних компонентах системи, в цілому основним розсадником проблем виявляється сам користувач. Зрозуміло, якщо ти зацікавиш ЦРУ, ніякої заборона дозволів додатків тебе не врятує , Але в звичайному житті необхідно вкрай насторожено ставитися до всього що встановлюється ПО, навіть якщо воно родом з Google Play. Як думаєш, чи варто ставити собі калькулятор, якщо він вимагає доступу в інтернет і можливості відправляти СМС?

висновок

Сьогодні ми познайомилися з одним з головних компонентів сучасного смартфона - телефонією (хоча, можливо, у інтернет-месенджерів інша думка з цього приводу). Як завжди, в повному обсязі працює так, як хотілося б, і світло в велосипедної майстерні ще довго не згасне, але така вже в програмістів доля. У будь-якому випадку «Хакер» триматиме тебе в курсі.

Що далі?
Як же вони працюють?
Так працює чи ні?
Поки що?
Коли в останній раз до тебе прилітали патчі?
Як думаєш, чи варто ставити собі калькулятор, якщо він вимагає доступу в інтернет і можливості відправляти СМС?

Дополнительная информация

rss
Карта