システム デコレーションのサポート

ディスプレイ固有の領域に対する更新内容は次のとおりです。

システム デコレーション

Android 10 では、特定のシステム デコレーション(壁紙、ナビゲーション バー、ランチャーなど)を表示するようにセカンダリ ディスプレイを設定できるようになりました。デフォルトで、プライマリ ディスプレイにはすべてのシステム デコレーションが表示され、セカンダリ ディスプレイには必要に応じて有効化した項目が表示されます。インプット メソッド エディタ(IME)のサポートは、他のシステム デコレーションとは別に設定できます。

DisplayWindowSettings#setShouldShowSystemDecorsLocked() を使用して特定のディスプレイにシステム デコレーションのサポートを追加するか、/data/system/display_settings.xml でデフォルト値を指定します。例については、ディスプレイのウィンドウ設定をご覧ください。

実装

DisplayWindowSettings#setShouldShowSystemDecorsLocked() は、テスト用に WindowManager#setShouldShowSystemDecors() でも公開されています。システム デコレーションを有効にする目的でこのメソッドをトリガーしても、以前に存在していないデコレーション ウィンドウが追加されたり、既存のデコレーション ウィンドウが削除されたりすることはありません。ほとんどの場合、システム デコレーションのサポートに対する変更は、デバイスの再起動後にのみ有効になります。

通常、WindowManager コードベースでのシステム デコレーション サポートのチェックは DisplayContent#supportsSystemDecorations() によって行われ、外部サービス(ナビゲーション バーを表示する必要があるかどうかを確認するシステム UI など)のチェックには WindowManager#shouldShowSystemDecors() が使用されます。この設定で制御される対象を理解するには、これらのメソッドの呼び出しポイントを確認します。

システム UI デコレーション ウィンドウ

Android 10 では、アクティビティとアプリの間の移動にナビゲーション バーの操作が不可欠なため、ナビゲーション バーのみに対してシステム デコレーション ウィンドウのサポートが追加されました。デフォルトで、ナビゲーション バーには [戻る] と [ホーム] のアフォーダンスが表示されます。これは、ターゲット ディスプレイがシステム デコレーションに対応している場合にのみ組み込まれます(DisplayWindowSettings を参照)。

ステータスバーは、通知シェード、クイック設定、ロック画面を含むため、より複雑なシステム ウィンドウです。Android 10 では、ステータスバーはセカンダリ ディスプレイではサポートされていません。したがって、通知、設定、フルキーガードはプライマリ ディスプレイでのみ使用できます。

[最近] システム ウィンドウはセカンダリ画面ではサポートされていません。Android 10 では、AOSP によってデフォルト ディスプレイにのみ [最近] が表示され、すべてのディスプレイのアクティビティが含まれます。[最近] から起動すると、セカンダリ ディスプレイのアクティビティはデフォルトでそのディスプレイの前面に表示されます。この方法には、アプリが他の画面に表示されたときにすぐに更新されないなどの既知の問題があります。

実装

システム UI の追加機能を実装するには、デバイス メーカーが、ディスプレイの追加と削除をリッスンして適切なコンテンツを表示する 1 つのシステム UI コンポーネントを使用している必要があります。

マルチディスプレイ(MD)をサポートするシステム UI コンポーネントは、次のケースに対応します。

  • 起動時のマルチ ディスプレイの初期化
  • 実行時に追加されるディスプレイ
  • 実行時に削除されるディスプレイ

システム UI が WindowManager より先にディスプレイの追加を検出すると、競合状態が発生します。これを回避するには、ディスプレイが追加されたときに、DisplayManager.DisplayListener イベントに登録するのではなく、WindowManager からシステム UI へのカスタム コールバックを実装します。リファレンス実装については、ナビゲーション バーのサポートは CommandQueue.Callbacks#onDisplayReady、壁紙は WallpaperManagerInternal#onDisplayReady をご覧ください。

また、Android 10 では次のアップデートが提供されています。

  • NavigationBarController クラスはナビゲーション バーに固有のすべての機能を制御します。
  • カスタマイズされたナビゲーション バーを確認するには、CarStatusBar をご覧ください。
  • TYPE_NAVIGATION_BAR は 1 つのインスタンスに制限されなくなったため、ディスプレイごとに使用できます。
  • IWindowManager#hasNavigationBar() はシステム UI の displayId パラメータのみを含むように更新されました。

ランチャー

Android 10 では、システム デコレーションをサポートするように設定された各ディスプレイに、デフォルトでタイプ WindowConfiguration#ACTIVITY_TYPE_HOME のランチャー アクティビティ専用のホームスタックがあります。各ディスプレイは、ランチャー アクティビティの個別のインスタンスを使用します。

図 1:platform/development/samples/MultiDisplay のマルチディスプレイのランチャー例

既存のランチャーのほとんどは複数のインスタンスをサポートしておらず、大画面サイズに対して最適化されません。また、セカンダリ ディスプレイまたは外部ディスプレイでは、さまざまな場面でエクスペリエンスが異なることが想定されます。セカンダリ画面に専用のアクティビティを提供するために、Android 10 ではインテント フィルタに SECONDARY_HOME カテゴリが導入されています。このアクティビティのインスタンスは、システム デコレーションをサポートするすべてのディスプレイで使用されます(ディスプレイごとに 1 つ)。

