Desde que tengo la app de Xiaomi “Mi Home”, la de Belkin Wemo no me funciona. Para poder abrirla, tengo que forzar el cierre de la de Xiaomi. Esto me intrigaba, así que me puse a investigar, y esto es lo que descubrí.
Empezando por el logcat, vemos el siguiente error:
System.out I CyberGarage warning : Thread: 1: bind failed: EADDRINUSE (Address already in use)
I java.net.BindException: bind failed: EADDRINUSE (Address already in use)
I at libcore.io.IoBridge.bind(IoBridge.java:99)
I at java.net.PlainDatagramSocketImpl.bind(PlainDatagramSocketImpl.java:60)
I at java.net.DatagramSocket.bind(DatagramSocket.java:484)
I at org.cybergarage.upnp.ssdp.HTTPUSocket.open(HTTPUSocket.java:140)
I at org.cybergarage.upnp.ssdp.HTTPUSocket.<init>(HTTPUSocket.java:65)
I at org.cybergarage.upnp.ssdp.SSDPSearchResponseSocket.<init>(SSDPSearchResponseSocket.java:45)
I at org.cybergarage.upnp.ssdp.SSDPSearchResponseSocketList.open(SSDPSearchResponseSocketList.java:102)
I at org.cybergarage.upnp.ControlPoint.start(ControlPoint.java:1223)
I at org.cybergarage.upnp.ControlPoint.start(ControlPoint.java:1320)
I at com.belkin.wemo.localsdk.WeMoSDKContext.initControlPoint(WeMoSDKContext.java:196)
I at com.belkin.wemo.localsdk.WeMoSDKContext.<init>(WeMoSDKContext.java:180)
I at com.belkin.wemo.localsdk.WeMoSDKContext.getInstance(WeMoSDKContext.java:126)
I at com.belkin.wemo.cache.devicelist.DeviceListManager.<init>(DeviceListManager.java:417)
I at com.belkin.wemo.cache.devicelist.DeviceListManager.getInstance(DeviceListManager.java:685)
I at com.belkin.activity.MainActivity.initDevManager(MainActivity.java:411)
I at com.belkin.activity.MainActivity.onCreate(MainActivity.java:82)
I at android.app.Activity.performCreate(Activity.java:6251)
I at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
I at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
I at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
I at android.app.ActivityThread.-wrap11(ActivityThread.java)
I at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
I at android.os.Handler.dispatchMessage(Handler.java:102)
I at android.os.Looper.loop(Looper.java:148)
I at android.app.ActivityThread.main(ActivityThread.java:5417)
I at java.lang.reflect.Method.invoke(Native Method)
I at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
I at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Lo primero que me sorprende, es que no utilicen Proguard. Esto hace que podamos ver el código de toda la app de forma rápida y sencilla. Por lo demás, parece que el fallo es debido a que la app de Xiaomi abre un socket de red y no lo cierra, por lo que Wemo no lo puede abrir (EADDRINUSE).
Pero, ¿para qué es este socket? Navegando un poco por el código vemos que utilizan una versión modificada de esta librería UPnP.
En ella, vemos este método:
public void notifyReceived(SSDPPacket packet) {
if (packet.isRootDevice()) {
if (packet.isAlive()) {
packet.setNotify(true);
addDevice(packet, true);
} else if (packet.isByeBye()) {
removeDevice(packet);
}
}
performNotifyListener(packet);
}
Parece que los dispositivos se anuncian vía UPnP usando paquetes SSDP, por lo que es necesario mantener el socket abierto.
Además, aunque la app mantiene una caché local, al abrirla ha de actualizar el estado de todos los dispositivos. Esto provoca, lo siguiente:
La app necesita “esperar un tiempo” antes de abrirse, para esperar a que se anuncien todos los dispositivos con su estado.
Si hay otra app, como en este caso, escuchando en la misma dirección, la app no funcionará.
En este caso, la app Xiaomi intenta evitar este retraso al abrir Mi Home, manteniendo siempre abierto el socket. Desde mi punto de vista, aunque no supone un problema de batería, en mi experiencia como usuario, Xiaomi debería portarse bien y cerrar los sockets cuando cierro la app.
Por otro lado, creo que Wemo debería detectar este caso e informar al usuario, en lugar de dar un Force Close.
Además, si utilizo cualquiera de las apps fuera de casa, no voy a recibir nada de mis dispositivos, por lo que todo el tráfico irá a través de red. Esto funciona sin problemas, por lo que sospecho que ambos marcas actualizan periódicamente el estado online y se podría utilizar siempre esta conexión, sólo abriendo dicho socket cuando vaya a añadir nuevos dispositivos.
Así que he decidido comprobarlo, y usando APK studio he recompilado mi propia versión de la app, cambiando el puerto por defecto, en la clase org/cybergarage/upnp/ControlPoint.java. Ahora ambas aplicaciones funcionan perfectamente como puedes ver en el siguiente vídeo.