sábado, 24 de julho de 2010

Capturando Eventos do Sistema com Android

Olá pessoal,

A facilidade da plataforma Android me encanta muito, nunca programei com nada tão bacana. Por isso, vou falar um pouquinho sobre como capturar eventos do sistema utilizando BroadcastReceivers. A classe android.content.BroadcastReceiver é utilizada para receber eventos de sistema (ou nossos próprios) e tratá-los em background. Ela não possui interface gráfica associada e possui um tempo de vida muito curto (cerca de 10 segundo), então se quisermos mostrar algum feedback pro usuário devemos delegar isso pra uma Activity.

Para implementarmos um BroadcastReceiver basta criar uma classe que a estenda, implementar o método onReceive(Context, Intent) e registrá-la com a tag <receiver> AndroidManifest.xml ou no código através do método context.registerReceiver(receiver, filtro). Para desativar podemos utilizar uma chamada para context.unregisterReceiver(receiver). Se quisermos enviar nossa própria mensagem de broadcast, podemos chamar o método sendBroadcast(Intent).

No nosso exemplo, capturaremos as mudanças de estado do telefone. Ou seja, quando for feita, recebida ou terminada uma ligação, nosso BroadcastReceiver entrará em ação.

Primeiro, crie a classe conforme abaixo:

public class InterceptCall extends BroadcastReceiver {

 public void onReceive(Context ctx, Intent it) {
   String estado = it.getStringExtra("state");
   String num = it.getStringExtra("incoming_number");
   String msg = null;
     
   if (estado.equals("RINGING")){
     msg = "O número "+ num +" está chamando.";
         
   } else if (estado.equals("OFFHOOK")){
     msg = "Em ligação.";
       
   } else if (estado.equals("IDLE")){
     if (num != null){
       msg = "Chamada não atendida de "+ num;
     } else {
       msg = "Chamada atendida, foi terminada.";
     }
   }
     
   Toast.makeText(ctx, msg, Toast.LENGTH_LONG).show();
 }
}
Com esse receiver, capturamos a mudança de estado do telefone em ligações. A Intent que o dispara tem dois extras: state e incoming_number. O primeiro indica o estado do telefone e o segundo o número do telefone de quem está ligando. O telefone vai para o estado RINGING (tocando) quando o telefone está recebendo a ligação. Ao atender ou fazer uma chamada o telefone vai para o estado OFFHOOK (fora do gancho). E por último, ao terminar uma ligação o telefone volta para o seu estado padrão, IDLE (parado).

Notem que nem OFFHOOK e nem IDLE quando não se atende a ligação, contam com o número do telefone que o chamou.

Para finalizar, basta registrarmos o nosso BroadcastReceiver no AndroidManifest.xml, dentro da tag <application>, conforme abaixo:

<receiver android:name="InterceptCall">
<intent-filter>
  <action
  android:name="android.intent.action.PHONE_STATE"/>
  <category
  android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
Também adicione a permissão para ler o estado da ligação no AndroidManifest.xml FORA DA TAG .
<uses-permission 
  android:name="android.permission.READ_PHONE_STATE"/>
Pronto! Agora é só brincar com os demais eventos do sistema.

4br4ç05,
nglauber

17 comentários:

Anônimo disse...

E como eu faço para testar o que foi feito? poderia explicar ?

Nelson Glauber disse...

Para testar, se você estiver usando o emulador, basta ir na perspectiva DDMS do Eclipse, selecionar a guia "Emulator Control". Na seção "Telephony Actions" você digita um número qualquer (será o número do telefone que estará chamando o emulador) e seleciona o radio button "Voice", depois é só clicar no botão "Call". Isso simulará uma chamada telefônica ao emulador.

Se for no telefone, é só discar pra o telefone que tem a aplicação :)

Anônimo disse...

Mais a seção "Telephony Actions" não esta habilitada para eu selecionar

Nelson Glauber disse...

Olá Anônimo Anônimo,

No Eclipse, vá até o menu Window > Show view > Other.
Quando abrir a janela "Show View", na pasta Android, selecione "Emulator Control".

4br4ç05,
nglauber

Cleiton disse...

