Suporte de exibição

As atualizações feitas nessas áreas específicas da tela são fornecidas abaixo:

Redimensionar atividades e telas

Para indicar que um app pode não oferecer suporte ao modo de várias janelas ou ao redimensionamento, as atividades usam o atributo resizeableActivity=false. Os problemas comuns encontrados pelos apps quando as atividades são redimensionadas incluem:

  • Uma atividade pode ter uma configuração diferente do app ou de outro componente não visual. Um erro comum é ler as métricas de exibição do contexto do app. Os valores retornados não serão ajustados às métricas da área visível em que uma atividade é exibida.
  • Uma atividade pode não processar o redimensionamento e falhar, mostrar uma interface distorcida ou perder o estado devido à reinicialização sem salvar o estado da instância.
  • Um app pode tentar usar coordenadas de entrada absolutas (em vez daquelas relativas à posição da janela), o que pode interromper a entrada no modo de várias janelas.

No Android 7 (e versões mais recentes), um app pode ser definido como resizeableActivity=false para sempre ser executado no modo de tela cheia. Nesse caso, a plataforma impede que atividades não redimensionáveis entrem na tela dividida. Se o usuário tentar invocar uma atividade não redimensionável na tela de início enquanto já estiver no modo de tela dividida, a plataforma vai sair desse modo e iniciar a atividade não redimensionável no modo de tela cheia.

Os apps que definem explicitamente esse atributo como false no manifesto não podem ser iniciados no modo de várias janelas, a menos que o modo de compatibilidade seja aplicado:

  • A mesma configuração é aplicada ao processo, que contém todas as atividades e componentes não relacionados a atividades.
  • A configuração aplicada atende aos requisitos do CDD para telas compatíveis com apps.

No Android 10, a plataforma ainda impede que atividades não redimensionáveis entrem no modo de tela dividida, mas elas podem ser temporariamente dimensionadas se a atividade tiver declarado uma orientação ou proporção fixa. Caso contrário, a atividade será redimensionada para preencher a tela inteira, como no Android 9 e versões anteriores.

A implementação padrão aplica a seguinte política:

Quando uma atividade declarada como incompatível com várias janelas usando o atributo android:resizeableActivity e quando essa atividade atende a uma das condições descritas abaixo, então, quando a configuração da tela aplicada precisar mudar, a atividade e o processo serão salvos com a configuração original, e o usuário receberá uma opção para reiniciar o processo do app e usar a configuração da tela atualizada.

  • É uma orientação fixa pela aplicação de android:screenOrientation
  • O app tem a proporção máxima ou mínima padrão segmentando o nível da API ou declara a proporção explicitamente

Esta figura mostra uma atividade não redimensionável com uma proporção declarada. Ao dobrar o dispositivo, a janela é reduzida para caber na área, mantendo a proporção usando o letterboxing apropriado. Além disso, uma opção de atividade de reinicialização é fornecida ao usuário sempre que a área de exibição da atividade é alterada.

Ao desdobrar o dispositivo, a configuração, o tamanho e a proporção da atividade não mudam, mas a opção de reiniciar a atividade é exibida.

Quando resizeableActivity não está definido (ou está definido como true), o app oferece suporte total ao redimensionamento.

Implementação

Uma atividade não redimensionável com orientação ou proporção fixa é chamada de modo de compatibilidade de tamanho (SCM, na sigla em inglês) no código. A condição é definida em ActivityRecord#shouldUseSizeCompatMode(). Quando uma atividade de SCM é iniciada, a configuração relacionada à tela (como tamanho ou densidade) é corrigida na configuração de substituição solicitada. Assim, a atividade não depende mais da configuração de exibição atual.

Se a atividade de SCM não puder preencher a tela inteira, ela será alinhada na parte de cima e centralizada horizontalmente. Os limites da atividade são calculados por AppWindowToken#calculateCompatBoundsTransformation().

Quando uma atividade de SCM usa uma configuração de tela diferente do contêiner (por exemplo, a tela é redimensionada ou a atividade é movida para outra tela), ActivityRecord#inSizeCompatMode() é verdadeiro e SizeCompatModeActivityController (na interface do sistema) recebe o callback para mostrar o botão de reinicialização do processo.

