Tutorial Android #20 – Alarmes

Padrão

Olá pessoal! Aqui estamos nós com mais um tutorial de Android para alegria geral da nação. No episódio tutorial de hoje, vamos ver como criar um alarme para nos avisar a hora do almoço (tudo a ver com o aplicativo de restaurante, não?).

O primeiro passo é criarmos uma forma para o usuário configurar o horário em que ele deseja ser avisado do almoço. Poderíamos definir isto em uma Activity, mas esta opção soa mais como uma configuração. Dessa forma, vamos criar a classe PreferenciaHorario no pacote net.rafaeltoledo.restaurante, estendendo a classe DialogPreference. Logo em seguida explicarei os conceitos principais dela.

package net.rafaeltoledo.restaurante;</p>
<p>import android.content.Context;<br />
import android.content.res.TypedArray;<br />
import android.preference.DialogPreference;<br />
import android.util.AttributeSet;<br />
import android.view.View;<br />
import android.widget.TimePicker;</p>
<p>public class PreferenciaHorario extends DialogPreference {</p>
<p>	private int ultimaHora = 0;<br />
	private int ultimoMinuto = 0;<br />
	private TimePicker picker = null;</p>
<p>	public static int obterHora(String tempo) {<br />
		String[] fragmentos = tempo.split(&quot;:&quot;);<br />
		return Integer.parseInt(fragmentos[0]);<br />
	}</p>
<p>	public static int obterMinuto(String tempo) {<br />
		String[] fragmentos = tempo.split(&quot;:&quot;);<br />
		return Integer.parseInt(fragmentos[1]);<br />
	}</p>
<p>	public PreferenciaHorario(Context contexto) {<br />
		this(contexto, null);<br />
	}</p>
<p>	public PreferenciaHorario(Context contexto, AttributeSet atributos) {<br />
		this(contexto, atributos, 0);<br />
	}</p>
<p>	public PreferenciaHorario(Context contexto, AttributeSet atributos, int estilo) {<br />
		super(contexto, atributos, estilo);</p>
<p>		setPositiveButtonText(&quot;Definir&quot;);<br />
		setNegativeButtonText(&quot;Cancelar&quot;);<br />
	}</p>
<p>	@Override<br />
	protected View onCreateDialogView() {<br />
		picker = new TimePicker(getContext());<br />
		return picker;<br />
	}</p>
<p>	@Override<br />
	protected void onBindDialogView(View view) {<br />
		super.onBindDialogView(view);</p>
<p>		picker.setCurrentHour(ultimaHora);<br />
		picker.setCurrentMinute(ultimoMinuto);<br />
	}</p>
<p>	@Override<br />
	protected void onDialogClosed(boolean positiveResult) {<br />
		super.onDialogClosed(positiveResult);</p>
<p>		if (positiveResult) {<br />
			ultimaHora = picker.getCurrentHour();<br />
			ultimoMinuto = picker.getCurrentMinute();</p>
<p>			String tempo = String.valueOf(ultimaHora) + &quot;:&quot; + String.valueOf(ultimoMinuto);</p>
<p>			if (callChangeListener(tempo)) {<br />
				persistString(tempo);<br />
			}<br />
		}<br />
	}</p>
<p>	@Override<br />
	protected Object onGetDefaultValue(TypedArray a, int index) {</p>
<p>		return a.getString(index);<br />
	}</p>
<p>	@Override<br />
	protected void onSetInitialValue(boolean restorePersistedValue,<br />
			Object defaultValue) {<br />
		String tempo = null;</p>
<p>		if (restorePersistedValue) {<br />
			if (defaultValue == null) {<br />
				tempo = getPersistedString(&quot;00:00&quot;);<br />
			} else {<br />
				tempo = getPersistedString(defaultValue.toString());<br />
			}<br />
		} else {<br />
			tempo = defaultValue.toString();<br />
		}</p>
<p>		ultimaHora = obterHora(tempo);<br />
		ultimoMinuto = obterMinuto(tempo);<br />
	}<br />
}

