Mendukung mode tampilan perangkat foldable

Perangkat foldable menawarkan pengalaman tampilan yang unik. Mode tampilan belakang dan mode layar ganda memungkinkan Anda membuat fitur tampilan khusus untuk perangkat foldable seperti pratinjau selfie kamera belakang dan tampilan yang bersamaan tetapi berbeda di layar dalam dan luar.

Mode tampilan belakang

Saat perangkat foldable dibentangkan, biasanya hanya layar dalam yang aktif. Dengan mode tampilan belakang, Anda dapat memindahkan aktivitas ke layar luar perangkat foldable, yang biasanya tidak menghadap ke pengguna saat perangkat dibentangkan. Layar dalam akan otomatis dinonaktifkan.

Penerapan terbaru mode ini adalah menampilkan pratinjau kamera di layar luar, sehingga pengguna dapat mengambil foto selfie dengan kamera belakang, yang biasanya menghasilkan gambar yang jauh lebih baik daripada kamera depan.

Untuk mengaktifkan mode tampilan belakang, pengguna perlu merespons dialog yang akan mengizinkan aplikasi beralih layar, misalnya:

Gambar 1. Dialog sistem yang memungkinkan mode tampilan belakang dimulai.

Sistem akan membuatkan dialognya, jadi Anda tidak perlu melakukan pengembangan apa pun. Berbagai dialog akan muncul bergantung status perangkat. Misalnya, sistem akan mengarahkan pengguna untuk membentangkan perangkat jika perangkat ditutup. Anda tidak dapat menyesuaikan dialog, dan ketentuan ini dapat bervariasi di perangkat dari OEM yang berbeda.

Anda dapat mencoba mode tampilan belakang dengan aplikasi kamera Pixel Fold. Lihat contoh implementasi di codelab Mengoptimalkan aplikasi kamera di perangkat foldable dengan Jetpack WindowManager.

Mode layar ganda

Mode layar ganda memungkinkan Anda menampilkan konten di kedua layar perangkat foldable secara bersamaan. Mode layar ganda tersedia di Pixel Fold yang menjalankan Android 14 (level API 34) atau yang lebih tinggi.

Contoh kasus penggunaan adalah penerjemah layar ganda.

Gambar 2. Penerjemah layar ganda yang menampilkan konten berbeda di layar depan dan belakang.

Mengaktifkan mode secara terprogram

Anda dapat mengakses mode tampilan belakang dan mode layar ganda melalui Jetpack WindowManager API, mulai dari library versi 1.2.0-beta03.

Tambahkan dependensi WindowManager ke file build.gradle modul aplikasi Anda:

Groovy

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

Kotlin

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

Titik entrinya adalah WindowAreaController, yang memberikan informasi dan perilaku terkait perpindahan jendela antar-layar atau antar area tampilan pada perangkat. WindowAreaController memungkinkan Anda meng-kueri daftar objek WindowAreaInfo yang tersedia.

Gunakan WindowAreaInfo untuk mengakses WindowAreaSession, antarmuka yang mewakili fitur area jendela aktif. Gunakan WindowAreaSession untuk menentukan ketersediaan WindowAreaCapability tertentu.

Setiap kemampuan terkait dengan WindowAreaCapability.Operation tertentu. Di versi 1.2.0-beta03, Jetpack WindowManager mendukung dua jenis operasi:

Berikut contoh cara mendeklarasikan variabel untuk mode tampilan belakang dan mode layar ganda dalam aktivitas utama aplikasi:

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;

Berikut cara melakukan inisialisasi variabel dalam metode onCreate() aktivitas Anda:

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

Sebelum memulai operasi, periksa ketersediaan kemampuan tertentu:

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 layar ganda

Contoh berikut menutup sesi jika kemampuan sudah aktif, atau memanggil fungsi 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);
    }
}

Perhatikan penggunaan aktivitas utama aplikasi sebagai argumen WindowAreaPresentationSessionCallback.

API tersebut menggunakan pendekatan pemroses: saat membuat permintaan untuk menampilkan konten ke tampilan lain perangkat foldable, Anda memulai sesi yang ditampilkan melalui metode onSessionStarted() pemroses. Saat menutup sesi, Anda akan mendapatkan konfirmasi dalam metode onSessionEnded().

Untuk membuat pemroses, implementasikan antarmuka WindowAreaPresentationSessionCallback:

Kotlin

class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback

Java

public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback

Pemroses perlu mengimplementasikan metode onSessionStarted(), onSessionEnded(), dan onContainerVisibilityChanged(). Metode callback tersebut akan memberi tahu Anda tentang status sesi dan memungkinkan Anda mengupdate aplikasi.

Callback onSessionStarted() menerima WindowAreaSessionPresenter sebagai argumen. Argumennya adalah penampung yang memungkinkan Anda mengakses area jendela dan menampilkan konten. Presentasi dapat otomatis ditutup oleh sistem saat pengguna menutup jendela aplikasi utama, atau presentasi dapat ditutup dengan memanggil WindowAreaSessionPresenter#close().

Untuk callback lainnya, agar lebih mudah, cukup periksa isi fungsi untuk menemukan error, dan catat statusnya:

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

Untuk mempertahankan konsistensi di seluruh ekosistem, gunakan ikon resmi Layar Ganda untuk menunjukkan kepada pengguna cara mengaktifkan atau menonaktifkan mode layar ganda.

Untuk contoh yang berfungsi, lihat DualScreenActivity.kt.

Mode tampilan belakang

Serupa dengan contoh mode layar ganda, contoh fungsi toggleRearDisplayMode() berikut menutup sesi jika kemampuan sudah aktif, atau memanggil fungsi 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);
    }
}

Dalam hal ini, aktivitas yang ditampilkan akan digunakan sebagai WindowAreaSessionCallback, yang lebih sederhana untuk diterapkan karena callback tidak menerima presenter yang memungkinkan konten ditampilkan di area jendela, tetapi callback justru mentransfer seluruh aktivitas ke area lain:

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

Untuk mempertahankan konsistensi di seluruh ekosistem, gunakan ikon resmi Kamera Belakang untuk menunjukkan kepada pengguna cara mengaktifkan atau menonaktifkan mode tampilan belakang.

Referensi lainnya