Ir al contenido

App Android

APX Android es una app nativa que encapsula la superficie de celular del daemon. Mantiene la UI móvil React existente como implementación única; Android maneja mascota flotante, notificaciones del sistema y apertura de la app.

La app abre /mobile, que el panel redirige a /m/chat — la dirección actual de la superficie de celular, donde convive con /m/tasks y /m/commitments detrás de una barra inferior.

  1. Hacé accesible el daemon desde el teléfono, por LAN o puerto inverso ADB.
  2. Ejecutá apx pair web en la máquina del daemon.
  3. Ingresá URL del daemon y código mostrado dentro de APX Android.
  4. Otorgá permiso de notificaciones cuando Android lo solicite.
  5. Otorgá Mostrar sobre otras apps solo si querés mascota flotante.

La app recibe un token cliente propio kind: android. No reutiliza el token guardado por Chrome o la PWA instalada porque Android aísla almacenamiento por app. Pairing web existente sigue válido; ambos clientes aparecen en apx pair list y pueden revocarse por separado.

Android no permite que una app se otorgue acceso a notificaciones ni permiso No molestar. Instalación nueva necesita dos switches explícitos del sistema para experiencia completa:

CapacidadAcción únicaNecesaria para
Notificaciones APXAceptar solicitud AndroidToda notificación APX
Avisos durante conducciónMenú APX → Permitir avisos durante conducción y activar APX en pantalla del sistemaCards flotantes mientras Android Auto activa No molestar
Detección de viajesMenú APX → Activar detección de viajes y activar acceso a notificaciones APXLeer estado y destino de navegación Google Maps, y detectar una sesión de Android Auto
UbicaciónElegir Mientras se usa la appEnviar el origen del viaje y la posición en vivo durante el viaje
OverlayActivar Mostrar sobre otras appsSolo mascota flotante; no necesario para cards del auto
BateríaMenú APX → Quitar restricción de batería y confirmar el diálogo de AndroidQue el inicio y el fin del viaje lleguen a tiempo con el teléfono en el bolsillo

