lunes, 27 de febrero de 2012

Aplicación en modo kiosko para Android

Hace un tiempo tuve que migrar una aplicación de Windows Mobile a Android. En sentido general el desarrollo en Android fue más simple y más rápido; pero cuando llegué al punto de duplicar el comportamiento del "modo kiosko", Android se tornó casi tan arisco como Windows Mobile. Aquí publico hoy la solución que apliqué para que sirva de base a otros desarrollos.

No es que hacer una aplicación que se ejecute en modo kiosko en Windows Mobile sea simple. De hecho lograr que la aplicación se mantuviera siempre en primer plano bloqueando los botones de hardware del teléfono fue toda una odisea que necesitó alguna chapucilla código particularizado para cubrir limitaciones (como que en los teléfonos Samsung se nos estaba vetado deshabilitar el botón de la cámara -según nos confirmaron desde Samsung- y, por tanto, cada vez que se pulsaba el botón de la cámara la aplicación perdía el foco).

Los teléfonos con Android, por suerte, tienen unas APIs más uniformes, pero no por esto el modo kiosko deja de ser un reto. Para comenzar, no encontré ningún lugar donde pusieran en claro qué hacer para que una interfaz se mantuviera en primer plano y a pantalla completa, independientemente de la interacción que tuviera el usuario con el dispositivo. Debido a que la información que encontré era incompleta y estaba dispersa, hoy pongo aquí el resumen de lo necesario para crear una aplicación que se ejecute en modo kiosko, acompañado del código fuente de un proyecto sobre el que se puede partir para hacer una aplicación que ya satisface estos requisitos.

Para comenzar, una aplicación en modo kiosko en Android debe:
  1. Mostrarse a pantalla completa, de modo que el usuario no se salga de la aplicación accediendo a otras opciones que aparezcan en la zona de notificaciones, en la barra de título.
  2. Bloquear los soft buttons y botones de hardware, de modo que el usuario no pueda salirse de la aplicacion presionando el botón back ni se interrumpa la aplicación por la presión de otros botones como el control del volumen.
  3. Remplazar el home del dispositivo por la pantalla inicial de la aplicación, de modo que, al presionar el botón "home" (que lamentablemente no puede ser interceptado, como el resto de botones) en lugar de salir de la aplicación para mostrar la pantalla de inicio del teléfono, se muestre la aplicación y se pueda restablecer la interfaz al punto donde se estaba.
Debo puntualizar que esta solución no es La Solución para todas las aplicaciones que requieran ejecutarse en modo kiosko pues cada una puede requerir un mayor o menor grado de control. Sin embargo, la esencia de dicho control puede obtenerse haciendo que las actividades de nuestra aplicación extiendan la clase KioskModeActivity cuyo código es el siguiente:

package org.carlosbello.android.kioskmode;

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;

/**
 * Actividad de base para crear interfaces en modo kiosko.
 * @author Carlos Bello
 */
public class KioskModeActivity extends Activity {
    /** Indica si deberá desactivarse el uso del botón de retroceso. */
    protected boolean blockBackButton = false;
    
    /**
     * Establece la visualización a pantalla completa para evitar una salida 
     * accidental a través de la barra de título o cualquier otro elemento 
     * interactivo que no sea nuestra propia interfaz.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }
    
    /**
     * Intercepta los eventos del teclado para evitar la interacción con 
     * cualquier elemento que no forme parte de nuestra interfaz, salvo la 
     * funcionalidad del botón de retroceso, si estuviera habilitado.
     */    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event)  {
        return !blockBackButton && keyCode == KeyEvent.KEYCODE_BACK
            ? super.onKeyDown(keyCode, event)
            : true;
    }
}

Ahora, para evitar salirse de la aplicación, la actividad inicial deberá establecer blockBackButton a true y registrarse en el manifiesto del proyecto con la categoría HOME; de modo que si reiniciamos el terminal se abrirá nuestra aplicación primero que cualquier otra y al pulsar el botón home se mostrará siempre nuestra aplicación.

Claro que convertir nuestra aplicación en el home del sistema trae como consecuencia que si queremos salir de la aplicación, terminaremos entrando en ella nuevamente pues el home del sistema es nuestra propia aplicación. De hecho, ese es parte del objetivo: que si nos salimos por accidente de la aplicación, ésta se abra automáticamente. Este "inconveniente" puede sortearse buscando otra aplicación que pueda funcionar como home y abriéndola explícitamente.

