Assurer la compatibilité avec les modes d'affichage des appareils pliables

En matière d'affichage, les appareils pliables offrent des expériences uniques. Le mode d'affichage arrière et le mode double écran vous permettent de créer des fonctionnalités d'affichage spéciales pour les appareils pliables, comme l'aperçu de selfie avec la caméra arrière et des affichages simultanés, mais différents, sur les écrans intérieur et extérieur.

Mode d'affichage arrière

En règle générale, lorsqu'un appareil pliable est en position dépliée, seul l'écran intérieur est actif. Le mode d'affichage arrière vous permet de déplacer une activité vers l'écran extérieur d'un appareil pliable, qui est généralement orienté vers l'extérieur lorsque l'appareil est déplié. L'écran intérieur s'éteint alors automatiquement.

Une nouvelle application consiste à afficher l'aperçu de l'appareil photo sur l'écran extérieur, afin que les utilisateurs puissent prendre des selfies avec la caméra arrière, ce qui offre généralement de bien meilleures performances de prise de vue que la caméra avant.

Pour activer le mode d'affichage arrière, les utilisateurs répondent à une boîte de dialogue permettant à l'application de changer d'écran, par exemple:

Figure 1 : Boîte de dialogue système permettant d'activer le mode d'affichage arrière.

Le système crée la boîte de dialogue. Aucun développement de votre part n'est donc requis. Différentes boîtes de dialogue s'affichent en fonction de l'état de l'appareil. Par exemple, le système demande aux utilisateurs de déplier l'appareil s'il est fermé. Vous ne pouvez pas personnaliser la boîte de dialogue, et elle peut varier selon les appareils de différents OEM.

Vous pouvez tester le mode d'affichage arrière avec l'application Appareil photo du Pixel Fold. Consultez un exemple d'implémentation dans l'atelier de programmation Optimiser votre application d'appareil photo sur les appareils pliables avec Jetpack WindowManager.

Mode Dual Screen

Le mode Dual Screen vous permet d'afficher du contenu sur les deux écrans d'un appareil pliable en même temps. Ce mode est disponible sur les Pixel Fold équipés d'Android 14 (niveau d'API 34) ou version ultérieure.

Le mode interprète Dual Screen est un exemple de cas d'utilisation.

Figure 2 : Mode interprète Dual Screen affichant des contenus différents sur les écrans avant et arrière.

Approche programmatique d'activation des modes

Vous pouvez accéder au mode d'affichage arrière et au mode double écran via les API Jetpack WindowManager, à partir de la version 1.2.0-beta03 de la bibliothèque.

Ajoutez la dépendance WindowManager au fichier build.gradle du module de votre application :

Groovy

dependencies {
    implementation "androidx.window:window:1.2.0-beta03"
}

Kotlin

dependencies {
    implementation("androidx.window:window:1.2.0-beta03")
}

Le point d'entrée est l'élément WindowAreaController, qui fournit les informations et le comportement liés au déplacement des fenêtres entre les écrans ou entre les zones d'affichage d'un appareil. WindowAreaController vous permet d'interroger la liste des objets WindowAreaInfo disponibles.

Utilisez WindowAreaInfo pour accéder à WindowAreaSession, une interface qui représente une fonctionnalité de zone de fenêtre active. Utilisez WindowAreaSession pour déterminer la disponibilité d'un WindowAreaCapability spécifique.

Chaque capacité est liée à une WindowAreaCapability.Operation particulier. Dans la version 1.2.0-beta03, Jetpack WindowManager prend en charge deux types d'opérations :

Voici un exemple de déclaration de variables pour les modes d'affichage arrière et Dual Screen dans l'activité principale de votre application :

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;

Voici comment initialiser les variables dans la méthode onCreate() de votre activité :

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;
        }
    }
});

Avant de démarrer une opération, vérifiez la disponibilité de la fonctionnalité concernée:

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.
}

Mode Dual Screen

L'exemple suivant ferme la session si la capacité est déjà active ou appelle la fonction 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);
    }
}

Notez l'utilisation de l'activité principale de l'application en tant qu'argument WindowAreaPresentationSessionCallback.

L'API utilise une approche d'écouteur : lorsque vous envoyez une requête pour afficher le contenu sur l'autre écran d'un appareil pliable, vous démarrez une session qui est renvoyée via la méthode onSessionStarted() de l'écouteur. Lorsque vous fermez la session, une confirmation s'affiche dans la méthode onSessionEnded().

Pour créer l'écouteur, implémentez l'interface WindowAreaPresentationSessionCallback:

Kotlin

class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback

Java

public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback

L'écouteur doit implémenter les méthodes onSessionStarted(), onSessionEnded(), et onContainerVisibilityChanged(). Les méthodes de rappel vous informent de l'état de la session et vous permettent de mettre à jour l'application en conséquence.

Le rappel onSessionStarted() reçoit un WindowAreaSessionPresenter en tant qu'argument. Il s'agit du conteneur qui vous permet d'accéder à une zone de fenêtre et d'afficher du contenu. Le système peut automatiquement ignorer la présentation lorsque l'utilisateur quitte la fenêtre principale de l'application. La présentation peut également être fermée en appelant WindowAreaSessionPresenter#close().

Pour les autres rappels, et pour plus de simplicité, il vous suffit de rechercher les erreurs éventuelles dans le corps de la fonction et d'enregistrer l'état :

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);
}

Pour assurer la cohérence au sein de l'écosystème, utilisez l'icône officielle Dual Screen pour indiquer aux utilisateurs comment activer ou désactiver ce mode.

Pour un exemple fonctionnel, consultez DualScreenActivity.kt.

Mode d'affichage arrière

Comme dans l'exemple du mode Dual Screen, l'exemple suivant de fonction toggleRearDisplayMode() ferme la session si la capacité est déjà active ou appelle la fonction 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);
    }
}

Dans ce cas, l'activité affichée est utilisée en tant que WindowAreaSessionCallback, ce qui est plus simple à implémenter, car le rappel ne reçoit pas de présentateur permettant d'afficher du contenu sur une zone de fenêtre, mais transfère à la place l'ensemble de l'activité vers une autre zone:

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}");
    }
}

Pour assurer la cohérence au sein de l'écosystème, utilisez l'icône officielle de la caméra arrière pour indiquer aux utilisateurs comment activer ou désactiver le mode d'affichage arrière.

Ressources supplémentaires