Gerenciamento de Locks de Suspensão no Android: Aquisição e Liberação

Dois eventos principais desencadeiam a entrada em suspensão (após executar operações necessárias antes de dormir):

  • Evento da tecla Power: invoca goToSleepFromNative() no PowerManagerService via JNI.
  • Timeout: definido nas configurações (Ajustes → Tela → Suspensão).

O fluxo de suspensão no PowerManagerService é iulstrado abaixo:

Ambos os caminhos evetnualmente chamam updatePowerStateLocked(), que, após atualizar flags e enviar notificações, invoca updateSuspendBlockerLocked() para liberar os locks de suspensão.

Processamento do Timeout no PowerManagerService

private void atualizarResumoAtividadeUsuarioLocked(long agora, int sujeira) {
    if ((sujeira & (SUJEIRA_ATIVIDADE_USUARIO | SUJEIRA_VIGILIA | SUJEIRA_CONFIGURACOES)) != 0) {
        mHandler.removeMessages(MSG_TIMEOUT_ATIVIDADE_USUARIO);
        long proximoTimeout = 0;
        if (mVigilia != VIGILIA_DORMINDO) {
            final int timeoutTelaDesligada = obterTimeoutTelaDesligadaLocked();
            final int duracaoDim = obterDuracaoDimLocked(timeoutTelaDesligada);
            mResumoAtividadeUsuario = 0;
            if (mUltimaAtividadeUsuario >= mUltimoDespertar) {
                proximoTimeout = mUltimaAtividadeUsuario + timeoutTelaDesligada - duracaoDim;
                if (agora < proximoTimeout) {
                    mResumoAtividadeUsuario |= ATIVIDADE_TELA_CLARA;
                } else {
                    proximoTimeout = mUltimaAtividadeUsuario + timeoutTelaDesligada;
                    if (agora < proximoTimeout) {
                        mResumoAtividadeUsuario |= ATIVIDADE_TELA_DIM;
                    }
                }
            }
            if (mResumoAtividadeUsuario == 0 && mUltimaAtividadeSemMudancaLuz >= mUltimoDespertar) {
                proximoTimeout = mUltimaAtividadeSemMudancaLuz + timeoutTelaDesligada;
                if (agora < proximoTimeout && mRequisicaoExibicao.estadoTela != ESTADO_TELA_DESLIGADA) {
                    mResumoAtividadeUsuario = (mRequisicaoExibicao.estadoTela == ESTADO_TELA_CLARA) ?
                            ATIVIDADE_TELA_CLARA : ATIVIDADE_TELA_DIM;
                }
            }
            if (mResumoAtividadeUsuario != 0) {
                Message msg = mHandler.obtainMessage(MSG_TIMEOUT_ATIVIDADE_USUARIO);
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, proximoTimeout);
            }
        } else {
            mResumoAtividadeUsuario = 0;
        }
    }
}

Manipulação do evento MSG_TIMEOUT_ATIVIDADE_USUARIO:

private final class PowerManagerHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        case MSG_TIMEOUT_ATIVIDADE_USUARIO:
            lidarComTimeoutAtividadeUsuario();
            break;
        }
    }
}

private void lidarComTimeoutAtividadeUsuario() {
    mSujeira |= SUJEIRA_ATIVIDADE_USUARIO;
    atualizarEstadoEnergiaLocked();
}

Aquisição e Liberação dos Locks de Suspensão no PowerManagerService

private void atualizarEstadoEnergiaLocked() {
    if (!mSistemaPronto || mSujeira == 0) {
        return;
    }
    // Fase 0: Atualizações básicas de estado.
    // Fase 1: Atualiza vigília.
    // Fase 2: Atualiza sonhos e estado da tela.
    // Fase 3: Envia notificações, se necessário.
    // Fase 4: Atualiza bloqueador de suspensão.
    atualizarBloqueadorSuspensaoLocked();
}

