Telas grandes desdobradas e estados dobrados exclusivos permitem novas experiências do usuário em dispositivos dobráveis. Para que o app reconheça um dispositivo dobrável, use a biblioteca Jetpack WindowManager, que tem uma superfície de API para recursos de janela de dispositivos dobráveis como articulações e dobras. Quando o app reconhece dobras, ele pode adaptar o layout para evitar colocar conteúdo importante na área delas ou de articulações, além de poder usar as dobras e articulações como separadores naturais.
Entender se um dispositivo é compatível com configurações como a posição de mesa ou de livro pode orientar decisões sobre a compatibilidade com diferentes layouts ou a disponibilização de recursos específicos.
Informações da janela
A interface WindowInfoTracker na Jetpack WindowManager expõe informações de layout de janelas. O método windowLayoutInfo() da interface retorna um fluxo de dados do WindowLayoutInfo que informa ao app sobre o estado da dobra de um dispositivo dobrável. O método WindowInfoTracker#getOrCreate() cria uma instância de WindowInfoTracker.
A WindowManager permite coletar dados WindowLayoutInfo usando
fluxos do Kotlin e callbacks do Java.
Fluxos Kotlin
Para iniciar e interromper a coleta de dados de WindowLayoutInfo, use uma corrotina reiniciável que reconhece o ciclo de vida, em que o bloco de código repeatOnLifecycle é executado quando o ciclo de vida é de pelo menos STARTED (iniciado) e interrompido quando o ciclo de vida é STOPPED (parado). A execução do bloco de código é reiniciada automaticamente
quando o ciclo de vida é STARTED (iniciado) novamente. No exemplo abaixo, o bloco de código
coleta e usa dados de WindowLayoutInfo:
class DisplayFeaturesActivity : AppCompatActivity() {
    private lateinit var binding: ActivityDisplayFeaturesBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityDisplayFeaturesBinding.inflate(layoutInflater)
        setContentView(binding.root)
        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                WindowInfoTracker.getOrCreate(this@DisplayFeaturesActivity)
                    .windowLayoutInfo(this@DisplayFeaturesActivity)
                    .collect { newLayoutInfo ->
                        // Use newLayoutInfo to update the layout.
                    }
            }
        }
    }
}
Callbacks do Java
A camada de compatibilidade de callback incluída na
dependência androidx.window:window-java permite coletar
atualizações de WindowLayoutInfo sem usar um fluxo Kotlin. O artefato inclui a classe WindowInfoTrackerCallbackAdapter, que adapta um WindowInfoTracker para oferecer suporte ao registro (e ao cancelamento) de callbacks para receber atualizações de WindowLayoutInfo, por exemplo:
public class SplitLayoutActivity extends AppCompatActivity {
    private WindowInfoTrackerCallbackAdapter windowInfoTracker;
    private ActivitySplitLayoutBinding binding;
    private final LayoutStateChangeCallback layoutStateChangeCallback =
            new LayoutStateChangeCallback();
   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());
       windowInfoTracker =
                new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
   }
   @Override
   protected void onStart() {
       super.onStart();
       windowInfoTracker.addWindowLayoutInfoListener(
                this, Runnable::run, layoutStateChangeCallback);
   }
   @Override
   protected void onStop() {
       super.onStop();
       windowInfoTracker
           .removeWindowLayoutInfoListener(layoutStateChangeCallback);
   }
   class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
       @Override
       public void accept(WindowLayoutInfo newLayoutInfo) {
           SplitLayoutActivity.this.runOnUiThread( () -> {
               // Use newLayoutInfo to update the layout.
           });
       }
   }
}
Suporte ao RxJava
Se você já usa o RxJava (versão 2 ou 3),
aproveite os artefatos que permitem usar um
Observable ou Flowable 
para coletar atualizações de WindowLayoutInfo sem usar um fluxo Kotlin.
A camada de compatibilidade fornecida pelas dependências de androidx.window:window-rxjava2 e
androidx.window:window-rxjava3 inclui os métodos
WindowInfoTracker#windowLayoutInfoFlowable() e
WindowInfoTracker#windowLayoutInfoObservable(), que permitem que o
app receba atualizações de WindowLayoutInfo, por exemplo:
class RxActivity: AppCompatActivity {
    private lateinit var binding: ActivityRxBinding
    private var disposable: Disposable? = null
    private lateinit var observable: Observable<WindowLayoutInfo>
   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       binding = ActivitySplitLayoutBinding.inflate(getLayoutInflater());
       setContentView(binding.getRoot());
        // Create a new observable.
        observable = WindowInfoTracker.getOrCreate(this@RxActivity)
            .windowLayoutInfoObservable(this@RxActivity)
   }
   @Override
   protected void onStart() {
       super.onStart();
        // Subscribe to receive WindowLayoutInfo updates.
        disposable?.dispose()
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { newLayoutInfo ->
            // Use newLayoutInfo to update the layout.
        }
   }
   @Override
   protected void onStop() {
       super.onStop();
        // Dispose of the WindowLayoutInfo observable.
        disposable?.dispose()
   }
}
Recursos de telas dobráveis
A classe WindowLayoutInfo da Jetpack WindowManager disponibiliza os recursos de uma
janela de exibição como uma lista de elementos DisplayFeature.
Um FoldingFeature é um tipo de DisplayFeature que fornece informações sobre telas dobráveis, incluindo as seguintes propriedades:
- state: o estado dobrado do dispositivo,- FLATou- HALF_OPENED.
- orientation: a orientação da dobra ou articulação,- HORIZONTALou- VERTICAL.
- occlusionType: indica se a dobra ou articulação oculta parte da tela,- NONEou- FULL.
- isSeparating: se a dobra ou articulação cria duas áreas de exibição lógicas ou não, "true" ou "false".
Um dispositivo dobrável que está HALF_OPENED sempre informa isSeparating como "true"
porque a tela é separada em duas áreas de exibição. Além disso, isSeparating é sempre "true" em um dispositivo de tela dupla quando o app abrange as duas telas.
A propriedade FoldingFeature bounds (herdada de DisplayFeature)
representa o retângulo delimitador de um recurso dobrável, como uma dobra ou articulação.
Os limites podem ser usados para posicionar elementos na tela em relação ao recurso:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
    // ...
    lifecycleScope.launch(Dispatchers.Main) {
        lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
            // Safely collects from WindowInfoTracker when the lifecycle is
            // STARTED and stops collection when the lifecycle is STOPPED.
            WindowInfoTracker.getOrCreate(this@MainActivity)
                .windowLayoutInfo(this@MainActivity)
                .collect { layoutInfo ->
                    // New posture information.
                    val foldingFeature = layoutInfo.displayFeatures
                        .filterIsInstance<FoldingFeature>()
                        .firstOrNull()
                    // Use information from the foldingFeature object.
                }
        }
    }
}
Java
private WindowInfoTrackerCallbackAdapter windowInfoTracker;
private final LayoutStateChangeCallback layoutStateChangeCallback =
                new LayoutStateChangeCallback();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    // ...
    windowInfoTracker =
            new WindowInfoTrackerCallbackAdapter(WindowInfoTracker.getOrCreate(this));
}
@Override
protected void onStart() {
    super.onStart();
    windowInfoTracker.addWindowLayoutInfoListener(
            this, Runnable::run, layoutStateChangeCallback);
}
@Override
protected void onStop() {
    super.onStop();
    windowInfoTracker.removeWindowLayoutInfoListener(layoutStateChangeCallback);
}
class LayoutStateChangeCallback implements Consumer<WindowLayoutInfo> {
    @Override
    public void accept(WindowLayoutInfo newLayoutInfo) {
        // Use newLayoutInfo to update the Layout.
        List<DisplayFeature> displayFeatures = newLayoutInfo.getDisplayFeatures();
        for (DisplayFeature feature : displayFeatures) {
            if (feature instanceof FoldingFeature) {
                // Use information from the feature object.
            }
        }
    }
}
Posição de mesa
Usando as informações incluídas no objeto FoldingFeature, o app pode
oferecer suporte a posições como o modo de mesa, em que o smartphone está em uma superfície, a articulação está
em uma posição horizontal e a tela dobrável está meio aberta.
O modo de mesa oferece aos usuários a conveniência de operar o smartphone sem segurar o dispositivo nas mãos. O modo de mesa é ótimo para assistir conteúdo de mídia, tirar fotos e fazer videochamadas.
 
    Use FoldingFeature.State e FoldingFeature.Orientation para determinar