En configuración Android Auto, mantener Ver notificaciones de mensajes activo. Activar Mostrar la primera línea de los mensajes para ver texto APX en card, no solo anuncio de mensaje nuevo.

  • Inicio normal abre <daemon>/mobile dentro de la app, no Chrome.
  • Mascota flota sobre escritorio y otras apps, puede arrastrarse y recuerda posición.
  • Avatar sigue Ajustes → Super-agente → Avatar. WebSocket autenticado envía valor actual al conectar y cada cambio posterior; Android lo guarda local y repinta overlay inmediatamente.
  • Se oculta mientras APX está abierta y reaparece cuando APX pasa al fondo.
  • Eventos APX entrantes disparan burbuja, sonido incluido y notificación Android.
  • Tocar mascota o notificación abre app nativa en /mobile.
  • Notificación persistente de mascota incluye acción Ocultar.
  • Puntos suspensivos exclusivos de app, junto a Preferencias, abren opciones nativas. Chrome y PWA instalada no los muestran.
  • Opciones nativas permiten mutear/reactivar sonido sin desactivar mascota.
  • Preferencias → Notificaciones muestra permiso Android real. Tocarlo abre ajustes de notificaciones de APX en sistema.
  • Menú nativo permite activar Detección de viajes APX. Con acceso a notificaciones otorgado, app detecta inicio y fin de navegación Google Maps mediante metadatos de notificación. Muestra barra nativa con icono de auto y acceso directo a Maps. Inicio nuevo envía evento autenticado trip.started a /api/mobility/events, con última ubicación disponible como origen. Si Maps omite destino, card Android Auto pregunta dónde vas; Responder acepta dictado de voz y completa mismo viaje. Cambiar destino inicia evaluación nueva. No sube ruta, historial ni contenido completo de notificaciones.
  • El banner de viaje abre los pendientes del viaje, no Maps: al tocarlo muestra una tarjeta por mandado pendiente, del más cercano al más lejano, con los mismos cuatro botones que trae la tarjeta de Telegram — Navegar, Sumar a la ruta, Voy, En la siguiente. Un “voy” acá llega al mismo registro que un “voy” en Telegram, y gana el mismo seguimiento después del viaje; una tarjeta ya contestada muestra la respuesta en lugar de volver a preguntar. Cuando un mandado todavía tiene varios lugares posibles la tarjeta lo dice, en vez de presentar el más cercano como una decisión que APX ya tomó. La etiqueta de la derecha lleva la cuenta en vivo (“8 cerca”). La lista sale de GET /api/mobility/trip, que es de solo lectura (consultarla nunca dispara un recordatorio), y las respuestas van a POST /api/mobility/errands/answer. Limitada a ocho mandados.
  • Mensajes de viaje tienen límite deliberado. Destino conocido debe quedar estable 45 segundos; navegación sin destino espera 10 minutos. Reabrir mismo destino queda silencioso 30 minutos; destino distinto no dispara otra evaluación Roby antes de 5 minutos. Cancelar/reiniciar rápido actualiza estado local sin ráfaga de mensajes. APX envía contexto de viaje al daemon apenas lo detecta; esas esperas limitan evaluación y avisos, no conciencia de Roby.
  • Como alternativa explícita, desde Maps en teléfono elegí Compartir progreso del viaje → Más → Compartir viaje con APX. APX acepta solo enlaces Google Maps, adjunta ubicación permitida y actualiza viaje activo. Shares actuales suelen traer solo enlace opaco maps.app.goo.gl, sin texto de destino; APX debe mostrarlo como progreso compartido y nunca inventar destino. Android Auto puede mostrar solo contactos y ocultar share targets de apps; en ese caso hacelo desde teléfono o usá respuesta de voz APX.
  • Daemon pide a Roby revisar tareas y compromisos pendientes usando herramientas de solo lectura. Eventos de viaje configurados por usuario se procesan al instante, fuera de rondas y presupuesto proactivo, pero actualizan contexto de movilidad en vez de forzar Telegram. Roby ve viaje actual, última pregunta y última respuesta botonera durante chats y rondas de secretaria posteriores. Solo escribe ante oportunidad nueva y concreta; SILENT queda como opción normal. Roby no afirma cercanía ni desvíos sin datos suficientes.
  • Con origen y destino disponibles, daemon geocodifica mediante Nominatim, calcula ruta con Valhalla/OSRM y busca comercios OpenStreetMap dentro de 700 m. Envía máximo tres coincidencias y enlaces Google Maps; cero coincidencias verificadas producen silencio.
  • Aviso Telegram ofrece Sí, voy ahora, No podré, Recordarme luego y No avisar más hoy. Recordar luego crea delivery pendiente para próxima ronda. Cerrar Maps antes del envío cancela aviso pendiente.
  • Android Auto es una segunda fuente de viaje, independiente de Maps. El mismo listener de notificaciones reconoce la notificación de sesión de Android Auto (“Android Auto · Conectado al vehículo”) y abre un viaje apenas el teléfono proyecta en una head unit, con ruta cargada o sin ella. El banner pasa a decir Estás en el auto. Los avisos (“tocá para configurar Android Auto”) y la notificación del servidor de unidad principal de modo desarrollador nunca cuentan como sesión. El viaje termina solo cuando se apagan LAS DOS fuentes: una ruta que termina con el teléfono todavía enchufado mantiene el mismo viaje y el mismo trip_id.
  • Mientras hay un viaje activo, APX reporta posición a /api/mobility/positions cada 30 s / 150 m desde un servicio en primer plano de tipo location. Ese servicio es la razón por la que APX no pide ACCESS_BACKGROUND_LOCATION: la notificación persistente “Viaje en curso” es el modelo de permiso, y el seguimiento se corta con el viaje. Cada muestra es un punto y su precisión: no se sube ni se guarda geometría de ruta ni historial de ubicación.
  • El daemon cruza cada posición con las tareas pendientes que nombran un mandado físico y avisa una vez cuando un lugar compatible queda a menos de 2 km. Un aviso por mandado y por viaje — una tarea “comprar ibuprofeno” que matchea trece farmacias recibe la más cercana, no trece tarjetas — y como mucho dos tarjetas por muestra. No se repite, ni siquiera después de reiniciar el daemon.
  • La tarjeta de cercanía trae cuatro chips: Navegar y Sumar a la ruta (links profundos a Google Maps; “Sumar a la ruta” conserva el destino actual e inserta el lugar como parada), más Voy y Avisar en la siguiente. Contestar “Voy” es una promesa, no una acción: cuando el viaje termina APX pregunta ¿Pasaste por ese lugar? con Hecho — que cierra la tarea — y Todavía no, que la deja abierta.
  • “Avisar en la siguiente” descarta el local, no el mandado. Dejar pasar la sucursal por la que justo venías manejando deja la tarea con un aviso pendiente: el próximo comercio que sirva para lo mismo todavía puede avisarte en el mismo viaje, y sólo la sucursal que descartaste no vuelve a aparecer. Cuando no hay siguiente se queda callado por el resto del viaje, que es lo que el viejo “Hoy no” le hacía al mandado entero al primer toque.
  • Mientras hay un viaje activo, cada respuesta automática de Telegram sale dos veces: primero una nota de voz y después las mismas palabras como texto bajo una línea discreta [Transcripción] — una etiqueta, no un cartel: el audio es el mensaje y el texto está para pasarle el ojo. El turno corre en modo voz, así que la respuesta son una o dos frases habladas y no un párrafo leído en voz alta. Si falla el TTS o ffmpeg se pierde el audio, nunca el mensaje. Se apaga con voice.mobility_replies: false. El seguimiento de fin de viaje va en texto plano: se lee estacionado.
  • Delivery de movilidad también genera card Android APX y sonido local cuando sonido nativo está activo.
  • Mensajes directos APX usan formato mensajería Android. Android Auto puede mostrarlos como avisos flotantes y leerlos sin abrir Telegram. Menú nativo incluye Probar aviso Android Auto para pruebas DHU o vehículo. Android Auto activa No molestar; concedé Avisos durante conducción una vez para permitir que APX lo atraviese.

