Modi für faltbare Displays unterstützen

Faltbare Geräte bieten einzigartige Möglichkeiten für die Videowiedergabe. Mit dem Modus für das äußere Display und dem Dual-Screen-Modus können Sie spezielle Displayfunktionen für faltbare Geräte entwickeln, z. B. eine Vorschau für Selfies mit der Rückkamera und die gleichzeitige, aber unterschiedliche Anzeige auf dem inneren und äußeren Display.

Anzeigemodus für das Rückdisplay

Normalerweise ist bei einem aufgeklappten faltbaren Gerät nur das innere Display aktiv. Im Modus „Rückseitendisplay“ können Sie eine Aktivität auf das äußere Display eines faltbaren Geräts verschieben. Dieses ist normalerweise vom Nutzer abgewandt, wenn das Gerät aufgeklappt ist. Das innere Display schaltet sich automatisch aus.

Eine neuartige Anwendung besteht darin, die Kameravorschau auf dem äußeren Display anzuzeigen, damit Nutzer Selfies mit der Rückkamera aufnehmen können, die in der Regel eine viel bessere Bildqualität als die Frontkamera bietet.

Um den Modus für das Rückdisplay zu aktivieren, müssen Nutzer in einem Dialogfeld bestätigen, dass die App den Bildschirm wechseln darf, z. B.:

Abbildung 1. Systemdialogfeld zum Starten des Modus für das rückseitige Display.

Das System erstellt den Dialog, sodass keine Entwicklung erforderlich ist. Je nach Gerätestatus werden unterschiedliche Dialogfelder angezeigt. Wenn das Gerät beispielsweise geschlossen ist, werden Nutzer aufgefordert, es aufzuklappen. Sie können den Dialog nicht anpassen. Er kann auf Geräten von verschiedenen OEMs variieren.

Sie können den Rückdisplaymodus mit der Kamera App auf dem Pixel Fold ausprobieren. Eine Beispielimplementierung finden Sie im Codelab Kamera App auf faltbaren Geräten mit Jetpack WindowManager optimieren.

Dual Screen-Modus

Im Dual-Screen-Modus können Sie Inhalte gleichzeitig auf beiden Displays eines faltbaren Geräts anzeigen lassen. Der Dual Screen-Modus ist auf dem Pixel Fold mit Android 14 (API‑Level 34) oder höher verfügbar.

Ein Beispiel hierfür ist der Dual Screen-Dolmetscher.

Abbildung 2. Der Dual Screen-Dolmetscher zeigt unterschiedliche Inhalte auf dem vorderen und hinteren Display an.

Modi programmatisch aktivieren

Ab der Bibliotheksversion 1.2.0-beta03 können Sie über die Jetpack WindowManager-APIs auf den Modus für das Rückdisplay und den Dual-Screen-Modus zugreifen.

Fügen Sie die WindowManager-Abhängigkeit der Datei build.gradle des Moduls Ihrer App hinzu:

Groovy

dependencies {
    // TODO: Define window_version in your project's build configuration.
    implementation "androidx.window:window:$window_version"
}

Kotlin

dependencies {
    // Define window_version in your project's build configuration.
    implementation("androidx.window:window:$window_version")
}

Der Einstiegspunkt ist WindowAreaController, das Informationen und Verhalten zum Verschieben von Fenstern zwischen Displays oder zwischen Displaybereichen auf einem Gerät bereitstellt. Mit WindowAreaController können Sie die Liste der verfügbaren WindowAreaInfo-Objekte abfragen.

Verwenden Sie WindowAreaInfo, um auf WindowAreaSession zuzugreifen, eine Schnittstelle, die ein aktives Fensterbereichs-Feature darstellt. Verwenden Sie WindowAreaSession, um die Verfügbarkeit einer bestimmten WindowAreaCapability zu ermitteln.

Jede Funktion ist mit einem bestimmten WindowAreaCapability.Operation verknüpft. In Version 1.2.0-beta03 unterstützt Jetpack WindowManager zwei Arten von Vorgängen:

Hier sehen Sie ein Beispiel dafür, wie Sie Variablen für den Rückdisplaymodus und den Dual-Screen-Modus in der Hauptaktivität Ihrer App deklarieren:

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;

So initialisieren Sie die Variablen in der Methode onCreate() Ihrer Aktivität:

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

Prüfen Sie vor dem Starten eines Vorgangs die Verfügbarkeit der jeweiligen Funktion:

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

Dual Screen-Modus

Im folgenden Beispiel wird die Sitzung geschlossen, wenn die Funktion bereits aktiv ist. Andernfalls wird die Funktion presentContentOnWindowArea() aufgerufen:

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

Beachten Sie, dass die Hauptaktivität der App als WindowAreaPresentationSessionCallback-Argument verwendet wird.

Die API verwendet einen Listener-Ansatz: Wenn Sie eine Anfrage stellen, um die Inhalte auf dem anderen Display eines faltbaren Geräts zu präsentieren, starten Sie eine Sitzung, die über die Methode onSessionStarted() des Listeners zurückgegeben wird. Wenn Sie die Sitzung schließen, erhalten Sie eine Bestätigung über die Methode onSessionEnded().

Implementieren Sie die WindowAreaPresentationSessionCallback-Schnittstelle, um den Listener zu erstellen:

Kotlin

class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback

Java

public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback

Der Listener muss die Methoden onSessionStarted(), onSessionEnded(), und onContainerVisibilityChanged() implementieren. Die Callback-Methoden informieren Sie über den Sitzungsstatus und ermöglichen es Ihnen, die App entsprechend zu aktualisieren.

Der onSessionStarted()-Callback empfängt ein WindowAreaSessionPresenter als Argument. Das Argument ist der Container, über den Sie auf einen Fensterbereich zugreifen und Inhalte anzeigen können. Die Präsentation kann automatisch vom System geschlossen werden, wenn der Nutzer das primäre Anwendungsfenster verlässt, oder durch Aufrufen von WindowAreaSessionPresenter#close().

Bei den anderen Callbacks prüfen Sie zur Vereinfachung einfach im Funktionskörper auf Fehler und protokollieren Sie den Status:

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

Um die Einheitlichkeit im gesamten Ökosystem zu wahren, verwenden Sie das offizielle Symbol für Dual Screen, um Nutzern zu zeigen, wie sie den Dual Screen-Modus aktivieren oder deaktivieren können.

Ein funktionierendes Beispiel finden Sie unter DualScreenActivity.kt.

Anzeigemodus für das Rückdisplay

Ähnlich wie im Beispiel für den Dual-Screen-Modus wird im folgenden Beispiel für eine toggleRearDisplayMode()-Funktion die Sitzung geschlossen, wenn die Funktion bereits aktiv ist. Andernfalls wird die Funktion transferActivityToWindowArea() aufgerufen:

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 toggleRearDisplayMode() {
    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);
    }
}

In diesem Fall wird die angezeigte Aktivität als WindowAreaSessionCallback verwendet. Das ist einfacher zu implementieren, da der Callback keinen Presenter empfängt, mit dem Inhalte in einem Fensterbereich angezeigt werden können. Stattdessen wird die gesamte Aktivität in einen anderen Bereich übertragen:

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

Um die Einheitlichkeit im gesamten Ökosystem zu wahren, verwenden Sie das offizielle Symbol für die Rückkamera, um Nutzern zu zeigen, wie sie den Rückdisplaymodus aktivieren oder deaktivieren können.

Zusätzliche Ressourcen