Garantindo a execução de alarmes no modo soneca do Android
Tags: android, mobile
Published: 2023-04-02 21:27:32

**Introdução**
A medida que novas versões do android vão surgindo, são criadas  diferentes formas de trabalho e acesso aos dados do ecossistema. Diante disso, desde a versão do android 6, a Google vem tratando a forma que os serviços são executados, visando uma boa experiência do usuário e diminuindo riscos em potenciais. 
A Google também avançou em diversas ferramentas para trabalhos em segundo plano e agendamentos de tarefas, no qual podemos citar: AlarmManager, WorkManager(oficial) e Firebase JobDispatcher. A medida que crescia a popularidade dessas ferramentas, a preocupação com o consumo de bateria também aumentava. 
Em suma, o consumo de bateria diminui quando estamos executando alguma tarefa em segundo plano, visto que a execução dela pode ser feita de diferentes formatos, desde uma vez a cada hora e até de maneira recorrente com poucos intervalos de 10 segundos. 
Com isso, a partir do android 6, foi introduzido o modo Soneca e amplamente apoiado pela comunidade.

**O que é o modo soneca?**
O modo soneca no android é uma forma de economizar bateria quando o dispositivo está muito tempo sem interação, ou seja, o celular só entra neste modo quando o usuário não utiliza. Sendo assim, o modo soneca entende que o dispositivo não está sendo utilizado e barra qualquer alarme, notificação, acesso a dados remotos e até tarefas que estão sendo executados em segundo plano, mas com uma condição: Essas tarefas, alarmes, notificações e etc são executados, mas em um formato de janela e varia com o tempo.