Obrigado Nelson,
Achei o que estava procurando. Devagar vou aprendendo.

Grato.

Anônimo disse...

boas,
se tiver um boadcast com varios receivers a tratar de eventos diferentes e um deles finalizar esse broadcast. o que acontece?

obrigado
Fernando

Nelson Glauber disse...

Olá anônimo,

Quando um evento do sistema acontece, o broadcast que está ouvindo aquele evento é instânciado. O método onReceive é chamado, e ao completar sua execução, aquela instância do broadcast receiver é desalocada da memória.
Então não existe (que eu saiba) "finalizar esse broadcast" ele será finalizado automaticamente quando o método onReceive terminar.

4br4ç05,
nglauber

Anônimo disse...

Nelson, PARABÉNS!!!!!!
Com as orientações,consegui pela primeira vez ver o estado do telefone.
Se for possível gostaria de sua ajuda??
Preciso retornar ao aplicativo que originou o receiver sempre que o estado = IDLE.
Desde já agradeço.
Luiz Carlos

Nelson Glauber disse...

Oi Luiz,

Tenta dar um startActivity para a Activity que você quer....

4br4ç05,
Glauber

Anônimo disse...

Nelson, boa tarde.
Obrigado pela resposta tão rápida.
Desculpe a "IGNORÂNCIA DO MACACO" mas onde coloco a
no receiver:
@Override
public void onReceive(Context ctx, Intent intent) {
String estado = intent.getStringExtra("state");
String num = intent.getStringExtra("incoming_number");
String msg = null;
if (estado.equals("RINGING")){
msg = "O número "+ num +" está chamando.";
} else if (estado.equals("OFFHOOK")){
msg = "Em ligação.";
} else if (estado.equals("IDLE")){
if (num != null){
msg = "Chamada não atendida de "+ num;
} else {
msg = "Chamada atendida, foi terminada.";
}
}
Toast.makeText(ctx, msg, Toast.LENGTH_LONG).show();
}
Grato, Luiz Carlos

Anônimo disse...

Nelson, boa tarde.
Resolvi a ultimo problema assim:
Intent iAlarm = new Intent( ctx, MainActivity.class ); iAlarm.addFlags(Intent.FLAG_FROM_BACKGROUND); iAlarm.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); iAlarm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ctx.startActivity(iAlarm);
Fui tentando tentando até dar certo.
Caso queira corrigir ou simplificar esteja a vontade afinal o código é seu.
Muito obrigado, se precisar te em comodo novamente.
Muito, Muito OBRIGADO.
Luiz Carlos

César Oliveira disse...

Boa noite,

Mais uma matéria muito bem explicada.

Parabéns

Quando começei a estudar Android e mais concretamente o BroadcastReceiver não percebia quem difenia o evento. Só depois me apercebi que quem fazia isso era o Android Manifest hehehhe
No inicio eu pensava que era dentro da classe do BroadcastReceiver. hehehe

Cumprimentos
Malainho

Anônimo disse...

Não existe nenhuma maneira de "pegar" o número ao efetuar chamadas, ou seja no estado OFFHOOK? Parabéns pelo post, muito bom.

Nelson Glauber disse...

Oi Anônimo,

Quando você está recebendo uma ligação, você pode pegar o número no estado de RINGING.
Para pegar ao efetuar chamadas você deve capturar a ação NEW_OUTGOING_CALL (http://developer.android.com/reference/android/content/Intent.html#ACTION_NEW_OUTGOING_CALL) ao invés do PHONE_STATE. E no receiver obter o número pelo EXTRA_PHONE_NUMBER.

4br4ç05,
nglauber

Anônimo disse...

Muito obrigado mesmo! Deu certinho

Anônimo disse...

É possível impedir o recebimento de uma chamada ou pelo menos cancelar? Tentei usar o abortBroadcast(), setResultData(null), aumentar a prioridade, mas não funcionou.

Nelson Glauber disse...

Oi Anônimo,

Pela minha demora acho que você já resolveu, mas para futuras consultas, já tentou isso aqui?

http://stackoverflow.com/questions/20965702/end-incoming-call-programmatically

4br4ç05,
nglauber