Uma breve análise de como monitorar conexão de rede wifi, execução de dhcpcd e controle de fonte de alimentação através de jni no Android
================================================= ================================================= =========
libs/android_runtime/android_net_wifi_Wifi.cpp
Parte da interface jni
JNINativeMethod estático gWifiMethods[] = {
{ "loadDriver", "()Z", (void *)android_net_wifi_loadDriver },
{ "setPowerModeCommand", "(I)Z", (void*) android_net_wifi_setPowerModeCommand },//Gerenciamento de energia
{ "connectToSupplicant", "()Z", (void *)android_net_wifi_connectToSupplicant },
{ "waitForEvent", "()Ljava/lang/String;", (void*) android_net_wifi_waitForEvent },
{ "disconnectCommand", "()Z", (void *)android_net_wifi_disconnectCommand },
...
};
int register_android_net_wifi_WifiManager(JNIEnv* env)
{
...
retornar AndroidRuntime::registerNativeMethods(env,
WIFI_PKG_NAME, gWifiMethods, NELEM(gWifiMethods));//Registrar jni
}
libs/android_runtime/AndroidRuntime.cpp
static const RegJNIRec gRegJNI[] = {
...
REG_JNI(register_android_net_wifi_WifiManager),
...
};
int AndroidRuntime::startReg(JNIEnv* env)
{
...
registrar_jni_procs(gRegJNI, NELEM(gRegJNI), env);
...
}
AndroidRuntime::start
=>startReg(env) chama o método int AndroidRuntime::startReg(JNIEnv* env)
================================================= ================================================= =========
wifi_load_driver
wifi_start_supplicant
=> garantir_config_file_exists
//Verifique se o arquivo /data/misc/wifi/wpa_supplicant.conf existe. Se não existir, copie dinamicamente uma cópia de /system/etc/wifi/wpa_supplicant.conf.
android_net_wifi_connectToSupplicant
=> wifi_connect_to_supplicant
=>
ctrl_conn = wpa_ctrl_open(ifnome);
monitor_conn = wpa_ctrl_open(ifnome);
wpa_ctrl_attach(monitor_conn);
android_net_wifi_waitForEvent
=>wifi_wait_for_event
=>wpa_ctrl_recv(monitor_conn, buf, &nread);
=>recv(ctrl->s, respond, *reply_len, 0);//Bloqueando e aguardando a chegada dos dados do netlink do wpa_supplicant
=>Se na área de dados buf recebidos, buf[0] for '<', significa que há informações de nível, então os dados '<'...'>' são removidos e então a função wifi_wait_for_event retorna [luther. gliethttp].
java/android/android/net/wifi/WifiMonitor.java
classe pública WifiMonitor {
...
public void startMonitoring() {
new MonitorThread().start();//Iniciar thread java
}
classe MonitorThread estende Thread {
public MonitorThread() {
super("Monitor Wifi");
}
execução nula pública() {
para (;;) {
garantirSupplicantConnection();//=>WifiNative.connectToSupplicant chama a função jni android_net_wifi_connectToSupplicant
String eventStr = WifiNative.waitForEvent();//=>Chamar função jni android_net_wifi_waitForEvent
//privado estático final int CONNECTED = 1;
//privado estático final int DISCONNECTED = 2;
//final estático privado String eventPrefix = "CTRL-EVENT-";
//final estático privado int eventPrefixLen = eventPrefix.length();
//privado estático final String conectadoEvent = "CONNECTED";
//privado estático final String desconectadoEvent = "DISCONNECTED";
String eventName = eventStr.substring(eventPrefixLen);//Remova a string "CTRL-EVENT-"
int nameEnd = eventName.indexOf(' ');//Encontre a posição de espaço subsequente, que é quando wpa_supplicant é enviado
//#define WPA_EVENT_CONNECTED "CTRL-EVENT-CONNECTED" possui espaços integrados.
if (nomeEnd! = -1)
nomedoevento = nomedoevento.substring(0, nomeEnd);
evento interno;
if (eventName.equals(connectedEvent)) // Detecta o tipo de ação da string do netlink
evento = CONECTADO;
senão if (eventName.equals(disconnectedEvent))
evento = DESCONECTADO;
...
int ind = eventStr.indexOf(" - ");//CTRL-EVENT-CONNECTED - Conexão com ...
se (ind! = -1)
eventData = eventStr.substring(ind + 3);
//Remova os caracteres de controle iniciais, use a string de descrição após "-" como dados reais e continue o processamento
...
if (evento == STATE_CHANGE) {
handleSupplicantStateChange(eventData);
} else if (evento == DRIVER_STATE) {
handleDriverEvent(eventData);
} outro {
handleEvent(event, eventData);//Para eventos netlink como CONNECTED e DISCONNECTED, esta operação será realizada para manipular [luther.gliethttp]
//Se o suplicante sumiu, sai do tópico
if (evento == TERMINANDO) {
quebrar;
}
}
...
void handleEvent(int evento, String resto) {
mudar (evento) {
caso DESCONECTADO:
handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, restante);
quebrar;
caso CONECTADO:
handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, restante);//Exibição da interface de controle
quebrar;
...
}
classe pública WifiStateTracker estende NetworkStateTracker {
...
public void startEventLoop() {
mWifiMonitor.startMonitoring();//Iniciar o thread MonitorThread acima
}
...
}
java/services/com/android/server/WifiService.java
classe pública WifiService estende IWifiManager.Stub {
...
private boolean setWifiEnabledBlocking(boolean enable) {
final int eventualWifiState = ativar WIFI_STATE_ENABLED: WIFI_STATE_DISABLED;
...
if (habilitar) {
if (WifiNative.loadDriver()) {
Log.e(TAG, "Falha ao carregar o driver Wi-Fi.");
atualizarWifiState(WIFI_STATE_UNKNOWN);
retornar falso;
}
if (WifiNative.startSupplicant()) {
WifiNative.unloadDriver();
Log.e(TAG, "Falha ao iniciar o daemon suplicante.");
atualizarWifiState(WIFI_STATE_UNKNOWN);
retornar falso;
}
mWifiStateTracker.startEventLoop();
//Inicie o thread MonitorThread, aguarde wpa_supplicant encaminhar os dados do netlink e, em seguida, afete ainda mais a exibição da interface de acordo com o tipo de ação do netlink [luther.gliethttp].
}
...
}
java/android/android/net/wifi/WifiStateTracker.java
Gerenciamento de energia
private void handleConnectedState() {
...
mDhcpTarget.obtainMessage(EVENT_DHCP_START).sendToTarget(); // Passa para o método handleMessage abaixo
...
}
public void onChange(boolean selfChange) {
...
handleConnectedState();
...
}
classe pública WifiStateTracker estende NetworkStateTracker {
...
public void handleMessage(Mensagem de mensagem) {
switch (msg.what) {
caso EVENT_SUPPLICANT_CONNECTION:
caso EVENT_NETWORK_STATE_CHANGED:
handleConnectedState(); //chama
...
classe privada DhcpHandler estende o manipulador {
manipulador privado mTarget;
public DhcpHandler(Looper looper, alvo do manipulador) {
super(loop);
mTarget = alvo;
}
public void handleMessage(Mensagem de mensagem) {
evento interno;
//final estático privado int DRIVER_POWER_MODE_AUTO = 0;
//final estático privado int DRIVER_POWER_MODE_ACTIVE = 1;
switch (msg.what) {
caso EVENT_DHCP_START:
sincronizado (este) {
WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_ACTIVE);//Defina o modo de energia e chame android_net_wifi_setPowerModeCommand
}
Log.d(TAG, "DhcpHandler: solicitação DHCP iniciada");
//libs/android_runtime/android_net_NetUtils.cpp
//JNINativeMethod estático gNetworkUtilMethods[] = {
//{ "runDhcp", "(Ljava/lang/String;Landroid/net/DhcpInfo;)Z", (void *)android_net_utils_runDhcp },
// ...
//};
if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) {//Executa o aplicativo dhcp para operação de endereço IP
evento = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED;
if (LOCAL_LOGD) Log.v(TAG, "DhcpHandler: solicitação DHCP bem-sucedida");
} outro {
evento = EVENT_INTERFACE_CONFIGURATION_FAILED;
Log.i(TAG, "DhcpHandler: falha na solicitação DHCP: " +
NetworkUtils.getDhcpError());
//Se o dhcpcd falhar na alocação do IP, então Message.obtain(mTarget, event).sendToTarget() será executado;
//WifiNative.disconnectCommand(); isto é: static JNINativeMethod gWifiMethods[] = {
//android_net_wifi_disconnectCommand envia a string "DISCONNECT" [luther.gliethttp]
//Em seguida, execute wpa_supplicant_ctrl_iface_process no servidor wpa_supplicant
//wpa_supplicant_disassociate
}
sincronizado (isto) {
WifiNative.setPowerModeCommand(DRIVER_POWER_MODE_AUTO);
}
Message.obtain(mTarget, evento).sendToTarget();
quebrar;
}
}
}
...
/**
* Envie ao rastreador uma notificação de que há conexão com o suplicante
* daemon foi estabelecido.
*/
//Na classe pública acima WifiMonitor=>ensureSupplicantConnection
//=>
//enquanto (!supplicantConnected) {
// booleano conectado;
//sincronizado (mWifiStateTracker) {
//connected = WifiNative.connectToSupplicant();//Se a conexão não for bem-sucedida, o loop while tenta até que a tentativa seja bem-sucedida ou oneShot seja definido, apenas uma tentativa
//=>mWifiStateTracker.notifySupplicantConnection();//Se WifiNative.connectToSupplicant() for bem-sucedido, ele será executado
//Chamada de mWifiStateTracker.notifySupplicantConnection();.
void notifySupplicantConnection() {//Envia mensagem para o objeto
Message.obtain(this, EVENT_SUPPLICANT_CONNECTION).sendToTarget();
}
void notifyStateChange(SupplicantState newState) {
Message.obtain(this, EVENT_SUPPLICANT_STATE_CHANGED, newState).sendToTarget();
}
...
}
jboolean estático android_net_wifi_setPowerModeCommand (JNIEnv * env, jobject clazz, modo jint)
{
charcmdstr[256];
sprintf(cmdstr, "DRIVER POWERMODE %d", modo);
return doBooleanCommand(cmdstr, "OK");
}
android_net_wifi_setPowerModeCommand
=>doBooleanCommand
=>doCommand
=> wifi_command
=> wifi_send_command
=>wpa_ctrl_request
=>enviar para wpa_supplicant
Então wpa_supplicant fará as seguintes operações de recebimento:
sistema/extra/wpa_supplicant/main.c
=>wpa_supplicant_add_iface
=>wpa_supplicant_init_iface2
=>wpa_supplicant_ctrl_iface_init
=>Registre as funções de processamento da porta de controle ctrl_conn e da porta de escuta monitor_conn
eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive, wpa_s, priv);//função de processamento do manipulador da porta ctrl_conn
wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);//Função de processamento de retorno de chamada da porta monitor_conn, processa dados de netlink para todas as portas de escuta monitor_conn
=>wpa_supplicant_ctrl_iface_receive //Para método de comunicação unix
=>wpa_supplicant_ctrl_iface_process
=> Se wpa_cli enviar um comando na forma de driver wpa_cli xxx, chame esta função
if (os_strncmp(buf, "DRIVER ", 7) == 0) {//Pule os 7 primeiros e passe o comando diretamente
resposta_len = wpa_supplicant_driver_cmd(wpa_s, buf + 7, resposta, resposta_size);
=>wpa_supplicant_driver_cmd
=>wpa_drv_driver_cmd
=> Personalize a função de processamento da extensão DRIVER, portanto, para o comando de gerenciamento de energia passado por java, wpa_drv_driver_cmd receberá a string "POWERMODE 0" ou "POWERMODE 1" [luther.gliethttp]
================================================= ================================================= =========
jni
=>executarDhcp
=>android_net_utils_runDhcp
libs/netutils/dhcp_utils.c
=>dhcp_do_request
=>
static const char DAEMON_NAME[] = "dhcpcd";
static const char DAEMON_PROP_NAME[] = "init.svc.dhcpcd";
static const char DHCP_PROP_NAME_PREFIX[] = "dhcp";
const char *ctrl_prop = "ctl.start";
const char *desired_status = "em execução";
snprintf(result_prop_name, sizeof(result_prop_name), "%s.%s.resultado",
DHCP_PROP_NAME_PREFIX,
interface);
property_set(result_prop_name, "");//Set dhcp.eth0.result="";
property_set(ctrl_prop, DAEMON_NAME);//Envia a palavra de comando de inicialização "ctrl.start" para o serviço chamado dhcpcd, que está em init.rc
//palavra de comando do processo de serviço dhcpcd em init.rc
//serviço dhcpcd /system/bin/dhcpcd eth0
// desabilitado
// tiro único
wait_for_property(DAEMON_PROP_NAME, status_desejado, 10);
//init.c=>processo de inicialização
//=>handle_property_set_fd é a palavra de comando "ctrl.start", então handle_control_message é chamado para processar a mensagem de controle.
//=>handle_control_message
//=>msg_start
//=>
// estrutura serviço *svc = service_find_by_name(nome);
//service_start(svc);//Iniciar svc, ou seja, executar: /system/bin/dhcpcd eth0
//=>service_start
//=>pid = fork();
// if(pid == 0)execve(svc->args[0], (char**) svc->args, (char**) ENV); o processo filho executa execve e executa /system/bin/dhcpcd); , parâmetros para eth0
//=>Caso contrário, o processo pai, ou seja, o processo init,
//=>notify_service_state(svc->name, "running"); Defina o suporte de estado do svc
// snprintf(pname, sizeof(pname), "init.svc.%s", nome);
// property_set(pname, state);//Então desta forma, wait_for_property(DAEMON_PROP_NAME, desejado_status, 10);
wait_for_property(result_prop_name, NULL, 15);//Aguardando dhcp.eth0.result=non-null
================================================= ================================================= =========
sistema/extra/dhcpcd-4.0.0-beta9/dhcpcd.c
dhcpcd
=>principal
#define SYSCONFDIR "/system/etc/dhcpcd"
#define PACOTE "dhcpcd"
#define CONFIG SYSCONFDIR "/" PACKAGE ".conf"
#define LIBEXECDIR "/system/etc/dhcpcd"
#define SCRIPT LIBEXECDIR "/" PACKAGE "-run-hooks"
=>strlcpy(opções->script, SCRIPT, sizeof(opções->script));//Opções padrão->script="/system/etc/dhcpcd/dhcpcd-run-hooks"
=>f = fopen(cf ? cf : CONFIG, "r");//Se nenhum arquivo .conf for especificado, use o arquivo .conf padrão
=>parse_config_line//Analisar o arquivo de configuração padrão "/system/etc/dhcpcd/dhcpcd.conf"
=>parse_option
=>Se houver uma seção "script" em "/system/etc/dhcpcd/dhcpcd.conf"
=>Em seguida, execute strlcpy(options->script, oarg, sizeof(options->script));
/*
{"script", argumento_requerido, NULL, 'c'},
{"opção", argumento_requerido, NULL, 'o'},
Parte do conteúdo em "/system/etc/dhcpcd/dhcpcd.conf" é o seguinte:
...
opção nome_do_domínio_servidores, nome_do_domínio, pesquisa_do_domínio, nome_do_host
...
*/
=>dhcp_run
=>handle_dhcp_packet
=>handle_dhcp
=>bind_dhcp
motivo = "TIMEOUT";motivo = "LIGADO";motivo = "REBIND";motivo = "RENOVAR";
sistema/extra/dhcpcd-4.0.0-beta9/configure.c
=> configurar(iface, razão, estado->novo, estado->antigo, &estado->arrendamento, opções, 1);
//Se o dhcp expirar ou o dhcp for bem-sucedido, exec_script será chamado para executar o script.
//Executa setprop dhcp.${interface}.result "failed" ou
//Executa setprop dhcp.${interface}.result "ok"
=>exec_script(opções, iface->nome, motivo, NULL, antigo);
=>Então configure_env passa o motivo para o script através da variável de ambiente
int exec_script(const struct opções *opções, const char *iface, const char *motivo,
const estrutura dhcp_message *dhcpn, const estrutura dhcp_message *dhcpo)
=>pid = garfo();
=>if(pid == 0)execve(options->script, argv, env);//O processo filho executa o script, o padrão é "/system/etc/dhcpcd/dhcpcd-run-hooks"
//o script dhcpcd-run-hooks decidirá se deve executar os arquivos correspondentes no diretório system/etc/dhcpcd/dhcpcd-hook/* com base no valor do nível
//Nosso sistema possui os 3 arquivos a seguir no diretório system/etc/dhcpcd/dhcpcd-hook/*
//95-configurado
//20-dns.conf
//01-teste
=> O processo pai retorna while (waitpid(pid, &status, 0) == -1) e aguarda a conclusão da execução do script do processo filho
sistema/extra/dhcpcd-4.0.0-beta9/dhcpcd-hooks/20-dns.conf
sistema/extra/dhcpcd-4.0.0-beta9/dhcpcd-hooks/95-configurado
...
setprop dhcp.${interface}.ipaddress "${new_ip_address}"
setprop dhcp.${interface}.result "ok"//Defina o atributo como ok
setprop dhcp.${interface}.result "falhou"
...
================================================= ================================================= =========
inet_init, tcp_prot
sock->ops->sendmsg(iocb, sock, msg, tamanho);
=>inetsw_array[]
=>inet_stream_ops
=>tcp_sendmsg
================================================= ================================================= =========
wpa_cli.c
=>principal
=>wpa_cli_interativo
=>wpa_cli_recv_pending(monitor_conn, 0, 0);//Bloqueando e aguardando wpa_supplicant enviar dados
=>Se action_monitor for verdadeiro, algumas operações simples de processamento serão realizadas, caso contrário os dados enviados por wpa_supplicant serão impressos diretamente no console [luther.gliethttp].