Dispositivos dobráveis oferecem experiências de visualização únicas. O modo de tela traseira e o Dual Screen permitem criar recursos especiais de exibição para dispositivos dobráveis, como a prévia da selfie de câmera traseira e telas interna e externa simultâneas.
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á aberto. O display interno é desligado automaticamente.
Um aplicativo inovador deve mostrar a prévia da câmera na tela externa. Assim, os usuários poderão tirar selfies com a câmera traseira, o que geralmente oferece uma qualidade de fotos muito 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. Portanto, não é necessário desenvolver nada da sua parte. 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. Confira um exemplo de implementação no codelab Otimizar o app de câmera em dispositivos dobráveis com o Jetpack WindowManager.
Modo de tela dupla
Esse modo permite mostrar conteúdo nas duas telas do dispositivo dobrável ao mesmo tempo. Ele está disponível no Pixel Fold com o Android 14 (nível 34 da API) e em versões mais recentes.
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 Dual Screen pelas APIs Jetpack WindowManager, a partir da versão 1.2.0-beta03 da biblioteca.
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 as
informações e o comportamento relacionados ao movimento de janelas entre telas ou
á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 é usada 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 recurso específico:
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 abaixo 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 o
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()
. Ao encerrar a
sessão, você recebe 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
você sobre o status da sessão e permitem atualizar 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 ícone oficial de Dual Screen para indicar aos usuários como ativar ou desativar esse modo.
Para um exemplo funcional, consulte DualScreenActivity.kt.
Modo de tela traseira
Semelhante ao exemplo do modo Dual Screen, o exemplo a seguir de uma
função toggleRearDisplayMode()
fecha a sessão se o capability já estiver
ativo ou chama a função
transferActivityToWindowArea()
:
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 esse modo.
Outros recursos
- Otimizar o app de câmera em dispositivos dobráveis com o codelab Jetpack WindowManager
- Resumo do pacote
androidx.window.area
- Exemplo de código da API Jetpack WindowManager (links em inglês):