Делаем собственную индикацию о входящем звонке

a50c883f59194770beca1af50874330a.png После последнего поста о нашем Android-приложении у некоторых читателей статьи возник вопрос, как именно показать собственную информационную плашку во время звонка? Ну что же, сегодня мы ответим на этот вопрос.Общий план достаточно прост: перехватываем событие «входящий звонок» с помощью intent filter; рисуем поверх окна телефонной звонилки собственное окошко с необходимой информацией. Пройдёмся же подробно по каждому пункту.Перехватываем звонокЧтобы иметь возможность перехватывать событие «нам звонят», нужно добавить в манифест приложения запрос прав на считывание состояния телефона. Там же зарегистрировать сервис для перехвата события «звонок». И наконец — написать немного кода обработки этого события. public class CallReceiver extends BroadcastReceiver { private static boolean incomingCall = false;

@Override public void onReceive (Context context, Intent intent) { if (intent.getAction ().equals («android.intent.action.PHONE_STATE»)) { String phoneState = intent.getStringExtra (TelephonyManager.EXTRA_STATE); if (phoneState.equals (TelephonyManager.EXTRA_STATE_RINGING)) { //Трубка не поднята, телефон звонит String phoneNumber = intent.getStringExtra (TelephonyManager.EXTRA_INCOMING_NUMBER); incomingCall = true; Log.debug («Show window:» + phoneNumber);

} else if (phoneState.equals (TelephonyManager.EXTRA_STATE_OFFHOOK)) { //Телефон находится в режиме звонка (набор номера при исходящем звонке / разговор) if (incomingCall) { Log.debug («Close window.»); incomingCall = false; } } else if (phoneState.equals (TelephonyManager.EXTRA_STATE_IDLE)) { //Телефон находится в ждущем режиме — это событие наступает по окончанию разговора //или в ситуации «отказался поднимать трубку и сбросил звонок». if (incomingCall) { Log.debug («Close window.»); incomingCall = false; } } } } } Обратите внимание — в данном примере мы ловим только событие «входящий звонок», но по коду видно, как его можно переделать, если нужно отслеживать и исходящий тоже. Переменная с информацией о звонке статическая, потому что BroadcastReceiver живёт по принципу «принял сообщение — обработал его — умер», и события «поднял трубку/закончил разговор» будет принимать уже новый экземпляр объекта.Отладка звонка Конечно, можно заниматься отладкой звонка на реальном телефоне, но проще и быстрее всё-таки тестировать на эмуляторе. Звонок с одного родного эмулятора на другой совершается с помощью стандартного же приложения-звонилки, в качестве номера телефона выступают 4 цифры — порт данного эмулятора.24442405f031462a96b95fc7136bf0d1.png

Альтернативный способ — позвонить из утилиты Android Device Monitor или из консоли с помощью ADB. Заметный минус всех этих методов — эмулятор на время звонка рвёт связь с отладчиком, но возможность протестировать поведение окна на разных версия ОС и разных разрешениях того стоит.

Показываем плашку Ну, а теперь самое интересное — показываем нашу плашку. Для этого, во-первых, нам понадобится добавить в манифест запрос прав для создания окон с флагом «системное уведомление». Во-вторых, отредактируем метод OnRecieve и заменим простую запись в лог на вызов или закрытие нашего окна. Log.debug («Show window:» + phoneNumber); showWindow (context, phoneNumber);//добавили

//[…]

Log.debug («Close window.»); closeWindow ();//добавили Ну и самое интересное — открытие и закрытие нашего окошка. private static WindowManager windowManager; private static ViewGroup windowLayout;

private void showWindow (Context context, String phone) { windowManager = (WindowManager) context.getSystemService (Context.WINDOW_SERVICE); LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService (Context.LAYOUT_INFLATER_SERVICE);

WindowManager.LayoutParams params = new WindowManager.LayoutParams ( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, PixelFormat.TRANSLUCENT); params.gravity = Gravity.TOP;

windowLayout = (ViewGroup) layoutInflater.inflate (R.layout.info, null);

TextView textViewNumber=(TextView) windowLayout.findViewById (R.id.textViewNumber); Button buttonClose=(Button) windowLayout.findViewById (R.id.buttonClose); textViewNumber.setText (phone); buttonClose.setOnClickListener (new View.OnClickListener () { @Override public void onClick (View v) { closeWindow (); } });

windowManager.addView (windowLayout, params); }

private void closeWindow () { if (windowLayout!=null){ windowManager.removeView (windowLayout); windowLayout =null; } } Обратите внимание, для отображения окна мы не запускаем отдельную activity, а руками выводим новое окно через WindowManager. Почему? Новая activity попадает в общий стек экранов, поэтому если ваше приложение имеет хотя бы один экран и в момент звонка оно запущено — произойдёт следующее: на экран выводится родная телефонная звонилка на экран выводится активный экран вашего приложения на экран выводится ваше «окно поверх» звонилки В результате пользователь не сможет ответить или отклонить звонок, не переключившись на звонилку самостоятельно. В случае же ручного создания окна пункт 2 не выполняется и пользователь увидит именно то, что мы хотели: телефонную звонилку и наше окно поверх неё.Подводные камни К сожалению, всё не так радужно как кажется. Как часто бывает в андроиде, 100% совместимости хитрой фичи добиться сложно.Во-первых, нужно понимать, что у пользователей могут быть телефоны с разными размером экрана, разным разрешением и разной версией андроида, и придется изрядно постараться, чтобы ваше окно не перекрыло родные элементы управления на всех возможных конфигурациях

Во-вторых, на части телефонов от HTC с собственной программой звонка блок с информацией просто-напросто не показывается! Похоже, их приложение-звонилка тоже отображается с системным приоритетом, поэтому наша плашка как бы оказывается «под их окном». Неприятно, но решения этой проблемы мы пока не нашли. Вполне возможно, что звонилки некоторых других телефонов тоже конфликтуют с этой возможностью, но пока что у нас есть негативный опыт только с некоторыми моделями от HTC.

Демонстрационный проект на GitHub.

© Habrahabr.ru