<activity>
    ...
    <intent-filter>
        <category android:name="android.intent.category.SECONDARY_HOME" />
        ...
    </intent-filter>
</activity>

アクティビティには、複数のインスタンスを妨げず各種画面サイズに適応可能な起動モードが必要です。起動モードを singleInstance または singleTask に設定することはできません。

実装

Android 10 では、ホーム画面が起動されるディスプレイに応じて、RootActivityContainer#startHomeOnDisplay() によって必要なコンポーネントとインテントが自動的に選択されます。RootActivityContainer#resolveSecondaryHomeActivity() には、現在選択されているランチャーに応じてランチャー アクティビティ コンポーネントを検索するロジックがあり、必要に応じてシステムのデフォルトが使用されます(ActivityTaskManagerService#getSecondaryHomeIntent() を参照)。

セキュリティ制限

セカンダリ ディスプレイのアクティビティに適用される制限に加え、悪意のあるアプリがシステム デコレーションを有効にした仮想ディスプレイを作成し、サーフェスからユーザー機密情報を読み取るリスクを回避するため、ランチャーはシステム所有の仮想ディスプレイにのみ表示されます。ランチャーは、システム以外の仮想ディスプレイにコンテンツを表示しません。

壁紙

Android 10 以降では、セカンダリ ディスプレイで壁紙がサポートされます。

図 2.内部ディスプレイ(上)と外部ディスプレイ(下)のライブ壁紙

デベロッパーは WallpaperInfo XML 定義で android:supportsMultipleDisplays="true" を指定して、壁紙機能のサポートを宣言できます。また、壁紙デベロッパーは WallpaperService.Engine#getDisplayContext() のディスプレイ コンテキストを使用してアセットを読み込むことが求められます。

フレームワークによってディスプレイごとに 1 つの WallpaperService.Engine インスタンスが作成されるため、各エンジンには独自のサーフェスとディスプレイ コンテキストがあります。デベロッパーは、各エンジンが VSYNC に応じて異なるフレームレートで個別に描画できることを確認する必要があります。

画面ごとの壁紙の選択

Android 10 では、各画面の壁紙を選択するための直接のプラットフォーム サポートは提供されていません。選択できるようにするには、ディスプレイごとに壁紙設定を維持するためにディスプレイの固定 ID が必要です。Display#getDisplayId() は動的なので、再起動後に物理ディスプレイの ID が同じであるという保証はありません。

ただし、Android 10 で追加された DisplayInfo.mAddress には、物理ディスプレイの固定 ID が含まれており、今後の完全な実装に使用できます。残念ながら、Android 10 にロジックを実装するには遅すぎます。推奨される解決策は次のとおりです。

  1. WallpaperManager API を使用して壁紙を設定します。
  2. WallpaperManagerContext オブジェクトから取得され、各 Context オブジェクトには対応するディスプレイに関する情報が含まれています(Context#getDisplay()/getDisplayId())。したがって、新しいメソッドを追加しなくても、WallpaperManager インスタンスから displayId を取得できます。
  3. フレームワーク側では、Context オブジェクトから取得した displayId を使用して静的 ID(物理ディスプレイのポートなど)にマッピングします。静的 ID を使用して、選択した壁紙を保持します。

この回避策では、壁紙選択ツールの既存の実装を使用します。ツールを特定のディスプレイで開いて適切なコンテキストを使用した場合、壁紙の設定が呼び出されると、自動的にディスプレイが特定されます。

現在のディスプレイ以外の壁紙を設定する必要がある場合は、ターゲット ディスプレイの新しい Context オブジェクトを作成して(Context#createDisplayContext)、そのディスプレイから WallpaperManager インスタンスを取得します。

セキュリティ制限

システムが所有していない仮想ディスプレイには壁紙は表示されません。 これは、セキュリティ上の懸念として、悪意のあるアプリがシステム デコレーション サポートを有効にした仮想ディスプレイを作成し、サーフェスからユーザー機密情報(個人の写真など)を読み取る可能性があるためです。

実装

Android 10 では、IWallpaperConnection#attachEngine() インターフェースと IWallpaperService#attach() インターフェースが displayId パラメータを受け取って、ディスプレイごとの接続を作成します。WallpaperManagerService.DisplayConnector はディスプレイごとの壁紙エンジンと接続をカプセル化します。WindowManager では、すべてのディスプレイに 1 つの WallpaperController ではなく、各 DisplayContent オブジェクトの作成時に壁紙コントローラが作成されます。

一部のパブリック WallpaperManager メソッドの実装(WallpaperManager#getDesiredMinimumWidth() など)は、対応するディスプレイの情報を計算して提供するように更新されました。WallpaperInfo#supportsMultipleDisplays() および対応するリソース属性が追加され、アプリ デベロッパーは複数の画面で利用できる壁紙を通知できるようになりました。

デフォルトのディスプレイに表示された壁紙サービスが複数のディスプレイに対応していない場合、システムはデフォルトの壁紙をセカンダリ ディスプレイに表示します。

図 3. セカンダリ ディスプレイの壁紙フォールバック ロジック