Flujo de conexión WiFi en Android 12

El mecanismo de conexión WiFi en Android 12 se inicia desde la interfaz de Configuración. A continuación, se detalla el flujo completo con ejemplos de código reestructurados para mayor claridad.

1. Interfaz de Configuración

En el archivo WifiSettings.java, se gestinoa la creación de un diálogo para la red WiFi seleccionada:

@Override
public Dialog crearDialogo(int idDialogo) {
    switch (idDialogo) {
        case ID_DIALOGO_WIFI:
            dialogo = WifiDialog2.generarModal(getActivity(), this, redWifiActual, modoOperacion);
            return dialogo;
        default:
            return super.crearDialogo(idDialogo);
    }
}

2. Gestión de eventos en el Diálogo

La clase WifiDialog2.java define constantes para botones y maneja las acciones del usuario:

private static final int BOTON_ENVIAR = DialogInterface.BUTTON_POSITIVE;
private static final int BOTON_OMITIR = DialogInterface.BUTTON_NEUTRAL;

@Override
public void onClick(DialogInterface interfaz, int identificador) {
    if (oyente != null) {
        switch (identificador) {
            case BOTON_ENVIAR:
                oyente.alEnviar(this);
                break;
            case BOTON_OMITIR:
                if (WifiUtils.redBloqueada(getContexto(), redWifiActual.obtenerConfig())) {
                    RestrictedLockUtils.enviarIntAdmin(getContexto(), RestrictedLockUtilsInternal.propietarioDispositivo(getContexto()));
                    return;
                }
                oyente.alOmitir(this);
                break;
        }
    }
}

private final OyenteDialogoWifi oyente;

La interfaz OyenteDialogoWifi se implementa en WifiSettings, donde se procesa la conexión:

@Override
public void alEnviar(WifiDialog2 dialogo) {
    int modoActual = dialogo.obtenerModo();
    WifiConfiguration config = dialogo.controlador().obtenerConfig();
    WifiEntry entradaRed = dialogo.obtenerEntradaRed();

    if (modoActual == WifiConfigUiBase2.MODO_MODIFICAR) {
        if (config == null) {
            Toast.makeText(contexto, R.string.error_guardar_red, Toast.LENGTH_SHORT).mostrar();
        } else {
            gestorWifi.guardar(config, oyenteGuardado);
        }
    } else if (modoActual == WifiConfigUiBase2.MODO_CONECTAR
            || (modoActual == WifiConfigUiBase2.MODO_VER && entradaRed.puedeConectarse())) {
        if (config == null) {
            conectar(entradaRed, false, false);
        } else {
            gestorWifi.conectar(config, new OyenteAccionWifi());
        }
    }
}

3. Invocación del WifiManager

Se llama al método conectar de WifiManager para iniciar la conexión:

public void conectar(int idRed, @Nullable OyenteAccion oyente) {
    if (idRed < 0) throw new IllegalArgumentException("ID de red no puede ser negativo");
    conectarInterno(null, idRed, oyente);
}

private void conectarInterno(@Nullable WifiConfiguration config, int idRed,
        @Nullable OyenteAccion oyente) {
    ProxyOyenteAccion proxy = null;
    if (oyente != null) {
        proxy = new ProxyOyenteAccion("conectar", looperPrincipal, oyente);
    }
    try {
        servicio.conectar(config, idRed, proxy);
    } catch (RemoteException e) {
        if (proxy != null) proxy.enFallo(ERROR);
    } catch (SecurityException e) {
        if (proxy != null) proxy.enFallo(NO_AUTORIZADO);
    }
}

Aquí, servicio es de tipo IWifiManager, implementado por BaseWifiService.

4. Implementación del Servicio WiFi

En WifiServiceImpl.java, se maneja la lógica de conexión:

@Override
public void conectar(WifiConfiguration config, int idRed, @Nullable IActionListener callback) {
    int uid = Binder.obtenerUidLlamante();
    if (!esPrivilegiado(Binder.obtenerPidLlamante(), uid)) {
        throw new SecurityException(TAG + ": Permiso denegado");
    }
    if (config != null) {
        config.idRed = eliminarTipoSeguridadDeIdRed(config.idRed);
    }
    final int idRedArg = eliminarTipoSeguridadDeIdRed(idRed);
    registro.info("conectar uid=%").c(uid).vaciar();
    ejecutorWifi.publicar(() -> {
        EnvoltorioOyenteAccion envoltorio = new EnvoltorioOyenteAccion(callback);
        final ResultadoActualizacionRed resultado;
        if (config != null) {
            if (utilPermisosWifi.tienePermisoAjustesRed(uid)) {
                metricasWifi.registrarEventoAccionUsuario(EVENTO_AGREGAR_O_ACTUALIZAR_RED, config.idRed);
            }
            resultado = gestorConfigWifi.agregarOActualizarRed(config, uid);
            if (!resultado.exito()) {
                Log.e(TAG, "conectar: fallo al agregar/actualizar config=" + config);
                envoltorio.enviarFallo(WifiManager.ERROR);
                return;
            }
            emitirCredencialCambiada(WifiManager.CREDENCIAL_WIFI_GUARDADA, config);
        } else {
            if (utilPermisosWifi.tienePermisoAjustesRed(uid)) {
                metricasWifi.registrarEventoAccionUsuario(EVENTO_CONEXION_MANUAL, idRedArg);
            }
            resultado = new ResultadoActualizacionRed(idRedArg);
        }
        WifiConfiguration configuracion = gestorConfigWifi.obtenerConfigRed(resultado.obtenerIdRed());
        if (configuracion == null) {
            Log.e(TAG, "conectar a ID de red inválido=" + idRedArg);
            envoltorio.enviarFallo(WifiManager.ERROR);
            return;
        }
        if (configuracion.configEmpresarial != null
                && configuracion.configEmpresarial.autenticacionBasadaEnSim()) {
            int subId = gestorInfoOperador.obtenerIdSuscripcionCoincidente(configuracion);
            if (!gestorInfoOperador.simListo(subId)) {
                Log.e(TAG, "conectar a config basada en SIM=" + configuracion + " mientras SIM no presente");
                envoltorio.enviarFallo(WifiManager.ERROR);
                return;
            }
            if (gestorInfoOperador.requiereCifradoImsi(subId)
                    && !gestorInfoOperador.infoCifradoImsiDisponible(subId)) {
                Log.e(TAG, "Protección Imsi requerida pero no disponible para Red=" + configuracion);
                envoltorio.enviarFallo(WifiManager.ERROR);
                return;
            }
        }
        gestorMakeBeforeBreak.detenerTodosClientesTransitorios(() ->
                auxiliarConexion.conectarARed(resultado, envoltorio, uid));
    });
}

5. Conexión a través de ConnectHelper

La clase ConnectHelper.java facilita la conexión usando el administrador de modo cliente primario:

public void conectarARed(
        @NonNull ResultadoActualizacionRed resultado,
        @NonNull EnvoltorioOyenteAccion envoltorio,
        int uidLlamante) {
    conectarARed(
            administradorModos.obtenerAdministradorModoClientePrimario(), resultado, envoltorio, uidLlamante);
}

public void conectarARed(
        @NonNull AdministradorModoCliente adminModo,
        @NonNull ResultadoActualizacionRed resultado,
        @NonNull EnvoltorioOyenteAccion envoltorio,
        int uidLlamante) {
    int idRed = resultado.obtenerIdRed();
    if (gestorConfigWifi.obtenerConfigRed(idRed) == null) {
        Log.e(TAG, "conectarARed: ID de red inválido=" + idRed);
        envoltorio.enviarFallo(WifiManager.ERROR);
        return;
    }
    gestorConfigWifi.actualizarAntesDeConexion(idRed, uidLlamante);
    adminModo.conectarRed(resultado, envoltorio, uidLlamante);
}

El administrador de modo cliente se obtiene desde ActiveModeWarden, que gestiona enstancias de ConcreteClientModeManager.

6. Implementación del Cliente Modo Concreto

En ConcreteClientModeManager.java, se delega a la implementación de ClientMode:

@Override
public void conectarRed(ResultadoActualizacionRed resultado, EnvoltorioOyenteAccion envoltorio, int uidLlamante) {
    obtenerModoCliente().conectarRed(resultado, envoltorio, uidLlamante);
}

@NonNull
private ModoCliente obtenerModoCliente() {
    if (implementacionModoCliente != null) {
        return implementacionModoCliente;
    }
    if (implementacionModoSoloEscaneo != null) {
        return implementacionModoSoloEscaneo;
    }
    return administradorModoClientePorDefecto;
}

7. Lógica de Conexión en ClientModeImpl

La clase ClientModeImpl.java envía un mensaje para iniciar la conexión:

public void conectarRed(ResultadoActualizacionRed resultado, EnvoltorioOyenteAccion envoltorio, int uidLlamante) {
    Message mensaje = obtenerMensaje(CMD_CONECTAR_RED, new MensajeConexionRed(resultado, envoltorio));
    mensaje.uidEnvio = uidLlamante;
    enviarMensaje(mensaje);
}

Al recibir el mensaje, se ejecuta conectarARedSeleccionadaPorUsuario, que prepara la conexión:

case CMD_CONECTAR_RED: {
    MensajeConexionRed mcn = (MensajeConexionRed) mensaje.obj;
    if (clienteIp == null) {
        logd("IpClient no listo, CMD_CONECTAR_RED descartado");
        mcn.oyente.enviarFallo(WifiManager.ERROR);
        break;
    }
    ResultadoActualizacionRed resultado = mcn.resultado;
    int idRed = resultado.obtenerIdRed();
    conectarARedSeleccionadaPorUsuario(idRed, mensaje.uidEnvio, resultado.tieneCredencialCambiada());
    metricasWifi.registrarEventoSTA(nombreInterfaz, TipoEventoSTA.CONECTAR_RED,
            gestorConfigWifi.obtenerConfigRed(idRed));
    mcn.oyente.enviarExito();
    break;
}

private void conectarARedSeleccionadaPorUsuario(int idRed, int uid, boolean forzarReconexion) {
    logd("conectarARedSeleccionadaPorUsuario idRed " + idRed + ", uid " + uid
            + ", forzarReconexion = " + forzarReconexion);
    if (!forzarReconexion && (ultimaIdRed == idRed || idRedObjetivo == idRed)) {
        logi("conectarARedSeleccionadaPorUsuario ya conectando/conectado=" + idRed);
    } else {
        gestorConectividadWifi.prepararParaConexionForzada(idRed);
        if (uid == Process.UID_SISTEMA) {
            metricasWifi.establecerNominadorParaRed(idRed,
                    WifiMetricsProto.EventoConexion.NOMINADOR_MANUAL);
        }
        iniciarConexionARed(idRed, uid, BSSID_CUALQUIERA_SUPPLICANT);
    }
}

public void iniciarConexionARed(int idRed, int uid, String bssid) {
    enviarMensaje(CMD_INICIAR_CONEXION, idRed, uid, bssid);
}

8. Inicio de la Conexión y Transición de Estado

El mensaje CMD_INICIAR_CONEXION inicia la conexión física, verificando confiugraciones y preparando el cliente IP:

case CMD_INICIAR_CONEXION: {
    if (clienteIp == null) {
        logd("IpClient no listo, CMD_INICIAR_CONEXION descartado");
        break;
    }
    int idRed = mensaje.arg1;
    int uid = mensaje.arg2;
    String bssid = (String) mensaje.obj;
    hlpEnviados = false;
    adminModoCliente.establecerReducirPuntajeRed(false);

    if (!tieneSolicitudesConexion()) {
        if (agenteRed == null) {
            loge("CMD_INICIAR_CONEXION pero sin solicitudes y no conectado, abortando");
        } else if (!utilPermisosWifi.tienePermisoAjustesRed(uid)) {
            loge("CMD_INICIAR_CONEXION pero sin solicitudes y conectado, pero app sin permisos, abortando");
            break;
        }
    }
    WifiConfiguration config = gestorConfigWifi.obtenerConfigRedSinEnmascarar(idRed);
    logd("CMD_INICIAR_CONEXION estado actual " + obtenerEstadoActual().obtenerNombre()
            + " idRed=" + idRed + " roaming=" + esAutoRoaming);
    if (config == null) {
        loge("CMD_INICIAR_CONEXION sin config, abortando...");
        break;
    }
    idRedObjetivo = idRed;
    ultimoRssiEscaneo = gestorConfigWifi.encontrarRssiEscaneo(idRed, monitorSaludWifi.tiempoValidezRssiEscaneo());
    tarjetaPuntajeWifi.registrarIntentoConexion(infoWifi, ultimoRssiEscaneo, config.SSID);
    monitorBloqueoWifi.establecerSsidPermitidos(config.SSID, Collections.emptyList());
    monitorBloqueoWifi.actualizarConfigRoamingFirmware(Set.of(config.SSID));

    actualizarConfigWifiAlIniciarConexion(config, bssid);
    reportarInicioIntentoConexion(config, bssidObjetivo,
            WifiMetricsProto.EventoConexion.ROAMING_NO_RELACIONADO);

    String macActual = wifiNative.obtenerDireccionMac(nombreInterfaz);
    infoWifi.establecerDireccionMac(macActual);
    Log.i(obtenerEtiqueta(), "Conectando con " + macActual + " como dirección MAC");

    configWifiObjetivo = config;
    contadorEventosRedNoEncontrada = 0;
    if (config.filsSha256Habilitado() || config.filsSha384Habilitado()) {
        boolean clienteIpIniciado = iniciarClienteIp(config, true);
        if (clienteIpIniciado) {
            clienteIpPreConexion = true;
            break;
        }
    }
    conectarARed(config);
    break;
}

