Confira abaixo as atualizações feitas nessas áreas específicas da tela:
- Redimensionar atividades e telas
- Tamanhos e proporções de tela
- Políticas de exibição
- Configurações da janela de exibição
- Identificadores de display estático
- Usar mais de duas telas
- Foco por tela
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
. Problemas comuns encontrados pelos apps quando as atividades são redimensionadas:
- Uma atividade pode ter uma configuração diferente do app ou de outro componente não visual. Um erro comum é ler métricas de exibição do contexto do app. Os valores retornados não serão ajustados às métricas de área visível em que uma atividade é mostrada.
- Uma atividade pode não processar o redimensionamento e falhar, mostrar uma interface distorcida ou perder o estado devido a uma 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 em várias janelas.
No Android 7 (e versões mais recentes), um app pode ser definido
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 que não são de atividade.
- 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 dimensionadas temporariamente 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, a atividade e o processo são salvos com a configuração original quando a configuração de tela aplicada precisa mudar. O usuário recebe uma opção para reiniciar o processo do app e usar a configuração de tela atualizada.
- A orientação fixa é feita pela aplicação de
android:screenOrientation
- O app tem proporção máxima ou mínima padrão ao segmentar 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 adequado. Além disso, uma opção de reiniciar atividade é 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 é totalmente compatível com o 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 do 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 do SCM não puder preencher toda a tela, ela será alinhada à parte de cima e centralizada horizontalmente. Os limites da atividade são calculados por
AppWindowToken#calculateCompatBoundsTransformation()
.
Quando uma atividade do SCM usa uma configuração de tela diferente do
container (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 exibição
O Android 10 oferece suporte a novas proporções
de telas altas e finas a proporções de 1:1. Os apps podem definir
ApplicationInfo#maxAspectRatio
e o ApplicationInfo#minAspectRatio
da tela que eles
conseguem processar.
Figura 1. Exemplo de proporções de apps compatíveis com o 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 as atividades que aceitam a compatibilidade com essas telas pequenas podem ser colocadas
nelas.
Os apps podem ativar esse recurso declarando um tamanho mínimo compatível menor ou igual ao tamanho da tela de destino. Use os atributos de layout de atividade android:minHeight
e android:minWidth
no AndroidManifest.
Políticas de display
O Android 10 separa e move determinadas políticas de exibição da implementação padrão de WindowManagerPolicy
em PhoneWindowManager
para classes por tela, como:
- Mostrar estado e rotação
- Algumas teclas e acompanhamento de eventos de movimento
- IU 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 da 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 janela configurável por tela foi expandida para incluir:
- Modo de janela de exibição padrão
- Valores de overscan
- Rotação de usuários e modo de rotação
- Tamanho, densidade e modo de dimensionamento forçados
- Modo de remoção de conteúdo (quando a exibição é removida)
- Suporte para decorações do sistema e IME
A classe DisplayWindowSettings
contém configurações para essas opções. Eles são mantidos no disco na partição /data
em display_settings.xml
sempre que uma configuração é alterada. Para
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
,
pode ser necessária outra lógica para restaurá-lo se ele for apagado por uma limpeza.
Por padrão, o Android 10 usa
DisplayInfo#uniqueId
como um identificador de uma tela ao manter
as configurações. uniqueId
precisa ser preenchido para todas as exibições. 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 definida 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ática.
As configurações são mantidas no diretório /data
por motivos históricos. Originalmente, eles eram usados para manter as configurações definidas pelo usuário, como
rotação da tela.
Identificadores de display 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 ela incrementando um contador estático. Se o sistema
adicionou e removeu a mesma tela, o resultado foi um ID diferente.
Se um dispositivo tiver várias telas disponíveis desde a inicialização, elas poderão 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 a externa.
O Android 10 muda DisplayInfo#uniqueId
para adicionar um identificador estável e diferenciar entre telas locais, de rede e
virtuais.
Tipo de exibição | Formato |
---|---|
Local | local:<stable-id> |
Rede | network:<mac-address> |
Virtual | virtual:<package-name-and-name> |
Além das atualizações em uniqueId
,
DisplayInfo.address
contém DisplayAddress
, um
identificador de exibição estável em reinicializações. No Android
10, o DisplayAddress
é compatível com telas físicas
e de rede. DisplayAddress.Physical
contém um ID de exibição estável (igual ao de 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 de forma estática. 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 do HWC (que pode ser opaco e nem sempre estável), esse
método retorna o número da 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 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 ao 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
presumiam 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 usar 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
para 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 sinalizadas como TYPE_EXTERNAL
independente 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, a tela principal não pode ser desconectada e, portanto, precisa ser uma tela interna. Alguns smartphones dobráveis têm várias telas internas.
- As telas secundárias são categorizadas corretamente como
Display.TYPE_INTERNAL
ouDisplay.TYPE_EXTERNAL
(antes conhecidas comoDisplay.TYPE_BUILT_IN
eDisplay.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 do 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 as que já foram vistas. Se o HWC
for compatível com 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 do 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 a 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 diferentes métodos ou dispositivos de entrada, como o Android Automotive.
É altamente recomendável que esse recurso não seja ativado para dispositivos comuns, incluindo aqueles com várias telas ou usados para experiências semelhantes a computadores. Isso ocorre principalmente devido a uma questã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 contenha informações sensíveis. Um app malicioso pode criar uma tela virtual fora da tela para executar uma atividade, também com um campo de entrada de texto. As 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 recente (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 principal do dispositivo.
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 raro caso em que duas janelas do mesmo processo seriam focalizadas, o sistema fornece foco apenas para a janela que está mais alta na ordem Z. Essa restrição é removida para apps destinados ao Android 10, quando se espera que eles possam suportar várias janelas em foco simultaneamente.
Implementação
O WindowManagerService#mPerDisplayFocusEnabled
controla a disponibilidade desse recurso. Em 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. Assim, apenas uma fonte, o WindowManager, precisa rastrear a ordem Z das atividades.
O ActivityStackSupervisor#getTopDisplayFocusedStack()
usa 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.
Agora, o InputDispatcher
pode ter várias janelas em foco (uma por tela). Se um evento de entrada for específico da tela, ele será enviado
para a janela em foco na tela correspondente. Caso contrário, ele será enviado
para a janela em foco na tela em foco, que é a tela com que o usuário
interagiu mais recentemente.
Consulte InputDispatcher::mFocusedWindowHandlesByDisplay
e InputDispatcher::setFocusedDisplay()
. Os apps em foco também são atualizados
separadamente no InputManagerService por
NativeInputManager::setFocusedApplication()
.
No WindowManager
, as janelas em foco 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
.