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!");
}
}