Infinito Nirone 7

白羽の矢を刺すスタイル

Android の Picture in Picture と Window Focus

TL;DR

他のアプリが Picture in Picture モードに入り、そのオーバーレイ表示が自分のアプリに重なる場合、自分のアプリのActivityViewonWindowFocusChanged(boolean)は次の順で計3回呼び出されます。

  1. onWindowFocusChanged(true): タスクが他のアプリから自分のアプリに切り替わったとき
  2. onWindowFocusChanged(false): Picture in Picture の表示(アニメーション)が完了してフォーカスがその表示に奪われたとき
  3. onWindowFocusChanged(true): 2からおよそ3〜4秒後に自分のアプリにフォーカスが戻ったとき

なお Android P Beta3 on Essential Phone と Android P on Essential Phone で確認しています。

Picture in Picture とは

Picture in Picture (PiP) は Android O で登場した機能で、アプリの画面 (Activity) を他のアプリに重ねてスクリーンの隅に小さく表示する機能です。たとえば Google Maps でナビゲーションをしているときに他のアプリに切り替えると、ナビゲーションの地図が自動で縮小されてスクリーンの隅に表示されるような機能を指して Picture in Picture と呼んでいます。

この機能はActivityを縮小すると同時にその縮小表示を他のアプリに重ねるため、Runtime Permissions とは別の権限を必要とします。インストールと同時に許可がおりるので普段はあまり気にすることはありませんが、Picture in Picture の表示やシステムの設定画面から、Picture in Picture の権限を取り消すことができます。

Window Focus とは

ざっくりいうと、KeyEvent や TouchEvent を受け付けるWindowを管理する仕組みです。

WindowそのものについてはAndroid を支える技術〈Ⅰ〉に解説がありますが、基本的には各ActivityはひとつのWindowに属し、WindowManagerの管理下に置かれるようになっています。WindowManagerに直接自分でViewを登録する方法もあり、この場合登録したViewは新たなWindowに属し、WindowManagerの管理下に入ります。つまり一つのアプリケーションで複数のWindowが存在し得ます。

Picture in Picture に入る処理

AndroidManifestでPicture in Pictureをサポートしている宣言をし、アプリケーションがActivity#enterPictureInPictureMode(PictureInPictureParams)を呼び出すとPicture in Pictureモードに入ります。このときの処理の実体は(ActivityManagerService#enterPictureInPictureMode(IBinder, PictureInPictureParams))https://android.googlesource.com/platform/frameworks/base.git/+/android-cts-8.1_r7/services/core/java/com/android/server/am/ActivityManagerService.java#8089にあります。おおよそ次のような順で Picture in Picture モードに入る処理を呼び出します。

  1. すでに Picture in Picture モードの場合は何もしない
  2. Activity が Picture in Picture モードをサポートしない場合も何もしない
  3. KeyGuard が表示されている場合は解除を試み、成功したら Picture in Picture モードに入る
  4. KeyGuard が表示されていないければ即座に Picture in Picture モードに入る

この処理の中の"Picture in Picture モードに入る"部分にあたるのはActivityStackSupervisor#moveActivityToPinnedStackLocked(ActivityRecord, Rect, float, boolean, String)です。 ここではフォアグラウンドにいた Activity を Picture in Picture モードにするため、画面のリサイズに関する計算やアニメーションの再生をしています。 このメソッドの最後で Picture in Picture の状態を監視しているリスナーに、Picture in Picture モードに入ったことを知らせています。

Picture in Picture モードに入ったかどうかを監視するリスナーのひとつに、PipManagerがあります。 このPipManagerはハンドセット端末用のものと、TV 端末用のものとがそれぞれ別に存在します。

ハンドセット端末用のPipManagerは、Picture in Picture の表示を管理する PipMenuActivityControllerや、タッチのインタラクションを制御するPipMotionHelperPipTouchHandlerなどへ処理を委譲します。

Picture in Picture に入ってからの処理

Picture in Picture の表示は SystemUI の PipMenuActivity が担当しています。このActivityは WindowFocus を取得してから 3500 ミリ秒後に、Picture in Picture のメニュー(閉じるボタンや設定ボタンなど)を非表示にします。このタイミングでPipMenuActivityから Window Focus が失われ、その時フォアグラウンドにいるアプリの Activity に Window Focus が移動します。冒頭のTL;DRに書いた"およそ3~4秒後"というのはまさに、この 3500 ミリ秒後にメニューを非表示にする処理によるものです。

Picture in Picture に入ったとき以外にも、Picture in Picture モードの表示をタップしてすこし拡大したときにもPipMenuActivityが立ち上がって WindowFocus を奪います。よって、ソフトキーボードが出ている状態で Picture in Picture の表示をタップするとソフトキーボードが閉じるというわけです。