Supporta le modalità di visualizzazione pieghevoli

I dispositivi pieghevoli offrono esperienze di visualizzazione uniche. Le modalità display posteriore e doppio schermo consentono di realizzare funzionalità di visualizzazione speciali per i dispositivi pieghevoli, come l'anteprima selfie con la fotocamera posteriore e display simultanei sugli schermi interno ed esterno.

Modalità schermo posteriore

In genere, quando un dispositivo pieghevole è aperto, è attivo solo lo schermo interno. La modalità display posteriore ti consente di spostare un'attività sullo schermo esterno di un dispositivo pieghevole, che di solito è rivolto verso l'utente mentre il dispositivo è aperto. Il display interno si spegne automaticamente.

Una nuova applicazione consiste nel visualizzare l'anteprima della fotocamera sullo schermo esterno, in modo che gli utenti possano scattare selfie con la fotocamera posteriore, che di solito offre prestazioni di scatto decisamente migliori rispetto alla fotocamera anteriore.

Per attivare la modalità di visualizzazione posteriore, gli utenti rispondono a una finestra di dialogo per consentire all'app di cambiare schermata, ad esempio:

Figura 1. Finestra di dialogo di sistema per consentire l'avvio della modalità schermo posteriore.

Il sistema crea la conversazione, quindi non è richiesta alcuna azione da parte tua. Vengono visualizzate finestre di dialogo diverse a seconda dello stato del dispositivo; ad esempio, il sistema indica agli utenti di aprire il dispositivo se è chiuso. Non è possibile personalizzare la finestra di dialogo, che può variare su dispositivi di OEM diversi.

Puoi provare la modalità display posteriore con l'app Fotocamera di Pixel Fold. Consulta un'implementazione di esempio nel codelab Ottimizzare l'app Fotocamera su dispositivi pieghevoli con Jetpack WindowManager.

Modalità doppio schermo

La modalità doppio schermo ti consente di mostrare contemporaneamente i contenuti su entrambi i display di un pieghevole. La modalità Dual Screen è disponibile su Pixel Fold con Android 14 (livello API 34) o versioni successive.

Un caso d'uso di esempio è l'interprete a doppio schermo.

Figura 2. Interprete su due schermi che mostra contenuti diversi sul display anteriore e posteriore.

Attiva le modalità in modo programmatico

Puoi accedere alla modalità display posteriore e alla modalità doppio schermo tramite le API Jetpack WindowManager, a partire dalla libreria versione 1.2.0-beta03.

Aggiungi la dipendenza WindowManager al file build.gradle del modulo dell'app:

Groovy

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

Kotlin

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

Il punto di contatto è WindowAreaController, che fornisce informazioni e comportamenti relativi allo spostamento delle finestre tra i display o tra le aree di visualizzazione su un dispositivo. WindowAreaController ti consente di eseguire query sull'elenco degli oggetti WindowAreaInfo disponibili.

Usa WindowAreaInfo per accedere a WindowAreaSession, un'interfaccia che rappresenta una funzionalità dell'area della finestra attiva. Utilizza WindowAreaSession per determinare la disponibilità di un WindowAreaCapability specifico.

Ogni funzionalità è correlata a un determinato WindowAreaCapability.Operation. Nella versione 1.2.0-beta03, Jetpack WindowManager supporta due tipi di operazioni:

Ecco un esempio di come dichiarare le variabili per la modalità di visualizzazione posteriore e la modalità dual‑screen nell'attività principale dell'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;

Ecco come inizializzare le variabili nel metodo onCreate() della tua attività:

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

Prima di iniziare un'operazione, controlla la disponibilità della funzionalità in questione:

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

Modalità doppio schermo

L'esempio seguente chiude la sessione se la funzionalità è già attiva oppure chiama la funzione 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);
    }
}

Nota l'utilizzo dell'attività principale dell'app come argomento WindowAreaPresentationSessionCallback.

L'API utilizza un approccio di ascolto: quando effettui una richiesta per presentare i contenuti all'altro display di un dispositivo pieghevole, avvii una sessione che viene restituita tramite il metodo onSessionStarted() dell'ascoltatore. Quando chiudi la sessione, ricevi una conferma nel metodo onSessionEnded().

Per creare il listener, implementa l'interfaccia WindowAreaPresentationSessionCallback:

Kotlin

class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback

Java

public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback

L'ascoltatore deve implementare i metodi onSessionStarted(), onSessionEnded(), e onContainerVisibilityChanged(). I metodi di callback ti inviano una notifica sullo stato della sessione e ti consentono di aggiornare l'app di conseguenza.

Il callback onSessionStarted() riceve un WindowAreaSessionPresenter come argomento. L'argomento è il contenitore che ti consente di accedere a un'area della finestra e di mostrare i contenuti. La presentazione può essere ignorata automaticamente dal sistema quando l'utente esce dalla finestra dell'applicazione principale oppure può essere chiusa chiamando WindowAreaSessionPresenter#close().

Per gli altri callback, per semplicità, controlla se nel corpo della funzione ci sono eventuali errori e registra lo stato:

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

Per mantenere la coerenza in tutto l'ecosistema, utilizza l'icona ufficiale Dual Screen per indicare agli utenti come attivare o disattivare la modalità Dual Screen.

Per un esempio funzionante, vedi DualScreenActivity.kt.

Modalità schermo posteriore

Analogamente all'esempio della modalità Dual Screen, il seguente esempio di funzione toggleRearDisplayMode() chiude la sessione se la funzionalità è già attiva o chiama in altro modo la funzione 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);
    }
}

In questo caso, l'attività visualizzata viene utilizzata come WindowAreaSessionCallback, che è più semplice da implementare perché il callback non riceve un presentatore che consente di mostrare i contenuti in un'area della finestra, ma trasferisce l'intera attività in un'altra area:

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

Per mantenere la coerenza nell'intero ecosistema, utilizza l'icona ufficiale della fotocamera posteriore per indicare agli utenti come attivare o disattivare la modalità di visualizzazione posteriore.

Risorse aggiuntive