APX es una cáscara con un WebView adentro y, en reposo, no debería costar nada. El viaje es el único estado que puede ser caro: ahí corre el GPS y ahí un socket caído tiene que recuperarse en segundos.

  • La mascota se anima mientras la arrastrás, salta o muestra un mensaje. En reposo mantiene la pose y solo parpadea, en lugar de redibujarse treinta veces por segundo encima de la app que estés usando.
  • Salir de APX pausa el /mobile embebido y detiene sus timers de JavaScript; al volver se reanuda. Las notificaciones nativas no dependen de eso: llegan por el servicio en foreground, no por el WebView.
  • La escalera de reconexión del socket se corta en 15 segundos durante un viaje y en 5 minutos fuera de uno, y se detiene del todo cuando el teléfono no tiene red: despierta con el callback de conectividad de Android, no con un temporizador. Antes, un teléfono lejos del daemon lo rediscaba cada 15 segundos todo el día. Al abrirse un viaje reconecta enseguida.
  • Menú APX → Quitar restricción de batería abre el diálogo propio de Android. Conviene darlo para la detección de viajes: sin eso Android demora el inicio y el fin del viaje con el teléfono en el bolsillo. La fila del menú muestra el estado actual, y una vez que APX está exento la misma fila abre la lista del sistema, que es donde se revierte.
  • También decide si el seguimiento por GPS puede arrancar solo. La ubicación es un permiso de “mientras se usa”, así que otorgarlo no alcanza: Android además exige que la app esté en un “estado elegible” para levantar un servicio en primer plano de tipo ubicación, y el listener de notificaciones que detecta el viaje corre en segundo plano. Sin la excepción ese arranque se rechaza, APX lo registra y sigue: el daemon igual se entera de que el viaje empezó, pero no hay flujo de posiciones y por lo tanto no hay avisos de cercanía hasta que abrís APX, lo que vuelve elegible a la app y reintenta. Con la excepción, el seguimiento arranca por sí solo.

El plan del viaje: cuántas búsquedas cuesta manejar

Sección titulada «El plan del viaje: cuántas búsquedas cuesta manejar»

Un mandado no siempre es un lugar. “Comprar pan” lo resuelve la panadería de la esquina o cualquiera de los dos supermercados, y cuál corresponde depende de dónde está el auto en ese momento. Contestar esa pregunta buscando de nuevo en cada muestra de GPS sería una llamada al proveedor de lugares cada pocos segundos; contestarla una sola vez al principio dejaría pasar al conductor por tres opciones mejores.

Por eso el daemon mantiene un plan de viaje: una entrada por mandado, con todos los lugares que podrían resolverlo y aquel al que apunta ahora.

  • El plan se arma cuando te subís al auto, desde el origen que reporta el teléfono — no de forma perezosa en la primera muestra, tomada 800 m más adelante.
  • Reordenar es gratis. Elegir el candidato más cercano mientras el auto se mueve es aritmética sobre coordenadas que ya están en memoria. Sin llamadas, sin modelo.
  • Volver a buscar es raro. Pasa solo cuando el auto se aleja 8 km del área buscada, o cuando cambia la lista de mandados abiertos — una lectura de archivo local, chequeada en cada muestra, así que una tarea agregada en un semáforo está viva en la siguiente.
  • Un mandado resuelto deja de buscar. Un único candidato es la decisión, y un “voy” también: los dos fijan el mandado, y un mandado fijado no se vuelve a buscar ni a reapuntar. Cuando todos están fijados el plan está terminado y el resto del viaje no cuesta nada.