**O que são as janelas de execução?**
As [janelas de execução](https://developer.android.com/training/monitoring-device-state/doze-standby?hl=pt-br) são feitas para poder executar qualquer tarefa quando o dispositivo estiver no modo soneca. A partir da janela, o sistema operacional aciona algumas tarefas e coloca em uma janela. Com isso, todas as tarefas são executadas naquele período. O problema é que a medida que uma janela é colocada, a próxima executa em um tempo maior que a outra, sendo assim, a tarefa que era para ser executada a cada 30 segundos, naquela janela ela será executada daqui a 30 minutos e prejudicando o seu aplicativo.

**AlarmManager**
_Não vamos nos aprofundar nesse momento com o alarmManager, mas é necessário termos um breve conhecimento._
Segundo a [documentação do AlarmManager](https://developer.android.com/training/scheduling/alarms?hl), ele é uma ferramenta para realizar operações baseadas em tempo fora da vida útil do seu app. Em qualquer projeto tem seu caso de uso, mas podemos imaginar uma o conceito de despertador, mas imaginemos o seguinte: o despertador só sabe que precisa tocar todos os dias as 16:30 horas porque foi programado através do alarmManager para ele realizar aquela operação. 

**Problemática** 
Temos um seguinte problema e precisamos resolver ele urgente: Um grupo de desenvolvedores criaram um aplicativo que gerencia o tempo de estudos dos usuários. Logo após o desenvolvimento, eles notaram que aqueles estudantes que passavam um bom tempo sem o celular começaram a reclamar que o dispositivo não estava tocando no horário programado e ocasionando em tempos indevidos de estudos.
Basicamente, é isso que vamos resolver aqui. Será criado uma solução para este problema e fazer com que o dispositivo toque mesmo no modo soneca. 

**Implementação** 
Criando um projeto android, iremos criar a classe _IdleAlarmManager_, onde fica responsável por configurar toda ação de alarme do aplicativo. 
- _IdleAlarmManager_
```
class IdleAlarmManager(context: Context) {
    private val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
    private val alarmIntent =
        PendingIntent.getBroadcast(context, 0, Intent(context, AlarmReceiver::class.java), 0)
    private val calendar = Calendar.getInstance().apply {
        timeInMillis = System.currentTimeMillis()
        set(Calendar.HOUR_OF_DAY, SUA_HORA)
        set(Calendar.MINUTE, SEU_MINUTO)
    }

    fun setAlarmManager() = alarmManager.setExactAndAllowWhileIdle (
        AlarmManager.RTC_WAKEUP,
        calendar.timeInMillis,
        alarmIntent
    )
}
```
Explicação do código acima: 
- É criado uma pendingIntent e passando qual broadcast receiver deve ser chamado quando o alarme for disparado. 
- O objeto _calendar_ é utilizado para definir o horário que o alarme será disparado. 
- A parte mais importante do código: O metódo setAlarmManager() faz a configuração do alarme e a sua característica, no nosso caso, um alarme que dispara mesmo no modo soneca. 
     - o metódo _setExactAndAllowWhileIdle()_ garante que o alarme será chamado mesmo quando estiver no modo soneca, diferente do _setAndAllowWhileIdle_(), no qual não tem total garantia. Este método exato ou o outro deve ser chamado para que o alarme seja tocado. Se for usado qualquer outro método, só será disparado quando o dispositivo sair do modo soneca. Lembrando que ambos podem haver atrasos em disparar esse alarme.
 
Depois, para mostrar a notificação após o alarme ser executado, será necessário criar um broadcast receiver e registrar no manifest. O broadcast receiver permite que um aplicativo receba mensagens ou "broadcasts" do sistema operacional ou de outros aplicativos, mesmo quando o aplicativo não está em execução. O metódo onReceive() é chamado toda vez que recebe uma mensagem do sistema.

- _Android Manifest_

``` 
        <receiver android:name="com.github.tumusx.idle.alarmManager.AlarmReceiver"/>
``` 
- _AlarmReceiver_ 
``` 
class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
            val builder = NotificationCompat.Builder(context, SEU_CHANNEL_ID)
                .setSmallIcon(R.drawable.notification)
                .setContentTitle(SEU_TITULO)
                .setContentText(SUA_MENSAGEM)
                .setStyle(
                    NotificationCompat.BigTextStyle()
                        .bigText(SUA_MENSAGEM)
                )
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            val name = SEU_NOME
            val description = SUA_DESCRICAO
            val channelNotification = NotificationChannel(
                SEU_CHANNEL_ID,
                name,
                NotificationManager.IMPORTANCE_HIGH,
            ).also {
                it.description = description
            }
            val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channelNotification)
            notificationManager.notify(SEU_CHANNEL_ID, builder.build())
}

```
Não irei entrar em detalhes sobre o código acima pois não é o objetivo tratar notificações, mas em resumo, toda vez que o alarme for disparado, será construído e mostrado uma notificação para o usuário. 

> **NÃO É POSSÍVEL ALTERAR PROGRAMATICAMENTE O MODO SONECA NO ANDROID**

Para testar se realmente o alarme está sendo despertado no modo soneca, é necessário executar alguns comandos para forçar o dispositivo a entrar no modo soneca:
Com o seguinte caminho, abra o cmd: 

> C:\Users\SEU_USER\AppData\Local\Android\Sdk\platform-tools>

1. adb shell dumpsys deviceidle enable: Força a entrar em modo econômico;
2. adb shell dumpsys deviceidle force-idle: Força a entrar em modo soneca;
3. adb shell dumpsys deviceidle get deep: Checa o estado atual do dispositivo. Se retornar a mensagem IDLE, ele está em modo soneca;
4. adb shell dumpsys deviceidle unforce: Remove o dispositivo do modo soneca.

Caso queira consultar o [repositório] (https://github.com/tumusx/tools-android/tree/master/idle), se encontra no github.

**Considerações finais**
Como foi mostrado, é possível sim executar alguns processos mesmo quando o dispositivo estiver no modo soneca. Lembrando que o uso dessa implementação, o modo soneca não é desativado. Apenas garante que o alarme seja disparado quando estiver neste modo. 
Existe outra solução para o problema, mas é utilizado o Firebase Cloud Messaging e a notificação tem que ser definida como prioridade alta. 

**Contate-me**
[Linkedin](https://www.linkedin.com/in/tumusx/)