ICSのソース全体がrepo syncでget出来たので、 日記/2011/10/23/Android4.0(IceCreamSandwich)の画面キャプチャ機能 で気になってた実際のソースを探検してみました。
といっても、かなり主観と偏見に基づいて、ざっくりとした流れだけを追ってます。もしかしたら漏れがあるかもしれないし、権限周りなどセキュリティ的な視点は後回しにしてます。
先に三行でまとめ:
もう少し詳しく流れをメモしていきます。ちなみにソースコード検索にはmilkode使いました。さすがにICSのソース全体をmilkodeに載せるのには数時間かかり、検索も十数秒かかってしまいました。(こういう時こそAmazon EC2とか使うといいのかも。)
ソース: frameworks/base/cmds/screenshot/screenshot.c
流れ:
興味深いのはsetgroups(2)とかsetuid(2)してるというところでしょうか。すなわち、screenshotの実行ファイルはrootにsetuidされてると思われます。
ソース: frameworks/base/cmds/screencap/screencap.cpp
流れ:上に書いたとおり、ScreenshotClientクラスのupdate()が成功すればそちらから、失敗すれば /dev/graphics/fb0 から読んでます。
ソース:
流れ:
実際にbitmapを取得しているのは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クラスの定義:
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で何か制限してるのかとか調べたいです。
コメント