Tamanhos e proporções de tela

O Android 10 oferece suporte a novas proporções, desde proporções altas de telas longas e finas até proporções de 1:1. Os apps podem definir ApplicationInfo#maxAspectRatio e o ApplicationInfo#minAspectRatio da tela que eles podem processar.

proporções de apps no Android 10

Figura 1. Exemplo de proporções de apps com suporte no Android 10

As implementações de dispositivos podem ter telas secundárias com tamanhos e resoluções menores do que as exigidas pelo Android 9 e versões anteriores (mínimo de 2, 5 polegadas de largura ou altura, mínimo de 320 DP para smallestScreenWidth), mas apenas atividades que aceitam oferecer suporte a essas telas pequenas podem ser colocadas nelas.

Os apps podem aceitar declarando um tamanho mínimo com suporte menor ou igual ao tamanho de exibição de destino. Use os atributos de layout de atividade android:minHeight e android:minWidth no AndroidManifest para fazer isso.

Políticas de exibição

O Android 10 separa e move determinadas políticas de exibição da implementação WindowManagerPolicy padrão em PhoneWindowManager para classes por tela, como:

  • Estado e rotação da tela
  • Algumas chaves e acompanhamento de eventos de movimento
  • Interface do sistema e janelas de decoração

No Android 9 (e versões anteriores), a classe PhoneWindowManager processava políticas de exibição, estado e configurações, rotação, rastreamento de frame de janela de decoração e muito mais. O Android 10 move a maior parte disso para a classe DisplayPolicy, exceto o rastreamento de rotação, que foi movido para DisplayRotation.

Configurações da janela de exibição

No Android 10, a configuração de janelas configurável por tela foi expandida para incluir:

  • Modo de janelas de exibição padrão
  • Valores de overscan
  • Rotação e modo de rotação do usuário
  • Tamanho, densidade e modo de escalonamento forçados
  • Modo de remoção de conteúdo (quando a tela é removida)
  • Suporte a decorações do sistema e IME

A classe DisplayWindowSettings contém configurações para essas opções. Elas são mantidas no disco na partição /data em display_settings.xml sempre que uma configuração é alterada. Para mais detalhes, consulte DisplayWindowSettings.AtomicFileStorage e DisplayWindowSettings#writeSettings(). Os fabricantes de dispositivos podem fornecer valores padrão em display_settings.xml para a configuração do dispositivo. No entanto, como o arquivo é armazenado em /data, uma lógica extra pode ser necessária para restaurar o arquivo se ele for apagado por uma limpeza.

Por padrão, o Android 10 usa DisplayInfo#uniqueId como um identificador para uma tela ao manter as configurações. uniqueId precisa ser preenchido para todas as telas. Além disso, ele é estável para telas físicas e de rede. Também é possível usar a porta de uma tela física como identificador, que pode ser definido em DisplayWindowSettings#mIdentifier. Em cada gravação, todas as configurações são gravadas. Portanto, é seguro atualizar a chave usada para uma entrada de exibição no armazenamento. Para mais detalhes, consulte Identificadores de exibição estáticos.

As configurações são mantidas no diretório /data por motivos históricos. Originalmente, elas eram usadas para manter as configurações definidas pelo usuário, como a rotação da tela.

Identificadores de exibição estáticos

O Android 9 (e versões anteriores) não fornecia identificadores estáveis para telas no framework. Quando uma tela era adicionada ao sistema, Display#mDisplayId ou DisplayInfo#displayId era gerado para essa tela incrementando um contador estático. Se o sistema adicionasse e removesse a mesma tela, um ID diferente seria gerado.

Se um dispositivo tivesse várias telas disponíveis na inicialização, elas poderiam receber identificadores diferentes, dependendo do momento. Embora o Android 9 (e versões anteriores) incluísse DisplayInfo#uniqueId, ele não continha informações suficientes para diferenciar as telas, porque as telas físicas eram identificadas como local:0 ou local:1, para representar a tela integrada e externa.

O Android 10 muda DisplayInfo#uniqueId para adicionar um identificador estável e diferenciar entre telas locais, de rede e virtuais.

