Advertisement
  1. Code
  2. Android SDK

Usa texto a voz en Android para leer mensajes entrantes

Scroll to top
Read Time: 9 min

Spanish (Español) translation by Ana Paulina Figueroa (you can also view the original English article)

Las aplicaciones con interfaces que hacen uso de la voz tienen un atractivo único. Estas tienden a lograr que sus usuarios sientan que están usando algo futurista. Desde sus inicios, Android ha tenido una funcionalidad de texto a voz (TTS, o text-to-speech en inglés) muy robusta. Este año, Google ha añadido muchas voces de alta calidad a su motor de TTS, y esta es una razón más por la que los desarrolladores deben usar esta funcionalidad en sus aplicaciones.

En este tutorial aprenderás cómo crear una aplicación simple — con una interfaz de usuario minimalista — que puede recibir mensajes de texto y leerlos al usuario.

Prerrequisitos

Asegúrate de tener configurado el paquete Eclipse ADT. Puedes descargarlo en el sitio web de Android Developer. Para obtener los mejores resultados, también vas a necesitar un dispositivo Android real y algunos amigos que puedan enviarte mensajes de texto.

1. Crea un nuevo proyecto

Abre Eclipse y crea una nueva aplicación de Android. Asigna el nombre SMSReader a esta aplicación. Si estás pensando en publicar esta aplicación en Google Play para compartirla con tus amigos, entonces asegúrate de usar un nombre de paquete único. Configura el valor de Minimum Required SDK (SDK mínimo requerido) para que sea Android 2.2 y establece el Target SDK (SDK destino) en Android 4.4.

New Android Application windowNew Android Application windowNew Android Application window

Esta aplicación tendrá una Activity. Selecciona Create Activity (Crear Activity) y elige Empty Activity (Activity vacía).

Create an Empty new ActivityCreate an Empty new ActivityCreate an Empty new Activity

Asígnale el nombre MainActivity y haz clic en Finish (Finalizar).

Name the ActivityName the ActivityName the Activity

2. Edita el manifiesto

Esta aplicación necesita tres permisos:

  • RECEIVE_SMS para saber que el dispositivo ha recibido un SMS
  • READ_SMS para leer ese SMS
  • READ_CONTACTS para relacionar el número telefónico del remitente con un nombre (si es posible)

Agrega las siguientes líneas a tu AndroidManifest.xml.

1
<uses-permission android:name="android.permission.READ_SMS"/>
2
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
3
<uses-permission android:name="android.permission.READ_CONTACTS"/>

Esta aplicación solamente va a tener una orientación de pantalla, vertical. Por lo tanto, edita la etiqueta activity y agrégale el siguiente atributo:

1
android:screenOrientation="portrait"

Ahora el manifiesto está completo.

3. Edita strings.xml

No es absolutamente necesario, pero almacenar todas las cadenas que la aplicación usa en el archivo res/values/strings.xml es una buena práctica. Edita este archivo para que tenga el siguiente contenido:

1
<?xml version="1.0" encoding="utf-8"?>
2
<resources>
3
    <string name="app_name">SMSReader</string>
4
    <string name="sms_label">Latest SMS</string>
5
    <string name="none">None</string>
6
    <string name="speech_toggle_on">START SPEAKING</string>
7
  <string name="speech_toggle_off">STOP SPEAKING</string>
8
	
9
	<string name="start_speaking">Okay! I will read your messages out loud for you now.</string>
10
	<string name="stop_speaking">Okay! I will stay silent now.</string>
11
</resources>

La mayoría de estas cadenas se usan en el siguiente paso.

4. Edita el diseño

Edita res/layout/activity_main.xml para añadir lo siguiente:

  • un TextView para mostrar el nombre de la persona que envió el SMS más reciente
  • un TextView para mostrar el contenido del SMS más reciente
  • Un ToggleButton para activar y desactivar la salida de voz

