«

»

dic 29 2013

Integrar comandos personalizados en Google Voice Search

androcode_voicesearch

No os mentiré si os digo que cada vez que uso o pruebo Google Now y Google Voice Search (a veces erróneamente interpretados como la misma cosa) no puedo evitar pensar en la de posibilidades que tendríamos de existir una API con la que integrar nuestras aplicaciones. Y no os mentiré tampoco si os digo que a día de hoy no existe tal cosa… de manera oficial. Existe una forma de aprovechar Google Voice Search al menos para interceptar los comandos de voz que le demos, y hoy os voy a contar cómo. Aquí tenéis un ejemplo de lo que yo mismo conseguí en una mañana para controlar remotamente mi estufa gracias a mi Raspberry Pi.

Google Voice Search API y Xposed Framework

La magia que cuento arriba tiene condiciones, no todo podía ser de color rosa. Es necesario tener el acceso root en el dispositivo, e instalar el famoso Xposed Framework. Éste es un framework que funciona mediante módulos, con los que se permite modificar el comportamiento de ciertas partes del sistema sin necesidad de instalar ninguna ROM especial, sólo teniendo acceso root. Hay multitud de módulos para realizar diversas modificaciones, entre ellos uno gracias a Mohammad Abu-Garbeyyeh que nos da acceso a una API para trabajar con Google Search. No es tan potente como podría de ser una API oficial, pero más que suficiente para hacer alguna cosa curiosa como la que veis en el vídeo anterior. No os voy a explicar cómo montar todo esto en vuestro teléfono. Es realmente fácil, y si no sabéis hay muchos tutoriales por la red. Sólo hay que tener root, instalar Xposed Framework, e instalar la Google Voice Search API.

IWbHojml

El funcionamiento es francamente simple: cada vez que el usuario haga una búsqueda en Google, se lanzará un Broadcast Intent con el texto de búsqueda. Nuestra aplicación puede recibir dicho Intent y hacer lo que le apetezca con la cadena de búsqueda: buscar patrones o palabras claves, lanzar una aplicación, ejecutar un servicio, etc.

Notice

Como todo lo que requiere root, hay que tener cuidado. Esta API enviará siempre todas las búsquedas en la aplicación de Google Search a las aplicaciones que lo soliciten. En otras palabras, cualquier aplicación que haga uso de la API podría potencialmente saber qué cosas buscamos, lo que en determinados casos podría ser un problema de privacidad. Aunque el uso de la API requiere un permiso especial, mucho ojo con lo que instalamos.

Show me the code

Veamos algún ejemplo de cómo usar esta API. Os muestro la parte del código que utilizo yo en el ejemplo de la estufa remota que puse más arriba. Lo primero que os recomiendo es copiar este archivo de código a vuestra aplicación. Contiene las constantes para manejar los Intents de recepción de consultas y contestar mediante voz.

package com.mohammadag.googlesearchapi;

import android.content.Context;
import android.content.Intent;

public class GoogleSearchApi {
	public static final String INTENT_NEW_SEARCH = "com.mohammadag.googlesearchapi.NEW_SEARCH";
	public static final String INTENT_REQUEST_SPEAK = "com.mohammadag.googlesearchapi.SPEAK";

	public static final String KEY_VOICE_TYPE = "is_voice_search";
	public static final String KEY_QUERY_TEXT = "query_text";
	public static final String KEY_TEXT_TO_SPEAK = "text_to_speak";

	/* This method allows you to do TTS without implementing your own activity.
	 * Note that this may not work if Google Search is not in the foreground.
	 */
	public static void speak(Context context, String text) {
		Intent intent = new Intent(INTENT_REQUEST_SPEAK);
		intent.putExtra(KEY_TEXT_TO_SPEAK, text);
		context.sendBroadcast(intent);
	}
}

A continuación creamos un BroadcastReceiver que recibirá el Intent de búsqueda, y opcionalmente otros. Limitarse a admitir sólo este método para recibir comandos es limitarse mucho. Si implementáis comandos por voz en vuestra aplicación, quizá queráis permitir la introducción de comandos a usuarios que no tengan root ni Xposed Framework instalado, por ejemplo mediante un widget dedicado que active el reconocimiento, o un botón en la misma aplicación. Dependerá de vuestro caso, en el mío decidí asignar dos acciones adicionales al Receiver para encender y apagar la estufa por otros medios que no vienen ahora al caso.

public class HeaterBroadcastReceiver extends BroadcastReceiver {

