Multitarefa na TV

O Android 14 (nível 34 da API) apresenta algumas melhorias nas APIs picture-in-picture (PiP) para permitir a multitarefa. Embora o suporte ao PiP tenha sido introduzido no Android 8.0 (nível 26 da API), ele não era amplamente compatível com o Android TV e não era compatível com o Google TV antes do Android 13. A multitarefa para TV usa o modo PiP para permitir que dois apps separados coexistam na tela: um em tela cheia e outro no modo PiP. Há requisitos diferentes para apps executados em qualquer um desses modos.

O comportamento padrão é que o app PiP sobreponha o app de tela cheia. Isso é muito parecido com o comportamento padrão de picture-in-picture do Android.

Ao integrar a multitarefa, o aplicativo precisa declarar os tipos de uso de acordo com as diretrizes de qualidade de apps para TV.

Executar o app no modo PiP

Para dispositivos de TV com o Android 14 (nível 34 da API) ou mais recente, execute o app no modo PiP chamando enterPictureInPictureMode(). Os dispositivos de TV com versões anteriores do Android não oferecem suporte ao modo PiP.

Confira um exemplo de como implementar a lógica de um botão para entrar no modo PiP:

Kotlin

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    pictureInPictureButton.visibility =
        if (requireActivity().packageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
            pictureInPictureButton.setOnClickListener {
                val aspectRatio = Rational(view.width, view.height)
                val params = PictureInPictureParams.Builder()
                    .setAspectRatio(aspectRatio)
                    .build()
                val result = requireActivity().enterPictureInPictureMode(params)
            }
            View.VISIBLE
        } else {
            View.GONE
        }
}

Java

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    if (requireActivity().getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
        pictureInPictureButton.setVisibility(View.VISIBLE);
        pictureInPictureButton.setOnClickListener(v -> {
            Rational aspectRatio = new Rational(view.getWidth(), view.getHeight());
            PictureInPictureParams params = new PictureInPictureParams.Builder()
                    .setAspectRatio(aspectRatio)
                    .setTitle("My Streaming App")
                    .setSubtitle("My On-Demand Content")
                    .build();
            Boolean result = requireActivity().enterPictureInPictureMode(params);
        });
    } else {
        pictureInPictureButton.setVisibility(View.GONE);
    }
}

A ação só é adicionada se o dispositivo tiver o recurso do sistema FEATURE_PICTURE_IN_PICTURE. Além disso, quando a ação é acionada, a proporção do modo PiP é definida para corresponder à proporção do vídeo que está sendo reproduzido.

Adicione um título e legenda para informar ao usuário sobre o uso geral desse PIP.

Coexistir com apps em execução no modo PiP

Quando o app está sendo executado como um app de tela cheia, ele pode precisar se adaptar a outros apps em execução no modo PiP.

APIs de manter limpo

Em alguns casos, o app PiP pode sobrepor componentes importantes da interface do usuário no app de tela cheia. Para atenuar isso, há APIs de manter limpo que os apps podem usar para identificar componentes críticos da interface do usuário que não devem ser sobrepostos. O sistema tenta atender às solicitações para evitar cobrir esses componentes, reposicionando a janela PiP.

Posicionar a janela PiP para evitar cobrir componentes importantes da interface
Figura 1. Posicionamento da janela PiP para evitar cobrir componentes importantes da interface do usuário.

Para especificar que uma visualização não deve ser sobreposta, use preferKeepClear no seu layout XML, como no exemplo a seguir:

<TextView
    android:id="@+id/important_text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:preferKeepClear="true"
    android:text="@string/app_name"/>

Você também pode fazer isso de maneira programática usando setPreferKeepClear():

Kotlin

private lateinit var binding: MyLayoutBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    binding = MyLayoutBinding.inflate(layoutInflater)
    setContentView(binding.root)
    binding.importantText.isPreferKeepClear = true
}

Java

private MyLayoutBinding binding;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    binding = MyLayoutBinding.inflate(getLayoutInflater());
    setContentView(binding.getRoot());
    binding.importantText.setPreferKeepClear(true);
}

Pode haver momentos em que não é necessário manter uma View inteira limpa, mas apenas uma seção dela. O setPreferKeepClearRects() pode ser usado para especificar regiões da View que não devem ser sobrepostas. As interfaces que não usam Views nativamente, como Flutter, Jetpack Compose e WebView, podem ter subseções que precisam de regiões mantidas limpas. Essa API pode ser usada nesses casos.

Tipos de uso

O app precisa declarar um atributo de valor de metadados de com.google.android.tv.pip.category que corresponda ao tipo ou tipos principais de uso do modo picture-in-picture. Qualquer <activity> que tenha definido android:supportsPictureInPicture="true" precisa declarar esse atributo com um valor relevante da tabela a seguir.

Os tipos de uso que não se enquadram em nenhuma dessas categorias, em particular qualquer reprodução de conteúdo de mídia, não são permitidos no modo picture-in-picture na TV.

Valor Descrição
"communication" Casos de uso de comunicação, como chamadas de vídeo ou voz.
"smartHome" Integrações de casa inteligente, como campainhas conectadas ou babás eletrônicas.
"health" Casos de uso de saúde, como monitoramento de condicionamento físico ou de saúde.
"ticker" Casos de uso de ticker, como placares esportivos ao vivo ou notícias e tickers de ações.

Vários valores são separados por uma barra vertical (|). Por exemplo:

<meta-data android:name="com.google.android.tv.pip.category" android:value="smartHome|health" />