- Gestión de eventos de teclado y hardware
La clase UiDevice proporciona métodos esenciales para simular la pulsación de botones físicos del dispositivo, como Inicio, Atrás, Menú o teclas específicas mediante códigos de tecla (KeyCodes).
UiDevice dispositivo = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Simular pulsación del botón de Inicio
dispositivo.pressHome();
// Simular pulsación del botón de Atrás
dispositivo.pressBack();
// Simular pulsación del botón de Menú
dispositivo.pressMenu();
Para una simulación más granular, se puede utilizar pressKeyCode(int keyCode, int metaState). Los códigos de tecla corresponden a los enteros definidos en KeyEvent de Android.
// Introducir la letra 'a' (minúscula)
dispositivo.pressKeyCode(KeyEvent.KEYCODE_A);
// Introducir la letra 'A' (mayúscula) usando el meta estado SHIFT
dispositivo.pressKeyCode(KeyEvent.KEYCODE_A, KeyEvent.META_SHIFT_ON);
El parámetro metaState permite modificar teclas con modificadores:
- 0: Ningún modificador activado.
- 1: Shift o Caps Lock activo.
- 2: Alt activo.
- 3: Combinación de Alt y Shift.
- Manipulación de coordenadas y pantalla
Permite obtener las dimensiones de la pantalla y ejecutar eventos de toque (tap) en coordenadas absolutas. Es fundamantal realizar cálculos dinámicos basados en el tamaño de la pantalla para asegurar la compatibilidad entre diferentes dispositivos.
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
int anchoPantalla = device.getDisplayWidth();
int altoPantalla = device.getDisplayHeight();
// Calcular el centro de la pantalla
int centroX = anchoPantalla / 2;
int centroY = altoPantalla / 2;
// Ejecutar un clic en el centro
boolean resultado = device.click(centroX, centroY);
// Clic en una coordenada específica
device.click(300, 500);
Nota técnica: Se recomienda mantener una referencia singleton de UiDevice (usando getInstance()) en lugar de llamar a métodos de fábrica en múltiples clases para evitar estados inconsistentes.
- Control de orientación y rotación
La API ofrece control total sobre la rotación de la pantalla, permitiendo forzar orientaciones específicas o congelar el sensor de rotación actual.
public void probarRotacion() throws RemoteException {
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Forzar rotación a la izquierda
device.setOrientationLeft();
// Forzar rotación natural (vertical estándar)
device.setOrientationNatural();
// Congelar la rotación actual (desactiva el sensor)
device.freezeRotation();
// Reactivar el sensor de rotación automática
device.unfreezeRotation();
// Verificar el estado actual
if (device.isNaturalOrientation()) {
// El dispositivo está en su orientación por defecto
}
int rotacionActual = device.getDisplayRotation();
// Los valores retornan: 0=0°, 1=90°, 2=180°, 3=270°
}
- Gestión de estado de energía (Encendido/Apagado)
Permite controlar el estado de la pantalla smiulando la presión del botón de encendido.
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
if (device.isScreenOn()) {
// Si la pantalla está encendida, la apagamos
device.sleep();
} else {
// Si la pantalla está apagada, la encendemos
device.wakeUp();
}
- Sincronización y estados de espera
La sincronización es crítica para evitar "race conditions". waitForIdle pausa la ejecución hasta que la interfaz de usuario no procesa ningún evento en la cola de mensajes.
public void probarSincronizacion() {
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Click en un elemento para abrir una actividad
device.click(600, 900);
// Esperar hasta que el dispositivo esté inactivo (tiempo máximo 5 segundos)
device.waitForIdle(5000);
// Esperar a que se actualice una ventana específica de un paquete
boolean actualizado = device.waitForWindowUpdate("com.ejemplo.paquete", 2000);
}
El estado "Idle" (inactivo) se alcanza cuando la aplicación ha terminado de cargar la UI actual y no hay transiciones ni renderizados pendientes.
- Captura de pantalla (Screenshots)
La función takeScreenshot permite guardar el estado visual actual del dispositivo en un archivo.
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Captura con configuración por defecto (escala 1.0, calidad 90)
File capturaPorDefecto = new File("/sdcard/captura_simple.png");
device.takeScreenshot(capturaPorDefecto);
// Captura con parámetros personalizados (escala 0.5f, calidad 50)
File capturaPersonalizada = new File("/sdcard/captura_pequena.png");
float escala = 0.5f;
int calidad = 50;
boolean exito = device.takeScreenshot(capturaPersonalizada, escala, calidad);
El archivo de salida debe tener formato .png. El método devuelve true si la escritura del archivo fue exitosa.
- Gestos complejos: Arrastrar y Deslizar
Se distingue entre drag (arrastrar un elemento) y swipe (deslizamiento de pantalla). El parámetro steps controla la duración del gesto (5ms por paso).
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Ejemplo 1: Deslizar para abrir el menú deslizante (izquierda a derecha)
int ancho = device.getDisplayWidth();
int alto = device.getDisplayHeight();
device.swipe(ancho - 20, alto / 2, 20, alto / 2, 40);
// Ejemplo 2: Arrastrar un elemento de un punto a otro
int xInicial = 150;
int yInicial = 400;
int xFinal = 150;
int yFinal = 200; // Mover hacia arriba
device.drag(xInicial, yInicial, xFinal, yFinal, 30);
// Ejemplo 3: Dibujar un patrón o forma utilizando un array de puntos
Point p1 = new Point(100, 300);
Point p2 = new Point(500, 300);
Point p3 = new Point(500, 800);
Point p4 = new Point(100, 800);
Point[] forma = {p1, p2, p3, p4};
device.swipe(forma, 30); // 30 pasos entre puntos
- Información del sistema y utilidades
Obtención de metadatos de la aplicación en primer plano y manipulación de paneles del sistema.
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Obtener el nombre del paquete de la actividad actual
String paquete = device.getCurrentPackageName();
Log.d("UiDevice", "Paquete activo: " + paquete);
// Abrir panel de notificaciones
device.openNotification();
// Abrir ajustes rápidos
device.openQuickSettings();
// Volcar la jerarquía de la vista actual a un archivo XML
// Útil para depurar selectores
device.dumpWindowHierarchy("hierarchia_actual.xml");
// El archivo se guarda en /data/local/tmp/
- Manejo de interrupciones con UiWatcher
Los observadores (UiWatcher) permiten reanudar la ejecución de una prueba tras una interrupción inesperada (como una llamada entrante o un cuadro de diálogo del sistema). Deben registrarse al inicio del script.
public void configurarWatchers() {
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Registrar un observador para manejar llamadas entrantes
device.registerWatcher("gestorLlamada", new UiWatcher() {
@Override
public boolean checkForCondition() {
// Buscar indicios de una llamada (ej. texto específico)
UiObject alertaLlamada = new UiObject(new UiSelector().text("Llamada entrante"));
if (alertaLlamada.exists()) {
try {
// Lógica para colgar o rechazar la llamada (deslizar o botón)
UiObject btnColgar = new UiObject(new UiSelector().description("Finalizar"));
if(btnColgar.exists()) btnColgar.click();
return true; // Condición manejada
} catch (Exception e) {
e.printStackTrace();
}
}
return false; // No ocurrió la interrupción
}
});
// Ejecutar lógica principal
try {
UiObject iconoApp = new UiObject(new UiSelector().text("MiApp"));
for (int i = 0; i < 5; i++) {
iconoApp.clickAndWaitForNewWindow();
Thread.sleep(1000);
device.pressHome();
// Si llega una llamada aquí, el Watcher la maneja automáticamente
}
} catch (Exception e) {
e.printStackTrace();
}
}
Es vital comprobar si un watcher se disparó usando hasAnyWatcherTriggered() para depurar fallos en la ejecución de las pruebas.