ディスプレイ カットアウト

Android 9 には、さまざまな種類のディスプレイ カットアウトをデバイスに実装するためのサポートが追加されています。ディスプレイ カットアウトにより、デバイスの前面に重要なセンサーのスペースを確保しながら、臨場感あふれるエッジ ツー エッジのエクスペリエンスを作成できます。

上部中央のディスプレイ カットアウト

図 1:上部中央のディスプレイ カットアウト

Android 9 では、次のタイプのカットアウトがサポートされています。

  • 上部中央: 上端の中央のカットアウト
  • 中央揃えでない上部: 隅にあるか中央から少しずれているカットアウト
  • 下部: 下部のカットアウト
  • デュアル: 上部に 1 つ、下部に 1 つのカットアウト

例とソース

PhoneWindowManager.java の以下のウィンドウ マネージャー コードでは、LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS が設定されていないときに表示フレームがセーフエリアに挿入される仕組みを示しています。

// Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
// the cutout safe zone.
if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
    final Rect displayCutoutSafeExceptMaybeBars = mTmpDisplayCutoutSafeExceptMaybeBarsRect;
    displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
    if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
            && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
        // At the top we have the status bar, so apps that are
        // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
        // already expect that there's an inset there and we don't need to exclude
        // the window from that area.
        displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
    }
    if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
            && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
        // Same for the navigation bar.
        switch (mNavigationBarPosition) {
            case NAV_BAR_BOTTOM:
                displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
                break;
            case NAV_BAR_RIGHT:
                displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
                break;
            case NAV_BAR_LEFT:
                displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
                break;
        }
    }
    if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
        // The IME can always extend under the bottom cutout if the navbar is there.
        displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
    }
    // Windows that are attached to a parent and laid out in said parent already avoid
    // the cutout according to that parent and don't need to be further constrained.
    // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
    // They will later be cropped or shifted using the displayFrame in WindowState,
    // which prevents overlap with the DisplayCutout.
    if (!attachedInParent && !floatingInScreenWindow) {
        mTmpRect.set(pf);
        pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
        parentFrameWasClippedByDisplayCutout |= !mTmpRect.equals(pf);
    }
    // Make sure that NO_LIMITS windows clipped to the display don't extend under the
    // cutout.
    df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
}

SystemUI は、カットアウト領域でレンダリングし、描画できる場所を決定する必要があります。 PhoneStatusBarView.java は、ディスプレイ カットアウトの位置、大きさ、およびナビゲーション バーからの挿入がカットアウト領域を回避するかどうかを指定するビューの例を提供しています。

onApplyWindowInsets() をオーバーライドすることにより、ビューはカットアウトがどこにあるかを判断し、そのレイアウトをそれに応じて更新できます。

@Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        if (updateOrientationAndCutout(mLastOrientation)) {
            updateLayoutForCutout();
            requestLayout();
        }
        return super.onApplyWindowInsets(insets);
    }

これらのメソッドは、すべての状況(すべての回転での上部中央、中央揃えでない上部、下部、デュアル カットアウト)におけるカットアウトのステータスバーでの処理方法の概要を示します。

要件

アプリがカットアウトによる悪影響を受けないようにするには、次のことを確認してください。

  • ステータスバーは、縦表示の場合にカットアウトの高さ以上にまで拡張される
  • カットアウト領域は、全画面表示と横表示の場合にレターボックス表示される

デバイスの各短辺(上部と下部)には最大 1 つのカットアウトを設定できます。

詳細については、CDD をご覧ください。

実装

デバイスでディスプレイ カットアウトを実装するには、システム UI に次の値を設定する必要があります。

説明
quick_qs_offset_height

クイック設定パネルの上マージンを定義します。パネルの上のスペースに時計と電池が表示されます。

values-land では、status_bar_height_landscape に設定し、縦向きでは、デフォルトの 48 dp またはカットアウトの高さのいずれか大きい方に設定します。必要に応じてカットアウトより大きくすることも可能です。