private void conectarARed(WifiConfiguration config) {
    if ((config != null) && wifiNative.conectarARed(nombreInterfaz, config)) {
        vigilanteUltimoRecurso.registrarTiempoInicioConexion(config.idRed);
        metricasWifi.registrarEventoSTA(nombreInterfaz, TipoEventoSTA.CMD_INICIAR_CONEXION, config);
    esAutoRoaming = false;
        transicionarA(estadoConexionL2);
    } else {
        loge("CMD_INICIAR_CONEXION fallo al iniciar conexión a red " + config);
        configWifiObjetivo = null;
        detenerClienteIp();
        reportarFinIntentoConexion(
                WifiMetrics.EventoConexion.FALLO_CONEXION_RED_FALLIDA,
                WifiMetricsProto.EventoConexion.HLF_NINGUNO,
                WifiMetricsProto.EventoConexion.RAZON_FALLO_DESCONOCIDA);
    }
}

9. Capa de WPA Supplicant

La conexión se pasa a WifiNative.java, que interactúa con el supplicant:

public boolean conectarARed(@NonNull String nombreInterfaz, WifiConfiguration configuracion) {
    gestorCondicionesWifi.abortarEscaneo(nombreInterfaz);
    return halInterfazSupplicantSta.conectarARed(nombreInterfaz, configuracion);
}

En SupplicantStaIfaceHal.java, se gestiona la configuración de la red y se invoca la selección:

public boolean conectarARed(@NonNull String nombreInterfaz, @NonNull WifiConfiguration config) {
    sincronizado (bloqueo) {
        logd("conectarARed " + config.obtenerClavePerfil());
        WifiConfiguration configActual = obtenerConfigLocalRedActual(nombreInterfaz);
        if (WifiConfigurationUtil.esMismaRed(config, configActual)) {
            String bssidSeleccionado = config.obtenerEstadoSeleccionRed().obtenerBssidSeleccionRed();
            String bssidActual = configActual.obtenerEstadoSeleccionRed().obtenerBssidSeleccionRed();
            if (Objects.equals(bssidSeleccionado, bssidActual)) {
                logd("Red ya guardada, no se desencadena operación de eliminar y agregar.");
            } else {
                logd("Red ya guardada, pero se necesita actualizar BSSID.");
                if (!establecerBssidRedActual(nombreInterfaz,
                        config.obtenerEstadoSeleccionRed().obtenerBssidSeleccionRed())) {
                    loge("Fallo al establecer BSSID de red actual.");
                    return false;
                }
                configsLocalesRedActual.put(nombreInterfaz, new WifiConfiguration(config));
            }
        } else {
            manejadoresRemotosRedActual.remove(nombreInterfaz);
            configsLocalesRedActual.remove(nombreInterfaz);
            configsEnlazadasRedLocalYRemota.remove(nombreInterfaz);
            if (!eliminarTodasRedes(nombreInterfaz)) {
                loge("Fallo al eliminar redes existentes");
                return false;
            }
            Par<SupplicantStaNetworkHal, WifiConfiguration> par = agregarRedYGuardarConfig(nombreInterfaz, config);
            if (par == null) {
                loge("Fallo al agregar/guardar configuración de red: " + config.obtenerClavePerfil());
                return false;
            }
            manejadoresRemotosRedActual.put(nombreInterfaz, par.primero);
            configsLocalesRedActual.put(nombreInterfaz, par.segundo);
        }
        SupplicantStaNetworkHal manejadorRed = verificarRedSupplicantYRegistrarFallo(nombreInterfaz, "conectarARed");
        if (manejadorRed == null) {
            loge("No hay manejador de red remoto válido para configuración: " + config.obtenerClavePerfil());
            return false;
        }

        DatosCachePmk datosPmk = entradasCachePmk.get(config.idRed);
        if (datosPmk != null
                && !WifiConfigurationUtil.esConfigParaRedPsk(config)
                && datosPmk.tiempoExpiracionEnSeg > reloj.obtenerMilisegundosDesdeArranque() / 1000) {
            logi("Establecer cache PMK para config id " + config.idRed);
            if (manejadorRed.establecerCachePmk(datosPmk.datos)) {
                metricasWifi.establecerConexionCachePmk(nombreInterfaz, true);
            }
        }

        if (!manejadorRed.seleccionar()) {
            loge("Fallo al seleccionar configuración de red: " + config.obtenerClavePerfil());
            return false;
        }
        return true;
    }
}

Finalmente, en SupplicantStaNetworkHal.java, se llama al método seleccionar que invoca al capa HAL:

public boolean seleccionar() {
    sincronizado (bloqueo) {
        String metodoStr = "seleccionar";
        if (!verificarISupplicantStaNetworkYRegistrarFallo(metodoStr)) return false;
        try {
            EstadoSupplicant estado = iSupplicantStaNetwork.seleccionar();
            return verificarEstadoYRegistrarFallo(estado, metodoStr);
        } catch (RemoteException e) {
            manejarExcepcionRemota(e, metodoStr);
            return false;
        }
    }
}

Esto lleva a la implementación en wpa_supplicant, donde se ejecuta la conexión a nivel de driver.

Etiquetas: Android 12 WiFi WPA supplicant java C++

Publicado el 6-28 02:49