Компания
69,97
рейтинг
13 ноября 2014 в 07:50

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

После последнего поста о нашем 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 попадает в общий стек экранов, поэтому если ваше приложение имеет хотя бы один экран и в момент звонка оно запущено — произойдёт следующее:

  1. на экран выводится родная телефонная звонилка
  2. на экран выводится активный экран вашего приложения
  3. на экран выводится ваше «окно поверх» звонилки

В результате пользователь не сможет ответить или отклонить звонок, не переключившись на звонилку самостоятельно. В случае же ручного создания окна пункт 2 не выполняется и пользователь увидит именно то, что мы хотели: телефонную звонилку и наше окно поверх неё.

Подводные камни


К сожалению, всё не так радужно как кажется. Как часто бывает в андроиде, 100% совместимости хитрой фичи добиться сложно.

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

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

Демонстрационный проект на GitHub.
Автор: @Newbilius

Комментарии (16)

  • 0
    Отлично, а как мне «игнорировать» определенный номер? Т.е. запретить показ окна звонка? Есть такая возможность сделать?
    • +2
      Ну вам (как разработчику) нужно обязательно дать возможность пользователю управлять фичей в каких-то пределах, как минимум — совсем отключать. У нас в программе реализовано именно так, окно появляется или для всех контрагентов из программы, или ни для кого.
      • 0
        А теперь понял как это делать, спасибо за ответ!
  • 0
    А можно ли как-то полностью убрать стандартное окно вызова и сделать своё?
    • 0
      Как вариант, можно выводить окошко на весь экран и перекрыть стандартную звонилку, как показано в этой статье.

      Второй вариант (требует проверки) — вроде бы можно создать приложение, которое будет устанавливаться в систему как «звонилка по умолчанию», но можно ли сделать это без root-доступа — не уверен. Нужно копнуть в эту сторону.
      • 0
        Нельзя ли чуточку больше про второй вариант услышать?
        • 0
          Сразу как только раскопаю сам, думаю посмотреть на днях. Статью отдельную на хабре видимо делать не буду, интересующихся темой мало, а вот в личку ссылки отправлю.
          • +3
            Лучше не в личку, а в коммент. Чтобы все увидели.
          • +1
            Если что-то накопаете мне тоже отправьте пожалуйста.
          • +1
            Тоже был бы признателен!
          • 0
            появилась ли какая новая информация?
  • –1
    В подводные камни также стоит добавить android 5.0 =)
    • 0
      На эмуляторе проверяли — фича продолжнает работать. У вас есть информация, что на реальном телефоне финт не пройдёт?
      • +1
        тем что теперь входящий звонок отображается не экраном, а нотификацией.
  • +1
    Тоже нужно было свой экран звонка прикрутить. После гугления решил что оптимальное решение — выводить свою активити и на ней дублировать кнопки сброса и ответа (фото контакта и прочее). Но после прикрутки все равно не работает как надо, не сбрасывает вызов, также нужно делать задержку своего экрана (иначе перекроет родным). Если у кого есть обкатанное решение просьба поделиться.

    Решение в посте кажется топорным: ломает стиль, закрывает фото.
    • 0
      Оформление остаётся полностью на вашей совести, как оформить и где разместить окно. Заменить же целиком звонилку кажется ну очень экстремальным методом, на который не каждый пользователь согласится, если данное окно — лишь дополнительный функционал, а не основная возможность программы. Если же вы пишите именно звонилку, тогда другое дело.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка