From 5c11852110eeb03dc5a69111354b383f98d15336 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Tue, 21 Oct 2008 07:00:00 -0700 Subject: Initial Contribution --- simulator/app/Android.mk | 130 ++ simulator/app/AssetStream.h | 102 ++ simulator/app/DeviceManager.cpp | 1220 ++++++++++++++++ simulator/app/DeviceManager.h | 284 ++++ simulator/app/DeviceWindow.cpp | 264 ++++ simulator/app/DeviceWindow.h | 70 + simulator/app/ExternalRuntime.cpp | 94 ++ simulator/app/ExternalRuntime.h | 26 + simulator/app/LinuxKeys.h | 20 + simulator/app/LoadableImage.cpp | 100 ++ simulator/app/LoadableImage.h | 73 + simulator/app/LocalBiChannel.cpp | 444 ++++++ simulator/app/LocalBiChannel.h | 54 + simulator/app/LogBundle.h | 47 + simulator/app/LogMessage.cpp | 98 ++ simulator/app/LogMessage.h | 73 + simulator/app/LogPool.cpp | 90 ++ simulator/app/LogPool.h | 56 + simulator/app/LogPrefsDialog.cpp | 404 ++++++ simulator/app/LogPrefsDialog.h | 70 + simulator/app/LogWindow.cpp | 1156 ++++++++++++++++ simulator/app/LogWindow.h | 127 ++ simulator/app/MainFrame.cpp | 1462 ++++++++++++++++++++ simulator/app/MainFrame.h | 115 ++ simulator/app/MessageStream.cpp | 402 ++++++ simulator/app/MessageStream.h | 190 +++ simulator/app/MyApp.cpp | 547 ++++++++ simulator/app/MyApp.h | 93 ++ simulator/app/PhoneButton.cpp | 180 +++ simulator/app/PhoneButton.h | 80 ++ simulator/app/PhoneCollection.cpp | 174 +++ simulator/app/PhoneCollection.h | 52 + simulator/app/PhoneData.cpp | 868 ++++++++++++ simulator/app/PhoneData.h | 365 +++++ simulator/app/PhoneWindow.cpp | 1051 ++++++++++++++ simulator/app/PhoneWindow.h | 186 +++ simulator/app/Preferences.cpp | 475 +++++++ simulator/app/Preferences.h | 104 ++ simulator/app/PrefsDialog.cpp | 292 ++++ simulator/app/PrefsDialog.h | 54 + simulator/app/PropertyServer.cpp | 465 +++++++ simulator/app/PropertyServer.h | 68 + simulator/app/README | 74 + simulator/app/Resource.h | 98 ++ simulator/app/Semaphore.cpp | 516 +++++++ simulator/app/Semaphore.h | 59 + simulator/app/Shmem.cpp | 558 ++++++++ simulator/app/Shmem.h | 95 ++ simulator/app/SimRuntime.h | 124 ++ simulator/app/UserEvent.cpp | 20 + simulator/app/UserEvent.h | 54 + simulator/app/UserEventMessage.h | 84 ++ simulator/app/assets/android-dream/arrow_down.png | Bin 0 -> 3438 bytes simulator/app/assets/android-dream/arrow_left.png | Bin 0 -> 4122 bytes simulator/app/assets/android-dream/arrow_right.png | Bin 0 -> 4147 bytes simulator/app/assets/android-dream/arrow_up.png | Bin 0 -> 3493 bytes simulator/app/assets/android-dream/back.png | Bin 0 -> 3564 bytes simulator/app/assets/android-dream/background.png | Bin 0 -> 62579 bytes simulator/app/assets/android-dream/end.png | Bin 0 -> 3562 bytes simulator/app/assets/android-dream/home.png | Bin 0 -> 3578 bytes simulator/app/assets/android-dream/key.png | Bin 0 -> 2857 bytes simulator/app/assets/android-dream/layout.xml | 356 +++++ simulator/app/assets/android-dream/menu.png | Bin 0 -> 3079 bytes simulator/app/assets/android-dream/power.png | Bin 0 -> 3782 bytes simulator/app/assets/android-dream/select.png | Bin 0 -> 3374 bytes simulator/app/assets/android-dream/send.png | Bin 0 -> 3561 bytes simulator/app/assets/android-dream/spacebar.png | Bin 0 -> 2916 bytes simulator/app/assets/android-dream/volume_down.png | Bin 0 -> 3586 bytes simulator/app/assets/android-dream/volume_up.png | Bin 0 -> 3856 bytes simulator/app/assets/android-sooner/back.png | Bin 0 -> 607 bytes simulator/app/assets/android-sooner/background.png | Bin 0 -> 111656 bytes simulator/app/assets/android-sooner/down.png | Bin 0 -> 502 bytes simulator/app/assets/android-sooner/end.png | Bin 0 -> 514 bytes simulator/app/assets/android-sooner/favorites.png | Bin 0 -> 545 bytes simulator/app/assets/android-sooner/home.png | Bin 0 -> 538 bytes simulator/app/assets/android-sooner/imap | 15 + simulator/app/assets/android-sooner/layout.xml | 34 + simulator/app/assets/android-sooner/left.png | Bin 0 -> 999 bytes simulator/app/assets/android-sooner/menu.png | Bin 0 -> 427 bytes simulator/app/assets/android-sooner/right.png | Bin 0 -> 997 bytes simulator/app/assets/android-sooner/select.png | Bin 0 -> 521 bytes simulator/app/assets/android-sooner/send.png | Bin 0 -> 529 bytes simulator/app/assets/android-sooner/up.png | Bin 0 -> 470 bytes simulator/app/assets/chimera-2000/layout.xml | 194 +++ .../assets/danger-hiptop-m1/background-open.png | Bin 0 -> 349179 bytes .../app/assets/danger-hiptop-m1/background.png | Bin 0 -> 205663 bytes simulator/app/assets/danger-hiptop-m1/layout.xml | 31 + simulator/app/assets/htc-audiovox-5600/0.png | Bin 0 -> 6229 bytes simulator/app/assets/htc-audiovox-5600/1.png | Bin 0 -> 6965 bytes simulator/app/assets/htc-audiovox-5600/2.png | Bin 0 -> 7751 bytes simulator/app/assets/htc-audiovox-5600/3.png | Bin 0 -> 7480 bytes simulator/app/assets/htc-audiovox-5600/4.png | Bin 0 -> 7220 bytes simulator/app/assets/htc-audiovox-5600/5.png | Bin 0 -> 6964 bytes simulator/app/assets/htc-audiovox-5600/6.png | Bin 0 -> 7615 bytes simulator/app/assets/htc-audiovox-5600/7.png | Bin 0 -> 7879 bytes simulator/app/assets/htc-audiovox-5600/8.png | Bin 0 -> 6934 bytes simulator/app/assets/htc-audiovox-5600/9.png | Bin 0 -> 7681 bytes .../app/assets/htc-audiovox-5600/arrow-down.png | Bin 0 -> 6202 bytes .../app/assets/htc-audiovox-5600/arrow-up.png | Bin 0 -> 4522 bytes simulator/app/assets/htc-audiovox-5600/back.png | Bin 0 -> 6615 bytes .../assets/htc-audiovox-5600/background-orange.png | Bin 0 -> 204261 bytes .../app/assets/htc-audiovox-5600/background.png | Bin 0 -> 183099 bytes simulator/app/assets/htc-audiovox-5600/home.png | Bin 0 -> 6781 bytes simulator/app/assets/htc-audiovox-5600/layout.xml | 53 + simulator/app/assets/htc-audiovox-5600/pound.png | Bin 0 -> 6470 bytes simulator/app/assets/htc-audiovox-5600/select.png | Bin 0 -> 4157 bytes .../app/assets/htc-audiovox-5600/soft-left.png | Bin 0 -> 5058 bytes .../app/assets/htc-audiovox-5600/soft-right.png | Bin 0 -> 5713 bytes simulator/app/assets/htc-audiovox-5600/star.png | Bin 0 -> 6838 bytes simulator/app/assets/htc-tornado/0.png | Bin 0 -> 20704 bytes simulator/app/assets/htc-tornado/1.png | Bin 0 -> 20270 bytes simulator/app/assets/htc-tornado/2.png | Bin 0 -> 21797 bytes simulator/app/assets/htc-tornado/3.png | Bin 0 -> 20720 bytes simulator/app/assets/htc-tornado/4.png | Bin 0 -> 18031 bytes simulator/app/assets/htc-tornado/5.png | Bin 0 -> 19427 bytes simulator/app/assets/htc-tornado/6.png | Bin 0 -> 18261 bytes simulator/app/assets/htc-tornado/7.png | Bin 0 -> 18032 bytes simulator/app/assets/htc-tornado/8.png | Bin 0 -> 19246 bytes simulator/app/assets/htc-tornado/9.png | Bin 0 -> 18359 bytes simulator/app/assets/htc-tornado/arrow-down.png | Bin 0 -> 5634 bytes simulator/app/assets/htc-tornado/arrow-left.png | Bin 0 -> 5753 bytes simulator/app/assets/htc-tornado/arrow-right.png | Bin 0 -> 5697 bytes simulator/app/assets/htc-tornado/arrow-up.png | Bin 0 -> 5658 bytes simulator/app/assets/htc-tornado/back.png | Bin 0 -> 10584 bytes simulator/app/assets/htc-tornado/background.png | Bin 0 -> 915802 bytes simulator/app/assets/htc-tornado/home.png | Bin 0 -> 10575 bytes simulator/app/assets/htc-tornado/internet.png | Bin 0 -> 12036 bytes simulator/app/assets/htc-tornado/layout.xml | 64 + simulator/app/assets/htc-tornado/pause-play.png | Bin 0 -> 11127 bytes simulator/app/assets/htc-tornado/phone-dial.png | Bin 0 -> 19864 bytes simulator/app/assets/htc-tornado/phone-hangup.png | Bin 0 -> 19846 bytes simulator/app/assets/htc-tornado/pound.png | Bin 0 -> 19447 bytes simulator/app/assets/htc-tornado/select.png | Bin 0 -> 5165 bytes simulator/app/assets/htc-tornado/skip-backward.png | Bin 0 -> 11755 bytes simulator/app/assets/htc-tornado/skip-forward.png | Bin 0 -> 11712 bytes simulator/app/assets/htc-tornado/soft-left.png | Bin 0 -> 10729 bytes simulator/app/assets/htc-tornado/soft-right.png | Bin 0 -> 10797 bytes simulator/app/assets/htc-tornado/star.png | Bin 0 -> 19512 bytes simulator/app/assets/motorola-q/back.png | Bin 0 -> 4496 bytes simulator/app/assets/motorola-q/background.png | Bin 0 -> 293293 bytes simulator/app/assets/motorola-q/down.png | Bin 0 -> 3213 bytes simulator/app/assets/motorola-q/end.png | Bin 0 -> 4213 bytes simulator/app/assets/motorola-q/home.png | Bin 0 -> 4698 bytes simulator/app/assets/motorola-q/layout.xml | 36 + simulator/app/assets/motorola-q/left.png | Bin 0 -> 3645 bytes simulator/app/assets/motorola-q/menu-left.png | Bin 0 -> 5941 bytes simulator/app/assets/motorola-q/menu-right.png | Bin 0 -> 5974 bytes simulator/app/assets/motorola-q/right.png | Bin 0 -> 3273 bytes simulator/app/assets/motorola-q/select.png | Bin 0 -> 2119 bytes simulator/app/assets/motorola-q/send.png | Bin 0 -> 3871 bytes simulator/app/assets/motorola-q/up.png | Bin 0 -> 3411 bytes simulator/app/assets/panasonic-x70/arrow-down.png | Bin 0 -> 5866 bytes simulator/app/assets/panasonic-x70/arrow-up.png | Bin 0 -> 8012 bytes simulator/app/assets/panasonic-x70/back.png | Bin 0 -> 6750 bytes simulator/app/assets/panasonic-x70/background.png | Bin 0 -> 263738 bytes simulator/app/assets/panasonic-x70/home.png | Bin 0 -> 7468 bytes simulator/app/assets/panasonic-x70/layout.xml | 50 + simulator/app/assets/panasonic-x70/select.png | Bin 0 -> 6634 bytes simulator/app/assets/panasonic-x70/soft-left.png | Bin 0 -> 3537 bytes simulator/app/assets/panasonic-x70/soft-right.png | Bin 0 -> 3454 bytes simulator/app/assets/samsung-flip-2005/0.png | Bin 0 -> 8624 bytes simulator/app/assets/samsung-flip-2005/1.png | Bin 0 -> 8843 bytes simulator/app/assets/samsung-flip-2005/2.png | Bin 0 -> 8767 bytes simulator/app/assets/samsung-flip-2005/3.png | Bin 0 -> 9221 bytes simulator/app/assets/samsung-flip-2005/4.png | Bin 0 -> 8752 bytes simulator/app/assets/samsung-flip-2005/5.png | Bin 0 -> 8477 bytes simulator/app/assets/samsung-flip-2005/6.png | Bin 0 -> 8512 bytes simulator/app/assets/samsung-flip-2005/7.png | Bin 0 -> 8495 bytes simulator/app/assets/samsung-flip-2005/8.png | Bin 0 -> 8530 bytes simulator/app/assets/samsung-flip-2005/9.png | Bin 0 -> 8757 bytes simulator/app/assets/samsung-flip-2005/abc.png | Bin 0 -> 9091 bytes .../app/assets/samsung-flip-2005/arrow-down.png | Bin 0 -> 7427 bytes .../app/assets/samsung-flip-2005/arrow-left.png | Bin 0 -> 7673 bytes .../app/assets/samsung-flip-2005/arrow-right.png | Bin 0 -> 7054 bytes .../app/assets/samsung-flip-2005/arrow-up.png | Bin 0 -> 7177 bytes .../assets/samsung-flip-2005/back-background.png | Bin 0 -> 9146 bytes simulator/app/assets/samsung-flip-2005/back.png | Bin 0 -> 8677 bytes .../samsung-flip-2005/background-176_208.png | Bin 0 -> 268917 bytes .../samsung-flip-2005/background-176x220.png | Bin 0 -> 266760 bytes simulator/app/assets/samsung-flip-2005/clear.png | Bin 0 -> 7508 bytes simulator/app/assets/samsung-flip-2005/end.png | Bin 0 -> 10122 bytes .../assets/samsung-flip-2005/home-background.png | Bin 0 -> 9032 bytes simulator/app/assets/samsung-flip-2005/home.png | Bin 0 -> 8598 bytes simulator/app/assets/samsung-flip-2005/layout.xml | 56 + simulator/app/assets/samsung-flip-2005/menu.png | Bin 0 -> 8861 bytes simulator/app/assets/samsung-flip-2005/pound.png | Bin 0 -> 8511 bytes simulator/app/assets/samsung-flip-2005/select.png | Bin 0 -> 6022 bytes simulator/app/assets/samsung-flip-2005/send.png | Bin 0 -> 10441 bytes .../app/assets/samsung-flip-2005/soft-left.png | Bin 0 -> 7146 bytes .../app/assets/samsung-flip-2005/soft-right.png | Bin 0 -> 6954 bytes simulator/app/assets/samsung-flip-2005/star.png | Bin 0 -> 8346 bytes simulator/app/help/unnamed.htb | Bin 0 -> 1688 bytes simulator/wrapsim/Android.mk | 53 + simulator/wrapsim/BitVector.c | 102 ++ simulator/wrapsim/BitVector.h | 34 + simulator/wrapsim/Common.h | 18 + simulator/wrapsim/DevAudio.c | 124 ++ simulator/wrapsim/DevConsoleTty.c | 53 + simulator/wrapsim/DevEvent.c | 414 ++++++ simulator/wrapsim/DevFb.c | 267 ++++ simulator/wrapsim/DevLog.c | 485 +++++++ simulator/wrapsim/DevPower.c | 208 +++ simulator/wrapsim/DevVibrator.c | 47 + simulator/wrapsim/FakeDev.c | 315 +++++ simulator/wrapsim/FakeDev.h | 123 ++ simulator/wrapsim/Globals.h | 208 +++ simulator/wrapsim/Init.c | 188 +++ simulator/wrapsim/Intercept.c | 816 +++++++++++ simulator/wrapsim/LaunchWrapper.c | 301 ++++ simulator/wrapsim/Log.c | 74 + simulator/wrapsim/Log.h | 19 + simulator/wrapsim/README.txt | 21 + simulator/wrapsim/SimMgr.c | 988 +++++++++++++ simulator/wrapsim/SimMgr.h | 75 + 214 files changed, 19729 insertions(+) create mode 100644 simulator/app/Android.mk create mode 100644 simulator/app/AssetStream.h create mode 100644 simulator/app/DeviceManager.cpp create mode 100644 simulator/app/DeviceManager.h create mode 100644 simulator/app/DeviceWindow.cpp create mode 100644 simulator/app/DeviceWindow.h create mode 100644 simulator/app/ExternalRuntime.cpp create mode 100644 simulator/app/ExternalRuntime.h create mode 100644 simulator/app/LinuxKeys.h create mode 100644 simulator/app/LoadableImage.cpp create mode 100644 simulator/app/LoadableImage.h create mode 100644 simulator/app/LocalBiChannel.cpp create mode 100644 simulator/app/LocalBiChannel.h create mode 100644 simulator/app/LogBundle.h create mode 100644 simulator/app/LogMessage.cpp create mode 100644 simulator/app/LogMessage.h create mode 100644 simulator/app/LogPool.cpp create mode 100644 simulator/app/LogPool.h create mode 100644 simulator/app/LogPrefsDialog.cpp create mode 100644 simulator/app/LogPrefsDialog.h create mode 100644 simulator/app/LogWindow.cpp create mode 100644 simulator/app/LogWindow.h create mode 100644 simulator/app/MainFrame.cpp create mode 100644 simulator/app/MainFrame.h create mode 100644 simulator/app/MessageStream.cpp create mode 100644 simulator/app/MessageStream.h create mode 100644 simulator/app/MyApp.cpp create mode 100644 simulator/app/MyApp.h create mode 100644 simulator/app/PhoneButton.cpp create mode 100644 simulator/app/PhoneButton.h create mode 100644 simulator/app/PhoneCollection.cpp create mode 100644 simulator/app/PhoneCollection.h create mode 100644 simulator/app/PhoneData.cpp create mode 100644 simulator/app/PhoneData.h create mode 100644 simulator/app/PhoneWindow.cpp create mode 100644 simulator/app/PhoneWindow.h create mode 100644 simulator/app/Preferences.cpp create mode 100644 simulator/app/Preferences.h create mode 100644 simulator/app/PrefsDialog.cpp create mode 100644 simulator/app/PrefsDialog.h create mode 100644 simulator/app/PropertyServer.cpp create mode 100644 simulator/app/PropertyServer.h create mode 100644 simulator/app/README create mode 100644 simulator/app/Resource.h create mode 100644 simulator/app/Semaphore.cpp create mode 100644 simulator/app/Semaphore.h create mode 100644 simulator/app/Shmem.cpp create mode 100644 simulator/app/Shmem.h create mode 100644 simulator/app/SimRuntime.h create mode 100644 simulator/app/UserEvent.cpp create mode 100644 simulator/app/UserEvent.h create mode 100644 simulator/app/UserEventMessage.h create mode 100644 simulator/app/assets/android-dream/arrow_down.png create mode 100644 simulator/app/assets/android-dream/arrow_left.png create mode 100644 simulator/app/assets/android-dream/arrow_right.png create mode 100644 simulator/app/assets/android-dream/arrow_up.png create mode 100644 simulator/app/assets/android-dream/back.png create mode 100644 simulator/app/assets/android-dream/background.png create mode 100644 simulator/app/assets/android-dream/end.png create mode 100644 simulator/app/assets/android-dream/home.png create mode 100644 simulator/app/assets/android-dream/key.png create mode 100644 simulator/app/assets/android-dream/layout.xml create mode 100644 simulator/app/assets/android-dream/menu.png create mode 100644 simulator/app/assets/android-dream/power.png create mode 100644 simulator/app/assets/android-dream/select.png create mode 100644 simulator/app/assets/android-dream/send.png create mode 100644 simulator/app/assets/android-dream/spacebar.png create mode 100644 simulator/app/assets/android-dream/volume_down.png create mode 100644 simulator/app/assets/android-dream/volume_up.png create mode 100644 simulator/app/assets/android-sooner/back.png create mode 100644 simulator/app/assets/android-sooner/background.png create mode 100644 simulator/app/assets/android-sooner/down.png create mode 100644 simulator/app/assets/android-sooner/end.png create mode 100644 simulator/app/assets/android-sooner/favorites.png create mode 100644 simulator/app/assets/android-sooner/home.png create mode 100644 simulator/app/assets/android-sooner/imap create mode 100644 simulator/app/assets/android-sooner/layout.xml create mode 100644 simulator/app/assets/android-sooner/left.png create mode 100644 simulator/app/assets/android-sooner/menu.png create mode 100644 simulator/app/assets/android-sooner/right.png create mode 100644 simulator/app/assets/android-sooner/select.png create mode 100644 simulator/app/assets/android-sooner/send.png create mode 100644 simulator/app/assets/android-sooner/up.png create mode 100644 simulator/app/assets/chimera-2000/layout.xml create mode 100644 simulator/app/assets/danger-hiptop-m1/background-open.png create mode 100644 simulator/app/assets/danger-hiptop-m1/background.png create mode 100644 simulator/app/assets/danger-hiptop-m1/layout.xml create mode 100644 simulator/app/assets/htc-audiovox-5600/0.png create mode 100644 simulator/app/assets/htc-audiovox-5600/1.png create mode 100644 simulator/app/assets/htc-audiovox-5600/2.png create mode 100644 simulator/app/assets/htc-audiovox-5600/3.png create mode 100644 simulator/app/assets/htc-audiovox-5600/4.png create mode 100644 simulator/app/assets/htc-audiovox-5600/5.png create mode 100644 simulator/app/assets/htc-audiovox-5600/6.png create mode 100644 simulator/app/assets/htc-audiovox-5600/7.png create mode 100644 simulator/app/assets/htc-audiovox-5600/8.png create mode 100644 simulator/app/assets/htc-audiovox-5600/9.png create mode 100644 simulator/app/assets/htc-audiovox-5600/arrow-down.png create mode 100644 simulator/app/assets/htc-audiovox-5600/arrow-up.png create mode 100644 simulator/app/assets/htc-audiovox-5600/back.png create mode 100644 simulator/app/assets/htc-audiovox-5600/background-orange.png create mode 100644 simulator/app/assets/htc-audiovox-5600/background.png create mode 100644 simulator/app/assets/htc-audiovox-5600/home.png create mode 100644 simulator/app/assets/htc-audiovox-5600/layout.xml create mode 100644 simulator/app/assets/htc-audiovox-5600/pound.png create mode 100644 simulator/app/assets/htc-audiovox-5600/select.png create mode 100644 simulator/app/assets/htc-audiovox-5600/soft-left.png create mode 100644 simulator/app/assets/htc-audiovox-5600/soft-right.png create mode 100644 simulator/app/assets/htc-audiovox-5600/star.png create mode 100644 simulator/app/assets/htc-tornado/0.png create mode 100644 simulator/app/assets/htc-tornado/1.png create mode 100644 simulator/app/assets/htc-tornado/2.png create mode 100644 simulator/app/assets/htc-tornado/3.png create mode 100644 simulator/app/assets/htc-tornado/4.png create mode 100644 simulator/app/assets/htc-tornado/5.png create mode 100644 simulator/app/assets/htc-tornado/6.png create mode 100644 simulator/app/assets/htc-tornado/7.png create mode 100644 simulator/app/assets/htc-tornado/8.png create mode 100644 simulator/app/assets/htc-tornado/9.png create mode 100644 simulator/app/assets/htc-tornado/arrow-down.png create mode 100644 simulator/app/assets/htc-tornado/arrow-left.png create mode 100644 simulator/app/assets/htc-tornado/arrow-right.png create mode 100644 simulator/app/assets/htc-tornado/arrow-up.png create mode 100644 simulator/app/assets/htc-tornado/back.png create mode 100644 simulator/app/assets/htc-tornado/background.png create mode 100644 simulator/app/assets/htc-tornado/home.png create mode 100644 simulator/app/assets/htc-tornado/internet.png create mode 100644 simulator/app/assets/htc-tornado/layout.xml create mode 100644 simulator/app/assets/htc-tornado/pause-play.png create mode 100644 simulator/app/assets/htc-tornado/phone-dial.png create mode 100644 simulator/app/assets/htc-tornado/phone-hangup.png create mode 100644 simulator/app/assets/htc-tornado/pound.png create mode 100644 simulator/app/assets/htc-tornado/select.png create mode 100644 simulator/app/assets/htc-tornado/skip-backward.png create mode 100644 simulator/app/assets/htc-tornado/skip-forward.png create mode 100644 simulator/app/assets/htc-tornado/soft-left.png create mode 100644 simulator/app/assets/htc-tornado/soft-right.png create mode 100644 simulator/app/assets/htc-tornado/star.png create mode 100644 simulator/app/assets/motorola-q/back.png create mode 100644 simulator/app/assets/motorola-q/background.png create mode 100644 simulator/app/assets/motorola-q/down.png create mode 100644 simulator/app/assets/motorola-q/end.png create mode 100644 simulator/app/assets/motorola-q/home.png create mode 100644 simulator/app/assets/motorola-q/layout.xml create mode 100644 simulator/app/assets/motorola-q/left.png create mode 100644 simulator/app/assets/motorola-q/menu-left.png create mode 100644 simulator/app/assets/motorola-q/menu-right.png create mode 100644 simulator/app/assets/motorola-q/right.png create mode 100644 simulator/app/assets/motorola-q/select.png create mode 100644 simulator/app/assets/motorola-q/send.png create mode 100644 simulator/app/assets/motorola-q/up.png create mode 100644 simulator/app/assets/panasonic-x70/arrow-down.png create mode 100644 simulator/app/assets/panasonic-x70/arrow-up.png create mode 100644 simulator/app/assets/panasonic-x70/back.png create mode 100644 simulator/app/assets/panasonic-x70/background.png create mode 100644 simulator/app/assets/panasonic-x70/home.png create mode 100644 simulator/app/assets/panasonic-x70/layout.xml create mode 100644 simulator/app/assets/panasonic-x70/select.png create mode 100644 simulator/app/assets/panasonic-x70/soft-left.png create mode 100644 simulator/app/assets/panasonic-x70/soft-right.png create mode 100644 simulator/app/assets/samsung-flip-2005/0.png create mode 100644 simulator/app/assets/samsung-flip-2005/1.png create mode 100644 simulator/app/assets/samsung-flip-2005/2.png create mode 100644 simulator/app/assets/samsung-flip-2005/3.png create mode 100644 simulator/app/assets/samsung-flip-2005/4.png create mode 100644 simulator/app/assets/samsung-flip-2005/5.png create mode 100644 simulator/app/assets/samsung-flip-2005/6.png create mode 100644 simulator/app/assets/samsung-flip-2005/7.png create mode 100644 simulator/app/assets/samsung-flip-2005/8.png create mode 100644 simulator/app/assets/samsung-flip-2005/9.png create mode 100644 simulator/app/assets/samsung-flip-2005/abc.png create mode 100644 simulator/app/assets/samsung-flip-2005/arrow-down.png create mode 100644 simulator/app/assets/samsung-flip-2005/arrow-left.png create mode 100644 simulator/app/assets/samsung-flip-2005/arrow-right.png create mode 100644 simulator/app/assets/samsung-flip-2005/arrow-up.png create mode 100644 simulator/app/assets/samsung-flip-2005/back-background.png create mode 100644 simulator/app/assets/samsung-flip-2005/back.png create mode 100644 simulator/app/assets/samsung-flip-2005/background-176_208.png create mode 100644 simulator/app/assets/samsung-flip-2005/background-176x220.png create mode 100644 simulator/app/assets/samsung-flip-2005/clear.png create mode 100644 simulator/app/assets/samsung-flip-2005/end.png create mode 100644 simulator/app/assets/samsung-flip-2005/home-background.png create mode 100644 simulator/app/assets/samsung-flip-2005/home.png create mode 100644 simulator/app/assets/samsung-flip-2005/layout.xml create mode 100644 simulator/app/assets/samsung-flip-2005/menu.png create mode 100644 simulator/app/assets/samsung-flip-2005/pound.png create mode 100644 simulator/app/assets/samsung-flip-2005/select.png create mode 100644 simulator/app/assets/samsung-flip-2005/send.png create mode 100644 simulator/app/assets/samsung-flip-2005/soft-left.png create mode 100644 simulator/app/assets/samsung-flip-2005/soft-right.png create mode 100644 simulator/app/assets/samsung-flip-2005/star.png create mode 100644 simulator/app/help/unnamed.htb create mode 100644 simulator/wrapsim/Android.mk create mode 100644 simulator/wrapsim/BitVector.c create mode 100644 simulator/wrapsim/BitVector.h create mode 100644 simulator/wrapsim/Common.h create mode 100644 simulator/wrapsim/DevAudio.c create mode 100644 simulator/wrapsim/DevConsoleTty.c create mode 100644 simulator/wrapsim/DevEvent.c create mode 100644 simulator/wrapsim/DevFb.c create mode 100644 simulator/wrapsim/DevLog.c create mode 100644 simulator/wrapsim/DevPower.c create mode 100644 simulator/wrapsim/DevVibrator.c create mode 100644 simulator/wrapsim/FakeDev.c create mode 100644 simulator/wrapsim/FakeDev.h create mode 100644 simulator/wrapsim/Globals.h create mode 100644 simulator/wrapsim/Init.c create mode 100644 simulator/wrapsim/Intercept.c create mode 100644 simulator/wrapsim/LaunchWrapper.c create mode 100644 simulator/wrapsim/Log.c create mode 100644 simulator/wrapsim/Log.h create mode 100644 simulator/wrapsim/README.txt create mode 100644 simulator/wrapsim/SimMgr.c create mode 100644 simulator/wrapsim/SimMgr.h (limited to 'simulator') diff --git a/simulator/app/Android.mk b/simulator/app/Android.mk new file mode 100644 index 000000000..59c147c9e --- /dev/null +++ b/simulator/app/Android.mk @@ -0,0 +1,130 @@ +# Copyright 2005 The Android Open Source Project +# + +ifneq ($(TARGET_ARCH),arm) + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + DeviceManager.cpp \ + DeviceWindow.cpp \ + ExternalRuntime.cpp \ + LoadableImage.cpp \ + LocalBiChannel.cpp \ + LogMessage.cpp \ + LogPool.cpp \ + LogPrefsDialog.cpp \ + LogWindow.cpp \ + MainFrame.cpp \ + MessageStream.cpp \ + MyApp.cpp \ + PhoneButton.cpp \ + PhoneCollection.cpp \ + PhoneData.cpp \ + PhoneWindow.cpp \ + Preferences.cpp \ + PrefsDialog.cpp \ + PropertyServer.cpp \ + Semaphore.cpp \ + Shmem.cpp \ + UserEvent.cpp + +LOCAL_STATIC_LIBRARIES := \ + libtinyxml +LOCAL_WHOLE_STATIC_LIBRARIES := \ + libutils\ + libcutils +LOCAL_MODULE := simulator + +LOCAL_LDLIBS += -lpthread + +LOCAL_CFLAGS := -UNDEBUG +#LOCAL_LDFLAGS := + +LOCAL_C_INCLUDES += \ + external/tinyxml \ + commands/runtime + +# wxWidgets defines +LOCAL_C_INCLUDES += \ + /usr/include/wx-2.6 \ + /usr/lib/wx/include/gtk2-unicode-release-2.6 + +ifeq ($(HOST_OS),linux) + # You can install wxWidgets with "sudo apt-get libwxgtk2.6-dev" + LOCAL_LDFLAGS += -lwx_baseu-2.6 \ + -lwx_baseu_net-2.6 \ + -lwx_baseu_xml-2.6 \ + -lwx_gtk2u_adv-2.6 \ + -lwx_gtk2u_core-2.6 \ + -lwx_gtk2u_html-2.6 \ + -lwx_gtk2u_qa-2.6 \ + -lwx_gtk2u_xrc-2.6 + + # this next line makes the simulator able to find its shared libraries + # without us explicitly setting the LD_LIBRARY_PATH environment variable + LOCAL_LDLIBS += -Wl,-z,origin + LOCAL_CFLAGS += -DGTK_NO_CHECK_CASTS -D__WXGTK__ -D_FILE_OFFSET_BITS=64 \ + -D_LARGE_FILES -D_LARGEFILE_SOURCE=1 + LOCAL_LDLIBS += -lrt +endif +ifeq ($(HOST_OS),darwin) + # NOTE: OS X is no longer supported + LOCAL_C_INCLUDES += prebuilt/$(HOST_PREBUILT_TAG)/wxwidgets + LOCAL_LDLIBS += \ + -framework QuickTime \ + -framework IOKit \ + -framework Carbon \ + -framework Cocoa \ + -framework System \ + -lwx_mac_xrc-2.6 \ + -lwx_mac_qa-2.6 \ + -lwx_mac_html-2.6 \ + -lwx_mac_adv-2.6 \ + -lwx_mac_core-2.6 \ + -lwx_base_carbon_xml-2.6 \ + -lwx_base_carbon_net-2.6 \ + -lwx_base_carbon-2.6 \ + -lwxexpat-2.6 \ + -lwxtiff-2.6 \ + -lwxjpeg-2.6 \ + -lwxpng-2.6 \ + -lz \ + -lpthread \ + -liconv + LOCAL_CFLAGS += \ + -D__WXMAC__ \ + -D_FILE_OFFSET_BITS=64 \ + -D_LARGE_FILES \ + -DNO_GCC_PRAGMA +endif + + +include $(BUILD_HOST_EXECUTABLE) + +ifeq ($(HOST_OS),darwin) +# Add the carbon resources to the executable. +$(LOCAL_BUILT_MODULE): PRIVATE_POST_PROCESS_COMMAND := \ + /Developer/Tools/Rez -d __DARWIN__ -t APPL \ + -d __WXMAC__ -o $(LOCAL_BUILT_MODULE) Carbon.r +endif + +# also, we need to copy our assets. We place these by hand now, because +# I'd like to clean this up as part of some pdk cleanup I want to do. + +asset_files := $(addprefix $(LOCAL_PATH)/assets/,$(call find-subdir-assets,$(LOCAL_PATH)/assets)) +asset_target := $(HOST_COMMON_OUT_ROOT)/sim-assets/simulator$(COMMON_PACKAGE_SUFFIX) +$(asset_target): PRIVATE_ASSET_ROOT := $(LOCAL_PATH)/assets + +$(asset_target) : $(asset_files) $(AAPT) + @echo host Package $@ + $(hide) mkdir -p $(dir $@) + $(hide) $(AAPT) package -u -A $(PRIVATE_ASSET_ROOT) -F $@ + +$(LOCAL_INSTALLED_MODULE): | $(asset_target) + +ALL_DEFAULT_INSTALLED_MODULES += $(asset_target) + +endif diff --git a/simulator/app/AssetStream.h b/simulator/app/AssetStream.h new file mode 100644 index 000000000..0ab2d12c9 --- /dev/null +++ b/simulator/app/AssetStream.h @@ -0,0 +1,102 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Provide a wxInputStream subclass based on the Android Asset class. +// This is necessary because some wxWidgets functions require either a +// filename or a wxInputStream (e.g. wxImage). +// +#ifndef _SIM_ASSETSTREAM_H +#define _SIM_ASSETSTREAM_H + +#include "wx/stream.h" +#include + +/* + * There is no sample code or concrete documentation about providing + * input streams, but it seems straightforward. The PNG loading code + * uses the following: + * OnSysTell() + * OnSysSeek() + * Read() + * + * The AssetStream takes ownership of the Asset. + */ +class AssetStream : public wxInputStream { +public: + AssetStream(android::Asset* pAsset) + : mpAsset(pAsset) + {} + virtual ~AssetStream(void) { + delete mpAsset; + } + + virtual wxFileOffset GetLength() const { + //printf("## GetLength --> %ld\n", (long) mpAsset->getLength()); + return mpAsset->getLength(); + } + virtual size_t GetSize() const { + //printf("## GetSize --> %ld\n", (long) mpAsset->getLength()); + return mpAsset->getLength(); + } + virtual bool IsSeekable() const { return true; } + + virtual bool Eof() const { + //printf("## Eof\n"); + return (mpAsset->seek(0, SEEK_CUR) == mpAsset->getLength()); + } + + virtual bool CanRead() const { + //printf("## CanRead\n"); + return !Eof(); + } + + virtual wxInputStream& Read(void* buffer, size_t size) { + OnSysRead(buffer, size); + + return *this; + } + +protected: + /* read data, return number of bytes or 0 if EOF reached */ + virtual size_t OnSysRead(void* buffer, size_t size) { + ssize_t actual = mpAsset->read(buffer, size); + if (actual < 0) { + // TODO: flag error + actual = 0; + } + //printf("## OnSysRead(%p %u) --> %d\n", buffer, size, actual); + return actual; + } + + /* seek, using wxWidgets-defined values for "whence" */ + virtual wxFileOffset OnSysSeek(wxFileOffset seek, wxSeekMode mode) { + int whence; + off_t newPosn; + + if (mode == wxFromStart) + whence = SEEK_SET; + else if (mode == wxFromEnd) + whence = SEEK_END; + else + whence = SEEK_CUR; + newPosn = mpAsset->seek(seek, whence); + //printf("## OnSysSeek(%ld %d) --> %ld\n", + // (long) seek, mode, (long) newPosn); + if (newPosn == (off_t) -1) + return wxInvalidOffset; + else + return newPosn; + } + + virtual wxFileOffset OnSysTell() const { + //printf("## OnSysTell() --> %ld\n", (long) mpAsset->seek(0, SEEK_CUR)); + return mpAsset->seek(0, SEEK_CUR); + } + +private: + android::Asset* mpAsset; + + DECLARE_NO_COPY_CLASS(AssetStream); // private copy-ctor and op= +}; + +#endif // _SIM_ASSETSTREAM_H diff --git a/simulator/app/DeviceManager.cpp b/simulator/app/DeviceManager.cpp new file mode 100644 index 000000000..1f65d9955 --- /dev/null +++ b/simulator/app/DeviceManager.cpp @@ -0,0 +1,1220 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Management of the simulated device. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +#include "wx/image.h" + +#include "DeviceManager.h" +#include "MyApp.h" +#include "DeviceWindow.h" +#include "LogWindow.h" +#include "UserEvent.h" +#include "UserEventMessage.h" + +#include "SimRuntime.h" +#include "utils.h" + +#include +#include +#include + +#if !defined(SIGKILL) // doesn't exist in MinGW +# if defined(SIGBREAK) +# define SIGKILL SIGBREAK // intended for Ctrl-Break +# else +# define SIGKILL SIGABRT +# endif +#endif + + +/* + * Constructor. + */ +DeviceManager::DeviceManager(void) + : mThread(NULL), mDisplay(NULL), mNumDisplays(0), mKeyMap(NULL), + mpStatusWindow(NULL) +{ + //printf("--- DeviceManager constructor\n"); +} + +/* + * Destructor. Snuff the thread if it's still kicking. + */ +DeviceManager::~DeviceManager(void) +{ + //printf("--- DeviceManager destructor\n"); + + if (mThread != NULL && mThread->IsRunning()) { + mThread->KillChildProcesses(); + } + if (mThread != NULL) { + wxThread::ExitCode code; + + printf("Sim: Waiting for old runtime thread..."); fflush(stdout); + code = mThread->Wait(); // join the old thread + printf("done (code=%d)\n", (int) code); + } + delete mThread; + mThread = NULL; + + delete[] mDisplay; + free((void*)mKeyMap); +} + +/* + * Initialize the device configuration. + * + * "statusWindow" is where message boxes with failure messages go, usually + * the main frame. + */ +bool DeviceManager::Init(int numDisplays, wxWindow* statusWindow) +{ + //if (IsRunning()) { + // fprintf(stderr, "ERROR: tried to Configure device while running\n"); + // return false; + //} + assert(mDisplay == NULL); + assert(numDisplays > 0); + + //if (mDisplay != NULL) + // delete[] mDisplay; + + mDisplay = new Display[numDisplays]; + mNumDisplays = numDisplays; + + mpStatusWindow = statusWindow; + + return true; +} + +/* + * Have we been initialized already? + */ +bool DeviceManager::IsInitialized(void) const +{ + return (mDisplay != NULL); +} + +#if 0 +/* + * Return the Nth display. + */ +int DeviceManager::GetShmemKey(int displayIndex) +{ + assert(displayIndex >= 0 && displayIndex < mNumDisplays); + return mDisplay[displayIndex].GetShmemKey(); +} +#endif + +/* + * Define mapping between the device's display and a wxWidgets window. + */ +bool DeviceManager::SetDisplayConfig(int displayIndex, wxWindow* window, + int width, int height, android::PixelFormat format, int refresh) +{ + assert(displayIndex >= 0 && displayIndex < mNumDisplays); + + if (!mDisplay[displayIndex].Create(displayIndex, window, width, height, + format, refresh)) + { + fprintf(stderr, "Sim: ERROR: unable to configure display %d\n", + displayIndex); + return false; + } else { + printf("Sim: configured display %d (w=%d h=%d f=%d re=%d)\n", + displayIndex, width, height, format, refresh); + return true; + } +} + +/* + * Define the keyboard + */ +bool DeviceManager::SetKeyboardConfig(const char *keymap) { + free((void*)mKeyMap); + mKeyMap = strdup(keymap); + return true; +} + +/* + * Called before the phone window dialog destroys itself. The goal here + * is to prevent the runtime thread from trying to draw after the phone + * window has closed for business but before the device manager destructor + * gets called. + */ +void DeviceManager::WindowsClosing(void) +{ + int i; + + for (i = 0; i < mNumDisplays; i++) + mDisplay[i].Uncreate(); +} + +/* + * Launch a new runtime process. If there is an existing device manager + * thread, we assume that it is in the process of shutting down. + */ +bool DeviceManager::StartRuntime(void) +{ + return DeviceManager::DeviceThread::LaunchProcess(mpStatusWindow); +} + +/* + * Start the runtime management thread when a runtime connects to us. If + * there is an existing thread, we assume that it is in the process of + * shutting down. + */ +bool DeviceManager::StartRuntime(android::Pipe* reader, android::Pipe* writer) +{ + if (mThread != NULL) { + wxThread::ExitCode code; + + if (mThread->IsRunning()) { + fprintf(stderr, + "Sim: ERROR: start requested, but thread running\n"); + return false; + } + + // clean up old thread + printf("Sim: Waiting for old runtime thread..."); fflush(stdout); + code = mThread->Wait(); // join the old thread + printf("done (code=%d)\n", (int) code); + + delete mThread; + mThread = NULL; + } + + assert(mpStatusWindow != NULL); + mThread = new DeviceThread(this, mpStatusWindow, reader, writer); + if (mThread->Create() != wxTHREAD_NO_ERROR) { + fprintf(stderr, "Sim: ERROR: can't create thread\n"); + return false; + } + mThread->Run(); + + return true; +} + +/* + * Get the message stream. Returns NULL if it doesn't exist. + */ +android::MessageStream* DeviceManager::GetStream(void) +{ + if (!IsRunning()) { + fprintf(stderr, "Sim: ERROR: runtime thread not active\n"); + return NULL; + } + + assert(mThread != NULL); + android::MessageStream* pStream = mThread->GetStream(); + assert(pStream != NULL); + + if (!pStream->isReady()) { + fprintf(stderr, "Sim: NOTE: connection to runtime not ready\n"); + return NULL; + } + + return pStream; +} + +/* + * Stop the runtime, politely. + * + * We don't clean up the thread here, because it might not exit immediately. + */ +bool DeviceManager::StopRuntime(void) +{ + android::MessageStream* pStream = GetStream(); + if (pStream == NULL) + return false; + + printf("Sim: Sending quit command\n"); + + android::Message msg; + msg.setCommand(android::Simulator::kCommandQuit, 0); + pStream->send(&msg); + return true; +} + +/* + * Kill the runtime as efficiently as possible. + */ +void DeviceManager::KillRuntime(void) +{ + if (mThread != NULL && mThread->IsRunning()) + mThread->KillChildProcesses(); +} + +#if 0 +/* + * Check if the modified time is newer than mLastModified + */ +bool DeviceManager::RefreshRuntime(void) +{ + return (IsRunning() && mThread->IsRuntimeNew()); +} + +/* + * Tells the device manager that the user does not want to update + * the runtime + */ +void DeviceManager::UserCancelledRefresh(void) +{ + mThread->UpdateLastModified(); +} +#endif + +/* + * Send an event to the runtime. + * + * The events are defined in display_device.h. + */ +void DeviceManager::SendKeyEvent(KeyCode keyCode, bool down) +{ + android::MessageStream* pStream = GetStream(); + if (pStream == NULL) + return; + + int event = down ? android::Simulator::kCommandKeyDown : + android::Simulator::kCommandKeyUp; + + //printf("Sim: sending key-%s %d\n", down ? "down" : "up", keyCode); + + android::Message msg; + msg.setCommand(event, keyCode); + pStream->send(&msg); +} + +/* + * Send a "touch screen" event to the runtime. + * + * "mode" can be "down" (we're pressing), "up" (we're lifting our finger + * off) or "drag". + */ +void DeviceManager::SendTouchEvent(android::Simulator::TouchMode mode, + int x, int y) +{ + android::MessageStream* pStream = GetStream(); + if (pStream == NULL) + return; + + //printf("Sim: sending touch-%d x=%d y=%d\n", (int) mode, x, y); + + android::Message msg; + msg.setCommandExt(android::Simulator::kCommandTouch, mode, x, y); + pStream->send(&msg); +} + +/* + * The runtime has sent us a new frame of stuff to display. + * + * NOTE: we're still in the runtime management thread. We have to pass the + * bitmap through AddPendingEvent to get it over to the main thread. + * + * We have to make a copy of the data from the runtime; the easiest + * way to do that is to convert it to a bitmap here. However, X11 gets + * all worked up about calls being made from multiple threads, so we're + * better off just copying it into a buffer. + * + * Because we're decoupled from the runtime, there is a chance that we + * could drop frames. Buffering them up is probably worse, since it + * creates the possibility that we could stall and run out of memory. + * We could save a copy by handing the runtime a pointer to our buffer, + * but then we'd have to mutex the runtime against the simulator window + * Paint function. + */ +void DeviceManager::ShowFrame(int displayIndex) +{ + assert(displayIndex >= 0 && displayIndex < mNumDisplays); + + // copy the data to local storage and convert + mDisplay[displayIndex].CopyFromShared(); + + // create a user event and send it to the window + UserEvent uev(0, (void*) displayIndex); + + wxWindow* pEventWindow = mDisplay[displayIndex].GetWindow(); + if (pEventWindow != NULL) { + //printf("runtime has image, passing up\n"); + pEventWindow->AddPendingEvent(uev); + } else { + fprintf(stderr, "NOTE: runtime has image, display not available\n"); + } +} + +void DeviceManager::Vibrate(int vibrateOn) +{ + ((MyApp*)wxTheApp)->Vibrate(vibrateOn); +} + +/* + * Get the display data from the specified display. + */ +wxBitmap* DeviceManager::GetImageData(int displayIndex) +{ + assert(displayIndex >= 0 && displayIndex < mNumDisplays); + return mDisplay[displayIndex].GetImageData(); +} + +/* + * Send an event to all device windows + */ +void DeviceManager::BroadcastEvent(UserEvent& userEvent) { + int numDisplays = GetNumDisplays(); + for (int i = 0; i < numDisplays; i++) { + wxWindow* pEventWindow = mDisplay[i].GetWindow(); + if (pEventWindow != NULL) { + pEventWindow->AddPendingEvent(userEvent); + } + } +} + + +/* + * =========================================================================== + * DeviceManager::Display + * =========================================================================== + */ + +/* + * Fill out the various interesting fields based on the parameters. + */ +bool DeviceManager::Display::Create(int displayNum, wxWindow* window, + int width, int height, android::PixelFormat format, int refresh) +{ + //printf("DeviceManager::Display constructor\n"); + + assert(window != NULL); + if (mImageData != NULL) { + assert(false); // no re-init + return false; + } + + mDisplayNum = displayNum; + mDisplayWindow = window; + mWidth = width; + mHeight = height; + mFormat = format; + mRefresh = refresh; + + // use a fixed key for now + mShmemKey = GenerateKey(displayNum); + // allocate 24bpp for now + mpShmem = new android::Shmem; + if (!mpShmem->create(mShmemKey, width * height * 3, true)) + return false; + //printf("--- CREATED shmem, key=0x%08x addr=%p\n", + // mShmemKey, mpShmem->getAddr()); + + mImageData = new unsigned char[width * height * 3]; + if (mImageData == NULL) + return false; + + return true; +} + +/* + * The UI components are starting to shut down. We need to do away with + * our wxWindow pointer so that the runtime management thread doesn't try + * to send it display update events. + * + * We also need to let go of our side of the shared memory, because a + * new DeviceManager may get started up before our destructor gets called, + * and we may be re-using the key. + */ +void DeviceManager::Display::Uncreate(void) +{ + wxMutexLocker locker(mImageDataLock); + + //printf("--- Uncreate\n"); + + mDisplayWindow = NULL; + + // the "locker" mutex keeps this from hosing CopyFromShared() + if (mpShmem != NULL) { + //printf("--- DELETING shmem, addr=%p\n", mpShmem->getAddr()); + delete mpShmem; + mpShmem = NULL; + } +} + +/* + * Make a local copy of the image data. The UI grabs this data from a + * different thread, so we have to mutex it. + */ +void DeviceManager::Display::CopyFromShared(void) +{ + wxMutexLocker locker(mImageDataLock); + + if (mpShmem == NULL) { + //printf("Sim: SKIP CopyFromShared\n"); + return; + } + + //printf("Display %d: copying data from %p to %p\n", + // mDisplayNum, mpShmem->getAddr(), mImageData); + + /* data is always 24bpp RGB */ + mpShmem->lock(); // avoid tearing + memcpy(mImageData, mpShmem->getAddr(), mWidth * mHeight * 3); + mpShmem->unlock(); +} + +/* + * Get the image data in the form of a newly-allocated bitmap. + * + * This MUST be called from the UI thread. Creating wxBitmaps in the + * runtime management thread will cause X11 failures (e.g. + * "Xlib: unexpected async reply"). + */ +wxBitmap* DeviceManager::Display::GetImageData(void) +{ + wxMutexLocker locker(mImageDataLock); + + assert(mImageData != NULL); + + //printf("HEY: creating tmpImage, w=%d h=%d data=%p\n", + // mWidth, mHeight, mImageData); + + /* create a temporary wxImage; it does not own the data */ + wxImage tmpImage(mWidth, mHeight, (unsigned char*) mImageData, true); + + /* return a new bitmap with the converted-for-display data */ + return new wxBitmap(tmpImage); +} + + +/* + * =========================================================================== + * DeviceManager::DeviceThread + * =========================================================================== + */ + +/* + * Some notes on process management under Linux/Mac OS X: + * + * We want to put the runtime into its own process group. That way we + * can send SIGKILL to the entire group to guarantee that we kill it and + * all of its children. Simply killing the sim's direct descendant doesn't + * do what we want. If it's a debugger, we will just orphan the runtime + * without killing it. Even if the runtime is our child, the children of + * the runtime might outlive it. + * + * We want to be able to run the child under GDB or Valgrind, both + * of which take input from the tty. They need to be in the "foreground" + * process group. We might be debugging or valgrinding the simulator, + * or operating in a command-line-only "headless" mode, so in that case + * the sim front-end should actually be in the foreground group. + * + * Putting the runtime in the background group means it can't read input + * from the tty (not an issue) and will generate SIGTTOU signals when it + * writes output to the tty (easy to ignore). The trick, then, is to + * have the simulator and gdb/valgrind in the foreground pgrp while the + * runtime itself is in a different group. This group needs to be known + * to the simulator so that it can send signals to the appropriate place. + * + * The solution is to have the runtime process change its process group + * after it starts but before it creates any new processes, and then send + * the process group ID back to the simulator. The sim can then send + * signals to the pgrp to ensure that we don't end up with zombies. Any + * "pre-launch" processes, like GDB, stay in the sim's pgrp. This also + * allows a consistent API for platforms that don't have fork/exec + * (e.g. MinGW). + * + * This doesn't help us with interactive valgrind (e.g. --db-attach=yes), + * because valgrind is an LD_PRELOAD shared library rather than a + * separate process. For that, we actually need to use termios(3) to + * change the terminal's pgrp, or the interactive stuff just doesn't work. + * We don't want to do that every time or attempting to debug the simulator + * front-end will have difficulties. + * + * Making this even more entertaining is the fact that the simulator + * front-end could itself be launched in the background. It's essential + * that we be careful about assigning a process group to the foreground, + * and that we don't restore ourselves unless we were in the foreground to + * begin with. + * + * + * Some notes on process management under Windows (Cygwin, MinGW): + * + * Signals cannot be caught or ignored under MinGW. All signals are fatal. + * + * Signals can be ignored under Cygwin, but not caught. + * + * Windows has some process group stuff (e.g. CREATE_NEW_PROCESS_GROUP flag + * and GenerateConsoleCtrlEvent()). Need to explore. + * + * + * UPDATE: we've abandoned Mac OS and MinGW, so we now launch the runtime in + * a separate xterm. This avoids all tty work on our side. We still need + * to learn the pgrp from the child during the initial communication + * handshake so we can do necessary cleanup. + */ + + +/* + * Convert a space-delimited string into an argument vector. + * + * "arg" is the current arg offset. + */ +static int stringToArgv(char* mangle, const char** argv, int arg, int maxArgs) +{ + bool first = true; + + while (*mangle != '\0') { + assert(arg < maxArgs); + if (first) { + argv[arg++] = mangle; + first = false; + } + if (*mangle == ' ') { + *mangle = '\0'; + first = true; + } + mangle++; + } + + return arg; +} + +/* + * Launch the runtime process in its own terminal window. Start by setting + * up the argument vector to the runtime process. + * + * The last entry in the vector will be a NULL pointer. + * + * This is awkward and annoying because the wxWidgets strings are current + * configured for UNICODE. + */ +/*static*/ bool DeviceManager::DeviceThread::LaunchProcess(wxWindow* statusWindow) +{ + static const char* kLaunchWrapper = "launch-wrapper"; + const int kMaxArgs = 64; + Preferences* pPrefs; + wxString errMsg; + wxString runtimeExe; + wxString debuggerExe; + wxString debuggerScript; + wxString valgrinderExe; + wxString launchWrapperExe; + wxString launchWrapperArgs; + wxString javaAppName; + wxString termCmd; + wxString tmpStr; + char gammaVal[8]; + //bool bval; + double dval; + bool result = false; + bool doDebug, doValgrind, doCheckJni, doEnableSound, doEnableFakeCamera; + const char** argv = NULL; + int arg; + wxCharBuffer runtimeExeTmp; + wxCharBuffer debuggerExeTmp; + wxCharBuffer debuggerScriptTmp; + wxCharBuffer javaAppNameTmp; + wxCharBuffer valgrinderExeTmp; + wxCharBuffer termCmdTmp; + wxCharBuffer launchWrapperExeTmp; + wxCharBuffer launchWrapperArgsTmp; + + pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + if (pPrefs == NULL) { + errMsg = wxT("Preferences were not loaded."); + goto bail; + } + + /* + * Set environment variables. This stuff should be passed through as + * arguments, but the runtime binary currently has a disconnect + * between main() and the VM initilization. + * + * TODO: remove this in favor of system properties + */ +#if 0 + // TODO: restore this + doCheckJni = false; + pPrefs->GetBool("check-jni", &doCheckJni); +#endif + + tmpStr.Empty(); + pPrefs->GetString("ld-assume-kernel", /*ref*/ tmpStr); + if (tmpStr.IsEmpty()) { + unsetenv("LD_ASSUME_KERNEL"); + } else { + setenv("LD_ASSUME_KERNEL", tmpStr.ToAscii(), 1); + } + + doEnableSound = false; + pPrefs->GetBool("enable-sound", &doEnableSound); + if (doEnableSound) + setenv("ANDROIDSOUND", "1", 1); + + doEnableFakeCamera = false; + pPrefs->GetBool("enable-fake-camera", &doEnableFakeCamera); + if (doEnableFakeCamera) + setenv("ANDROIDFAKECAMERA", "1", 1); + + /* + * Set the Dalvik bootstrap class path. Normally this is set by "init". + */ + setenv("BOOTCLASSPATH", + "/system/framework/core.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar", + 1); + + /* + * Figure out where the "runtime" binary lives. + */ + runtimeExe = ((MyApp*)wxTheApp)->GetRuntimeExe(); + assert(!runtimeExe.IsEmpty()); + + //UpdateLastModified(); + + /* + * Initialize argv. + */ + argv = new const char*[kMaxArgs]; + if (argv == NULL) + goto bail; + arg = 0; + + /* + * We want to launch the runtime in its own terminal window so we don't + * have to fight over who gets access to the controlling tty. We allow + * the user to specify the command they want to use to perform the + * launch. Here we cut it into pieces for argv. + * + * To make life easier here, we require that the launch command be + * all one piece, i.e. it's not "xterm -e -geom blah" with our + * stuff in the middle. + */ + termCmd.Empty(); + pPrefs->GetString("launch-command", /*ref*/ termCmd); + if (termCmd.IsEmpty()) { + fprintf(stderr, "Sim: WARNING: launch-command is empty\n"); + } else { + termCmdTmp = termCmd.ToAscii(); + char* mangle = strdup(termCmdTmp); + arg = stringToArgv(mangle, argv, arg, kMaxArgs); + } + + /* + * The "launch-wrapper" binary lives in the same place as the runtime. + * This sets up LD_PRELOAD and some other environment variables. + */ + int charIdx; + + charIdx = runtimeExe.Find('/', true); + if (charIdx == -1) { + launchWrapperExe = wxString::FromAscii(kLaunchWrapper); + } else { + launchWrapperExe = runtimeExe.Mid(0, charIdx+1); + launchWrapperExe.Append(wxString::FromAscii(kLaunchWrapper)); + } + printf("Sim launch wrapper: %s\n", (const char*)launchWrapperExe.ToAscii()); + + argv[arg++] = launchWrapperExeTmp = launchWrapperExe.ToAscii(); + + launchWrapperArgs.Empty(); + pPrefs->GetString("launch-wrapper-args", /*ref*/ launchWrapperArgs); + if (!launchWrapperArgs.IsEmpty()) { + launchWrapperArgsTmp = launchWrapperArgs.ToAscii(); + char* mangle = strdup(launchWrapperArgsTmp); + arg = stringToArgv(mangle, argv, arg, kMaxArgs); + } + + /* + * If we're launching under GDB or valgrind, set that up. + */ + doDebug = doValgrind = false; + pPrefs->GetBool("debug", &doDebug); + if (((MyApp*)wxTheApp)->GetDebuggerOption()) { + doDebug = true; + } + debuggerScript = ((MyApp*)wxTheApp)->GetDebuggerScript(); + + pPrefs->GetBool("valgrind", &doValgrind); + if (doDebug || doValgrind) { + + pPrefs->GetString("debugger", /*ref*/ debuggerExe); + pPrefs->GetString("valgrinder", /*ref*/ valgrinderExe); + + // check for empty or undefined preferences + if (doDebug && debuggerExe.IsEmpty()) { + errMsg = wxT("Debugger not defined."); + goto bail; + } + if (doValgrind && valgrinderExe.IsEmpty()) { + errMsg = wxT("Valgrinder not defined."); + goto bail; + } + + if (doValgrind) { + argv[arg++] = valgrinderExeTmp = valgrinderExe.ToAscii(); + //argv[arg++] = "--tool=callgrind"; + argv[arg++] = "--tool=memcheck"; + argv[arg++] = "--leak-check=yes"; // check for leaks too + argv[arg++] = "--leak-resolution=med"; // increase from 2 to 4 + argv[arg++] = "--num-callers=8"; // reduce from 12 to 8 + //argv[arg++] = "--show-reachable=yes"; // show still-reachable + if (doDebug) { + //mTerminalFollowsChild = true; // interactive + argv[arg++] = "--db-attach=yes"; + } + //mSlowExit = true; + } else /*doDebug*/ { + argv[arg++] = debuggerExeTmp = debuggerExe.ToAscii(); + if (!debuggerScript.IsEmpty()) { + argv[arg++] = "-x"; + argv[arg++] = debuggerScriptTmp = debuggerScript.ToAscii(); + } + argv[arg++] = runtimeExeTmp = runtimeExe.ToAscii(); + argv[arg++] = "--args"; + } + } + + /* + * Get runtime args. + */ + + argv[arg++] = runtimeExeTmp = (const char*) runtimeExe.ToAscii(); + + javaAppName = ((MyApp*)wxTheApp)->GetAutoRunApp(); + if (javaAppName.IsEmpty()) { + if (!pPrefs->GetString("java-app-name", /*ref*/ javaAppName)) { + javaAppName = wxT(""); + } + } + + if (!javaAppName.IsEmpty()) + { + argv[arg++] = "-j"; + argv[arg++] = javaAppNameTmp = (const char*) javaAppName.ToAscii(); + } + + if (pPrefs->GetDouble("gamma", &dval) && dval != 1.0) { + snprintf(gammaVal, sizeof(gammaVal), "%.3f", dval); + argv[arg++] = "-g"; + argv[arg++] = gammaVal; + } + + /* finish arg set */ + argv[arg++] = NULL; + + assert(arg <= kMaxArgs); + +#if 1 + printf("ARGS:\n"); + for (int i = 0; i < arg; i++) + printf(" %d: '%s'\n", i, argv[i]); +#endif + + if (fork() == 0) { + execvp(argv[0], (char* const*) argv); + fprintf(stderr, "execvp '%s' failed: %s\n", argv[0], strerror(errno)); + exit(1); + } + + /* + * We assume success; if it didn't succeed we'll just sort of hang + * out waiting for a connection. There are ways to fix this (create + * a non-close-on-exec pipe and watch to see if the other side closes), + * but at this stage it's not worthwhile. + */ + result = true; + + tmpStr = wxT("=== launched "); + tmpStr += runtimeExe; + LogWindow::PostLogMsg(tmpStr); + + assert(errMsg.IsEmpty()); + +bail: + if (!errMsg.IsEmpty()) { + assert(result == false); + + UserEventMessage* pUem = new UserEventMessage; + pUem->CreateErrorMessage(errMsg); + + UserEvent uev(0, (void*) pUem); + + assert(statusWindow != NULL); + statusWindow->AddPendingEvent(uev); + } + delete[] argv; + return result; +} + +/* + * This is the entry point for the device thread. The thread launches the + * runtime process and monitors it. When the runtime exits, the thread + * exits. + * + * Because this isn't running in the UI thread, any user interaction has + * to be channeled through "user events" to the appropriate window. + */ +void* DeviceManager::DeviceThread::Entry(void) +{ + //android::MessageStream stream; + android::Message msg; + wxString errMsg; + char statusBuf[64] = "(no status)"; + int result = 1; + + /* print this so we can make sense of log messages */ + LOG(LOG_DEBUG, "", "Sim: device management thread starting (pid=%d)\n", + getpid()); + + assert(mReader != NULL && mWriter != NULL); + + /* + * Tell the main thread that we're running. If something fails here, + * we'll send them a "stopped running" immediately afterward. + */ + { + UserEventMessage* pUem = new UserEventMessage; + pUem->CreateRuntimeStarted(); + + UserEvent uev(0, (void*) pUem); + + assert(mpStatusWindow != NULL); + mpStatusWindow->AddPendingEvent(uev); + } + LogWindow::PostLogMsg( + "=============================================================="); + LogWindow::PostLogMsg("=== runtime starting"); + + /* + * Establish contact with runtime. + */ + if (!mStream.init(mReader, mWriter, true)) { + errMsg = wxT("ERROR: Unable to establish communication with runtime.\n"); + goto bail; + } + + /* + * Tell the runtime to put itself into a new process group and set + * itself up as the foreground process. The latter is only really + * necessary to make valgrind+gdb work. + */ + msg.setCommand(android::Simulator::kCommandNewPGroup, true); + mStream.send(&msg); + + printf("Sim: Sending hardware configuration\n"); + + /* + * Send display config. + * + * Right now we're just shipping a big binary blob over. + */ + assert(android::Simulator::kValuesPerDisplay >= 5); + long buf[1 + 1 + mpDeviceManager->GetNumDisplays() * + android::Simulator::kValuesPerDisplay]; + buf[0] = android::Simulator::kDisplayConfigMagic; + buf[1] = mpDeviceManager->GetNumDisplays(); + for (int i = 0; i < mpDeviceManager->GetNumDisplays(); i++) { + DeviceManager::Display* pDisplay = mpDeviceManager->GetDisplay(i); + long* pBuf = &buf[2 + android::Simulator::kValuesPerDisplay * i]; + + pBuf[0] = pDisplay->GetWidth(); + pBuf[1] = pDisplay->GetHeight(); + pBuf[2] = pDisplay->GetFormat(); + pBuf[3] = pDisplay->GetRefresh(); + pBuf[4] = pDisplay->GetShmemKey(); + } + msg.setRaw((const unsigned char*)buf, sizeof(buf), + android::Message::kCleanupNoDelete); + mStream.send(&msg); + + /* + * Send other hardware config. + * + * Examples: + * - Available input devices. + * - Set of buttons on device. + * - External devices (Bluetooth, etc). + * - Initial mode (e.g. "flipped open" vs. "flipped closed"). + */ + + msg.setConfig("keycharmap", mpDeviceManager->GetKeyMap()); + mStream.send(&msg); + + /* + * Done with config. + */ + msg.setCommand(android::Simulator::kCommandConfigDone, 0); + mStream.send(&msg); + + /* + * Sit forever, waiting for messages from the runtime process. + */ + while (1) { + if (!mStream.recv(&msg, true)) { + /* + * The read failed. This usually means the child has died. + */ + printf("Sim: runtime process has probably died\n"); + break; + } + + if (msg.getType() == android::Message::kTypeCommand) { + int cmd, arg; + + if (!msg.getCommand(&cmd, &arg)) { + fprintf(stderr, "Sim: Warning: failed unpacking command\n"); + /* keep going? */ + } else { + switch (cmd) { + case android::Simulator::kCommandNewPGroupCreated: + // runtime has moved into a separate process group + // (not expected for external) + printf("Sim: child says it's now in pgrp %d\n", arg); + mRuntimeProcessGroup = arg; + break; + case android::Simulator::kCommandRuntimeReady: + // sim is up and running, do late init + break; + case android::Simulator::kCommandUpdateDisplay: + // new frame of graphics is ready + //printf("RCVD display update %d\n", arg); + mpDeviceManager->ShowFrame(arg); + break; + case android::Simulator::kCommandVibrate: + // vibrator on or off + //printf("RCVD vibrator update %d\n", arg); + mpDeviceManager->Vibrate(arg); + break; + default: + printf("Sim: got unknown command %d/%d\n", cmd, arg); + break; + } + } + } else if (msg.getType() == android::Message::kTypeLogBundle) { + android_LogBundle bundle; + + if (!msg.getLogBundle(&bundle)) { + fprintf(stderr, "Sim: Warning: failed unpacking logBundle\n"); + /* keep going? */ + } else { + LogWindow::PostLogMsg(&bundle); + } + } else { + printf("Sim: got unknown message type=%d\n", msg.getType()); + } + } + + result = 0; + +bail: + printf("Sim: DeviceManager thread preparing to exit\n"); + + /* kill the comm channel; should encourage runtime to die */ + mStream.close(); + delete mReader; + delete mWriter; + mReader = mWriter = NULL; + + /* + * We never really did get a "friendly death" working, so just slam + * the thing if we have the process group. + */ + if (mRuntimeProcessGroup != 0) { + /* kill the group, not our immediate child */ + printf("Sim: killing pgrp %d\n", (int) mRuntimeProcessGroup); + kill(-mRuntimeProcessGroup, 9); + } + + if (!errMsg.IsEmpty()) { + UserEventMessage* pUem = new UserEventMessage; + pUem->CreateErrorMessage(errMsg); + + UserEvent uev(0, (void*) pUem); + mpStatusWindow->AddPendingEvent(uev); + } + + /* notify the main window that the runtime has stopped */ + { + UserEventMessage* pUem = new UserEventMessage; + pUem->CreateRuntimeStopped(); + + UserEvent uev(0, (void*) pUem); + mpStatusWindow->AddPendingEvent(uev); + } + + /* show exit status in log file */ + wxString exitMsg; + exitMsg.Printf(wxT("=== runtime exiting - %s"), statusBuf); + LogWindow::PostLogMsg(exitMsg); + LogWindow::PostLogMsg( + "==============================================================\n"); + + /* + * Reset system properties for future runs. + */ + ResetProperties(); + + return (void*) result; +} + + +/* + * Wait for a little bit to see if the thread will exit. + * + * "delay" is in 0.1s increments. + */ +void DeviceManager::DeviceThread::WaitForDeath(int delay) +{ + const int kDelayUnit = 100000; + int i; + + for (i = 0; i < delay; i++) { + if (!IsRunning()) + return; + usleep(kDelayUnit); + } +} + + +/* + * Kill the runtime process. The goal is to cause our local runtime + * management thread to exit. If it doesn't, this will kill the thread + * before it returns. + */ +void DeviceManager::DeviceThread::KillChildProcesses(void) +{ + if (!this->IsRunning()) + return; + + /* clear "slow exit" flag -- we're forcefully killing this thing */ + //this->mSlowExit = false; + + /* + * Use the ChildProcess object in the thread to send signals. There's + * a risk that the DeviceThread will exit and destroy the object while + * we're using it. Using a mutex here gets a little awkward because + * we can't put it in DeviceThread. It's easier to make a copy of + * ChildProcess and operate on the copy, but we have to do that very + * carefully to avoid interfering with the communcation pipes. + * + * For now, we just hope for the best. FIX this someday. + * + * We broadcast to the process group, which will ordinarily kill + * everything. If we're running with valgrind+GDB everything is in our + * pgrp and we can't do the broadcast; if GDB alone, then only GDB is + * in our pgrp, so the broadcast will hit everything except it. We + * hit the group and then hit our child for good measure. + */ + if (mRuntimeProcessGroup != 0) { + /* kill the group, not our immediate child */ + printf("Sim: killing pgrp %d\n", (int) mRuntimeProcessGroup); + kill(-mRuntimeProcessGroup, 9); + WaitForDeath(15); + } + + /* + * Close the communication channel. This should cause our thread + * to snap out of its blocking read and the runtime thread to bail + * out the next time it tries to interact with us. We should only + * get here if somebody other than our direct descendant has the + * comm channel open and our broadcast didn't work, which should + * no longer be possible. + */ + if (this->IsRunning()) { + printf("Sim: killing comm channel\n"); + mStream.close(); + delete mReader; + delete mWriter; + mReader = mWriter = NULL; + WaitForDeath(15); + } + + /* + * At this point it's possible that our DeviceThread is just wedged. + * Kill it. + * + * Using the thread Kill() function can orphan resources, including + * locks and semaphores. There is some risk that the simulator will + * be hosed after this. + */ + if (this->IsRunning()) { + fprintf(stderr, "Sim: WARNING: killing runtime thread (%ld)\n", + (long) GetId()); + this->Kill(); + WaitForDeath(15); + } + + /* + * Now I'm scared. + */ + if (this->IsRunning()) { + fprintf(stderr, "Sim: thread won't die!\n"); + } +} + + +/* + * Configure system properties for the simulated device. + * + * Property requests can arrive *before* the full connection to the + * simulator is established, so we want to reset these during cleanup. + */ +void DeviceManager::DeviceThread::ResetProperties(void) +{ + wxWindow* mainFrame = ((MyApp*)wxTheApp)->GetMainFrame(); + PropertyServer* props = ((MainFrame*)mainFrame)->GetPropertyServer(); + + props->ClearProperties(); + props->SetDefaultProperties(); +} + + +#if 0 +/* + * Return true if the executable found is newer than + * what is currently running + */ +bool DeviceManager::DeviceThread::IsRuntimeNew(void) +{ + if (mLastModified == 0) { + /* + * Haven't called UpdateLastModified yet, or called it but + * couldn't stat() the executable. + */ + return false; + } + + struct stat status; + if (stat(mRuntimeExe.ToAscii(), &status) == 0) { + return (status.st_mtime > mLastModified); + } else { + // doesn't exist, so it can't be newer + fprintf(stderr, "Sim: unable to stat '%s': %s\n", + (const char*) mRuntimeExe.ToAscii(), strerror(errno)); + return false; + } +} + +/* + * Updates mLastModified to reflect the current executables mtime + */ +void DeviceManager::DeviceThread::UpdateLastModified(void) +{ + struct stat status; + if (stat(mRuntimeExe.ToAscii(), &status) == 0) { + mLastModified = status.st_mtime; + } else { + fprintf(stderr, "Sim: unable to stat '%s': %s\n", + (const char*) mRuntimeExe.ToAscii(), strerror(errno)); + mLastModified = 0; + } +} +#endif + diff --git a/simulator/app/DeviceManager.h b/simulator/app/DeviceManager.h new file mode 100644 index 000000000..bd4371e87 --- /dev/null +++ b/simulator/app/DeviceManager.h @@ -0,0 +1,284 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Class that manages the simulated device. +// +#ifndef _SIM_DEVICE_MANAGER_H +#define _SIM_DEVICE_MANAGER_H + +#include "UserEvent.h" + +#include "Shmem.h" +#include "MessageStream.h" +#include "SimRuntime.h" + +#include "ui/PixelFormat.h" +#include "ui/KeycodeLabels.h" + +#include + +/* + * Manage the simulated device. This includes starting/stopping as well + * as sending messages to it and receiving events from it. + * + * The object may span multiple invocations of a specific device. If + * the simulator is reconfigured to use a device with different + * characteristics, the object should be destroyed and recreated (which + * guarantees that the runtime is restarted). + */ +class DeviceManager { +public: + DeviceManager(void); + virtual ~DeviceManager(void); + + /* + * Initialize the object. Call this once. + * + * "numDisplays" is the number of displays that the simulated hardware + * supports. The displays themselves are configured with separate calls. + * + * "statusWindow" should be the main frame. Messages indicating runtime + * startup/shutdown are sent, as well as error messages that should be + * displayed in message boxes. + */ + bool Init(int numDisplays, wxWindow* statusWindow); + bool IsInitialized(void) const; + + /* + * Tell the device manager that the windows used to display its output + * are closing down. + */ + void WindowsClosing(void); + + /* + * "displayWindow" is the window to notify when a new frame of graphics + * data is available. This can be set independently for each display. + */ + bool SetDisplayConfig(int displayIndex, wxWindow* window, + int width, int height, android::PixelFormat format, int refresh); + + /* + * set the key map + */ + bool SetKeyboardConfig(const char *keymap); + + /* + * Return the number of displays we're configured for. + */ + int GetNumDisplays(void) const { return mNumDisplays; } + + /* + * Return the shmem key for the Nth display. + */ + //int GetShmemKey(int displayIndex); + + /* + * Is the runtime process still running? + */ + bool IsRunning(void) const { + if (mThread != NULL) + return mThread->IsRunning(); + return false; + } + bool IsKillable(void) const { + return true; + } + + // (Re-)configure the device, e.g. when #of displays changes because + // a different phone model has been selected. Call this before doing + // any display-specific setup. DO NOT call this if the runtime is active. +// void Configure(int numDisplays); + + // start the runtime, acting as parent + bool StartRuntime(void); + // start the runtime, acting as peer + bool StartRuntime(android::Pipe* reader, android::Pipe* writer); + // politely ask the runtime to stop + bool StopRuntime(void); + // kill the runtime with extreme prejudice + void KillRuntime(void); + +#if 0 + // Returns if the executable is new + bool RefreshRuntime(void); + // Update the time of the current runtime because the user cancelled a + // refresh + void UserCancelledRefresh(void); +#endif + + // send a key-up or key-down event to the runtime + void SendKeyEvent(KeyCode keyCode, bool down); + // send touch-screen events + void SendTouchEvent(android::Simulator::TouchMode mode, int x, int y); + + wxBitmap* GetImageData(int displayIndex); + + void BroadcastEvent(UserEvent &userEvent); + +private: + /* + * Threads in wxWidgets use sub-classing to define interfaces and + * entry points. We use this to create the thread that interacts + * with the runtime. + * + * The "reader" and "writer" arguments may be NULL. If they are, + * we will launch the runtime ourselves. If not, we will use them + * to speak with an externally-launched runtime process. The thread + * will own the pipes, shutting them down when it exits. + */ + class DeviceThread : public wxThread { + public: + DeviceThread(DeviceManager* pDM, wxWindow* pStatusWindow, + android::Pipe* reader, android::Pipe* writer) + : wxThread(wxTHREAD_JOINABLE), mpStatusWindow(pStatusWindow), + mReader(reader), mWriter(writer), + mpDeviceManager(pDM), /*mTerminalFollowsChild(false), + mSlowExit(false), mIsExternal(false), mLastModified(0),*/ + mRuntimeProcessGroup(0) + {} + virtual ~DeviceThread(void) { + delete mReader; + delete mWriter; + } + + /* thread entry point */ + virtual void* Entry(void); + + // wxThread class supplies an IsRunning() method + + /* + * This kills the runtime process to force this thread to exit. + * If the thread doesn't exit after a short period of time, it + * is forcibly terminated. + */ + void KillChildProcesses(void); + +#if 0 + /* + * Return if the runtime executable is new + */ + bool IsRuntimeNew(void); + + void UpdateLastModified(void); +#endif + + android::MessageStream* GetStream(void) { return &mStream; } + + static bool LaunchProcess(wxWindow* statusWindow); + + private: + void WaitForDeath(int delay); + void ResetProperties(void); + + android::MessageStream mStream; + wxWindow* mpStatusWindow; + android::Pipe* mReader; + android::Pipe* mWriter; + DeviceManager* mpDeviceManager; + pid_t mRuntimeProcessGroup; + //time_t mLastModified; + wxString mRuntimeExe; + }; + + friend class DeviceThread; + + /* + * We need one of these for each display on the device. Most devices + * only have one, but some flip phones have two. + */ + class Display { + public: + Display(void) + : mDisplayWindow(NULL), mpShmem(NULL), mShmemKey(0), + mImageData(NULL), mDisplayNum(-1), mWidth(-1), mHeight(-1), + mFormat(android::PIXEL_FORMAT_UNKNOWN), mRefresh(0) + {} + ~Display() { + delete mpShmem; + delete[] mImageData; + } + + /* initialize goodies */ + bool Create(int displayNum, wxWindow* window, int width, int height, + android::PixelFormat format, int refresh); + + /* call this if we're shutting down soon */ + void Uncreate(void); + + /* copy & convert data from shared memory */ + void CopyFromShared(void); + + /* get image data in the form of a 24bpp bitmap */ + wxBitmap* GetImageData(void); + + /* get a pointer to our display window */ + wxWindow* GetWindow(void) const { return mDisplayWindow; } + + /* get our shared memory key */ + int GetShmemKey(void) const { return mShmemKey; } + + int GetWidth(void) const { return mWidth; } + int GetHeight(void) const { return mHeight; } + android::PixelFormat GetFormat(void) const { return mFormat; } + int GetRefresh(void) const { return mRefresh; } + + private: + int GenerateKey(int displayNum) { + return 0x41544d00 | displayNum; + } + + // control access to image data shared between runtime mgr and UI + wxMutex mImageDataLock; + // we send an event here when we get stuff to display + wxWindow* mDisplayWindow; + + // shared memory segment + android::Shmem* mpShmem; + int mShmemKey; + + // local copy of data from shared mem, converted to 24bpp + unsigned char* mImageData; + + // mainly for debugging -- which display are we? + int mDisplayNum; + + // display characteristics + int mWidth; + int mHeight; + android::PixelFormat mFormat; + int mRefresh; // fps + }; + + Display* GetDisplay(int dispNum) { return &mDisplay[dispNum]; } + + const char* GetKeyMap() { return mKeyMap ? mKeyMap : "qwerty"; } + + void ShowFrame(int displayIndex); + + void Vibrate(int vibrateOn); + + // get the message stream from the device thread + android::MessageStream* GetStream(void); + + // send a request to set the visible layers + void SendSetVisibleLayers(void); + + // points at the runtime's thread (while it's running) + DeviceThread* mThread; + + // array of Displays, one per display on the device + Display* mDisplay; + int mNumDisplays; + + // the key map + const char * mKeyMap; + + // which graphics layers are visible? + int mVisibleLayers; + + // where to send status messages + wxWindow* mpStatusWindow; + +}; + +#endif // _SIM_DEVICE_MANAGER_H diff --git a/simulator/app/DeviceWindow.cpp b/simulator/app/DeviceWindow.cpp new file mode 100644 index 000000000..619a614c6 --- /dev/null +++ b/simulator/app/DeviceWindow.cpp @@ -0,0 +1,264 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Displays output from the device. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +#include "wx/image.h" // needed for Windows build +#include "wx/dcbuffer.h" + +#include "AssetStream.h" +#include "DeviceWindow.h" +#include "MyApp.h" +#include "Preferences.h" + +BEGIN_EVENT_TABLE(DeviceWindow, wxWindow) + EVT_SIZE(DeviceWindow::OnSize) + EVT_ERASE_BACKGROUND(DeviceWindow::OnErase) + EVT_PAINT(DeviceWindow::OnPaint) + EVT_KEY_DOWN(DeviceWindow::OnKeyDown) + EVT_KEY_UP(DeviceWindow::OnKeyUp) + + EVT_LEFT_DOWN(DeviceWindow::OnMouseLeftDown) + EVT_LEFT_DCLICK(DeviceWindow::OnMouseLeftDown) + EVT_LEFT_UP(DeviceWindow::OnMouseLeftUp) + EVT_RIGHT_DOWN(DeviceWindow::OnMouseRightDown) + EVT_RIGHT_DCLICK(DeviceWindow::OnMouseRightDown) + EVT_RIGHT_UP(DeviceWindow::OnMouseRightUp) + EVT_MOTION(DeviceWindow::OnMouseMotion) + + EVT_USER_EVENT(DeviceWindow::OnUserEvent) +END_EVENT_TABLE() + + +/* + * Create a new DeviceWindow. This should be a child of PhoneWindow. + * + * Note the DeviceManager may not be fully initialized yet. + */ +DeviceWindow::DeviceWindow(wxWindow* parent, DeviceManager* pDM) + : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, + wxNO_BORDER | wxWANTS_CHARS), + mpDeviceManager(pDM) +{ + //printf("DW: created (parent=%p DM=%p)\n", parent, pDM); + + SetBackgroundStyle(wxBG_STYLE_CUSTOM); + + // create a trivial bitmap so we have something allocated + mBitmap.Create(1, 1); + +} + +/* + * Destructor. + */ +DeviceWindow::~DeviceWindow(void) +{ +} + +/* + * We don't want to trap key or mouse events here. + * + * event.Skip() didn't seem to do the trick, so we call AddPendingEvent() + * to add it to the parent's input queue. + */ +void DeviceWindow::OnKeyDown(wxKeyEvent& event) +{ + //printf("DW: down: %d\n", event.GetKeyCode()); + GetParent()->AddPendingEvent(event); +} +void DeviceWindow::OnKeyUp(wxKeyEvent& event) +{ + //printf("DW: up: %d\n", event.GetKeyCode()); + GetParent()->AddPendingEvent(event); +} + +/* + * Handle mouse events. We want to pass these up to the PhoneWindow, since + * that's where the "touch screen" code is. + */ +void DeviceWindow::OnMouseLeftDown(wxMouseEvent& event) +{ + ClampMouse(&event); + GetParent()->AddPendingEvent(event); +} +void DeviceWindow::OnMouseLeftUp(wxMouseEvent& event) +{ + ClampMouse(&event); + GetParent()->AddPendingEvent(event); +} +void DeviceWindow::OnMouseRightDown(wxMouseEvent& event) +{ + ClampMouse(&event); + GetParent()->AddPendingEvent(event); +} +void DeviceWindow::OnMouseRightUp(wxMouseEvent& event) +{ + ClampMouse(&event); + GetParent()->AddPendingEvent(event); +} +void DeviceWindow::OnMouseMotion(wxMouseEvent& event) +{ + ClampMouse(&event); + GetParent()->AddPendingEvent(event); +} + +/* + * Clamp the mouse movement to the window bounds. + */ +void DeviceWindow::ClampMouse(wxMouseEvent* pEvent) +{ + wxWindow* pEventWindow = (wxWindow*) pEvent->GetEventObject(); + int width, height; + + pEventWindow->GetSize(&width, &height); + if (pEvent->m_x < 0) + pEvent->m_x = 0; + else if (pEvent->m_x >= width) + pEvent->m_x = width-1; + + if (pEvent->m_y < 0) + pEvent->m_y = 0; + else if (pEvent->m_y >= height) + pEvent->m_y = height-1; +} + + +/* + * Handle a "user event". We get these when the runtime wants us to + * know that it has a new frame of graphics to display. + * + */ +void DeviceWindow::OnUserEvent(UserEvent& event) +{ + wxBitmap* pBitmap; + int displayIndex; + + displayIndex = (int) event.GetData(); + + //printf("GOT UAE %d\n", displayIndex); + + // a displayIndex of -1 means just update the onion skin + if (displayIndex >= 0) { + /* get a newly-allocated bitmap with converted image data */ + pBitmap = mpDeviceManager->GetImageData(displayIndex); + + /* do a ptr/refcount assignment to hold the data */ + mBitmap = *pBitmap; + /* delete the temporary object; does not delete the bitmap storage */ + delete pBitmap; + } + + if (displayIndex >= -1) { + mHasOnionSkinBitmap = false; + + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + + bool overlayOnionSkin; + char* onionSkinFileName = NULL; + + bool overlayOnionSkinExists = pPrefs->GetBool("overlay-onion-skin", &overlayOnionSkin); + if (overlayOnionSkinExists && overlayOnionSkin) { + bool fileNameExists = pPrefs->GetString("onion-skin-file-name", &onionSkinFileName); + if (fileNameExists && *onionSkinFileName) { + wxImage onionSkinImage(wxString::FromAscii(onionSkinFileName)); + onionSkinImage.SetAlpha(NULL); + bool hasAlpha = onionSkinImage.HasAlpha(); + int width = onionSkinImage.GetWidth(); + int height = onionSkinImage.GetHeight(); + if (hasAlpha) { + unsigned char *alpha = onionSkinImage.GetAlpha(); + int alphaVal = 127; + pPrefs->GetInt("onion-skin-alpha-value", &alphaVal); + for (int i = (width * height) - 1; i >= 0; i--) { + alpha[i] = alphaVal; + } + } + mOnionSkinBitmap = wxBitmap(onionSkinImage); + mHasOnionSkinBitmap = true; + } + } + } + + /* induce an update */ + Refresh(); +} + +/* + * Window has been moved or resized. + * + * We get this when the model of phone is changed. + * + * FIX: in the future this only happens when the phone is rotated 90deg. + */ +void DeviceWindow::OnSize(wxSizeEvent& WXUNUSED(event)) +{ + int width, height; + + GetClientSize(&width, &height); + printf("Sim: device window resize: %dx%d\n", width, height); + + mBitmap.Create(width, height); + + wxMemoryDC memDC; + memDC.SelectObject(mBitmap); + + wxColour backColor(96, 122, 121); + memDC.SetBrush(wxBrush(backColor)); + memDC.SetPen(wxPen(backColor, 1)); + wxRect windowRect(wxPoint(0, 0), GetClientSize()); + memDC.DrawRectangle(windowRect); +} + +/* + * No need to erase the background. + */ +void DeviceWindow::OnErase(wxEraseEvent& WXUNUSED(event)) +{ + //printf("erase device\n"); +} + +/* + * Repaint the simulator output. + */ +void DeviceWindow::OnPaint(wxPaintEvent& WXUNUSED(event)) +{ + wxPaintDC dc(this); + + /* draw background image */ + dc.DrawBitmap(mBitmap, 0, 0, TRUE); + + /* If necessary, draw onion skin image on top */ + if (mHasOnionSkinBitmap) { + dc.DrawBitmap(mOnionSkinBitmap, 0, 0, TRUE); + } + +#if 0 + // debug - draw the corners + int xoff = 0; + int yoff = 0; + int width; + int height; + GetClientSize(&width, &height); + + dc.SetPen(*wxGREEN_PEN); + dc.DrawLine(xoff, yoff+9, xoff, yoff); + dc.DrawLine(xoff, yoff, xoff+10, yoff); + dc.DrawLine(xoff+width-10, yoff, xoff+width, yoff); + dc.DrawLine(xoff+width-1, yoff, xoff+width-1, yoff+10); + dc.DrawLine(xoff, yoff+height-10, xoff, yoff+height); + dc.DrawLine(xoff, yoff+height-1, xoff+10, yoff+height-1); + dc.DrawLine(xoff+width-1, yoff+height-10, xoff+width-1, yoff+height); + dc.DrawLine(xoff+width-1, yoff+height-1, xoff+width-11, yoff+height-1); +#endif +} + diff --git a/simulator/app/DeviceWindow.h b/simulator/app/DeviceWindow.h new file mode 100644 index 000000000..4548fc3b9 --- /dev/null +++ b/simulator/app/DeviceWindow.h @@ -0,0 +1,70 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Window with simulated phone. +// +#ifndef _SIM_DEVICE_WINDOW_H +#define _SIM_DEVICE_WINDOW_H + +#include "UserEvent.h" +#include "DeviceManager.h" + +/* + * This window displays the device output. + */ +class DeviceWindow : public wxWindow { +public: + DeviceWindow(wxWindow* parent, DeviceManager* pDM); + virtual ~DeviceWindow(void); + +#if 0 // can't work -- can't create bitmaps in other threads + /* this gets tucked into a user event */ + class FrameData { + public: + FrameData(void) + : mDisplayIndex(-1), mpBitmap(NULL) + {} + ~FrameData(void) { + delete mpBitmap; + } + + void Create(int displayIndex, wxBitmap* pBitmap) { + mDisplayIndex = displayIndex; + mpBitmap = pBitmap; + } + + int GetDisplayIndex(void) const { return mDisplayIndex; } + wxBitmap* GetBitmap(void) const { return mpBitmap; } + + private: + int mDisplayIndex; + wxBitmap* mpBitmap; + }; +#endif + + void DeviceManagerClosing(void) { mpDeviceManager = NULL; } + +private: + void OnKeyDown(wxKeyEvent& event); + void OnKeyUp(wxKeyEvent& event); + void OnMouseLeftDown(wxMouseEvent& event); + void OnMouseLeftUp(wxMouseEvent& event); + void OnMouseRightDown(wxMouseEvent& event); + void OnMouseRightUp(wxMouseEvent& event); + void OnMouseMotion(wxMouseEvent& event); + void OnSize(wxSizeEvent& WXUNUSED(event)); + void OnErase(wxEraseEvent& event); + void OnPaint(wxPaintEvent& WXUNUSED(event)); + void OnUserEvent(UserEvent& event); + + void ClampMouse(wxMouseEvent* pEvent); + + DeviceManager* mpDeviceManager; + wxBitmap mBitmap; + wxBitmap mOnionSkinBitmap; + bool mHasOnionSkinBitmap; + + DECLARE_EVENT_TABLE() +}; + +#endif // _SIM_DEVICE_WINDOW_H diff --git a/simulator/app/ExternalRuntime.cpp b/simulator/app/ExternalRuntime.cpp new file mode 100644 index 000000000..9f2cc5e31 --- /dev/null +++ b/simulator/app/ExternalRuntime.cpp @@ -0,0 +1,94 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Management of the simulated device. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +#include "wx/image.h" + +#include "ExternalRuntime.h" +#include "MyApp.h" +#include "UserEvent.h" +#include "UserEventMessage.h" + +#include "SimRuntime.h" +#include "LocalBiChannel.h" +#include "utils.h" + + +using namespace android; + +/* + * Destructor. + */ +ExternalRuntime::~ExternalRuntime(void) +{ + if (IsRunning()) { + // TODO: cause thread to stop, then Wait for it + } + printf("Sim: in ~ExternalRuntime()\n"); +} + +/* + * Create and run the thread. + */ +bool ExternalRuntime::StartThread(void) +{ + if (Create() != wxTHREAD_NO_ERROR) { + fprintf(stderr, "Sim: ERROR: can't create ExternalRuntime thread\n"); + return false; + } + + Run(); + return true; +} + +/* + * Thread entry point. + * + * This just sits and waits for a new connection. It hands it off to the + * main thread and then goes back to waiting. + * + * There is currently no "polite" way to shut this down. + */ +void* ExternalRuntime::Entry(void) +{ + LocalBiChannel lbic; + Pipe* reader; + Pipe* writer; + + reader = writer = NULL; + + if (!lbic.create(ANDROID_PIPE_NAME)) { + fprintf(stderr, "Sim: failed creating named pipe '%s'\n", + ANDROID_PIPE_NAME); + return NULL; + } + + while (lbic.listen(&reader, &writer)) { + /* + * Throw it over the wall. + */ + wxWindow* pMainFrame = ((MyApp*)wxTheApp)->GetMainFrame(); + + UserEventMessage* pUem = new UserEventMessage; + pUem->CreateExternalRuntime(reader, writer); + + UserEvent uev(0, (void*) pUem); + pMainFrame->AddPendingEvent(uev); + + reader = writer = NULL; + } + + printf("Sim: ExternalRuntime thread wants to bail\n"); + + return NULL; +} + diff --git a/simulator/app/ExternalRuntime.h b/simulator/app/ExternalRuntime.h new file mode 100644 index 000000000..774a2cd98 --- /dev/null +++ b/simulator/app/ExternalRuntime.h @@ -0,0 +1,26 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Class that manages the simulated device. +// +#ifndef _SIM_EXTERNAL_RUNTIME_H +#define _SIM_EXTERNAL_RUNTIME_H + +/* + * Define a thread that listens for the launch of an external runtime. + * When we spot one we notify the main thread, which can choose to + * accept or reject it. + */ +class ExternalRuntime : public wxThread { +public: + ExternalRuntime(void) {} + virtual ~ExternalRuntime(void); + + /* start the thread running */ + bool StartThread(void); + + /* thread entry point */ + virtual void* Entry(void); +}; + +#endif // _SIM_EXTERNAL_RUNTIME_H diff --git a/simulator/app/LinuxKeys.h b/simulator/app/LinuxKeys.h new file mode 100644 index 000000000..6382de9e4 --- /dev/null +++ b/simulator/app/LinuxKeys.h @@ -0,0 +1,20 @@ +#ifndef _SIM_LINUXKEYS_H +#define _SIM_LINUXKEYS_H + +#include + +/* ubuntu has these, goobuntu doesn't */ +#ifndef KEY_SWITCHVIDEOMODE +# define KEY_SWITCHVIDEOMODE 227 +#endif +#ifndef KEY_KBDILLUMTOGGLE +# define KEY_KBDILLUMTOGGLE 228 +#endif +#ifndef KEY_KBDILLUMUP +# define KEY_KBDILLUMUP 230 +#endif +#ifndef KEY_REPLY +# define KEY_REPLY 232 +#endif + +#endif /*_SIM_LINUXKEYS_H*/ diff --git a/simulator/app/LoadableImage.cpp b/simulator/app/LoadableImage.cpp new file mode 100644 index 000000000..e5bd0f3e2 --- /dev/null +++ b/simulator/app/LoadableImage.cpp @@ -0,0 +1,100 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Simple class to hold an image that can be loaded and unloaded. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +#include "wx/image.h" // needed for Windows build + +#include "LoadableImage.h" +#include "AssetStream.h" +#include "MyApp.h" + +#include + +#include + + +/* + * Load the image. + */ +bool LoadableImage::Create(const char* fileName, int x, int y) +{ + if (fileName == NULL || x < 0 || y < 0) { + fprintf(stderr, "bad params to %s\n", __PRETTY_FUNCTION__); + return false; + } + + delete[] mName; + mName = android::strdupNew(fileName); + + mX = x; + mY = y; + + return true; +} + +/* + * Load the bitmap. + */ +bool LoadableImage::LoadResources(void) +{ + if (mName == NULL) + return false; + + if (mpBitmap != NULL) // already loaded? + return true; + + //printf("LoadResources: '%s'\n", (const char*) mName); +#ifdef BEFORE_ASSET + wxImage img(mName); +#else + android::AssetManager* pAssetMgr = ((MyApp*)wxTheApp)->GetAssetManager(); + android::Asset* pAsset; + + pAsset = pAssetMgr->open(mName, android::Asset::ACCESS_RANDOM); + if (pAsset == NULL) { + fprintf(stderr, "ERROR: unable to load '%s'\n", mName); + return false; + } else { + //printf("--- opened asset '%s'\n", + // (const char*) pAsset->getAssetSource()); + } + AssetStream astr(pAsset); + + wxImage img(astr); +#endif + + mWidth = img.GetWidth(); + mHeight = img.GetHeight(); + if (mWidth <= 0 || mHeight <= 0) { + /* image failed to load or decode */ + fprintf(stderr, "ERROR: unable to load/decode '%s'\n", mName); + //delete img; + return false; + } + + mpBitmap = new wxBitmap(img); + + //delete img; + + return true; +} + +/* + * Unload the bitmap. + */ +bool LoadableImage::UnloadResources(void) +{ + delete mpBitmap; + mpBitmap = NULL; + return true; +} + diff --git a/simulator/app/LoadableImage.h b/simulator/app/LoadableImage.h new file mode 100644 index 000000000..368d520bf --- /dev/null +++ b/simulator/app/LoadableImage.h @@ -0,0 +1,73 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Simulated device definition. +// +#ifndef _SIM_LOADABLE_IMAGE_H +#define _SIM_LOADABLE_IMAGE_H + +#include "utils.h" + +/* + * Holds an image that may or may not be loaded at present. The image + * has an (x,y) offset. + */ +class LoadableImage { +public: + LoadableImage(void) + : mName(NULL), mpBitmap(NULL), mX(-1), mY(-1), mWidth(-1), mHeight(-1) + {} + virtual ~LoadableImage(void) { + delete[] mName; + delete mpBitmap; + } + LoadableImage(const LoadableImage& src) + : mName(NULL), mpBitmap(NULL) + { + CopyMembers(src); + } + LoadableImage& operator=(const LoadableImage& src) { + if (this != &src) // self-assignment + CopyMembers(src); + return *this; + } + void CopyMembers(const LoadableImage& src) { + // Need to delete resources in case we're using operator= and + // assigning into an object that already holds some. + delete mName; + delete mpBitmap; + mName = android::strdupNew(src.mName); + if (src.mpBitmap == NULL) + mpBitmap = NULL; + else + mpBitmap = new wxBitmap(*(src.mpBitmap)); + mX = src.mX; + mY = src.mY; + mWidth = src.mWidth; + mHeight = src.mHeight; + } + + virtual bool Create(const char* fileName, int x, int y); + + // load or unload the bitmap + bool LoadResources(void); + bool UnloadResources(void); + + // accessors + int GetX(void) const { return mX; } + int GetY(void) const { return mY; } + int GetWidth(void) const { return mWidth; } + int GetHeight(void) const { return mHeight; } + wxBitmap* GetBitmap(void) const { return mpBitmap; } + +private: + char* mName; + wxBitmap* mpBitmap; + + int mX; // position relative to phone image + int mY; + int mWidth; // from image (cached values) + int mHeight; +}; + +#endif // _SIM_LOADABLE_IMAGE_H diff --git a/simulator/app/LocalBiChannel.cpp b/simulator/app/LocalBiChannel.cpp new file mode 100644 index 000000000..93a997d45 --- /dev/null +++ b/simulator/app/LocalBiChannel.cpp @@ -0,0 +1,444 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Local named bi-directional communication channel. +// +#include "LocalBiChannel.h" +#include "utils/Log.h" + +#if defined(HAVE_WIN32_IPC) +# define _WIN32_WINNT 0x0500 +# include +#else +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include + +#ifndef SUN_LEN +/* + * Our current set of ARM header files don't define this. + */ +# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \ + + strlen ((ptr)->sun_path)) +#endif + +using namespace android; + +const unsigned long kInvalidHandle = (unsigned long) -1; + +/* + * Initialize data fields. + */ +LocalBiChannel::LocalBiChannel(void) + : mFileName(NULL), mIsListener(false), mHandle(kInvalidHandle) +{ +} + +#if defined(HAVE_WIN32_IPC) +/* + * Implementation for Win32, using named pipes. + * + * Cygwin actually supports UNIX-domain sockets, but we want to stuff + * the file handles into a Pipe, which uses HANDLE under Win32. + */ + +const int kPipeSize = 4096; + +/* + * Destructor. If we're the server side, we may need to clean up after + * ourselves. + */ +LocalBiChannel::~LocalBiChannel(void) +{ + if (mHandle != kInvalidHandle) + CloseHandle((HANDLE)mHandle); + + delete[] mFileName; +} + +/* + * Construct the full path. The caller must delete[] the return value. + */ +static char* makeFilename(const char* name) +{ + static const char* kBasePath = "\\\\.\\pipe\\android-"; + char* fileName; + + assert(name != NULL && name[0] != '\0'); + + fileName = new char[strlen(kBasePath) + strlen(name) + 1]; + strcpy(fileName, kBasePath); + strcat(fileName, name); + + return fileName; +} + +/* + * Create a named pipe, so the client has something to connect to. + */ +bool LocalBiChannel::create(const char* name) +{ + delete[] mFileName; + mFileName = makeFilename(name); + +#if 0 + HANDLE hPipe; + + hPipe = CreateNamedPipe( + mFileName, // unique pipe name + PIPE_ACCESS_DUPLEX | // open mode + FILE_FLAG_FIRST_PIPE_INSTANCE, + 0, // pipe mode (byte, blocking) + 1, // max instances + kPipeSize, // output buffer + kPipeSize, // input buffer + NMPWAIT_USE_DEFAULT_WAIT, // client time-out + NULL); // security + + if (hPipe == 0) { + LOG(LOG_ERROR, "lbicomm", + "CreateNamedPipe failed (err=%ld)\n", GetLastError()); + return false; + } + + mHandle = (unsigned long) hPipe; +#endif + + return true; +} + +/* + * Attach to an existing named pipe. + */ +bool LocalBiChannel::attach(const char* name, Pipe** ppReadPipe, + Pipe** ppWritePipe) +{ + HANDLE hPipe, dupHandle; + + delete[] mFileName; + mFileName = makeFilename(name); + + hPipe = CreateFile( + mFileName, // filename + GENERIC_READ | GENERIC_WRITE, // access + 0, // no sharing + NULL, // security + OPEN_EXISTING, // don't create + 0, // attributes + NULL); // template + if (hPipe == INVALID_HANDLE_VALUE) { + LOG(LOG_ERROR, "lbicomm", + "CreateFile on pipe '%s' failed (err=%ld)\n", name, GetLastError()); + return false; + } + + assert(mHandle == kInvalidHandle); + + /* + * Set up the pipes. Use the new handle for one, and a duplicate + * of it for the other, in case we decide to only close one side. + */ + *ppReadPipe = new Pipe(); + (*ppReadPipe)->createReader((unsigned long) hPipe); + + DuplicateHandle( + GetCurrentProcess(), + hPipe, + GetCurrentProcess(), + &dupHandle, + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + *ppWritePipe = new Pipe(); + (*ppWritePipe)->createWriter((unsigned long) dupHandle); + + return true; +} + +/* + * Listen for a new connection, discarding any existing connection. + */ +bool LocalBiChannel::listen(Pipe** ppReadPipe, Pipe** ppWritePipe) +{ + BOOL connected; + HANDLE hPipe; + + /* + * Create up to 3 instances of the named pipe: + * - currently active connection + * - connection currently being rejected because one is already active + * - a new listener to wait for the next round + */ + hPipe = CreateNamedPipe( + mFileName, // unique pipe name + PIPE_ACCESS_DUPLEX // open mode + /*| FILE_FLAG_FIRST_PIPE_INSTANCE*/, + 0, // pipe mode (byte, blocking) + 3, // max instances + kPipeSize, // output buffer + kPipeSize, // input buffer + NMPWAIT_USE_DEFAULT_WAIT, // client time-out + NULL); // security + + if (hPipe == 0) { + LOG(LOG_ERROR, "lbicomm", + "CreateNamedPipe failed (err=%ld)\n", GetLastError()); + return false; + } + + /* + * If a client is already connected to us, this fails with + * ERROR_PIPE_CONNECTED. It returns success if we had to wait + * a little bit before the connection happens. + */ + connected = ConnectNamedPipe(hPipe, NULL) ? + TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); + + if (connected) { + /* + * Create the pipes. Give one a duplicated handle so that, + * when one closes, we don't lose both. + */ + HANDLE dupHandle; + + *ppReadPipe = new Pipe(); + (*ppReadPipe)->createReader((unsigned long) hPipe); + + DuplicateHandle( + GetCurrentProcess(), + hPipe, + GetCurrentProcess(), + &dupHandle, + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + *ppWritePipe = new Pipe(); + (*ppWritePipe)->createWriter((unsigned long) dupHandle); + + return true; + } else { + LOG(LOG_WARN, "lbicomm", + "ConnectNamedPipe failed (err=%ld)\n", GetLastError()); +#ifdef HAVE_WIN32_THREADS + Sleep(500); /* 500 ms */ +#else + usleep(500000); // DEBUG DEBUG +#endif + return false; + } +} + +#else + +/* + * Implementation for Linux and Darwin, using UNIX-domain sockets. + */ + +/* + * Destructor. If we're the server side, blow away the socket file. + */ +LocalBiChannel::~LocalBiChannel(void) +{ + if (mHandle != kInvalidHandle) + close((int) mHandle); + + if (mIsListener && mFileName != NULL) { + LOG(LOG_DEBUG, "lbicomm", "Removing '%s'\n", mFileName); + (void) unlink(mFileName); + } + delete[] mFileName; +} + +/* + * Construct the full path. The caller must delete[] the return value. + */ +static char* makeFilename(const char* name) +{ + static const char* kBasePath = "/tmp/android-"; + char* fileName; + + assert(name != NULL && name[0] != '\0'); + + fileName = new char[strlen(kBasePath) + strlen(name) + 1]; + strcpy(fileName, kBasePath); + strcat(fileName, name); + + return fileName; +} + +/* + * Create a UNIX domain socket, carefully removing it if it already + * exists. + */ +bool LocalBiChannel::create(const char* name) +{ + struct stat sb; + bool result = false; + int sock = -1; + int cc; + + delete[] mFileName; + mFileName = makeFilename(name); + + cc = stat(mFileName, &sb); + if (cc < 0) { + if (errno != ENOENT) { + LOG(LOG_ERROR, "lbicomm", + "Unable to stat '%s' (errno=%d)\n", mFileName, errno); + goto bail; + } + } else { + /* don't touch it if it's not a socket */ + if (!(S_ISSOCK(sb.st_mode))) { + LOG(LOG_ERROR, "lbicomm", + "File '%s' exists and is not a socket\n", mFileName); + goto bail; + } + + /* remove the cruft */ + if (unlink(mFileName) < 0) { + LOG(LOG_ERROR, "lbicomm", + "Unable to remove '%s' (errno=%d)\n", mFileName, errno); + goto bail; + } + } + + struct sockaddr_un addr; + + sock = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + LOG(LOG_ERROR, "lbicomm", + "UNIX domain socket create failed (errno=%d)\n", errno); + goto bail; + } + + /* bind the socket; this creates the file on disk */ + strcpy(addr.sun_path, mFileName); // max 108 bytes + addr.sun_family = AF_UNIX; + cc = ::bind(sock, (struct sockaddr*) &addr, SUN_LEN(&addr)); + if (cc < 0) { + LOG(LOG_ERROR, "lbicomm", + "AF_UNIX bind failed for '%s' (errno=%d)\n", mFileName, errno); + goto bail; + } + + mHandle = (unsigned long) sock; + sock = -1; + mIsListener = true; + result = true; + +bail: + if (sock >= 0) + close(sock); + return result; +} + +/* + * Attach to an existing UNIX domain socket. + */ +bool LocalBiChannel::attach(const char* name, Pipe** ppReadPipe, + Pipe** ppWritePipe) +{ + bool result = false; + int sock = -1; + int cc; + + assert(ppReadPipe != NULL); + assert(ppWritePipe != NULL); + + delete[] mFileName; + mFileName = makeFilename(name); + + struct sockaddr_un addr; + + sock = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + LOG(LOG_ERROR, "lbicomm", + "UNIX domain socket create failed (errno=%d)\n", errno); + goto bail; + } + + /* connect to socket; fails if file doesn't exist */ + strcpy(addr.sun_path, mFileName); // max 108 bytes + addr.sun_family = AF_UNIX; + cc = ::connect(sock, (struct sockaddr*) &addr, SUN_LEN(&addr)); + if (cc < 0) { + // ENOENT means socket file doesn't exist + // ECONNREFUSED means socket exists but nobody is listening + LOG(LOG_ERROR, "lbicomm", + "AF_UNIX connect failed for '%s': %s\n", mFileName,strerror(errno)); + goto bail; + } + + /* + * Create the two halves. We dup() the sock so that closing one side + * does not hose the other. + */ + *ppReadPipe = new Pipe(); + (*ppReadPipe)->createReader(sock); + *ppWritePipe = new Pipe(); + (*ppWritePipe)->createWriter(dup(sock)); + + assert(mHandle == kInvalidHandle); + sock = -1; + mIsListener = false; + + result = true; + +bail: + if (sock >= 0) + close(sock); + return result; +} + +/* + * Listen for a new connection. + */ +bool LocalBiChannel::listen(Pipe** ppReadPipe, Pipe** ppWritePipe) +{ + bool result = false; + struct sockaddr_un from; + socklen_t fromlen; + int sock, lsock; + int cc; + + assert(mHandle != kInvalidHandle); + lsock = (int) mHandle; + + LOG(LOG_DEBUG, "lbicomm", "AF_UNIX listening\n"); + cc = ::listen(lsock, 5); + if (cc < 0) { + LOG(LOG_ERROR, "lbicomm", "AF_UNIX listen failed (errno=%d)\n", errno); + goto bail; + } + + fromlen = sizeof(from); // not SUN_LEN() + sock = ::accept(lsock, (struct sockaddr*) &from, &fromlen); + if (sock < 0) { + LOG(LOG_WARN, "lbicomm", "AF_UNIX accept failed (errno=%d)\n", errno); + goto bail; + } + + /* + * Create the two halves. We dup() the sock so that closing one side + * does not hose the other. + */ + *ppReadPipe = new Pipe(); + (*ppReadPipe)->createReader(sock); + *ppWritePipe = new Pipe(); + (*ppWritePipe)->createWriter(dup(sock)); + result = true; + +bail: + return result; +} + +#endif diff --git a/simulator/app/LocalBiChannel.h b/simulator/app/LocalBiChannel.h new file mode 100644 index 000000000..ce04bc0e0 --- /dev/null +++ b/simulator/app/LocalBiChannel.h @@ -0,0 +1,54 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Create or attach to a named bi-directional channel on the local machine. +// +#ifndef __LIBS_LOCALBICHANNEL_H +#define __LIBS_LOCALBICHANNEL_H + +#ifdef HAVE_ANDROID_OS +#error DO NOT USE THIS FILE IN THE DEVICE BUILD +#endif + +#include + +namespace android { + +/* + * This is essentially a wrapper class for UNIX-domain sockets. The + * idea is to set one up with create() or attach to one with attach() + * and then extract the unidirectional read/write Pipes. These can + * be used directly or stuffed into a MessageStream. + * + * The name for the pipe should be a short filename made up of alphanumeric + * characters. Depending on the implementation, we may create a file in + * /tmp with the specified name, removing any existing copy. + */ +class LocalBiChannel { +public: + LocalBiChannel(void); + ~LocalBiChannel(void); + + /* create the "listen" side */ + bool create(const char* name); + + /* + * Listen for a connection. When we get one, we create unidirectional + * read/write pipes and return them. + */ + bool listen(Pipe** ppReadPipe, Pipe** ppWritePipe); + + /* + * Attach to a channel created by somebody else. Returns pipes. + */ + bool attach(const char* name, Pipe** ppReadPipe, Pipe** ppWritePipe); + +private: + char* mFileName; + bool mIsListener; + unsigned long mHandle; +}; + +}; // namespace android + +#endif // __LIBS_LOCALBICHANNEL_H diff --git a/simulator/app/LogBundle.h b/simulator/app/LogBundle.h new file mode 100644 index 000000000..2cba97bc5 --- /dev/null +++ b/simulator/app/LogBundle.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _SIM_LOG_BUNDLE_H +#define _SIM_LOG_BUNDLE_H + +#ifdef HAVE_PTHREADS +#include +#endif +#include // for android_LogPriority. + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct android_LogBundle { + time_t when; + android_LogPriority priority; + pid_t pid; +#ifndef HAVE_PTHREADS + unsigned tid; +#else + pthread_t tid; +#endif + const char* tag; + const struct iovec* msgVec; + size_t msgCount; + int fd; +} android_LogBundle; + +#ifdef __cplusplus +} +#endif + +#endif // _SIM_LOG_BUNDLE_H diff --git a/simulator/app/LogMessage.cpp b/simulator/app/LogMessage.cpp new file mode 100644 index 000000000..63cb76445 --- /dev/null +++ b/simulator/app/LogMessage.cpp @@ -0,0 +1,98 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Hold a single log message. +// +#include "LogMessage.h" +#include + +/* + * Constructor. + * + * Initializers here aren't necessary, since we can only create one of + * these through Create(), which touches every field. + */ +LogMessage::LogMessage(void) +{ +} + +/* + * Destructor. + */ +LogMessage::~LogMessage(void) +{ + delete[] mTag; + delete[] mMsg; +} + +/* + * Create a new LogMessage object, and populate it with the contents of + * "*pBundle". + */ +/*static*/ LogMessage* LogMessage::Create(const android_LogBundle* pBundle) +{ + LogMessage* newMsg = new LogMessage; + + if (newMsg == NULL) + return NULL; + assert(pBundle != NULL); + + newMsg->mWhen = pBundle->when; + newMsg->mPriority = pBundle->priority; + newMsg->mPid = pBundle->pid; + newMsg->mTag = android::strdupNew(pBundle->tag); + + size_t len = 0; + size_t i; + for (i=0; imsgCount; i++) len += pBundle->msgVec[i].iov_len; + newMsg->mMsg = new char[len+1]; + char* p = newMsg->mMsg; + for (i=0; imsgCount; i++) { + memcpy(p, pBundle->msgVec[i].iov_base, pBundle->msgVec[i].iov_len); + p += pBundle->msgVec[i].iov_len; + } + *p = 0; + + newMsg->mRefCnt = 1; + newMsg->mInternal = false; + newMsg->mFootprint = 8 * sizeof(int) + strlen(newMsg->mTag) + + strlen(newMsg->mMsg) + 4; + newMsg->mTextCtrlLen = 0; + newMsg->mpPrev = NULL; + newMsg->mpNext = NULL; + + return newMsg; +} + +/* + * Create a new LogMessage object, with a simple message in it. + * + * Sets "mInternal" so we display it appropriately. + */ +/*static*/ LogMessage* LogMessage::Create(const char* msg) +{ + LogMessage* newMsg; + android_LogBundle bundle; + + assert(msg != NULL); + + memset(&bundle, 0, sizeof(bundle)); + bundle.when = time(NULL); + bundle.priority = ANDROID_LOG_ERROR; + bundle.pid = getpid(); + bundle.tag = "-"; + iovec iov; + iov.iov_base = (void*)msg; + iov.iov_len = strlen(msg); + bundle.msgVec = &iov; + bundle.msgCount = 1; + + newMsg = Create(&bundle); + + if (newMsg != NULL) { + newMsg->mInternal = true; + } + + return newMsg; +} + diff --git a/simulator/app/LogMessage.h b/simulator/app/LogMessage.h new file mode 100644 index 000000000..fe77dc0aa --- /dev/null +++ b/simulator/app/LogMessage.h @@ -0,0 +1,73 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Class to hold a single log message. Not thread safe. +// +#ifndef _SIM_LOG_MESSAGE_H +#define _SIM_LOG_MESSAGE_H + +#include "utils.h" +#include "LogBundle.h" + +/* + * Hold a single log message. + * + * To reduce malloc strain we could over-allocate the object and tuck the + * message text into the object storage. On this off chance this becomes + * important, the implementation keeps its constructor private. + */ +class LogMessage { +public: + ~LogMessage(void); + + static LogMessage* Create(const android_LogBundle* pBundle); + static LogMessage* Create(const char* msg); + + /* the total length of text added to the text ctrl */ + int GetTextCtrlLen(void) const { return mTextCtrlLen; } + void SetTextCtrlLen(int len) { mTextCtrlLen = len; } + + /* log pool */ + LogMessage* GetPrev(void) const { return mpPrev; } + void SetPrev(LogMessage* pPrev) { mpPrev = pPrev; } + LogMessage* GetNext(void) const { return mpNext; } + void SetNext(LogMessage* pNext) { mpNext = pNext; } + int GetFootprint(void) const { return mFootprint; } + + /* message contents */ + time_t GetWhen(void) const { return mWhen; } + android_LogPriority GetPriority(void) const { return mPriority; } + pid_t GetPid(void) const { return mPid; } + const char* GetTag(void) const { return mTag; } + const char* GetMsg(void) const { return mMsg; } + + bool GetInternal(void) const { return mInternal; } + + void Acquire(void) { mRefCnt++; } + void Release(void) { + if (!--mRefCnt) + delete this; + } + +private: + LogMessage(void); + LogMessage(const LogMessage& src); // not implemented + LogMessage& operator=(const LogMessage& src); // not implemented + + /* log message contents */ + time_t mWhen; + android_LogPriority mPriority; + pid_t mPid; + char* mTag; + char* mMsg; + + /* additional goodies */ + int mRefCnt; // reference count + bool mInternal; // message generated internally by us? + int mFootprint; // approx. size of this object in memory + int mTextCtrlLen; // #of characters req'd in text ctrl + LogMessage* mpPrev; // link to previous item in log pool + LogMessage* mpNext; // link to next item in log pool +}; + +#endif // _SIM_LOG_MESSAGE_H diff --git a/simulator/app/LogPool.cpp b/simulator/app/LogPool.cpp new file mode 100644 index 000000000..43bf4be7f --- /dev/null +++ b/simulator/app/LogPool.cpp @@ -0,0 +1,90 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Hold a collection of log messages, limiting ourselves to a certain +// fixed maximum amount of memory. +// +#include "LogPool.h" +#include + + +/* + * Add a message at the head of the pool. + */ +void LogPool::Add(LogMessage* pLogMessage) +{ + pLogMessage->Acquire(); // bump up the ref count + + assert(pLogMessage->GetPrev() == NULL); + assert(pLogMessage->GetNext() == NULL); + + if (mpHead == NULL) { + assert(mpTail == NULL); + mpTail = mpHead = pLogMessage; + } else { + assert(mpHead->GetPrev() == NULL); + mpHead->SetPrev(pLogMessage); + pLogMessage->SetNext(mpHead); + mpHead = pLogMessage; + } + + /* update the pool size, and remove old entries if necessary */ + mCurrentSize += pLogMessage->GetFootprint(); + + while (mCurrentSize > mMaxSize) + RemoveOldest(); +} + +/* + * Remove the oldest message (from the tail of the list). + */ +void LogPool::RemoveOldest(void) +{ + LogMessage* pPrev; + + if (mpTail == NULL) { + fprintf(stderr, "HEY: nothing left to remove (cur=%ld)\n", + mCurrentSize); + assert(false); + return; + } + + if (mpTail == mpBookmark) + mpBookmark = NULL; + + //printf("--- removing oldest, size %ld->%ld (%s)\n", + // mCurrentSize, mCurrentSize - mpTail->GetFootprint(),mpTail->GetMsg()); + mCurrentSize -= mpTail->GetFootprint(); + + pPrev = mpTail->GetPrev(); + mpTail->Release(); + mpTail = pPrev; + if (mpTail == NULL) { + //printf("--- pool is now empty (size=%ld)\n", mCurrentSize); + mpHead = NULL; + } else { + mpTail->SetNext(NULL); + } +} + +/* + * Resize the log pool. + */ +void LogPool::Resize(long maxSize) +{ + assert(maxSize >= 0); + + mMaxSize = maxSize; + while (mCurrentSize > mMaxSize) + RemoveOldest(); +} + +/* + * Remove all entries. + */ +void LogPool::Clear(void) +{ + while (mpTail != NULL) + RemoveOldest(); +} + diff --git a/simulator/app/LogPool.h b/simulator/app/LogPool.h new file mode 100644 index 000000000..b824fec1c --- /dev/null +++ b/simulator/app/LogPool.h @@ -0,0 +1,56 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Pool of log messages. Not thread safe -- operations on the log pool +// should only happen in the main UI thread. +// +#ifndef _SIM_LOG_POOL_H +#define _SIM_LOG_POOL_H + +#include "LogMessage.h" + +/* + * This contains the pool of log messages. The messages themselves are + * allocated individually and reference counted. We add new messages to + * the head and, when the total "footprint" exceeds our stated max, we + * delete one or more from the tail. + * + * To support pause/resume, we allow a "bookmark" to be set. This is + * just a pointer to a message in the pool. If the bookmarked message + * is deleted, we discard the bookmark. + */ +class LogPool { +public: + LogPool(void) + : mpHead(NULL), mpTail(NULL), mpBookmark(NULL), + mCurrentSize(0), mMaxSize(10240) + {} + ~LogPool(void) { Clear(); } + + void Clear(void); + + /* add a new message to the pool */ + void Add(LogMessage* pLogMessage); + + /* resize the pool, removing excess messages */ + void Resize(long maxSize); + + /* return the current limit, in bytes */ + long GetMaxSize(void) const { return mMaxSize; } + + LogMessage* GetHead(void) const { return mpHead; } + + void SetBookmark(void) { mpBookmark = mpHead; } + LogMessage* GetBookmark(void) const { return mpBookmark; } + +private: + void RemoveOldest(void); + + LogMessage* mpHead; + LogMessage* mpTail; + LogMessage* mpBookmark; + long mCurrentSize; // current size, in bytes + long mMaxSize; // maximum size, in bytes +}; + +#endif // _SIM_LOG_POOL_H diff --git a/simulator/app/LogPrefsDialog.cpp b/simulator/app/LogPrefsDialog.cpp new file mode 100644 index 000000000..dc3e07c5f --- /dev/null +++ b/simulator/app/LogPrefsDialog.cpp @@ -0,0 +1,404 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Log preferences modal dialog. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif + +#include "LogPrefsDialog.h" +#include "Preferences.h" +#include "Resource.h" +#include "utils.h" + +BEGIN_EVENT_TABLE(LogPrefsDialog, wxDialog) + EVT_CHECKBOX(IDC_LOG_PREFS_WRITE_FILE, LogPrefsDialog::OnWriteFile) +END_EVENT_TABLE() + +static const wxString gSpacerChoices[] = { + wxT("0"), wxT("1"), wxT("2") +}; +static const wxString gPointSizes[] = { + wxT("4"), wxT("6"), wxT("8"), wxT("10"), wxT("12"), wxT("14"), wxT("16") +}; + + +/* + * Constructor. + */ +LogPrefsDialog::LogPrefsDialog(wxWindow* parent) + : wxDialog(parent, IDD_LOG_PREFS, wxT("Log Preferences"), wxDefaultPosition, + wxDefaultSize, wxDEFAULT_DIALOG_STYLE), + mHeaderFormat(kHFFull), mSingleLine(false), mExtraSpacing(0), + mUseColor(false), mFontMonospace(false), mDisplayMax(0), mPoolSizeKB(0) +{ + CreateControls(); +} + + +/* + * Destructor. Not much to do. + */ +LogPrefsDialog::~LogPrefsDialog(void) +{ +} + +/* + * Create all of the pages and add them to the notebook. + */ +void LogPrefsDialog::CreateControls(void) +{ + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + wxBoxSizer* okCancelSizer = new wxBoxSizer(wxHORIZONTAL); + mNotebook.Create(this, wxID_ANY); + wxPanel* page; + + page = CreateFormatPage(&mNotebook); + mNotebook.AddPage(page, wxT("Format"), true); + page = CreateLimitsPage(&mNotebook); + mNotebook.AddPage(page, wxT("Limits"), false); + page = CreateFilesPage(&mNotebook); + mNotebook.AddPage(page, wxT("Files"), false); + + // note to self: could use CreateButtonSizer here? + wxButton* cancel = new wxButton(this, wxID_CANCEL, wxT("&Cancel"), + wxDefaultPosition, wxDefaultSize, 0); + okCancelSizer->Add(cancel, 0, wxALL, kInterSpacing); + + wxButton* ok = new wxButton(this, wxID_OK, wxT("&OK"), + wxDefaultPosition, wxDefaultSize, 0); + okCancelSizer->Add(ok, 0, wxALL, kInterSpacing); + + mainSizer->Add(&mNotebook); + mainSizer->Add(okCancelSizer, 0, wxALIGN_RIGHT); + + SetSizer(mainSizer); + + mainSizer->Fit(this); // shrink-to-fit + mainSizer->SetSizeHints(this); // define minimum size +} + +/* + * Transfer data from our members to the window controls. + */ +bool LogPrefsDialog::TransferDataToWindow(void) +{ + /* + * Do standard dialog setup. + */ + wxRadioButton* fmtFull = (wxRadioButton*) FindWindow(IDC_LOG_PREFS_FMT_FULL); + wxRadioButton* fmtBrief = (wxRadioButton*) FindWindow(IDC_LOG_PREFS_FMT_BRIEF); + wxRadioButton* fmtMinimal = (wxRadioButton*) FindWindow(IDC_LOG_PREFS_FMT_MINIMAL); + wxCheckBox* singleLine = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_SINGLE_LINE); + wxComboBox* extraSpacing = (wxComboBox*) FindWindow(IDC_LOG_PREFS_EXTRA_SPACING); + wxComboBox* pointSize = (wxComboBox*) FindWindow(IDC_LOG_PREFS_POINT_SIZE); + wxCheckBox* useColor = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_USE_COLOR); + wxCheckBox* fontMono = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_FONT_MONO); + // - + wxTextCtrl* displayMax = (wxTextCtrl*) FindWindow(IDC_LOG_PREFS_DISPLAY_MAX); + wxTextCtrl* poolSize = (wxTextCtrl*) FindWindow(IDC_LOG_PREFS_POOL_SIZE); + // - + wxCheckBox* writeFile = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_WRITE_FILE); + wxTextCtrl* fileName = (wxTextCtrl*) FindWindow(IDC_LOG_PREFS_FILENAME); + wxCheckBox* truncateOld = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_TRUNCATE_OLD); + // - + + fmtFull->SetValue(mHeaderFormat == kHFFull); + fmtBrief->SetValue(mHeaderFormat == kHFBrief); + fmtMinimal->SetValue(mHeaderFormat == kHFMinimal); + singleLine->SetValue(mSingleLine); + if (mExtraSpacing < 0 || mExtraSpacing > NELEM(gSpacerChoices)) + mExtraSpacing = 0; + extraSpacing->SetSelection(mExtraSpacing); + + pointSize->SetSelection(0); + for (int i = 0; i < NELEM(gPointSizes); i++) { + if (atoi(gPointSizes[i].ToAscii()) == mPointSize) { + pointSize->SetSelection(i); + break; + } + } + useColor->SetValue(mUseColor); + fontMono->SetValue(mFontMonospace); + + wxString tmpStr; + tmpStr.Printf(wxT("%d"), mDisplayMax); + displayMax->SetValue(tmpStr); + tmpStr.Printf(wxT("%d"), mPoolSizeKB); + poolSize->SetValue(tmpStr); + + writeFile->SetValue(mWriteFile); + fileName->SetValue(mFileName); + truncateOld->SetValue(mTruncateOld); + + EnableFileControls(mWriteFile); + + return true; +} + +/* + * Convert a string to a number. The number is expected to be unsigned. + * Returns < 0 on failure. + */ +static long ConvertUnsigned(const wxString& str) +{ + long val; + if (!str.ToLong(&val)) + return -1; + return val; +} + +/* + * Transfer and validate data from the window controls. + * + * This doesn't get called if the user cancels out of the dialog. + */ +bool LogPrefsDialog::TransferDataFromWindow(void) +{ + /* + * Do standard dialog export. + */ + //wxRadioButton* fmtFull = (wxRadioButton*) FindWindow(IDC_LOG_PREFS_FMT_FULL); + wxRadioButton* fmtBrief = (wxRadioButton*) FindWindow(IDC_LOG_PREFS_FMT_BRIEF); + wxRadioButton* fmtMinimal = (wxRadioButton*) FindWindow(IDC_LOG_PREFS_FMT_MINIMAL); + wxCheckBox* singleLine = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_SINGLE_LINE); + wxComboBox* extraSpacing = (wxComboBox*) FindWindow(IDC_LOG_PREFS_EXTRA_SPACING); + wxComboBox* pointSize = (wxComboBox*) FindWindow(IDC_LOG_PREFS_POINT_SIZE); + wxCheckBox* useColor = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_USE_COLOR); + wxCheckBox* fontMono = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_FONT_MONO); + // - + wxTextCtrl* displayMax = (wxTextCtrl*) FindWindow(IDC_LOG_PREFS_DISPLAY_MAX); + wxTextCtrl* poolSize = (wxTextCtrl*) FindWindow(IDC_LOG_PREFS_POOL_SIZE); + // - + wxCheckBox* writeFile = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_WRITE_FILE); + wxTextCtrl* fileName = (wxTextCtrl*) FindWindow(IDC_LOG_PREFS_FILENAME); + wxCheckBox* truncateOld = (wxCheckBox*) FindWindow(IDC_LOG_PREFS_TRUNCATE_OLD); + // - + + mHeaderFormat = kHFFull; + if (fmtBrief->GetValue()) + mHeaderFormat = kHFBrief; + else if (fmtMinimal->GetValue()) + mHeaderFormat = kHFMinimal; + + wxString tmpStr; + + mSingleLine = (singleLine->GetValue() != 0); + mExtraSpacing = extraSpacing->GetSelection(); + mPointSize = ConvertUnsigned(pointSize->GetValue()); + mUseColor = useColor->GetValue(); + mFontMonospace = fontMono->GetValue(); + + tmpStr = displayMax->GetValue(); + mDisplayMax = ConvertUnsigned(tmpStr); + if (mDisplayMax <= 0 || mDisplayMax > 1000 * 1000) { + wxMessageBox(wxT("Bad value for display max -- must be > 0 and <= 1,000,000"), + wxT("Hoser"), wxOK, this); + return false; + } + + tmpStr = poolSize->GetValue(); + mPoolSizeKB = ConvertUnsigned(tmpStr); + if (mDisplayMax <= 0 || mDisplayMax > 1048576) { + wxMessageBox(wxT("Bad value for pool size -- must be > 0 and <= 1048576"), + wxT("Hoser"), wxOK, this); + return false; + } + + mWriteFile = (writeFile->GetValue() != 0); + mFileName = fileName->GetValue(); + mTruncateOld = (truncateOld->GetValue() != 0); + if (mWriteFile && mFileName.IsEmpty()) { + wxMessageBox(wxT("Log filename may not be blank"), + wxT("Hoser"), wxOK, this); + return false; + } + + return true; +} + + +/* + * Create the log Format page. + */ +wxPanel* LogPrefsDialog::CreateFormatPage(wxBookCtrlBase* parent) +{ + wxPanel* panel = new wxPanel(parent); + + wxStaticBoxSizer* headerOpts = new wxStaticBoxSizer(wxVERTICAL, panel, + wxT("Header")); + headerOpts->Add(new wxRadioButton(panel, IDC_LOG_PREFS_FMT_FULL, + wxT("Full header"), wxDefaultPosition, wxDefaultSize, + wxRB_GROUP)); + headerOpts->Add(new wxRadioButton(panel, IDC_LOG_PREFS_FMT_BRIEF, + wxT("Brief header"))); + headerOpts->Add(new wxRadioButton(panel, IDC_LOG_PREFS_FMT_MINIMAL, + wxT("Minimal, integrated header"))); + + wxCheckBox* singleLine = new wxCheckBox(panel, IDC_LOG_PREFS_SINGLE_LINE, + wxT("Put headers and message on same line")); + + wxStaticText* extraSpacingDescr = new wxStaticText(panel, wxID_STATIC, + wxT("Extra line spacing:")); + wxComboBox* extraSpacing = new wxComboBox(panel, + IDC_LOG_PREFS_EXTRA_SPACING, wxT("blah"), + wxDefaultPosition, wxDefaultSize, NELEM(gSpacerChoices), + gSpacerChoices, wxCB_READONLY); + wxBoxSizer* extraSpacingSizer = new wxBoxSizer(wxHORIZONTAL); + extraSpacingSizer->Add(extraSpacingDescr, 0, wxALIGN_CENTER_VERTICAL); + extraSpacingSizer->AddSpacer(kInterSpacing); + extraSpacingSizer->Add(extraSpacing); + + wxStaticBoxSizer* textOpts = new wxStaticBoxSizer(wxVERTICAL, panel, + wxT("Text")); + textOpts->Add( + new wxStaticText(panel, wxID_STATIC, wxT("Point size:")) ); + textOpts->AddSpacer(kInterSpacing); + textOpts->Add( + new wxComboBox(panel, + IDC_LOG_PREFS_POINT_SIZE, wxT("blah"), + wxDefaultPosition, wxDefaultSize, NELEM(gPointSizes), + gPointSizes, wxCB_READONLY) ); + textOpts->AddSpacer(kInterSpacing); + textOpts->Add( + new wxCheckBox(panel, IDC_LOG_PREFS_USE_COLOR, + wxT("Colorful messages")) ); + textOpts->AddSpacer(kInterSpacing); + textOpts->Add( + new wxCheckBox(panel, IDC_LOG_PREFS_FONT_MONO, + wxT("Use monospace font")) ); + + + wxBoxSizer* sizerPanel = new wxBoxSizer(wxVERTICAL); + sizerPanel->Add(kMinWidth, kEdgeSpacing); // forces minimum width + sizerPanel->Add(headerOpts); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(singleLine); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(extraSpacingSizer); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(textOpts); + sizerPanel->AddSpacer(kInterSpacing); + + wxBoxSizer* horizIndent = new wxBoxSizer(wxHORIZONTAL); + horizIndent->AddSpacer(kEdgeSpacing); + horizIndent->Add(sizerPanel); + horizIndent->AddSpacer(kEdgeSpacing); + panel->SetSizer(horizIndent); + + return panel; +} + +/* + * Create the log Limits page. + */ +wxPanel* LogPrefsDialog::CreateLimitsPage(wxBookCtrlBase* parent) +{ + wxPanel* panel = new wxPanel(parent); + + wxBoxSizer* displayMaxSizer = new wxBoxSizer(wxHORIZONTAL); + displayMaxSizer->Add( + new wxStaticText(panel, wxID_ANY, + wxT("Maximum entries in log window:"), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT), + 0, wxALIGN_CENTER_VERTICAL); + displayMaxSizer->AddSpacer(kInterSpacing); + displayMaxSizer->Add( + new wxTextCtrl(panel, IDC_LOG_PREFS_DISPLAY_MAX)); + + wxBoxSizer* poolSizeSizer = new wxBoxSizer(wxHORIZONTAL); + poolSizeSizer->Add( + new wxStaticText(panel, wxID_ANY, + wxT("Size of the log pool (KB):"), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT), + 0, wxALIGN_CENTER_VERTICAL); + poolSizeSizer->AddSpacer(kInterSpacing); + poolSizeSizer->Add( + new wxTextCtrl(panel, IDC_LOG_PREFS_POOL_SIZE)); + + + wxBoxSizer* sizerPanel = new wxBoxSizer(wxVERTICAL); + sizerPanel->Add(kMinWidth, kEdgeSpacing); // forces minimum width + sizerPanel->Add(displayMaxSizer); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(poolSizeSizer); + sizerPanel->AddSpacer(kInterSpacing); + + wxBoxSizer* horizIndent = new wxBoxSizer(wxHORIZONTAL); + horizIndent->AddSpacer(kEdgeSpacing); + horizIndent->Add(sizerPanel); + horizIndent->AddSpacer(kEdgeSpacing); + panel->SetSizer(horizIndent); + + return panel; +} + +/* + * Create the log Files page. + */ +wxPanel* LogPrefsDialog::CreateFilesPage(wxBookCtrlBase* parent) +{ + wxPanel* panel = new wxPanel(parent); + wxStaticBoxSizer* logOpts = new wxStaticBoxSizer(wxVERTICAL, panel, + wxT("Log File")); + + wxCheckBox* writeCopy = + new wxCheckBox(panel, IDC_LOG_PREFS_WRITE_FILE, + wxT("Write a copy of log output to a file")); + + logOpts->AddSpacer(kInterSpacing); + logOpts->Add( + new wxStaticText(panel, wxID_ANY, + wxT("Filename:"), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT)); + logOpts->AddSpacer(kInterSpacing); + logOpts->Add( + new wxTextCtrl(panel, IDC_LOG_PREFS_FILENAME), 0, wxEXPAND); + logOpts->AddSpacer(kInterSpacing); + logOpts->Add( + new wxCheckBox(panel, IDC_LOG_PREFS_TRUNCATE_OLD, + wxT("Truncate the file if more than 8 hours old ")) ); + + + wxBoxSizer* sizerPanel = new wxBoxSizer(wxVERTICAL); + sizerPanel->Add(kMinWidth, kEdgeSpacing); // forces minimum width + sizerPanel->Add(writeCopy); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(logOpts); + sizerPanel->AddSpacer(kInterSpacing); + + wxBoxSizer* horizIndent = new wxBoxSizer(wxHORIZONTAL); + horizIndent->AddSpacer(kEdgeSpacing); + horizIndent->Add(sizerPanel); + horizIndent->AddSpacer(kEdgeSpacing); + panel->SetSizer(horizIndent); + + return panel; +} + + +/* + * Handle clicks on the "write file" checkbox. + */ +void LogPrefsDialog::OnWriteFile(wxCommandEvent& event) +{ + EnableFileControls(event.GetInt()); +} + +/* + * Enable or disable some of the controls on the "file" page. + */ +void LogPrefsDialog::EnableFileControls(bool enable) +{ + FindWindow(IDC_LOG_PREFS_FILENAME)->Enable(enable); + FindWindow(IDC_LOG_PREFS_TRUNCATE_OLD)->Enable(enable); +} + diff --git a/simulator/app/LogPrefsDialog.h b/simulator/app/LogPrefsDialog.h new file mode 100644 index 000000000..17a73a33c --- /dev/null +++ b/simulator/app/LogPrefsDialog.h @@ -0,0 +1,70 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Log preferences modal dialog. +// +#ifndef _SIM_LOG_PREFS_DIALOG_H +#define _SIM_LOG_PREFS_DIALOG_H + +/* + * Declaration of log preferences dialog. This class defines the outer + * wrapper as well as all of the pages. + */ +class LogPrefsDialog : public wxDialog { + DECLARE_EVENT_TABLE() + +public: + LogPrefsDialog(wxWindow* parent); + virtual ~LogPrefsDialog(void); + + void CreateControls(void); + + /* these correspond to radio buttons */ + typedef enum HeaderFormat { + kHFFull = 0, + kHFBrief, + kHFMinimal, + kHFInternal, // special -- used for internally generated msgs + }; + + /* + * Values edited in the preference pages. By Windows convention, + * these are public. + */ + /* format options */ + HeaderFormat mHeaderFormat; + bool mSingleLine; // put whole message on one line? + int mExtraSpacing; // double/triple-space messages? + int mPointSize; // text size + bool mUseColor; // colorful messages? + bool mFontMonospace; // use monospace font? + + /* limit options */ + int mDisplayMax; + int mPoolSizeKB; + + /* file options */ + bool mWriteFile; + wxString mFileName; + bool mTruncateOld; + +private: + bool TransferDataToWindow(void); + bool TransferDataFromWindow(void); + + wxPanel* CreateFormatPage(wxBookCtrlBase* parent); + wxPanel* CreateLimitsPage(wxBookCtrlBase* parent); + wxPanel* CreateFilesPage(wxBookCtrlBase* parent); + + void OnWriteFile(wxCommandEvent& event); + void EnableFileControls(bool enable); + + /* main notebook; for aesthetic reasons we may want a Choicebook */ + wxNotebook mNotebook; + + enum { + kMinWidth = 300, // minimum prefs dialog width, in pixels + }; +}; + +#endif // _SIM_LOG_PREFS_DIALOG_H diff --git a/simulator/app/LogWindow.cpp b/simulator/app/LogWindow.cpp new file mode 100644 index 000000000..53ba9d07e --- /dev/null +++ b/simulator/app/LogWindow.cpp @@ -0,0 +1,1156 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Display runtime log output. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +#include "wx/image.h" // needed for Windows build +#include "wx/dcbuffer.h" + +#include "LogWindow.h" +#include "LogMessage.h" +#include "LogPrefsDialog.h" +#include "MyApp.h" +#include "Preferences.h" +#include "Resource.h" +#include "UserEventMessage.h" + +#include + +static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...); +static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args); + + +using namespace android; + +#if 0 // experiment -- works on Win32, but not with GTK +class MyTextCtrl : public wxTextCtrl { +public: + MyTextCtrl(wxWindow* parent, wxWindowID id, const wxString& value, + const wxPoint& pos, const wxSize& size, int style = 0) + : wxTextCtrl(parent, id, value, pos, size, style) + { + printf("***************** MyTextCtrl!\n"); + } + + void OnScroll(wxScrollWinEvent& event); + void OnScrollBottom(wxScrollWinEvent& event); + +private: + DECLARE_EVENT_TABLE() +}; + +BEGIN_EVENT_TABLE(MyTextCtrl, wxTextCtrl) + EVT_SCROLLWIN(MyTextCtrl::OnScroll) + EVT_SCROLLWIN_BOTTOM(MyTextCtrl::OnScrollBottom) +END_EVENT_TABLE() + +void MyTextCtrl::OnScroll(wxScrollWinEvent& event) +{ + printf("OnScroll!\n"); +} + +void MyTextCtrl::OnScrollBottom(wxScrollWinEvent& event) +{ + printf("OnScrollBottom!\n"); +} +#endif + + +BEGIN_EVENT_TABLE(LogWindow, wxDialog) + EVT_CLOSE(LogWindow::OnClose) + EVT_MOVE(LogWindow::OnMove) + EVT_COMBOBOX(IDC_LOG_LEVEL, LogWindow::OnLogLevel) + EVT_BUTTON(IDC_LOG_CLEAR, LogWindow::OnLogClear) + EVT_BUTTON(IDC_LOG_PAUSE, LogWindow::OnLogPause) + EVT_BUTTON(IDC_LOG_PREFS, LogWindow::OnLogPrefs) +END_EVENT_TABLE() + +/* + * Information about log levels. + * + * Each entry here corresponds to an entry in the combo box. The first + * letter of each name should be unique. + */ +static const struct { + wxString name; + android_LogPriority priority; +} gLogLevels[] = { + { wxT("Verbose"), ANDROID_LOG_VERBOSE }, + { wxT("Debug"), ANDROID_LOG_DEBUG }, + { wxT("Info"), ANDROID_LOG_INFO }, + { wxT("Warn"), ANDROID_LOG_WARN }, + { wxT("Error"), ANDROID_LOG_ERROR } +}; + + +/* + * Create a new LogWindow. This should be a child of the main frame. + */ +LogWindow::LogWindow(wxWindow* parent) + : wxDialog(parent, wxID_ANY, wxT("Log Output"), wxDefaultPosition, + wxDefaultSize, + wxCAPTION | wxSYSTEM_MENU | wxCLOSE_BOX | wxRESIZE_BORDER), + mDisplayArray(NULL), mMaxDisplayMsgs(0), mPaused(false), + mMinPriority(ANDROID_LOG_VERBOSE), + mHeaderFormat(LogPrefsDialog::kHFFull), + mSingleLine(false), mExtraSpacing(0), mPointSize(10), mUseColor(true), + mFontMonospace(true), mWriteFile(false), mTruncateOld(true), mLogFp(NULL), + mNewlyShown(false), mLastPosition(wxDefaultPosition), mVisible(false) +{ + ConstructControls(); + + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + + int poolSize = 10240; // 10MB + pPrefs->GetInt("log-pool-size-kbytes", &poolSize); + assert(poolSize > 0); + mPool.Resize(poolSize * 1024); + + mMaxDisplayMsgs = 1000; + pPrefs->GetInt("log-display-msg-count", &mMaxDisplayMsgs); + assert(mMaxDisplayMsgs > 0); + mDisplayArray = new LogMessage*[mMaxDisplayMsgs]; + memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs); + mTopPtr = -1; + mNextPtr = 0; + + int tmpInt = (int) mHeaderFormat; + pPrefs->GetInt("log-header-format", &tmpInt); + mHeaderFormat = (LogPrefsDialog::HeaderFormat) tmpInt; + pPrefs->GetBool("log-single-line", &mSingleLine); + pPrefs->GetInt("log-extra-spacing", &mExtraSpacing); + pPrefs->GetInt("log-point-size", &mPointSize); + pPrefs->GetBool("log-use-color", &mUseColor); + pPrefs->SetBool("log-font-monospace", &mFontMonospace); + SetTextStyle(); + + mFileName = wxT("/tmp/android-log.txt"); + pPrefs->GetBool("log-write-file", &mWriteFile); + pPrefs->GetString("log-filename", /*ref*/mFileName); + pPrefs->GetBool("log-truncate-old", &mTruncateOld); + + PrepareLogFile(); +} + +/* + * Destroy everything we own. + */ +LogWindow::~LogWindow(void) +{ + ClearDisplay(); + delete[] mDisplayArray; + + if (mLogFp != NULL) + fclose(mLogFp); +} + +/* + * Set the text style, based on our preferences. + */ +void LogWindow::SetTextStyle(void) +{ + wxTextCtrl* pTextCtrl; + pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); + wxTextAttr style; + style = pTextCtrl->GetDefaultStyle(); + + if (mFontMonospace) { + wxFont font(mPointSize, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, + wxFONTWEIGHT_NORMAL); + style.SetFont(font); + } else { + wxFont font(mPointSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, + wxFONTWEIGHT_NORMAL); + style.SetFont(font); + } + + pTextCtrl->SetDefaultStyle(style); +} + +/* + * Set up the goodies in the window. + * + * Also initializes mMinPriority. + */ +void LogWindow::ConstructControls(void) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + wxPanel* base = new wxPanel(this, wxID_ANY); + wxBoxSizer* masterSizer = new wxBoxSizer(wxVERTICAL); + wxBoxSizer* indentSizer = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer* configPrioritySizer = new wxBoxSizer(wxHORIZONTAL); + wxGridSizer* configSizer = new wxGridSizer(4, 1); + + /* + * Configure log level combo box. + */ + wxComboBox* logLevel; + int defaultLogLevel = 1; + pPrefs->GetInt("log-display-level", &defaultLogLevel); + logLevel = new wxComboBox(base, IDC_LOG_LEVEL, wxT(""), + wxDefaultPosition, wxDefaultSize, 0, NULL, + wxCB_READONLY /*| wxSUNKEN_BORDER*/); + for (int i = 0; i < NELEM(gLogLevels); i++) { + logLevel->Append(gLogLevels[i].name); + logLevel->SetClientData(i, (void*) gLogLevels[i].priority); + } + logLevel->SetSelection(defaultLogLevel); + mMinPriority = gLogLevels[defaultLogLevel].priority; + + /* + * Set up stuff at the bottom, starting with the options + * at the bottom left. + */ + configPrioritySizer->Add(new wxStaticText(base, wxID_ANY, wxT("Log level:"), + wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT), + 0, wxALIGN_CENTER_VERTICAL); + configPrioritySizer->AddSpacer(kInterSpacing); + configPrioritySizer->Add(logLevel); + + wxButton* clear = new wxButton(base, IDC_LOG_CLEAR, wxT("&Clear"), + wxDefaultPosition, wxDefaultSize, 0); + wxButton* pause = new wxButton(base, IDC_LOG_PAUSE, wxT("&Pause"), + wxDefaultPosition, wxDefaultSize, 0); + wxButton* prefs = new wxButton(base, IDC_LOG_PREFS, wxT("C&onfigure"), + wxDefaultPosition, wxDefaultSize, 0); + + configSizer->Add(configPrioritySizer, 0, wxALIGN_LEFT); + configSizer->Add(clear, 0, wxALIGN_CENTER); + configSizer->Add(pause, 0, wxALIGN_CENTER); + configSizer->Add(prefs, 0, wxALIGN_RIGHT); + + /* + * Create text ctrl. + */ + wxTextCtrl* pTextCtrl; + pTextCtrl = new wxTextCtrl(base, IDC_LOG_TEXT, wxT(""), + wxDefaultPosition, wxDefaultSize, + wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxTE_NOHIDESEL | + wxHSCROLL); + + /* + * Add components to master sizer. + */ + masterSizer->AddSpacer(kEdgeSpacing); + masterSizer->Add(pTextCtrl, 1, wxEXPAND); + masterSizer->AddSpacer(kInterSpacing); + masterSizer->Add(configSizer, 0, wxEXPAND); + masterSizer->AddSpacer(kEdgeSpacing); + + /* + * Indent from sides. + */ + indentSizer->AddSpacer(kEdgeSpacing); + indentSizer->Add(masterSizer, 1, wxEXPAND); + indentSizer->AddSpacer(kEdgeSpacing); + + base->SetSizer(indentSizer); + + indentSizer->Fit(this); // shrink-to-fit + indentSizer->SetSizeHints(this); // define minimum size +} + +/* + * In some cases, this means the user has clicked on our "close" button. + * We don't really even want one, but both WinXP and KDE put one on our + * window whether we want it or not. So, we make it work as a "hide" + * button instead. + * + * This also gets called when the app is shutting down, and we do want + * to destroy ourselves then, saving various information about our state. + */ +void LogWindow::OnClose(wxCloseEvent& event) +{ + /* just hide the window, unless we're shutting down */ + if (event.CanVeto()) { + event.Veto(); + Show(false); + return; + } + + /* + * Save some preferences. + */ + SaveWindowPrefs(); + + /* if we can't veto the Close(), destroy ourselves */ + Destroy(); +} + +/* + * Save all of our preferences to the config file. + */ +void LogWindow::SaveWindowPrefs(void) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + + /* + * Save shown/hidden state. + */ + pPrefs->SetBool("window-log-show", IsShown()); + + /* + * Limits and formatting prefs. + */ + pPrefs->SetInt("log-display-msg-count", mMaxDisplayMsgs); + pPrefs->SetInt("log-pool-size-kbytes", mPool.GetMaxSize() / 1024); + + pPrefs->SetInt("log-header-format", mHeaderFormat); + pPrefs->SetBool("log-single-line", mSingleLine); + pPrefs->SetInt("log-extra-spacing", mExtraSpacing); + pPrefs->SetInt("log-point-size", mPointSize); + pPrefs->SetBool("log-use-color", mUseColor); + pPrefs->SetBool("log-font-monospace", mFontMonospace); + + pPrefs->SetBool("log-write-file", mWriteFile); + pPrefs->SetString("log-filename", mFileName.ToAscii()); + pPrefs->SetBool("log-truncate-old", mTruncateOld); + + /* + * Save window size and position. + */ + wxPoint posn; + wxSize size; + + assert(pPrefs != NULL); + + posn = GetPosition(); + size = GetSize(); + + pPrefs->SetInt("window-log-x", posn.x); + pPrefs->SetInt("window-log-y", posn.y); + pPrefs->SetInt("window-log-width", size.GetWidth()); + pPrefs->SetInt("window-log-height", size.GetHeight()); + + /* + * Save current setting of debug level combo box. + */ + wxComboBox* pCombo; + int selection; + pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL); + selection = pCombo->GetSelection(); + pPrefs->SetInt("log-display-level", selection); +} + +/* + * Return the desired position and size. + */ +/*static*/ wxRect LogWindow::GetPrefWindowRect(void) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + int x, y, width, height; + + assert(pPrefs != NULL); + + x = y = 10; + width = 500; + height = 200; + + /* these don't modify the arg if the pref doesn't exist */ + pPrefs->GetInt("window-log-x", &x); + pPrefs->GetInt("window-log-y", &y); + pPrefs->GetInt("window-log-width", &width); + pPrefs->GetInt("window-log-height", &height); + + return wxRect(x, y, width, height); +} + +/* + * Under Linux+GTK, the first time you show the window, it appears where + * it's supposed to. If you then hide it and show it again, it gets + * moved on top of the parent window. After that, you can reposition it + * and it remembers its position across hide/show. + * + * To counter this annoyance, we save the position when we hide, and + * reset the position after a show. The "newly shown" flag ensures that + * we only reposition the window as the result of a Show(true) call. + * + * Sometimes, something helpful will shift the window over if it's + * partially straddling a seam between two monitors. I don't see an easy + * way to block this, and I'm not sure I want to anyway. + */ +void LogWindow::OnMove(wxMoveEvent& event) +{ + wxPoint point; + point = event.GetPosition(); + //printf("Sim: log window is at (%d,%d) (new=%d)\n", point.x, point.y, + // mNewlyShown); + + if (mNewlyShown) { + if (mLastPosition == wxDefaultPosition) { + //printf("Sim: no last position established\n"); + } else { + Move(mLastPosition); + } + + mNewlyShown = false; + } +} + +/* + * Set the "newly shown" flag. + */ +bool LogWindow::Show(bool show) +{ + if (show) { + mNewlyShown = true; + Redisplay(); + } else { + mLastPosition = GetPosition(); + } + + mVisible = show; + return wxDialog::Show(show); +} + +/* + * User has adjusted the log level. Update the display appropriately. + * + * This is a wxEVT_COMMAND_COMBOBOX_SELECTED event. + */ +void LogWindow::OnLogLevel(wxCommandEvent& event) +{ + int selection; + android_LogPriority priority; + + selection = event.GetInt(); + wxComboBox* pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL); + priority = (android_LogPriority) (int)pCombo->GetClientData(event.GetInt()); + + printf("Sim: log level selected: %d (%s)\n", (int) priority, + (const char*) gLogLevels[selection].name.ToAscii()); + mMinPriority = priority; + Redisplay(); +} + +/* + * Clear out the log. + */ +void LogWindow::OnLogClear(wxCommandEvent& event) +{ + ClearDisplay(); + mPool.Clear(); +} + +/* + * Handle the pause/resume button. + * + * If we're un-pausing, we need to get caught up. + */ +void LogWindow::OnLogPause(wxCommandEvent& event) +{ + mPaused = !mPaused; + + wxButton* pButton = (wxButton*) FindWindow(IDC_LOG_PAUSE); + if (mPaused) { + pButton->SetLabel(wxT("&Resume")); + + mPool.SetBookmark(); + } else { + pButton->SetLabel(wxT("&Pause")); + + LogMessage* pMsg = mPool.GetBookmark(); + if (pMsg == NULL) { + /* bookmarked item fell out of pool */ + printf("--- bookmark was lost, redisplaying\n"); + Redisplay(); + } else { + /* + * The bookmark points to the last item added to the display. + * We want to chase its "prev" pointer to walk toward the head + * of the list, adding items from oldest to newest. + */ + pMsg = pMsg->GetPrev(); + while (pMsg != NULL) { + if (FilterMatches(pMsg)) + AddToDisplay(pMsg); + pMsg = pMsg->GetPrev(); + } + } + } +} + +/* + * Open log preferences dialog. + */ +void LogWindow::OnLogPrefs(wxCommandEvent& event) +{ + LogPrefsDialog dialog(this); + + /* + * Set up the dialog. + */ + dialog.mHeaderFormat = mHeaderFormat; + dialog.mSingleLine = mSingleLine; + dialog.mExtraSpacing = mExtraSpacing; + dialog.mPointSize = mPointSize; + dialog.mUseColor = mUseColor; + dialog.mFontMonospace = mFontMonospace; + + dialog.mDisplayMax = mMaxDisplayMsgs; + dialog.mPoolSizeKB = mPool.GetMaxSize() / 1024; + + dialog.mWriteFile = mWriteFile; + dialog.mFileName = mFileName; + dialog.mTruncateOld = mTruncateOld; + + /* + * Show it. If they hit "OK", copy the updated values out, and + * re-display the log output. + */ + if (dialog.ShowModal() == wxID_OK) { + /* discard old display arra */ + ClearDisplay(); + delete[] mDisplayArray; + + mHeaderFormat = dialog.mHeaderFormat; + mSingleLine = dialog.mSingleLine; + mExtraSpacing = dialog.mExtraSpacing; + mPointSize = dialog.mPointSize; + mUseColor = dialog.mUseColor; + mFontMonospace = dialog.mFontMonospace; + + assert(dialog.mDisplayMax > 0); + assert(dialog.mPoolSizeKB > 0); + mMaxDisplayMsgs = dialog.mDisplayMax; + mPool.Resize(dialog.mPoolSizeKB * 1024); + + mWriteFile = dialog.mWriteFile; + if (mLogFp != NULL && mFileName != dialog.mFileName) { + printf("--- log file name changed, closing\n"); + fclose(mLogFp); + mLogFp = NULL; + } + mFileName = dialog.mFileName; + mTruncateOld = dialog.mTruncateOld; + + mDisplayArray = new LogMessage*[mMaxDisplayMsgs]; + memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs); + Redisplay(); + + PrepareLogFile(); + } +} + +/* + * Handle a log message "user event". This should only be called in + * the main UI thread. + * + * We take ownership of "*pLogMessage". + */ +void LogWindow::AddLogMessage(LogMessage* pLogMessage) +{ + mPool.Add(pLogMessage); + + if (!mPaused && mVisible && FilterMatches(pLogMessage)) { + /* + * Thought: keep a reference to the previous message. If it + * matches in most fields (all except timestamp?), hold it and + * increment a counter. If we get a message that doesn't match, + * or a timer elapses, synthesize a "previous message repeated N + * times" string. + */ + AddToDisplay(pLogMessage); + } + + // release the initial ref caused by allocation + pLogMessage->Release(); + + if (mLogFp != NULL) + LogToFile(pLogMessage); +} + +/* + * Clear out the display, releasing any log messages held in the display + * array. + */ +void LogWindow::ClearDisplay(void) +{ + wxTextCtrl* pTextCtrl; + pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); + pTextCtrl->Clear(); + + /* + * Just run through the entire array. + */ + for (int i = 0; i < mMaxDisplayMsgs; i++) { + if (mDisplayArray[i] != NULL) { + mDisplayArray[i]->Release(); + mDisplayArray[i] = NULL; + } + } + mTopPtr = -1; + mNextPtr = 0; +} + +/* + * Clear the current display and regenerate it from the log pool. We need + * to do this whenever we change filters or log message formatting. + */ +void LogWindow::Redisplay(void) +{ + /* + * Freeze output rendering so it doesn't flash during update. Doesn't + * seem to help for GTK, and it leaves garbage on the screen in WinXP, + * so I'm leaving it commented out. + */ + //wxTextCtrl* pText = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); + //pText->Freeze(); + + //printf("--- redisplay\n"); + ClearDisplay(); + + /* + * Set up the default wxWidgets text style stuff. + */ + SetTextStyle(); + + /* + * Here's the plan: + * - Start at the head of the pool (where the most recently added + * items are). + * - Check to see if the current item passes our filter. If it does, + * increment the "found count". + * - Continue in this manner until we run out of pool or have + * sufficient items to fill the screen. + * - Starting from the current position, walk back toward the head, + * adding the items that meet the current filter criteria. + * + * Don't forget that the log pool could be empty. + */ + LogMessage* pMsg = mPool.GetHead(); + + if (pMsg != NULL) { + int foundCount = 0; + + // note this stops before it runs off the end + while (pMsg->GetNext() != NULL && foundCount < mMaxDisplayMsgs) { + if (FilterMatches(pMsg)) + foundCount++; + pMsg = pMsg->GetNext(); + } + + while (pMsg != NULL) { + if (FilterMatches(pMsg)) + AddToDisplay(pMsg); + pMsg = pMsg->GetPrev(); + } + } + + //pText->Thaw(); +} + + +/* + * Returns "true" if the currently specified filters would allow this + * message to be shown. + */ +bool LogWindow::FilterMatches(const LogMessage* pLogMessage) +{ + if (pLogMessage->GetPriority() >= mMinPriority) + return true; + else + return false; +} + +/* + * Realloc the array of pointers, and remove anything from the display + * that should no longer be there. + */ +void LogWindow::SetMaxDisplayMsgs(int max) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + + pPrefs->SetInt("log-display-msg-count", max); +} + +/* + * Add the message to the display array and to the screen. + */ +void LogWindow::AddToDisplay(LogMessage* pLogMessage) +{ + wxTextCtrl* pTextCtrl; + pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); + + if (mNextPtr == mTopPtr) { + /* + * The display array is full. + * + * We need to eliminate the topmost entry. This requires removing + * it from the array and removing the text from the wxTextCtrl. + */ + pTextCtrl->Remove(0, mDisplayArray[mTopPtr]->GetTextCtrlLen()); + mDisplayArray[mTopPtr]->Release(); + mTopPtr = (mTopPtr + 1) % mMaxDisplayMsgs; + } + + /* + * Add formatted text to the text ctrl. Track how much actual space + * is required. The space may be different on Win32 (CRLF-based) vs. + * GTK (LF-based), so we need to measure it, not compute it from the + * text string. + */ + long lastBefore, lastAfter; + //long insertBefore; + //insertBefore = pTextCtrl->GetInsertionPoint(); + lastBefore = pTextCtrl->GetLastPosition(); + FormatMessage(pLogMessage, pTextCtrl); + lastAfter = pTextCtrl->GetLastPosition(); + pLogMessage->SetTextCtrlLen(lastAfter - lastBefore); + + /* + * If we restore the old insertion point, we will be glued to where + * we were. This is okay until we start deleting text from the top, + * at which point we need to adjust it to retain our position. + * + * If we set the insertion point to the bottom, we effectively + * implement "scroll to bottom on output". + * + * If we don't set it at all, we get slightly strange behavior out + * of GTK, which seems to be par for the course here. + */ + //pTextCtrl->SetInsertionPoint(insertBefore); // restore insertion pt + pTextCtrl->SetInsertionPoint(lastAfter); + + /* add it to array, claim ownership */ + mDisplayArray[mNextPtr] = pLogMessage; + pLogMessage->Acquire(); + + /* adjust pointers */ + if (mTopPtr < 0) // first time only + mTopPtr = 0; + mNextPtr = (mNextPtr + 1) % mMaxDisplayMsgs; +} + + +/* + * Return a human-readable string for the priority level. Always returns + * a valid string. + */ +static const wxCharBuffer GetPriorityString(android_LogPriority priority) +{ + int idx; + + idx = (int) priority - (int) ANDROID_LOG_VERBOSE; + if (idx < 0 || idx >= NELEM(gLogLevels)) + return "?unknown?"; + return gLogLevels[idx].name.ToAscii(); +} + +/* + * Format a message and write it to the text control. + */ +void LogWindow::FormatMessage(const LogMessage* pLogMessage, + wxTextCtrl* pTextCtrl) +{ +#if defined(HAVE_LOCALTIME_R) + struct tm tmBuf; +#endif + struct tm* ptm; + char timeBuf[32]; + char msgBuf[256]; + int msgLen = 0; + char* outBuf; + char priChar; + LogPrefsDialog::HeaderFormat headerFmt; + + headerFmt = mHeaderFormat; + if (pLogMessage->GetInternal()) + headerFmt = LogPrefsDialog::kHFInternal; + + priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0]; + + /* + * Get the current date/time in pretty form + * + * It's often useful when examining a log with "less" to jump to + * a specific point in the file by searching for the date/time stamp. + * For this reason it's very annoying to have regexp meta characters + * in the time stamp. Don't use forward slashes, parenthesis, + * brackets, asterisks, or other special chars here. + */ + time_t when = pLogMessage->GetWhen(); + const char* fmt = NULL; +#if defined(HAVE_LOCALTIME_R) + ptm = localtime_r(&when, &tmBuf); +#else + ptm = localtime(&when); +#endif + switch (headerFmt) { + case LogPrefsDialog::kHFFull: + case LogPrefsDialog::kHFInternal: + fmt = "%m-%d %H:%M:%S"; + break; + case LogPrefsDialog::kHFBrief: + case LogPrefsDialog::kHFMinimal: + fmt = "%H:%M:%S"; + break; + default: + break; + } + if (fmt != NULL) + strftime(timeBuf, sizeof(timeBuf), fmt, ptm); + else + strcpy(timeBuf, "-"); + + const int kMaxExtraNewlines = 2; + char hdrNewline[2]; + char finalNewlines[kMaxExtraNewlines+1 +1]; + + if (mSingleLine) + hdrNewline[0] = ' '; + else + hdrNewline[0] = '\n'; + hdrNewline[1] = '\0'; + + assert(mExtraSpacing <= kMaxExtraNewlines); + int i; + for (i = 0; i < mExtraSpacing+1; i++) + finalNewlines[i] = '\n'; + finalNewlines[i] = '\0'; + + wxTextAttr msgColor; + switch (pLogMessage->GetPriority()) { + case ANDROID_LOG_WARN: + msgColor.SetTextColour(*wxBLUE); + break; + case ANDROID_LOG_ERROR: + msgColor.SetTextColour(*wxRED); + break; + case ANDROID_LOG_VERBOSE: + case ANDROID_LOG_DEBUG: + case ANDROID_LOG_INFO: + default: + msgColor.SetTextColour(*wxBLACK); + break; + } + if (pLogMessage->GetInternal()) + msgColor.SetTextColour(*wxGREEN); + + /* + * Construct a buffer containing the log header. + */ + bool splitHeader = true; + outBuf = msgBuf; + switch (headerFmt) { + case LogPrefsDialog::kHFFull: + splitHeader = true; + msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), + "[ %s %5d %c/%-6.6s]%s", + timeBuf, pLogMessage->GetPid(), priChar, + pLogMessage->GetTag(), hdrNewline); + break; + case LogPrefsDialog::kHFBrief: + splitHeader = true; + msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), + "[%s %5d]%s", + timeBuf, pLogMessage->GetPid(), hdrNewline); + break; + case LogPrefsDialog::kHFMinimal: + splitHeader = false; + msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), + "%s %5d- %s", + timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg()); + break; + case LogPrefsDialog::kHFInternal: + splitHeader = false; + msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), + "[%s] %s", timeBuf, pLogMessage->GetMsg()); + break; + default: + fprintf(stderr, "Sim: unexpected header format %d\n", headerFmt); + assert(false); + break; + } + + if (msgLen < 0) { + fprintf(stderr, "WHOOPS\n"); + assert(outBuf == msgBuf); + return; + } + + if (splitHeader) { + if (mUseColor) + pTextCtrl->SetDefaultStyle(wxTextAttr(*wxLIGHT_GREY)); + pTextCtrl->AppendText(wxString::FromAscii(outBuf)); + if (mUseColor) + pTextCtrl->SetDefaultStyle(msgColor); + pTextCtrl->AppendText(wxString::FromAscii(pLogMessage->GetMsg())); + if (mUseColor) + pTextCtrl->SetDefaultStyle(*wxBLACK); + pTextCtrl->AppendText(wxString::FromAscii(finalNewlines)); + } else { + if (mUseColor) + pTextCtrl->SetDefaultStyle(msgColor); + pTextCtrl->AppendText(wxString::FromAscii(outBuf)); + if (mUseColor) + pTextCtrl->SetDefaultStyle(*wxBLACK); + pTextCtrl->AppendText(wxString::FromAscii(finalNewlines)); + } + + /* if we allocated storage for this message, free it */ + if (outBuf != msgBuf) + free(outBuf); +} + +/* + * Write the message to the log file. + * + * We can't just do this in FormatMessage(), because that re-writes all + * messages on the display whenever the output format or filter changes. + * + * Use a one-log-per-line format here to make "grep" useful. + */ +void LogWindow::LogToFile(const LogMessage* pLogMessage) +{ +#if defined(HAVE_LOCALTIME_R) + struct tm tmBuf; +#endif + struct tm* ptm; + char timeBuf[32]; + char msgBuf[256]; + int msgLen; + char* outBuf; + char priChar; + + assert(mLogFp != NULL); + + time_t when = pLogMessage->GetWhen(); +#if defined(HAVE_LOCALTIME_R) + ptm = localtime_r(&when, &tmBuf); +#else + ptm = localtime(&when); +#endif + + strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm); + priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0]; + + outBuf = msgBuf; + if (pLogMessage->GetInternal()) { + msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), + "[%s %5d *] %s\n", + timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg()); + } else { + msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), + "[%s %5d %c] %s)\n", + timeBuf, pLogMessage->GetPid(), priChar, + pLogMessage->GetMsg()); + } + if (fwrite(outBuf, msgLen, 1, mLogFp) != 1) + fprintf(stderr, "Sim: WARNING: partial log write\n"); + fflush(mLogFp); + + /* if we allocated storage for this message, free it */ + if (outBuf != msgBuf) + free(outBuf); +} + +/* + * Get the modification date of a file. + */ +static bool GetFileModDate(const char* fileName, time_t* pModWhen) +{ + struct stat sb; + + if (stat(fileName, &sb) < 0) + return false; + + *pModWhen = sb.st_mtime; + return true; +} + +/* + * Open or close the log file as appropriate. + */ +void LogWindow::PrepareLogFile(void) +{ + const int kLogFileMaxAge = 8 * 60 * 60; // 8 hours + + if (!mWriteFile && mLogFp != NULL) { + printf("Sim: closing log file\n"); + fclose(mLogFp); + mLogFp = NULL; + } else if (mWriteFile && mLogFp == NULL) { + printf("Sim: opening log file '%s'\n", (const char*)mFileName.ToAscii()); + time_t now, modWhen = 0; + const char* openFlags; + + now = time(NULL); + if (!mTruncateOld || + (GetFileModDate(mFileName.ToAscii(), &modWhen) && + modWhen + kLogFileMaxAge > now)) + { + if (modWhen != 0) { + printf("--- log file is %.3f hours old, appending\n", + (now - modWhen) / 3600.0); + } + openFlags = "a"; // open for append (text mode) + } else { + if (modWhen != 0) { + printf("--- log file is %.3f hours old, truncating\n", + (now - modWhen) / 3600.0); + } + openFlags = "w"; // open for writing, truncate (text mode) + } + + mLogFp = fopen(mFileName.ToAscii(), openFlags); + if (mLogFp == NULL) { + fprintf(stderr, "Sim: failed opening log file '%s': %s\n", + (const char*) mFileName.ToAscii(), strerror(errno)); + } else { + fprintf(mLogFp, "\n\n"); + fflush(mLogFp); + } + } +} + +/* + * Add a new log message. + * + * This function can be called from any thread. It makes a copy of the + * stuff in "*pBundle" and sends it to the main UI thread. + */ +/*static*/ void LogWindow::PostLogMsg(const android_LogBundle* pBundle) +{ + LogMessage* pNewMessage = LogMessage::Create(pBundle); + + SendToWindow(pNewMessage); +} + +/* + * Post a simple string to the log. + */ +/*static*/ void LogWindow::PostLogMsg(const char* msg) +{ + LogMessage* pNewMessage = LogMessage::Create(msg); + + SendToWindow(pNewMessage); +} + +/* + * Post a simple wxString to the log. + */ +/*static*/ void LogWindow::PostLogMsg(const wxString& msg) +{ + LogMessage* pNewMessage = LogMessage::Create(msg.ToAscii()); + + SendToWindow(pNewMessage); +} + +/* + * Send a log message to the log window. + */ +/*static*/ void LogWindow::SendToWindow(LogMessage* pMessage) +{ + if (pMessage != NULL) { + wxWindow* pMainFrame = ((MyApp*)wxTheApp)->GetMainFrame(); + UserEventMessage* pUem = new UserEventMessage; + pUem->CreateLogMessage(pMessage); + + UserEvent uev(0, (void*) pUem); + + pMainFrame->AddPendingEvent(uev); + } else { + fprintf(stderr, "Sim: failed to add new log message\n"); + } +} + + +/* + * This is a sanity check. We need to stop somewhere to avoid trashing + * the system on bad input. + */ +#define kMaxLen 65536 + +#define VSNPRINTF vsnprintf // used to worry about _vsnprintf + + +/* + * Print a formatted message into a buffer. Pass in a buffer to try to use. + * + * If the buffer isn't big enough to hold the message, allocate storage + * with malloc() and return that instead. The caller is responsible for + * freeing the storage. + * + * Returns the length of the string, or -1 if the printf call failed. + */ +static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args) +{ + int charsOut; + char* localBuf = NULL; + + assert(pBuf != NULL && *pBuf != NULL); + assert(bufLen > 0); + assert(format != NULL); + + while (1) { + /* + * In some versions of libc, vsnprintf only returns 0 or -1, where + * -1 indicates the the buffer wasn't big enough. In glibc 2.1 + * and later, it returns the actual size needed. + * + * MinGW is just returning -1, so we have to retry there. + */ + char* newBuf; + + charsOut = VSNPRINTF(*pBuf, bufLen, format, args); + + if (charsOut >= 0 && charsOut < bufLen) + break; + + //fprintf(stderr, "EXCEED: %d vs %d\n", charsOut, bufLen); + if (charsOut < 0) { + /* exact size not known, double previous size */ + bufLen *= 2; + if (bufLen > kMaxLen) + goto fail; + } else { + /* exact size known, just use that */ + + bufLen = charsOut + 1; + } + //fprintf(stderr, "RETRY at %d\n", bufLen); + + newBuf = (char*) realloc(localBuf, bufLen); + if (newBuf == NULL) + goto fail; + *pBuf = localBuf = newBuf; + } + + // On platforms where snprintf() doesn't return the number of + // characters output, we would need to call strlen() here. + + return charsOut; + +fail: + if (localBuf != NULL) { + free(localBuf); + *pBuf = NULL; + } + return -1; +} + +/* + * Variable-arg form of the above. + */ +static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...) +{ + va_list args; + int result; + + va_start(args, format); + result = android_vsnprintfBuffer(pBuf, bufLen, format, args); + va_end(args); + + return result; +} + + diff --git a/simulator/app/LogWindow.h b/simulator/app/LogWindow.h new file mode 100644 index 000000000..ca9f133e4 --- /dev/null +++ b/simulator/app/LogWindow.h @@ -0,0 +1,127 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Window with log output. +// +#ifndef _SIM_LOG_WINDOW_H +#define _SIM_LOG_WINDOW_H + +#include "PhoneData.h" +#include "UserEvent.h" +#include "LogMessage.h" +#include "LogPool.h" +#include "LogPrefsDialog.h" + + +/* + * Display log output from runtime process. + * + * We receive the messages broken into components (date, log level, tag, + * function name, etc.) and do the formatting ourselves. We receive all + * messages regardless of log level, and provide filter controls in the + * window. + * + * Messages are stored in a "log pool", which has a fixed memory footprint. + * The messages that are currently visible in the log output window are + * also pointed at from a fixed-size display array. Changes to output + * format cause us to clear the display and re-show everything in the + * display array, while changes to the output filter cause us to + * re-evaluate the contents of the display pool. + */ +class LogWindow : public wxDialog { +public: + LogWindow(wxWindow* parent); + virtual ~LogWindow(void); + + /* we override this, to cope with annoying GTK behavior */ + virtual bool Show(bool show = true); + + /* return preferred size and position */ + static wxRect GetPrefWindowRect(void); + + /* handle a log message "user event" */ + void AddLogMessage(LogMessage* pLogMessage); + + /* resize the display messages array */ + void SetMaxDisplayMsgs(int max); + + /* post a message to the log; may be called from non-main thread */ + static void PostLogMsg(const android_LogBundle* pBundle); + static void PostLogMsg(const wxString& msg); + static void PostLogMsg(const char* msg); + +private: + void OnMove(wxMoveEvent& event); + void OnClose(wxCloseEvent& event); + void OnLogLevel(wxCommandEvent& event); + void OnLogClear(wxCommandEvent& event); + void OnLogPause(wxCommandEvent& event); + void OnLogPrefs(wxCommandEvent& event); + + /* handle incoming log message */ + void OnUserEvent(UserEvent& event); + + void SaveWindowPrefs(void); + void ConstructControls(void); + + void AddToDisplay(LogMessage* pLogMessage); + void ClearDisplay(void); + void Redisplay(void); + void SetTextStyle(void); + + bool FilterMatches(const LogMessage* pLogMessage); + + void FormatMessage(const LogMessage* pLogMessage, + wxTextCtrl* pTextCtrl); + + void LogToFile(const LogMessage* pLogMessage); + void PrepareLogFile(void); + static void SendToWindow(LogMessage* pMessage); + + /* + * Message pool. + */ + LogPool mPool; + + /* + * Display array. This is a fixed-size circular array that holds + * pointers to the log messages currently displayed on screen. + */ + LogMessage** mDisplayArray; // ptrs to messages currently displayed + int mMaxDisplayMsgs; // max #of messages + int mTopPtr; // index of top message + int mNextPtr; // index of next empty slot + + bool mPaused; // is output paused for review? + + /* + * Current filter. + */ + android_LogPriority mMinPriority; // messages at or above are shown + + /* format options */ + LogPrefsDialog::HeaderFormat mHeaderFormat; + bool mSingleLine; // put whole message on one line? + int mExtraSpacing; // double/triple-space messages? + int mPointSize; // text point size; + bool mUseColor; // colorful messages? + bool mFontMonospace; // use monospace font? + + /* log file options */ + bool mWriteFile; + wxString mFileName; + bool mTruncateOld; + + FILE* mLogFp; + + /* + * Window position stuff. + */ + bool mNewlyShown; + wxPoint mLastPosition; + bool mVisible; + + DECLARE_EVENT_TABLE() +}; + +#endif // _SIM_LOG_WINDOW_H diff --git a/simulator/app/MainFrame.cpp b/simulator/app/MainFrame.cpp new file mode 100644 index 000000000..b13caab2d --- /dev/null +++ b/simulator/app/MainFrame.cpp @@ -0,0 +1,1462 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Main window, menu bar, and associated goodies. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +#include "wx/button.h" +#include "wx/help.h" +#include "wx/filedlg.h" +#include "wx/slider.h" +#include "wx/textctrl.h" + +#include "MainFrame.h" +#include "MyApp.h" +#include "Resource.h" +#include "PhoneCollection.h" +#include "PhoneData.h" +#include "PhoneWindow.h" +#include "DeviceWindow.h" +#include "UserEventMessage.h" +#include "PrefsDialog.h" + +#include "SimRuntime.h" + + +static wxString kStatusNotRunning = wxT("Idle"); +static wxString kStatusRunning = wxT("Run"); + +static wxString kDeviceMenuString = wxT("&Device"); + +static const wxString gStdJavaApps[] = { + wxT(""), + wxT("com.android.testharness.TestList"), + wxT("com.android.apps.contacts.ContactsList"), + wxT("mikeapp") +}; + + +BEGIN_EVENT_TABLE(MainFrame::MainFrame, wxFrame) + EVT_CLOSE(MainFrame::OnClose) + EVT_TIMER(kHalfSecondTimerId, MainFrame::OnTimer) + //EVT_IDLE(MainFrame::OnIdle) + + EVT_ACTIVATE(MainFrame::OnActivate) + EVT_ACTIVATE_APP(MainFrame::OnActivate) + EVT_COMBOBOX(IDC_MODE_SELECT, MainFrame::OnComboBox) + EVT_COMBOBOX(IDC_JAVA_VM, MainFrame::OnComboBox) + EVT_CHECKBOX(IDC_USE_GDB, MainFrame::OnCheckBox) + EVT_CHECKBOX(IDC_USE_VALGRIND, MainFrame::OnCheckBox) + EVT_CHECKBOX(IDC_CHECK_JNI, MainFrame::OnCheckBox) + EVT_CHECKBOX(IDC_OVERLAY_ONION_SKIN, MainFrame::OnCheckBox) + EVT_TEXT(IDC_JAVA_APP_NAME, MainFrame::OnText) + EVT_TEXT_ENTER(IDC_ONION_SKIN_FILE_NAME, MainFrame::OnTextEnter) + EVT_BUTTON(IDC_ONION_SKIN_BUTTON, MainFrame::OnButton) + EVT_COMMAND_SCROLL(IDC_ONION_SKIN_ALPHA_VAL, MainFrame::OnSliderChange) + + EVT_MENU(IDM_FILE_PREFERENCES, MainFrame::OnFilePreferences) + EVT_MENU(IDM_FILE_EXIT, MainFrame::OnFileExit) + EVT_MENU(IDM_RUNTIME_START, MainFrame::OnSimStart) + EVT_UPDATE_UI(IDM_RUNTIME_START, MainFrame::OnUpdateSimStart) + EVT_MENU(IDM_RUNTIME_STOP, MainFrame::OnSimStop) + EVT_UPDATE_UI(IDM_RUNTIME_STOP, MainFrame::OnUpdateSimStop) + EVT_MENU(IDM_RUNTIME_RESTART, MainFrame::OnSimRestart) + EVT_UPDATE_UI(IDM_RUNTIME_RESTART, MainFrame::OnUpdateSimRestart) + EVT_MENU(IDM_RUNTIME_KILL, MainFrame::OnSimKill) + EVT_UPDATE_UI(IDM_RUNTIME_KILL, MainFrame::OnUpdateSimKill) + EVT_MENU_RANGE(IDM_DEVICE_SEL0, IDM_DEVICE_SELN, + MainFrame::OnDeviceSelected) + EVT_MENU(IDM_DEVICE_RESCAN, MainFrame::OnDeviceRescan) + EVT_UPDATE_UI(IDM_DEBUG_SHOW_LOG, MainFrame::OnUpdateDebugShowLog) + EVT_MENU(IDM_DEBUG_SHOW_LOG, MainFrame::OnDebugShowLog) + EVT_MENU(IDM_HELP_CONTENTS, MainFrame::OnHelpContents) + EVT_MENU(IDM_HELP_ABOUT, MainFrame::OnHelpAbout) + + EVT_USER_EVENT(MainFrame::OnUserEvent) +END_EVENT_TABLE() + + +/* + * Main window constructor. + * + * Creates menus and status bar. + */ +MainFrame::MainFrame(const wxString& title, const wxPoint& pos, + const wxSize& size, long style) + : wxFrame((wxFrame *)NULL, -1, title, pos, size, style), + mSimRunning(false), + mRestartRequested(false), + mpPhoneWindow(NULL), + mPhoneWindowPosn(wxDefaultPosition), + mTimer(this, kHalfSecondTimerId) +{ + mSimAssetPath = ((MyApp*)wxTheApp)->GetSimAssetPath(); + mSimAssetPath += wxT("/simulator/default/default"); + + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + int val; + + val = mPhoneWindowPosn.x; + pPrefs->GetInt("window-device-x", &val); + mPhoneWindowPosn.x = val; + val = mPhoneWindowPosn.y; + pPrefs->GetInt("window-device-y", &val); + mPhoneWindowPosn.y = val; + + /* + * Create main menu. + */ + ConstructMenu(); + + /* + * Create the status bar. + */ + int widths[2] = { -1, 50 }; + CreateStatusBar(2, wxFULL_REPAINT_ON_RESIZE); // no wxST_SIZEGRIP + SetStatusWidths(2, widths); + SetStatusText(wxT("Ready")); + SetStatusText(kStatusNotRunning, 1); + + /* + * Create main window controls. + */ + ConstructControls(); + +#if 0 + /* + * Use the standard window color for the main frame (which usually + * has a darker color). This has a dramatic effect under Windows. + */ + wxColour color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + SetOwnBackgroundColour(color); +#endif + + /* + * Create the log window. + */ + wxRect layout = LogWindow::GetPrefWindowRect(); + mpLogWindow = new LogWindow(this); + mpLogWindow->Move(layout.GetTopLeft()); + mpLogWindow->SetSize(layout.GetSize()); + bool showLogWindow = true; + pPrefs->GetBool("window-log-show", &showLogWindow); + if (showLogWindow) + mpLogWindow->Show(); + + /* + * Set up a frequent timer. We use this to keep our "run/idle" + * display up to date. (Ideally this will go away.) + */ + mTimer.Start(400); // arg is delay in ms + + /* + * Handle auto-power-on by sending ourselves an event. That way it + * gets handled after window initialization finishes. + */ + bool autoPowerOn = false; + pPrefs->GetBool("auto-power-on", &autoPowerOn); + if (autoPowerOn) { + printf("Sim: Auto power-up\n"); + wxCommandEvent startEvent(wxEVT_COMMAND_MENU_SELECTED, IDM_RUNTIME_START); + this->AddPendingEvent(startEvent); + } + + /* + * wxThread wants these to be on the heap -- it will call delete on the + * object when the thread exits. + */ + mExternalRuntimeThread = new ExternalRuntime(); + mExternalRuntimeThread->StartThread(); + mPropertyServerThread = new PropertyServer(); + mPropertyServerThread->StartThread(); +} + +/* + * Construct the main menu. Called from the constructor. + */ +void MainFrame::ConstructMenu(void) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + + /* + * Scan for available phones. + */ + PhoneCollection* pCollection = PhoneCollection::GetInstance(); + pCollection->ScanForPhones(mSimAssetPath.ToAscii()); + + /* + * Create the "File" menu. + */ + wxMenu* menuFile = new wxMenu; + + menuFile->Append(IDM_FILE_PREFERENCES, wxT("&Preferences..."), + wxT("Edit simulator preferences")); + menuFile->AppendSeparator(); + menuFile->Append(IDM_FILE_EXIT, wxT("E&xit\tCtrl-Q"), + wxT("Stop simulator and exit")); + + /* + * Create the "Runtime" menu. + */ + wxMenu* menuRuntime = new wxMenu; + menuRuntime->Append(IDM_RUNTIME_START, wxT("&Power On\tCtrl-G"), + wxT("Start the device")); +// menuRuntime->Append(IDM_RUNTIME_STOP, wxT("Power &Off"), +// wxT("Stop the device")); + menuRuntime->AppendSeparator(); +// menuRuntime->Append(IDM_RUNTIME_RESTART, wxT("&Restart"), +// wxT("Restart the device")); + menuRuntime->Append(IDM_RUNTIME_KILL, wxT("&Kill\tCtrl-K"), + wxT("Kill the runtime processes")); + + /* + * Create "Device" menu. + */ + wxString defaultDevice = wxT("Sooner"); + pPrefs->GetString("default-device", /*ref*/ defaultDevice); + wxMenu* menuDevice = CreateDeviceMenu(defaultDevice.ToAscii()); + + /* + * Create "Debug" menu. + */ + wxMenu* menuDebug = new wxMenu; + menuDebug->AppendCheckItem(IDM_DEBUG_SHOW_LOG, wxT("View &Log Output"), + wxT("View log output window")); + + /* + * Create the "Help" menu. + */ + wxMenu* menuHelp = new wxMenu; + menuHelp->Append(IDM_HELP_CONTENTS, wxT("&Contents...\tF1"), + wxT("Simulator help")); + menuHelp->AppendSeparator(); + menuHelp->Append(IDM_HELP_ABOUT, wxT("&About..."), + wxT("See the fabulous 'about' box")); + + /* + * Create the menu bar. + */ + wxMenuBar *menuBar = new wxMenuBar; + menuBar->Append(menuFile, wxT("&File")); + menuBar->Append(menuDevice, kDeviceMenuString); + menuBar->Append(menuRuntime, wxT("&Runtime")); + menuBar->Append(menuDebug, wxT("&Debug")); + menuBar->Append(menuHelp, wxT("&Help")); + + SetMenuBar(menuBar); + +} + +/* + * Construct the "device" menu from our phone collection. + */ +wxMenu* MainFrame::CreateDeviceMenu(const char* defaultItemName) +{ + wxMenu* menuDevice = new wxMenu; + PhoneCollection* pCollection = PhoneCollection::GetInstance(); + int defaultModel = 0; + + for (int i = 0; i < pCollection->GetPhoneCount(); i++) { + PhoneData* pPhoneData = pCollection->GetPhoneData(i); + assert(pPhoneData != NULL); + + menuDevice->AppendRadioItem(IDM_DEVICE_SEL0 + i, + wxString::FromAscii(pPhoneData->GetTitle())); + + // use this one as default if the string matches + if (strcasecmp(pPhoneData->GetName(), defaultItemName) == 0) + defaultModel = i; + } + + menuDevice->Check(IDM_DEVICE_SEL0 + defaultModel, true); + + menuDevice->AppendSeparator(); + menuDevice->Append(IDM_DEVICE_RESCAN, wxT("Re-scan")); + + return menuDevice; +} + +/* + * Create some controls in the main window. + * + * The main frame doesn't use the normal background color that you find + * in dialog windows, so we create a "panel" and put all the controls + * on that. + */ +void MainFrame::ConstructControls(void) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + wxPanel* base = new wxPanel(this, wxID_ANY); + wxBoxSizer* masterSizer = new wxBoxSizer(wxVERTICAL); + wxBoxSizer* tmpSizer; + wxStaticBoxSizer* displayOptSizer; + wxStaticBoxSizer* runtimeOptSizer; + wxStaticBoxSizer* onionSkinOptSizer; + wxComboBox* pModeSelection; + wxCheckBox* pUseGDB; + wxCheckBox* pUseValgrind; + wxCheckBox* pCheckJni; + wxCheckBox* pOverlayOnionSkin; + + displayOptSizer = new wxStaticBoxSizer(wxHORIZONTAL, base, + wxT("Configuration")); + runtimeOptSizer = new wxStaticBoxSizer(wxVERTICAL, base, + wxT("Runtime Options")); + onionSkinOptSizer = new wxStaticBoxSizer(wxVERTICAL, base, + wxT("Onion Skin Options")); + + /* + * Set up the configuration sizer (nee "display options"). + */ + tmpSizer = new wxBoxSizer(wxHORIZONTAL); + displayOptSizer->Add(tmpSizer); + tmpSizer->Add( + new wxStaticText(base, wxID_ANY, wxT("Device mode:"), + wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT), 0, wxALIGN_CENTER_VERTICAL); + pModeSelection = new wxComboBox(base, IDC_MODE_SELECT, wxT(""), + wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY); + tmpSizer->AddSpacer(kInterSpacing); + tmpSizer->Add(pModeSelection); + + displayOptSizer->AddSpacer(kInterSpacing); + + /* + * Configure the runtime options sizer. + */ + wxComboBox* pJavaAppName; + tmpSizer = new wxBoxSizer(wxHORIZONTAL); + pUseGDB = new wxCheckBox(base, IDC_USE_GDB, wxT("Use &debugger")); + tmpSizer->Add(pUseGDB); + tmpSizer->AddSpacer(kInterSpacing); + pUseValgrind = new wxCheckBox(base, IDC_USE_VALGRIND, wxT("Use &valgrind")); + tmpSizer->Add(pUseValgrind); + tmpSizer->AddSpacer(kInterSpacing); + pCheckJni = new wxCheckBox(base, IDC_CHECK_JNI, wxT("Check &JNI")); + tmpSizer->Add(pCheckJni); + + pJavaAppName = new wxComboBox(base, IDC_JAVA_APP_NAME, wxT(""), + wxDefaultPosition, wxSize(320, -1), NELEM(gStdJavaApps), gStdJavaApps, + wxCB_DROPDOWN); + wxBoxSizer* javaAppSizer = new wxBoxSizer(wxHORIZONTAL); + javaAppSizer->Add( + new wxStaticText(base, wxID_ANY, + wxT("Java app:"), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT), + 0, wxALIGN_CENTER_VERTICAL); + javaAppSizer->AddSpacer(kInterSpacing); + javaAppSizer->Add(pJavaAppName); + + runtimeOptSizer->Add(tmpSizer); + + runtimeOptSizer->AddSpacer(kInterSpacing); + runtimeOptSizer->Add(javaAppSizer); + runtimeOptSizer->AddSpacer(kInterSpacing); + + wxString tmpStr; + SetCheckFromPref(pUseGDB, "debug", false); + SetCheckFromPref(pUseValgrind, "valgrind", false); + SetCheckFromPref(pCheckJni, "check-jni", false); + if (pPrefs->GetString("java-app-name", /*ref*/ tmpStr)) + pJavaAppName->SetValue(tmpStr); + + /* + * Configure the onion skin options sizer. + */ + wxTextCtrl* pOnionSkinFileNameText; + wxButton* pOnionSkinFileButton; + wxSlider* pOnionSkinAlphaSlider; + tmpSizer = new wxBoxSizer(wxHORIZONTAL); + pOverlayOnionSkin = new wxCheckBox(base, + IDC_OVERLAY_ONION_SKIN, wxT("Overlay &onion skin")); + tmpSizer->Add(pOverlayOnionSkin); + + pOnionSkinFileNameText = new wxTextCtrl(base, + IDC_ONION_SKIN_FILE_NAME, wxT(""), + wxDefaultPosition, wxSize(250, -1), + wxTE_PROCESS_ENTER); + pOnionSkinFileButton = new wxButton(base, IDC_ONION_SKIN_BUTTON, + wxT("Choose")); + + wxBoxSizer* onionSkinFileNameSizer = new wxBoxSizer(wxHORIZONTAL); + onionSkinFileNameSizer->Add( + new wxStaticText(base, wxID_ANY, + wxT("Filename:"), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT), + 0, wxALIGN_CENTER_VERTICAL); + onionSkinFileNameSizer->AddSpacer(kInterSpacing); + onionSkinFileNameSizer->Add(pOnionSkinFileNameText); + onionSkinFileNameSizer->Add(pOnionSkinFileButton); + + wxBoxSizer * onionSkinAlphaSizer = new wxBoxSizer(wxHORIZONTAL); + int initialAlphaVal = 127; + pPrefs->GetInt("onion-skin-alpha-value", &initialAlphaVal); + pOnionSkinAlphaSlider = new wxSlider(base, IDC_ONION_SKIN_ALPHA_VAL, + initialAlphaVal, 0, 255, wxDefaultPosition, wxSize(150, 20)); + onionSkinAlphaSizer->Add( + new wxStaticText(base, wxID_ANY, + wxT("Transparency:"), + wxDefaultPosition, wxDefaultSize, + wxALIGN_LEFT), + 0, wxALIGN_CENTER_VERTICAL); + onionSkinAlphaSizer->AddSpacer(kInterSpacing); + onionSkinAlphaSizer->Add(pOnionSkinAlphaSlider, 1, wxCENTRE | wxALL, 5); + + onionSkinOptSizer->Add(tmpSizer); + onionSkinOptSizer->AddSpacer(kInterSpacing); + onionSkinOptSizer->Add(onionSkinFileNameSizer); + onionSkinOptSizer->Add(onionSkinAlphaSizer); + + wxString tmpStr2; + SetCheckFromPref(pOverlayOnionSkin, "overlay-onion-skin", false); + if (pPrefs->GetString("onion-skin-file-name", /*ref*/ tmpStr2)) + pOnionSkinFileNameText->SetValue(tmpStr2); + + /* + * Add the various components to the master sizer. + */ + masterSizer->Add(displayOptSizer); + masterSizer->AddSpacer(kInterSpacing * 2); + masterSizer->Add(runtimeOptSizer); + masterSizer->AddSpacer(kInterSpacing * 2); + masterSizer->Add(onionSkinOptSizer); + //masterSizer->AddSpacer(kInterSpacing); + + /* + * I don't see a way to guarantee that the window is wide enough to + * show the entire menu bar, so just throw some pixels at it. + */ + wxBoxSizer* minWidthSizer = new wxBoxSizer(wxVERTICAL); + minWidthSizer->Add(300, kEdgeSpacing); // forces minimum width + minWidthSizer->Add(masterSizer); + minWidthSizer->AddSpacer(kInterSpacing * 2); + + /* move us a few pixels in from the left */ + wxBoxSizer* indentSizer = new wxBoxSizer(wxHORIZONTAL); + indentSizer->AddSpacer(kEdgeSpacing); + indentSizer->Add(minWidthSizer); + indentSizer->AddSpacer(kEdgeSpacing); + + base->SetSizer(indentSizer); + + indentSizer->Fit(this); + indentSizer->SetSizeHints(this); +} + +/* + * Set the value of a checkbox based on a value from the config file. + */ +void MainFrame::SetCheckFromPref(wxCheckBox* pControl, const char* prefStr, + bool defaultVal) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + + bool val = defaultVal; + pPrefs->GetBool(prefStr, &val); + + pControl->SetValue(val); +} + +/* + * Destructor. + */ +MainFrame::~MainFrame(void) +{ + PhoneCollection::DestroyInstance(); + + delete mExternalRuntimeThread; + delete mPropertyServerThread; + + // don't touch mpModeSelection -- child of window +} + +/* + * File->Quit or click on close box. + * + * If we want an "are you sure you want to quit" box, add it here. + */ +void MainFrame::OnClose(wxCloseEvent& event) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + +/* + if (event.CanVeto()) + printf("Closing (can veto)\n"); + else + printf("Closing (mandatory)\n"); +*/ + + /* + * Generally speaking, Close() is not guaranteed to close the window. + * However, we want to use it here because (a) our windows are + * guaranteed to close, and (b) it provides our windows an opportunity + * to tell others that they are about to vanish. + */ + if (mpPhoneWindow != NULL) + mpPhoneWindow->Close(true); + + /* save position of main window */ + wxPoint pos = GetPosition(); + pPrefs->SetInt("window-main-x", pos.x); + pPrefs->SetInt("window-main-y", pos.y); + + /* save default device selection */ + int idx = GetSelectedDeviceIndex(); + if (idx >= 0) { + PhoneCollection* pCollection = PhoneCollection::GetInstance(); + PhoneData* pPhoneData = pCollection->GetPhoneData(idx); + pPrefs->SetString("default-device", pPhoneData->GetName()); + } + + if (mpLogWindow != NULL) + mpLogWindow->Close(true); + Destroy(); +} + +/* + * File->Preferences + */ +void MainFrame::OnFilePreferences(wxCommandEvent& WXUNUSED(event)) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + PrefsDialog dialog(this); + int result; + + result = dialog.ShowModal(); + if (result == wxID_OK) { + /* + * The dialog handles writing changes to Preferences, so all we + * need to deal with here are changes that have an immediate + * impact on us. (which is currently nothing) + */ + } +} + +/* + * File->Exit + */ +void MainFrame::OnFileExit(wxCommandEvent& WXUNUSED(event)) +{ + Close(FALSE); // false means "allow veto" +} + +/* + * Decide whether Simulator->Start should be enabled. + */ +void MainFrame::OnUpdateSimStart(wxUpdateUIEvent& event) +{ + if (IsRuntimeRunning()) + event.Enable(FALSE); + else + event.Enable(TRUE); +} + +/* + * Simulator->Start + */ +void MainFrame::OnSimStart(wxCommandEvent& WXUNUSED(event)) +{ + // keyboard equivalents can still get here even if menu item disabled + if (IsRuntimeRunning()) + return; + + int id = GetSelectedDeviceIndex(); + if (id < 0) { + fprintf(stderr, "Sim: could not identify currently selected device\n"); + return; + } + +#if 0 + static int foo = 0; + foo++; + if (foo == 2) { + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + + pPrefs->SetBool("debug", true); + } +#endif + + SetupPhoneUI(id, NULL); + if (mpPhoneWindow != NULL) + mpPhoneWindow->GetDeviceManager()->StartRuntime(); +} + +/* + * Decide whether Simulator->Stop should be enabled. + */ +void MainFrame::OnUpdateSimStop(wxUpdateUIEvent& event) +{ + if (IsRuntimeRunning()) + event.Enable(TRUE); + else + event.Enable(FALSE); +} + +/* + * Simulator->Stop + */ +void MainFrame::OnSimStop(wxCommandEvent& WXUNUSED(event)) +{ + if (!IsRuntimeRunning()) + return; + assert(mpPhoneWindow != NULL); + mpPhoneWindow->GetDeviceManager()->StopRuntime(); +} + +/* + * Decide whether Simulator->Restart should be enabled. + */ +void MainFrame::OnUpdateSimRestart(wxUpdateUIEvent& event) +{ + if (IsRuntimeRunning()) + event.Enable(TRUE); + else + event.Enable(FALSE); +} + +/* + * Simulator->Restart - stop then start the device runtime. + */ +void MainFrame::OnSimRestart(wxCommandEvent& WXUNUSED(event)) +{ + if (!IsRuntimeRunning()) + return; + + printf("Restart requested\n"); + mpPhoneWindow->GetDeviceManager()->StopRuntime(); + + mRestartRequested = true; +} + +/* + * Decide whether Simulator->Kill should be enabled. + */ +void MainFrame::OnUpdateSimKill(wxUpdateUIEvent& event) +{ + if (IsRuntimeKillable()) + event.Enable(TRUE); + else + event.Enable(FALSE); +} + +/* + * Simulator->Kill + */ +void MainFrame::OnSimKill(wxCommandEvent& WXUNUSED(event)) +{ + if (!IsRuntimeKillable()) + return; + assert(mpPhoneWindow != NULL); + mpPhoneWindow->GetDeviceManager()->KillRuntime(); +} + + +/* + * Device->[select] + */ +void MainFrame::OnDeviceSelected(wxCommandEvent& event) +{ + wxBusyCursor busyc; + int id = event.GetId() - IDM_DEVICE_SEL0; + + SetupPhoneUI(id, NULL); +} + +/* + * Device->Rescan + */ +void MainFrame::OnDeviceRescan(wxCommandEvent& event) +{ + wxBusyCursor busyc; + wxMenuBar* pMenuBar; + PhoneCollection* pCollection; + wxMenu* pOldMenu; + wxMenu* pNewMenu; + const char* curDevName = NULL; + int idx; + + /* figure out the current device name */ + pCollection = PhoneCollection::GetInstance(); + idx = GetSelectedDeviceIndex(); + if (idx >= 0) { + PhoneData* pPhoneData; + + pPhoneData = pCollection->GetPhoneData(idx); + curDevName = pPhoneData->GetName(); + printf("--- device name is '%s'\n", (const char*) curDevName); + } + + /* reconstruct device menu with new data */ +#ifdef BEFORE_ASSET + pCollection->ScanForPhones(mSimAssetPath); +#else + pCollection->ScanForPhones(NULL); +#endif + + pMenuBar = GetMenuBar(); + idx = pMenuBar->FindMenu(kDeviceMenuString); + if (idx == wxNOT_FOUND) { + fprintf(stderr, "Sim: couldn't find %s menu\n", (const char*) kDeviceMenuString.ToAscii()); + return; + } + + pNewMenu = CreateDeviceMenu(curDevName); + + pOldMenu = pMenuBar->Replace(idx, pNewMenu, kDeviceMenuString); + delete pOldMenu; + + /* tell the PhoneWindow about it; may cause runtime to exit */ + if (mpPhoneWindow != NULL) + mpPhoneWindow->DevicesRescanned(); +} + +/* + * Set checkbox on menu item. + */ +void MainFrame::OnUpdateDebugShowLog(wxUpdateUIEvent& event) +{ + if (mpLogWindow == NULL) { + event.Enable(false); + } else { + event.Enable(true); + event.Check(mpLogWindow->IsShown()); + } +} + +/* + * Debug->ShowLog toggle. + */ +void MainFrame::OnDebugShowLog(wxCommandEvent& WXUNUSED(event)) +{ + mpLogWindow->Show(!mpLogWindow->IsShown()); +} + +/* + * Help->Contents + */ +void MainFrame::OnHelpContents(wxCommandEvent& WXUNUSED(event)) +{ + ((MyApp*)wxTheApp)->GetHelpController()->DisplayContents(); +} + +/* + * Help->About + */ +void MainFrame::OnHelpAbout(wxCommandEvent& WXUNUSED(event)) +{ + wxMessageBox(wxT("Android Simulator v0.1\n" + "Copyright 2006 The Android Open Source Project"), + wxT("About..."), wxOK | wxICON_INFORMATION, this); +} + +/* + * Sent from phonewindow or when activated + */ +void MainFrame::OnActivate(wxActivateEvent& event) +{ +#if 0 + if (event.GetActive()) + { + if (mpPhoneWindow != NULL && + mpPhoneWindow->GetDeviceManager()->RefreshRuntime()) + { + wxString msg; + int sel; + + msg = wxT("Newer runtime executable found. Would you like to reload the device?"); + + sel = wxMessageBox(msg, wxT("Android Safety Patrol"), + wxYES | wxNO | wxICON_QUESTION, mpPhoneWindow); + //printf("BUTTON was %d (yes=%d)\n", sel, wxYES); + if (sel == wxYES) + { + mpPhoneWindow->GetDeviceManager()->StopRuntime(); + mpPhoneWindow->Close(); + mpPhoneWindow = NULL; + mRestartRequested = true; + } + else + { + mpPhoneWindow->GetDeviceManager()->UserCancelledRefresh(); + } + } + } +#endif + + // let wxWidgets do whatever it needs to do + event.Skip(); +} + + +/* + * Device mode selection box. + */ +void MainFrame::OnComboBox(wxCommandEvent& event) +{ + const char* pref; + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + + if (IDC_MODE_SELECT == event.GetId()) + { + int id = GetSelectedDeviceIndex(); + if (id < 0) + return; + //printf("--- mode selected: '%s'\n", (const char*) event.GetString().ToAscii()); + + /* + * Call the phone window's setup function. Don't call our SetupPhoneUI + * function from here -- updating the combo box from a combo box callback + * could cause problems. + */ + if (mpPhoneWindow != NULL) { + mpPhoneWindow->SetCurrentMode(event.GetString()); + mpPhoneWindow->Setup(id); + } + } else if (event.GetId() == IDC_JAVA_VM) { + wxComboBox* pBox = (wxComboBox*) FindWindow(IDC_JAVA_VM); + pPrefs->SetString("java-vm", pBox->GetValue().ToAscii()); + } +} + +/* + * One of our option checkboxes has been changed. + * + * We update the prefs database so that the settings are retained when + * the simulator is next used. + */ +void MainFrame::OnCheckBox(wxCommandEvent& event) +{ + const char* pref; + + switch (event.GetId()) { + case IDC_USE_GDB: pref = "debug"; break; + case IDC_USE_VALGRIND: pref = "valgrind"; break; + case IDC_CHECK_JNI: pref = "check-jni"; break; + case IDC_OVERLAY_ONION_SKIN: pref = "overlay-onion-skin"; break; + default: + printf("Sim: unrecognized checkbox %d in OnCheckBox\n", event.GetId()); + return; + } + + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + + pPrefs->SetBool(pref, (bool) event.GetInt()); + //printf("--- set pref '%s' to %d\n", pref, (bool) event.GetInt()); + if (event.GetId() == IDC_OVERLAY_ONION_SKIN) { + BroadcastOnionSkinUpdate(); + } + if (event.GetId() == IDC_CHECK_JNI) { + const char* val = "0"; + if ((bool) event.GetInt()) + val = "1"; + mPropertyServerThread->SetProperty(PropertyServer::kPropCheckJni, val); + + } +} + +void MainFrame::BroadcastOnionSkinUpdate() { + if (mpPhoneWindow != NULL) { + // broadcast a user event indicating an onion skin update + UserEvent uev(0, (void*) -1); + mpPhoneWindow->GetDeviceManager()->BroadcastEvent(uev); + } +} + +/* + * A text control on the main page is being updated. + * + * The current implementation updates the preferences database on every + * change, which is a bit silly but is easy to do. + */ +void MainFrame::OnText(wxCommandEvent& event) +{ + const char* pref; + + switch (event.GetId()) { + case IDC_JAVA_APP_NAME: pref = "java-app-name"; break; + default: + printf("Sim: unrecognized textctrl %d in OnText\n", event.GetId()); + return; + } + + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + + // event.GetString() does not work on Mac -- always blank + //pPrefs->SetString(pref, event.GetString()); + assert(event.GetId() == IDC_JAVA_APP_NAME); // fix if we add more + wxComboBox* pBox; + pBox = (wxComboBox*) FindWindow(IDC_JAVA_APP_NAME); + pPrefs->SetString(pref, pBox->GetValue().ToAscii()); + //printf("--- set pref '%s' to '%s'\n", pref,(const char*)pBox->GetValue()); +} + +/* + * A user pressed enter in a text control on the main page. + * + * The current implementation updates the preferences database on every + * change, which is a bit silly but is easy to do. + */ +void MainFrame::OnTextEnter(wxCommandEvent& event) +{ + const char* pref; + + switch (event.GetId()) { + case IDC_ONION_SKIN_FILE_NAME: + pref = "onion-skin-file-name"; + break; + default: + printf("Sim: unrecognized textctrl %d in OnTextEnter\n", event.GetId()); + return; + } + + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + + assert(event.GetId() == IDC_ONION_SKIN_FILE_NAME); // fix if we add more + wxTextCtrl* pTextCtrl; + pTextCtrl = (wxTextCtrl*) FindWindow(IDC_ONION_SKIN_FILE_NAME); + wxString onionSkinFileNameWxString = pTextCtrl->GetValue(); + char* onionSkinFileName = ""; + if (onionSkinFileNameWxString.Len() > 0) { + onionSkinFileName = android::strdupNew(onionSkinFileNameWxString.ToAscii()); + } + pPrefs->SetString(pref, onionSkinFileName); + BroadcastOnionSkinUpdate(); +} + +/* + * A user pressed a button on the main page + * + */ + void MainFrame::OnButton(wxCommandEvent& event) + { + wxWindow* base; + wxFileDialog* pOnionSkinFileChooser; + int retVal; + switch (event.GetId()) { + case IDC_ONION_SKIN_BUTTON: + base = FindWindow(IDC_ONION_SKIN_BUTTON)->GetParent(); + pOnionSkinFileChooser = new wxFileDialog(base, + wxT("Choose the onion skin image file."), + wxT(""), wxT(""), wxT("*.*"), + wxOPEN | wxFILE_MUST_EXIST); + retVal = pOnionSkinFileChooser->ShowModal(); + if (retVal == pOnionSkinFileChooser->GetAffirmativeId()) { + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + wxString fileNameWxString = pOnionSkinFileChooser->GetPath(); + const char* fileName = android::strdupNew(fileNameWxString.ToAscii()); + wxTextCtrl* fileTextCtrl = (wxTextCtrl*) FindWindow(IDC_ONION_SKIN_FILE_NAME); + fileTextCtrl->SetValue(fileNameWxString); + pPrefs->SetString("onion-skin-file-name", fileName); + BroadcastOnionSkinUpdate(); + } + break; + default: + printf("Sim: unrecognized button %d in OnButton\n", event.GetId()); + return; + } + } + + /* + * The user moved a slider on the main page + */ + void MainFrame::OnSliderChange(wxScrollEvent& event) + { + wxSlider* pSlider; + Preferences* pPrefs; + switch (event.GetId()) { + case IDC_ONION_SKIN_ALPHA_VAL: + pSlider = (wxSlider*) FindWindow(IDC_ONION_SKIN_ALPHA_VAL); + pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + pPrefs->SetInt("onion-skin-alpha-value", pSlider->GetValue()); + BroadcastOnionSkinUpdate(); + break; + default: + printf("Sim: unrecognized scroller or slider %d in OnSliderChange\n", event.GetId()); + return; + } + } + +#if 0 +/* + * Idle processing. Under wxWidgets this only called once after UI + * activity unless you call event.RequestMore(). + */ +void MainFrame::OnIdle(wxIdleEvent& event) +{ + event.Skip(); // let base class handler do stuff +} +#endif + +/* + * Handle the timer. + * + * This is being called in the main thread, so multithreading with the + * rest of MainFrame isn't a concern here. + */ +void MainFrame::OnTimer(wxTimerEvent& event) +{ + bool status; + + /* + * Check to see if the runtime died without telling us. This can only + * happen if we forcibly kill our thread. We shouldn't really be + * doing that anymore, but keep this in for now just in case. + */ + status = IsRuntimeRunning(); + + if (mSimRunning != status) { + if (!status) { + printf("Sim: fixed mSimRunning=%d actual=%d\n", + mSimRunning, status); + mSimRunning = status; + + if (!status) + HandleRuntimeStop(); + } else { + /* + * This was happening when we were shutting down but the + * device management thread hadn't completely gone away. The + * simple IsRunning test passes, so we get a false positive. + * Ignore it. + */ + } + } + + if (gWantToKill) { + if (IsRuntimeRunning()) { + printf("Sim: handling kill request\n"); + mpPhoneWindow->GetDeviceManager()->KillRuntime(); + } + gWantToKill = false; + + /* see if Ctrl-C should kill us too */ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + bool die = false; + + pPrefs->GetBool("trap-sigint-suicide", &die); + if (die) { + printf("Sim: goodbye cruel world!\n"); + exit(0); + } + } +} + +/* + * Determine whether or not the simulator is running. + */ +bool MainFrame::IsRuntimeRunning(void) +{ + bool result; + + if (mpPhoneWindow == NULL) + result = false; + else if (!mpPhoneWindow->IsReady()) + result = false; + else + result = mpPhoneWindow->GetDeviceManager()->IsRunning(); + + return result; +} + +/* + * Determine whether or not the runtime can be killed. + */ +bool MainFrame::IsRuntimeKillable(void) +{ + bool result; + + result = IsRuntimeRunning(); + if (result) + result = mpPhoneWindow->GetDeviceManager()->IsKillable(); + + return result; +} + +/* + * Determine whether two devices are sufficiently compatible. + */ +bool MainFrame::CompatibleDevices(PhoneData* pData1, PhoneData* pData2) +{ + int displayCount; + + displayCount = pData1->GetNumDisplays(); + if (pData2->GetNumDisplays() != displayCount) + return false; + + for (int i = 0; i < displayCount; i++) { + PhoneDisplay* pDisplay1 = pData1->GetPhoneDisplay(i); + PhoneDisplay* pDisplay2 = pData2->GetPhoneDisplay(i); + + if (!PhoneDisplay::IsCompatible(pDisplay1, pDisplay2)) + return false; + } + + return true; +} + +/* + * (Re-)arrange the UI for the currently selected phone model. + * + * If the simulator is running, and the set of displays for the current + * device are incompatible with the new device, we need to restart the + * runtime. We need to ask for permission first though. + */ +void MainFrame::SetupPhoneUI(int idx, const char* defaultMode) +{ + PhoneCollection* pCollection; + PhoneData* pPhoneData; + wxString* choices = NULL; + int numChoices = 0; + int numKeyboards = 0; + bool haveDefaultMode = false; + wxCharBuffer currentMode; + int i; + + pCollection = PhoneCollection::GetInstance(); + pPhoneData = pCollection->GetPhoneData(idx); + if (pPhoneData == NULL) { + fprintf(stderr, "ERROR: device index %d not valid\n", idx); + goto bail; + } + + /* + * We have a window up. If the displays aren't compatible, we'll + * need to recreate it. + */ + if (mpPhoneWindow != NULL) { + PhoneData* pCurData = mpPhoneWindow->GetPhoneData(); + + if (!CompatibleDevices(pCurData, pPhoneData)) { + /* + * We need to trash the window. This will also kill the + * runtime. If it's running, ask permission. + */ + if (IsRuntimeRunning()) { + wxString msg; + int sel; + + msg = wxT("Switching to the new device requires restarting the"); + msg += wxT(" runtime. Continue?"); + + sel = wxMessageBox(msg, wxT("Android Safety Patrol"), + wxOK | wxCANCEL | wxICON_QUESTION, this); + printf("BUTTON was %d (ok=%d)\n", sel, wxOK); + if (sel == wxCANCEL) + goto bail; + + /* shut it down (politely), ask for an eventual restart */ + mpPhoneWindow->GetDeviceManager()->StopRuntime(); + mpPhoneWindow->Close(); + mpPhoneWindow = NULL; + mRestartRequested = true; + goto bail; + } else { + /* not running, just trash the window and continue */ + mpPhoneWindow->Close(); + mpPhoneWindow = NULL; + } + } + } + + /* + * Figure out the set of available modes. + */ + + numChoices = pPhoneData->GetNumModes(); + if (numChoices > 0) { + choices = new wxString[numChoices]; + for (i = 0; i < numChoices; i++) { + PhoneMode* pPhoneMode; + pPhoneMode = pPhoneData->GetPhoneMode(i); + choices[i] = wxString::FromAscii(pPhoneMode->GetName()); + if (defaultMode != NULL && + strcmp(defaultMode, pPhoneMode->GetName()) == 0) + { + haveDefaultMode = true; + } + } + } + + if (choices == NULL) { + /* had a failure earlier; configure UI with default stuff */ + choices = new wxString[1]; + choices[0] = wxT("(none)"); + } + + if (!haveDefaultMode) { + /* + * Default mode wasn't found. If we specify it as the default + * in the wxComboBox create call it shows up in the combo box + * under Linux, even if it doesn't exist in the list. So, we + * make sure that it doesn't get used if we can't find it. + */ + if (defaultMode != NULL) { + printf("Sim: HEY: default mode '%s' not found in list\n", + defaultMode); + } + currentMode = choices[0].ToAscii(); + } else { + currentMode = defaultMode; + } + + + /* + * Create the window if necessary. + */ + if (mpPhoneWindow == NULL) { + // create, setup, and then show window + mpPhoneWindow = new PhoneWindow(this, mPhoneWindowPosn); + mpPhoneWindow->SetCurrentMode((const char*)currentMode); + if (!mpPhoneWindow->Setup(idx)) { + delete mpPhoneWindow; + mpPhoneWindow = NULL; + } + if (mpPhoneWindow != NULL) { + mpPhoneWindow->Show(); + //mpPhoneWindow->CheckPlacement(); + } + } else { + // just set up for new device + mpPhoneWindow->SetCurrentMode((const char*)currentMode); + if (!mpPhoneWindow->Setup(idx)) { + // it's in an uncertain state, blow it away + delete mpPhoneWindow; + mpPhoneWindow = NULL; + } + } + + /* + * Reconfigure mode selection box. + */ + wxComboBox* pModeSelection; + pModeSelection = (wxComboBox*)FindWindow(IDC_MODE_SELECT); + pModeSelection->Clear(); + for (i = 0; i < numChoices; i++) + pModeSelection->Append(choices[i]); + pModeSelection->SetSelection(0); + pModeSelection->Enable(numChoices > 1); + + /* + * configure qwerty keyboard attribute + */ + numKeyboards = pPhoneData->GetNumKeyboards(); + if (numKeyboards > 0) { + // only use the first keyboard for now + PhoneKeyboard* pPhoneKeyboard; + pPhoneKeyboard = pPhoneData->GetPhoneKeyboard(0); + if (pPhoneKeyboard->getQwerty()) { + printf("Sim: set 'qwerty' env\n"); + setenv("qwerty", "true", true); + } + } + +bail: + delete[] choices; +} + +/* + * Figure out which device is currently selected. + * + * The easiest way to do this is just run down the list of possible IDs + * and stop when something claims to be checked. + * + * Returns -1 if it can't find a checked item (which can happen if no + * device layouts were found). + */ +int MainFrame::GetSelectedDeviceIndex(void) +{ + wxMenuBar* pMenuBar; + wxMenu* pMenu; + int idx; + + pMenuBar = GetMenuBar(); + idx = pMenuBar->FindMenu(kDeviceMenuString); + if (idx == wxNOT_FOUND) { + fprintf(stderr, "Sim: couldn't find %s menu\n", (const char*) kDeviceMenuString.ToAscii()); + return -1; + } + + pMenu = pMenuBar->GetMenu(idx); + + //printf("Menu.MenuItemCount = %d\n", pMenu->GetMenuItemCount()); + for (int j = pMenu->GetMenuItemCount() -1; j >= 0; j--) { + wxMenuItem* pItem; + + pItem = pMenu->FindItemByPosition(j); + //printf("ITEM %d: %s\n", j, (const char*) pItem->GetLabel()); + if (pItem->IsChecked()) { + printf("Sim: selected device is '%s'\n", + (const char*) pItem->GetLabel().ToAscii()); + return j; + } + } + + return -1; +} + +/* + * Receive a status message from the runtime thread. + */ +void MainFrame::OnUserEvent(UserEvent& event) +{ + UserEventMessage* pUem; + + pUem = (UserEventMessage*) event.GetData(); + assert(pUem != NULL); + + switch (pUem->GetType()) { + case UserEventMessage::kRuntimeStarted: + printf("Sim: runtime thread started!\n"); + HandleRuntimeStart(); + break; + case UserEventMessage::kRuntimeStopped: + printf("Sim: runtime thread stopped!\n"); + HandleRuntimeStop(); + break; + case UserEventMessage::kErrorMessage: + { + wxString msg = pUem->GetString(); + wxMessageBox(msg, wxT("Android Runtime Error"), + wxOK | wxICON_WARNING, this); + } + break; + case UserEventMessage::kLogMessage: + mpLogWindow->AddLogMessage(pUem->GetLogMessage()); + break; + case UserEventMessage::kExternalRuntime: + HandleExternalRuntime(pUem->GetReader(), pUem->GetWriter()); + break; + default: + printf("Sim: MESSAGE: unknown UserEventMessage rcvd (type=%d)\n", + pUem->GetType()); + break; + } + + delete pUem; +} + +/* + * The device management thread is up, so the runtime should be fully + * running shortly. + */ +void MainFrame::HandleRuntimeStart(void) +{ + mSimRunning = true; + + SetStatusText(kStatusRunning, 1); +} + +/* + * The device management thread is exiting, so the runtime must be dead. + */ +void MainFrame::HandleRuntimeStop(void) +{ + mSimRunning = false; + + SetStatusText(kStatusNotRunning, 1); + + if (mRestartRequested) { + printf("Sim: restarting runtime\n"); + mRestartRequested = false; + SetupPhoneUI(GetSelectedDeviceIndex(), NULL); + if (mpPhoneWindow != NULL) + mpPhoneWindow->GetDeviceManager()->StartRuntime(); + } +} + +/* + * Handle a connection from an external runtime. + */ +void MainFrame::HandleExternalRuntime(android::Pipe* reader, + android::Pipe* writer) +{ + android::MessageStream msgStream; + android::Message msg; + + if (IsRuntimeRunning()) { + /* + * Tell the new guy to go away. + */ + if (!msgStream.init(reader, writer, true)) { + fprintf(stderr, "Sim: WARNING: unable to talk to remote runtime\n"); + goto bail; + } + + printf("Sim: telling external runtime to go away\n"); + msg.setCommand(android::Simulator::kCommandGoAway, 0); + msgStream.send(&msg); + } else { + printf("Sim: new external runtime wants to talk to us\n"); + + /* + * Launch the pieces necessary to talk to this guy. + */ + int id = GetSelectedDeviceIndex(); + if (id < 0) { + fprintf(stderr, + "Sim: could not identify currently selected device\n"); + goto bail; + } + + /* kill existing window, so it pops up and reclaims focus */ + if (mpPhoneWindow != NULL) { + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + bool okay; + + if (pPrefs->GetBool("refocus-on-restart", &okay) && okay) { + printf("Sim: inducing phone window refocus\n"); + mpPhoneWindow->Close(TRUE); // no veto + mpPhoneWindow = NULL; + } + } + + SetupPhoneUI(id, NULL); + if (mpPhoneWindow != NULL) { + mpPhoneWindow->GetDeviceManager()->StartRuntime(reader, writer); + } else { + fprintf(stderr, "Sim: ERROR: unable to get runtime going\n"); + goto bail; + } + + // we don't own these anymore + reader = writer = NULL; + } + +bail: + delete reader; + delete writer; +} + +/* + * The phone window is about to destroy itself. Get rid of our pointer + * to it, and record its last position so we can create the new one in + * the same place. + */ +void MainFrame::PhoneWindowClosing(int x, int y) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + + mpPhoneWindow = NULL; + + mPhoneWindowPosn.x = x; + mPhoneWindowPosn.y = y; + + pPrefs->SetInt("window-device-x", x); + pPrefs->SetInt("window-device-y", y); +} + diff --git a/simulator/app/MainFrame.h b/simulator/app/MainFrame.h new file mode 100644 index 000000000..817a1820c --- /dev/null +++ b/simulator/app/MainFrame.h @@ -0,0 +1,115 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Main window declaration. +// +#ifndef _SIM_MAINFRAME_H +#define _SIM_MAINFRAME_H + +#include "PhoneWindow.h" +#include "DeviceWindow.h" +#include "LogWindow.h" +#include "ExternalRuntime.h" +#include "PropertyServer.h" + +/* + * Main window. + */ +class MainFrame : public wxFrame { +public: + /* define a constructor so we can set up menus */ + MainFrame(const wxString& title, const wxPoint& pos, const wxSize& size, + long style); + virtual ~MainFrame(void); + + /* called by modeless phone window dialog when it closes */ + void PhoneWindowClosing(int x, int y); + + void Vibrate(int vibrateOn) { mpPhoneWindow->Vibrate(vibrateOn); } + + PropertyServer* GetPropertyServer(void) { return mPropertyServerThread; } + +private: + void ConstructMenu(void); + void ConstructControls(void); + + void OnClose(wxCloseEvent& event); + void OnTimer(wxTimerEvent& event); + //void OnIdle(wxIdleEvent& event); + void OnActivate(wxActivateEvent& event); + void OnButton(wxCommandEvent& event); + void OnComboBox(wxCommandEvent& event); + void OnCheckBox(wxCommandEvent& event); + void OnText(wxCommandEvent& event); + void OnTextEnter(wxCommandEvent& event); + void OnUserEvent(UserEvent& event); + void OnSliderChange(wxScrollEvent& event); + + void OnFilePreferences(wxCommandEvent& event); + void OnFileExit(wxCommandEvent& event); + void OnUpdateSimStart(wxUpdateUIEvent& event); + void OnSimStart(wxCommandEvent& event); + void OnUpdateSimStop(wxUpdateUIEvent& event); + void OnSimStop(wxCommandEvent& event); + void OnUpdateSimReload(wxUpdateUIEvent& event); + void OnSimReload(wxCommandEvent& event); + void OnUpdateSimRestart(wxUpdateUIEvent& event); + void OnSimRestart(wxCommandEvent& event); + void OnUpdateSimKill(wxUpdateUIEvent& event); + void OnSimKill(wxCommandEvent& event); + void OnDeviceSelected(wxCommandEvent& event); + void OnDeviceRescan(wxCommandEvent& event); + void OnUpdateDebugShowLog(wxUpdateUIEvent& event); + void OnDebugShowLog(wxCommandEvent& event); + void OnHelpContents(wxCommandEvent& event); + void OnHelpAbout(wxCommandEvent& event); + + wxMenu* CreateDeviceMenu(const char* defaultItemName); + void SetCheckFromPref(wxCheckBox* pControl, const char* prefStr, + bool defaultVal); + + void UpdateRuntimeExeStr(void); + + /* prep the phone UI; "defaultMode" may be NULL */ + void SetupPhoneUI(int idx, const char* defaultMode); + + bool CompatibleDevices(PhoneData* pData1, PhoneData* pData2); + + void HandleRuntimeStart(void); + void HandleRuntimeStop(void); + void HandleExternalRuntime(android::Pipe* reader, android::Pipe* writer); + + int GetSelectedDeviceIndex(void); + bool IsRuntimeRunning(void); + bool IsRuntimeKillable(void); + + void BroadcastOnionSkinUpdate(void); + + bool mSimRunning; + bool mRestartRequested; + + enum { kHalfSecondTimerId = 1000 }; + + wxString mSimAssetPath; + + /* if we have a phone running, this points to its state */ + PhoneWindow* mpPhoneWindow; + + /* phone window position */ + wxPoint mPhoneWindowPosn; + + /* window that captures log output */ + LogWindow* mpLogWindow; + + wxTimer mTimer; + + /* watches for connection from runtime */ + ExternalRuntime* mExternalRuntimeThread; + + /* serve up system properties */ + PropertyServer* mPropertyServerThread; + + DECLARE_EVENT_TABLE() +}; + +#endif // _SIM_MAINFRAME_H diff --git a/simulator/app/MessageStream.cpp b/simulator/app/MessageStream.cpp new file mode 100644 index 000000000..2397c63be --- /dev/null +++ b/simulator/app/MessageStream.cpp @@ -0,0 +1,402 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Message stream abstraction. +// +#include "MessageStream.h" +#include "LogBundle.h" + +#include "utils/Log.h" + +#include +#include + +using namespace android; + +/* + * =========================================================================== + * Message + * =========================================================================== + */ + +/* + * Send a blob of raw data. + */ +void Message::setRaw(const unsigned char* data, int len, Cleanup cleanup) +{ + reset(); + + mData = const_cast(data); + mLength = len; + mCleanup = cleanup; + mType = kTypeRaw; +} + +/* + * Send a "name=value" config pair. + */ +void Message::setConfig(const char* name, const char* value) +{ + reset(); + + assert(name != NULL && value != NULL); + + int nlen = strlen(name) +1; + int vlen = strlen(value) +1; + mData = new unsigned char[nlen+vlen]; + mCleanup = kCleanupDelete; + mLength = nlen + vlen; + mType = kTypeConfig; + + memcpy(mData, name, nlen); + memcpy(mData + nlen, value, vlen); +} + +/* + * Try to return the contents of the message as if it were a name/value pair. + */ +bool Message::getConfig(const char** pName, const char** pValue) +{ + if (mLength < 2) + return false; + assert(mData != NULL); + + *pName = (const char*) mData; + *pValue = (const char*) (mData + strlen((char*)mData) +1); + return true; +} + +/* + * Send a command/arg pair. + */ +void Message::setCommand(int cmd, int arg) +{ + reset(); + + mData = new unsigned char[sizeof(int) * 2]; + mCleanup = kCleanupDelete; + mLength = sizeof(int) * 2; + mType = kTypeCommand; + + int* pInt = (int*) mData; + pInt[0] = cmd; + pInt[1] = arg; +} + +/* + * Send a command with 3 args instead of just one. + */ +void Message::setCommandExt(int cmd, int arg0, int arg1, int arg2) +{ + reset(); + + mData = new unsigned char[sizeof(int) * 4]; + mCleanup = kCleanupDelete; + mLength = sizeof(int) * 4; + mType = kTypeCommandExt; + + int* pInt = (int*) mData; + pInt[0] = cmd; + pInt[1] = arg0; + pInt[2] = arg1; + pInt[3] = arg2; +} + +/* + * Try to return the contents of the message as if it were a "command". + */ +bool Message::getCommand(int* pCmd, int* pArg) +{ + if (mLength != sizeof(int) * 2) { + LOG(LOG_WARN, "", "type is %d, len is %d\n", mType, mLength); + return false; + } + assert(mData != NULL); + + const int* pInt = (const int*) mData; + *pCmd = pInt[0]; + *pArg = pInt[1]; + + return true; +} + +/* + * Serialize a log message. + * + * DO NOT call LOG() from here. + */ +void Message::setLogBundle(const android_LogBundle* pBundle) +{ + reset(); + + /* get string lengths; we add one here to include the '\0' */ + int tagLen, msgLen; + tagLen = strlen(pBundle->tag) + 1; + size_t i; + msgLen = 0; + for (i=0; imsgCount; i++) msgLen += pBundle->msgVec[i].iov_len; + msgLen += 1; + + /* set up the structure */ + mCleanup = kCleanupDelete; + mLength = sizeof(pBundle->when) + + sizeof(pBundle->priority) + + sizeof(pBundle->pid) + + tagLen + + msgLen; + mData = new unsigned char[mLength]; + mType = kTypeLogBundle; + + unsigned char* pCur = mData; + + /* copy the stuff over */ + *((time_t*)pCur) = pBundle->when; + pCur += sizeof(pBundle->when); + *((android_LogPriority*)pCur) = pBundle->priority; + pCur += sizeof(pBundle->priority); + *((pid_t*)pCur) = pBundle->pid; + pCur += sizeof(pBundle->pid); + memcpy(pCur, pBundle->tag, tagLen); + pCur += tagLen; + for (i=0; imsgCount; i++) { + memcpy(pCur, pBundle->msgVec[i].iov_base, pBundle->msgVec[i].iov_len); + pCur += pBundle->msgVec[i].iov_len; + } + *pCur++ = 0; + + assert(pCur - mData == mLength); +} + +/* + * Extract the components of a log bundle. + * + * We're just returning points inside the message buffer, so the caller + * will need to copy them out before the next reset(). + */ +bool Message::getLogBundle(android_LogBundle* pBundle) +{ + if (mLength < (int)(sizeof(time_t) + sizeof(int)*2 + 4)) { + LOG(LOG_WARN, "", "type is %d, len is %d, too small\n", + mType, mLength); + return false; + } + assert(mData != NULL); + + unsigned char* pCur = mData; + + pBundle->when = *((time_t*) pCur); + pCur += sizeof(pBundle->when); + pBundle->priority = *((android_LogPriority*) pCur); + pCur += sizeof(pBundle->priority); + pBundle->pid = *((pid_t*) pCur); + pCur += sizeof(pBundle->pid); + pBundle->tag = (const char*) pCur; + pCur += strlen((const char*) pCur) +1; + mVec.iov_base = (char*) pCur; + mVec.iov_len = strlen((const char*) pCur); + pBundle->msgVec = &mVec; + pBundle->msgCount = 1; + pCur += mVec.iov_len +1; + + if (pCur - mData != mLength) { + LOG(LOG_WARN, "", "log bundle rcvd %d, used %d\n", mLength, + (int) (pCur - mData)); + return false; + } + + return true; +} + +/* + * Read the next event from the pipe. + * + * This is not expected to work well when multiple threads are reading. + */ +bool Message::read(Pipe* pPipe, bool wait) +{ + if (pPipe == NULL) + return false; + assert(pPipe->isCreated()); + + if (!wait) { + if (!pPipe->readReady()) + return false; + } + + reset(); + + unsigned char header[4]; + if (pPipe->read(header, 4) != 4) + return false; + + mType = (MessageType) header[2]; + mLength = header[0] | header[1] << 8; + mLength -= 2; // we already read two of them in the header + + if (mLength > 0) { + int actual; + + mData = new unsigned char[mLength]; + if (mData == NULL) { + LOG(LOG_ERROR, "", "alloc failed\n"); + return false; + } + mCleanup = kCleanupDelete; + + actual = pPipe->read(mData, mLength); + if (actual != mLength) { + LOG(LOG_WARN, "", "failed reading message body (%d of %d bytes)\n", + actual, mLength); + return false; + } + } + + return true; +} + +/* + * Write this event to a pipe. + * + * It would be easiest to write the header and message body with two + * separate calls, but that will occasionally fail on multithreaded + * systems when the writes are interleaved. We have to allocate a + * temporary buffer, copy the data, and write it all at once. This + * would be easier with writev(), but we can't rely on having that. + * + * DO NOT call LOG() from here, as we could be in the process of sending + * a log message. + */ +bool Message::write(Pipe* pPipe) const +{ + char tmpBuf[128]; + char* writeBuf = tmpBuf; + bool result = false; + int kHeaderLen = 4; + + if (pPipe == NULL) + return false; + assert(pPipe->isCreated()); + + if (mData == NULL || mLength < 0) + return false; + + /* if it doesn't fit in stack buffer, allocate space */ + if (mLength + kHeaderLen > (int) sizeof(tmpBuf)) { + writeBuf = new char[mLength + kHeaderLen]; + if (writeBuf == NULL) + goto bail; + } + + /* + * The current value of "mLength" does not include the 4-byte header. + * Two of the 4 header bytes are included in the length we output + * (the type byte and the pad byte), so we adjust mLength. + */ + writeBuf[0] = (unsigned char) (mLength + kHeaderLen -2); + writeBuf[1] = (unsigned char) ((mLength + kHeaderLen -2) >> 8); + writeBuf[2] = (unsigned char) mType; + writeBuf[3] = 0; + if (mLength > 0) + memcpy(writeBuf + kHeaderLen, mData, mLength); + + int actual; + + actual = pPipe->write(writeBuf, mLength + kHeaderLen); + if (actual != mLength + kHeaderLen) { + fprintf(stderr, + "Message::write failed writing message body (%d of %d bytes)\n", + actual, mLength + kHeaderLen); + goto bail; + } + + result = true; + +bail: + if (writeBuf != tmpBuf) + delete[] writeBuf; + return result; +} + + +/* + * =========================================================================== + * MessageStream + * =========================================================================== + */ + +/* + * Get ready to go. + */ +bool MessageStream::init(Pipe* readPipe, Pipe* writePipe, bool initiateHello) +{ + assert(mReadPipe == NULL && mWritePipe == NULL); // only once + + /* + * Swap "hello" messages. + * + * In a more robust implementation, this would include version numbers + * and capability flags. + */ + if (initiateHello) { + long data = kHelloMsg; + Message msg; + + /* send hello */ + msg.setRaw((unsigned char*) &data, sizeof(data), + Message::kCleanupNoDelete); + if (!msg.write(writePipe)) { + LOG(LOG_WARN, "", "hello write failed in stream init\n"); + return false; + } + + LOG(LOG_DEBUG, "", "waiting for peer to ack my hello\n"); + + /* wait for the ack */ + if (!msg.read(readPipe, true)) { + LOG(LOG_WARN, "", "hello ack read failed in stream init\n"); + return false; + } + + const long* pAck; + pAck = (const long*) msg.getData(); + if (pAck == NULL || *pAck != kHelloAckMsg) { + LOG(LOG_WARN, "", "hello ack was bad\n"); + return false; + } + } else { + long data = kHelloAckMsg; + Message msg; + + LOG(LOG_DEBUG, "", "waiting for hello from peer\n"); + + /* wait for the hello */ + if (!msg.read(readPipe, true)) { + LOG(LOG_WARN, "", "hello read failed in stream init\n"); + return false; + } + + const long* pAck; + pAck = (const long*) msg.getData(); + if (pAck == NULL || *pAck != kHelloMsg) { + LOG(LOG_WARN, "", "hello was bad\n"); + return false; + } + + /* send hello ack */ + msg.setRaw((unsigned char*) &data, sizeof(data), + Message::kCleanupNoDelete); + if (!msg.write(writePipe)) { + LOG(LOG_WARN, "", "hello ack write failed in stream init\n"); + return false; + } + } + + /* success, set up our local stuff */ + mReadPipe = readPipe; + mWritePipe = writePipe; + + //LOG(LOG_DEBUG, "", "init success\n"); + + return true; +} + diff --git a/simulator/app/MessageStream.h b/simulator/app/MessageStream.h new file mode 100644 index 000000000..de9c39848 --- /dev/null +++ b/simulator/app/MessageStream.h @@ -0,0 +1,190 @@ +// +// Copyright 2005 The Android Open Source Project +// +// High-level message stream that sits on top of a pair of Pipes. Useful +// for inter-process communication, e.g. between "simulator" and "runtime". +// +// All messages are sent in packets: +// +00 16-bit length (of everything that follows), little-endian +// +02 8-bit message type +// +03 (reserved, must be zero) +// +04 message body +// +#ifndef _LIBS_UTILS_MESSAGE_STREAM_H +#define _LIBS_UTILS_MESSAGE_STREAM_H + +#ifdef HAVE_ANDROID_OS +#error DO NOT USE THIS FILE IN THE DEVICE BUILD +#endif + +#include +#include +#include + +// Defined in LogBundle.h. +struct android_LogBundle; + +namespace android { + +/* + * A single message, which can be filled out and sent, or filled with + * received data. + * + * Message objects are reusable. + */ +class Message { +public: + Message(void) + : mCleanup(kCleanupUnknown) + { reset(); } + ~Message(void) { reset(); } + + /* values for message type byte */ + typedef enum MessageType { + kTypeUnknown = 0, + kTypeRaw, // chunk of raw data + kTypeConfig, // send a name=value pair to peer + kTypeCommand, // simple command w/arg + kTypeCommandExt, // slightly more complicated command + kTypeLogBundle, // multi-part log message + } MessageType; + + /* what to do with data when we're done */ + typedef enum Cleanup { + kCleanupUnknown = 0, + kCleanupNoDelete, // do not delete data when object destroyed + kCleanupDelete, // delete with "delete[]" + } Cleanup; + + /* + * Stuff raw data into the object. The caller can use the "cleanup" + * parameter to decide whether or not the Message object owns the data. + */ + void setRaw(const unsigned char* data, int len, Cleanup cleanup); + + /* + * Send a "name=value" pair. + */ + void setConfig(const char* name, const char* value); + + /* + * Send a command/arg pair. + */ + void setCommand(int cmd, int arg); + void setCommandExt(int cmd, int arg0, int arg1, int arg2); + + /* + * Send a multi-part log message. + */ + void setLogBundle(const android_LogBundle* pBundle); + + /* + * Simple accessors. + */ + MessageType getType(void) const { return mType; } + const unsigned char* getData(void) const { return mData; } + int getLength(void) const { return mLength; } + + /* + * Not-so-simple accessors. These coerce the raw data into an object. + * + * The data returned by these may not outlive the Message, so make + * copies if you plan to use them long-term. + */ + bool getConfig(const char** pName, const char** pValue); + bool getCommand(int* pCmd, int* pArg); + bool getLogBundle(android_LogBundle* pBundle); + + /* + * Read or write this message on the specified pipe. + * + * If "wait" is true, read() blocks until a message arrives. Only + * one thread should be reading at a time. + */ + bool read(Pipe* pPipe, bool wait); + bool write(Pipe* pPipe) const; + +private: + Message& operator=(const Message&); // not defined + Message(const Message&); // not defined + + void reset(void) { + if (mCleanup == kCleanupDelete) + delete[] mData; + + mType = kTypeUnknown; + mCleanup = kCleanupNoDelete; + mData = NULL; + mLength = -1; + } + + MessageType mType; + Cleanup mCleanup; + unsigned char* mData; + int mLength; + struct iovec mVec; +}; + + +/* + * Abstraction of higher-level communication channel. + * + * This may be used from multiple threads simultaneously. Blocking on + * the read pipe from multiple threads will have unpredictable behavior. + * + * Does not take ownership of the pipes passed in to init(). + */ +class MessageStream { +public: + MessageStream(void) + : mReadPipe(NULL), mWritePipe(NULL) + {} + ~MessageStream(void) {} + + /* + * Initialize object and exchange greetings. "initateHello" determines + * whether we send "Hello" or block waiting for it to arrive. Usually + * the "parent" initiates. + */ + bool init(Pipe* readPipe, Pipe* writePipe, bool initiateHello); + + bool isReady(void) const { return mReadPipe != NULL && mWritePipe != NULL; } + + /* + * Send a message immediately. + */ + bool send(const Message* pMsg) { return pMsg->write(mWritePipe); } + + /* + * Receive a message. + */ + bool recv(Message* pMsg, bool wait) { return pMsg->read(mReadPipe, wait); } + + /* + * Close communication pipes. Further attempts to send or receive + * will fail. Note this doesn't actually "close" the pipes, because + * we don't own them. + */ + void close(void) { mReadPipe = mWritePipe = NULL; } + + /* + * Get our incoming traffic pipe. This is useful on Linux systems + * because it allows access to the file descriptor which can be used + * in a select() call. + */ + Pipe* getReadPipe(void) { return mReadPipe; } + +private: + enum { + kHelloMsg = 0x4e303047, // 'N00G' + kHelloAckMsg = 0x31455221, // '1ER!' + }; + + /* communication pipes; note we don't own these */ + Pipe* mReadPipe; + Pipe* mWritePipe; +}; + +}; // namespace android + +#endif // _LIBS_UTILS_MESSAGE_STREAM_H diff --git a/simulator/app/MyApp.cpp b/simulator/app/MyApp.cpp new file mode 100644 index 000000000..313e44d80 --- /dev/null +++ b/simulator/app/MyApp.cpp @@ -0,0 +1,547 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Application entry point. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +#include "wx/image.h" // needed for Windows build +#include "wx/fs_zip.h" + +#include "MainFrame.h" +#include "MyApp.h" +#include + +#include +#include +#include +#include + +#if defined(HAVE_WINDOWS_PATHS) +# include +#endif + + +/* the name of our config file */ +static wxString kConfigFileName = wxT(".android.cf"); + +#ifdef HAVE_WINDOWS_PATHS +static wxString kExeSuffix = wxT(".exe"); +#else +static wxString kExeSuffix = wxT(""); +#endif + +/* do we want to kill the runtime? */ +bool gWantToKill = false; + +/* + * Signal handler for Ctrl-C. Under Linux we seem to get hit twice, + * possibly once for each thread. + * + * Avoid using LOG here -- it's not reentrant. Actually, just avoid doing + * anything here. + * + * Cygwin will ignore the signal but doesn't seem to call the signal + * handler. MinGW just kills the process. + */ +static void SignalHandler(int sigNum) +{ + printf("Sim: received signal %d (%s)\n", sigNum, + sigNum == SIGINT ? "SIGINT" : "???"); + gWantToKill = true; +} + + +/* wxWidgets magic; creates appropriate main entry function */ +IMPLEMENT_APP(MyApp) + +/* + * Application entry point. + */ +bool MyApp::OnInit() +{ + static wxString helpFilePath = wxT("simulator/help/unnamed.htb"); + + /* + * Parse args. + */ + + SetDefaults(); + + char** cargv = (char**)malloc(argc * sizeof(char*)); + for (int i=0; iInitialize(helpFileName); + + /* + * Create the main window, which just holds some of our UI. + */ + wxPoint pos(wxDefaultPosition); + mPrefs.GetInt("window-main-x", &pos.x); + mPrefs.GetInt("window-main-y", &pos.y); + mpMainFrame = new MainFrame(wxT("Android Simulator"), pos, wxDefaultSize, + wxDEFAULT_FRAME_STYLE); + mpMainFrame->Show(TRUE); + SetTopWindow(mpMainFrame); + + return TRUE; +} + +/* + * Change our asset directory. This requires deleting the existing + * AssetManager and creating a new one. Note that any open Assets will + * still be valid. + */ +void MyApp::ChangeAssetDirectory(const wxString& dir) +{ + delete mpAssetManager; + mpAssetManager = new android::AssetManager; + android::String8 path(dir.ToAscii()); + path.appendPath("simulator.zip"); + mpAssetManager->addAssetPath(path, NULL); + // mpAssetManager->setLocale(xxx); + mpAssetManager->setVendor("google"); +} + + +/* + * App is shutting down. Save the config file. + */ +int MyApp::OnExit(void) +{ + if (mPrefs.GetDirty()) { + printf("Sim: writing config file to '%s'\n", + (const char*) mConfigFile.ToAscii()); + if (!mPrefs.Save(mConfigFile.ToAscii())) { + fprintf(stderr, "Sim: ERROR: prefs save to '%s' failed\n", + (const char*) mConfigFile.ToAscii()); + } + } + + return 0; +} + +static ssize_t +find_last_slash(const wxString& s) +{ + int slash = s.Last('/'); + if (slash < 0) { + slash = s.Last('\\'); + } + return slash; +} + + +/* + * Set some default parameters + */ +void MyApp::SetDefaults() +{ + mDebuggerOption = false; + + /* Get the path to this executable, which should + * end in something like "/host/linux-x86/bin/simulator". + * (The full path may begin with something like "out" + * or "out/debug".) + */ + char exepath[PATH_MAX]; + executablepath(exepath); + wxString out = wxString::FromAscii(exepath); + + /* Get the path to the root host directory; e.g., "out/host". + * We can do this by removing the last three slashes + * and everything after/between them ("/linux-x86/bin/simulator"). + */ + for (int i = 0; i < 3; i++) { + int slash = find_last_slash(out); + assert(slash >= 0); + out.Truncate(slash); + } + + /* Get the location of the assets directory; something like + * "out/host/common/sim-assets" + */ + mSimAssetPath = out; + mSimAssetPath.Append(wxT("/common/sim-assets")); + + /* Get the location of the simulated device filesystem. + * We can't reliably predict this based on the executable + * location, so try to get it from the environment. + */ + char *envOut = getenv("ANDROID_PRODUCT_OUT"); + if (envOut == NULL) { + fprintf(stderr, + "WARNING: $ANDROID_PRODUCT_OUT not set in environment\n"); + envOut = ""; + } + + // the root of the android stuff + mAndroidRoot = wxString::FromAscii(envOut); + mAndroidRoot.Append(wxT("/system")); + + // where runtime is + mRuntimeExe = mAndroidRoot; + mRuntimeExe.Append(wxT("/bin/runtime")); + mRuntimeExe.Append(kExeSuffix); + + printf("mAndroidRoot='%s'\n", (const char*) mAndroidRoot.ToAscii()); + printf("mSimAssetPath='%s'\n", (const char*) mSimAssetPath.ToAscii()); +} + + +/* + * Parse command-line arguments. + * + * Returns "false" if we have a parsing error. + */ +bool MyApp::ParseArgs(int argc, char** argv) +{ + int ic; + + opterr = 0; // don't complain about unrecognized options + + if (false) { + printf("MyApp args:\n"); + for (int i = 0; i < argc; i++) + printf(" %2d: '%s'\n", i, (const char*) argv[i]); + } + + while (1) { + ic = getopt(argc, argv, "tj:da:f:rx:"); + if (ic < 0) + break; + + switch (ic) { + case 'j': + mAutoRunApp = wxString::FromAscii(optarg); + break; + case 't': + mAutoRunApp = wxT("com.android.testharness.RunAll"); + break; + case 'd': + mDebuggerOption = true; + break; + case 'x': + mDebuggerScript = wxString::FromAscii(optarg); + mDebuggerOption = true; // force debug if a script is being used + break; + case 'a': // simulator asset dir + mSimAssetPath = wxString::FromAscii(optarg); + break; + case 'f': // simulator config file + mConfigFile = wxString::FromAscii(optarg); + break; + case 'r': // reset path-based options to defaults + mResetPaths = true; + break; + default: + fprintf(stderr, "WARNING: unknown sim option '%c'\n", ic); + break; + } + } + + return true; +} + + +/* + * Convert a path to absolute form, if needed. + * + * String manipulation would be more efficient than system calls, but + * less reliable. + * + * We need to use GetCurrentDirectory() under Windows because, under + * Cygwin, some wxWidgets features require "C:" paths rather than + * local-rooted paths. Probably needed for stand-alone MinGW too. + */ +void MyApp::AbsifyPath(wxString& dir) +{ + char oldDir[512], newDir[512]; + wxString newDirStr; + + // We still need to do this under Cygwin even if the path is + // already absolute. + //if (dir[0] == '/' || dir[0] == '\\') + // return; + + if (getcwd(oldDir, sizeof(oldDir)) == NULL) { + fprintf(stderr, "getcwd() failed\n"); + return; + } + + if (chdir(dir.ToAscii()) == 0) { +#if defined(HAVE_WINDOWS_PATHS) + DWORD dwRet; + dwRet = GetCurrentDirectory(sizeof(newDir), newDir); + if (dwRet == 0 || dwRet > sizeof(newDir)) + sprintf(newDir, "GET_DIR_FAILED %lu", dwRet); +#else + if (getcwd(newDir, sizeof(newDir)) == NULL) + strcpy(newDir, "GET_DIR_FAILED"); +#endif + newDirStr = wxString::FromAscii(newDir); + chdir(oldDir); + } else { + fprintf(stderr, "WARNING: unable to chdir to '%s' from '%s'\n", + (const char*) dir.ToAscii(), oldDir); + newDirStr = dir; + } + + //dir = "c:/dev/cygwin"; + //dir += newDirStr; + dir = newDirStr; +} + + +/* + * Load and process our configuration file. + */ +bool MyApp::ProcessConfigFile(void) +{ + wxString homeConfig; + bool configLoaded = false; + + if (getenv("HOME") != NULL) { + homeConfig = wxString::FromAscii(getenv("HOME")); + homeConfig += '/'; + homeConfig += kConfigFileName; + } else { + homeConfig = wxT("./"); + homeConfig += kConfigFileName; + } + + /* + * Part 1: read the config file. + */ + + if (mConfigFile.Length() > 0) { + /* + * Read from specified config file. We absolutify the path + * first so that we're guaranteed to be hitting the same file + * even if the cwd changes. + */ + if (access(mConfigFile.ToAscii(), R_OK) != 0) { + fprintf(stderr, "ERROR: unable to open '%s'\n", + (const char*) mConfigFile.ToAscii()); + return false; + } + if (!mPrefs.Load(mConfigFile.ToAscii())) { + fprintf(stderr, "Failed loading config file '%s'\n", + (const char*) mConfigFile.ToAscii()); + return false; + } else { + configLoaded = true; + } + } else { + /* + * Try ./android.cf, then $HOME/android.cf. If we find one and + * read it successfully, save the name in mConfigFile. + */ + { + wxString fileName; + + fileName = wxT("."); + AbsifyPath(fileName); + fileName += wxT("/"); + fileName += kConfigFileName; + + if (access(fileName.ToAscii(), R_OK) == 0) { + if (mPrefs.Load(fileName.ToAscii())) { + mConfigFile = fileName; + configLoaded = true; + } else { + /* damaged config files are always fatal */ + fprintf(stderr, "Failed loading config file '%s'\n", + (const char*) fileName.ToAscii()); + return false; + } + } + } + if (!configLoaded) { + if (homeConfig.Length() > 0) { + if (access(homeConfig.ToAscii(), R_OK) == 0) { + if (mPrefs.Load(homeConfig.ToAscii())) { + mConfigFile = homeConfig; + configLoaded = true; + } else { + /* damaged config files are always fatal */ + fprintf(stderr, "Failed loading config file '%s'\n", + (const char*) homeConfig.ToAscii()); + return false; + } + } + } + } + + } + + /* if we couldn't find one to load, create a new one in $HOME */ + if (!configLoaded) { + mConfigFile = homeConfig; + if (!mPrefs.Create()) { + fprintf(stderr, "prefs creation failed\n"); + return false; + } + } + + /* + * Part 2: reset some entries if requested. + * + * If you want to reset local items (like paths to binaries) without + * disrupting other options, specifying the "reset" flag will cause + * some entries to be removed, and new defaults generated below. + */ + + if (mResetPaths) { + if (mPrefs.RemovePref("debugger")) + printf(" removed pref 'debugger'\n"); + if (mPrefs.RemovePref("valgrinder")) + printf(" removed pref 'valgrinder'\n"); + } + + /* + * Find GDB. + */ + if (!mPrefs.Exists("debugger")) { + static wxString paths[] = { + wxT("/bin"), wxT("/usr/bin"), wxString() + }; + wxString gdbPath; + + FindExe(wxT("gdb"), paths, wxT("/usr/bin/gdb"), &gdbPath); + mPrefs.SetString("debugger", gdbPath.ToAscii()); + } + + + /* + * Find Valgrind. It currently only exists in Linux, and is installed + * in /usr/bin/valgrind by default on our systems. The default version + * is old and sometimes fails, so look for a newer version. + */ + if (!mPrefs.Exists("valgrinder")) { + static wxString paths[] = { + wxT("/home/fadden/local/bin"), wxT("/usr/bin"), wxString() + }; + wxString valgrindPath; + + FindExe(wxT("valgrind"), paths, wxT("/usr/bin/valgrind"), &valgrindPath); + mPrefs.SetString("valgrinder", valgrindPath.ToAscii()); + } + + /* + * Set misc options. + */ + if (!mPrefs.Exists("auto-power-on")) + mPrefs.SetBool("auto-power-on", true); + if (!mPrefs.Exists("gamma")) + mPrefs.SetDouble("gamma", 1.0); + + if (mPrefs.GetDirty()) { + printf("Sim: writing config file to '%s'\n", + (const char*) mConfigFile.ToAscii()); + if (!mPrefs.Save(mConfigFile.ToAscii())) { + fprintf(stderr, "Sim: ERROR: prefs save to '%s' failed\n", + (const char*) mConfigFile.ToAscii()); + } + } + + return true; +} + +/* + * Find an executable by searching in several places. + */ +/*static*/ void MyApp::FindExe(const wxString& exeName, const wxString paths[], + const wxString& defaultPath, wxString* pOut) +{ + wxString exePath; + wxString slashExe; + + slashExe = wxT("/"); + slashExe += exeName; + slashExe += kExeSuffix; + + while (!(*paths).IsNull()) { + wxString tmp; + + tmp = *paths; + tmp += slashExe; + if (access(tmp.ToAscii(), X_OK) == 0) { + printf("Sim: Found '%s' in '%s'\n", (const char*) exeName.ToAscii(), + (const char*) tmp.ToAscii()); + *pOut = tmp; + return; + } + + paths++; + } + + printf("Sim: Couldn't find '%s', defaulting to '%s'\n", + (const char*) exeName.ToAscii(), (const char*) defaultPath.ToAscii()); + *pOut = defaultPath; +} + diff --git a/simulator/app/MyApp.h b/simulator/app/MyApp.h new file mode 100644 index 000000000..d18368dd0 --- /dev/null +++ b/simulator/app/MyApp.h @@ -0,0 +1,93 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Application class. +// +#ifndef _SIM_APPMAIN_H +#define _SIM_APPMAIN_H + +#include "wx/help.h" +#include "wx/html/helpctrl.h" + +#include "MainFrame.h" +#include "DeviceManager.h" +#include "Preferences.h" + +#include + +/* flag set from signal handler */ +extern bool gWantToKill; + +/* + * Class representing the application. + */ +class MyApp : public wxApp { +public: + MyApp(void) + : mHelpController(NULL), mpMainFrame(NULL), mpAssetManager(NULL), + mResetPaths(false) // configurable; reset prefs with paths + {} + ~MyApp(void) + { + delete mpAssetManager; + delete mHelpController; + } + + virtual bool OnInit(void); + virtual int OnExit(void); + + wxHtmlHelpController* GetHelpController(void) const { + return mHelpController; + } + + Preferences* GetPrefs(void) { return &mPrefs; } + + /* return a pointer to the main window */ + wxWindow* GetMainFrame(void) { return mpMainFrame; } + + /* get a pointer to our Asset Manager */ + android::AssetManager* GetAssetManager(void) { return mpAssetManager; } + + /* change the asset dir; requires re-creating Asset Manager */ + void ChangeAssetDirectory(const wxString& dir); + + const wxString& GetConfigFileName(void) const { return mConfigFile; } + + wxString GetSimAssetPath() { return mSimAssetPath; } + wxString GetAndroidRoot() { return mAndroidRoot; } + wxString GetRuntimeExe() { return mRuntimeExe; } + bool GetDebuggerOption() { return mDebuggerOption; } + wxString GetDebuggerScript() { return mDebuggerScript; } + wxString GetAutoRunApp() { return mAutoRunApp; } + + void Vibrate(int vibrateOn) { ((MainFrame*)mpMainFrame)->Vibrate(vibrateOn); } + +private: + void SetDefaults(); + bool ParseArgs(int argc, char** argv); + void AbsifyPath(wxString& dir); + bool ProcessConfigFile(void); + static void FindExe(const wxString& exeName, const wxString paths[], + const wxString& defaultPath, wxString* pOut); + + wxHtmlHelpController* mHelpController; + + wxWindow* mpMainFrame; + + android::AssetManager* mpAssetManager; + + wxString mAndroidRoot; + wxString mSimAssetPath; + wxString mRuntimeExe; + + /* command-line options */ + wxString mConfigFile; + bool mResetPaths; + bool mDebuggerOption; + wxString mDebuggerScript; + wxString mAutoRunApp; + + Preferences mPrefs; +}; + +#endif // _SIM_APPMAIN_H diff --git a/simulator/app/PhoneButton.cpp b/simulator/app/PhoneButton.cpp new file mode 100644 index 000000000..eca7ddcb1 --- /dev/null +++ b/simulator/app/PhoneButton.cpp @@ -0,0 +1,180 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Simulated device data. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +#include "wx/image.h" // needed for Windows build + +#include "LinuxKeys.h" +#include "PhoneButton.h" + +using namespace android; + + +/* + * Create a PhoneButton without a backing image. + */ +bool PhoneButton::Create(const char* label) +{ + assert(!mHasImage); // quick check for re-use + + mKeyCode = LookupKeyCode(label); + if (mKeyCode == kKeyCodeUnknown) { + fprintf(stderr, "WARNING: key code '%s' not recognized\n", label); + // keep going + } + + return true; +} + +/* + * Create a PhoneButton with an associated image. Don't load the image yet. + */ +bool PhoneButton::Create(const char* label, const char* imageFileName, + int x, int y) +{ + if (!Create(label)) + return false; + + if (mSelectedImage.Create(imageFileName, x, y)) + mHasImage = true; + else + fprintf(stderr, "Warning: image create (%s, %d, %d) failed\n", + imageFileName, x, y); + + return true; +} + +/* + * Load the image, if any. + */ +bool PhoneButton::LoadResources(void) +{ + if (!mHasImage) + return true; // no image associated with this button + + bool result = mSelectedImage.LoadResources(); + if (result) + CreateHighlightedBitmap(); + return result; +} + +/* + * Unload the image if we loaded one. + */ +bool PhoneButton::UnloadResources(void) +{ + if (!mHasImage) + return true; + + return mSelectedImage.UnloadResources(); +} + +/* use an inline instead of macro so we don't evaluate args multiple times */ +static inline int MinVal(int a, int b) { return (a < b ? a : b); } + +/* + * Create the "highlighted" bitmap from the "selected" image. + */ +void PhoneButton::CreateHighlightedBitmap(void) +{ + wxBitmap* src = mSelectedImage.GetBitmap(); + assert(src != NULL); + wxImage tmpImage = src->ConvertToImage(); + + unsigned char* pRGB = tmpImage.GetData(); // top-left RGBRGB... + int x, y; + + /* + * Modify the color used for the "highlight" image. + */ + for (y = tmpImage.GetHeight()-1; y >= 0; --y) { + for (x = tmpImage.GetWidth()-1; x >= 0; --x) { + *(pRGB) = MinVal(*(pRGB) + *(pRGB) / 8, 255); + *(pRGB+1) = MinVal(*(pRGB+1) + *(pRGB+1) / 8, 255); + *(pRGB+2) = *(pRGB+2) * 5 / 8; + + pRGB += 3; + } + } + + mHighlightedBitmap = wxBitmap(tmpImage); +} + +/* + * Check to see if the button "collides" with the specified point. + * + * This is currently a simple rectangle check, but could be modified + * to take image transparency into account. + */ +bool PhoneButton::CheckCollision(int x, int y) const +{ + if (!mHasImage) + return false; + + return (x >= mSelectedImage.GetX() && + x < mSelectedImage.GetX() + mSelectedImage.GetWidth() && + y >= mSelectedImage.GetY() && + y < mSelectedImage.GetY() + mSelectedImage.GetHeight()); +} + +/* + * Look up a key code based on a string. + * + * Returns kKeyCodeUnknown if the label doesn't match anything. + */ +KeyCode PhoneButton::LookupKeyCode(const char* label) const +{ + static const struct { + const char* label; + int keyCode; + } codeList[] = { + { "soft-left", KEY_MENU }, + { "soft-right", KEY_KBDILLUMUP }, + { "home", KEY_HOME }, + { "back", KEY_BACK }, + { "call", KEY_F3 }, + { "phone-dial", KEY_F3 }, + { "end-call", KEY_F4 }, + { "phone-hangup", KEY_F4 }, + { "0", KEY_0 }, + { "1", KEY_1 }, + { "2", KEY_2 }, + { "3", KEY_3 }, + { "4", KEY_4 }, + { "5", KEY_5 }, + { "6", KEY_6 }, + { "7", KEY_7 }, + { "8", KEY_8 }, + { "9", KEY_9 }, + { "star", KEY_SWITCHVIDEOMODE }, + { "pound", KEY_KBDILLUMTOGGLE }, + { "dpad-up", KEY_UP }, + { "dpad-down", KEY_DOWN }, + { "dpad-left", KEY_LEFT }, + { "dpad-right", KEY_RIGHT }, + { "dpad-center", KEY_REPLY }, + { "volume-up", KEY_VOLUMEUP }, + { "volume-down", KEY_VOLUMEDOWN }, + { "power", KEY_POWER }, + { "camera", KEY_CAMERA }, + //{ "clear", kKeyCodeClear }, + }; + const int numCodes = sizeof(codeList) / sizeof(codeList[0]); + + for (int i = 0; i < numCodes; i++) { + if (strcmp(label, codeList[i].label) == 0) + return (KeyCode) codeList[i].keyCode; + } + + return kKeyCodeUnknown; +}; + diff --git a/simulator/app/PhoneButton.h b/simulator/app/PhoneButton.h new file mode 100644 index 000000000..4bbaf5666 --- /dev/null +++ b/simulator/app/PhoneButton.h @@ -0,0 +1,80 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Phone button image holder. +// +#ifndef _SIM_PHONE_BUTTON_H +#define _SIM_PHONE_BUTTON_H + +#include "LoadableImage.h" +#include + +/* + * One button on a phone. Position, size, and a highlight graphic. The + * coordinates are relative to the device graphic. + * + * We now have a "highlighted" graphic for mouse-overs and a "selected" + * graphic for button presses. We assume they have the same dimensions. + * We currently assume that either both or neither exist, because we + * generate one from the other. + */ +class PhoneButton { +public: + PhoneButton(void) + : mHasImage(false), mKeyCode(kKeyCodeUnknown) + {} + virtual ~PhoneButton(void) {} + PhoneButton(const PhoneButton& src) + : mHasImage(false), mKeyCode(kKeyCodeUnknown) + { + CopyMembers(src); + } + PhoneButton& operator=(const PhoneButton& src) { + if (this != &src) { + // Unload any resources in case we're using operator= to + // assign to an existing object. + mSelectedImage.UnloadResources(); + // Copy fields. + CopyMembers(src); + } + return *this; + } + void CopyMembers(const PhoneButton& src) { + mSelectedImage = src.mSelectedImage; + mHighlightedBitmap = src.mHighlightedBitmap; + mHasImage = src.mHasImage; + mKeyCode = src.mKeyCode; + } + + /* finish construction of PhoneButton, with or without an image */ + bool Create(const char* label); + bool Create(const char* label, const char* imageFileName, int x, int y); + + int GetX(void) const { return mSelectedImage.GetX(); } + int GetY(void) const { return mSelectedImage.GetY(); } + int GetWidth(void) const { return mSelectedImage.GetWidth(); } + int GetHeight(void) const { return mSelectedImage.GetHeight(); } + wxBitmap* GetHighlightedBitmap(void) { return &mHighlightedBitmap; } + wxBitmap* GetSelectedBitmap(void) const { + return mSelectedImage.GetBitmap(); + } + + bool CheckCollision(int x, int y) const; + KeyCode GetKeyCode(void) const { return mKeyCode; } + + // load or unload the image bitmap, if any + bool LoadResources(void); + bool UnloadResources(void); + +private: + void CreateHighlightedBitmap(void); + KeyCode LookupKeyCode(const char* label) const; + + LoadableImage mSelectedImage; + wxBitmap mHighlightedBitmap; + bool mHasImage; // both exist or neither exist + + KeyCode mKeyCode; +}; + +#endif // _SIM_PHONE_BUTTON_H diff --git a/simulator/app/PhoneCollection.cpp b/simulator/app/PhoneCollection.cpp new file mode 100644 index 000000000..5cddfa8b3 --- /dev/null +++ b/simulator/app/PhoneCollection.cpp @@ -0,0 +1,174 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Our collection of devices. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +//#include "wx/image.h" // needed for Windows build + + +#include "PhoneCollection.h" +#include "PhoneData.h" +#include "MyApp.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace android; + +/*static*/ PhoneCollection* PhoneCollection::mpInstance = NULL; + +/*static*/ const char* PhoneCollection::kLayoutFile = "layout.xml"; + + +/* + * (Re-)scan the specified directory for phones. We register a hit if we can + * see a file called "/layout.xml". + */ +void PhoneCollection::ScanForPhones(const char* directory) +{ + /* + * Scan through the directory and find everything that looks like it + * might hold phone data. + */ + StringArray strArr; + +#ifdef BEFORE_ASSET + DIR* dirp; + struct dirent* entp; + + dirp = opendir(directory); + if (dirp == NULL) { + char buf[512]; + fprintf(stderr, "ERROR: unable to scan directory '%s' for phone data\n", + directory); + fprintf(stderr, "Current dir is %s\n", getcwd(buf, sizeof(buf))); + return; + } + + while (1) { + wxString dirName; + wxString fileName; + + entp = readdir(dirp); + if (entp == NULL) + break; // done with scan + dirName = directory; + dirName += '/'; + dirName += entp->d_name; + fileName = dirName; + fileName += '/'; + fileName += kLayoutFile; + + if (access(fileName, R_OK) == 0) { + strArr.push_back(dirName); + //printf("--- examining '%s'\n", (const char*) fileName); + } + } + closedir(dirp); +#else + android::AssetManager* pAssetMgr = ((MyApp*)wxTheApp)->GetAssetManager(); + android::AssetDir* pDir; + int i, count; + + pDir = pAssetMgr->openDir(""); + assert(pDir != NULL); + count = pDir->getFileCount(); + + for (i = 0; i < count; i++) { + android::String8 layoutPath; + + if (pDir->getFileType(i) != kFileTypeDirectory) + continue; + + layoutPath = pDir->getFileName(i); + layoutPath.appendPath(kLayoutFile); + + if (pAssetMgr->getFileType(layoutPath.string()) == kFileTypeRegular) { + strArr.push_back(pDir->getFileName(i).string()); + printf("--- examining '%s'\n", layoutPath.string()); + } + } + + delete pDir; +#endif + + if (strArr.size() == 0) { + fprintf(stderr, "ERROR: no phone data found in '%s'\n", directory); + return; + } + + /* + * Found some candidates. If they parse successfully, add them to + * our list. + * + * We sort them first, because it's nice when everybody's user + * interface looks the same. Note we're sorting the directory name, + * so it's possible to define a sort order in the filesystem that + * doesn't require messing up the phone's title string. + */ + mPhoneList.clear(); + strArr.sort(StringArray::cmpAscendingAlpha); + + for (int i = 0; i < strArr.size(); i++) { + PhoneData tmpPhone; + + if (!tmpPhone.Create(strArr.getEntry(i))) { + fprintf(stderr, "Sim: Abandoning phone '%s'\n", strArr.getEntry(i)); + //strArr.erase(i); + //i--; + } else { + if (GetPhoneData(tmpPhone.GetName()) != NULL) { + fprintf(stderr, "Sim: ERROR: duplicate name '%s' in '%s'\n", + tmpPhone.GetName(), strArr.getEntry(i)); + } else { + mPhoneList.push_back(tmpPhone); + } + } + } +} + + +/* + * Return the Nth member of the phone data array. (Replace w/Vector.) + */ +PhoneData* PhoneCollection::GetPhoneData(int idx) +{ + typedef List::iterator Iter; + + for (Iter ii = mPhoneList.begin(); ii != mPhoneList.end(); ++ii) { + if (idx == 0) + return &(*ii); + --idx; + } + return NULL; +} + +/* + * Return the entry whose phone data name matches "name". + */ +PhoneData* PhoneCollection::GetPhoneData(const char* name) +{ + typedef List::iterator Iter; + + for (Iter ii = mPhoneList.begin(); ii != mPhoneList.end(); ++ii) { + if (strcasecmp((*ii).GetName(), name) == 0) + return &(*ii); + } + return NULL; +} + diff --git a/simulator/app/PhoneCollection.h b/simulator/app/PhoneCollection.h new file mode 100644 index 000000000..d16c4a03f --- /dev/null +++ b/simulator/app/PhoneCollection.h @@ -0,0 +1,52 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Our collection of devices. +// +#ifndef _SIM_PHONE_COLLECTION_H +#define _SIM_PHONE_COLLECTION_H + +#include +#include "PhoneData.h" + +/* + * Only one instance of this class exists. It contains a list of all + * known devices, and methods for scanning for devices. + */ +class PhoneCollection { +public: + /* get the global instance */ + static PhoneCollection* GetInstance(void) { + if (mpInstance == NULL) + mpInstance = new PhoneCollection; + return mpInstance; + } + /* destroy the global instance when shutting down */ + static void DestroyInstance(void) { + delete mpInstance; + mpInstance = NULL; + } + + /* scan for phones in subdirectories of "directory" */ + void ScanForPhones(const char* directory); + + /* get phone data */ + int GetPhoneCount(void) const { return mPhoneList.size(); } // slow + PhoneData* GetPhoneData(int idx); + PhoneData* GetPhoneData(const char* name); + + /* layout.xml filename -- a string constant used in various places */ + static const char* kLayoutFile; + +private: + PhoneCollection(void) {} + ~PhoneCollection(void) {} + + /* the phone data; make this a Vector someday */ + android::List mPhoneList; + + /* storage for global instance pointer */ + static PhoneCollection* mpInstance; +}; + +#endif // _SIM_PHONE_COLLECTION_H diff --git a/simulator/app/PhoneData.cpp b/simulator/app/PhoneData.cpp new file mode 100644 index 000000000..48614fd7a --- /dev/null +++ b/simulator/app/PhoneData.cpp @@ -0,0 +1,868 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Simulated device data. +// + +// For compilers that support precompilation, include "wx/wx.h". +#include "wx/wxprec.h" + +// Otherwise, include all standard headers +#ifndef WX_PRECOMP +# include "wx/wx.h" +#endif +#include "wx/image.h" // needed for Windows build + + +#include "PhoneData.h" +#include "PhoneButton.h" +#include "PhoneCollection.h" +#include "MyApp.h" + +#include +#include +#include + +#include "tinyxml.h" + +#include +#include +#include +#include +#include + +using namespace android; + +/* image relative path hack */ +static const char* kRelPathMagic = "::/"; + + +/* + * =========================================================================== + * PhoneKeyboard + * =========================================================================== + */ + +/* + * Load a chunk. + */ +bool PhoneKeyboard::ProcessAndValidate(TiXmlNode* pNode) +{ + //TiXmlNode* pChild; + TiXmlElement* pElem; + int qwerty = 0; + + assert(pNode->Type() == TiXmlNode::ELEMENT); + + pElem = pNode->ToElement(); + pElem->Attribute("qwerty", &qwerty); + const char *kmap = pElem->Attribute("keycharmap"); + + if (qwerty == 1) { + printf("############## PhoneKeyboard::ProcessAndValidate: qwerty = true!\n"); + mQwerty = true; + } + + if (kmap != NULL) { + printf("############## PhoneKeyboard::ProcessAndValidate: keycharmap = %s\n", kmap); + mKeyMap = strdup(kmap); + } + + return true; +} + + +/* + * =========================================================================== + * PhoneDisplay + * =========================================================================== + */ + +/* + * Load a chunk. + */ +bool PhoneDisplay::ProcessAndValidate(TiXmlNode* pNode) +{ + //TiXmlNode* pChild; + TiXmlElement* pElem; + const char* name; + const char* format; + + assert(pNode->Type() == TiXmlNode::ELEMENT); + + /* + * Process attributes. Right now they're all mandatory, but some of + * them could be defaulted (e.g. "rotate"). + * + * [We should do some range-checking here.] + */ + pElem = pNode->ToElement(); + name = pElem->Attribute("name"); + if (name == NULL) + goto missing; + if (pElem->Attribute("width", &mWidth) == NULL) + goto missing; + if (pElem->Attribute("height", &mHeight) == NULL) + goto missing; + if (pElem->Attribute("refresh", &mRefresh) == NULL) + goto missing; + format = pElem->Attribute("format"); + if (format == NULL) + goto missing; + + delete[] mName; + mName = strdupNew(name); + + if (strcasecmp(format, "rgb565") == 0) { + mFormat = android::PIXEL_FORMAT_RGB_565; + } else { + fprintf(stderr, "SimCFG: unexpected value for display format\n"); + return false; + } + + return true; + +missing: + fprintf(stderr, + "SimCFG: requires name/width/height/format/refresh\n"); + return false; +} + + +/* + * Returns "true" if the two displays are compatible, "false" if not. + * + * Compatibility means they have the same resolution, format, refresh + * rate, and so on. Anything transmitted to the runtime as part of the + * initial configuration setup should be tested. + */ +/*static*/ bool PhoneDisplay::IsCompatible(PhoneDisplay* pDisplay1, + PhoneDisplay* pDisplay2) +{ + return (pDisplay1->mWidth == pDisplay2->mWidth && + pDisplay1->mHeight == pDisplay2->mHeight && + pDisplay1->mFormat == pDisplay2->mFormat && + pDisplay1->mRefresh == pDisplay2->mRefresh); +} + + +/* + * =========================================================================== + * PhoneView + * =========================================================================== + */ + +/* + * Load a chunk. + */ +bool PhoneView::ProcessAndValidate(TiXmlNode* pNode, const char* directory) +{ + TiXmlNode* pChild; + TiXmlElement* pElem; + int rotate; + const char* displayName; + + assert(pNode->Type() == TiXmlNode::ELEMENT); + + /* + * Process attributes. Right now they're all mandatory, but some of + * them could be defaulted (e.g. "rotate"). + * + * [We should do some range-checking here.] + */ + pElem = pNode->ToElement(); + displayName = pElem->Attribute("display"); + if (displayName == NULL) + goto missing; + if (pElem->Attribute("x", &mXOffset) == NULL) + goto missing; + if (pElem->Attribute("y", &mYOffset) == NULL) + goto missing; + if (pElem->Attribute("rotate", &rotate) == NULL) + goto missing; + + switch (rotate) { + case 0: mRotation = kRot0; break; + case 90: mRotation = kRot90; break; + case 180: mRotation = kRot180; break; + case 270: mRotation = kRot270; break; + default: + fprintf(stderr, "SimCFG: unexpected value for rotation\n"); + mRotation = kRotUnknown; + return false; + } + + delete[] mDisplayName; + mDisplayName = android::strdupNew(displayName); + + /* + * Process elements. + */ + for (pChild = pNode->FirstChild(); pChild != NULL; + pChild = pChild->NextSibling()) + { + if (pChild->Type() == TiXmlNode::COMMENT) + continue; + + if (pChild->Type() == TiXmlNode::ELEMENT) { + if (strcasecmp(pChild->Value(), "image") == 0) { + if (!ProcessImage(pChild, directory)) + return false; + } else if (strcasecmp(pChild->Value(), "button") == 0) { + if (!ProcessButton(pChild, directory)) + return false; + } else { + fprintf(stderr, + "SimCFG: Warning: unexpected elements in \n"); + } + } else { + fprintf(stderr, "SimCFG: Warning: unexpected stuff in \n"); + } + } + + return true; + +missing: + fprintf(stderr, + "SimCFG: requires display/x/y/rotate\n"); + return false; +} + +/* + * Handle . + */ +bool PhoneView::ProcessImage(TiXmlNode* pNode, const char* directory) +{ + TiXmlNode* pChild; + TiXmlElement* pElem; + int x, y; + const char* src; + LoadableImage tmpLimg; + android::String8 fileName; + + pChild = pNode->FirstChild(); + if (pChild != NULL) { + fprintf(stderr, "SimCFG: is funky\n"); + return false; + } + + /* + * All attributes are mandatory. + */ + pElem = pNode->ToElement(); + src = pElem->Attribute("src"); + if (src == NULL) + goto missing; + if (pElem->Attribute("x", &x) == NULL) + goto missing; + if (pElem->Attribute("y", &y) == NULL) + goto missing; + + if (strncmp(src, kRelPathMagic, strlen(kRelPathMagic)) == 0) { + fileName = src + strlen(kRelPathMagic); + } else { + fileName = directory; + fileName += "/"; + fileName += src; + } + + tmpLimg.Create(fileName, x, y); + mImageList.push_back(tmpLimg); + + return true; + +missing: + fprintf(stderr, "SimCFG: requires src/x/y\n"); + return false; +} + +/* + * Handle