    public static final String ACTION_HEATER_ON = "com.sloydev.remoteheater.ACTION_HEATER_ON";
    public static final String ACTION_HEATER_OFF = "com.sloydev.remoteheater.ACTION_HEATER_OFF";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null) {
            return;
        }
        Log.d("Heater", "Broadcast Received: " + action);

        if (action.equals(GoogleSearchApi.INTENT_NEW_SEARCH)) {
            String queryText = intent.getStringExtra(GoogleSearchApi.KEY_QUERY_TEXT);
            if (queryText == null) {
                return;
            }
            queryText = queryText.toLowerCase();

            if (queryText.contains("make it burn")) {
                action = HeaterBroadcastReceiver.ACTION_HEATER_ON;
                GoogleSearchApi.speak(context, "Burning");
            } else if (queryText.contains("stop it")) {
                action = HeaterBroadcastReceiver.ACTION_HEATER_OFF;
                GoogleSearchApi.speak(context, "Yes sir");
            } else if (queryText.contains("heater") && queryText.contains("turn")) {
                if (queryText.contains("on")) {
                    action = HeaterBroadcastReceiver.ACTION_HEATER_ON;
                    GoogleSearchApi.speak(context, "Turning it on");
                } else if (queryText.contains("off")) {
                    action = HeaterBroadcastReceiver.ACTION_HEATER_OFF;
                    GoogleSearchApi.speak(context, "Turning it off");
                } else {
                    return;
                }
            } else if (queryText.contains("estufa")) { //Español
                if (queryText.contains("enciende") || queryText.contains("encender")) {
                    action = HeaterBroadcastReceiver.ACTION_HEATER_ON;
                    GoogleSearchApi.speak(context, "Encendiendo");
                } else if (queryText.contains("apaga")) {
                    action = HeaterBroadcastReceiver.ACTION_HEATER_OFF;
                    GoogleSearchApi.speak(context, "Apagando");
                }
            } else {
                return;
            }
        }

        // Lanzo otro intent para abrir un IntentService que, en mi caso, hará lo necesario 
        // para encender o apagar la estufa según el contenido de action
        Intent serviceIntent = new Intent(context, HeaterController.class);
        serviceIntent.setAction(action);
        context.startService(serviceIntent);
    }
}

Como veis el código es bastante intuitivo. Cuando recibe un Intent, primero comprueba si la acción que lo lanzó corresponde a la de Google Search. En caso afirmativo, busca determinadas palabras clave en el texto. En resumen, cuando el comando contenga la frase “make it burn” encenderá la estufa, y cuando contenga “stop it” la apagará. Además, si contiene las palabras “heater” y “turn“, la encenderá o apagará dependiendo de si contiene “on” o “off“. Y por último, por si dije el comando en español, si contiene la palabra “estufa” buscará “enciende/encender” o “apaga/apagar” para realizar la acción correspondiente. En todos los casos el móvil contesta con una frase en voz alta para confirmar la acción.

Al final, lanzo un Intent con la acción seleccionada para ejecutar un IntentService, que en el caso de mi estufa conectará con la Raspberry Pi, pero esta parte cambiará en vuestra aplicación. Hay que tener en cuenta que este Receiver se llamará cada vez que hagamos una búsqueda cualquiera en la aplicación de Google Search, por lo que si el texto recibido no se corresponde con ningún comando nuestro debemos salir del método sin hacer nada. Además, en mi caso como dije este Receiver puede recibir otras dos acciones aparte de la de búsqueda, por lo que si la acción recibida no era ésta se la paso directamente al IntentService, ya que éste sabe lo que debe hacer con ellas. Por último, en el AndroidManifest debemos añadir un permiso para usar esta API, y de paso declaramos el Receiver para recibir los comandos de voz.

   ...
    <uses-permission android:name="com.mohammadag.googlesearchapi.permission.ACCESS_GGOGLE_SEARCH_API" />

    <application
        ...
        >
        <receiver android:name="com.sloydev.remoteheater.HeaterBroadcastReceiver">
            <intent-filter>
                <action android:name="com.sloydev.remoteheater.ACTION_HEATER_ON"/>
                <action android:name="com.sloydev.remoteheater.ACTION_HEATER_OFF"/>
                <action android:name="com.mohammadag.googlesearchapi.NEW_SEARCH" />
            </intent-filter>
        </receiver>
        ...

Y listo, ya tenemos nuestra aplicación recibiendo comandos de voz personalizados. Podéis ver más ejemplos e información en el post oficial del módulo en XDA. ¡A disfrutarlo! Contandos, ¿qué os gustaría hacer con esta API?

Acerca del autor

Rafa Vázquez

Estudio Ingeniería Informática del Software en Sevilla. Me considero geek sin dinero, amante y desarrollador novato de Android. He creado algunas aplicaciones como SeviBus, TicTacDroide, Kill Bieber y Traductor Hoygan, si es que se puede llamar aplicaciones a estas dos últimas ;) Ganas de aprender más y más no me faltan, e intentaré compartir mis experiencias con vosotros en la medida de lo posible.

  • Joaquin

    Muy bueno el contenido amigo!

  • https://www.facebook.com/gerardo.hernandezvite.9 Gerardo Hernandez Vite

    wao esto es lo que estaba buscando para mi app michas gracias