折りたたみ式ディスプレイ モードをサポートする

折りたたみ式デバイスでは、折りたたみ式ならではの視聴エクスペリエンスを実現できます。背面ディスプレイ モードと デュアル スクリーン モードでは、折りたたみ式デバイス向けの特別なディスプレイ機能を構築可能 背面カメラ セルフィー プレビューや、同時ではあるものの異なる画面 内外のディスプレイに表示されます。

背面ディスプレイ モード

通常、折りたたみ式デバイスを開くと、内側の画面のみがアクティブになります。 背面ディスプレイ モードでは、アクティビティを折りたたみ式の外側の画面に移動できます (通常は、デバイスを開いた状態ではユーザーの反対側を向く)「 インナー ディスプレイが自動的にオフになります。

新しいアプリは、外側の画面にカメラのプレビューを表示するというものです。 背面カメラで自撮り写真を撮影できるため、通常は背面カメラで 前面カメラより高精細な写真を撮影できます。

背面ディスプレイ モードを有効にするには、ユーザーがダイアログに応答して、アプリに以下の操作を許可します。 画面を切り替えます。たとえば、

図 1. 背面ディスプレイ モードを有効にするシステム ダイアログ

ダイアログはシステムによって作成されるため、デベロッパー側での開発は必要ありません。 デバイスの状態に応じて異なるダイアログが表示されます。たとえば、デバイスが閉じている場合は、デバイスを開くようシステムがユーザーに指示します。カスタマイズはできません デバイスに応じて OEM ごとに異なる場合があります。

Google Pixel Fold のカメラアプリで背面ディスプレイ モードを試すことができます。Codelab のJetpack WindowManager を使用して折りたたみ式デバイスのカメラアプリを最適化するの実装例をご覧ください。

デュアル スクリーン モード

デュアル スクリーン モードでは、折りたたみ式デバイスの両方のディスプレイに同時にコンテンツを表示できます。デュアル スクリーン モードは Android 14 を搭載した Google Pixel Fold で利用できます (API レベル 34)以降が必要です。

ユースケースの一例として、デュアル スクリーンの通訳があります。

図 2. 前面ディスプレイと背面ディスプレイに異なるコンテンツを表示するデュアル スクリーンの通訳

モードをプログラマティックに有効にする

ライブラリ バージョン 1.2.0-beta03 以降では、Jetpack WindowManager API を使用して、背面ディスプレイ モードとデュアル スクリーン モードにアクセスできます。

アプリのモジュール build.gradle ファイルに WindowManager の依存関係を追加します。

Groovy

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

Kotlin

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

エントリ ポイントは WindowAreaController です。これにより、 ディスプレイ間やディスプレイ間のウィンドウの移動に関する情報や動作 デバイスの表示領域ですWindowAreaController を使用すると、使用可能な WindowAreaInfo オブジェクトのリストを確認できます。

WindowAreaInfo を使用して、WindowAreaSession というインターフェースにアクセスします。 アクティブなウィンドウ領域機能を表します。WindowAreaSession を使用して決定する 特定の WindowAreaCapability の可用性。

各 capability は特定の WindowAreaCapability.Operation に関連付けられています。バージョン 1.2.0-beta03 では、Jetpack WindowManager は次の 2 種類のオペレーションをサポートしています。

背面ディスプレイ モードの変数を宣言する方法の例と、 デュアル スクリーン モードをアプリのメイン アクティビティで使用する必要があります。

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;

アプリケーションの onCreate() メソッドで変数を初期化する方法は次のとおりです。 アクティビティ:

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

オペレーションを開始する前に、特定の capability について使用可否を確認します。

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

デュアル スクリーン モード

次の例では、ケーパビリティがすでにアクティブになっている場合にセッションを閉じます。 それ以外の場合は、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);
    }
}

ここでは、アプリのメイン アクティビティを WindowAreaPresentationSessionCallback 引数。

この API はリスナー アプローチを使用します。折りたたみ式デバイスの反対側のディスプレイにコンテンツを表示するリクエストを行うと、リスナーの onSessionStarted() メソッドを通じて返されるセッションが開始されます。セッションを閉じると、onSessionEnded() メソッドに確認メッセージが表示されます。

リスナーを作成するには、WindowAreaPresentationSessionCallback インターフェースを実装します。

Kotlin

class MainActivity : AppCompatActivity(), windowAreaPresentationSessionCallback

Java

public class MainActivity extends AppCompatActivity implements WindowAreaPresentationSessionCallback

リスナーは onSessionStarted()onSessionEnded(), を実装する必要があります。 および onContainerVisibilityChanged() メソッドを使用します。このコールバック メソッドが 通知が表示され、それに応じてアプリをアップデートできます。

onSessionStarted() コールバックは、WindowAreaSessionPresenter を 渡します。この引数は、ウィンドウ領域にアクセスできるようにするコンテナです。 表示されます。プレゼンテーションは、ユーザーがメインのアプリケーション ウィンドウを閉じたときに、システムによって自動的に閉じることができます。または、WindowAreaSessionPresenter#close() を呼び出してプレゼンテーションを閉じることもできます。

他のコールバックでは、わかりやすくするために、関数本体でエラーを確認し、状態をログに記録します。

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

エコシステム全体で一貫性を保つため、デュアル スクリーンの公式アイコンを使用して、デュアル スクリーン モードを有効または無効にする方法をユーザーに示します。

実際のサンプルについては、DualScreenActivity.kt をご覧ください。

背面ディスプレイ モード

デュアル スクリーン モードの例と同様に、次の toggleRearDisplayMode() 関数の例では、capability がすでにアクティブな場合はセッションを閉じます。そうでない場合は 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);
    }
}

この場合、表示されるアクティビティが WindowAreaSessionCallback として使用されます。 コールバックはプレゼンターを受け取る必要がないため、実装が簡単です。 ウィンドウ領域にコンテンツを表示できますが、 移動することもできます。

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

エコシステム全体で一貫性を保つため、Rear Camera(背面カメラ)公式の アイコンで、背面ディスプレイ モードを有効または無効にする方法をユーザーに示します。

参考情報