Como el código para hacer esta búsqueda es algo más extenso, dejo en el siguiente enlace un proyecto de ejemplo de una aplicación que puede ejecutarse en en modo kiosko:
http://ejectodemo.googlecode.com/files/20120227_AndroidKioskMode.zip

Update: desde agosto de 2014 el código está disponible en GitHub:
https://github.com/carlosbello/efectodemo/tree/master/AndroidKioskMode

Atención: Debe tenerse en cuenta que si durante la ejecución de la aplicación se presiona el botón home y se marca la casilla de "Usar por defecto" se reescribirá la pantalla home del sistema y se necesitará eliminar los valores por defecto, almacenados para la aplicación AndroidKioskMode o desinstalarla para restablecer el home original.

Bueno, esto es todo. Agradeceré cualquier colaboración que mejore esta solución o plantee una solución diferente.

15 comentarios:

  1. Instale la aplicación, pero no veo donde comprar pipas, ni kikos, ni dada.... vaya kiosko de mierda

    ResponderEliminar
    Respuestas
    1. dashiell zayas sierra1 de marzo de 2012, 13:46

      que fácil es instalar y criticar destructivamente sin antes proponer algo para ayudar a desarrollar mejor...y sobre todo hablar desde 1 anónimo.
      Si quieres pipas y kikos vete al chino de tu barrio...
      Un saludo

      Eliminar
  2. genial tio!!! aun no lo he probado.. pero se que me sera muy util en el futuro

    ResponderEliminar
    Respuestas
    1. Me alegra que te sirva. Si tienes cualquier dificultad poniéndolo a funcionar, no dudes en preguntar... y si te funciona, compártelo ;-)

      Eliminar
  3. Es un ejemplo sencillo, funcional y practico.
    Gracias por compartirlo.

    Saludos, Chelo Lipe.

    ResponderEliminar
  4. Es Genial! Me salvas la vida, tengo que hacer una aplicación para un cliente y me viene de perlas. Se me ocurre, es posible hacer otra aplicación con menús de administración y todo protegida por contraseña? para tener un modo de administración?

    Mil gracias!!

    ResponderEliminar
  5. Muy bueno carlos es lo que andaba buscando!!! Muchas gracias. Te queria consultar como hacer para que siempre te lanze el mismo launcher cuando presionas la tecla home no quiero que vaya a otro launcher excepto que el usuario elimine el launcher

    Gracias

    ResponderEliminar
    Respuestas
    1. Hola Leandro,

      Para eso hay que poner nuestro launcher como el launcher por defecto. Si tu actividad no es el launcher por defecto, al presionar la tecla home pueden pasar 2 cosas: que aparezca una lista de launchers disponibles o que vaya al launcher definido por defecto.

      a. Si la pulsar home aparece una lista de launchers disponibles, basta con chequear la opción de "Utilizar de forma predeterminada esta acción" y pulsar sobre tu launcher. De modo que, a partir de entonces, tu launcher pase a ser el launcher por defecto del sistema y siempre que se pulse el botón home, se abrirá.

      b. Si al pulsar home, se va directamente al launcher del sistema, la solución depende de tu versión de Android.

      b.1 Si tienes un Android 4.x deberás buscar en los ajustes del sistema la opción "Pantalla de inicio" y, dentro de esta, "Seleccionar inicio". Ahí debería aparecerte una lista con todos los launchers del sistema en la que deberás seleccionar tu launcher para que sea el que se active, por defecto.

      b.2 Si tienes una versión algo más antigua (un 2.3 o así) la solución es más complicada porque tendrás que saber cómo se llama el launcher que se está activando y buscarlo en los Ajustes del sistema, Aplicaciones, Administrar aplicaciones... Una vez localizado, pulsas sobre éste y aparecerán varias opciones como "Forzar detención", "Limpiar datos"... y, la que nos interesa, en una sección llamada "Ejecutar de forma predeterminada", deberás pulsar sobre el botón "Borrar valores predeterminados". Después de esto, no habrá definido ningún launcher por defecto y, al pulsar el botón home, te aparecerá una lista con los disponibles, así que podrás seguir las indicaciones del punto (a).

      Bueno... espero que puedas solucionarlo.

      Saludos

      Eliminar
  6. Carlos, buscando informacion sobre el modo kiosko encuentro tu articulo en el primer link y tu codigo me es de mucha utilidad. Tenemos varios amigos en comun: Leonardo, Arturo, Boris, Esmeraldo y PP Zayaz. Tengo una duda, Necesito deshabilitar tambien el comportamiento del boton "Recent Applications", ya que con el se puede navegar a otras aplicaciones, Por otro lado como hacer para que la aplicacion pueda llamar a otras activities del sistema, como el mapa, por ejemplo sin que comprometa el modo kiosko. Gracias

    ResponderEliminar
    Respuestas
    1. Hola Narciso, aunque no nos conocemos personalmente tu nombre e historias han aflorado con frecuencia en las conversaciones de nuestros amigos comunes, sobre todo de Leonardo.

      Sobre tu pregunta, la respuesta corta es: no se puede deshabilitar la lista de aplicaciones recientes.

      No obstante, se pueden seguir varias estrategias para paliar los inconvenientes que esto pueda traer:

      1. Limpiar la lista de aplicaciones recientes antes de lanzar la aplicación, ya sea manualmente o reiniciando el dispositivo.
      2. Recuperar el foco de la aplicación, una vez perdido.

      El primer procedimiento no siempre es posible hacerlo, de modo que solo nos queda recuperar el foco si otra aplicación se lo arrebatara a la nuestra; y esto puede pasar, tanto si el usuario selecciona una aplicación de la lista de aplicaciones recientes, como si se produce un evento que provoque la carga de otra aplicación.

      Para ello puedes analizar en el evento onPause si tu actividad está siendo pausada por otra actividad de tu aplicación o por una aplicación distinta a la tuya, siguiendo la estrategia expuesta aquí:
      http://stackoverflow.com/questions/20469619/detecting-android-application-going-to-background

      No obstante, te adelanto que lidiar con la pérdida del foco puede ser engorrosa, según la versión del sistema y la implementación de las otras aplicaciones. Uno de los eventos más problemáticos con los que me he topado es la entrada de llamadas telefónicas.

      El motivo de que las llamadas telefónicas sean problemáticas es porque en algunas implementaciones de la aplicación del teléfono -no todas- hacen que ésta reintente recuperar el foco si lo pierde durante su carga y, como consecuencia, tu actividad pierde el foco por segunda vez, después de intentar recuperarlo estando ya pausada. Así que, además de la estrategia del enlace de Stackoverflow, es probable que termines implementando algún tipo de Watchdog fuera de tu actividad que la relance si, a pesar de todo, tu actividad perdiera el foco definitivamente.

      Bueno... espero esto te sirva de algo y, cuando implementes tu estrategia, nos la compartas.

      Saludos

      Eliminar
  7. hola!!
    Estoy haciendo una App y necesito desactivar el botón de aplicaciones recientes podrás ayudarme??

    ResponderEliminar
    Respuestas
    1. Hola,

      Lamentablemente ese comportamiento no se puede evitar. En algunos dispositivos hay un botón dedicado a la visualización de aplicaciones recientes y en otros es un comportamiento asociado a la pulsación prolongada del botón "home". En el segundo caso, por ejemplo, te encuentras con el problema de que el botón "home" no es reprogramable y no puedes modificar su comportamiento. Así que solo te queda limpiar manualmente la lista de tareas recientes o forzar un reinicio del sistema al lanzar tu aplicación en modo kiosko, de modo que la lista de tareas recientes esté limpia y, si un usuario intentara acceder a ella, no vería nada más que tu propia aplicación.

      Puedes ver más detalles de los límites que tenemos con Andorid cuando hacemos una app en modo kiosko en el post Aplicación en modo kiosko para Android: lo que no se puede hacer.

      Saludos

      Eliminar
  8. Hola Carlos, esto es exactamente lo que necesito, pero como tu post es de hace varios años atras me queda la duda si funciona para las versiones mas modernas de android como la 6,7 u 8 inclusive. Y asi mismo, el link para descargar tu aplicacion demo esta caido. Mil gracias.

    ResponderEliminar
    Respuestas
    1. Hola Luis,
      Efectivamente, este post es muy antiguo y no estoy seguro de que las estrategias descritas funcionen con versiones recientes de Android.
      Sobre el link, tienes razón: ahora el código está publicado en GitHub en https://github.com/carlosbello/efectodemo/tree/master/AndroidKioskMode
      Tanto si te funciona como si usas otra estrategia, tu feedback podría venirnos bien a todos.
      ¡Gracias!

      Eliminar
  9. Exelente amigo por compartir ha sido de mucha ayuda.

    ResponderEliminar