После последнего поста о нашем Android-приложении у некоторых читателей статьи возник вопрос, как именно показать собственную информационную плашку во время звонка? Ну что же, сегодня мы ответим на этот вопрос.
Общий план достаточно прост:
- перехватываем событие «входящий звонок» с помощью intent filter;
- рисуем поверх окна телефонной звонилки собственное окошко с необходимой информацией.
Пройдёмся же подробно по каждому пункту.
Перехватываем звонок
Чтобы иметь возможность перехватывать событие «нам звонят», нужно добавить в манифест приложения запрос прав на считывание состояния телефона.
</application>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
Там же зарегистрировать сервис для перехвата события «звонок».
<receiver android:name=".CallReceiver">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
</intent-filter>
</receiver>
</application>
И наконец — написать немного кода обработки этого события.
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 цифры — порт данного эмулятора.
Альтернативный способ — позвонить из утилиты Android Device Monitor или из консоли с помощью ADB. Заметный минус всех этих методов — эмулятор на время звонка рвёт связь с отладчиком, но возможность протестировать поведение окна на разных версия ОС и разных разрешениях того стоит.
Показываем плашку
Ну, а теперь самое интересное — показываем нашу плашку. Для этого, во-первых, нам понадобится добавить в манифест запрос прав для создания окон с флагом «системное уведомление».
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Во-вторых, отредактируем метод 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.
Автор: Newbilius