Tipo de tela Formato
Local
local:<stable-id>
Rede
network:<mac-address>
Virtual
virtual:<package-name-and-name>

Além das atualizações para uniqueId, DisplayInfo.address contém DisplayAddress, um identificador de tela estável em reinicializações. No Android 10, DisplayAddress oferece suporte a telas físicas e de rede. DisplayAddress.Physical contém um ID de tela estável (o mesmo que em uniqueId) e pode ser criado com DisplayAddress#fromPhysicalDisplayId().

O Android 10 também oferece um método conveniente para receber informações de porta (Physical#getPort()). Esse método pode ser usado no framework para identificar telas estaticamente. Por exemplo, ele é usado em DisplayWindowSettings). DisplayAddress.Network contém o endereço MAC e pode ser criado com DisplayAddress#fromMacAddress().

Essas adições permitem que os fabricantes de dispositivos identifiquem telas em configurações estáticas de várias telas e configurem diferentes configurações e recursos do sistema usando identificadores de tela estáticos, como portas para telas físicas. Esses métodos são ocultos e destinados apenas ao uso em system_server.

Dado um ID de tela HWC (que pode ser opaco e nem sempre estável), esse método retorna o número de porta de 8 bits (específico da plataforma) que identifica um conector físico para saída de tela, bem como o blob EDID da tela. O SurfaceFlinger extrai informações do fabricante ou do modelo do EDID para gerar os IDs de tela estáveis de 64 bits expostos ao framework. Se esse método não for compatível ou gerar erros, o SurfaceFlinger vai voltar para o modo MD legado, em que DisplayInfo#address é nulo e DisplayInfo#uniqueId é codificado, conforme descrito acima.

Para verificar se esse recurso é compatível, execute:

$ dumpsys SurfaceFlinger --display-id
# Example output.
Display 21691504607621632 (HWC display 0): port=0 pnpId=SHP displayName="LQ123P1JX32"
Display 9834494747159041 (HWC display 2): port=1 pnpId=HWP displayName="HP Z24i"
Display 1886279400700944 (HWC display 1): port=2 pnpId=AUS displayName="ASUS MB16AP"

Usar mais de duas telas

No Android 9 (e versões anteriores), o SurfaceFlinger e o DisplayManagerService assumiam a existência de no máximo duas telas físicas com IDs codificados 0 e 1.

A partir do Android 10, o SurfaceFlinger pode aproveitar uma API Hardware Composer (HWC) para gerar IDs de tela estáveis, o que permite gerenciar um número arbitrário de telas físicas. Para saber mais, consulte Identificadores de exibição estáticos.

O framework pode pesquisar o token IBinder de uma tela física usando SurfaceControl#getPhysicalDisplayToken depois de receber o ID de tela de 64 bits de SurfaceControl#getPhysicalDisplayIds ou de um evento de hotplug DisplayEventReceiver.

No Android 10 (e versões anteriores), a tela interna principal é TYPE_INTERNAL, e todas as telas secundárias são marcadas como TYPE_EXTERNAL, independentemente do tipo de conexão. Portanto, outras telas internas são tratadas como externas. Como solução alternativa, o código específico do dispositivo pode fazer suposições sobre DisplayAddress.Physical#getPort se o HWC for conhecido e a lógica de alocação de porta for previsível.

Essa limitação foi removida no Android 11 (e versões mais recentes).

  • No Android 11, a primeira tela informada durante a inicialização é a principal. O tipo de conexão (interna ou externa) é irrelevante. No entanto, continua sendo verdade que a tela principal não pode ser desconectada e, portanto, precisa ser uma tela interna na prática. Observe que alguns smartphones dobráveis têm várias telas internas.
  • As telas secundárias são categorizadas corretamente como Display.TYPE_INTERNAL ou Display.TYPE_EXTERNAL (anteriormente conhecidas como Display.TYPE_BUILT_IN e Display.TYPE_HDMI, respectivamente), dependendo do tipo de conexão.

Implementação

No Android 9 e versões anteriores, as telas são identificadas por IDs de 32 bits, em que 0 é a tela interna, 1 é a tela externa, [2, INT32_MAX] são telas virtuais HWC e -1 representa uma tela inválida ou uma tela virtual não HWC.

A partir do Android 10, as telas recebem IDs estáveis e persistentes, o que permite que o SurfaceFlinger e o DisplayManagerService rastreiem mais de duas telas e reconheçam telas vistas anteriormente. Se o HWC oferecer suporte a IComposerClient.getDisplayIdentificationData e fornecer dados de identificação de tela, o SurfaceFlinger vai analisar a estrutura EDID e alocar IDs de tela estáveis de 64 bits para telas físicas e virtuais HWC. Os IDs são expressos usando um tipo de opção, em que o valor nulo representa uma tela inválida ou uma tela virtual não HWC. Sem suporte ao HWC, o SurfaceFlinger volta ao comportamento legado com no máximo duas telas físicas.

Foco por tela

Para oferecer suporte a várias fontes de entrada que segmentam telas individuais ao mesmo tempo, o Android 10 pode ser configurado para oferecer suporte a várias janelas em foco, no máximo uma por tela. Isso é destinado apenas a tipos especiais de dispositivos quando vários usuários interagem com o mesmo dispositivo ao mesmo tempo e usam métodos ou dispositivos de entrada diferentes, como o Android Automotive.

É altamente recomendável que esse recurso não seja ativado para dispositivos comuns, incluindo dispositivos com várias telas ou aqueles usados para experiências semelhantes a computadores. Isso ocorre principalmente devido a uma preocupação de segurança que pode fazer com que os usuários se perguntem qual janela tem o foco de entrada.

Imagine o usuário que insere informações seguras em um campo de entrada de texto, talvez fazendo login em um app bancário ou inserindo texto que contém informações confidenciais. Um app malicioso pode criar uma tela virtual fora da tela para executar uma atividade, também com um campo de entrada de texto. Atividades legítimas e maliciosas têm foco e mostram um indicador de entrada ativo (cursor piscando).

No entanto, como a entrada de um teclado (hardware ou software) é inserida apenas na atividade mais alta (o app que foi iniciado mais recentemente), ao criar uma tela virtual oculta, um app malicioso pode capturar a entrada do usuário, mesmo ao usar um teclado de software na tela do dispositivo principal.

Use com.android.internal.R.bool.config_perDisplayFocusEnabled para definir o foco por tela.

Compatibilidade

Problema:no Android 9 e versões anteriores, no máximo uma janela no sistema tem foco por vez.

Solução:no caso raro em que duas janelas do mesmo processo seriam focadas, o sistema fornece foco apenas para a janela que está mais alta na ordem Z. Essa restrição é removida para apps direcionados ao Android 10, momento em que se espera que eles ofereçam suporte a várias janelas focadas simultaneamente.

Implementação

WindowManagerService#mPerDisplayFocusEnabled controla a disponibilidade desse recurso. No ActivityManager, ActivityDisplay#getFocusedStack() agora é usado em vez do rastreamento global em uma variável. ActivityDisplay#getFocusedStack() determina o foco com base na ordem Z em vez de armazenar o valor em cache. Isso ocorre para que apenas uma fonte, o WindowManager, precise rastrear a ordem Z das atividades.

ActivityStackSupervisor#getTopDisplayFocusedStack() adota uma abordagem semelhante para os casos em que a pilha focada mais alta no sistema precisa ser identificada. As pilhas são percorridas de cima para baixo, procurando a primeira pilha qualificada.

InputDispatcher agora pode ter várias janelas focadas (uma por tela). Se um evento de entrada for específico da tela, ele será enviado para a janela focada na tela correspondente. Caso contrário, ele será enviado para a janela focada na tela focada, que é a tela com que o usuário interagiu mais recentemente.

Consulte InputDispatcher::mFocusedWindowHandlesByDisplay e InputDispatcher::setFocusedDisplay(). Os apps focados também são atualizados separadamente no InputManagerService usando NativeInputManager::setFocusedApplication().

No WindowManager, as janelas focadas também são rastreadas separadamente. Consulte DisplayContent#mCurrentFocus e DisplayContent#mFocusedApp e os respectivos usos. Os métodos relacionados de rastreamento e atualização de foco foram movidos de WindowManagerService para DisplayContent.