Interação com voz no Android: Aprenda a desenvolver um aplicativo com interação por voz no Android

Desenvolvendo as funcionalidades

Primeiro, vamos importar os pacotes que serão usados no desenvolvimento do nosso projeto. Importe os pacotes presentes na próxima  listagem. Os pacotes referentes ao RecognizerIntent e o OnClickListener representam as importações necessárias para que o aplicativo possa fazer o reconhecimento do que está sendo falado. Os imports são extremamente importantes para o funcionamento da aplicação, caso alguns deles sejam esquecidos é muito provável que o aplicativo não compile e muito menos funcione.

package com.example.recvoice;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.widget.TextView;

Na programação Java para Android toda classe deriva da classe Activity, logo, em toda declaração de classe temos de ter um extends para a classe Activity. Assim como no Java convencional, onde temos as interfaces que são classes que possuem somente a declaração do método, sem a sua implementação, em Java para android não é diferente. Como essas classes não possuem a implementação de seus métodos é necessário então fazer isso em outra classe. Para isso utiliza-se o implements na declaração da classe que vai implementar todos os métodos da classe interface (é obrigatório implementar todos os métodos), tal ferramenta é útil na utilização de polimorfismo, que é um conceito comum no paradigma orientado a objetos.

No caso do aplicativo que esse artigo descreve será feita a implementação da classe interface SensorEventListener. Essa classe é utilizada para receber notificações da classe SensorManager quando os valores dos sensores presentes em um dispositivo móvel for alterado (dois exemplos de sensores são o acelerômetro e o giroscópio).

A declaração da classe VoiceActivity que implementará a classe interface SensorEventListener pode ser observada na listagem:

public class VoiceActivity extends Activity
    implements OnInitListener {

Em seguida declare as variáveis presentes na próxima listagem. Como foram descritos na secção que trata da interface da aplicação termos elementos na “programação” que farão referência a esses elementos, que são as variáveis. Logo temos a variável relativa ao botão que redireciona para interface da guitarra do piano e ao de fala. Assim como a ListView que será utilizada para buscarmos a palavra guitarra ou piano.

Foi descrito acima também que a classe SensorEventListener trata as notificações emitidas por um elemento da classe SensorManager, logo faz-se necessário a existência de uma variável que faça referência a essa classe.

Abaixo temos a declaração das variáveis:

private static final int REQUEST_CODE = 1234;
private ListView resultList;
private String telaAtiva;
private Button speakButton;
private Button backButton;
private Button bguitarra;
private Button bpiano;
private SensorManager sManager;
private int valor = 0;

O método onCreate é responsável por criar e inicializar a interface quando o aplicativo é inicializado.

Na próxima listagem criamos um objeto da classes SensorManager onde posteriormente chamaremos um método (voice_tela) que será responsável por inicializar e exibir a interface da tela principal. Essa “exibição” poderia ser feita também dentro desse método (e normalmente é), porém por uma questão de lógica e economia de código vamos fazer isso dentro de um outro método.

public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  telaAtiva = "inicial";
  sManager = (SensorManager) getSystemService(SENSOR_SERVICE);
  sManager.registerListener(this, sManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), sManager.SENSOR_DELAY_NORMAL);
  voice_tela();
}

Criamos um método para a exibição dos elementos de interface. Nesse método acessaremos cada elemento através de seu ID, que, por sua vez, foram definidos no código XML utilizados para montar a interface (finalmente vamos utilizá-lo!), a busca por esses IDs será feita através do método findViewById. É possível observar também que setamos a interface construída no arquivo XML, isso é feito através do método setContentView, esse método possui como argumento o nome do XML onde foi definido a interface.

Na próxima listagem está a implementação do método voice_tela, onde primeiramente carregamos a tela principal do aplicativo, definida pelo arquivo XML activity_voice. Instancia-se os botões que redirecionam a tela de piano e guitarra(bt_piano e bt_guitarra respectivamente). Logo abaixo atribuímos algumas características visuais aos botões.

