home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

日記/2011/11/27/Android4.0(ICS)の画面キャプチャ機能(続報)

日記/2011/11/27/Android4.0(ICS)の画面キャプチャ機能(続報)

日記 / 2011 / 11 / 27 / Android4.0(ICS)の画面キャプチャ機能(続報)
id: 1038 所有者: msakamoto-sf    作成日: 2011-11-27 18:30:30
カテゴリ: Android 

ICSのソース全体がrepo syncでget出来たので、 日記/2011/10/23/Android4.0(IceCreamSandwich)の画面キャプチャ機能 で気になってた実際のソースを探検してみました。

といっても、かなり主観と偏見に基づいて、ざっくりとした流れだけを追ってます。もしかしたら漏れがあるかもしれないし、権限周りなどセキュリティ的な視点は後回しにしてます。

先に三行でまとめ:

  1. /system/bin/screenshot (前回失敗) → /dev/graphics/fb0 から手動で読み込んでるのが原因か。
  2. /system/bin/screencap (前回成功) → ScreenshotClientクラスのupdate()が成功すれば、getPixels()で読み出している。update()が失敗なら /dev/graphics/fb0 から手動で読み込み。
  3. サービスとして:TakeScreenshotService → GlobalScreenshot : GlobalScreenshotをServiceとしてラップしているのがTaskScreenshotService。GlobalScreenshotの中でスクリーンショットを取得するときのアニメーション処理などを処理しているっぽいので、多分VolDown + Powerでのスクリーンショット取得で動作するのはこのコードで合ってるはず。最終的にはscreencapコマンドと同様、ScreenshotClientクラスのupdate()→OpenGLのglReadPixels()でスクリーンショットを取得している。

もう少し詳しく流れをメモしていきます。ちなみにソースコード検索にはmilkode使いました。さすがにICSのソース全体をmilkodeに載せるのには数時間かかり、検索も十数秒かかってしまいました。(こういう時こそAmazon EC2とか使うといいのかも。)

/system/bin/screenshot

ソース: frameworks/base/cmds/screenshot/screenshot.c

流れ:

  1. /dev/graphics/fb0 を開いて、
  2. AID_LOG, AID_SDCARD_RW にsetgroups()して、
  3. setuid(AID_SHELL) して、
  4. fb0から適当に読み込んでlibpngでPNGに変換、保存してます。

興味深いのはsetgroups(2)とかsetuid(2)してるというところでしょうか。すなわち、screenshotの実行ファイルはrootにsetuidされてると思われます。

/system/bin/screencap

ソース: frameworks/base/cmds/screencap/screencap.cpp

流れ:上に書いたとおり、ScreenshotClientクラスのupdate()が成功すればそちらから、失敗すれば /dev/graphics/fb0 から読んでます。

TakeScreenshotService → GlobalScreenshot

ソース:

  • frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
  • frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java

流れ:

  1. TakeScreenshotServiceはServiceクラスから派生したサービスの器。
  2. 実際の処理はGlobalScreenshotで処理。GlobalScreenshotのtakeScreenshot()で、
    1. android.view.Surface.screenshot()でbitmap取得
    2. startAnimation()の中で
      1. createScreenshotDropInAnimation(), createScreenshotDropOutAnimation() でスクリーンショット取得時のアニメーションを処理
      2. バックグランドタスクでbitmapをファイルに保存(SaveImageInBackgroundTask)

実際にbitmapを取得しているのはandroid.view.Surfaceクラスのscreenshot()メソッドであることが分かりました。

android.view.Surfaceクラスのscreenshot()メソッド

ソース:frameworks/base/core/java/android/view/Surface.java

宣言は省略しますが、screenshot()メソッドは public static native 、つまりJNIとして実装されていることが分かりました。さらにJavaDocで"@hide"が指定されているので、いわゆる隠しAPIとなっていました。

JNIのソース:frameworks/base/core/jni/android_view_Surface.cpp

screenshot()の中身は static 関数であるdoScreenshot()で実装されていました。

さらにdoScreenshot()では同ファイル内で宣言されているScreenshotPixelRefクラスのupdate()メソッドでbitmapデータを取得しており、
さらにScreenshotPixelRefクラスでは最終的にScreenshotClientクラスのupdate()メソッドを使っていました。

ヤヤコシイので流れをまとめます:

TaskScreenshotService.java
  -> GlobalScreenshot.java
    -> Surface.screenshot()
      -> doScreenshot() in android_view_Surface.cp
        -> (ScreenshotPixelRef -> ) ScreenshotClient#update()

ちなみに、screencapコマンドもScreenshotClientクラスのupdate()を呼んでいますので、多分これで合ってるはずです。

ScreenshotClientクラスのupdate()メソッド

ScreenshotClientクラスの定義:
frameworks/base/include/surfaceflinger/SurfaceComposerClient.h

ScreenshotClientクラスの実装:
frameworks/base/libs/gui/SurfaceComposerClient.cpp

で、update()メソッドなんですが

sp<ISurfaceComposer> s(ComposerService::getComposerService());

のようにこれまたサービス経由でインターフェイスを取得して、

return s->captureScreen(...);

みたいにしてます。実装を分離してるんですね。じゃぁ実装はどこだ、で"captureScreen"をmilkodeで検索してみると今のところ次のファイルが実装みたいです。

ソース:frameworks/base/services/surfaceflinger/SurfaceFlinger.cpp

SurfaceFlinger::captureScreen() メソッドをナナメ読みすると、実際のbitmap取得処理はcaptureScreenImplLocked()メソッドぽいです。これも同じファイルに実装されてます。
captureScreenImplLocked()をナナメ読みすると、OpenGLの関数を沢山呼んでいて、最終的にここでスクリーンショットのデータを取得している模様です。

// capture the screen with glReadPixels()
glReadPixels(0, 0, sw, sh, GL_RGBA, GL_UNSIGNED_BYTE, ptr);

まとめ:

TaskScreenshotService.java
  -> GlobalScreenshot.java
    -> Surface.screenshot()
      -> doScreenshot() in android_view_Surface.cp
        -> (ScreenshotPixelRef -> ) ScreenshotClient::update()
          -> SurfaceFlinger::captureScreen()
            -> SurfaceFlinger::captureScreenImplLocked()
              -> glReadPixels() (OpenGL)

あとはOpenGLの中の話になるので、ここで終点となります。

screencapが動作してscreenshotが動作しないのは、OpenGLからか、/dev/graphics/fb0 からか、の違いが影響してそうです。

暇があればpermisssionで何か制限してるのかとか調べたいです。


プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2011-11-27 19:28:55
md5:2a25a032b54026984fbec012daeee48c
sha1:0524bdf9339900e615aefaafccae035041fa6405
コメント
コメントを投稿するにはログインして下さい。