quick_qs_total_height

時計のあるパネルの上のスペースを含む、通知シェードを展開したときのクイック クイック設定パネル(折りたたまれたクイック設定パネル)の合計の高さ。

クイック設定のレイアウトの仕方により、(オフセットを含む)クイック クイック設定パネルの合計の高さは静的に既知である必要があります。したがって、この値は同じデルタ quick_qs_offset_height で調整する必要があります。この値は、values-land では 152 dp に、縦向きでは 176 dp にデフォルトで設定されます。

status_bar_height_portrait

フレームワークの観点からのステータスバーのデフォルトの高さ。

ほとんどのデバイスでは、この値はデフォルトで 24 dp に設定されます。カットアウトがある場合は、この値をカットアウトの高さに設定します。必要に応じてカットアウトより大きくすることも可能です。

status_bar_height_landscape

横向きのステータスバーの高さ。カットアウトはデバイスの短辺でのみサポートされるため、ステータスバーの高さは常に不変です。

カットアウトのないデバイスでは、これは status_bar_height_portrait と同じです。カットアウトがある場合、この値はデフォルトのステータスバーの高さで維持されます。

config_mainBuiltInDisplayCutout

カットアウトの形を定義するパス。これは、android.util.PathParser により解析可能な文字列で、カットアウトのサイズと形がシステムにどのように定義されるかを示します。

@dp は、パスで指定して、異なるデバイスをターゲットにする形をエミュレートできます。実際のカットアウトには正確なピクセルサイズがあるため、ハードウェア ノッチのパスを定義するときに @dp 指定子を使用しないでください。

config_fillMainBuiltinDisplayCutout

上記で定義されたカットアウト パスをソフトウェアで描画するかどうかを指定するブール値。カットアウトをエミュレートしたり、実際のカットアウトを埋め込んだりする際に使用してアンチ エイリアスを実現できます。

true の場合、config_mainBuiltInDisplayCutout は黒で塗りつぶされています。

デフォルトの定義については、これらの dimens ファイルをご覧ください。

エミュレートされたカットアウトのオーバーレイの例:

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">

    <!-- The bounding path of the cutout region of the main built-in display.
         Must either be empty if there is no cutout region, or a string that is parsable by
         {@link android.util.PathParser}.

         The path is assumed to be specified in display coordinates with pixel units and in
         the display's native orientation, with the origin of the coordinate system at the
         center top of the display.

         To facilitate writing device-independent emulation overlays, the marker `@dp` can be
         appended after the path string to interpret coordinates in dp instead of px units.
         Note that a physical cutout should be configured in pixels for the best results.
         -->
    <string translatable="false" name="config_mainBuiltInDisplayCutout">
        M 0,0
        L -48, 0
        L -44.3940446283, 36.0595537175
        C -43.5582133885, 44.4178661152 -39.6, 48.0 -31.2, 48.0
        L 31.2, 48.0
        C 39.6, 48.0 43.5582133885, 44.4178661152 44.3940446283, 36.0595537175
        L 48, 0
        Z
        @dp
    </string>

    <!-- Whether the display cutout region of the main built-in display should be forced to
         black in software (to avoid aliasing or emulate a cutout that is not physically existent).
     -->
    <bool name="config_fillMainBuiltInDisplayCutout">true</bool>

    <!-- Height of the status bar -->
    <dimen name="status_bar_height_portrait">48dp</dimen>
    <dimen name="status_bar_height_landscape">28dp</dimen>
    <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
    <dimen name="quick_qs_offset_height">48dp</dimen>
    <!-- Total height of QQS (quick_qs_offset_height + 128) -->
    <dimen name="quick_qs_total_height">176dp</dimen>

</resources>

検証

ディスプレイ カットアウトの実装を検証するには、tests/framework/base/windowmanager/src/android/server/wm で CTS テストを実行します。