Criando um aplicativo Android POS que não pode ser fechado
Publicados: 2022-03-11O mundo do desenvolvimento de aplicativos móveis é vasto e em constante evolução, com novas estruturas e tecnologias surgindo quase diariamente. Quando você pensa em um dispositivo móvel, provavelmente pensa em seu telefone ou talvez em seu tablet, mesmo que eles não sejam tão populares quanto os smartphones.
O iOS da Apple e o Android do Google dominam o mercado móvel, cada um tendo seus altos e baixos na última década. Hoje, vou falar mais sobre o Android e seu uso em dispositivos que não são necessariamente móveis.
Ser de código aberto teve um efeito colateral muito interessante no sistema operacional móvel do Google. Claro, podemos pensar em todos os diferentes forks do Android de várias empresas de smartphones, mas e todos os dispositivos que executam o Android que não são móveis? Tudo, desde geladeiras, fornos inteligentes, fechaduras ou até mesmo dispositivos de ponto de venda (POS) podem rodar o Android hoje em dia. Estes últimos são a razão pela qual acabei escrevendo este artigo.
Sistemas de PDV Android
Cerca de um ano atrás, eu joguei com um dispositivo Android que era tudo menos comum, e não é algo que a maioria das pessoas provavelmente usará. O dispositivo em questão é um sistema POS baseado em Android de um fornecedor chinês que também possui uma impressora térmica integrada (como as usadas para imprimir recibos em lojas ou caixas eletrônicos).
A maior surpresa, porém, foi seu software: ele estava executando uma versão de estoque do Android. Se bem me lembro, na época, ele estava executando o Android 8 ou o Android Oreo, se você preferir codinomes do Google. O dispositivo em si parece um dispositivo POS portátil da velha escola, mas em vez do teclado físico onde você digitaria seu PIN, ele possui uma tela sensível ao toque capacitiva como as usadas nos telefones Android do passado.
Meu requisito era fácil: eu tinha que ver se havia uma maneira de usar os recursos deste dispositivo, como a impressora térmica, enquanto também executava o aplicativo que estávamos desenvolvendo. Assim que percebi que o requisito em si era possível, outro problema me chamou a atenção: segurança .
O problema é que, se você possui um dispositivo que lida com pagamentos com cartão e outros tipos de transações, talvez não queira que o mesmo dispositivo execute TikTok, Gmail ou Snapchat. Este dispositivo estava se comportando exatamente como um tablet, e ainda veio com a Play Store do Google pré-instalada. Imagine ir a uma pequena loja de conveniência e ver seu caixa tirando selfies, abrindo e-mails de um príncipe nigeriano e navegando em sites estranhos e cheios de malware.
E depois, o caixa lhe entrega o mesmo dispositivo para inserir seu PIN. Pessoalmente, não me sentiria seguro em fornecer as informações do meu cartão de crédito em tal dispositivo.
Bloqueando usuários fora dos menus do Android
Segurança à parte, tive que enfrentar um desafio ainda mais importante: tive que bloquear a pessoa usando o dispositivo Android POS dentro do meu aplicativo. Mexer com o sistema operacional não era uma opção, pois esses dispositivos eram entregues a pessoas não técnicas.
Claro, os caixas são mais do que capazes de instalar um aplicativo, mas a maioria deles não pode fazer flash de ROMs personalizados ou lidar com outras operações de nível inferior. O aplicativo em si foi escrito em React Native, embora isso seja irrelevante neste contexto. Todas as modificações que fiz estão no código Java nativo, portanto, não importa o que você esteja usando para desenvolver seu aplicativo principal, esses ajustes devem funcionar.
Como um pequeno aviso, este procedimento funciona apenas para aplicativos Android . A Apple não nos dá o controle de que precisamos para realizar facilmente algo assim em um iPhone ou iPad, o que é compreensível, dada a natureza fechada do iOS.
Há quatro maneiras pelas quais um usuário pode sair de um aplicativo:
- Use o botão Início .
- Use o botão Voltar .
- Use o botão Recentes .
- Saia do seu aplicativo pela barra de notificação .
Clicar em uma notificação recente ou acessar as configurações dessa barra faria com que o usuário saísse do nosso aplicativo. Você também tem gestos, mas no final do dia, esses gestos acionam exatamente as mesmas ações que os pressionamentos regulares de botões.
Além disso, ter um sistema de PIN para desbloquear o aplicativo pode ser muito útil para alguém gerenciar o dispositivo. Dessa forma, apenas alguém com um PIN poderá instalar uma versão diferente do aplicativo, sem oferecer acesso mais profundo ao usuário final.
O botão de início
Para evitar que um usuário pressione o botão Home, não precisamos desativá-lo.
Um recurso útil do Android é a disponibilidade de diferentes lançadores. Normalmente, esses aplicativos fornecem diferentes telas iniciais, gavetas de aplicativos e acesso a várias personalizações de interface do usuário. Todo dispositivo Android tem um pré-instalado pelo fabricante. Em última análise, esses são apenas aplicativos normais e regulares com uma exceção pequena, mas crucial.
O que isso significa é que se o sistema operacional pudesse reconhecer nosso aplicativo como sendo um iniciador, poderíamos defini-lo como um iniciador padrão. O efeito colateral disso é que toda vez que você pressionar o botão Home, o dispositivo o levará ao inicializador Home. E se nosso aplicativo for o inicializador Home, basicamente, esse botão Home se torna inútil. Para fazer isso, temos que editar o arquivo XML AndroidManifest em nosso projeto Android e adicionar estas duas linhas de código:
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.HOME" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
A primeira linha tornará nosso aplicativo elegível para ser selecionado caso o usuário pressione o botão Home, e a segunda linha permite que nosso aplicativo seja definido como padrão sempre que essa ação ocorrer.
Agora, restava apenas o agente de campo instalar o aplicativo no dispositivo ao entregá-lo ao cliente. Sempre que você instalar um aplicativo com potencial para ser um iniciador, o Android perguntará se você deseja usar outro iniciador e se deseja definir esse como padrão.
Então, agora, se você pressionar o botão Home ou se limpar todos os seus aplicativos recentes, o dispositivo o direcionará automaticamente para meu aplicativo.
O botão Voltar
Em seguida, temos que lidar com o botão Voltar. Os aplicativos móveis geralmente fornecem uma maneira na tela de voltar pelas telas, especialmente porque muitos dispositivos não têm uma tecla “voltar” dedicada.
Há alguns anos, era o caso dos dispositivos iOS da Apple, que apresentavam seu design já icônico com um único botão físico abaixo da tela. No entanto, nos últimos anos, a maioria dos dispositivos Android também abandonou os botões físicos Home. Primeiro, eles mudaram para os botões na tela, e agora estamos vendo-os sendo eliminados dos telefones em favor de gestos, à medida que os fabricantes de telefones mudam para dispositivos de tela inteira com pequenas molduras e queixo.
O que isso significa é que o botão Voltar que o Android fornece por padrão não é realmente necessário e, para tornar esse botão completamente inútil, basta adicionar um simples bloco de código em nossa atividade:
@Override public void onBackPressed() { }