Después de añadir código para posicionar y estilizar estos elementos, tu archivo deberá tener el siguiente contenido:

1
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2
    xmlns:tools="http://schemas.android.com/tools"
3
    android:layout_width="match_parent"
4
    android:layout_height="match_parent"
5
    tools:context="${packageName}.${activityClass}"
6
    android:background="#99CC00"
7
    >
8
9
    <TextView
10
        android:id="@+id/sms_sender"
11
        android:layout_width="wrap_content"
12
        android:layout_height="wrap_content"
13
        android:layout_alignParentTop="true"
14
        android:layout_centerHorizontal="true"
15
        android:layout_marginTop="20dp"
16
        android:text="@string/sms_label"
17
        android:textColor="#ffffff"
18
        android:textAppearance="?android:attr/textAppearanceSmall" />
19
20
    <TextView
21
        android:id="@+id/sms_text"
22
        android:layout_width="wrap_content"
23
        android:layout_height="wrap_content"
24
        android:layout_below="@+id/sms_sender"
25
        android:layout_centerHorizontal="true"
26
        android:layout_marginTop="10dp"
27
        android:text="@string/none"
28
        android:textAppearance="?android:attr/textAppearanceLarge" 
29
        android:textColor="#ffffff"
30
        />
31
32
    <ToggleButton
33
        android:id="@+id/speechToggle"
34
        android:layout_width="wrap_content"
35
        android:layout_height="wrap_content"
36
        android:layout_centerHorizontal="true"
37
        android:layout_centerVertical="true"
38
        android:textOff="@string/speech_toggle_on"
39
        android:textOn="@string/speech_toggle_off"                 
40
        />
41
    
42
</RelativeLayout>

El diseño para nuestra aplicación ahora está terminado.

5. Crea una clase de ayuda

Ahora vamos a crear una clase de ayuda para el motor de TTS. Crea una nueva clase de Java y asígnale el nombre Speaker.java. Esta clase se usa para evitar invocar a la API TTS directamente desde la Activity.

Esta clase implementa la interfaz OnInitListener para que sepa cuando el motor de TTS esté listo. Almacenamos este estado en una variable booleana llamada ready. Usamos otra variable booleana llamada allowed cuyo valor es true solamente si el usuario ha permitido que el motor de TTS hable. También añadimos métodos para obtener y establecer los valores de esta variable. En este punto, Speaker.java debe tener el siguiente contenido:

1
public class Speaker implements OnInitListener {
2
3
    private TextToSpeech tts;
4
	
5
	private boolean ready = false;
6
	
7
	private boolean allowed = false;
8
	
9
	public Speaker(Context context){
10
		tts = new TextToSpeech(context, this);		
11
	}	
12
	
13
	public boolean isAllowed(){
14
		return allowed;
15
	}
16
	
17
	public void allow(boolean allowed){
18
		this.allowed = allowed;
19
	}
20
}

La interfaz OnInitListener solamente tiene un método, onInit. Este método es invocado cuando el motor de TTS ha sido inicializado. El parámetro status nos permite saber si la inicialización tuvo éxito. Una vez que sabemos que la inicialización fue exitosa, establecemos el lenguaje del motor de TTS. Esto es importante paras que sea comprensible. Agrega el siguiente código:

1
@Override
2
public void onInit(int status) {
3
	if(status == TextToSpeech.SUCCESS){
4
		// Change this to match your

5
		// locale

6
		tts.setLanguage(Locale.US);
7
		ready = true;
8
	}else{
9
		ready = false;
10
	}
11
}

A continuación vamos a añadir un método llamado speak, que usa el motor para leer cualquier texto que se le envíe. Antes de hacer eso, verifica si los valores allowed y ready tienen el valor true. La voz que genera se coloca en el flujo de notificaciones.