setContentView(R.layout.activity_voice);
speakButton = (Button) findViewById (R.id.speakButton);
bguitarra = (Button) findViewById(R.id.bguitarra);
bpiano = (Button) findViewById (R.id.bpiano);
bpiano.setEnabled(false);
bpiano.setVisibility(0);
bguitarra.setEnabled(false); bguitarra.setVisibility(0);
resultList = (ListView);

Nesse momento vamos começar o desenvolvimento da parte relativa ao botão para falar para a aplicação, ou seja, é o momento do RecognizerIntent. Primeiramente vamos criar e inicializar uma variável da classe PackageManager, essa classe recupera informações de vários tipos sobre os pacotes da aplicação que estão instalados no dispositivo. Uma questão crucial que não pode ser esquecida é que temos de atribuir uma ação quando um botão é pressionado, caso contrário a sua existência não faria sentido, imagine pressionar um botão no aplicativo e em nada o seu comportamento for alterado.

Logo para atribuir uma ação quando um botão é pressionado utilizamos o método setOnClickListener. Dentro desse método existe outro método chamado onClick, é dentro dele que a ação realmente acontecerá. Exportando a próxima listagem para uma aplicação prática veremos que se o botão speakButton for pressionado então teremos de executar o método startVoiceRecognitionActivity, logo ele deve estar dentro do método onClick. Nesse caso acessaremos o PackageManager para descobrir se a captação de áudio do dispositivo móvel está realmente ativa. Na próxima listagem verificamos se os serviços para a captação de voz estão disponíveis, caso não estejam é exibido um Toast avisando que o Recognizer não está disponível. Conseguimos essa informação através da classe PackageManager que possui dados sobre os pacotes instalados atualmente na aplicação. Abaixo é possível observar o evento relacionado ao botão que deve ser pressionado para falar (speakButton), está definido que no momento em que ele for pressionado o método startVoiceRecognitionActivity será chamado.

// Desabilitamos o botao caso nao tenha o servico
PackageManager pm = getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(
new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
if (activities.size() == 0) {
  speakButton.setEnabled(false);
  Toast.makeText(getApplicationContext(), "Reconhecedor de voz nao encontrado", Toast.LENGTH_LONG).show();
}
speakButton.setOnClickListener(new OnClickListener() {
  public void onClick(View v) {
    startVoiceRecognitionActivity();
  }
});

Vamos agora para a implementação do método startVoiceRecognitionActivity. Primeiramente cria-se um objeto da classes Intent, pois é através dela que podemos enviar comandos ao sistema operacional Android para a realização de alguma ação, além de poder enviar e recuperar dados. O método putExtra insere na Intent algum valor, ele possui dois parâmetros, o primeiro é um nome para identificação e o segundo um valor que pode ser de qualquer tipo (string, inteiro, float, etc). Utilizamos o putExtra na aplicação pois vamos atribuir à string EXTRA_LANGUAGE_MODEL o valor LANGUAGE_MODEL_FREE_FORM. Esse valor não delimita o reconhecimento a uma linguagem específica. Segue abaixo a implementação do método startVoiceRecognitionActivity, onde são escolhidos os parâmetros que serão usados na requisição, já que o reconhecimento não é processado pelo dispositivo móvel, mas sim por um servidor do Google.

private void startVoiceRecognitionActivity() {
  Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
  intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
  intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "AndroidBite Voice Recognition...");
  startActivityForResult(intent, REQUEST_CODE);
}

Finalmente chegamos ao momento em que detectamos qual a palavra que foi dita e realizamos uma ação se ela for guitarra ou piano. O mecanismo de análise é incrivelmente simples, primeiramente o usuário diz algo ao dispositivo móvel, todos os resultados possíveis são armazenados na ArrayList matches. Então basta percorrer essa lista e identificar se uma das palavras esperadas encontra-se lá. Nesse caso se encontrarmos a palavra guitarra ou piano redirecionamos a aplicação para uma tela cujo plano de fundo é o respectivo instrumento.