Un viaje normal con la lista de compras resuelta cuesta entonces una búsqueda de lugares. Un viaje donde cada tarea trae su propia ubicación cuesta cero — y cero llamadas al modelo en ambos casos, porque una tarea trip dice qué es y dónde, en vez de pedirle a un modelo que lea el título y adivine.

Si alguna vez querés pagar por mejores lugares

Sección titulada «Si alguna vez querés pagar por mejores lugares»

Lo que viene de fábrica es el stack gratuito (Nominatim para lugares, Valhalla/OSRM para rutas), y el plan de arriba es lo que hace barato cambiarlo. Como referencia, a precios de 2026 las alternativas de Google cobran por llamada, así que la cantidad de llamadas es todo el juego:

  • Places API Nearby/Text Search — 5.000 llamadas gratis por mes, después USD 32 cada 1.000. Una búsqueda por viaje es accesible; una por muestra de GPS no.
  • Routes API computeRoutes — desde USD 5 cada 1.000, y puede optimizar el orden de hasta 25 paradas intermedias. Esta es la pieza que armaría una ruta real de varios mandados en vez de una lista por cercanía.
  • Search Along RoutecomputeRoutes devuelve una polilínea codificada, y Text Search la acepta como searchAlongRouteParameters, devolviendo solo lugares que están en el camino. Dos llamadas por viaje, y estrictamente mejor que buscar en un radio alrededor de un punto cuando hay destino.
  • El geofencing propio de Android (GeofencingClient, parte de Play Services y sin costo) es la otra mitad: le entrega hasta 100 cercos al sistema operativo, que despierta la app cuando se cruza uno usando geofencing por hardware, en vez de que la app muestree GPS con un temporizador. El plan de arriba ya produce exactamente el conjunto chico y estable de lugares que una API así necesita.

APX no recibe polilínea exacta de Google Maps. Android envía último punto GPS disponible como origen y, cuando existe, texto de destino expuesto por notificación Maps, compartido explícitamente o confirmado por voz. Daemon geocodifica destino y arma ruta propia con Valhalla; usa OSRM como fallback.

Ruta reconstruida alcanza para buscar corredor y lugares cercanos, pero puede diferir de Google Maps: no incluye tráfico Google, preferencias de desvío ni decisiones sobre caminos privados. Si falta destino, Roby recibe solo posición actual y no afirma conocer ruta ni evalúa proximidad.

Android Auto también manda mensajes semánticos de navegación al head unit. Canal privado del protocolo de proyección; no API disponible para app Android. Prueba USB/DHU real capturó:

  • 0x8003: navegación activa/inactiva;
  • 0x8006: maniobra, calles y dirección de destino;
  • 0x8007: distancia restante y llegada.

Payload real incluyó guía Bosque Petrificado y destino R8400, Río Negro. Resultado: head unit APX puede detectar contexto automáticamente, sin leer notificaciones, voz, compartir Maps ni API Google.

Android → head unit/proxy APX → decoder navegación
→ /api/mobility/events autenticado → Roby

Conexión prueba contexto de conducción. Emitir trip.started solo cuando navegación activa y destino útil. Emitir trip.ended al terminar navegación o desconectar. Deduplicar frames 0x8006; nunca guardar video/audio proyectado.

Instalarla, y elegir a qué dirección se conecta, tiene su propia página: Instalar en Android. En corto:

Ventana de terminal
apx android install # por USB: instala, abre el túnel y vincula de una

o abrir el APK publicado desde el teléfono.

Ventana de terminal
cd src/interfaces/android
./gradlew testDebugUnitTest assembleDebug
adb install -r app/build/outputs/apk/debug/app-debug.apk

Un build release necesita una keystore en el entorno (APX_KEYSTORE, APX_KEYSTORE_PASSWORD, APX_KEY_ALIAS, APX_KEY_PASSWORD); sin ella, assembleRelease produce un APK sin firmar que Android no instala. CI hace esto en cada cambio bajo src/interfaces/android/ y republica el release — ver .github/workflows/android.yml.