private void atualizarBloqueadorSuspensaoLocked() {
    final boolean precisaBloqueadorWakeLock = ((mResumoWakeLock & WAKE_LOCK_CPU) != 0);
    final boolean precisaBloqueadorTela = precisaBloqueadorTela();

    // Adquire bloqueadores se necessário
    if (precisaBloqueadorWakeLock && !mSegurandoBloqueadorWakeLock) {
        mBloqueadorWakeLock.adquirir();
        mSegurandoBloqueadorWakeLock = true;
    }
    if (precisaBloqueadorTela && !mSegurandoBloqueadorTela) {
        mBloqueadorTela.adquirir();
        mSegurandoBloqueadorTela = true;
    }

    // Libera bloqueadores se necessário
    if (!precisaBloqueadorWakeLock && mSegurandoBloqueadorWakeLock) {
        mBloqueadorWakeLock.liberar();
        mSegurandoBloqueadorWakeLock = false;
    }
    if (!precisaBloqueadorTela && mSegurandoBloqueadorTela) {
        mBloqueadorTela.liberar();
        mSegurandoBloqueadorTela = false;
    }
}

private final class BloqueadorSuspensaoImpl implements SuspendBlocker {
    private final String mNome;
    private int mContadorReferencia;

    public BloqueadorSuspensaoImpl(String nome) {
        mNome = nome;
    }

    @Override
    public void adquirir() {
        synchronized (this) {
            mContadorReferencia++;
            if (mContadorReferencia == 1) {
                nativoAdquirirBloqueadorSuspensao(mNome);
            }
        }
    }

    @Override
    public void liberar() {
        synchronized (this) {
            mContadorReferencia--;
            if (mContadorReferencia == 0) {
                nativoLiberarBloqueadorSuspensao(mNome);
            }
        }
    }
}

A aquisição e liberação dos locks de suspensão são realizadsa através de chamadas JNI que escrevem nos arquivos /sys/power/wake_lock e /sys/power/wake_unlock:

// Interface JNI
// (com_android_server_power_PowerManagerService.cpp)
static void nativoAdquirirBloqueadorSuspensao(JNIEnv *env, jclass clazz, jstring nomeStr) {
    ScopedUtfChars nome(env, nomeStr);
    adquirir_wake_lock(PARTIAL_WAKE_LOCK, nome.c_str());
}

// Definição dos arquivos a serem acessados
// (power.c)
const char *const NOVOS_CAMINHOS[] = {
    "/sys/power/wake_lock",
    "/sys/power/wake_unlock",
};

// Inicialização dos descritores de arquivo
static inline void inicializar_fds(void) {
    if (g_inicializado == 0) {
        if (abrir_descriptores_arquivo(NOVOS_CAMINHOS) < 0)
            abrir_descriptores_arquivo(CAMINHOS_ANTIGOS);
        g_inicializado = 1;
    }
}

static int abrir_descriptores_arquivo(const char * const caminhos[]) {
    int i;
    for (i=0; i<QUANTIDADE_FDS; i++) {
        int fd = open(caminhos[i], O_RDWR);
        if (fd < 0) {
            fprintf(stderr, "erro fatal ao abrir \"%s\"\n", caminhos[i]);
            g_erro = errno;
            return -1;
        }
        g_fds[i] = fd;
    }
    g_erro = 0;
    return 0;
}

// id é o nome do lock; após abrir, escreve no dispositivo
int adquirir_wake_lock(int lock, const char* id) {
    inicializar_fds();
    if (g_erro) return g_erro;
    int fd;
    if (lock == PARTIAL_WAKE_LOCK) {
        fd = g_fds[ADQUIRIR_WAKE_LOCK_PARCIAL];
    } else {
        return EINVAL;
    }
    return write(fd, id, strlen(id));
}

Tags: android PowerManagerService SuspendBlocker WakeLock JNI

Publicado em 7-25 23:02