Antes de fazer isso é necessário fazer a verificação se as permissões foram realmente adquiridas, isso através do REQUEST_CODE, feito a verificação e tudo estiver certo, a busca é realizada. Na próxima listagem temos a implementação do método onActivityResult, onde todo o mecanismo de busca descrito acima é implementado.

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
    ArrayList<String> matches = data.getStringArrayListExtra( RecognizerIntent.EXTRA_RESULTS);
    resultList.setAdapter(newArrayAdapter<String>(this, android.R.layout.simple_list_item_1, matches));
    for(int i = 0; i < matches.size();i++) {
      if(matches.get(i).equalsIgnoreCase("guitarra")) {
        tela_guit();
        break;
      }
      if(matches.get(i).equalsIgnoreCase("piano")) {
        tela_piano();
        break;
      }
    }
  }
  super.onActivityResult(requestCode, resultCode, data);
}

Para acessar as telas relativas ao piano e à guitarra usamos os métodos tela_piano e tela_guit, respectivamente. As próximas listagens dizem respeito aos eventos atribuídos aos botões bguitarra e bpiano. Em ambos os casos temos o mesmo conceito de evento do botão sendo pressionado para a chamada de um método.

bguitarra.setOnClickListener(new View.OnClickListener() {
  public void onClick(View v) {
    tela_guit();
    telaAtiva = "guitarra";
  }
});
bpiano.setOnClickListener(new View.OnClickListener() {
  public void onClick(View v) {
    tela_piano();
    telaAtiva = "piano";
  }
});

Seguimos agora para a implementação dos métodos tela_guit e tela_piano, em ambos o layout definido no arquivo XML explicado anteriormente será acessado e setado na tela do dispositivo móvel. Isso é feito através do método setContentView, é possível observar que o padrão seguido na tela principal é o mesmo definido para todas. Dentro das duas telas teremos botões que redirecionarão para a tela principal, pois quando pressionados chamarão o método voice_tela, que, por sua vez, redireciona para a tela principal da aplicação. Seguem os métodos relativos a tela_piano e tela_guit, que possuem o mecanismo descrito acima.

public void tela_piano() {
  setContentView(R.layout.itf_piano);
  backButton = (Button) findViewById(R.id.btbackp);
  backButton.setOnClickListener(new OnClickListener() {
    public void onClick(View v) {
      voice_tela();
    }
  });
}
public void tela_guit() {
  setContentView(R.layout.itf_guitarra);
  backButton = (Button) findViewById(R.id.btback);
  backButton.setOnClickListener(new OnClickListener() {
    public void onClick(View v) {
      voice_tela();
    }
  });
}

Permissões e AndroidManifest.xml

Esse é o arquivo principal do projeto, pois dentro dele estão todas as configurações. A aplicação em questão necessita de conexão com a internet para que funcione, porém essa conexão tem de ser permitida primeiramente. Isso é feito adicionando uma permissão a um arquivo XML chamado Androidmanifest.xml. Para adicionarmos a permissão basta adicionar a seguinte linha depois da tag manifest:

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

Dessa forma garantiremos que o dispositivo móvel permitirá que o arquivo se conecte com a internet e realiza a busca pela palavra pronunciada.

Conclusões

Este artigo apresentou as características básicas para se construir um aplicativo que “ouve” o usuário e realiza uma busca pelo que foi dito. Após a análise da voz, uma mudança de tela pode ocorrer, mais especificamente se o usuário disser as palavras guitarra ou piano será redirecionado para uma tela que contém a imagem do instrumento como plano de fundo. Veja que isso é um exemplo ingênuo do que pode ser feito, conseguir reconhecer e atribuir uma ação através da fala abre inúmeras possibilidades de desenvolvimento!

Links

  1. Site oficial do Android SDKhttp://developer.android.com/sdk/index.html
  2. Site com guias para iniciantes no desenvolvimento para Androidhttp://developer.android.com/training/index.html

Publicado na edição 43 (PDF) da Revista PROGRAMAR.