Configuração Dinâmica da Orientação da Tela no Android 13

Em alguns projetos com restrições de hardware, a orientação da tela precisa ser definida dinamicamente entre quatro ângulos: 0°, 90°, 180° e 270°. A solução pode ser controlada por uma propriedade do sistema, por exemplo persist.customer.set.orientation=0.

O fluxo de inicialização pode ser dividido em quatro etapas: boot logo, kernel logo, bootanimation e interface do launcher. As duas primeiras dependem de camadas abaixo do framework e não serão tratadas aqui. O foco está na personalização do BootAnimation e da camada de janelas do Android para que o sistema respeite a orientação configurada.

BootAnimation

A classe responsável pela animação de boot fica em frameworks/base/cmds/bootanimation/BootAnimation.cpp. O método readyToRun() monta a animação e cria a superfície nativa. Antes de criar essa superfície, lemos a propriedade de orientação e aplicamos uma projeção de display.

// Retorna o ângulo configurado pelo cliente
static int getRequestedOrientation() {
    char value[PROPERTY_VALUE_MAX];
    property_get("persist.customer.set.orientation", value, "0");
    return atoi(value);
}

// Converte um ângulo numérico em valor de rotação do SurfaceFlinger
static ui::Rotation toSurfaceRotation(int angle) {
    switch (angle) {
        case 90:  return ui::ROTATION_90;
        case 180: return ui::ROTATION_180;
        case 270: return ui::ROTATION_270;
        default:  return ui::ROTATION_0;
    }
}

status_t BootAnimation::readyToRun() {
    mAssets.addDefaultAssets();

    mDisplayToken = SurfaceComposerClient::getInternalDisplayToken();
    if (mDisplayToken == nullptr) {
        return NAME_NOT_FOUND;
    }

    DisplayMode mode;
    status_t err = SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &mode);
    if (err != NO_ERROR) {
        return err;
    }

    int maxW = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
    int maxH = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);

    ui::Size size = limitSurfaceSize(mode.resolution.width, mode.resolution.height);

    // Aplica rotação solicitada
    int rotation = getRequestedOrientation();
    if (rotation != 0) {
        bool swap = (rotation == 90 || rotation == 270);
        int w = swap ? size.getHeight() : size.getWidth();
        int h = swap ? size.getWidth() : size.getHeight();

        Rect fullRect(w, h);
        SurfaceComposerClient::Transaction tx;
        tx.setDisplayProjection(mDisplayToken, toSurfaceRotation(rotation),
                              fullRect, fullRect);
        tx.apply();

        size = ui::Size(w, h);
    }

    sp<SurfaceControl> control = session()->createSurface(
            String8("BootAnimation"),
            size.getWidth(), size.getHeight(),
            PIXEL_FORMAT_RGB_565);

    // ... restante da configuração da animação
}

Alternativamente, o próprio AOSP oferece suporte à rotação da animação de boot por meio de ro.bootanim.set_orientation_<display_id>, processado pelos métodos parseOrientationProperty() e rotateAwayFromNaturalOrientationIfNeeded(). Caso prefira não usar uma propriedade customizada, basta configurar a propriedade nativa correspondente.

Aplicações e Framework

Após o bootanimation, a orientação da tela passa a ser controlada pelo WindowManager. As alterações principais ficam em frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java e DisplayContent.java.

DisplayRotation.java

No construtor, lemos a propriedade e armazenamos a rotação desejada. Em seguida, forçamos essa rotação tanto na inicialização quanto durante as atualizações.

public class DisplayRotation {
    private int mCustomRotation = Surface.ROTATION_0;

    DisplayRotation(WindowManagerService service, DisplayContent displayContent,
            DisplayAddress displayAddress, DisplayPolicy displayPolicy,
            DisplayWindowSettings displayWindowSettings, Context context, Object lock,
            @NonNull DeviceStateController deviceStateController) {

        // ... inicializações existentes

        int requested = SystemProperties.getInt("persist.customer.set.orientation", 0);
        mCustomRotation = mapDegreesToRotation(requested);

        mRotation = mCustomRotation;

        // ... restante do construtor
    }

    private static int mapDegreesToRotation(int degrees) {
        switch (degrees) {
            case 90:  return Surface.ROTATION_90;
            case 180: return Surface.ROTATION_180;
            case 270: return Surface.ROTATION_270;
            default:  return Surface.ROTATION_0;
        }
    }

    boolean updateRotationUnchecked(boolean forceUpdate) {
        final int displayId = mDisplayContent.getDisplayId();
        // ... código de verificação

        final int oldRotation = mRotation;
        final int lastOrientation = mLastOrientation;

        // Substitui a lógica padrão de escolha de rotação
        int rotation = mCustomRotation;

        // ... aplicação da rotação
    }

    @Surface.Rotation
    int rotationForOrientation(@ScreenOrientation int orientation,
            @Surface.Rotation int lastRotation) {
        // Força a orientação personalizada
        if (true) {
            return mCustomRotation;
        }

        // ... lógica original
    }
}

DisplayContent.java

Outro ponto de verificação é o método getOrientation() de DisplayContent. Podemos interceptá-lo para retornar a orientação configurada.

@ScreenOrientation
@Override
int getOrientation() {
    int requested = SystemProperties.getInt("persist.customer.set.orientation", 0);
    switch (requested) {
        case 0:   return Surface.ROTATION_0;
        case 90:  return Surface.ROTATION_90;
        case 180: return Surface.ROTATION_180;
        case 270: return Surface.ROTATION_270;
        default:  break;
    }

    if (mWmService.mDisplayFrozen) {
        if (mWmService.mPolicy.isKeyguardLocked()) {
            // ... comportamento original
        }
    }

    // ... restante do método
}

Permissão para rotação em todas as direções

Para que o acelerômetro possa girar a tela em todas as direções, ajuste o overlay em frameworks/base/core/res/res/values/config.xml:

<bool name="config_allowAllRotations">true</bool>

SurfaceFlinger

Outra abordagem é alterar a rotação diretamente no SurfaceFlinger, em frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp. A função getPhysicalDisplayOrientation informa a orientação física do display.

ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(
        DisplayId displayId, bool isPrimary) const {

    char value[PROPERTY_VALUE_MAX];
    property_get("persist.customer.set.orientation", value, "0");
    int angle = atoi(value);

    switch (angle) {
        case 90:  return ui::ROTATION_90;
        case 180: return ui::ROTATION_180;
        case 270: return ui::ROTATION_270;
        default:  return ui::ROTATION_0;
    }
}

Em dispositivos com criptografia de dados (FBE/FDE), a propriedade pode não estar disponível muito cedo no boot. Nesse caso, é preciso garantir que a partição de dados seja montada o quanto antes, ajustando arquivos como init.mount_all_early.rc e o fstab correspondente, ou usar uma propriedade que resida em uma partição acessível antes da montagem de dados.

O método startBootAnim() permanece responsável por disparar a animação após a configuração das propriedades:

void SurfaceFlinger::startBootAnim() {
    mStartPropertySetThread->Start();
    if (mStartPropertySetThread->join() != NO_ERROR) {
        ALOGE("Falha ao aguardar StartPropertySetThread!");
    }
}

Tags: Android 13 AOSP SurfaceFlinger BootAnimation WindowManager

Publicado em 6-25 16:29