Bastante coisa, não? Vamos por partes.

Os métodos obterHora() e obterMinuto() servem para extrair a parte inteira do horário que será armazenado como uma string “00:00“. Temos três versões do construtor da classe, que no final sempre referenciam o terceiro. Isso é devido à superclasse. Ainda no construtor, definimos os nomes dos botões na janela de configuração de horário. onCreateDialogView() devolve um objeto View com a tela criada. Poderíamos aqui definirmos um layout, mas simplesmente devolvemos um widget TimePicker. O método onBindDialogView() é chamado após o onCreateDialogView() é encarregado de preencher a caixa de diálogo. onDialogClose(), como o próprio nome diz, é chamado quando a janelinha é encerrada. Caso o usuário pressione o botão de confirmar (condição positiveResult), o valor é armazenado em SharedPreferences. O método onGetDefaultValue() é utilizado para a conversão interna do Android para o tipo do objeto. Por último, o método onSetInitialValue(), como o próprio nome diz, atribui um valor padrão. Ele verifica se há algum valor já salvo, ou padrão ou então atribui 00:00.

O próximo passo é adicionar a opção para a configuração do alarme pelo usuário. Dessa forma, edite o arquivo preferencias.xml da seguinte forma:

&lt;PreferenceScreen xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;<br />
	&lt;ListPreference<br />
	    android:key=&quot;listagem&quot;<br />
	    android:title=&quot;Modo de Listagem&quot;<br />
	    android:summary=&quot;Escolha o modo de listagem a ser utilizado&quot;<br />
	    android:entries=&quot;@array/nomes_ordenacao&quot;<br />
		android:entryValues=&quot;@array/opcoes_ordenacao&quot;<br />
		android:dialogTitle=&quot;Escolha o modo de listagem&quot; /&gt;<br />
	&lt;CheckBoxPreference<br />
	    android:key=&quot;alarme&quot;<br />
	    android:title=&quot;Tocar Alarme no Almoço&quot;<br />
	    android:summary=&quot;Marque se deseja ser informado sobre a hora do almoço&quot;/&gt;<br />
	&lt;net.rafaeltoledo.restaurante.PreferenciaHorario<br />
	    android:key=&quot;horario_alarme&quot;<br />
	    android:title=&quot;Horário do Alarme do Almoço&quot;<br />
	    android:defaultValue=&quot;12:00&quot;<br />
	    android:summary=&quot;Configure seu horário desejado para o alarme&quot;<br />
	    android:dependency=&quot;alarme&quot;/&gt;<br />
&lt;/PreferenceScreen&gt;

A primeira opção adicionada, do tipo CheckBoxPreference não tem muito segredo… a segunda, foi a que definimos na classe PreferenciaH0rario. Configuramos seu valor padrão para 12:00 e definimos que ela depende da opção alarme, ou seja, ela só estará habilitada caso alarme também esteja habilitada.

Neste projeto vamos utilizar o AlarmManager para gerenciar o nosso alarme. Porém, ele tem uma falha: toda vez que o celular é desligado, ao ligar novamente os alarmes não são configurados. Para resolver isso, vamos criar a classe ReceptorBoot para realizar essa configuração toda vez que o sistema for ligado. Crie-a no pacote net.rafaeltoledo.restaurante.

package net.rafaeltoledo.restaurante;</p>
<p>import android.content.BroadcastReceiver;<br />
import android.content.Context;<br />
import android.content.Intent;</p>
<p>public class ReceptorBoot extends BroadcastReceiver {</p>
<p>	@Override<br />
	public void onReceive(Context context, Intent intent) {</p>
<p>	}<br />
}

A tarefa do ReceptorBoot será realizado no método onReceive(). Por enquanto, coloque-o pra descansar. Já já voltamos nele.

