Dispositivos dobráveis oferecem experiências de visualização únicas. Modo de tela traseira e o modo Dual Screen permite criar recursos especiais de exibição para dispositivos dobráveis como a prévia de selfie de câmera traseira e imagens simultâneas, mas diferentes exibidos nas telas interna e externa.
Modo de tela traseira
Normalmente, quando um dispositivo dobrável é aberto, apenas a tela interna fica ativa. O modo de tela traseira permite mover uma atividade para a tela externa de um dispositivo dobrável que geralmente fica voltada para o lado oposto ao usuário enquanto o dispositivo está desdobrado. O display interno é desligado automaticamente.
Um aplicativo novo é exibir a visualização da câmera na tela externa, para que os usuários podem tirar selfies com a câmera traseira, o que geralmente oferece uma experiência desempenho de tirar fotos melhor do que a câmera frontal.
Para ativar o modo de tela traseira, os usuários respondem a uma caixa de diálogo que permite a troca de tela pelo app. Por exemplo:

O sistema cria a caixa de diálogo. Assim, ela não precisa ser desenvolvida por você. Diferentes caixas de diálogo aparecem dependendo do estado do dispositivo. Por exemplo, o sistema orienta os usuários a abrir o dispositivo se ele estiver fechado. Não é possível personalizar a caixa de diálogo, e ela pode variar de acordo com os dispositivos de diferentes OEMs.
Você pode testar o modo de tela traseira com o app de câmera do Pixel Fold. Ver um exemplo implementação no codelab Otimizar o app de câmera em dispositivos dobráveis usando Jetpack WindowManager.
Modo Dual Screen
O modo Dual Screen permite mostrar conteúdo nas duas telas de um dispositivo dobrável ao mesmo tempo. O modo Dual Screen está disponível no Pixel Fold com o Android 14 (nível 34 da API) ou mais recente.
Um exemplo de caso de uso é o intérprete com Dual Screen.