se o dispositivo está na posição de mesa:
Kotlin
fun isTableTopPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.HORIZONTAL
}
Java
boolean isTableTopPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL);
}
Quando você detectar que o dispositivo está na posição de mesa, atualize o layout do app de acordo. Em apps de música, isso normalmente significa colocar a reprodução acima da dobra e posicionar os controles e o conteúdo suplementar logo abaixo para uma experiência de visualização ou escuta viva-voz.
No Android 15 (nível 35 da API) e versões mais recentes, é possível invocar uma API síncrona para detectar se um dispositivo oferece suporte à postura de mesa, independente do estado atual do dispositivo.
A API fornece uma lista de posturas compatíveis com o dispositivo. Se a lista contiver a postura de mesa, divida o layout do app para oferecer suporte a ela e execute testes A/B na interface do app para layouts de mesa e em tela cheia.
Kotlin
if (WindowSdkExtensions.getInstance().extensionsVersion >= 6) {
    val postures = WindowInfoTracker.getOrCreate(context).supportedPostures
    if (postures.contains(TABLE_TOP)) {
        // Device supports tabletop posture.
   }
}
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    List<SupportedPosture> postures = WindowInfoTracker.getOrCreate(context).getSupportedPostures();
    if (postures.contains(SupportedPosture.TABLETOP)) {
        // Device supports tabletop posture.
    }
}
Exemplos
- App - MediaPlayerActivity(link em inglês): confira como usar o Exoplayer da Media3 e o WindowManager para criar um player de vídeo com reconhecimento de dobra.
- Codelab Otimizar o app de câmera em dispositivos dobráveis com o Jetpack WindowManager: aprenda a implementar a postura de mesa para apps de fotografia. Mostre o visor na metade de cima da tela (acima da dobra) e os controles na metade de baixo (abaixo da dobra). 
Posição de livro
Outra posição dobrável exclusiva é o modo de livro, em que o dispositivo fica meio aberto com a articulação na vertical. A posição de livro é ótima para ler e-books. Com um layout de duas páginas em uma tela dobrável grande, aberta como um livro encadernado, a postura de leitura reproduz a experiência de ler um livro real.
Ele também pode ser usado para fotografia se você quiser capturar uma proporção diferente ao tirar fotos por viva-voz.
Implemente a postura de livro com as mesmas técnicas usadas para a postura de mesa. A única diferença é que o código precisa verificar se a orientação do recurso dobrável é vertical em vez de horizontal:
Kotlin
fun isBookPosture(foldFeature : FoldingFeature?) : Boolean {
    contract { returns(true) implies (foldFeature != null) }
    return foldFeature?.state == FoldingFeature.State.HALF_OPENED &&
            foldFeature.orientation == FoldingFeature.Orientation.VERTICAL
}
Java
boolean isBookPosture(FoldingFeature foldFeature) {
    return (foldFeature != null) &&
           (foldFeature.getState() == FoldingFeature.State.HALF_OPENED) &&
           (foldFeature.getOrientation() == FoldingFeature.Orientation.VERTICAL);
}
Mudanças no tamanho das janelas
A área de exibição de um app pode mudar devido a uma modificação na configuração do dispositivo. Por exemplo, quando o dispositivo é dobrado, desdobrado, girado ou uma janela é redimensionada no modo de várias janelas.
A classe WindowMetricsCalculator da Jetpack WindowManager permite
extrair as métricas atuais e máximas da janela. Semelhante à plataforma
WindowMetrics introduzida no nível 30 da API, a WindowManager
WindowMetrics fornece os limites de janela, mas a API é compatível com versões anteriores
até o nível 14 da API.
Consulte Usar classes de tamanho de janela.
Outros recursos
Amostras
- Jetpack WindowManager: exemplo de como usar a biblioteca WindowManager do Jetpack.
- Jetcaster : implementação da postura de mesa com o Compose.
Codelabs
- Oferecer suporte a dispositivos dobráveis e de duas telas usando a biblioteca do Jetpack WindowManager
- Otimizar o app de câmera em dispositivos dobráveis com o Jetpack WindowManager