1
public void speak(String text){
2
	
3
	// Speak only if the TTS is ready

4
	// and the user has allowed speech

5
	
6
	if(ready && allowed) {
7
		HashMap<String, String> hash = new HashMap<String,String>();
8
		hash.put(TextToSpeech.Engine.KEY_PARAM_STREAM, 
9
				String.valueOf(AudioManager.STREAM_NOTIFICATION));
10
		tts.speak(text, TextToSpeech.QUEUE_ADD, hash);
11
	}
12
}

Después añadimos un método que reproduce silencio durante un lapso específico. Usando este método, podemos añadir pausas en la voz para que suene un poco más clara. Agrega el siguiente código a la implementación:

1
public void pause(int duration){
2
	tts.playSilence(duration, TextToSpeech.QUEUE_ADD, null);
3
}

Finalmente agrega un método para liberar recursos cuando el motor de TTS ya no sea necesario.

1
// Free up resources

2
public void destroy(){
3
	tts.shutdown();
4
}

6. Edita la clase Activity

Edita MainActivity.java y declara todas las vistas que mencionamos en el diseño. Declara dos enteros, LONG_DURATION y SHORT_DURATION. Estos son solamente valores que se envían al método pause de Speaker.

También declara un entero CHECK_CODE. Su valor no es importante. Este se envía al método startActivityforResult y luego es usado para identificar el resultado.

Por último, declara un objeto Speaker y un objeto BroadcastReceiver.

En este momento, tu clase debe verse de esta manera:

1
public class MainActivity extends Activity {    
2
    
3
	private final int CHECK_CODE = 0x1;
4
	private final int LONG_DURATION = 5000;
5
	private final int SHORT_DURATION = 1200;
6
	
7
	private Speaker speaker;	
8
	
9
	private ToggleButton toggle;
10
	private OnCheckedChangeListener toggleListener;
11
	
12
	private TextView smsText;
13
	private TextView smsSender;
14
	
15
	private BroadcastReceiver smsReceiver;
16
}

Agrega un método para verificar si hay algún motor de TTS instalado en el dispositivo. La verificación se lleva a cabo haciendo uso del resultado de otra Activity.

1
private void checkTTS(){
2
	Intent check = new Intent();
3
	check.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
4
	startActivityForResult(check, CHECK_CODE);
5
}

Cuando el resultado de startActivityForResult llega, se invoca el método onActivityResult. Por lo tanto necesitamos sobrescribirlo. En este método, si el resultado es positivo inicializamos el objeto Speaker. Si no hay ningún motor de TTS instalado, entonces redireccionamos al usuario para que lo instale.

1
@Override
2
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
3
	if(requestCode == CHECK_CODE){
4
		if(resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS){
5
			speaker = new Speaker(this);
6
		}else {
7
            Intent install = new Intent();
8
            install.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
9
            startActivity(install);
10
        }
11
	}
12
}

Ahora es momento de crear nuestro BroadcastReceiver para gestionar los mensajes que el dispositivo esté recibiendo. Cada vez que haya nuevos mensajes se invocará el método onReceive. Analizamos los mensajes, que llegan como arreglos de tipo byte, usando la clase SmsMessage. Una vez que el mensaje haya sido analizado, usamos métodos como getDisplayMessageBody y getOriginatingAddress para extraer información útil de él.

Con esta información generamos el texto que el motor de TTS debe leer en voz alta. Pausamos durante el valor de LONG_DURATION antes de leer un nuevo SMS y durante el valor de SHORT_DURATION entre el momento en el que se dice el nombre del remitente del SMS y el momento en el que se dice el cuerpo del SMS.

Agrega el siguiente código a la implementación:

1
private void initializeSMSReceiver(){
2
	smsReceiver = new BroadcastReceiver(){
3
		@Override
4
		public void onReceive(Context context, Intent intent) {
5
			
6
			Bundle bundle = intent.getExtras();
7
			if(bundle!=null){
8
				Object[] pdus = (Object[])bundle.get("pdus");
9
				for(int i=0;i<pdus.length;i++){
10
					byte[] pdu = (byte[])pdus[i];
11
					SmsMessage message = SmsMessage.createFromPdu(pdu);
12
					String text = message.getDisplayMessageBody();
13
					String sender = getContactName(message.getOriginatingAddress());
14
					speaker.pause(LONG_DURATION);
15
					speaker.speak("You have a new message from" + sender + "!");
16
					speaker.pause(SHORT_DURATION);
17
					speaker.speak(text);
18
					smsSender.setText("Message from " + sender);
19
					smsText.setText(text);
20
				}
21
			}
22
			
23
		}			
24
	};		
25
}

Solamente podemos extraer el número telefónico del remitente usando el mensaje. Para relacionar este número con el nombre de un contacto, tenemos que usar los contactos del usuario. El siguiente método hace una consulta en los datos de los contactos. Si el número telefónico no está disponible en los contactos del usuario, entonces simplemente devuelve la cadena unknown number:

1
private String getContactName(String phone){
2
	Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phone));
3
	String projection[] = new String[]{ContactsContract.Data.DISPLAY_NAME};
4
	Cursor cursor = getContentResolver().query(uri, projection, null, null, null);				
5
	if(cursor.moveToFirst()){
6
		return cursor.getString(0);
7
	}else {
8
		return "unknown number";
9
	}
10
}

Antes de poder usar el BroadcastReceiver, este tiene que ser registrado. En el siguiente método creamos un IntentFilter para mensajes de texto entrantes y luego registramos nuestro smsReceiver para él:

1
private void registerSMSReceiver() {	
2
	IntentFilter intentFilter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
3
	registerReceiver(smsReceiver, intentFilter);
4
}

A continuación creamos el método onCreate. Aquí es donde vamos a inicializar todos los objetos que declaramos. Inicializamos el toggleListener para establecer el valor de allowed en la clase Speaker.

Después de estas inicializaciones, invocamos a los métodos checkTTSinitializeSMSReceiver y registerSMSReceiver.

1
@Override
2
protected void onCreate(Bundle savedInstanceState) {
3
	super.onCreate(savedInstanceState);
4
	setContentView(R.layout.activity_main);			
5
	
6
	toggle = (ToggleButton)findViewById(R.id.speechToggle);	
7
	smsText = (TextView)findViewById(R.id.sms_text);
8
	smsSender = (TextView)findViewById(R.id.sms_sender);
9
10
	toggleListener = new OnCheckedChangeListener() {			
11
		@Override
12
		public void onCheckedChanged(CompoundButton view, boolean isChecked) {
13
			if(isChecked){
14
				speaker.allow(true);
15
				speaker.speak(getString(R.string.start_speaking));
16
			}else{
17
				speaker.speak(getString(R.string.stop_speaking));
18
				speaker.allow(false);					
19
			}
20
		}
21
	};		
22
	toggle.setOnCheckedChangeListener(toggleListener);
23
	
24
	checkTTS();
25
	initializeSMSReceiver();
26
	registerSMSReceiver();
27
}	

Finalmente, en el método onDestroy de la activity eliminamos el registro de nuestro receptor y desconectamos el motor de TTS para liberar recursos.

1
@Override
2
protected void onDestroy() {	
3
	super.onDestroy();
4
	unregisterReceiver(smsReceiver);
5
	speaker.destroy();
6
}

7. Ejecuta y prueba

La aplicación ahora está lista para ser probada. Compila y ejecútala en un dispositivo Android físico. Pulsa el botón para activar la voz y envíate un SMS desde otro teléfono, o pide a alguno de tus amigos que lo haga. Pronto deberás poder escuchar tu teléfono leyendo el SMS por ti.

Este es un ejemplo de la voz generada por el motor de TTS:

Conclusión

En este tutorial has aprendido no solamente a usar la API de texto a voz, sino también a usar receptores de difusión y a entender los datos sin procesar de los SMS. Ahora puedes continuar y personalizar esta aplicación de acuerdo a tus necesidades.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.