Ativar os modos de forma programática
Você pode acessar o modo de tela traseira e o modo de tela dupla pelas APIs Jetpack WindowManager da versão 1.2.0-beta03 da biblioteca em diante.
Adicione a dependência WindowManager ao arquivo build.gradle
do módulo do app:
Groovy
dependencies { implementation "androidx.window:window:1.2.0-beta03" }
Kotlin
dependencies { implementation("androidx.window:window:1.2.0-beta03") }
O ponto de entrada é o WindowAreaController
, que fornece
informações e o comportamento ao mover janelas entre telas ou entre
áreas de exibição em um dispositivo. WindowAreaController
permite consultar a lista de
objetos WindowAreaInfo
disponíveis.
Use WindowAreaInfo
para acessar a WindowAreaSession
, uma interface que
representa um recurso de área da janela ativa. Use WindowAreaSession
para determinar
a disponibilidade de uma WindowAreaCapability
específica.
Cada recurso está relacionado a uma WindowAreaCapability.Operation
específica.
Na versão 1.2.0-beta03, a API Jetpack WindowManager tem suporte a dois tipos de operações:
WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA
, que é usado para iniciar o modo Dual ScreenWindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
, usado para iniciar o modo de tela traseira
Confira um exemplo de como declarar variáveis para o modo de tela traseira e Dual Screen na atividade principal do app:
Kotlin
private lateinit var windowAreaController: WindowAreaController private lateinit var displayExecutor: Executor private var windowAreaSession: WindowAreaSession? = null private var windowAreaInfo: WindowAreaInfo? = null private var capabilityStatus: WindowAreaCapability.Status = WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED private val dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA private val rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA
Java
private WindowAreaControllerCallbackAdapter windowAreaController = null; private Executor displayExecutor = null; private WindowAreaSessionPresenter windowAreaSession = null; private WindowAreaInfo windowAreaInfo = null; private WindowAreaCapability.Status capabilityStatus = WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED; private WindowAreaCapability.Operation dualScreenOperation = WindowAreaCapability.Operation.OPERATION_PRESENT_ON_AREA; private WindowAreaCapability.Operation rearDisplayOperation = WindowAreaCapability.Operation.OPERATION_TRANSFER_ACTIVITY_TO_AREA;
Confira como inicializar as variáveis no método onCreate()
da sua
atividade:
Kotlin
displayExecutor = ContextCompat.getMainExecutor(this) windowAreaController = WindowAreaController.getOrCreate() lifecycleScope.launch(Dispatchers.Main) { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { windowAreaController.windowAreaInfos .map { info -> info.firstOrNull { it.type == WindowAreaInfo.Type.TYPE_REAR_FACING } } .onEach { info -> windowAreaInfo = info } .map { it?.getCapability(operation)?.status ?: WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED } .distinctUntilChanged() .collect { capabilityStatus = it } } }
Java
displayExecutor = ContextCompat.getMainExecutor(this); windowAreaController = new WindowAreaControllerCallbackAdapter(WindowAreaController.getOrCreate()); windowAreaController.addWindowAreaInfoListListener(displayExecutor, this); windowAreaController.addWindowAreaInfoListListener(displayExecutor, windowAreaInfos -> { for(WindowAreaInfo newInfo : windowAreaInfos){ if(newInfo.getType().equals(WindowAreaInfo.Type.TYPE_REAR_FACING)){ windowAreaInfo = newInfo; capabilityStatus = newInfo.getCapability(presentOperation).getStatus(); break; } } });
Antes de iniciar uma operação, verifique a disponibilidade do capacidade:
Kotlin
when (capabilityStatus) { WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED -> { // The selected display mode is not supported on this device. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE -> { // The selected display mode is not available. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE -> { // The selected display mode is available and can be enabled. } WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE -> { // The selected display mode is already active. } else -> { // The selected display mode status is unknown. } }
Java
if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNSUPPORTED)) { // The selected display mode is not supported on this device. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_UNAVAILABLE)) { // The selected display mode is not available. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_AVAILABLE)) { // The selected display mode is available and can be enabled. } else if (capabilityStatus.equals(WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE)) { // The selected display mode is already active. } else { // The selected display mode status is unknown. }
Modo de tela dupla
O exemplo a seguir fecha a sessão se o recurso já estiver ativo ou
chama a função presentContentOnWindowArea()
:
Kotlin
fun toggleDualScreenMode() { if (windowAreaSession != null) { windowAreaSession?.close() } else { windowAreaInfo?.token?.let { token -> windowAreaController.presentContentOnWindowArea( token = token, activity = this, executor = displayExecutor, windowAreaPresentationSessionCallback = this ) } } }
Java
private void toggleDualScreenMode() { if(windowAreaSession != null) { windowAreaSession.close(); } else { Binder token = windowAreaInfo.getToken(); windowAreaController.presentContentOnWindowArea( token, this, displayExecutor, this); } }
Observe o uso da atividade principal do app como
Argumento WindowAreaPresentationSessionCallback
.
A API usa uma abordagem de listener: ao fazer uma solicitação para apresentar o conteúdo
na outra tela de um dispositivo dobrável, você inicia uma sessão que é retornada
usando o método onSessionStarted()
. Quando você fechar o
você vai receber uma confirmação no método onSessionEnded()
.
Para criar o listener, implemente a interface WindowAreaPresentationSessionCallback
:
Kotlin
class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback
Java
public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback
O listener precisa implementar os métodos onSessionStarted()
, onSessionEnded(),
e onContainerVisibilityChanged()
. Os métodos de callback notificam
o status da sessão e permitem que você atualize o app adequadamente.
O callback onSessionStarted()
recebe um WindowAreaSessionPresenter
como
argumento. O argumento é o contêiner que permite acessar uma área da janela
e mostrar conteúdo. A apresentação poderá ser dispensada automaticamente pelo sistema
quando o usuário sair da janela principal do app ou ser fechada
chamando WindowAreaSessionPresenter#close()
.
Para os outros callbacks, você pode simplificar o processo verificando se há erros no corpo da função e registrando o estado:
Kotlin
override fun onSessionStarted(session: WindowAreaSessionPresenter) { windowAreaSession = session val view = TextView(session.context) view.text = "Hello world!" session.setContentView(view) } override fun onSessionEnded(t: Throwable?) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}") } } override fun onContainerVisibilityChanged(isVisible: Boolean) { Log.d(logTag, "onContainerVisibilityChanged. isVisible = $isVisible") }
Java
@Override public void onSessionStarted(@NonNull WindowAreaSessionPresenter session) { windowAreaSession = session; TextView view = new TextView(session.getContext()); view.setText("Hello world, from the other screen!"); session.setContentView(view); } @Override public void onSessionEnded(@Nullable Throwable t) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}"); } } @Override public void onContainerVisibilityChanged(boolean isVisible) { Log.d(logTag, "onContainerVisibilityChanged. isVisible = " + isVisible); }
Para manter a consistência em todo o ecossistema, use o Dual Screen (em inglês) oficial para indicar aos usuários como ativar ou desativar o modo Dual Screen.
Para um exemplo funcional, consulte DualScreenActivity.kt.
Modo de tela traseira
Semelhante ao exemplo do modo Dual Screen, o exemplo a seguir de um
A função toggleRearDisplayMode()
fecha a sessão se o recurso é
já está ativo ou chama transferActivityToWindowArea()
função:
Kotlin
fun toggleRearDisplayMode() { if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) { if(windowAreaSession == null) { windowAreaSession = windowAreaInfo?.getActiveSession( operation ) } windowAreaSession?.close() } else { windowAreaInfo?.token?.let { token -> windowAreaController.transferActivityToWindowArea( token = token, activity = this, executor = displayExecutor, windowAreaSessionCallback = this ) } } }
Java
void toggleDualScreenMode() { if(capabilityStatus == WindowAreaCapability.Status.WINDOW_AREA_STATUS_ACTIVE) { if(windowAreaSession == null) { windowAreaSession = windowAreaInfo.getActiveSession( operation ) } windowAreaSession.close() } else { Binder token = windowAreaInfo.getToken(); windowAreaController.transferActivityToWindowArea(token, this, displayExecutor, this); } }
Nesse caso, a atividade mostrada é usada como um WindowAreaSessionCallback
,
que é mais simples de implementar porque o callback não recebe um apresentador
que permita mostrar conteúdo em uma área de janela, mas transfere toda a
atividade para outra área:
Kotlin
override fun onSessionStarted() { Log.d(logTag, "onSessionStarted") } override fun onSessionEnded(t: Throwable?) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}") } }
Java
@Override public void onSessionStarted(){ Log.d(logTag, "onSessionStarted"); } @Override public void onSessionEnded(@Nullable Throwable t) { if(t != null) { Log.e(logTag, "Something was broken: ${t.message}"); } }
Para manter a consistência em todo o ecossistema, use o ícone oficial de câmera traseira para indicar aos usuários como ativar ou desativar o modo de exibição traseira.
Outros recursos
- Otimizar o app de câmera em dispositivos dobráveis com a Jetpack WindowManager codelab
- Resumo do pacote
androidx.window.area
- Exemplo de código da API Jetpack WindowManager (links em inglês):