ICSのソース全体がrepo syncでget出来たので、 [[1028]] で気になってた実際のソースを探検してみました。 といっても、かなり主観と偏見に基づいて、ざっくりとした流れだけを追ってます。もしかしたら漏れがあるかもしれないし、権限周りなどセキュリティ的な視点は後回しにしてます。 先に三行でまとめ: + /system/bin/screenshot (前回失敗) → /dev/graphics/fb0 から手動で読み込んでるのが原因か。 + /system/bin/screencap (前回成功) → ScreenshotClientクラスのupdate()が成功すれば、getPixels()で読み出している。update()が失敗なら /dev/graphics/fb0 から手動で読み込み。 + サービスとして:TakeScreenshotService → GlobalScreenshot : GlobalScreenshotをServiceとしてラップしているのがTaskScreenshotService。GlobalScreenshotの中でスクリーンショットを取得するときのアニメーション処理などを処理しているっぽいので、多分VolDown + Powerでのスクリーンショット取得で動作するのはこのコードで合ってるはず。最終的にはscreencapコマンドと同様、ScreenshotClientクラスのupdate()→OpenGLのglReadPixels()でスクリーンショットを取得している。 #more|| もう少し詳しく流れをメモしていきます。ちなみにソースコード検索にはmilkode使いました。さすがにICSのソース全体をmilkodeに載せるのには数時間かかり、検索も十数秒かかってしまいました。(こういう時こそAmazon EC2とか使うといいのかも。) * /system/bin/screenshot ソース: frameworks/base/cmds/screenshot/screenshot.c 流れ: + /dev/graphics/fb0 を開いて、 + AID_LOG, AID_SDCARD_RW にsetgroups()して、 + setuid(AID_SHELL) して、 + 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 流れ: + TakeScreenshotServiceはServiceクラスから派生したサービスの器。 + 実際の処理はGlobalScreenshotで処理。GlobalScreenshotのtakeScreenshot()で、 ++ android.view.Surface.screenshot()でbitmap取得 ++ startAnimation()の中で +++ createScreenshotDropInAnimation(), createScreenshotDropOutAnimation() でスクリーンショット取得時のアニメーションを処理 +++ バックグランドタスクで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 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で何か制限してるのかとか調べたいです。