Este é um bloco de código bastante simples: nossa atividade principal nos permite interceptar sempre que um usuário pressiona o botão Voltar. No nosso caso, como não queremos que o usuário pressione esse botão muitas vezes para sair do aplicativo, podemos simplesmente substituir o método padrão por um que não faz nada, dizendo ao nosso aplicativo para não fazer nada caso o Back botão é pressionado.
É assim que alguns aplicativos pedem confirmação antes de você sair acidentalmente deles voltando muitas vezes.
O botão Recentes
Ainda precisamos lidar com o botão Recentes, e este é o mais complicado. Além disso, essa definitivamente não é uma prática recomendada ou algo que você deve enviar para a Play Store, mas funciona para o nosso caso de nicho aqui.
Da mesma forma que a atividade principal nos permite saber quando o botão Voltar é pressionado, também nos permite saber quando o aplicativo está pausado. O que isto significa? Esse código é acionado toda vez que nosso aplicativo passa de aplicativo em primeiro plano para segundo plano.
Ao interceptar este evento, obteremos o ID da tarefa do nosso aplicativo atual e informaremos ao gerenciador de atividades para mover essa tarefa para a frente. Para fazer isso, precisamos de uma permissão especial no mesmo arquivo de manifesto do Android que editamos anteriormente.
<manifest xmlns:andro package="com.johnwick"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.REORDER_TASKS" /> <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme"> <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.HOME" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> </application> </manifest>
Isso nos permitirá ler as tarefas em andamento e fazer alterações nessas tarefas. Além disso, ainda precisamos interceptar o momento em que nosso aplicativo está sendo enviado para segundo plano. Podemos novamente substituir o método onPause
em nossa atividade.
Aqui, pegamos o gerenciador de tarefas e o forçamos a mover uma tarefa específica para o primeiro plano. No nosso caso, essa tarefa específica é aquela que acabou de ser enviada para segundo plano (nosso aplicativo).
@Override public void onPause() { super.onPause(); ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE); activityManager.moveTaskToFront(getTaskId(), 0); }
Agora, toda vez que você quiser ir ao menu Recentes, o aplicativo irá refocar automaticamente. Claro, você pode ter um pouco de tela piscando às vezes, mas não poderá sair deste aplicativo. E aqui está mais uma coisa legal - lembra como eu disse que você também pode sair clicando em uma notificação ou indo diretamente para as configurações na bandeja de notificações? Bem, executar essas ações colocará o aplicativo em segundo plano, o que acionará nosso código e, em seguida, o usuário será empurrado de volta.
Tudo isso acontece tão rapidamente que o usuário não perceberá o que acontece em segundo plano. Além disso, outra parte interessante dessa abordagem é que suas alternâncias rápidas ainda estão disponíveis. Você ainda pode selecionar uma rede wifi, por exemplo, ou desativar sons, mas qualquer coisa que exija que você acesse o aplicativo Configurações real não é permitido.
A solução
Não tenho certeza se esta é a melhor maneira de fazê-lo, mas, no entanto, foi um processo muito interessante enquanto pesquisava um tópico que eu nem sabia que era possível. E funciona! Uma palavra de aviso: neste ponto, existem apenas duas maneiras de sair do aplicativo como desenvolvedor - você reinstala o sistema operacional ou elimina/desinstala o aplicativo por meio do ADB.
Se você de alguma forma perder a conexão ADB com o dispositivo, não conheço uma maneira fácil de tirá-lo. Para evitar isso, acabei construindo um sistema PIN.
Casos de borda
Existem algumas situações que precisamos ter certeza de que contabilizamos. Em primeiro lugar, e se um dispositivo for reinicializado? Não precisa ser uma reinicialização manual, também pode ser uma falha do sistema operacional.
Como definimos nosso aplicativo como o inicializador padrão anteriormente, assim que o sistema operacional inicializar, ele deve iniciar automaticamente nosso aplicativo. Mas como o Android sabe carregar sua tela inicial na inicialização? É porque basicamente apenas carrega seu inicializador padrão. Como somos o inicializador padrão neste momento, as reinicializações não devem ser um problema. E o Android poderia matar nosso aplicativo em algum momento? Teoricamente, isso pode matar o aplicativo se a memória RAM ficar cheia, mas na vida real, isso é quase impossível. Como nosso aplicativo não pode ser fechado, ninguém pode abrir outros aplicativos e, portanto, a memória RAM não deve ser preenchida.
A única maneira de pensar em preencher é se nosso aplicativo tiver um grande vazamento de memória, mas nesse caso, você teria problemas maiores do que manter seu usuário dentro do aplicativo. Ainda assim, mesmo que o Android de alguma forma acione um sinal de morte para nosso aplicativo, sempre que você tentar voltar para casa, o sistema operacional tentará iniciar nosso aplicativo novamente, pois é o inicializador padrão, mantendo o usuário bloqueado.
Construindo uma porta dos fundos
Como explicação rápida, havia um local nas configurações do aplicativo onde você poderia inserir um PIN para desbloquear o aplicativo. Se o PIN estiver correto, ele desabilitará as limitações definidas por nossos métodos onPause e onBackPressed fazendo uma instrução condicional simples. A partir daí, um usuário teria permissão para inserir as configurações por meio do menu de alternância rápida. Depois, você sempre pode definir o iniciador padrão de volta para o estoque, e isso o tiraria completamente do aplicativo. Há muitas maneiras de lidar com essa parte, mas é bom ter um mecanismo para desativar as mesmas limitações que você colocou em prática. Talvez você possa fazer uma autenticação de impressão digital para desbloquear. As possibilidades são quase infinitas.
Empacotando
Eventualmente, fiquei com um aplicativo que ninguém poderia fechar ou matar. Mesmo a reinicialização do dispositivo não ajudaria, pois ele seria ligado diretamente no inicializador padrão, que atualmente é nosso aplicativo. Ele provou ser útil para o nosso projeto, e a satisfação de experimentar algo tão maluco e fora de linha foi realmente grande e altamente motivadora.
Existem muitos dispositivos e casos de uso em que o Android facilitou a vida dos desenvolvedores. Atualmente, escrever um aplicativo Android é muito mais fácil do que usar muitas linguagens e ferramentas específicas de plataforma diferentes. Pense em dispositivos IoT, aplicativos de quiosque, sistemas de ponto de venda, gateways de navegação e pagamento para táxis e muito mais.
Esses são casos de uso em que o Android facilitou o desenvolvimento de aplicativos, mas também casos de uso de nicho em que você deseja restringir o acesso de maneira semelhante ao que demonstramos neste artigo.