Prosseguindo, precisamos adicionar o nó <receiver> no arquivo AndroidManifest.xml para que ele possa atuar no boot. Adicione-o ao final do nó application.

&lt;receiver android:name=&quot;.ReceptorBoot&quot;<br />
    android:enabled=&quot;false&quot;&gt;<br />
    &lt;intent-filter&gt;<br />
        &lt;action android:name=&quot;android.intent.action.BOOT_COMPLETED&quot;/&gt;<br />
    &lt;/intent-filter&gt;<br />
&lt;/receiver&gt;

Além disso, adicione também a permissão para obter o sinal de boot completo do sistema.

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

Precisamos agora tratar as preferências do usuário para configurar o alarme. Quando o usuário ativar o checkbox do alarme, precisamos ativar o alarme no tempo selecionado. Quando o usuário modificar o alarme (por exemplo, para 11:00), devemos criar um novo alarme com o AlarmManager. Se ele desativar, precisamos cancelar o alarme existente. E, por fim, em um processo de boot, se o alarme estiver selecionado, precisamos criá-lo.

Para fazer todo esse trabalho, adicione os seguintes métodos na classe ReceptorBoot. Para corrigir os imports, só lembrar do Ctrl + Shift + O.

public static void configurarAlarme(Context contexto) {<br />
	AlarmManager gerenciador = (AlarmManager) contexto.getSystemService(Context.ALARM_SERVICE);<br />
	Calendar cal = Calendar.getInstance();<br />
	SharedPreferences preferencias = PreferenceManager.getDefaultSharedPreferences(contexto);<br />
	String horario = preferencias.getString(&quot;horario_alarme&quot;, &quot;12:00&quot;);</p>
<p>	cal.set(Calendar.HOUR_OF_DAY, PreferenciaHorario.obterHora(horario));<br />
	cal.set(Calendar.MINUTE, PreferenciaHorario.obterMinuto(horario));<br />
	cal.set(Calendar.SECOND, 0);<br />
	cal.set(Calendar.MILLISECOND, 0);</p>
<p>	if (cal.getTimeInMillis() &lt; System.currentTimeMillis()) {<br />
		cal.add(Calendar.DAY_OF_YEAR, 1);<br />
	}</p>
<p>	gerenciador.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(),<br />
			AlarmManager.INTERVAL_DAY, obterIntentPendente(contexto));<br />
}</p>
<p>public static void cancelarAlarme(Context contexto) {<br />
	AlarmManager gerenciador = (AlarmManager) contexto.getSystemService(Context.ALARM_SERVICE);<br />
	gerenciador.cancel(obterIntentPendente(contexto));<br />
}</p>
<p>private static PendingIntent obterIntentPendente(Context contexto) {<br />
	Intent i = new Intent(contexto, ReceptorAlarme.class);<br />
	return PendingIntent.getBroadcast(contexto, 0, i, 0);<br />
}

Também atualize o método onReceive():

@Override<br />
public void onReceive(Context context, Intent intent) {<br />
	configurarAlarme(context);<br />
}

Bem, no código listado acima, primeiramente, ao receber o sinal do boot (método onReceive()), configuramos o alarme, através do método configurarAlarme(). Neste método, obtemos o AlarmManager, e obtemos as preferências do usuário para o alarme (se existirem), e a montamos em um objeto do tipo Calendar. Caso alarme seja anterior ao horário atual, adicionamos um dia a ele e configuramos para repeti-lo diariamente. Já no método cancelarAlarme(), cancelamos o alarme vinculado ao contexto, obtendo o AlarmManager e obtendo um objeto PendingIntent (como se fosse uma tarefa pendente) com o método obterIntentPendente().

No código que temos até agora, o alarme só é armado na inicialização do sistema. Para que ele funcione da maneira como desejamos, precisamos adicionar alguns método a classe EdicaoPreferencias:

@Override<br />
protected void onResume() {<br />
	super.onResume();</p>
<p>	preferencias = PreferenceManager.getDefaultSharedPreferences(this);<br />
	preferencias.registerOnSharedPreferenceChangeListener(onChange);<br />
}</p>
<p>@Override<br />
protected void onPause() {<br />
	preferencias.unregisterOnSharedPreferenceChangeListener(onChange);</p>
<p>	super.onPause();<br />
}</p>
<p>OnSharedPreferenceChangeListener onChange = new SharedPreferences.OnSharedPreferenceChangeListener() {</p>
<p>	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,<br />
			String key) {<br />
		if (&quot;alarme&quot;.equals(key)) {<br />
			boolean habilitado = preferencias.getBoolean(key, false);<br />
			int flag = (habilitado ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED : PackageManager.COMPONENT_ENABLED_STATE_DISABLED);<br />
			ComponentName componente = new ComponentName(EdicaoPreferencias.this, ReceptorBoot.class);</p>
<p>			getPackageManager().setComponentEnabledSetting(componente, flag, PackageManager.DONT_KILL_APP);</p>
<p>			if (habilitado) {<br />
				ReceptorBoot.configurarAlarme(EdicaoPreferencias.this);<br />
			} else {<br />
				ReceptorBoot.cancelarAlarme(EdicaoPreferencias.this);<br />
			}<br />
		} else if (&quot;horario_alarme&quot;.equals(key)) {<br />
			ReceptorBoot.cancelarAlarme(EdicaoPreferencias.this);<br />
			ReceptorBoot.configurarAlarme(EdicaoPreferencias.this);<br />
		}<br />
	}<br />
};

Lembre-se também de adicionar o membro privado da classe chamado preferencias:

SharedPreferences preferencias = null;

O que nos falta fazer é criar um receptor que exiba o alarme na tela quando o alarme disparar. Para isso, primeiramente crie o arquivo alarme.xml na pasta res/layout:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;<br />
&lt;TextView xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;<br />
    android:layout_width=&quot;wrap_content&quot;<br />
    android:layout_height=&quot;wrap_content&quot;<br />
    android:text=&quot;Hora do almoço!&quot;<br />
    android:textSize=&quot;30sp&quot;<br />
    android:textStyle=&quot;bold&quot; /&gt;

Bastante simples, ele simplesmente exibirá bem grande na tela Hora do almoço!. Agora vamos criar a Activity que exibirá o aviso propriamente dito. Crie a classe AlarmeActivity no pacote net.rafaeltoledo.restaurante:

package net.rafaeltoledo.restaurante;</p>
<p>import android.app.Activity;<br />
import android.os.Bundle;</p>
<p>public class AlarmeActivity extends Activity {</p>
<p>	@Override<br />
	protected void onCreate(Bundle savedInstanceState) {<br />
		super.onCreate(savedInstanceState);<br />
		setContentView(R.layout.alarme);<br />
	}<br />
}

Crie também uma classe chamada ReceptorAlarme que será encarregada de iniciar a AlarmeActivity.

package net.rafaeltoledo.restaurante;</p>
<p>import android.content.BroadcastReceiver;<br />
import android.content.Context;<br />
import android.content.Intent;</p>
<p>public class ReceptorAlarme extends BroadcastReceiver {</p>
<p>	@Override<br />
	public void onReceive(Context context, Intent intent) {<br />
		Intent i = new Intent(context, AlarmeActivity.class);<br />
		i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);<br />
		context.startActivity(i);<br />
	}<br />
}

Encerrando (ufa!), falta somente adicionarmos esse último receptor no AndroidManifest.xml. Adicione-o no fim do nó application.

&lt;receiver android:name=&quot;.ReceptorAlarme&quot;&gt;<br />
&lt;/receiver&gt;

E é isso!

Como sempre, pra baixar o projeto, só clicar aqui.

Até logo!