diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 |
| commit | 5c11852110eeb03dc5a69111354b383f98d15336 (patch) | |
| tree | 7b26fde6effb80c241f28fc3e620016e7f86418e /simulator | |
| download | android_development-5c11852110eeb03dc5a69111354b383f98d15336.tar.gz android_development-5c11852110eeb03dc5a69111354b383f98d15336.tar.bz2 android_development-5c11852110eeb03dc5a69111354b383f98d15336.zip | |
Initial Contribution
Diffstat (limited to 'simulator')
214 files changed, 19729 insertions, 0 deletions
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 <utils/Asset.h> + +/* + * 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 <unistd.h> +#include <signal.h> +#include <errno.h> + +#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 <stuff> -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 <sys/stat.h> + +/* + * 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 <linux/input.h> + +/* 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 <utils.h> + +#include <stdio.h> + + +/* + * 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 <windows.h> +#else +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/stat.h> +# include <sys/un.h> +#endif + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <assert.h> + +#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 <utils/Pipe.h> + +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 <pthread.h> +#endif +#include <cutils/logd.h> // 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 <assert.h> + +/* + * 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; i<pBundle->msgCount; i++) len += pBundle->msgVec[i].iov_len; + newMsg->mMsg = new char[len+1]; + char* p = newMsg->mMsg; + for (i=0; i<pBundle->msgCount; 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 <assert.h> + + +/* + * 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 <errno.h> + +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 <string.h> +#include <assert.h> + +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<unsigned char*>(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; i<pBundle->msgCount; 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; i<pBundle->msgCount; 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 <utils/Pipe.h> +#include <stdlib.h> +#include <cutils/uio.h> + +// 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 <utils/executablepath.h> + +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <signal.h> + +#if defined(HAVE_WINDOWS_PATHS) +# include <windows.h> +#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; i<argc; i++) { + wxCharBuffer tmp = wxString(argv[i]).ToAscii(); + cargv[i] = tmp.release(); + } + if (!ParseArgs(argc, cargv)) { + for (int i=0; i<argc; i++) + free(cargv[i]); + free(cargv); + return FALSE; + } + for (int i=0; i<argc; i++) + free(cargv[i]); + free(cargv); + + if (!ProcessConfigFile()) + return FALSE; + + /* + * (Try to) catch SIGINT (Ctrl-C). + */ + bool trapInt = false; + mPrefs.GetBool("trap-sigint", &trapInt); + if (trapInt) { + printf("Sim: catching SIGINT\n"); + signal(SIGINT, SignalHandler); + } + + signal(SIGPIPE, SIG_IGN); + + /* + * Set stdout to unbuffered. This is needed for MinGW/MSYS. + * Set stderr while we're at it. + */ + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + /* + * Initialize asset manager. + */ + mpAssetManager = NULL; + printf("Sim: looking in '%s' for my assets\n", (const char*) mSimAssetPath.ToAscii()); + ChangeAssetDirectory(mSimAssetPath); + + /* + * Add JPEG and PNG image handlers. + */ + ::wxInitAllImageHandlers(); + + /* + * Set up the help file browser. We're using wxHtmlHelpController + * because it seems to be the only "portable" version other than + * the "use external browser" version. + */ + wxFileSystem::AddHandler(new wxZipFSHandler); + mHelpController = new wxHtmlHelpController; + + wxString helpFileName; + helpFileName = mSimAssetPath; + helpFileName += '/'; + helpFileName += helpFilePath; + mHelpController->Initialize(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 <utils/AssetManager.h> + +/* 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 <ui/KeycodeLabels.h> + +/* + * 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 <utils.h> + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <dirent.h> +#include <assert.h> + +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 "<directory>/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<PhoneData>::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<PhoneData>::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 <stdlib.h> +#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<PhoneData> 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 <utils.h> +#include <utils/AssetManager.h> +#include <utils/String8.h> + +#include "tinyxml.h" + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +using namespace android; + +/* image relative path hack */ +static const char* kRelPathMagic = "::/"; + + +/* + * =========================================================================== + * PhoneKeyboard + * =========================================================================== + */ + +/* + * Load a <keyboard> 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 <display> 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: <display> 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 <view> 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 <display>\n"); + } + } else { + fprintf(stderr, "SimCFG: Warning: unexpected stuff in <display>\n"); + } + } + + return true; + +missing: + fprintf(stderr, + "SimCFG: <view> requires display/x/y/rotate\n"); + return false; +} + +/* + * Handle <image src="zzz" x="123" y="123"/>. + */ +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: <image> 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: <image> requires src/x/y\n"); + return false; +} + +/* + * Handle <button keyCode="zzz" src="zzz" x="123" y="123"/> and + * <button keyCode="zzz"/>. + */ +bool PhoneView::ProcessButton(TiXmlNode* pNode, const char* directory) +{ + TiXmlNode* pChild; + TiXmlElement* pElem; + int x, y; + const char* keyCode; + const char* src; + PhoneButton tmpButton; + android::String8 fileName; + + pChild = pNode->FirstChild(); + if (pChild != NULL) { + fprintf(stderr, "SimCFG: button is funky\n"); + return false; + } + + /* + * Only keyCode is mandatory. If they specify "src", then "x" and "y" + * are also required. + */ + pElem = pNode->ToElement(); + keyCode = pElem->Attribute("keyCode"); + if (keyCode == NULL) + goto missing; + + src = pElem->Attribute("src"); + if (src != NULL) { + if (pElem->Attribute("x", &x) == NULL) + goto missing; + if (pElem->Attribute("y", &y) == NULL) + goto missing; + } + + if (src == NULL) + tmpButton.Create(keyCode); + else { + if (strncmp(src, kRelPathMagic, strlen(kRelPathMagic)) == 0) { + fileName = src + strlen(kRelPathMagic); + } else { + fileName = directory; + fileName += "/"; + fileName += src; + } + tmpButton.Create(keyCode, fileName, x, y); + } + + mButtonList.push_back(tmpButton); + + return true; + +missing: + fprintf(stderr, "SimCFG: <button> requires keycode and may have src/x/y\n"); + return false; +} + + +/* + * Load all resources associated with the display. + */ +bool PhoneView::LoadResources(void) +{ + typedef List<LoadableImage>::iterator LIter; + typedef List<PhoneButton>::iterator BIter; + + for (LIter ii = mImageList.begin(); ii != mImageList.end(); ++ii) + (*ii).LoadResources(); + for (BIter ii = mButtonList.begin(); ii != mButtonList.end(); ++ii) + (*ii).LoadResources(); + return true; +} + +/* + * Unload all resources associated with the display. + */ +bool PhoneView::UnloadResources(void) +{ + typedef List<LoadableImage>::iterator LIter; + typedef List<PhoneButton>::iterator BIter; + + for (LIter ii = mImageList.begin(); ii != mImageList.end(); ++ii) + (*ii).UnloadResources(); + for (BIter ii = mButtonList.begin(); ii != mButtonList.end(); ++ii) + (*ii).UnloadResources(); + return true; +} + + +/* + * Get the #of images. + */ +int PhoneView::GetBkgImageCount(void) const +{ + return mImageList.size(); +} + +/* + * Return the Nth entry. + */ +const LoadableImage* PhoneView::GetBkgImage(int idx) const +{ + typedef List<LoadableImage>::const_iterator Iter; + + for (Iter ii = mImageList.begin(); ii != mImageList.end(); ++ii) { + if (!idx) + return &(*ii); + --idx; + } + + return NULL; +} + + +/* + * Find the first button that covers the specified coordinates. + * + * The coordinates must be relative to the upper left corner of the + * phone image. + */ +PhoneButton* PhoneView::FindButtonHit(int x, int y) +{ + typedef List<PhoneButton>::iterator Iter; + + for (Iter ii = mButtonList.begin(); ii != mButtonList.end(); ++ii) { + if ((*ii).CheckCollision(x, y)) + return &(*ii); + } + + return NULL; +} + +/* + * Find the first button with a matching key code. + */ +PhoneButton* PhoneView::FindButtonByKey(KeyCode keyCode) +{ + typedef List<PhoneButton>::iterator Iter; + + for (Iter ii = mButtonList.begin(); ii != mButtonList.end(); ++ii) { + if ((*ii).GetKeyCode() == keyCode) + return &(*ii); + } + + return NULL; +} + + +/* + * =========================================================================== + * PhoneMode + * =========================================================================== + */ + +/* + * Process a <mode name="zzz"> chunk. + */ +bool PhoneMode::ProcessAndValidate(TiXmlNode* pNode, const char* directory) +{ + TiXmlNode* pChild; + const char* name; + + assert(pNode->Type() == TiXmlNode::ELEMENT); + + name = pNode->ToElement()->Attribute("name"); + if (name == NULL) { + fprintf(stderr, "SimCFG: <mode> requires name attrib\n"); + return false; + } + SetName(name); + + for (pChild = pNode->FirstChild(); pChild != NULL; + pChild = pChild->NextSibling()) + { + if (pChild->Type() == TiXmlNode::COMMENT) + continue; + + if (pChild->Type() == TiXmlNode::ELEMENT && + strcasecmp(pChild->Value(), "view") == 0) + { + PhoneView tmpDisplay; + bool result; + + result = tmpDisplay.ProcessAndValidate(pChild, directory); + if (!result) + return false; + + mViewList.push_back(tmpDisplay); + } else { + fprintf(stderr, "SimCFG: Warning: unexpected stuff in <mode>\n"); + } + } + + if (mViewList.size() == 0) { + fprintf(stderr, "SimCFG: no <view> entries found\n"); + return false; + } + + return true; +} + + +/* + * Load all resources associated with the phone. + */ +bool PhoneMode::LoadResources(void) +{ + typedef List<PhoneView>::iterator Iter; + + for (Iter ii = mViewList.begin(); ii != mViewList.end(); ++ii) + (*ii).LoadResources(); + return true; +} + +/* + * Unload all resources associated with the phone. + */ +bool PhoneMode::UnloadResources(void) +{ + typedef List<PhoneView>::iterator Iter; + + for (Iter ii = mViewList.begin(); ii != mViewList.end(); ++ii) + (*ii).UnloadResources(); + return true; +} + + +/* + * Return the Nth entry. [make this a Vector?] + */ +PhoneView* PhoneMode::GetPhoneView(int viewNum) +{ + typedef List<PhoneView>::iterator Iter; + + for (Iter ii = mViewList.begin(); ii != mViewList.end(); ++ii) { + if (viewNum == 0) + return &(*ii); + --viewNum; + } + return NULL; +} + + +/* + * =========================================================================== + * PhoneData + * =========================================================================== + */ + + +/* + * Look for a "layout.xml" in the specified directory. If found, parse + * the contents out. + * + * Returns "true" on success, "false" on failure. + */ +bool PhoneData::Create(const char* directory) +{ + android::String8 fileName; + + SetDirectory(directory); + + fileName = directory; + fileName += "/"; + fileName += PhoneCollection::kLayoutFile; + +#ifdef BEFORE_ASSET + TiXmlDocument doc(fileName); + if (!doc.LoadFile()) +#else + android::AssetManager* pAssetMgr = ((MyApp*)wxTheApp)->GetAssetManager(); + TiXmlDocument doc; + android::Asset* pAsset; + bool result; + + pAsset = pAssetMgr->open(fileName, Asset::ACCESS_STREAMING); + if (pAsset == NULL) { + fprintf(stderr, "Unable to open asset '%s'\n", (const char*) fileName); + return false; + } else { + //printf("--- opened asset '%s'\n", + // (const char*) pAsset->getAssetSource()); + } + + /* TinyXml insists that the buffer be NULL-terminated... ugh */ + char* buf = new char[pAsset->getLength() +1]; + pAsset->read(buf, pAsset->getLength()); + buf[pAsset->getLength()] = '\0'; + + delete pAsset; + result = doc.Parse(buf); + delete[] buf; + + if (!result) +#endif + { + fprintf(stderr, "SimCFG: ERROR: failed parsing '%s'\n", + (const char*) fileName); + if (doc.ErrorRow() != 0) + fprintf(stderr, " XML: %s (row=%d col=%d)\n", + doc.ErrorDesc(), doc.ErrorRow(), doc.ErrorCol()); + else + fprintf(stderr, " XML: %s\n", doc.ErrorDesc()); + return false; + } + + if (!ProcessAndValidate(&doc)) { + fprintf(stderr, "SimCFG: ERROR: failed analyzing '%s'\n", + (const char*) fileName); + return false; + } + + printf("SimCFG: loaded data from '%s'\n", (const char*) fileName); + + return true; +} + +/* + * TinyXml has loaded and parsed the XML document for us. We need to + * run through the DOM tree, pull out the interesting bits, and make + * sure the stuff we need is present. + * + * Returns "true" on success, "false" on failure. + */ +bool PhoneData::ProcessAndValidate(TiXmlDocument* pDoc) +{ + bool deviceFound = false; + TiXmlNode* pChild; + + assert(pDoc->Type() == TiXmlNode::DOCUMENT); + + for (pChild = pDoc->FirstChild(); pChild != NULL; + pChild = pChild->NextSibling()) + { + /* + * Find the <device> entry. There should be exactly one. + */ + if (pChild->Type() == TiXmlNode::ELEMENT) { + if (strcasecmp(pChild->Value(), "device") != 0) { + fprintf(stderr, + "SimCFG: Warning: unexpected element '%s' at top level\n", + pChild->Value()); + continue; + } + if (deviceFound) { + fprintf(stderr, "SimCFG: one <device> per customer\n"); + return false; + } + + bool result = ProcessDevice(pChild); + if (!result) + return false; + deviceFound = true; + } + } + + if (!deviceFound) { + fprintf(stderr, "SimCFG: no <device> section found\n"); + return false; + } + + return true; +} + +/* + * Process a <device name="zzz"> chunk. + */ +bool PhoneData::ProcessDevice(TiXmlNode* pNode) +{ + TiXmlNode* pChild; + const char* name; + + assert(pNode->Type() == TiXmlNode::ELEMENT); + + name = pNode->ToElement()->Attribute("name"); + if (name == NULL) { + fprintf(stderr, "SimCFG: <device> requires name attrib\n"); + return false; + } + SetName(name); + + /* + * Walk through the children and find interesting stuff. + * + * Might be more correct to process all <display> entries and + * then process all <view> entries, since <view> has "pointers" + * to <display>. We're deferring the lookup until later, though, + * so for now it doesn't really matter. + */ + for (pChild = pNode->FirstChild(); pChild != NULL; + pChild = pChild->NextSibling()) + { + bool result; + + if (pChild->Type() == TiXmlNode::COMMENT) + continue; + + if (pChild->Type() == TiXmlNode::ELEMENT && + strcasecmp(pChild->Value(), "title") == 0) + { + result = ProcessTitle(pChild); + if (!result) + return false; + } else if (pChild->Type() == TiXmlNode::ELEMENT && + strcasecmp(pChild->Value(), "display") == 0) + { + PhoneDisplay tmpDisplay; + + result = tmpDisplay.ProcessAndValidate(pChild); + if (!result) + return false; + + mDisplayList.push_back(tmpDisplay); + } else if (pChild->Type() == TiXmlNode::ELEMENT && + strcasecmp(pChild->Value(), "keyboard") == 0) + { + PhoneKeyboard tmpKeyboard; + result = tmpKeyboard.ProcessAndValidate(pChild); + if (!result) + return false; + + mKeyboardList.push_back(tmpKeyboard); + } else if (pChild->Type() == TiXmlNode::ELEMENT && + strcasecmp(pChild->Value(), "mode") == 0) + { + PhoneMode tmpMode; + + result = tmpMode.ProcessAndValidate(pChild, mDirectory); + if (!result) + return false; + + mModeList.push_back(tmpMode); + } else { + fprintf(stderr, "SimCFG: Warning: unexpected stuff in <device>\n"); + } + } + + if (mDisplayList.size() == 0) { + fprintf(stderr, "SimCFG: no <display> entries found\n"); + return false; + } + if (mModeList.size() == 0) { + fprintf(stderr, "SimCFG: no <mode> entries found\n"); + return false; + } + + return true; +} + +/* + * Handle <title>. + */ +bool PhoneData::ProcessTitle(TiXmlNode* pNode) +{ + TiXmlNode* pChild; + + pChild = pNode->FirstChild(); + if (pChild->Type() != TiXmlNode::TEXT) { + fprintf(stderr, "SimCFG: title is funky\n"); + return false; + } + + SetTitle(pChild->Value()); + return true; +} + + +/* + * Load all resources associated with the phone. + */ +bool PhoneData::LoadResources(void) +{ + typedef List<PhoneMode>::iterator Iter; + + for (Iter ii = mModeList.begin(); ii != mModeList.end(); ++ii) + (*ii).LoadResources(); + return true; +} + +/* + * Unload all resources associated with the phone. + */ +bool PhoneData::UnloadResources(void) +{ + typedef List<PhoneMode>::iterator Iter; + + for (Iter ii = mModeList.begin(); ii != mModeList.end(); ++ii) + (*ii).UnloadResources(); + return true; +} + + +/* + * Return the PhoneMode entry with the matching name. + * + * Returns NULL if no match was found. + */ +PhoneMode* PhoneData::GetPhoneMode(const char* modeName) +{ + typedef List<PhoneMode>::iterator Iter; + + for (Iter ii = mModeList.begin(); ii != mModeList.end(); ++ii) { + if (strcmp((*ii).GetName(), modeName) == 0) + return &(*ii); + } + return NULL; +} + +/* + * Return the Nth phone mode entry. + */ +PhoneMode* PhoneData::GetPhoneMode(int idx) +{ + typedef List<PhoneMode>::iterator Iter; + + for (Iter ii = mModeList.begin(); ii != mModeList.end(); ++ii) { + if (!idx) + return &(*ii); + --idx; + } + return NULL; +} + + +/* + * Return the PhoneDisplay entry with the matching name. + * + * Returns NULL if no match was found. + */ +PhoneDisplay* PhoneData::GetPhoneDisplay(const char* dispName) +{ + typedef List<PhoneDisplay>::iterator Iter; + + for (Iter ii = mDisplayList.begin(); ii != mDisplayList.end(); ++ii) { + if (strcmp((*ii).GetName(), dispName) == 0) + return &(*ii); + } + return NULL; +} + +/* + * Return the Nth phone mode entry. + */ +PhoneDisplay* PhoneData::GetPhoneDisplay(int idx) +{ + typedef List<PhoneDisplay>::iterator Iter; + + for (Iter ii = mDisplayList.begin(); ii != mDisplayList.end(); ++ii) { + if (!idx) + return &(*ii); + --idx; + } + return NULL; +} + +/* + * Find the PhoneDisplay entry with the matching name, and return its index. + * + * Returns -1 if the entry wasn't found. + */ +int PhoneData::GetPhoneDisplayIndex(const char* dispName) +{ + typedef List<PhoneDisplay>::iterator Iter; + int idx = 0; + + for (Iter ii = mDisplayList.begin(); ii != mDisplayList.end(); ++ii) { + if (strcmp((*ii).GetName(), dispName) == 0) + return idx; + idx++; + } + return -1; +} + + +/* + * Return the Nth phone keyboard entry. + */ +PhoneKeyboard* PhoneData::GetPhoneKeyboard(int idx) +{ + typedef List<PhoneKeyboard>::iterator Iter; + + for (Iter ii = mKeyboardList.begin(); ii != mKeyboardList.end(); ++ii) { + if (!idx) + return &(*ii); + --idx; + } + return NULL; +} diff --git a/simulator/app/PhoneData.h b/simulator/app/PhoneData.h new file mode 100644 index 000000000..c7f473274 --- /dev/null +++ b/simulator/app/PhoneData.h @@ -0,0 +1,365 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Simulated device definition. +// +// The "root" of the data structures here is PhoneCollection, which may +// discard the entire set if the user asks to re-scan the phone definitions. +// These structures should be considered read-only. +// +// PhoneCollection (single global instance) +// -->PhoneData +// -->PhoneDisplay +// -->PhoneMode +// -->PhoneView +// +#ifndef _SIM_PHONE_DATA_H +#define _SIM_PHONE_DATA_H + +#include <stdio.h> +#include "tinyxml.h" + +#include "PhoneButton.h" +#include "LoadableImage.h" +#include <ui/PixelFormat.h> +#include <utils.h> + + +/* + * This represents the keyboard type of the simulated device + */ +class PhoneKeyboard { +public: + PhoneKeyboard(void) + : mQwerty(false), mKeyMap(NULL) + {} + ~PhoneKeyboard(void) { + free((void*)mKeyMap); + } + + PhoneKeyboard(const PhoneKeyboard& src) + : mQwerty(false), mKeyMap(NULL) + { + CopyMembers(src); + } + PhoneKeyboard& operator=(const PhoneKeyboard& src) { + if (this != &src) // self-assignment + CopyMembers(src); + return *this; + } + void CopyMembers(const PhoneKeyboard& src) { + mQwerty = src.mQwerty; + mKeyMap = src.mKeyMap ? strdup(src.mKeyMap) : NULL; + } + + bool ProcessAndValidate(TiXmlNode* pNode); + + bool getQwerty() { return mQwerty; } + + const char *getKeyMap() { return mKeyMap; } +private: + bool mQwerty; + const char * mKeyMap; +}; + +/* + * This represents a single display device, usually an LCD screen. + * It also includes an optional surrounding graphic, usually a picture of + * the device itself. + */ +class PhoneDisplay { +public: + PhoneDisplay(void) + : mName(NULL) + {} + ~PhoneDisplay(void) { + delete[] mName; + } + + PhoneDisplay(const PhoneDisplay& src) + : mName(NULL) + { + CopyMembers(src); + } + PhoneDisplay& operator=(const PhoneDisplay& src) { + if (this != &src) // self-assignment + CopyMembers(src); + return *this; + } + void CopyMembers(const PhoneDisplay& src) { + // Can't memcpy and member-copy the container classes, because the + // containers have already been constructed, and for operator= they + // might even have stuff in them. + delete[] mName; + mName = android::strdupNew(src.mName); + mWidth = src.mWidth; + mHeight = src.mHeight; + mFormat = src.mFormat; + mRefresh = src.mRefresh; + } + + bool ProcessAndValidate(TiXmlNode* pNode); + + const char* GetName(void) const { return mName; } + 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; } + + static bool IsCompatible(PhoneDisplay* pDisplay1, PhoneDisplay* pDisplay2); + +private: + char* mName; + + // display dimensions, in pixels + int mWidth; + int mHeight; + + // frame buffer format + android::PixelFormat mFormat; + + // display refresh rate, in fps + int mRefresh; +}; + +/* + * This is a "view" of a device, which includes the display, a background + * image, and perhaps some clickable keys for input. + * + * Because the key graphics are associated with a particular display, we + * hold a list of keys here. (It also allows the possibility of handling + * a situation where the same key shows up in multiple background images, + * e.g. a flip phone with a "volume" key on the side. If we include the + * key in both places, we can highlight it on both displays.) + */ +class PhoneView { +public: + PhoneView(void) + : mDisplayName(NULL) + {} + ~PhoneView(void) { + delete[] mDisplayName; + } + + PhoneView(const PhoneView& src) { + CopyMembers(src); + } + PhoneView& operator=(const PhoneView& src) { + if (this != &src) // self-assignment + CopyMembers(src); + return *this; + } + void CopyMembers(const PhoneView& src) { + // Can't memcpy and member-copy the container classes, because the + // containers have already been constructed, and for operator= they + // might even have stuff in them. + mImageList = src.mImageList; + mButtonList = src.mButtonList; + mDisplayName = android::strdupNew(src.mDisplayName); + mXOffset = src.mXOffset; + mYOffset = src.mYOffset; + mRotation = src.mRotation; + } + + // load or unload resources, e.g. wxBitmaps from image files + bool LoadResources(void); + bool UnloadResources(void); + + // simple accessors + int GetXOffset(void) const { return mXOffset; } + int GetYOffset(void) const { return mYOffset; } + const char* GetDisplayName(void) const { return mDisplayName; } + + // image list access + int GetBkgImageCount(void) const; + const LoadableImage* GetBkgImage(int idx) const; + + // find the first button that covers the specified coords + PhoneButton* FindButtonHit(int x, int y); + + // find the first button with a matching key code + PhoneButton* FindButtonByKey(KeyCode keyCode); + + bool ProcessAndValidate(TiXmlNode* pNode, const char* directory); + bool ProcessImage(TiXmlNode* pNode, const char* directory); + bool ProcessButton(TiXmlNode* pNode, const char* directory); + +private: + // background images for the phone picture that surrounds the display + android::List<LoadableImage> mImageList; + + // list of accessible buttons, some of which have highlight graphics + android::List<PhoneButton> mButtonList; + + char* mDisplayName; + + // these determine where in the image the display output goes + int mXOffset; + int mYOffset; + + // clockwise rotation of the output; sim must rotate in opposite direction + typedef enum Rotation { + kRotUnknown = 0, + kRot0, + kRot90, + kRot180, + kRot270, + } Rotation; + Rotation mRotation; +}; + +/* + * One mode of a phone. Simple devices only have one mode. Flip phones + * have two (opened and closed). Other devices might have more. The + * mode is communicated to the runtime because it may need to process + * input events differently. + */ +class PhoneMode { +public: + PhoneMode(void) + : mName(NULL) + {} + ~PhoneMode(void) { + delete[] mName; + } + + PhoneMode(const PhoneMode& src) + : mName(NULL) + { + CopyMembers(src); + } + PhoneMode& operator=(const PhoneMode& src) { + if (this != &src) // self-assignment + CopyMembers(src); + return *this; + } + void CopyMembers(const PhoneMode& src) { + delete[] mName; + mName = android::strdupNew(src.mName); + mViewList = src.mViewList; + } + + + // load or unload resources for this object and all members of mViewList + bool LoadResources(void); + bool UnloadResources(void); + + // get the #of views + int GetNumViews(void) const { return mViewList.size(); } + // get the Nth display + PhoneView* GetPhoneView(int viewNum); + + const char* GetName(void) const { return mName; } + void SetName(const char* name) { + delete[] mName; + mName = android::strdupNew(name); + } + + // load the <mode> section from the config file + bool ProcessAndValidate(TiXmlNode* pNode, const char* directory); + +private: + char* mName; + + android::List<PhoneView> mViewList; +}; + +/* + * This holds the data for one device. + * + * Each device may have multiple "modes", e.g. a flip-phone that can be + * open or shut. Each mode has different configurations for the visible + * displays and active keys. + */ +class PhoneData { +public: + PhoneData(void) : + mName(NULL), mTitle(NULL), mDirectory(NULL) + {} + virtual ~PhoneData(void) { + delete[] mName; + delete[] mTitle; + delete[] mDirectory; + } + + PhoneData(const PhoneData& src) + : mName(NULL), mTitle(NULL), mDirectory(NULL) + { + CopyMembers(src); + } + PhoneData& operator=(const PhoneData& src) { + if (this != &src) // self-assignment + CopyMembers(src); + return *this; + } + void CopyMembers(const PhoneData& src) { + delete[] mName; + delete[] mTitle; + delete[] mDirectory; + mName = android::strdupNew(src.mName); + mTitle = android::strdupNew(src.mTitle); + mDirectory = android::strdupNew(src.mDirectory); + mModeList = src.mModeList; + mDisplayList = src.mDisplayList; + mKeyboardList = src.mKeyboardList; + } + + // initialize the object with the phone data in the specified dir + bool Create(const char* directory); + + // load or unload resources, e.g. wxBitmaps from image files + bool LoadResources(void); + bool UnloadResources(void); + + // simple accessors + const char* GetName(void) const { return mName; } + void SetName(const char* name) { + delete[] mName; + mName = android::strdupNew(name); + } + const char* GetTitle(void) const { return mTitle; } + void SetTitle(const char* title) { + delete[] mTitle; + mTitle = android::strdupNew(title); + } + const char* GetDirectory(void) const { return mDirectory; } + void SetDirectory(const char* dir) { + delete[] mDirectory; + mDirectory = android::strdupNew(dir); + } + + + // get number of modes + int GetNumModes(void) const { return mModeList.size(); } + // get the specified mode object + PhoneMode* GetPhoneMode(int idx); + PhoneMode* GetPhoneMode(const char* modeName); + + // get number of displays + int GetNumDisplays(void) const { return mDisplayList.size(); } + // get the specified display object + PhoneDisplay* GetPhoneDisplay(int idx); + PhoneDisplay* GetPhoneDisplay(const char* displayName); + // get the index of the matching display + int GetPhoneDisplayIndex(const char* displayName); + + // get number of keyboards + int GetNumKeyboards(void) const { return mKeyboardList.size(); } + // get the specified display object + PhoneKeyboard* GetPhoneKeyboard(int idx); + +private: + bool ProcessAndValidate(TiXmlDocument* pDoc); + bool ProcessDevice(TiXmlNode* pNode); + bool ProcessTitle(TiXmlNode* pNode); + + char* mName; + char* mTitle; + char* mDirectory; + + android::List<PhoneMode> mModeList; + android::List<PhoneDisplay> mDisplayList; + android::List<PhoneKeyboard> mKeyboardList; +}; + +#endif // _SIM_PHONE_DATA_H diff --git a/simulator/app/PhoneWindow.cpp b/simulator/app/PhoneWindow.cpp new file mode 100644 index 000000000..60a98090c --- /dev/null +++ b/simulator/app/PhoneWindow.cpp @@ -0,0 +1,1051 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Displays the phone image and handles user input. +// + +// 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 "LinuxKeys.h" +#include "PhoneWindow.h" +#include "DeviceWindow.h" +#include "PhoneData.h" +#include "PhoneCollection.h" +#include "MainFrame.h" +#include "MyApp.h" + +using namespace android; + +BEGIN_EVENT_TABLE(PhoneWindow, wxWindow) // NOT wxDialog + EVT_ACTIVATE(PhoneWindow::OnActivate) + //EVT_ACTIVATE_APP(PhoneWindow::OnActivate) + EVT_CLOSE(PhoneWindow::OnClose) + EVT_MOVE(PhoneWindow::OnMove) + EVT_ERASE_BACKGROUND(PhoneWindow::OnErase) + EVT_PAINT(PhoneWindow::OnPaint) + + EVT_KEY_DOWN(PhoneWindow::OnKeyDown) + EVT_KEY_UP(PhoneWindow::OnKeyUp) + EVT_LEFT_DOWN(PhoneWindow::OnMouseLeftDown) + EVT_LEFT_DCLICK(PhoneWindow::OnMouseLeftDown) + EVT_LEFT_UP(PhoneWindow::OnMouseLeftUp) + EVT_RIGHT_DOWN(PhoneWindow::OnMouseRightDown) + EVT_RIGHT_DCLICK(PhoneWindow::OnMouseRightDown) + EVT_RIGHT_UP(PhoneWindow::OnMouseRightUp) + EVT_MOTION(PhoneWindow::OnMouseMotion) + EVT_LEAVE_WINDOW(PhoneWindow::OnMouseLeaveWindow) + EVT_TIMER(kVibrateTimerId, PhoneWindow::OnTimer) +END_EVENT_TABLE() + + +/* + * Create a new PhoneWindow. This should be a child of the main frame. + */ +PhoneWindow::PhoneWindow(wxWindow* parent, const wxPoint& posn) + : wxDialog(parent, wxID_ANY, wxT("Device"), posn, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE), + mpMOHViewIndex(-1), + mpMOHButton(NULL), + mMouseKeySent(kKeyCodeUnknown), + mpViewInfo(NULL), + mNumViewInfo(0), + mpDeviceWindow(NULL), + mNumDeviceWindows(0), + mPhoneModel(-1), + mCurrentMode(wxT("(unknown)")), + mPlacementChecked(false), + mpParent((MainFrame*)parent), + mTimer(this, kVibrateTimerId), + mTrackingTouch(false) +{ + SetBackgroundColour(*wxLIGHT_GREY); + SetBackgroundStyle(wxBG_STYLE_CUSTOM); + + //SetCursor(wxCursor(wxCURSOR_HAND)); // a bit distracting (pg.276) +} + +/* + * Destroy everything we own. + * + * This might be called well after we've been closed and another + * PhoneWindow has been created, because wxWidgets likes to defer things. + */ +PhoneWindow::~PhoneWindow(void) +{ + //printf("--- ~PhoneWindow %p\n", this); + delete[] mpViewInfo; + if (mpDeviceWindow != NULL) { + for (int i = 0; i < mNumDeviceWindows; i++) { + /* make sure they don't try to use our member */ + mpDeviceWindow[i]->DeviceManagerClosing(); + /* make sure the child window gets destroyed -- not necessary? */ + mpDeviceWindow[i]->Destroy(); + } + + /* delete our array of pointers */ + delete[] mpDeviceWindow; + } +} + +/* + * Check for an updated runtime when window becomes active + */ +void PhoneWindow::OnActivate(wxActivateEvent& event) +{ + /* + * DO NOT do this. Under Windows, it causes the parent window to get + * an activate event, which causes our parent to get the focus. With + * this bit of code active it is impossible for the phone window to + * receive user input. + */ + //GetParent()->AddPendingEvent(event); + + // If we are being deactivated, go ahead and send key up events so that the + // runtime doesn't think we are holding down the key. Issue #685750 + if (!event.GetActive()) { + ListIter iter; + for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ) { + KeyCode keyCode = (*iter).GetKeyCode(); + GetDeviceManager()->SendKeyEvent(keyCode, false); + iter = mPressedKeys.erase(iter); + } + } +} + +/* + * Close the phone window. + */ +void PhoneWindow::OnClose(wxCloseEvent& event) +{ + //printf("--- PhoneWindow::OnClose %p\n", this); +#if 0 + if (mDeviceManager.IsRunning() && !mDeviceManager.IsKillable()) { + printf("Sim: refusing to close window on external runtime\n"); + event.Veto(); + return; + } +#endif + + wxRect rect = GetRect(); + printf("Sim: Closing phone window (posn=(%d,%d))\n", rect.x, rect.y); + + /* notify others */ + mpParent->PhoneWindowClosing(rect.x, rect.y); + mDeviceManager.WindowsClosing(); + + /* end it all */ + Destroy(); +} + +/* + * Prep the PhoneWindow to display a specific phone model. Pass in the + * model index. + * + * This gets called whenever the display changes. This could be a new + * device with identical characteristics, or a different mode for the same + * device. + * + * The window can be re-used so long as the display characteristics are + * the same. If the display characteristics are different, we have to + * restart the device. + */ +bool PhoneWindow::Setup(int phoneIdx) +{ + wxString fileName; + PhoneCollection* pCollection = PhoneCollection::GetInstance(); + + if (phoneIdx < 0 || phoneIdx >= pCollection->GetPhoneCount()) { + fprintf(stderr, "Bogus phone index %d\n", phoneIdx); + return false; + } + + /* + * Clear these out so that failure here is noticeable to caller. We + * regenerate the ViewInfo array every time, because the set of Views + * is different for every Mode. + */ + delete[] mpViewInfo; + mpViewInfo = NULL; + mNumViewInfo = -1; + + PhoneData* pPhoneData; + PhoneMode* pPhoneMode; + PhoneView* pPhoneView; + + pPhoneData = pCollection->GetPhoneData(phoneIdx); + + pPhoneMode = pPhoneData->GetPhoneMode(GetCurrentMode().ToAscii()); + if (pPhoneMode == NULL) { + fprintf(stderr, "current mode (%s) not known\n", + (const char*) GetCurrentMode().ToAscii()); + return false; + } + + int numViews = pPhoneMode->GetNumViews(); + if (numViews == 0) { + fprintf(stderr, "Phone %d mode %s has no views\n", + phoneIdx, pPhoneMode->GetName()); + return false; + } + + const int kBorder = 2; + int i; + int maxHeight = 0; + int fullWidth = kBorder; + ViewInfo* pViewInfo; + + pViewInfo = new ViewInfo[numViews]; + + /* figure out individual and overall dimensions */ + for (i = 0; i < numViews; i++) { + pPhoneView = pPhoneMode->GetPhoneView(i); + if (pPhoneView == NULL) { + fprintf(stderr, "view %d not found\n", i); + return false; + } + + if (!GetDimensions(pPhoneData, pPhoneView, &pViewInfo[i])) + return false; + + if (maxHeight < pViewInfo[i].GetHeight()) + maxHeight = pViewInfo[i].GetHeight(); + fullWidth += pViewInfo[i].GetWidth() + kBorder; + } + + /* create the device windows if we don't already have them */ + if (mpDeviceWindow == NULL) { + mNumDeviceWindows = pPhoneData->GetNumDisplays(); + mpDeviceWindow = new DeviceWindow*[mNumDeviceWindows]; + if (mpDeviceWindow == NULL) + return false; + + for (i = 0; i < mNumDeviceWindows; i++) { + mpDeviceWindow[i] = new DeviceWindow(this, &mDeviceManager); + } + } else { + assert(pPhoneData->GetNumDisplays() == mNumDeviceWindows); + } + + /* + * Position device windows within their views, taking into account + * border areas. + */ + int shift = kBorder; + for (i = 0; i < numViews; i++) { + int displayIdx; + PhoneDisplay* pPhoneDisplay; + + displayIdx = pViewInfo[i].GetDisplayIndex(); + pPhoneDisplay = pPhoneData->GetPhoneDisplay(displayIdx); + //printf("View %d: display %d\n", i, displayIdx); + + pViewInfo[i].SetX(shift); + pViewInfo[i].SetY(kBorder); + + mpDeviceWindow[displayIdx]->SetSize( + pViewInfo[i].GetX() + pViewInfo[i].GetDisplayX(), + pViewInfo[i].GetY() + pViewInfo[i].GetDisplayY(), + pPhoneDisplay->GetWidth(), pPhoneDisplay->GetHeight()); + + // incr by width of view + shift += pViewInfo[i].GetWidth() + kBorder; + } + + /* configure the device manager if it's not already running */ + if (!mDeviceManager.IsInitialized()) { + mDeviceManager.Init(pPhoneData->GetNumDisplays(), mpParent); + + for (i = 0; i < pPhoneData->GetNumDisplays(); i++) { + PhoneDisplay* pPhoneDisplay; + bool res; + + pPhoneDisplay = pPhoneData->GetPhoneDisplay(i); + + res = mDeviceManager.SetDisplayConfig(i, mpDeviceWindow[i], + pPhoneDisplay->GetWidth(), pPhoneDisplay->GetHeight(), + pPhoneDisplay->GetFormat(), pPhoneDisplay->GetRefresh()); + if (!res) { + fprintf(stderr, "Sim: ERROR: could not configure device mgr\n"); + return false; + } + } + const char *kmap = pPhoneData->GetPhoneKeyboard(0)->getKeyMap(); + mDeviceManager.SetKeyboardConfig(kmap); + } else { + assert(pPhoneData->GetNumDisplays() == mDeviceManager.GetNumDisplays()); + } + + /* + * Success. Finish up. + */ + mPhoneModel = phoneIdx; + mpViewInfo = pViewInfo; + mNumViewInfo = numViews; + + /* set up our window */ + SetClientSize(fullWidth, maxHeight + kBorder * 2); + SetBackgroundColour(*wxLIGHT_GREY); + //SetBackgroundColour(*wxBLUE); + SetTitle(wxString::FromAscii(pPhoneData->GetTitle())); + + SetFocus(); // set keyboard input focus + + return true; +} + +/* + * The device table has been reloaded. We need to throw out any pointers + * we had into it and possibly reload some stuff. + */ +void PhoneWindow::DevicesRescanned(void) +{ + mpMOHButton = NULL; + mpMOHViewIndex = -1; + + /* + * Re-evaluate phone definition. There is an implicit assumption + * that the re-scanned version is compatible with the previous + * version (i.e. it still exists and has the same screen size). + * + * We're also currently assuming that no phone definitions have been + * added or removed, which is bad -- we should get the new index for + * for phone by searching for it by name. + * + * TODO: don't make these assumptions. + */ + Setup(mPhoneModel); +} + +/* + * Check the initial placement of the window. We get one of these messages + * when the window is first placed, and every time it's moved thereafter. + * + * Right now we're just trying to make sure wxWidgets doesn't shove it off + * the top of the screen under Linux. Might want to change this to + * remember the previous placement and put the window back. + */ +void PhoneWindow::OnMove(wxMoveEvent& event) +{ + if (mPlacementChecked) + return; + + wxPoint point; + point = event.GetPosition(); + if (point.y < 0) { + printf("Sim: window is at (%d,%d), adjusting\n", point.x, point.y); + point.y = 0; + Move(point); + } + + mPlacementChecked = true; +} + +/* + * Figure out the dimensions required to contain the specified view. + * + * This is usually the size of the background image, but if we can't + * load it or it's too small just create a trivial window. + */ +bool PhoneWindow::GetDimensions(PhoneData* pPhoneData, PhoneView* pPhoneView, + ViewInfo* pInfo) +{ + PhoneDisplay* pPhoneDisplay; + int xoff=0, yoff=0, width, height; + int displayIdx; + + displayIdx = pPhoneData->GetPhoneDisplayIndex(pPhoneView->GetDisplayName()); + if (displayIdx < 0) + return false; + + pPhoneDisplay = pPhoneData->GetPhoneDisplay(displayIdx); + if (pPhoneDisplay == NULL) { + fprintf(stderr, "display '%s' not found in device '%s'\n", + pPhoneView->GetDisplayName(), pPhoneData->GetName()); + return false; + } + + // load images for this phone + (void) pPhoneView->LoadResources(); + + width = height = 0; + + // by convention, the background bitmap is the first image in the list + if (pPhoneView->GetBkgImageCount() > 0) { + wxBitmap* pBitmap = pPhoneView->GetBkgImage(0)->GetBitmap(); + if (pBitmap != NULL) { + // size window to match bitmap + xoff = pPhoneView->GetXOffset(); + yoff = pPhoneView->GetYOffset(); + width = pBitmap->GetWidth(); + height = pBitmap->GetHeight(); + } + } + + // no bitmap, or bitmap is smaller than display + if (width < pPhoneDisplay->GetWidth() || + height < pPhoneDisplay->GetHeight()) + { + // create window to just hold display + xoff = yoff = 0; + width = pPhoneDisplay->GetWidth(); + height = pPhoneDisplay->GetHeight(); + } + if (width <= 0 || height <= 0) { + fprintf(stderr, "ERROR: couldn't determine display size\n"); + return false; + } + + pInfo->SetX(0); + pInfo->SetY(0); // another function determines these + pInfo->SetDisplayX(xoff); + pInfo->SetDisplayY(yoff); + pInfo->SetWidth(width); + pInfo->SetHeight(height); + pInfo->SetDisplayIndex(displayIdx); + + //printf("xoff=%d yoff=%d width=%d height=%d index=%d\n", + // pInfo->GetDisplayX(), pInfo->GetDisplayY(), + // pInfo->GetWidth(), pInfo->GetHeight(), pInfo->GetDisplayIndex()); + + return true; +} + +/* + * Return PhoneData pointer for the current phone model. + */ +PhoneData* PhoneWindow::GetPhoneData(void) const +{ + PhoneCollection* pCollection = PhoneCollection::GetInstance(); + return pCollection->GetPhoneData(mPhoneModel); +} + +/* + * Convert a wxWidgets key code into a device key code. + * + * Someday we may want to make this configurable. + * + * NOTE: we need to create a mapping between simulator key and desired + * function. The "return" key should always mean "select", whether + * it's a "select" button or pressing in on the d-pad. Ditto for + * the arrow keys, whether we have a joystick, d-pad, or four buttons. + * Each key here should have a set of things that it could possibly be, + * and we match it up with the set of buttons actually defined for the + * phone. [for convenience, need to ensure that buttons need not have + * an associated image] + */ +int PhoneWindow::ConvertKeyCode(int wxKeyCode) const +{ + switch (wxKeyCode) { + case WXK_NUMPAD_INSERT: + case WXK_NUMPAD0: + case '0': return KEY_0; + case WXK_NUMPAD_HOME: + case WXK_NUMPAD1: + case '1': return KEY_1; + case WXK_NUMPAD_UP: + case WXK_NUMPAD2: + case '2': return KEY_2; + case WXK_NUMPAD_PRIOR: + case WXK_NUMPAD3: + case '3': return KEY_3; + case WXK_NUMPAD_LEFT: + case WXK_NUMPAD4: + case '4': return KEY_4; + case WXK_NUMPAD_BEGIN: + case WXK_NUMPAD5: + case '5': return KEY_5; + case WXK_NUMPAD_RIGHT: + case WXK_NUMPAD6: + case '6': return KEY_6; + case WXK_NUMPAD_END: + case WXK_NUMPAD7: + case '7': return KEY_7; + case WXK_NUMPAD_DOWN: + case WXK_NUMPAD8: + case '8': return KEY_8; + case WXK_NUMPAD_NEXT: + case WXK_NUMPAD9: + case '9': return KEY_9; + case WXK_NUMPAD_MULTIPLY: return KEY_SWITCHVIDEOMODE; //kKeyCodeStar; + case WXK_LEFT: return KEY_LEFT; + case WXK_RIGHT: return KEY_RIGHT; + case WXK_UP: return KEY_UP; + case WXK_DOWN: return KEY_DOWN; + case WXK_NUMPAD_ENTER: return KEY_REPLY; //kKeyCodeDpadCenter; + case WXK_HOME: return KEY_HOME; + case WXK_PRIOR: + case WXK_PAGEUP: return KEY_MENU; //kKeyCodeSoftLeft; + case WXK_NEXT: + case WXK_PAGEDOWN: return KEY_KBDILLUMUP; //kKeyCodeSoftRight; + case WXK_DELETE: + case WXK_BACK: return KEY_BACKSPACE; //kKeyCodeDel; + case WXK_ESCAPE: + case WXK_END: return KEY_BACK; //kKeyCodeBack; + case WXK_NUMPAD_DELETE: + case WXK_NUMPAD_DECIMAL: return KEY_KBDILLUMTOGGLE; //kKeyCodePound; + case WXK_SPACE: return KEY_SPACE; //kKeyCodeSpace; + case WXK_RETURN: return KEY_ENTER; //kKeyCodeNewline; + case WXK_F3: return KEY_F3; //kKeyCodeCall; + case WXK_F4: return KEY_F4; //kKeyCodeEndCall; + case WXK_NUMPAD_ADD: + case WXK_F5: return KEY_VOLUMEUP; + case WXK_NUMPAD_SUBTRACT: + case WXK_F6: return KEY_VOLUMEDOWN; + case WXK_F7: return KEY_POWER; + case WXK_F8: return KEY_CAMERA; + case 'A': return KEY_A; + case 'B': return KEY_B; + case 'C': return KEY_C; + case 'D': return KEY_D; + case 'E': return KEY_E; + case 'F': return KEY_F; + case 'G': return KEY_G; + case 'H': return KEY_H; + case 'I': return KEY_I; + case 'J': return KEY_J; + case 'K': return KEY_K; + case 'L': return KEY_L; + case 'M': return KEY_M; + case 'N': return KEY_N; + case 'O': return KEY_O; + case 'P': return KEY_P; + case 'Q': return KEY_Q; + case 'R': return KEY_R; + case 'S': return KEY_S; + case 'T': return KEY_T; + case 'U': return KEY_U; + case 'V': return KEY_V; + case 'W': return KEY_W; + case 'X': return KEY_X; + case 'Y': return KEY_Y; + case 'Z': return KEY_Z; + case ',': return KEY_COMMA; + case '.': return KEY_DOT; + case '<': return KEY_COMMA; + case '>': return KEY_DOT; + case '`': return KEY_GREEN; /*KEY_GRAVE;*/ + case '-': return KEY_MINUS; + case '=': return KEY_EQUAL; + case '[': return KEY_LEFTBRACE; + case ']': return KEY_RIGHTBRACE; + case '\\': return KEY_BACKSLASH; + case ';': return KEY_SEMICOLON; + case '\'': return KEY_APOSTROPHE; + case '/': return KEY_SLASH; + case WXK_SHIFT: return KEY_LEFTSHIFT; + case WXK_CONTROL: + case WXK_ALT: return KEY_LEFTALT; + case WXK_TAB: return KEY_TAB; + // don't show "ignoring key" message for these + case WXK_MENU: + break; + default: + printf("(ignoring key %d)\n", wxKeyCode); + break; + } + + return kKeyCodeUnknown; +} + + +/* + * Keyboard handling. These get converted into Android-defined key + * constants here. + * + * NOTE: would be nice to handle menu keyboard accelerators here. + * Simply stuffing the key events into MainFrame with AddPendingEvent + * didn't seem to do the trick. + */ +void PhoneWindow::OnKeyDown(wxKeyEvent& event) +{ + KeyCode keyCode; + + keyCode = (KeyCode) ConvertKeyCode(event.GetKeyCode()); + if (keyCode != kKeyCodeUnknown) { + if (!IsKeyPressed(keyCode)) { + //printf("PW: down: key %d\n", keyCode); + GetDeviceManager()->SendKeyEvent(keyCode, true); + AddPressedKey(keyCode); + } + } else { + //printf("PW: down: %d\n", event.GetKeyCode()); + event.Skip(); // not handled by us + } +} + +/* + * Pass key-up events to runtime. + */ +void PhoneWindow::OnKeyUp(wxKeyEvent& event) +{ + KeyCode keyCode; + + keyCode = (KeyCode) ConvertKeyCode(event.GetKeyCode()); + if (keyCode != kKeyCodeUnknown) { + // Send the key event if we already have this key pressed. + if (IsKeyPressed(keyCode)) { + //printf("PW: up: key %d\n", keyCode); + GetDeviceManager()->SendKeyEvent(keyCode, false); + RemovePressedKey(keyCode); + } + } else { + //printf("PW: up: %d\n", event.GetKeyCode()); + event.Skip(); // not handled by us + } +} + +/* + * Mouse handling. + * + * Unlike more conventional button tracking, we highlight on mouse-over + * and send the key on mouse-down. This behavior may be confusing for + * people expecting standard behavior, but it allows us to simulate the + * effect of holding a key down. + * + * We want to catch both "down" and "double click" events; otherwise + * fast clicking results in a lot of discarded events. + */ +void PhoneWindow::OnMouseLeftDown(wxMouseEvent& event) +{ + if (mpMOHButton != NULL) { + //printf("PW: left down\n"); + KeyCode keyCode = mpMOHButton->GetKeyCode(); + GetDeviceManager()->SendKeyEvent(keyCode, true); + mMouseKeySent = keyCode; + AddPressedKey(keyCode); + } else { + int screenX, screenY; + + if (GetTouchPosition(event, &screenX, &screenY)) { + //printf("TOUCH at %d,%d\n", screenX, screenY); + mTrackingTouch = true; + mTouchX = screenX; + mTouchY = screenY; + GetDeviceManager()->SendTouchEvent(Simulator::kTouchDown, + mTouchX, mTouchY); + } else { + //printf("(ignoring left click)\n"); + } + } +} + +/* + * Left button has been released. Do something clever. + * + * On some platforms we will lose this if the mouse leaves the window. + */ +void PhoneWindow::OnMouseLeftUp(wxMouseEvent& WXUNUSED(event)) +{ + if (mMouseKeySent != kKeyCodeUnknown) { + //printf("PW: left up\n"); + GetDeviceManager()->SendKeyEvent(mMouseKeySent, false); + RemovePressedKey(mMouseKeySent); + } else { + if (mTrackingTouch) { + //printf("TOUCH release (last was %d,%d)\n", mTouchX, mTouchY); + mTrackingTouch = false; + GetDeviceManager()->SendTouchEvent(Simulator::kTouchUp, + mTouchX, mTouchY); + } else { + //printf("(ignoring left-up)\n"); + } + } + mMouseKeySent = kKeyCodeUnknown; +} + +void PhoneWindow::OnMouseRightDown(wxMouseEvent& event) +{ + //printf("(ignoring right-down)\n"); +} +void PhoneWindow::OnMouseRightUp(wxMouseEvent& event) +{ + //printf("(ignoring right-up)\n"); +} + +/* + * Track mouse motion so we can do mouse-over button highlighting. + */ +void PhoneWindow::OnMouseMotion(wxMouseEvent& event) +{ + /* + * If the mouse motion event occurred inside the device window, + * we treat it differently than mouse movement over the picture of + * the device. + */ + if (event.GetEventObject() == mpDeviceWindow[0]) { + if (mpMOHViewIndex >= 0) { + /* can happen if the mouse moves fast enough */ + //printf("Mouse now in dev window, clearing button highlight\n"); + mpMOHViewIndex = -1; + mpMOHButton = NULL; + Refresh(); + } + + if (!event.LeftIsDown() && event.RightIsDown()) { + /* right-button movement */ + //printf("(ignoring right-drag)\n"); + return; + } + + //printf("moveto: %d,%d\n", event.m_x, event.m_y); + + int screenX, screenY; + if (mTrackingTouch) { + if (GetTouchPosition(event, &screenX, &screenY)) { + //printf("TOUCH moved to %d,%d\n", screenX, screenY); + mTouchX = screenX; + mTouchY = screenY; + GetDeviceManager()->SendTouchEvent(Simulator::kTouchDrag, + mTouchX, mTouchY); + } else { + //printf("TOUCH moved off screen\n"); + } + } + + return; + } + + PhoneData* pPhoneData = GetPhoneData(); + if (pPhoneData == NULL) + return; + + /* + * Check to see if we're on top of a button. If our "on top of + * something" state has changed, force a redraw. + * + * We have to run through the list of Views and check all of the + * buttons in each. + */ + PhoneMode* pMode = pPhoneData->GetPhoneMode(GetCurrentMode().ToAscii()); + if (pMode == NULL) + return; + + int viewIndex = -1; + PhoneButton* pHighlight = NULL; + int i; + + for (i = pMode->GetNumViews()-1; i >= 0; i--) { + PhoneView* pView = pMode->GetPhoneView(i); + assert(pView != NULL); + + /* convert from window-relative to view-relative */ + pHighlight = pView->FindButtonHit(event.m_x - mpViewInfo[i].GetX(), + event.m_y - mpViewInfo[i].GetY()); + if (pHighlight != NULL) { + viewIndex = i; + break; + } + } + + if (viewIndex == mpMOHViewIndex && pHighlight == mpMOHButton) { + /* still hovering over same button */ + } else { + /* mouse has moved, possibly to a new button */ + + mpMOHViewIndex = viewIndex; + mpMOHButton = pHighlight; + + /* force refresh */ + Refresh(); + } +} + +/* + * Mouse has left the building. All keys and mouse buttons up. + * + * We get one of these if the mouse moves over a child window, such as + * our DeviceWindow, so it is not the case that we no longer receive + * key input after getting this event. + */ +void PhoneWindow::OnMouseLeaveWindow(wxMouseEvent& WXUNUSED(event)) +{ + //printf("--- mouse is GONE\n"); + ClearPressedKeys(); +} + +/* + * Determine device touch screen (x,y) based on window position. + * + * Returns "true" if the click corresponds to a location on the display. + * + * TODO: should return display index as well -- currently this only + * supports touch on the main display. + */ +bool PhoneWindow::GetTouchPosition(const wxMouseEvent& event, int* pScreenX, + int* pScreenY) +{ + /* + * If the click came from our device window, treat it as a touch. + */ + if (event.GetEventObject() != mpDeviceWindow[0]) + return false; + + *pScreenX = event.m_x; + *pScreenY = event.m_y; + return true; +} + +/* + * We don't want to erase the background now, because it causes flicker + * under Windows. + */ +void PhoneWindow::OnErase(wxEraseEvent& WXUNUSED(event)) +{ + //printf("erase\n"); +} + +/* + * Paint the phone and any highlighted buttons. + * + * The device output is drawn by DeviceWindow. + */ +void PhoneWindow::OnPaint(wxPaintEvent& WXUNUSED(event)) +{ + int view; + + /* + * Under Mac OS X, the parent window is redrawn every time the child + * window is redrawn. This causes poor performance in the simulator. + * If we're being asked to update a region that corresponds exactly + * to one of the device output windows, skip the redraw. + */ + assert(mpViewInfo != NULL); + for (view = 0; view < mNumViewInfo; view++) { + int displayIndex; + + displayIndex = mpViewInfo[view].GetDisplayIndex(); + assert(displayIndex >= 0); + DeviceWindow* pDeviceWindow = mpDeviceWindow[displayIndex]; + assert(pDeviceWindow != NULL); + + wxRect displayRect = pDeviceWindow->GetRect(); + wxRect updateRect = GetUpdateClientRect(); + + if (displayRect == updateRect) { + //printf("(skipping redraw)\n"); + return; + } + } + + wxBufferedPaintDC dc(this); + + /* + * Erase the background to the currently-specified background color. + */ + wxColour backColor = GetBackgroundColour(); + dc.SetBrush(wxBrush(backColor)); + dc.SetPen(wxPen(backColor, 1)); + wxRect windowRect(wxPoint(0, 0), GetClientSize()); + dc.DrawRectangle(windowRect); + + PhoneData* pPhoneData = GetPhoneData(); + if (pPhoneData == NULL) { + fprintf(stderr, "OnPaint: no phone data\n"); + return; + } + + PhoneMode* pPhoneMode; + PhoneView* pPhoneView; + int numImages; + + pPhoneMode = pPhoneData->GetPhoneMode(GetCurrentMode().ToAscii()); + if (pPhoneMode == NULL) { + fprintf(stderr, "current mode (%s) not known\n", + (const char*) GetCurrentMode().ToAscii()); + return; + } + + for (view = 0; view < pPhoneMode->GetNumViews(); view++) { + pPhoneView = pPhoneMode->GetPhoneView(view); + if (pPhoneView == NULL) { + fprintf(stderr, "view %d not found\n", view); + return; + } + + /* draw background image and "button patches" */ + numImages = pPhoneView->GetBkgImageCount(); + for (int i = 0; i < numImages; i++) { + const LoadableImage* pLimg = pPhoneView->GetBkgImage(i); + wxBitmap* pBitmap = pLimg->GetBitmap(); + if (pBitmap != NULL) + dc.DrawBitmap(*pBitmap, + mpViewInfo[view].GetX() + pLimg->GetX(), + mpViewInfo[view].GetY() + pLimg->GetY(), + TRUE); + } + } + + + /* + * Draw button mouse-over highlight. + * + * Currently we don't do anything different when the button is held down. + */ + if (mpMOHViewIndex >= 0 && mpMOHButton != NULL) { + // button must have graphic, or hit-testing wouldn't have worked + assert(mpMOHButton->GetHighlightedBitmap() != NULL); + dc.DrawBitmap(*mpMOHButton->GetHighlightedBitmap(), + mpViewInfo[mpMOHViewIndex].GetX() + mpMOHButton->GetX(), + mpViewInfo[mpMOHViewIndex].GetY() + mpMOHButton->GetY(), + TRUE); + } + + /* + * Highlight pressed keys. We want to do this in all views, because + * some buttons on the side of the phone might be visible in more + * than one view. + */ + for (view = 0; view < pPhoneMode->GetNumViews(); view++) { + pPhoneView = pPhoneMode->GetPhoneView(view); + assert(pPhoneView != NULL); + + ListIter iter; + for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ++iter) { + KeyCode keyCode; + PhoneButton* pButton; + + keyCode = (*iter).GetKeyCode(); + pButton = pPhoneView->FindButtonByKey(keyCode); + if (pButton != NULL) { + wxBitmap* pBitmap = pButton->GetSelectedBitmap(); + if (pBitmap != NULL) { + dc.DrawBitmap(*pBitmap, + mpViewInfo[view].GetX() + pButton->GetX(), + mpViewInfo[view].GetY() + pButton->GetY(), + TRUE); + } + } + } + } +} + + +/* + * Press a key on the device. + * + * Schedules a screen refresh if the set of held-down keys changes. + */ +void PhoneWindow::AddPressedKey(KeyCode keyCode) +{ + /* + * See if the key is already down. This usually means that the key + * repeat has kicked into gear. It could also mean that we + * missed the key-up event, or the user has hit the same device + * key with both mouse and keyboard. Either way, we don't add it + * a second time. This way, if we did lose a key-up somehow, they + * can "clear" the stuck key by hitting it again. + */ + if (keyCode == kKeyCodeUnknown) { + //printf("--- not adding kKeyCodeUnknown!\n"); + return; + } + + ListIter iter; + for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ++iter) { + if ((*iter).GetKeyCode() == keyCode) + break; + } + if (iter == mPressedKeys.end()) { + KeyInfo newInfo; + newInfo.SetKeyCode(keyCode); + mPressedKeys.push_back(newInfo); + //printf("--- added down=%d\n", keyCode); + Refresh(); // redraw w/ highlight + } else { + //printf("--- already have down=%d\n", keyCode); + } +} + +/* + * Release a key on the device. + * + * Schedules a screen refresh if the set of held-down keys changes. + */ +void PhoneWindow::RemovePressedKey(KeyCode keyCode) +{ + /* + * Release the key. If it's not in the list, we either missed a + * key-down event, or the user used both mouse and keyboard and we + * removed the key when the first device went up. + */ + ListIter iter; + for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ++iter) { + if ((*iter).GetKeyCode() == keyCode) { + mPressedKeys.erase(iter); + //printf("--- removing down=%d\n", keyCode); + Refresh(); // redraw w/o highlight + break; + } + } + if (iter == mPressedKeys.end()) { + //printf("--- didn't find down=%d\n", keyCode); + } +} + +/* + * Clear the set of keys that we think are being held down. + */ +void PhoneWindow::ClearPressedKeys(void) +{ + //printf("--- All keys up (count=%d)\n", mPressedKeys.size()); + + if (!mPressedKeys.empty()) { + ListIter iter = mPressedKeys.begin(); + while (iter != mPressedKeys.end()) { + KeyCode keyCode = (*iter).GetKeyCode(); + GetDeviceManager()->SendKeyEvent(keyCode, false); + iter = mPressedKeys.erase(iter); + } + Refresh(); + } +} + +/* + * Returns "true" if the specified key is currently pressed. + */ +bool PhoneWindow::IsKeyPressed(KeyCode keyCode) +{ + ListIter iter; + for (iter = mPressedKeys.begin(); iter != mPressedKeys.end(); ++iter) { + if ((*iter).GetKeyCode() == keyCode) + return true; + } + return false; +} + +void PhoneWindow::Vibrate(int vibrateOn) +{ + wxRect rect = GetRect(); + if(vibrateOn) + { + mVibrateX = 0; + mTimer.Start(25); // arg is delay in ms + Move(rect.x-2,rect.y); + } + else if(mTimer.IsRunning()) + { + mTimer.Stop(); + if(mVibrateX&1) + Move(rect.x-2,rect.y); + else + Move(rect.x+2,rect.y); + } +} + +void PhoneWindow::OnTimer(wxTimerEvent& event) +{ + wxRect rect = GetRect(); + mVibrateX++; + if(mVibrateX&1) + Move(rect.x+4,rect.y); + else + Move(rect.x-4,rect.y); +} diff --git a/simulator/app/PhoneWindow.h b/simulator/app/PhoneWindow.h new file mode 100644 index 000000000..5eb18000d --- /dev/null +++ b/simulator/app/PhoneWindow.h @@ -0,0 +1,186 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Window with simulated phone. +// +#ifndef _SIM_PHONE_WINDOW_H +#define _SIM_PHONE_WINDOW_H + +#include "PhoneData.h" +#include "DeviceManager.h" +#include "DeviceWindow.h" +#include <ui/KeycodeLabels.h> + +class MainFrame; + +/* + * This window displays the simulated phone views, and handles keyboard and + * mouse input. + * + * If we switch to a different "mode", we may display different "views", + * but the set of "displays" remains the same. (Got that?) + * + * We can't just do these things in the main frame because we can't easily + * grab the keyboard input. + */ +class PhoneWindow : public wxDialog { +public: + PhoneWindow(wxWindow* parent, const wxPoint& posn); + virtual ~PhoneWindow(void); + + /* call this initially, and after a mode change */ + bool Setup(int phoneIdx); + + bool IsReady(void) const { + return (mNumViewInfo > 0 && mpViewInfo != NULL); + } + + PhoneData* GetPhoneData(void) const; + + const wxString& GetCurrentMode(void) const { return mCurrentMode; } + void SetCurrentMode(const wxString& mode) { mCurrentMode = mode; } + void SetCurrentMode(const char* mode) { mCurrentMode = wxString::FromAscii(mode); } + + DeviceManager* GetDeviceManager(void) { return &mDeviceManager; } + + /* this is called when the phone data is reloaded */ + void DevicesRescanned(void); + + void Vibrate(int vibrateOn); + +private: + /* + * Hold some information about the "views" being shown in our window. + */ + class ViewInfo { + public: + ViewInfo(void) + : mX(-1), mY(-1), mDisplayX(-1), mDisplayY(-1), + mWidth(-1), mHeight(-1), mDisplayIndex(-1) + {} + ~ViewInfo(void) {} + + int GetX(void) const { return mX; } + int GetY(void) const { return mY; } + int GetDisplayX(void) const { return mDisplayX; } + int GetDisplayY(void) const { return mDisplayY; } + int GetWidth(void) const { return mWidth; } + int GetHeight(void) const { return mHeight; } + int GetDisplayIndex(void) const { return mDisplayIndex; } + + void SetX(int val) { mX = val; } + void SetY(int val) { mY = val; } + void SetDisplayX(int val) { mDisplayX = val; } + void SetDisplayY(int val) { mDisplayY = val; } + void SetWidth(int val) { mWidth = val; } + void SetHeight(int val) { mHeight = val; } + void SetDisplayIndex(int val) { mDisplayIndex = val; } + + private: + int mX, mY; // view offset within PhoneWindow + int mDisplayX, mDisplayY; // display offset within view + int mWidth, mHeight; // view dimensions + + int mDisplayIndex; // index into mpDeviceWindow + }; + + /* + * Hold information about currently pressed keys. + */ + class KeyInfo { + public: + KeyInfo(void) : mKeyCode(kKeyCodeUnknown) {} + KeyInfo(const KeyInfo& src) { + mKeyCode = src.mKeyCode; + } + ~KeyInfo(void) {} + + KeyInfo& operator=(const KeyInfo& src) { + if (this != &src) { + mKeyCode = src.mKeyCode; + } + return *this; + } + + KeyCode GetKeyCode(void) const { return mKeyCode; } + void SetKeyCode(KeyCode keyCode) { mKeyCode = keyCode; } + + //PhoneButton* GetPhoneButton(void) const { return mpButton; } + //void SetPhoneButton(PhoneButton* pButton) { mpButton = pButton; } + + private: + KeyCode mKeyCode; + //PhoneButton* mpButton; + }; + + void OnActivate(wxActivateEvent& event); + void OnMove(wxMoveEvent& event); + void OnClose(wxCloseEvent& event); + void OnTimer(wxTimerEvent& event); + void OnKeyDown(wxKeyEvent& event); + void OnKeyUp(wxKeyEvent& event); + void OnErase(wxEraseEvent& event); + void OnPaint(wxPaintEvent& WXUNUSED(event)); + void OnMouseLeftDown(wxMouseEvent& event); + void OnMouseLeftUp(wxMouseEvent& event); + void OnMouseRightDown(wxMouseEvent& event); + void OnMouseRightUp(wxMouseEvent& event); + void OnMouseMotion(wxMouseEvent& event); + void OnMouseLeaveWindow(wxMouseEvent& WXUNUSED(event)); + bool GetTouchPosition(const wxMouseEvent& event, int* pScreenX, + int* pScreenY); + + bool GetDimensions(PhoneData* pPhoneData, PhoneView* pPhoneView, + ViewInfo* pDim); + int ConvertKeyCode(int wxKeyCode) const; + + /* press a key on the device */ + void AddPressedKey(KeyCode keyCode); + /* release a key on the device */ + void RemovePressedKey(KeyCode keyCode); + /* "raise" all keys */ + void ClearPressedKeys(void); + /* determine whether a key is down */ + bool IsKeyPressed(KeyCode keyCode); + + /* manage the device runtime */ + DeviceManager mDeviceManager; + + /* button mouse-over highlight handling */ + int mpMOHViewIndex; // mouse is in this view + PhoneButton* mpMOHButton; // over this button + KeyCode mMouseKeySent; // to handle "key up" for mouse button + + /* handle multiple simultaneous key presses */ + android::List<KeyInfo> mPressedKeys; + typedef android::List<KeyInfo>::iterator ListIter; + + /* ViewInfos, 1:1 with PhoneView entries for the current mode */ + ViewInfo* mpViewInfo; // array of view data + int mNumViewInfo; // #of elements in mpViewInfo + + /* DeviceWindows, 1:1 with PhoneDisplay entries for this device */ + DeviceWindow** mpDeviceWindow; // array of pointers to device windows + int mNumDeviceWindows; // #of device windows + + /* state */ + int mPhoneModel; // index into model list + wxString mCurrentMode; + + bool mPlacementChecked; // leave it offscreen if they want + + MainFrame* mpParent; // retain pointer to parent window + + enum { kVibrateTimerId = 1010 }; + wxTimer mTimer; + int mVibrateX; + + /* touchscreen simulation */ + bool mTrackingTouch; + int mTouchX; + int mTouchY; + + DECLARE_EVENT_TABLE() +}; + +#endif // _SIM_PHONE_WINDOW_H diff --git a/simulator/app/Preferences.cpp b/simulator/app/Preferences.cpp new file mode 100644 index 000000000..039f1ffe5 --- /dev/null +++ b/simulator/app/Preferences.cpp @@ -0,0 +1,475 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Preferences file access. +// + +// 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" +# include "wx/string.h" +#endif + +#include "Preferences.h" + +#include "utils.h" +#include "tinyxml.h" + +static const char* kName = "name"; +static const char* kValue = "value"; + + +/* + * Load from a file. + */ +bool Preferences::Load(const char* fileName) +{ + assert(fileName != NULL); + printf("SimPref: reading preferences file '%s'\n", fileName); + + // throw out any existing stuff + delete mpDoc; + + mpDoc = new TiXmlDocument; + if (mpDoc == NULL) + return false; + + if (!mpDoc->LoadFile(fileName)) { + fprintf(stderr, "SimPref: ERROR: failed loading '%s'\n", fileName); + if (mpDoc->ErrorRow() != 0) + fprintf(stderr, " XML: %s (row=%d col=%d)\n", + mpDoc->ErrorDesc(), mpDoc->ErrorRow(), mpDoc->ErrorCol()); + else + fprintf(stderr, " XML: %s\n", mpDoc->ErrorDesc()); + goto fail; + } + + TiXmlNode* pPrefs; + pPrefs = mpDoc->FirstChild("prefs"); + if (pPrefs == NULL) { + fprintf(stderr, "SimPref: ERROR: could not find <prefs> in '%s'\n", + fileName); + goto fail; + } + + // set defaults for anything we haven't set explicitly + SetDefaults(); + + return true; + +fail: + delete mpDoc; + mpDoc = NULL; + return false; +} + +/* + * Save to a file. + */ +bool Preferences::Save(const char* fileName) +{ + assert(fileName != NULL); + + if (mpDoc == NULL) + return false; + + if (!mpDoc->SaveFile(fileName)) { + fprintf(stderr, "SimPref: ERROR: failed saving '%s': %s\n", + fileName, mpDoc->ErrorDesc()); + return false; + } + + mDirty = false; + + return true; +} + +/* + * Create an empty collection of preferences. + */ +bool Preferences::Create(void) +{ + static const char* docBase = + "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<!-- Android device simulator preferences -->\n" + "<!-- This file is updated by the simulator -->\n" + "<prefs>\n" + "</prefs>\n"; + + // throw out any existing stuff + delete mpDoc; + + // alloc and initialize + mpDoc = new TiXmlDocument; + if (mpDoc == NULL) + return false; + + if (!mpDoc->Parse(docBase)) { + fprintf(stderr, "SimPref: bad docBase: %s\n", mpDoc->ErrorDesc()); + return false; + } + + SetDefaults(); + mDirty = true; // should already be, mbut make sure + return true; +} + +/* + * Add default values to XML doc. + * + * This isn't strictly necessary, because the functions that are interested + * in the preferences can set appropriate defaults themselves when the + * "get" function returns "false". However, in some cases a preference + * can be interesting to more than one function, and you either have to + * cut & paste the default value or write a "get default for xxx" function. + * + * We want this to work even if they already have an older config file, so + * this only sets values that don't already exist. + */ +void Preferences::SetDefaults(void) +{ + /* table of default values */ + static const struct { + const char* type; + const char* name; + const char* value; + } kDefault[] = { + { "pref", "auto-power-on", "true" }, + { "pref", "debug", "false" }, + { "pref", "valgrind", "false" }, + { "pref", "check-jni", "true" }, + { "pref", "enable-sound", "true" }, + { "pref", "enable-fake-camera", "true" }, + { "pref", "java-vm", "Dalvik" }, + /* goobuntu dapper needed LD_ASSUME_KERNEL or gdb choked badly */ + { "pref", "ld-assume-kernel", "" /*2.4.19*/ }, + { "pref", "launch-command", + "xterm -geom 80x60+10+10 -sb -title Simulator -e" }, + { "pref", "launch-wrapper-args", "-wait" }, + }; + TiXmlNode* pPrefs; + + assert(mpDoc != NULL); + + pPrefs = mpDoc->FirstChild("prefs"); + + /* + * Look up the name. If it doesn't exist, add it. + */ + for (int i = 0; i < NELEM(kDefault); i++) { + TiXmlNode* pNode = _FindNode(kDefault[i].type, kDefault[i].name); + + if (pNode == NULL) { + TiXmlElement elem(kDefault[i].type); + elem.SetAttribute(kName, kDefault[i].name); + elem.SetAttribute(kValue, kDefault[i].value); + pPrefs->InsertEndChild(elem); + + printf("SimPref: added default <%s> '%s'='%s'\n", + kDefault[i].type, kDefault[i].name, kDefault[i].value); + } else { + printf("SimPref: found existing <%s> '%s'\n", + kDefault[i].type, kDefault[i].name); + } + } +} + +static TiXmlNode* get_next_node(TiXmlNode* pNode) +{ + if (!pNode->NoChildren()) + { + pNode = pNode->FirstChild(); + } + else if (pNode->NoChildren() && + (pNode->NextSibling() == NULL)) + { + pNode = pNode->Parent()->NextSibling(); + } + else + { + pNode = pNode->NextSibling(); + } + return pNode; +} + +/* + * Returns the node with element type and name specified + * + * WARNING: this searches through the tree and returns the first matching + * node. + */ +TiXmlNode* Preferences::_FindNode(const char* type, const char* str) const +{ + assert((type != NULL) && (str != NULL)); + TiXmlNode* pRoot; + TiXmlNode* pNode; + + pRoot = mpDoc->FirstChild("prefs"); + assert(pRoot != NULL); + + for (pNode = pRoot->FirstChild(); pNode != NULL;) + { + if (pNode->Type() != TiXmlNode::ELEMENT || + strcasecmp(pNode->Value(), type) != 0) + { + pNode = get_next_node(pNode); + continue; + } + + TiXmlElement* pElem = pNode->ToElement(); + assert(pElem != NULL); + + const char* name = pElem->Attribute(kName); + + /* 1. If the name is blank, something is wrong with the config file + * 2. If the name matches the passed in string, we found the node + * 3. If the node has children, descend another level + * 4. If there are no children and no siblings of the node, go up a level + * 5. Otherwise, grab the next sibling + */ + if (name == NULL) + { + fprintf(stderr, "WARNING: found <%s> without name\n", type); + continue; + } + else if (strcasecmp(name, str) == 0) + { + return pNode; + } + else + { + pNode = get_next_node(pNode); + } + } + + return NULL; +} + +/* + * Locate the specified preference. + */ +TiXmlNode* Preferences::FindPref(const char* str) const +{ + TiXmlNode* pNode = _FindNode("pref", str); + return pNode; +} + +/* + * Like FindPref(), but returns a TiXmlElement. + */ +TiXmlElement* Preferences::FindPrefElement(const char* str) const +{ + TiXmlNode* pNode; + + pNode = FindPref(str); + if (pNode != NULL) + return pNode->ToElement(); + return NULL; +} + +/* + * Add a new preference entry with a blank entry for value. Returns a + * pointer to the new element. + */ +TiXmlElement* Preferences::AddPref(const char* str) +{ + assert(FindPref(str) == NULL); + + TiXmlNode* pPrefs; + + pPrefs = mpDoc->FirstChild("prefs"); + assert(pPrefs != NULL); + + TiXmlElement elem("pref"); + elem.SetAttribute(kName, str); + elem.SetAttribute(kValue, ""); + pPrefs->InsertEndChild(elem); + + TiXmlNode* pNewPref = FindPref(str); + return pNewPref->ToElement(); +} + +/* + * Remove a node from the tree + */ +bool Preferences::_RemoveNode(TiXmlNode* pNode) +{ + if (pNode == NULL) + return false; + + TiXmlNode* pParent = pNode->Parent(); + if (pParent == NULL) + return false; + + pParent->RemoveChild(pNode); + mDirty = true; + return true; +} + +/* + * Remove a preference entry. + */ +bool Preferences::RemovePref(const char* delName) +{ + return _RemoveNode(FindPref(delName)); +} + +/* + * Test for existence. + */ +bool Preferences::Exists(const char* name) const +{ + TiXmlElement* pElem = FindPrefElement(name); + return (pElem != NULL); +} + +/* + * Internal implemenations for getting values + */ +bool Preferences::_GetBool(TiXmlElement* pElem, bool* pVal) const +{ + if (pElem != NULL) + { + const char* str = pElem->Attribute(kValue); + if (str != NULL) + { + if (strcasecmp(str, "true") == 0) + *pVal = true; + else if (strcasecmp(str, "false") == 0) + *pVal = false; + else + { + printf("SimPref: evaluating as bool name='%s' val='%s'\n", + pElem->Attribute(kName), str); + return false; + } + return true; + } + } + return false; +} + +bool Preferences::_GetInt(TiXmlElement* pElem, int* pInt) const +{ + int val; + if (pElem != NULL && pElem->Attribute(kValue, &val) != NULL) { + *pInt = val; + return true; + } + return false; +} + +bool Preferences::_GetDouble(TiXmlElement* pElem, double* pDouble) const +{ + double val; + if (pElem != NULL && pElem->Attribute(kValue, &val) != NULL) { + *pDouble = val; + return true; + } + return false; +} + +bool Preferences::_GetString(TiXmlElement* pElem, wxString& str) const +{ + const char* val; + if (pElem != NULL) { + val = pElem->Attribute(kValue); + if (val != NULL) { + str = wxString::FromAscii(val); + return true; + } + } + return false; +} + +/* + * Get a value. Do not disturb "*pVal" unless we have something to return. + */ +bool Preferences::GetBool(const char* name, bool* pVal) const +{ + return _GetBool(FindPrefElement(name), pVal); +} + +bool Preferences::GetInt(const char* name, int* pInt) const +{ + return _GetInt(FindPrefElement(name), pInt); +} + +bool Preferences::GetDouble(const char* name, double* pDouble) const +{ + return _GetDouble(FindPrefElement(name), pDouble); +} + +bool Preferences::GetString(const char* name, char** pVal) const +{ + wxString str = wxString::FromAscii(*pVal); + if (_GetString(FindPrefElement(name), str)) + { + *pVal = android::strdupNew(str.ToAscii()); + return true; + } + return false; +} + +bool Preferences::GetString(const char* name, wxString& str) const +{ + return _GetString(FindPrefElement(name), str); +} + +/* + * Set a value. If the preference already exists, and the value hasn't + * changed, don't do anything. This avoids setting the "dirty" flag + * unnecessarily. + */ +void Preferences::SetBool(const char* name, bool val) +{ + bool oldVal; + if (GetBool(name, &oldVal) && val == oldVal) + return; + + SetString(name, val ? "true" : "false"); + mDirty = true; +} + +void Preferences::SetInt(const char* name, int val) +{ + int oldVal; + if (GetInt(name, &oldVal) && val == oldVal) + return; + + TiXmlElement* pElem = FindPrefElement(name); + if (pElem == NULL) + pElem = AddPref(name); + pElem->SetAttribute(kValue, val); + mDirty = true; +} + +void Preferences::SetDouble(const char* name, double val) +{ + double oldVal; + if (GetDouble(name, &oldVal) && val == oldVal) + return; + + TiXmlElement* pElem = FindPrefElement(name); + if (pElem == NULL) + pElem = AddPref(name); + pElem->SetDoubleAttribute(kValue, val); + mDirty = true; +} + +void Preferences::SetString(const char* name, const char* val) +{ + wxString oldVal; + if (GetString(name, /*ref*/oldVal) && strcmp(oldVal.ToAscii(), val) == 0) + return; + + TiXmlElement* pElem = FindPrefElement(name); + if (pElem == NULL) + pElem = AddPref(name); + pElem->SetAttribute(kValue, val); + mDirty = true; +} + + diff --git a/simulator/app/Preferences.h b/simulator/app/Preferences.h new file mode 100644 index 000000000..91c35dec9 --- /dev/null +++ b/simulator/app/Preferences.h @@ -0,0 +1,104 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Preferences file access. +// +#ifndef _SIM_PREFERENCES_H +#define _SIM_PREFERENCES_H + +#include "tinyxml.h" + +/* + * This class provides access to a preferences file. It's possible to + * have more than one instance, though it's probably unwise to have multiple + * objects for the same file on disk. + * + * All value are stored as strings. The class doesn't really try to + * enforce type safety, but it will complain if you try to do something + * nonsensical (like convert "foo" to an integer). + */ +class Preferences { +public: + Preferences(void) : + mpDoc(NULL), mDirty(false) + {} + ~Preferences(void) { + delete mpDoc; + } + + /* load all preferences from a file */ + bool Load(const char* fileName); + + /* save all preferences to a file */ + bool Save(const char* fileName); + + /* create new preferences set (use when file does not exist) */ + bool Create(void); + + /* + * Retrieve a value from the preferences database. + * + * These return "false" if the value was not found or could not be + * converted to the expected type. The value pointed to be the second + * arg is guaranteed to be left undisturbed in this case. + * + * The value set by GetString(const char*, char**) will be set to + * newly-allocated storage that must be freed with "delete[]". This + * is done instead of returning a const char* because it's unclear + * what guarantees TinyXml makes wrt string lifetime (especially in + * a multithreaded environment). + */ + bool GetBool(const char* name, bool* pVal) const; + bool GetInt(const char* name, int* pInt) const; + bool GetDouble(const char* name, double* pDouble) const; + bool GetString(const char* name, char** pVal) const; + bool GetString(const char* name, wxString& str) const; + + /* + * Set a value in the database. + */ + void SetBool(const char* name, bool val); + void SetInt(const char* name, int val); + void SetDouble(const char* name, double val); + void SetString(const char* name, const char* val); + + /* + * Just test for existence. + */ + bool Exists(const char* name) const; + + /* + * Remove a <pref> from the config file. + */ + bool RemovePref(const char* name); + + /* + * Get the value of the "dirty" flag. + */ + bool GetDirty(void) const { return mDirty; } + +private: + /* Implementation of getters */ + bool _GetBool(TiXmlElement* pElem, bool* pVal) const; + bool _GetInt(TiXmlElement* pElem, int* pInt) const; + bool _GetDouble(TiXmlElement* pElem, double* pDouble) const; + bool _GetString(TiXmlElement* pElem, wxString& str) const; + + /* this can be used to generate some defaults */ + void SetDefaults(void); + + /* locate the named preference */ + TiXmlNode* _FindNode(const char* type, const char* name) const; + TiXmlNode* FindPref(const char* str) const; + /* like FindPref, but returns a TiXmlElement */ + TiXmlElement* FindPrefElement(const char* str) const; + /* add a new preference entry */ + TiXmlElement* AddPref(const char* str); + /* removes a node */ + bool _RemoveNode(TiXmlNode* pNode); + + TiXmlDocument* mpDoc; + bool mDirty; +}; + +#endif // _SIM_PREFERENCES_H diff --git a/simulator/app/PrefsDialog.cpp b/simulator/app/PrefsDialog.cpp new file mode 100644 index 000000000..e146a56b9 --- /dev/null +++ b/simulator/app/PrefsDialog.cpp @@ -0,0 +1,292 @@ +// +// Copyright 2005 The Android Open Source Project +// +// 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 "PrefsDialog.h" +#include "Preferences.h" +#include "MyApp.h" +#include "Resource.h" + +BEGIN_EVENT_TABLE(PrefsDialog, wxDialog) +END_EVENT_TABLE() + +/* + * Constructor. + */ +PrefsDialog::PrefsDialog(wxWindow* parent) + : wxDialog(parent, IDD_PREFS, wxT("Preferences"), wxDefaultPosition, + wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ), + mAutoPowerOn(false), + mGammaCorrection(1.0), + mEnableSound(true), + mEnableFakeCamera(true), + mLogLevel(0) +{ + LoadPreferences(); + CreateControls(); +} + +/* + * Destructor. Not much to do. + */ +PrefsDialog::~PrefsDialog() +{ +} + +/* + * Create all of the pages and add them to the notebook. + */ +void PrefsDialog::CreateControls(void) +{ + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + wxBoxSizer* okCancelSizer = new wxBoxSizer(wxHORIZONTAL); + mNotebook.Create(this, wxID_ANY); + wxPanel* page; + + /* pages added to notebook are owned by notebook */ + page = CreateSimulatorPage(&mNotebook); + mNotebook.AddPage(page, wxT("Simulator"), true); // selected page + page = CreateRuntimePage(&mNotebook); + mNotebook.AddPage(page, wxT("Runtime"), false); + + wxButton* cancel = new wxButton(this, wxID_CANCEL, wxT("&Cancel"), + wxDefaultPosition, wxDefaultSize, 0); + okCancelSizer->Add(cancel, 0, wxALL | wxALIGN_RIGHT, kInterSpacing); + + wxButton* ok = new wxButton(this, wxID_OK, wxT("&OK"), + wxDefaultPosition, wxDefaultSize, 0); + okCancelSizer->Add(ok, 0, wxALL | wxALIGN_RIGHT, kInterSpacing); + + mainSizer->Add(&mNotebook, 1, wxEXPAND); + mainSizer->Add(okCancelSizer, 0, wxALIGN_RIGHT); + + SetSizer(mainSizer); + + mainSizer->Fit(this); // shrink-to-fit + mainSizer->SetSizeHints(this); // define minimum size +} + +/* + * Load preferences from config file + */ +void PrefsDialog::LoadPreferences(void) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + + /* + * Load preferences. + */ + mConfigFile = ((MyApp*)wxTheApp)->GetConfigFileName(); + + pPrefs->GetDouble("gamma", &mGammaCorrection); + pPrefs->GetString("debugger", /*ref*/ mDebugger); + pPrefs->GetString("valgrinder", /*ref*/ mValgrinder); + pPrefs->GetBool("auto-power-on", &mAutoPowerOn); + pPrefs->GetBool("enable-sound", &mEnableSound); + pPrefs->GetBool("enable-fake-camera", &mEnableFakeCamera); +} + +/* + * Transfer data from our members to the window controls. + * + * First we have to pull the data out of the preferences database. + * Anything that hasn't already been added with a default value will + * be given a default here, which may or may not match the default + * behavior elsewhere. The best solution to this is to define the + * default when the preferences file is created or read, so that we're + * never left guessing here. + */ +bool PrefsDialog::TransferDataToWindow(void) +{ + /* + * Do standard dialog setup. + */ + wxTextCtrl* configFileName = (wxTextCtrl*) FindWindow(IDC_SPREFS_CONFIG_NAME); + wxTextCtrl* debugger = (wxTextCtrl*) FindWindow(IDC_SPREFS_DEBUGGER); + wxTextCtrl* valgrinder = (wxTextCtrl*) FindWindow(IDC_SPREFS_VALGRINDER); + wxCheckBox* autoPowerOn = (wxCheckBox*) FindWindow(IDC_SPREFS_AUTO_POWER_ON); + wxCheckBox* enableSound = (wxCheckBox*) FindWindow(IDC_RPREFS_ENABLE_SOUND); + wxCheckBox* enableFakeCamera = (wxCheckBox*) FindWindow(IDC_RPREFS_ENABLE_FAKE_CAMERA); + + wxTextCtrl* gamma = (wxTextCtrl*) FindWindow(IDC_RPREFS_GAMMA); + + configFileName->SetValue(mConfigFile); + debugger->SetValue(mDebugger); + valgrinder->SetValue(mValgrinder); + autoPowerOn->SetValue(mAutoPowerOn); + enableSound->SetValue(mEnableSound); + enableFakeCamera->SetValue(mEnableFakeCamera); + + wxString tmpStr; + tmpStr.Printf(wxT("%.3f"), mGammaCorrection); + gamma->SetValue(tmpStr); + + return true; +} + +/* + * Transfer and validate data from the window controls. + * + * This doesn't get called if the user cancels out of the dialog. + */ +bool PrefsDialog::TransferDataFromControls(void) +{ + /* + * Do standard dialog export. + * + * We should error-check all of these. + */ + // configName is read-only, don't need it here + wxTextCtrl* debugger = (wxTextCtrl*) FindWindow(IDC_SPREFS_DEBUGGER); + wxTextCtrl* valgrinder = (wxTextCtrl*) FindWindow(IDC_SPREFS_VALGRINDER); + wxCheckBox* autoPowerOn = (wxCheckBox*) FindWindow(IDC_SPREFS_AUTO_POWER_ON); + wxCheckBox* enableSound = (wxCheckBox*) FindWindow(IDC_RPREFS_ENABLE_SOUND); + wxCheckBox* enableFakeCamera = (wxCheckBox*) FindWindow(IDC_RPREFS_ENABLE_FAKE_CAMERA); + + wxTextCtrl* gamma = (wxTextCtrl*) FindWindow(IDC_RPREFS_GAMMA); + + mDebugger = debugger->GetValue(); + mValgrinder = valgrinder->GetValue(); + mAutoPowerOn = autoPowerOn->GetValue(); + mEnableSound = enableSound->GetValue(); + mEnableFakeCamera = enableFakeCamera->GetValue(); + + wxString tmpStr; + tmpStr = gamma->GetValue(); + bool toDouble = tmpStr.ToDouble(&mGammaCorrection); // returns 0.0 on err; use strtof()? + + if (!toDouble || mGammaCorrection <= 0.0 || mGammaCorrection > 2.0) { + wxMessageBox(wxT("Bad value for gamma -- must be > 0.0 and <= 2.0"), + wxT("Hoser"), wxOK, this); + return false; + } + + return true; +} + +/* + * Transfer preferences to config file + */ +bool PrefsDialog::TransferDataFromWindow(void) +{ + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + assert(pPrefs != NULL); + + /* + * Grab the information from the controls and save in member field + */ + + if (!TransferDataFromControls()) + return false; + + pPrefs->SetString("debugger", mDebugger.ToAscii()); + pPrefs->SetString("valgrinder", mValgrinder.ToAscii()); + pPrefs->SetBool("auto-power-on", mAutoPowerOn); + pPrefs->SetBool("enable-sound", mEnableSound); + pPrefs->SetBool("enable-fake-camera", mEnableFakeCamera); + + pPrefs->SetDouble("gamma", mGammaCorrection); + + return true; +} + + +/* + * Create the Simulator Preferences page. + */ +wxPanel* PrefsDialog::CreateSimulatorPage(wxBookCtrlBase* parent) +{ + wxPanel* panel = new wxPanel(parent); + + wxStaticText* configNameDescr = new wxStaticText(panel, wxID_STATIC, + wxT("Config file:")); + wxTextCtrl* configName = new wxTextCtrl(panel, IDC_SPREFS_CONFIG_NAME, + wxT(""), wxDefaultPosition, wxDefaultSize, wxTE_READONLY); + // make it visibly different; unfortunately this kills scroll, copy&paste + configName->Enable(false); + + wxStaticText* debuggerDescr = new wxStaticText(panel, wxID_STATIC, + wxT("Debugger:")); + wxTextCtrl* debugger = new wxTextCtrl(panel, IDC_SPREFS_DEBUGGER); + + wxStaticText* valgrinderDescr = new wxStaticText(panel, wxID_STATIC, + wxT("Valgrind:")); + wxTextCtrl* valgrinder = new wxTextCtrl(panel, IDC_SPREFS_VALGRINDER); + + wxCheckBox* autoPowerOn = new wxCheckBox(panel, IDC_SPREFS_AUTO_POWER_ON, + wxT("Boot runtime when simulator starts")); + + wxBoxSizer* sizerPanel = new wxBoxSizer(wxVERTICAL); + sizerPanel->Add(kMinWidth, kEdgeSpacing); // forces minimum width + sizerPanel->Add(configNameDescr); + sizerPanel->Add(configName, 0, wxEXPAND); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(debuggerDescr); + sizerPanel->Add(debugger, 0, wxEXPAND); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(valgrinderDescr); + sizerPanel->Add(valgrinder, 0, wxEXPAND); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(autoPowerOn); + sizerPanel->AddSpacer(kInterSpacing); + + wxBoxSizer* horizIndent = new wxBoxSizer(wxHORIZONTAL); + horizIndent->AddSpacer(kEdgeSpacing); + horizIndent->Add(sizerPanel, wxSHAPED); + horizIndent->AddSpacer(kEdgeSpacing); + panel->SetSizer(horizIndent); + + return panel; +} + +/* + * Create the Runtime Preferences page. + */ +wxPanel* PrefsDialog::CreateRuntimePage(wxBookCtrlBase* parent) +{ + wxPanel* panel = new wxPanel(parent); + + wxStaticText* gammaStrDescr = new wxStaticText(panel, wxID_STATIC, + wxT("Gamma correction:")); + wxTextCtrl* gammaStr = new wxTextCtrl(panel, IDC_RPREFS_GAMMA); + + wxBoxSizer* gammaSizer = new wxBoxSizer(wxHORIZONTAL); + gammaSizer->Add(gammaStrDescr, 0, wxALIGN_CENTER_VERTICAL); + gammaSizer->AddSpacer(kInterSpacing); + gammaSizer->Add(gammaStr); + + wxBoxSizer* sizerPanel = new wxBoxSizer(wxVERTICAL); + sizerPanel->Add(kMinWidth, kEdgeSpacing); // forces minimum width + sizerPanel->Add(gammaSizer); + sizerPanel->AddSpacer(kInterSpacing); + + wxCheckBox* enableSound = new wxCheckBox(panel, IDC_RPREFS_ENABLE_SOUND, + wxT("Enable Sound")); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(enableSound); + + wxCheckBox* enableFakeCamera = new wxCheckBox(panel, IDC_RPREFS_ENABLE_FAKE_CAMERA, + wxT("Enable Fake Camera")); + sizerPanel->AddSpacer(kInterSpacing); + sizerPanel->Add(enableFakeCamera); + + wxBoxSizer* horizIndent = new wxBoxSizer(wxHORIZONTAL); + horizIndent->AddSpacer(kEdgeSpacing); + horizIndent->Add(sizerPanel, wxEXPAND); + horizIndent->AddSpacer(kEdgeSpacing); + panel->SetSizer(horizIndent); + + return panel; +} + diff --git a/simulator/app/PrefsDialog.h b/simulator/app/PrefsDialog.h new file mode 100644 index 000000000..250f79e15 --- /dev/null +++ b/simulator/app/PrefsDialog.h @@ -0,0 +1,54 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Prefs modal dialog. +// +#ifndef _SIM_PREFS_DIALOG_H +#define _SIM_PREFS_DIALOG_H + +/* + * Declaration of preferences dialog. This class defines the outer + * wrapper as well as all of the pages. + */ +class PrefsDialog : public wxDialog { + //DECLARE_CLASS(PrefsDialog) // shown in book, but causes link problems + DECLARE_EVENT_TABLE() + +public: + PrefsDialog(wxWindow* parent); + virtual ~PrefsDialog(); + + void CreateControls(void); + + wxString mConfigFile; + +private: + bool TransferDataToWindow(void); + bool TransferDataFromWindow(void); + bool TransferDataFromControls(void); + void LoadPreferences(void); + + wxPanel* CreateSimulatorPage(wxBookCtrlBase* parent); + wxPanel* CreateRuntimePage(wxBookCtrlBase* parent); + + /* main notebook; for aesthetic reasons we may want a Choicebook */ + wxNotebook mNotebook; + + /* Global simulator options */ + wxString mDebugger; + wxString mValgrinder; + bool mAutoPowerOn; + // log window size? + + /* Global runtime options */ + double mGammaCorrection; + bool mEnableSound; + bool mEnableFakeCamera; + int mLogLevel; + + enum { + kMinWidth = 300, // minimum prefs dialog width, in pixels + }; +}; + +#endif // _SIM_PREFS_DIALOG_H diff --git a/simulator/app/PropertyServer.cpp b/simulator/app/PropertyServer.cpp new file mode 100644 index 000000000..ee1765719 --- /dev/null +++ b/simulator/app/PropertyServer.cpp @@ -0,0 +1,465 @@ +// +// Copyright 2007 The Android Open Source Project +// +// Property sever. Mimics behavior provided on the device by init(8) and +// some code built into libc. +// + +// 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 "PropertyServer.h" +#include "MyApp.h" +#include "Preferences.h" +#include "MainFrame.h" +#include "utils.h" + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/un.h> + + +using namespace android; + +const char* PropertyServer::kPropCheckJni = "ro.kernel.android.checkjni"; + +/* + * Destructor. + */ +PropertyServer::~PropertyServer(void) +{ + if (IsRunning()) { + // TODO: cause thread to stop, then Wait for it + } + printf("Sim: in ~PropertyServer()\n"); +} + +/* + * Create and run the thread. + */ +bool PropertyServer::StartThread(void) +{ + if (Create() != wxTHREAD_NO_ERROR) { + fprintf(stderr, "Sim: ERROR: can't create PropertyServer thread\n"); + return false; + } + + Run(); + return true; +} + + +/* + * Clear out the list. + */ +void PropertyServer::ClearProperties(void) +{ + typedef List<Property>::iterator PropIter; + + for (PropIter pi = mPropList.begin(); pi != mPropList.end(); ++pi) { + pi = mPropList.erase(pi); + } +} + +/* + * Set default values for several properties. + */ +void PropertyServer::SetDefaultProperties(void) +{ + static const struct { + const char* key; + const char* value; + } propList[] = { + { "net.bt.name", "Android" }, + { "ro.kernel.mem", "60M" }, + { "ro.kernel.board_sardine.version", "4" }, + { "ro.kernel.console", "null" }, + { "ro.build.id", "engineering" }, + { "ro.build.date", "Wed Nov 28 07:44:14 PST 2007" }, + { "ro.build.date.utc", "1196264654" }, + { "ro.build.type", "eng" }, + { "ro.product.device", "simulator" /*"sooner"*/ }, + { "ro.product.brand", "generic" }, + { "ro.build.user", "fadden" }, + { "ro.build.host", "marathon" }, + { "ro.config.nocheckin", "yes" }, + { "ro.product.manufacturer", "" }, + { "ro.radio.use-ppp", "no" }, + { "ro.FOREGROUND_APP_ADJ", "0" }, + { "ro.VISIBLE_APP_ADJ", "1" }, + { "ro.SECONDARY_SERVER_ADJ", "2" }, + { "ro.HIDDEN_APP_MIN_ADJ", "7" }, + { "ro.CONTENT_PROVIDER_ADJ", "14" }, + { "ro.EMPTY_APP_ADJ", "15" }, + { "ro.FOREGROUND_APP_MEM", "1536" }, + { "ro.VISIBLE_APP_MEM", "2048" }, + { "ro.SECONDARY_SERVER_MEM", "4096" }, + { "ro.HIDDEN_APP_MEM", "8192" }, + { "ro.EMPTY_APP_MEM", "16384" }, + //{ "init.svc.adbd", "running" }, // causes ADB-JDWP + { "init.svc.usbd", "running" }, + { "init.svc.debuggerd", "running" }, + { "init.svc.ril-daemon", "running" }, + { "init.svc.zygote", "running" }, + { "init.svc.runtime", "running" }, + { "init.svc.dbus", "running" }, + { "init.svc.pppd_gprs", "running" }, + { "adb.connected", "0" }, + //{ "use-adb-networking", "1" }, + /* + { "status.battery.state", "Slow" }, + { "status.battery.level", "5" }, + { "status.battery.level_raw", "50" }, + { "status.battery.level_scale", "9" }, + */ + + /* disable the annoying setup wizard */ + { "app.setupwizard.disable", "1" }, + + /* Dalvik options, set by AndroidRuntime */ + { "dalvik.vm.execution-mode", "int:portable" }, + { "dalvik.vm.enableassertions", "all" }, // -ea + { "dalvik.vm.verify-bytecode", "false" }, // -Xverify + { "dalvik.vm.deadlock-predict", "off" }, // -Xdeadlockpredict + { "log.redirect-stdio", "false" }, // -Xlog-stdio + + /* SurfaceFlinger options */ + { "debug.sf.nobootanimation", "1" }, + { "debug.sf.showupdates", "0" }, + { "debug.sf.showcpu", "0" }, + { "debug.sf.showbackground", "0" }, + { "debug.sf.showfps", "0" }, + }; + + for (int i = 0; i < NELEM(propList); i++) + SetProperty(propList[i].key, propList[i].value); + + Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); + bool doCheckJni = false; + + pPrefs->GetBool("check-jni", &doCheckJni); + if (doCheckJni) + SetProperty(kPropCheckJni, "1"); + else + SetProperty(kPropCheckJni, "0"); +} + +/* + * Get the value of a property. + * + * "valueBuf" must hold at least PROPERTY_VALUE_MAX bytes. + * + * Returns "true" if the property was found. + */ +bool PropertyServer::GetProperty(const char* key, char* valueBuf) +{ + typedef List<Property>::iterator PropIter; + + assert(key != NULL); + assert(valueBuf != NULL); + + for (PropIter pi = mPropList.begin(); pi != mPropList.end(); ++pi) { + Property& prop = *pi; + if (strcmp(prop.key, key) == 0) { + if (strlen(prop.value) >= PROPERTY_VALUE_MAX) { + fprintf(stderr, + "GLITCH: properties table holds '%s' '%s' (len=%d)\n", + prop.key, prop.value, strlen(prop.value)); + abort(); + } + assert(strlen(prop.value) < PROPERTY_VALUE_MAX); + strcpy(valueBuf, prop.value); + return true; + } + } + + //printf("Prop: get [%s] not found\n", key); + return false; +} + +/* + * Set the value of a property, replacing it if it already exists. + * + * If "value" is NULL, the property is removed. + * + * If the property is immutable, this returns "false" without doing + * anything. (Not implemented.) + */ +bool PropertyServer::SetProperty(const char* key, const char* value) +{ + typedef List<Property>::iterator PropIter; + + assert(key != NULL); + assert(value != NULL); + + for (PropIter pi = mPropList.begin(); pi != mPropList.end(); ++pi) { + Property& prop = *pi; + if (strcmp(prop.key, key) == 0) { + if (value != NULL) { + //printf("Prop: replacing [%s]: [%s] with [%s]\n", + // prop.key, prop.value, value); + strcpy(prop.value, value); + } else { + //printf("Prop: removing [%s]\n", prop.key); + mPropList.erase(pi); + } + return true; + } + } + + //printf("Prop: adding [%s]: [%s]\n", key, value); + Property tmp; + strcpy(tmp.key, key); + strcpy(tmp.value, value); + mPropList.push_back(tmp); + return true; +} + +/* + * Create a UNIX domain socket, carefully removing it if it already + * exists. + */ +bool PropertyServer::CreateSocket(const char* fileName) +{ + struct stat sb; + bool result = false; + int sock = -1; + int cc; + + cc = stat(fileName, &sb); + if (cc < 0) { + if (errno != ENOENT) { + LOG(LOG_ERROR, "sim-prop", + "Unable to stat '%s' (errno=%d)\n", fileName, errno); + goto bail; + } + } else { + /* don't touch it if it's not a socket */ + if (!(S_ISSOCK(sb.st_mode))) { + LOG(LOG_ERROR, "sim-prop", + "File '%s' exists and is not a socket\n", fileName); + goto bail; + } + + /* remove the cruft */ + if (unlink(fileName) < 0) { + LOG(LOG_ERROR, "sim-prop", + "Unable to remove '%s' (errno=%d)\n", fileName, errno); + goto bail; + } + } + + struct sockaddr_un addr; + + sock = ::socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + LOG(LOG_ERROR, "sim-prop", + "UNIX domain socket create failed (errno=%d)\n", errno); + goto bail; + } + + /* bind the socket; this creates the file on disk */ + strcpy(addr.sun_path, fileName); // max 108 bytes + addr.sun_family = AF_UNIX; + cc = ::bind(sock, (struct sockaddr*) &addr, SUN_LEN(&addr)); + if (cc < 0) { + LOG(LOG_ERROR, "sim", + "AF_UNIX bind failed for '%s' (errno=%d)\n", fileName, errno); + goto bail; + } + + cc = ::listen(sock, 5); + if (cc < 0) { + LOG(LOG_ERROR, "sim", "AF_UNIX listen failed (errno=%d)\n", errno); + goto bail; + } + + mListenSock = sock; + sock = -1; + result = true; + +bail: + if (sock >= 0) + close(sock); + return result; +} + +/* + * Handle a client request. + * + * Returns true on success, false if the fd should be closed. + */ +bool PropertyServer::HandleRequest(int fd) +{ + char reqBuf[PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX]; + char valueBuf[1 + PROPERTY_VALUE_MAX]; + ssize_t actual; + + memset(valueBuf, 'x', sizeof(valueBuf)); // placate valgrind + + /* read the command byte; this determines the message length */ + actual = read(fd, reqBuf, 1); + if (actual <= 0) + return false; + + if (reqBuf[0] == kSystemPropertyGet) { + actual = read(fd, reqBuf, PROPERTY_KEY_MAX); + + if (actual != PROPERTY_KEY_MAX) { + fprintf(stderr, "Bad read on get: %d of %d\n", + actual, PROPERTY_KEY_MAX); + return false; + } + if (GetProperty(reqBuf, valueBuf+1)) + valueBuf[0] = 1; + else + valueBuf[0] = 0; + //printf("GET property [%s]: (found=%d) [%s]\n", + // reqBuf, valueBuf[0], valueBuf+1); + if (write(fd, valueBuf, sizeof(valueBuf)) != sizeof(valueBuf)) { + fprintf(stderr, "Bad write on get\n"); + return false; + } + } else if (reqBuf[0] == kSystemPropertySet) { + actual = read(fd, reqBuf, PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX); + if (actual != PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX) { + fprintf(stderr, "Bad read on set: %d of %d\n", + actual, PROPERTY_KEY_MAX + PROPERTY_VALUE_MAX); + return false; + } + //printf("SET property '%s'\n", reqBuf); + if (SetProperty(reqBuf, reqBuf + PROPERTY_KEY_MAX)) + valueBuf[0] = 1; + else + valueBuf[0] = 0; + if (write(fd, valueBuf, 1) != 1) { + fprintf(stderr, "Bad write on set\n"); + return false; + } + } else if (reqBuf[0] == kSystemPropertyList) { + /* TODO */ + assert(false); + } else { + fprintf(stderr, "Unexpected request %d from prop client\n", reqBuf[0]); + return false; + } + + return true; +} + +/* + * Serve up properties. + */ +void PropertyServer::ServeProperties(void) +{ + typedef List<int>::iterator IntIter; + fd_set readfds; + int maxfd; + + while (true) { + int cc; + + FD_ZERO(&readfds); + FD_SET(mListenSock, &readfds); + maxfd = mListenSock; + + for (IntIter ii = mClientList.begin(); ii != mClientList.end(); ++ii) { + int fd = (*ii); + + FD_SET(fd, &readfds); + if (maxfd < fd) + maxfd = fd; + } + + cc = select(maxfd+1, &readfds, NULL, NULL, NULL); + if (cc < 0) { + if (errno == EINTR) { + printf("hiccup!\n"); + continue; + } + return; + } + if (FD_ISSET(mListenSock, &readfds)) { + struct sockaddr_un from; + socklen_t fromlen; + int newSock; + + fromlen = sizeof(from); + newSock = ::accept(mListenSock, (struct sockaddr*) &from, &fromlen); + if (newSock < 0) { + LOG(LOG_WARN, "sim", + "AF_UNIX accept failed (errno=%d)\n", errno); + } else { + //printf("new props connection on %d --> %d\n", + // mListenSock, newSock); + + mClientList.push_back(newSock); + } + } + + for (IntIter ii = mClientList.begin(); ii != mClientList.end(); ) { + int fd = (*ii); + bool ok = true; + + if (FD_ISSET(fd, &readfds)) { + //printf("--- activity on %d\n", fd); + + ok = HandleRequest(fd); + } + + if (ok) { + ++ii; + } else { + //printf("--- closing %d\n", fd); + close(fd); + ii = mClientList.erase(ii); + } + } + } +} + +/* + * 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* PropertyServer::Entry(void) +{ + if (CreateSocket(SYSTEM_PROPERTY_PIPE_NAME)) { + assert(mListenSock >= 0); + SetDefaultProperties(); + + /* loop until it's time to exit or we fail */ + ServeProperties(); + + ClearProperties(); + + /* + * Close listen socket and all clients. + */ + LOG(LOG_INFO, "sim", "Cleaning up socket list\n"); + typedef List<int>::iterator IntIter; + for (IntIter ii = mClientList.begin(); ii != mClientList.end(); ++ii) + close((*ii)); + close(mListenSock); + } + + LOG(LOG_INFO, "sim", "PropertyServer thread exiting\n"); + return NULL; +} + diff --git a/simulator/app/PropertyServer.h b/simulator/app/PropertyServer.h new file mode 100644 index 000000000..193dd7047 --- /dev/null +++ b/simulator/app/PropertyServer.h @@ -0,0 +1,68 @@ +// +// Copyright 2007 The Android Open Source Project +// +// Serve properties to the simulated runtime. +// +#ifndef _SIM_PROPERTY_SERVER_H +#define _SIM_PROPERTY_SERVER_H + +#include "cutils/properties.h" +#include "utils/List.h" + +/* + * Define a thread that responds to requests from clients to get/set/list + * system properties. + */ +class PropertyServer : public wxThread { +public: + PropertyServer(void) : mListenSock(-1) {} + virtual ~PropertyServer(void); + + /* start the thread running */ + bool StartThread(void); + + /* thread entry point */ + virtual void* Entry(void); + + /* clear out all properties */ + void ClearProperties(void); + + /* add some default values */ + void SetDefaultProperties(void); + + /* copy a property into valueBuf; returns false if property not found */ + bool GetProperty(const char* key, char* valueBuf); + + /* set the property, replacing it if it already exists */ + bool SetProperty(const char* key, const char* value); + + /* property name constants */ + static const char* kPropCheckJni; + +private: + /* one property entry */ + typedef struct Property { + char key[PROPERTY_KEY_MAX]; + char value[PROPERTY_VALUE_MAX]; + } Property; + + /* create the UNIX-domain socket we listen on */ + bool CreateSocket(const char* fileName); + + /* serve up properties */ + void ServeProperties(void); + + /* handle a client request */ + bool HandleRequest(int fd); + + /* listen here for new connections */ + int mListenSock; + + /* list of connected fds to scan */ + android::List<int> mClientList; + + /* set of known properties */ + android::List<Property> mPropList; +}; + +#endif // PROPERTY_SERVER_H diff --git a/simulator/app/README b/simulator/app/README new file mode 100644 index 000000000..5a51f6322 --- /dev/null +++ b/simulator/app/README @@ -0,0 +1,74 @@ +Android Simulator README +Last updated: 14-Nov-2007 + +See "docs/sim-layout-xml.txt" for a description of the layout.xml files +in the device directories. + +The coding conventions here are generally aligned with wxWidgets' style, +which is similar to standard Windows style. The only significant shift +from Android style is that function names are capitalized. Note the +simulator code is not part of the "android" namespace. + + +===== Arguments ===== + +The config file for the simulator, ".android.cf", can live in your +$HOME directory or in $cwd. The copy in $cwd takes priority. If a +config file does not exist, one will be created in your home directory. +(Note that the current directory is set by "runsim.sh", so if you launch +the simulator from the script it will look for the config file in your +"install" directory.) + +The simulator takes the following optional arguments: + + -f <file> : specify the configuration file to use. + + -p <platform> : specify platform information. This is usually + something like "Linux-release" or "CYGWIN_NT-5.1-debug". + + -r : reset paths. This causes the simulator to regenerate the paths + based on defaults. This is useful when copying your .android.cf from + a different system, because it updates all the local paths without + disturbing the other options. + + +===== Preferences Quick Reference ===== + +Path preferences. These are reset by the "-r" flag: + +"debugger" (str) Path to the debugger (usually /usr/bin/gdb). +"valgrinder" (str) Path to valgrind (usually /usr/bin/valgrind). + +Common prefs: + +"auto-power-on" (bool) Automatically start runtime on simulator start. +"debug" (bool) Launch the runtime inside <debugger>. +"valgrind" (bool) Launch the runtime inside <valgrinder>. +"log-*" (various) Preferences for the log window. +"window-*" (int) Positions and sizes of windows. +"default-device" (str) Name of the device that opens initially. +"ld-assume-kernel" (str) Hack to make goobuntu GDB work; use "" to disable. + +Less-common prefs: + +"gamma" (float) Gamma correction factor (default 1.0). +"window-device-x" (int) Position of device window. +"window-device-y" (int) Position of device window. +"trap-sigint" (bool) Catch Ctrl-C. Kill the runtime when we do. +"refocus-on-restart" (bool) When runtime rstarts, give focus to phone window. +"launch-command" (str) Command to run, e.g. "xterm -e" (cmd is appended). +"launch-wrapper-args" (str) Args to launch wrapper, e.g. "-wait -output foo". + + +(If you prefer gnome-terminal to xterm, you can use something like +"gnome-terminal --disable-factory -x". The "disable-factory" arg is +needed to ensure that it inherits the environment variables.) + + +***** NOTE ***** + +If you're using a non-goobuntu system, make sure "ld-assume-kernel" is "" +in your .android.cf. gdb works correctly on Ubuntu 7.04 (fiesty) and 7.10 +(gutsy), and the goobuntu workaround will cause shared library version +failures on startup. + diff --git a/simulator/app/Resource.h b/simulator/app/Resource.h new file mode 100644 index 000000000..160242820 --- /dev/null +++ b/simulator/app/Resource.h @@ -0,0 +1,98 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Resource enumeration. +// +#ifndef _SIM_RESOURCE_H +#define _SIM_RESOURCE_H + +/* + * IDs for dialogs, controls, menu items, and whatever else comes along. + * + * Some standard defs are in "wx/defs.h". They run from 5000 to 6000. + */ +enum { + // common stuff + //ID_ANONYMOUS = -1, // use wxID_ANY(-1) or wxID_STATIC(5105) + + + // Menu item IDs + IDM_FILE_PREFERENCES = 100, + IDM_FILE_EXIT, + + IDM_RUNTIME_START, + IDM_RUNTIME_STOP, + IDM_RUNTIME_RESTART, + IDM_RUNTIME_KILL, + + IDM_DEVICE, + IDM_DEVICE_SEL0, + // leave space; each phone model gets a menu item ID + IDM_DEVICE_SELN = IDM_DEVICE_SEL0 + 32, + IDM_DEVICE_RESCAN, + + IDM_DEBUG_SHOW_LOG, + + IDM_HELP_CONTENTS, + IDM_HELP_ABOUT, + + + // Dialog IDs + IDD_PREFS, + IDD_LOG_PREFS, + + // Control IDs + IDC_MODE_SELECT, // main - combobox + IDC_USE_GDB, // main - checkbox + IDC_USE_VALGRIND, // main - checkbox + IDC_CHECK_JNI, // main - checkbox + IDC_JAVA_APP_NAME, // main - combobox + IDC_JAVA_VM, // main - combobox + IDC_OVERLAY_ONION_SKIN, // main - combobox + IDC_ONION_SKIN_FILE_NAME, // main - textctrl + IDC_ONION_SKIN_BUTTON, // main - button + IDC_ONION_SKIN_ALPHA_VAL, // main - slider + + IDC_SPREFS_CONFIG_NAME, // sim prefs page - textctrl + IDC_SPREFS_DEBUGGER, // sim prefs page - textctrl + IDC_SPREFS_VALGRINDER, // sim prefs page - textctrl + IDC_SPREFS_AUTO_POWER_ON, // sim prefs page - checkbox + + IDC_RPREFS_GAMMA, // runtime prefs page - textctrl + IDC_RPREFS_ENABLE_SOUND, // runtime prefs page - checkbox + IDC_RPREFS_ENABLE_FAKE_CAMERA,// runtime prefs page - checkbox + + IDC_LOG_TEXT, // log window - textctrl + IDC_LOG_LEVEL, // log window - combobox + IDC_LOG_CLEAR, // log window - button + IDC_LOG_PAUSE, // log window - button + IDC_LOG_PREFS, // log window - button + + IDC_LOG_PREFS_FMT_FULL, // log prefs - radio button + IDC_LOG_PREFS_FMT_BRIEF, // log prefs - radio button + IDC_LOG_PREFS_FMT_MINIMAL, // log prefs - radio button + IDC_LOG_PREFS_SINGLE_LINE, // log prefs - checkbox + IDC_LOG_PREFS_EXTRA_SPACING, // log prefs - combobox + IDC_LOG_PREFS_POINT_SIZE, // log prefs - textctrl + IDC_LOG_PREFS_USE_COLOR, // log prefs - checkbox + IDC_LOG_PREFS_FONT_MONO, // log prefs - checkbox + + IDC_LOG_PREFS_DISPLAY_MAX, // log prefs - textctrl + IDC_LOG_PREFS_POOL_SIZE, // log prefs - textctrl + + IDC_LOG_PREFS_WRITE_FILE, // log prefs - checkbox + IDC_LOG_PREFS_FILENAME, // log prefs - textctrl + IDC_LOG_PREFS_TRUNCATE_OLD, // log prefs - textctrl +}; + +/* + * Common definitions for control spacing. + * + * Doesn't really belong here, but it'll do. + */ +enum { + kEdgeSpacing = 4, // padding at edge of prefs pages, in pixels + kInterSpacing = 5, // padding between controls, in pixels +}; + +#endif // _SIM_RESOURCE_H diff --git a/simulator/app/Semaphore.cpp b/simulator/app/Semaphore.cpp new file mode 100644 index 000000000..3b6ee7b1d --- /dev/null +++ b/simulator/app/Semaphore.cpp @@ -0,0 +1,516 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Inter-process semaphores. +// +#include "Semaphore.h" + +#if defined(HAVE_MACOSX_IPC) +# include <semaphore.h> +#elif defined(HAVE_SYSV_IPC) +# include <sys/types.h> +# include <sys/ipc.h> +# include <sys/sem.h> +#elif defined(HAVE_WIN32_IPC) +# include <windows.h> +#elif defined(HAVE_ANDROID_IPC) +// not yet +#else +# error "unknown sem config" +#endif + +#include <utils/Log.h> + +#include <errno.h> +#include <assert.h> + +using namespace android; + + +#if defined(HAVE_ANDROID_IPC) // ---------------------------------------------- + +Semaphore::Semaphore(void) + : mHandle(0), mCreator(false), mKey(-1) +{} + +Semaphore::~Semaphore(void) +{} + +bool Semaphore::create(int key, int initialValue, bool deleteExisting) +{ + return false; +} + +bool Semaphore::attach(int key) +{ + return false; +} + +void Semaphore::acquire(void) +{} + +void Semaphore::release(void) +{} + +bool Semaphore::tryAcquire(void) +{ + return false; +} + +#elif defined(HAVE_MACOSX_IPC) // --------------------------------------------- + +/* + * The SysV semaphores don't work on all of our machines. The POSIX + * named semaphores seem to work better. + */ + +#define kInvalidHandle SEM_FAILED + +static const char* kSemStr = "/tmp/android-sem-"; + +/* + * Constructor. Just init fields. + */ +Semaphore::Semaphore(void) + : mHandle((unsigned long) kInvalidHandle), mCreator(false), mKey(-1) +{ +} + +/* + * Destructor. If we created the semaphore, destroy it. + */ +Semaphore::~Semaphore(void) +{ + LOG(LOG_VERBOSE, "sem", "~Semaphore(handle=%ld creator=%d)\n", + mHandle, mCreator); + + if (mHandle != (unsigned long) kInvalidHandle) { + sem_close((sem_t*) mHandle); + + if (mCreator) { + char nameBuf[64]; + int cc; + + snprintf(nameBuf, sizeof(nameBuf), "%s%d", kSemStr, mKey); + + cc = sem_unlink(nameBuf); + if (cc != 0) { + LOG(LOG_ERROR, "sem", + "Failed to remove sem '%s' (errno=%d)\n", nameBuf, errno); + } + } + } +} + +/* + * Create the semaphore. + */ +bool Semaphore::create(int key, int initialValue, bool deleteExisting) +{ + int cc; + char nameBuf[64]; + + snprintf(nameBuf, sizeof(nameBuf), "%s%d", kSemStr, key); + + if (deleteExisting) { + cc = sem_unlink(nameBuf); + if (cc != 0 && errno != ENOENT) { + LOG(LOG_WARN, "sem", "Warning: failed to remove sem '%s'\n", + nameBuf); + /* keep going? */ + } + } + + /* create and set initial value */ + sem_t* semPtr; + semPtr = sem_open(nameBuf, O_CREAT | O_EXCL, 0666, 1); + if (semPtr == (sem_t*)SEM_FAILED) { + LOG(LOG_ERROR, "sem", + "ERROR: sem_open failed to create '%s' (errno=%d)\n", + nameBuf, errno); + return false; + } + + mHandle = (unsigned long) semPtr; + mCreator = true; + mKey = key; + + return true; +} + +/* + * Attach to an existing semaphore. + */ +bool Semaphore::attach(int key) +{ + char nameBuf[64]; + + snprintf(nameBuf, sizeof(nameBuf), "%s%d", kSemStr, key); + + sem_t* semPtr; + semPtr = sem_open(nameBuf, 0, 0666, 0); + if (semPtr == (sem_t*) SEM_FAILED) { + LOG(LOG_ERROR, "sem", + "ERROR: sem_open failed to attach to '%s' (errno=%d)\n", + nameBuf, errno); + return false; + } + + mHandle = (unsigned long) semPtr; + assert(mCreator == false); + mKey = key; + + return true; +} + +/* + * Acquire or release the semaphore. + */ +void Semaphore::acquire(void) +{ + int cc = sem_wait((sem_t*) mHandle); + if (cc != 0) + LOG(LOG_WARN, "sem", "acquire failed (errno=%d)\n", errno); +} +void Semaphore::release(void) +{ + int cc = sem_post((sem_t*) mHandle); + if (cc != 0) + LOG(LOG_WARN, "sem", "release failed (errno=%d)\n", errno); +} +bool Semaphore::tryAcquire(void) +{ + int cc = sem_trywait((sem_t*) mHandle); + if (cc != 0) { + if (errno != EAGAIN) + LOG(LOG_WARN, "sem", "tryAcquire failed (errno=%d)\n", errno); + return false; + } + return true; +} + + +#elif defined(HAVE_SYSV_IPC) // ----------------------------------------------- + +/* + * Basic SysV semaphore stuff. + */ + +#define kInvalidHandle ((unsigned long)-1) + +#if defined(_SEM_SEMUN_UNDEFINED) +/* according to X/OPEN we have to define it ourselves */ +union semun { + int val; /* value for SETVAL */ + struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */ + unsigned short *array; /* array for GETALL, SETALL */ + /* Linux specific part: */ + struct seminfo *__buf; /* buffer for IPC_INFO */ +}; +#endif + +/* + * Constructor. Just init fields. + */ +Semaphore::Semaphore(void) + : mHandle(kInvalidHandle), mCreator(false) +{ +} + +/* + * Destructor. If we created the semaphore, destroy it. + */ +Semaphore::~Semaphore(void) +{ + LOG(LOG_VERBOSE, "sem", "~Semaphore(handle=%ld creator=%d)\n", + mHandle, mCreator); + + if (mCreator && mHandle != kInvalidHandle) { + int cc; + + cc = semctl((int) mHandle, 0, IPC_RMID); + if (cc != 0) { + LOG(LOG_WARN, "sem", + "Destructor failed to destroy key=%ld\n", mHandle); + } + } +} + +/* + * Create the semaphore. + */ +bool Semaphore::create(int key, int initialValue, bool deleteExisting) +{ + int semid, cc; + + if (deleteExisting) { + semid = semget(key, 1, 0); + if (semid != -1) { + LOG(LOG_DEBUG, "sem", "Key %d exists (semid=%d), removing\n", + key, semid); + cc = semctl(semid, 0, IPC_RMID); + if (cc != 0) { + LOG(LOG_ERROR, "sem", "Failed to remove key=%d semid=%d\n", + key, semid); + return false; + } else { + LOG(LOG_DEBUG, "sem", + "Removed previous semaphore with key=%d\n", key); + } + } + } + + semid = semget(key, 1, 0600 | IPC_CREAT | IPC_EXCL); + if (semid == -1) { + LOG(LOG_ERROR, "sem", "Failed to create key=%d (errno=%d)\n", + key, errno); + return false; + } + + mHandle = semid; + mCreator = true; + mKey = key; + + /* + * Set initial value. + */ + union semun init; + init.val = initialValue; + cc = semctl(semid, 0, SETVAL, init); + if (cc == -1) { + LOG(LOG_ERROR, "sem", + "Unable to initialize semaphore, key=%d iv=%d (errno=%d)\n", + key, initialValue, errno); + return false; + } + + return true; +} + +/* + * Attach to an existing semaphore. + */ +bool Semaphore::attach(int key) +{ + int semid; + + semid = semget(key, 0, 0); + if (semid == -1) { + LOG(LOG_ERROR, "sem", "Failed to find key=%d\n", key); + return false; + } + + mHandle = semid; + assert(mCreator == false); + mKey = key; + + return true; +} + +/* + * Acquire or release the semaphore. + */ +void Semaphore::acquire(void) +{ + assert(mHandle != kInvalidHandle); + adjust(-1, true); +} +void Semaphore::release(void) +{ + assert(mHandle != kInvalidHandle); + adjust(1, true); +} +bool Semaphore::tryAcquire(void) +{ + assert(mHandle != kInvalidHandle); + return adjust(-1, false); +} + +/* + * Do the actual semaphore manipulation. + * + * The semaphore's value indicates the number of free resources. Pass + * in a negative value for "adj" to acquire resources, or a positive + * value to free resources. + * + * Returns true on success, false on failure. + */ +bool Semaphore::adjust(int adj, bool wait) +{ + struct sembuf op; + int cc; + + op.sem_num = 0; + op.sem_op = adj; + op.sem_flg = SEM_UNDO; + if (!wait) + op.sem_flg |= IPC_NOWAIT; + + cc = semop((int) mHandle, &op, 1); + if (cc != 0) { + if (wait || errno != EAGAIN) { + LOG(LOG_WARN, "sem", + "semaphore adjust by %d failed for semid=%ld (errno=%d)\n", + adj, mHandle, errno); + } + return false; + } + + //LOG(LOG_VERBOSE, "sem", + // "adjusted semaphore by %d (semid=%ld)\n", adj, mHandle); + + return true; +} + + +#elif defined(HAVE_WIN32_IPC) // ---------------------------------------------- + +/* + * Win32 semaphore implementation. + * + * Pretty straightforward. + */ + +static const char* kSemStr = "android-sem-"; + +/* + * Constructor. Just init fields. + */ +Semaphore::Semaphore(void) + : mHandle((unsigned long) INVALID_HANDLE_VALUE), mCreator(false) +{ +} + +/* + * Destructor. Just close the semaphore handle. + */ +Semaphore::~Semaphore(void) +{ + LOG(LOG_DEBUG, "sem", "~Semaphore(handle=%ld creator=%d)\n", + mHandle, mCreator); + + if (mHandle != (unsigned long) INVALID_HANDLE_VALUE) + CloseHandle((HANDLE) mHandle); +} + +/* + * Create the semaphore. + */ +bool Semaphore::create(int key, int initialValue, bool deleteExisting) +{ + char keyBuf[64]; + HANDLE hSem; + long max; + + snprintf(keyBuf, sizeof(keyBuf), "%s%d", kSemStr, key); + + if (initialValue == 0) + max = 1; + else + max = initialValue; + + hSem = CreateSemaphore( + NULL, // security attributes + initialValue, // initial count + max, // max count, must be >= initial + keyBuf); // object name + if (hSem == NULL) { + DWORD err = GetLastError(); + if (err == ERROR_ALREADY_EXISTS) { + LOG(LOG_ERROR, "sem", "Semaphore '%s' already exists\n", keyBuf); + } else { + LOG(LOG_ERROR, "sem", "CreateSemaphore(%s) failed (err=%ld)\n", + keyBuf, err); + } + return false; + } + + mHandle = (unsigned long) hSem; + mCreator = true; + mKey = key; + + //LOG(LOG_DEBUG, "sem", "Semaphore '%s' created (handle=0x%08lx)\n", + // keyBuf, mHandle); + + return true; +} + +/* + * Attach to an existing semaphore. + */ +bool Semaphore::attach(int key) +{ + char keyBuf[64]; + HANDLE hSem; + + snprintf(keyBuf, sizeof(keyBuf), "%s%d", kSemStr, key); + + hSem = OpenSemaphore( + //SEMAPHORE_MODIFY_STATE, // mostly-full access + SEMAPHORE_ALL_ACCESS, // full access + FALSE, // don't let kids inherit handle + keyBuf); // object name + if (hSem == NULL) { + LOG(LOG_ERROR, "sem", "OpenSemaphore(%s) failed (err=%ld)\n", + keyBuf, GetLastError()); + return false; + } + + mHandle = (unsigned long) hSem; + assert(mCreator == false); + mKey = key; + + return true; +} + +/* + * Acquire or release the semaphore. + */ +void Semaphore::acquire(void) +{ + DWORD result; + + assert(mHandle != (unsigned long) INVALID_HANDLE_VALUE); + + result = WaitForSingleObject((HANDLE) mHandle, INFINITE); + if (result != WAIT_OBJECT_0) { + LOG(LOG_WARN, "sem", + "WaitForSingleObject(INF) on semaphore returned %ld (err=%ld)\n", + result, GetLastError()); + } +} +void Semaphore::release(void) +{ + DWORD result; + + assert(mHandle != (unsigned long) INVALID_HANDLE_VALUE); + + result = ReleaseSemaphore((HANDLE) mHandle, 1, NULL); // incr by 1 + if (result == 0) { + LOG(LOG_WARN, "sem", "ReleaseSemaphore failed (err=%ld)\n", + GetLastError()); + } +} +bool Semaphore::tryAcquire(void) +{ + DWORD result; + + assert(mHandle != (unsigned long) INVALID_HANDLE_VALUE); + result = WaitForSingleObject((HANDLE) mHandle, 0); + if (result == WAIT_OBJECT_0) + return true; // grabbed it + else if (result == WAIT_TIMEOUT) + return false; // not available + else if (result == WAIT_FAILED) { + LOG(LOG_WARN, "sem", "WaitForSingleObject(0) on sem failed (err=%ld)\n", + GetLastError()); + return false; + } else { + LOG(LOG_WARN, "sem", + "WaitForSingleObject(0) on sem returned %ld (err=%ld)\n", + result, GetLastError()); + return false; + } +} + +#endif // --------------------------------------------------------------------- diff --git a/simulator/app/Semaphore.h b/simulator/app/Semaphore.h new file mode 100644 index 000000000..3b834f3d0 --- /dev/null +++ b/simulator/app/Semaphore.h @@ -0,0 +1,59 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Inter-process semaphore. +// +// These are meant for IPC, not thread management. The Mutex and Condition +// classes are much lighter weight. +// +#ifndef __LIBS_SEMAPHORE_H +#define __LIBS_SEMAPHORE_H + +#ifdef HAVE_ANDROID_OS +#error DO NOT USE THIS FILE IN THE DEVICE BUILD +#endif + +namespace android { + +/* + * Platform-independent semaphore class. + * + * Each object holds a single semaphore. + * + * The "key" is usually the process ID of the process that created the + * semaphore (following POSIX semantics). See the comments in shmem.h. + */ +class Semaphore { +public: + Semaphore(void); + virtual ~Semaphore(void); + + /* + * Create a new semaphore, with the specified initial value. The + * value indicates the number of resources available. + */ + bool create(int key, int initialValue, bool deleteExisting); + + /* + * Attach to an existing semaphore. + */ + bool attach(int key); + + /* + * Acquire or release the semaphore. + */ + void acquire(void); + void release(void); + bool tryAcquire(void); // take a timeout? + +private: + bool adjust(int adj, bool wait); + + unsigned long mHandle; // semid(int) or HANDLE + bool mCreator; + int mKey; +}; + +}; // namespace android + +#endif // __LIBS_SEMAPHORE_H diff --git a/simulator/app/Shmem.cpp b/simulator/app/Shmem.cpp new file mode 100644 index 000000000..4c619c240 --- /dev/null +++ b/simulator/app/Shmem.cpp @@ -0,0 +1,558 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Shared memory interface. +// +#include "Shmem.h" +#include "utils/Log.h" + +#if defined(HAVE_MACOSX_IPC) || defined(HAVE_ANDROID_IPC) +# include <sys/mman.h> +# include <fcntl.h> +# include <unistd.h> +#elif defined(HAVE_SYSV_IPC) +# include <sys/types.h> +# include <sys/ipc.h> +# include <sys/shm.h> +#elif defined(HAVE_WIN32_IPC) +# include <windows.h> +#else +# error "unknown shm config" +#endif + +#include <errno.h> +#include <assert.h> + +using namespace android; + + +#if defined(HAVE_MACOSX_IPC) || defined(HAVE_ANDROID_IPC) + +/* + * SysV IPC under Mac OS X seems to have problems. It works fine on + * some machines but totally fails on others. We're working around it + * here by using mmap(). + */ + +#define kInvalidHandle ((unsigned long)-1) + +static const char* kShmemFile = "/tmp/android-"; + +/* + * Constructor. Just set up the fields. + */ +Shmem::Shmem(void) + : mHandle(kInvalidHandle), mAddr(MAP_FAILED), mLength(-1), mCreator(false), + mKey(-1) +{ +} + +/* + * Destructor. Detach and, if we created it, mark the segment for + * destruction. + */ +Shmem::~Shmem(void) +{ + if (mAddr != MAP_FAILED) + munmap(mAddr, mLength); + if ((long)mHandle >= 0) { + close(mHandle); + + if (mCreator) { + char nameBuf[64]; + int cc; + + snprintf(nameBuf, sizeof(nameBuf), "%s%d", kShmemFile, mKey); + cc = unlink(nameBuf); + if (cc != 0) { + LOG(LOG_WARN, "shmem", "Couldn't clean up '%s'\n", nameBuf); + /* oh well */ + } + } + } +} + +/* + * Create the segment and attach ourselves to it. + */ +bool Shmem::create(int key, long size, bool deleteExisting) +{ + char nameBuf[64]; + int fd, cc; + + snprintf(nameBuf, sizeof(nameBuf), "%s%d", kShmemFile, key); + + if (deleteExisting) { + cc = unlink(nameBuf); + if (cc != 0 && errno != ENOENT) { + LOG(LOG_ERROR, "shmem", "Failed to remove old map file '%s'\n", + nameBuf); + return false; + } + } + + fd = open(nameBuf, O_CREAT|O_EXCL|O_RDWR, 0600); + if (fd < 0) { + LOG(LOG_ERROR, "shmem", "Unable to create map file '%s' (errno=%d)\n", + nameBuf, errno); + return false; + } + + /* + * Set the file size by seeking and writing. + */ + if (ftruncate(fd, size) == -1) { + LOG(LOG_ERROR, "shmem", "Unable to set file size in '%s' (errno=%d)\n", + nameBuf, errno); + close(fd); + return false; + } + + mAddr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (mAddr == MAP_FAILED) { + LOG(LOG_ERROR, "shmem", "mmap failed (errno=%d)\n", errno); + close(fd); + return false; + } + + mHandle = fd; + mLength = size; + mCreator = true; + mKey = key; + + /* done with shmem, create the associated semaphore */ + if (!mSem.create(key, 1, true)) { + LOG(LOG_ERROR, "shmem", + "Failed creating semaphore for Shmem (key=%d)\n", key); + return false; + } + + return true; +} + +/* + * Attach ourselves to an existing segment. + */ +bool Shmem::attach(int key) +{ + char nameBuf[64]; + int fd; + + snprintf(nameBuf, sizeof(nameBuf), "%s%d", kShmemFile, key); + fd = open(nameBuf, O_RDWR, 0600); + if (fd < 0) { + LOG(LOG_ERROR, "shmem", "Unable to open map file '%s' (errno=%d)\n", + nameBuf, errno); + return false; + } + + off_t len; + len = lseek(fd, 0, SEEK_END); + if (len == (off_t) -1) { + LOG(LOG_ERROR, "shmem", + "Could not determine file size of '%s' (errno=%d)\n", + nameBuf, errno); + close(fd); + return false; + } + + mAddr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); + if (mAddr == MAP_FAILED) { + LOG(LOG_ERROR, "shmem", "mmap failed (errno=%d)\n", errno); + close(fd); + return false; + } + + mHandle = fd; + mLength = len; + assert(mCreator == false); + mKey = key; + + /* done with shmem, attach to associated semaphore */ + if (!mSem.attach(key)) { + LOG(LOG_ERROR, "shmem", + "Failed to attach to semaphore for Shmem (key=%d)\n", key); + return false; + } + + return true; +} + +/* + * Get address. + */ +void* Shmem::getAddr(void) +{ + assert(mAddr != MAP_FAILED); + return mAddr; +} + +/* + * Return the length of the segment. + * + * Returns -1 on failure. + */ +long Shmem::getLength(void) +{ + if (mLength >= 0) + return mLength; + + // we should always have it by now + assert(false); + return -1; +} + + +#elif defined(HAVE_SYSV_IPC) // ---------------------------------------------- + +/* + * SysV-style IPC. The SysV shared memory API is fairly annoying to + * deal with, but it's present on many UNIX-like systems. + */ + +#define kInvalidHandle ((unsigned long)-1) + +/* + * Constructor. Just set up the fields. + */ +Shmem::Shmem(void) + : mHandle(kInvalidHandle), mAddr(NULL), mLength(-1), mCreator(false), + mKey(-1) +{ +} + +/* + * Destructor. Detach and, if we created it, mark the segment for + * destruction. + */ +Shmem::~Shmem(void) +{ + int cc; + + //LOG(LOG_DEBUG, "shmem", "~Shmem(handle=%ld creator=%d)", + // mHandle, mCreator); + + if (mAddr != NULL) + cc = shmdt(mAddr); + + if (mCreator && mHandle != kInvalidHandle) { + cc = shmctl((int) mHandle, IPC_RMID, NULL); + if (cc != 0) { + LOG(LOG_WARN, "shmem", + "Destructor failed to remove shmid=%ld (errno=%d)\n", + mHandle, errno); + } + } +} + +/* + * Create the segment and attach ourselves to it. + */ +bool Shmem::create(int key, long size, bool deleteExisting) +{ + int shmid, cc; + + if (deleteExisting) { + shmid = shmget(key, size, 0); + if (shmid != -1) { + LOG(LOG_DEBUG, "shmem", + "Key %d exists (shmid=%d), marking for destroy", key, shmid); + cc = shmctl(shmid, IPC_RMID, NULL); + if (cc != 0) { + LOG(LOG_ERROR, "shmem", + "Failed to remove key=%d shmid=%d (errno=%d)\n", + key, shmid, errno); + return false; // IPC_CREAT | IPC_EXCL will fail, so bail now + } else { + LOG(LOG_DEBUG, "shmem", + "Removed previous segment with key=%d\n", key); + } + } + } + + shmid = shmget(key, size, 0600 | IPC_CREAT | IPC_EXCL); + if (shmid == -1) { + LOG(LOG_ERROR, "shmem", "Failed to create key=%d (errno=%d)\n", + key, errno); + return false; + } + + mHandle = shmid; + mCreator = true; + mKey = key; + + void* addr = shmat(shmid, NULL, 0); + if (addr == (void*) -1) { + LOG(LOG_ERROR, "shmem", + "Could not attach to key=%d shmid=%d (errno=%d)\n", + key, shmid, errno); + return false; + } + + mAddr = addr; + mLength = size; + + /* done with shmem, create the associated semaphore */ + if (!mSem.create(key, 1, true)) { + LOG(LOG_ERROR, "shmem", + "Failed creating semaphore for Shmem (key=%d)\n", key); + return false; + } + + return true; +} + +/* + * Attach ourselves to an existing segment. + */ +bool Shmem::attach(int key) +{ + int shmid; + + shmid = shmget(key, 0, 0); + if (shmid == -1) { + LOG(LOG_ERROR, "shmem", "Failed to find key=%d\n", key); + return false; + } + + mHandle = shmid; + assert(mCreator == false); + mKey = key; + + void* addr = shmat(shmid, NULL, 0); + if (addr == (void*) -1) { + LOG(LOG_ERROR, "shmem", "Could not attach to key=%d shmid=%d\n", + key, shmid); + return false; + } + + mAddr = addr; + + /* done with shmem, attach to associated semaphore */ + if (!mSem.attach(key)) { + LOG(LOG_ERROR, "shmem", + "Failed to attach to semaphore for Shmem (key=%d)\n", key); + return false; + } + + return true; +} + +/* + * Get address. + */ +void* Shmem::getAddr(void) +{ + assert(mAddr != NULL); + return mAddr; +} + +/* + * Return the length of the segment. + * + * Returns -1 on failure. + */ +long Shmem::getLength(void) +{ + if (mLength >= 0) + return mLength; + + assert(mHandle != kInvalidHandle); + + struct shmid_ds shmids; + int cc; + + cc = shmctl((int) mHandle, IPC_STAT, &shmids); + if (cc != 0) { + LOG(LOG_ERROR, "shmem", "Could not IPC_STAT shmid=%ld\n", mHandle); + return -1; + } + mLength = shmids.shm_segsz; // save a copy to avoid future lookups + + return mLength; +} + + +#elif defined(HAVE_WIN32_IPC) // --------------------------------------------- + +/* + * Win32 shared memory implementation. + * + * Shared memory is implemented as an "anonymous" file mapping, using the + * memory-mapped I/O interfaces. + */ + +static const char* kShmemStr = "android-shmem-"; + +/* + * Constructor. Just set up the fields. + */ +Shmem::Shmem(void) + : mHandle((unsigned long) INVALID_HANDLE_VALUE), + mAddr(NULL), mLength(-1), mCreator(false), mKey(-1) +{ +} + +/* + * Destructor. The Win32 API doesn't require a distinction between + * the "creator" and other mappers. + */ +Shmem::~Shmem(void) +{ + LOG(LOG_DEBUG, "shmem", "~Shmem(handle=%ld creator=%d)", + mHandle, mCreator); + + if (mAddr != NULL) + UnmapViewOfFile(mAddr); + if (mHandle != (unsigned long) INVALID_HANDLE_VALUE) + CloseHandle((HANDLE) mHandle); +} + +/* + * Create the segment and map it. + */ +bool Shmem::create(int key, long size, bool deleteExisting) +{ + char keyBuf[64]; + HANDLE hMapFile; + + snprintf(keyBuf, sizeof(keyBuf), "%s%d", kShmemStr, key); + + hMapFile = CreateFileMapping( + INVALID_HANDLE_VALUE, // use paging file, not actual file + NULL, // default security + PAGE_READWRITE, // read/write access + 0, // max size; no need to cap + size, // min size + keyBuf); // mapping name + if (hMapFile == NULL || hMapFile == INVALID_HANDLE_VALUE) { + LOG(LOG_ERROR, "shmem", + "Could not create mapping object '%s' (err=%ld)\n", + keyBuf, GetLastError()); + return false; + } + + mHandle = (unsigned long) hMapFile; + mCreator = true; + mKey = key; + + mAddr = MapViewOfFile( + hMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write + 0, // offset (hi) + 0, // offset (lo) + size); // #of bytes to map + if (mAddr == NULL) { + LOG(LOG_ERROR, "shmem", "Could not map shared area (err=%ld)\n", + GetLastError()); + return false; + } + mLength = size; + + /* done with shmem, create the associated semaphore */ + if (!mSem.create(key, 1, true)) { + LOG(LOG_ERROR, "shmem", + "Failed creating semaphore for Shmem (key=%d)\n", key); + return false; + } + + return true; +} + +/* + * Attach ourselves to an existing segment. + */ +bool Shmem::attach(int key) +{ + char keyBuf[64]; + HANDLE hMapFile; + + snprintf(keyBuf, sizeof(keyBuf), "%s%d", kShmemStr, key); + + hMapFile = OpenFileMapping( + FILE_MAP_ALL_ACCESS, // read/write + FALSE, // don't let kids inherit handle + keyBuf); // mapping name + if (hMapFile == NULL) { + LOG(LOG_ERROR, "shmem", + "Could not open mapping object '%s' (err=%ld)\n", + keyBuf, GetLastError()); + return false; + } + + mHandle = (unsigned long) hMapFile; + assert(mCreator == false); + mKey = key; + + mAddr = MapViewOfFile( + hMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write + 0, // offset (hi) + 0, // offset (lo) + 0); // #of bytes to map + if (mAddr == NULL) { + LOG(LOG_ERROR, "shmem", "Could not map shared area (err=%ld)\n", + GetLastError()); + return false; + } + + /* done with shmem, attach to associated semaphore */ + if (!mSem.attach(key)) { + LOG(LOG_ERROR, "shmem", + "Failed to attach to semaphore for Shmem (key=%d)\n", key); + return false; + } + + return true; +} + +/* + * Get address. + */ +void* Shmem::getAddr(void) +{ + assert(mAddr != NULL); + return mAddr; +} + +/* + * Get the length of the segment. + */ +long Shmem::getLength(void) +{ + SIZE_T size; + MEMORY_BASIC_INFORMATION mbInfo; + + if (mLength >= 0) + return mLength; + + assert(mAddr != NULL); + + size = VirtualQuery(mAddr, &mbInfo, sizeof(mbInfo)); + if (size == 0) { + LOG(LOG_WARN, "shmem", "VirtualQuery returned no data\n"); + return -1; + } + + mLength = mbInfo.RegionSize; + return mLength; +} + +#endif // -------------------------------------------------------------------- + +/* + * Semaphore operations. + */ +void Shmem::lock(void) +{ + mSem.acquire(); +} +void Shmem::unlock(void) +{ + mSem.release(); +} +bool Shmem::tryLock(void) +{ + return mSem.tryAcquire(); +} + diff --git a/simulator/app/Shmem.h b/simulator/app/Shmem.h new file mode 100644 index 000000000..3d53b58b6 --- /dev/null +++ b/simulator/app/Shmem.h @@ -0,0 +1,95 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Inter-process shared memory. +// +#ifndef __LIBS_SHMEM_H +#define __LIBS_SHMEM_H + +#ifdef HAVE_ANDROID_OS +#error DO NOT USE THIS FILE IN THE DEVICE BUILD +#endif + +#include "Semaphore.h" + +namespace android { + +/* + * Platform-independent shared memory. Each object can be used to + * create a chunk of memory that is shared between processes. + * + * For convenience, a semaphore is associated with each segment. + * (Whether this should have been done in a subclass is debatable.) + * + * The "key" is usually the process ID of the process that created the + * segment. The goal is to avoid clashing with other processes that are + * trying to do the same thing. It's a little awkward to use when you + * want to have multiple shared segments created in one process. In + * SysV you can work around this by using a "private" key and sharing + * the shmid with your friends, in Win32 you can use a string, but we're + * in lowest-common-denominator mode here. Assuming we have 16-bit PIDs, + * the upper 16 bits can be used to serialize keys. + * + * When the object goes out of scope, the shared memory segment is + * detached from the process. If the object was responsible for creating + * the segment, it is also marked for destruction on SysV systems. This + * will make it impossible for others to attach to. + * + * On some systems, the length returned by getLength() may be different + * for parent and child due to page size rounding. + */ +class Shmem { +public: + Shmem(void); + virtual ~Shmem(void); + + /* + * Create a new shared memory segment, with the specified size. If + * "deleteExisting" is set, any existing segment will be deleted first + * (useful for SysV IPC). + * + * Returns "true" on success, "false" on failure. + */ + bool create(int key, long size, bool deleteExisting); + + /* + * Attach to a shared memory segment. Use this from the process that + * didn't create the segment. + * + * Returns "true" on success, "false" on failure. + */ + bool attach(int key); + + /* + * Get the memory segment address and length. These will not change + * for the lifetime of the object, so it's okay to cache the results. + * + * On failure, getAddr() returns NULL and getLength() returns -1. + */ + void* getAddr(void); + long getLength(void); + + /* + * Lock or unlock the shared memory segment. This is useful if you + * are updating pieces of shared data. The segment is initially + * "unlocked". + * + * This does *not* lock down the segment in the virtual paging system. + * It's just a mutex. + */ + void lock(void); + void unlock(void); + bool tryLock(void); + +private: + Semaphore mSem; // uses the same value for "key" + unsigned long mHandle; // shmid(int) or HANDLE + void* mAddr; // address + long mLength; // length of segment (cached) + bool mCreator; // true if we created the segment + int mKey; // key passed in as arg +}; + +}; // namespace android + +#endif // __LIBS_SHMEM_H diff --git a/simulator/app/SimRuntime.h b/simulator/app/SimRuntime.h new file mode 100644 index 000000000..6dadfb25d --- /dev/null +++ b/simulator/app/SimRuntime.h @@ -0,0 +1,124 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Miscellaneous definitions and declarations used for interaction +// between the device and the simulator. +// +// This header is included on both sides, so try not to include +// any other headers from here. +// +#ifndef _RUNTIME_SIMULATOR_H +#define _RUNTIME_SIMULATOR_H + +#include "MessageStream.h" +#include "Shmem.h" +//#include "utils/RefBase.h" +#include "utils/Log.h" + +namespace android { + +#define ANDROID_PIPE_NAME "runtime" + +/* + * Hold simulator state. + */ +class Simulator { +public: + Simulator(void); + ~Simulator(void); + + /* + * Commands exchanged between simulator and runtime. + */ + typedef enum Command { + kCommandUnknown = 0, + + /* sent from sim to runtime */ + kCommandGoAway, // sim says: go away, I'm busy + kCommandConfigDone, // sim says: done sending config + kCommandQuit, // quit nicely + kCommandNewPGroup, // process group management + kCommandKeyDown, // key has been pressed + kCommandKeyUp, // key has been released + kCommandTouch, // finger touched/lifted/dragged + + /* sent from runtime to sim */ + kCommandNewPGroupCreated, // send process group as argument + kCommandRuntimeReady, // we're initialized and about to start + kCommandUpdateDisplay, // display has been updated + kCommandVibrate, // vibrate on or off + } Command; + + typedef enum TouchMode { + kTouchDown = 0, + kTouchUp = 1, + kTouchDrag = 2 + } TouchMode; + + /* + * Some parameters for config exchange. + */ + enum { + kDisplayConfigMagic = 0x44495350, + kValuesPerDisplay = 5, + }; + + /* + * Set up communication with parent process. + */ + //bool create(ParentProcess* pParent); + + /* + * Set up communication with detached simulator. + */ + bool create(Pipe* reader, Pipe* writer); + + /* + * Tell simulator that we're ready to go. + */ + void sendRuntimeReady(void); + + /* + * Tell the simulator that a display has been refreshed. + */ + void sendDisplayUpdate(int displayIndex); + + /* + * Tell the simulator to turn the vibrator on or off + */ + void sendVibrate(int vibrateOn); + + /* + * Get a pointer to the shared memory for the Nth display. + */ + Shmem* getGraphicsBuffer(int displayIndex); + + /* + * Return a copy of our input pipe so the event system can monitor + * it for pending activity. + */ + Pipe* getReadPipe(void) { return mStream.getReadPipe(); } + + /* + * Retrieve the next command from the parent. Returns NO_ERROR + * if all is okay, WOULD_BLOCK if blocking is false and there + * are no pending commands, or INVALID_OPERATION if the simulator + * has disappeared. + */ + int getNextKey(int32_t* outKey, bool* outDown); + + /* + * Log system callback function. + */ + static void writeLogMsg(const android_LogBundle* pBundle); + +private: + bool finishCreate(void); + bool handleDisplayConfig(const long* pData, int length); + + MessageStream mStream; +}; + +}; // namespace android + +#endif // _RUNTIME_SIMULATOR_H diff --git a/simulator/app/UserEvent.cpp b/simulator/app/UserEvent.cpp new file mode 100644 index 000000000..01d33377a --- /dev/null +++ b/simulator/app/UserEvent.cpp @@ -0,0 +1,20 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Some additional glue for "user event" type. +// + +// 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 "UserEvent.h" + +DEFINE_EVENT_TYPE(wxEVT_USER_EVENT) + +IMPLEMENT_DYNAMIC_CLASS(UserEvent, wxEvent) + diff --git a/simulator/app/UserEvent.h b/simulator/app/UserEvent.h new file mode 100644 index 000000000..76664e19f --- /dev/null +++ b/simulator/app/UserEvent.h @@ -0,0 +1,54 @@ +// +// Copyright 2005 The Android Open Source Project +// +// A "user event" for wxWidgets. +// +#ifndef _SIM_USER_EVENT_H +#define _SIM_USER_EVENT_H + +/* + * Event declaration. The book says to use DECLARE_EVENT_TYPE, but that + * causes a compiler warning and a link failure with gcc under MinGW. + * + * It looks like the "magic number", in this case 12345, is just picked + * by hand. There may be a better mechanism in this version of + * wxWidgets, but the documentation and sample code doesn't reflect it. + */ +BEGIN_DECLARE_EVENT_TYPES() + DECLARE_LOCAL_EVENT_TYPE(wxEVT_USER_EVENT, 12345) +END_DECLARE_EVENT_TYPES() + +/* + * A "user event" class. This can be used like any other wxWidgets + * event, but we get to stuff anything we want to in it. + */ +class UserEvent : public wxEvent { +public: + UserEvent(int id = 0, void* data = (void*) 0) + : wxEvent(id, wxEVT_USER_EVENT), mData(data) + {} + UserEvent(const UserEvent& event) + : wxEvent(event), mData(event.mData) + {} + + virtual wxEvent* Clone() const { + return new UserEvent(*this); + } + + void* GetData(void) const { return mData; } + + DECLARE_DYNAMIC_CLASS(UserEvent); + +private: + UserEvent& operator=(const UserEvent&); // not implemented + void* mData; +}; + +typedef void (wxEvtHandler::*UserEventFunction)(UserEvent&); + +#define EVT_USER_EVENT(fn) \ + DECLARE_EVENT_TABLE_ENTRY(wxEVT_USER_EVENT, wxID_ANY, wxID_ANY, \ + (wxObjectEventFunction)(wxEventFunction)(UserEventFunction)&fn, \ + (wxObject*) NULL ), + +#endif // _SIM_USER_EVENT_H diff --git a/simulator/app/UserEventMessage.h b/simulator/app/UserEventMessage.h new file mode 100644 index 000000000..4a66cc2cb --- /dev/null +++ b/simulator/app/UserEventMessage.h @@ -0,0 +1,84 @@ +// +// Copyright 2005 The Android Open Source Project +// +// Contents of the "user event" sent from the device thread. +// +#ifndef _SIM_USER_EVENT_MESSAGE_H +#define _SIM_USER_EVENT_MESSAGE_H + +#include <utils.h> +#include "LogMessage.h" + +/* + * This gets stuffed into a UserEvent, which is posted to the main thread + * from a worker thread. + * + * The object does NOT own anything you stuff into it. It's just a vehicle + * for carting data from one thread to another in a wxWidgets-safe manner, + * usually as pointers to data that can be shared between threads. + */ +class UserEventMessage { +public: + /* + * What type of message is this? + */ + typedef enum UEMType { + kUnknown = 0, + + kRuntimeStarted, + kRuntimeStopped, + kErrorMessage, // message in mString + kLogMessage, // ptr to heap-allocated LogMessage + kExternalRuntime, // external runtime wants to party + } UEMType; + + UserEventMessage(void) + : mType(kUnknown), mpLogMessage(NULL) + {} + ~UserEventMessage(void) { + } + + /* + * Create one of our various messages. + */ + void CreateRuntimeStarted(void) { + mType = kRuntimeStarted; + } + void CreateRuntimeStopped(void) { + mType = kRuntimeStopped; + } + void CreateErrorMessage(wxString& str) { + mType = kErrorMessage; + mString = str; + } + void CreateLogMessage(LogMessage* pLogMessage) { + mType = kLogMessage; + mpLogMessage = pLogMessage; + } + void CreateExternalRuntime(android::Pipe* reader, android::Pipe* writer) { + mType = kExternalRuntime; + mReader = reader; + mWriter = writer; + } + + /* + * Accessors. + */ + UEMType GetType(void) const { return mType; } + const wxString& GetString(void) const { return mString; } + LogMessage* GetLogMessage(void) const { return mpLogMessage; } + android::Pipe* GetReader(void) const { return mReader; } + android::Pipe* GetWriter(void) const { return mWriter; } + +private: + UserEventMessage& operator=(const UserEventMessage&); // not implemented + UserEventMessage(const UserEventMessage&); // not implemented + + UEMType mType; + wxString mString; // for kErrorMessage + LogMessage* mpLogMessage; // for kLogMessage + android::Pipe* mReader; // for kExternalRuntime + android::Pipe* mWriter; // for kExternalRuntime +}; + +#endif // _SIM_USER_EVENT_MESSAGE_H diff --git a/simulator/app/assets/android-dream/arrow_down.png b/simulator/app/assets/android-dream/arrow_down.png Binary files differnew file mode 100644 index 000000000..19b3764e8 --- /dev/null +++ b/simulator/app/assets/android-dream/arrow_down.png diff --git a/simulator/app/assets/android-dream/arrow_left.png b/simulator/app/assets/android-dream/arrow_left.png Binary files differnew file mode 100644 index 000000000..113e58427 --- /dev/null +++ b/simulator/app/assets/android-dream/arrow_left.png diff --git a/simulator/app/assets/android-dream/arrow_right.png b/simulator/app/assets/android-dream/arrow_right.png Binary files differnew file mode 100644 index 000000000..ffe3356c0 --- /dev/null +++ b/simulator/app/assets/android-dream/arrow_right.png diff --git a/simulator/app/assets/android-dream/arrow_up.png b/simulator/app/assets/android-dream/arrow_up.png Binary files differnew file mode 100644 index 000000000..81c54df51 --- /dev/null +++ b/simulator/app/assets/android-dream/arrow_up.png diff --git a/simulator/app/assets/android-dream/back.png b/simulator/app/assets/android-dream/back.png Binary files differnew file mode 100644 index 000000000..41034d910 --- /dev/null +++ b/simulator/app/assets/android-dream/back.png diff --git a/simulator/app/assets/android-dream/background.png b/simulator/app/assets/android-dream/background.png Binary files differnew file mode 100644 index 000000000..bab8c6d1c --- /dev/null +++ b/simulator/app/assets/android-dream/background.png diff --git a/simulator/app/assets/android-dream/end.png b/simulator/app/assets/android-dream/end.png Binary files differnew file mode 100644 index 000000000..6830a603b --- /dev/null +++ b/simulator/app/assets/android-dream/end.png diff --git a/simulator/app/assets/android-dream/home.png b/simulator/app/assets/android-dream/home.png Binary files differnew file mode 100644 index 000000000..7d021369b --- /dev/null +++ b/simulator/app/assets/android-dream/home.png diff --git a/simulator/app/assets/android-dream/key.png b/simulator/app/assets/android-dream/key.png Binary files differnew file mode 100644 index 000000000..7a3f563bd --- /dev/null +++ b/simulator/app/assets/android-dream/key.png diff --git a/simulator/app/assets/android-dream/layout.xml b/simulator/app/assets/android-dream/layout.xml new file mode 100644 index 000000000..8a7c5f862 --- /dev/null +++ b/simulator/app/assets/android-dream/layout.xml @@ -0,0 +1,356 @@ +<?xml version="1.0" ?> +<!-- Copyright 2006 The Android Open Source Project --> + +<device name="Dream"> + <!-- title for menus --> + <title>Android Dream</title> + + <display name="main" width="320" height="480" format="rgb565" refresh="30"/> + + <keyboard qwerty="1" keycharmap="qwerty2" /> + + <mode name="closed"> + <view display="main" x="75" y="84" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <image src="background.png" x="0" y="0"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="1" src="key.png" x="468" y="276"/> + <button keyCode="q" src="key.png" x="468" y="312"/> + <button keyCode="power" src="power.png" x="7" y="66"/> + <button keyCode="volume-up" src="volume_up.png" x="407" y="274"/> + <button keyCode="volume-down" src="volume_down.png" x="407" y="324"/> + + <button keyCode="home" src="home.png" x="93" y="602"/> + <button keyCode="back" src="back.png" x="331" y="602"/> + <button keyCode="dpad-up" src="arrow_up.png" x="185" y="608"/> + <button keyCode="dpad-down" src="arrow_down.png" x="185" y="669"/> + <button keyCode="dpad-left" src="arrow_left.png" x="155" y="610"/> + <button keyCode="dpad-right" src="arrow_right.png" x="266" y="610"/> + <button keyCode="dpad-center" src="select.png" x="186" y="637"/> + <button keyCode="phone-dial" src="send.png" x="93" y="658"/> + <button keyCode="phone-hangup" src="end.png" x="331" y="658"/> + <button keyCode="soft-left" src="menu.png" x="192" y="569"/> + +<!-- + 1 { + image key.png + x 468 + y 302 + } + 2 { + image key.png + x 505 + y 302 + } + 3 { + image key.png + x 543 + y 302 + } + 4 { + image key.png + x 579 + y 302 + } + 5 { + image key.png + x 616 + y 302 + } + 6 { + image key.png + x 653 + y 302 + } + 7 { + image key.png + x 690 + y 302 + } + 8 { + image key.png + x 727 + y 302 + } + 9 { + image key.png + x 763 + y 302 + } + 0 { + image key.png + x 801 + y 302 + } + + q { + image key.png + x 468 + y 338 + } + w { + image key.png + x 505 + y 338 + } + e { + image key.png + x 543 + y 338 + } + r { + image key.png + x 579 + y 338 + } + t { + image key.png + x 616 + y 338 + } + y { + image key.png + x 653 + y 338 + } + u { + image key.png + x 690 + y 338 + } + i { + image key.png + x 727 + y 338 + } + o { + image key.png + x 763 + y 338 + } + p { + image key.png + x 801 + y 338 + } + + a { + image key.png + x 468 + y 374 + } + s { + image key.png + x 505 + y 374 + } + d { + image key.png + x 543 + y 374 + } + f { + image key.png + x 579 + y 374 + } + g { + image key.png + x 616 + y 374 + } + h { + image key.png + x 653 + y 374 + } + j { + image key.png + x 690 + y 374 + } + k { + image key.png + x 727 + y 374 + } + l { + image key.png + x 763 + y 374 + } + DEL { + image key.png + x 801 + y 374 + } + + CAP { + image key.png + x 468 + y 410 + } + z { + image key.png + x 505 + y 410 + } + x { + image key.png + x 543 + y 410 + } + c { + image key.png + x 579 + y 410 + } + v { + image key.png + x 616 + y 410 + } + b { + image key.png + x 653 + y 410 + } + n { + image key.png + x 690 + y 410 + } + m { + image key.png + x 727 + y 410 + } + PERIOD { + image key.png + x 763 + y 410 + } + ENTER { + image key.png + x 801 + y 410 + } + + ALT { + image key.png + x 468 + y 446 + } + SYM { + image key.png + x 505 + y 446 + } + AT { + image key.png + x 543 + y 446 + } + SPACE { + image spacebar.png + x 579 + y 446 + } + SLASH { + image key.png + x 727 + y 446 + } + COMMA { + image key.png + x 763 + y 446 + } + ALT2 { + image key.png + x 801 + y 446 + } + + soft-left { + image menu.png + x 192 + y 623 + } + home { + image home.png + x 93 + y 656 + } + back { + image back.png + x 331 + y 656 + } + dpad-up { + image arrow_up.png + x 185 + y 662 + } + dpad-down { + image arrow_down.png + x 185 + y 723 + } + dpad-left { + image arrow_left.png + x 155 + y 664 + } + dpad-right { + image arrow_right.png + x 266 + y 664 + } + dpad-center { + image select.png + x 186 + y 691 + } + phone-dial { + image send.png + x 93 + y 712 + } + phone-hangup { + image end.png + x 331 + y 712 + } + + power { + image power.png + x 7 + y 120 + } + + volume-up { + image volume_up.png + x 407 + y 328 + } + + volume-down { + image volume_down.png + x 407 + y 378 + } +--> + </view> + </mode> + +<!-- + <mode name="open"> + <view display="main" x="0" y="0" rotate="90"> + </view> + </mode> +--> + +</device> diff --git a/simulator/app/assets/android-dream/menu.png b/simulator/app/assets/android-dream/menu.png Binary files differnew file mode 100644 index 000000000..e81d8abcf --- /dev/null +++ b/simulator/app/assets/android-dream/menu.png diff --git a/simulator/app/assets/android-dream/power.png b/simulator/app/assets/android-dream/power.png Binary files differnew file mode 100644 index 000000000..5894288f0 --- /dev/null +++ b/simulator/app/assets/android-dream/power.png diff --git a/simulator/app/assets/android-dream/select.png b/simulator/app/assets/android-dream/select.png Binary files differnew file mode 100644 index 000000000..803d49315 --- /dev/null +++ b/simulator/app/assets/android-dream/select.png diff --git a/simulator/app/assets/android-dream/send.png b/simulator/app/assets/android-dream/send.png Binary files differnew file mode 100644 index 000000000..f547c8817 --- /dev/null +++ b/simulator/app/assets/android-dream/send.png diff --git a/simulator/app/assets/android-dream/spacebar.png b/simulator/app/assets/android-dream/spacebar.png Binary files differnew file mode 100644 index 000000000..19fe60476 --- /dev/null +++ b/simulator/app/assets/android-dream/spacebar.png diff --git a/simulator/app/assets/android-dream/volume_down.png b/simulator/app/assets/android-dream/volume_down.png Binary files differnew file mode 100644 index 000000000..f8a88dec8 --- /dev/null +++ b/simulator/app/assets/android-dream/volume_down.png diff --git a/simulator/app/assets/android-dream/volume_up.png b/simulator/app/assets/android-dream/volume_up.png Binary files differnew file mode 100644 index 000000000..940457f70 --- /dev/null +++ b/simulator/app/assets/android-dream/volume_up.png diff --git a/simulator/app/assets/android-sooner/back.png b/simulator/app/assets/android-sooner/back.png Binary files differnew file mode 100644 index 000000000..5190939c7 --- /dev/null +++ b/simulator/app/assets/android-sooner/back.png diff --git a/simulator/app/assets/android-sooner/background.png b/simulator/app/assets/android-sooner/background.png Binary files differnew file mode 100644 index 000000000..32534b905 --- /dev/null +++ b/simulator/app/assets/android-sooner/background.png diff --git a/simulator/app/assets/android-sooner/down.png b/simulator/app/assets/android-sooner/down.png Binary files differnew file mode 100644 index 000000000..1d602b01c --- /dev/null +++ b/simulator/app/assets/android-sooner/down.png diff --git a/simulator/app/assets/android-sooner/end.png b/simulator/app/assets/android-sooner/end.png Binary files differnew file mode 100644 index 000000000..17d8cc7f0 --- /dev/null +++ b/simulator/app/assets/android-sooner/end.png diff --git a/simulator/app/assets/android-sooner/favorites.png b/simulator/app/assets/android-sooner/favorites.png Binary files differnew file mode 100644 index 000000000..ad85c5c89 --- /dev/null +++ b/simulator/app/assets/android-sooner/favorites.png diff --git a/simulator/app/assets/android-sooner/home.png b/simulator/app/assets/android-sooner/home.png Binary files differnew file mode 100644 index 000000000..04f9a2173 --- /dev/null +++ b/simulator/app/assets/android-sooner/home.png diff --git a/simulator/app/assets/android-sooner/imap b/simulator/app/assets/android-sooner/imap new file mode 100644 index 000000000..64c9aff3d --- /dev/null +++ b/simulator/app/assets/android-sooner/imap @@ -0,0 +1,15 @@ +# awk '{print $1, $4-$2 "x" $5-$3 "+" $2 "+" $3}' + +BACKGROUND=background-alpha.png + +convert $BACKGROUND -crop 43x47+55+410 send.png +convert $BACKGROUND -crop 43x47+338+411 end.png +convert $BACKGROUND -crop 43x40+98+393 menu.png +convert $BACKGROUND -crop 43x40+98+433 back.png +convert $BACKGROUND -crop 47x40+291+393 favorites.png +convert $BACKGROUND -crop 47x40+291+433 home.png +convert $BACKGROUND -crop 81x24+177+422 select.png +convert $BACKGROUND -crop 81x30+177+392 up.png +convert $BACKGROUND -crop 81x30+177+446 down.png +convert $BACKGROUND -crop 31x84+146+392 left.png +convert $BACKGROUND -crop 31x84+258+392 right.png diff --git a/simulator/app/assets/android-sooner/layout.xml b/simulator/app/assets/android-sooner/layout.xml new file mode 100644 index 000000000..d17b9d8e3 --- /dev/null +++ b/simulator/app/assets/android-sooner/layout.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" ?> +<!-- Copyright 2006 The Android Open Source Project --> + +<device name="Sooner"> + <!-- title for menus --> + <title>Android Sooner</title> + + <display name="main" width="320" height="240" format="rgb565" refresh="30"/> + + <keyboard qwerty="1" keycharmap="qwerty" /> + + <mode name="default"> + <view display="main" x="53" y="95" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <image src="background.png" x="0" y="0"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="soft-left" src="menu.png" x="93" y="367"/> + <button keyCode="soft-right" src="favorites.png" x="286" y="367"/> + <button keyCode="home" src="home.png" x="286" y="407"/> + <button keyCode="back" src="back.png" x="93" y="407"/> + <button keyCode="dpad-up" src="up.png" x="172" y="366"/> + <button keyCode="dpad-down" src="down.png" x="172" y="420"/> + <button keyCode="dpad-left" src="left.png" x="141" y="366"/> + <button keyCode="dpad-right" src="right.png" x="253" y="366"/> + <button keyCode="dpad-center" src="select.png" x="172" y="396"/> + + <button keyCode="phone-dial" src="send.png" x="50" y="384"/> + <button keyCode="phone-hangup" src="end.png" x="333" y="385"/> + </view> + </mode> + +</device> diff --git a/simulator/app/assets/android-sooner/left.png b/simulator/app/assets/android-sooner/left.png Binary files differnew file mode 100644 index 000000000..f712f5c10 --- /dev/null +++ b/simulator/app/assets/android-sooner/left.png diff --git a/simulator/app/assets/android-sooner/menu.png b/simulator/app/assets/android-sooner/menu.png Binary files differnew file mode 100644 index 000000000..4c7f15ac1 --- /dev/null +++ b/simulator/app/assets/android-sooner/menu.png diff --git a/simulator/app/assets/android-sooner/right.png b/simulator/app/assets/android-sooner/right.png Binary files differnew file mode 100644 index 000000000..b1aa1f95f --- /dev/null +++ b/simulator/app/assets/android-sooner/right.png diff --git a/simulator/app/assets/android-sooner/select.png b/simulator/app/assets/android-sooner/select.png Binary files differnew file mode 100644 index 000000000..79606d185 --- /dev/null +++ b/simulator/app/assets/android-sooner/select.png diff --git a/simulator/app/assets/android-sooner/send.png b/simulator/app/assets/android-sooner/send.png Binary files differnew file mode 100644 index 000000000..9bf103dcb --- /dev/null +++ b/simulator/app/assets/android-sooner/send.png diff --git a/simulator/app/assets/android-sooner/up.png b/simulator/app/assets/android-sooner/up.png Binary files differnew file mode 100644 index 000000000..324d67efd --- /dev/null +++ b/simulator/app/assets/android-sooner/up.png diff --git a/simulator/app/assets/chimera-2000/layout.xml b/simulator/app/assets/chimera-2000/layout.xml new file mode 100644 index 000000000..d80828146 --- /dev/null +++ b/simulator/app/assets/chimera-2000/layout.xml @@ -0,0 +1,194 @@ +<?xml version="1.0" ?> +<!-- Copyright 2005 The Android Open Source Project --> + +<!-- +This is a silly, contrived example of a dual-screen display device. +The "front" and "back" are shown side-by-side. I used the phone graphics +from two phones, which are presented as if they were glued back-to-back. + +This also illustrates front and back views, which for our purposes just +determines which phone is on the left. + +This used to use relative paths (../whatever) to get at the phone content, +but relative paths aren't supported in the "AssetManager" world. We now +use "::/whatever", which is interpreted by the simulator. (The change from +'.' to ':' was done to make it obvious that we weren't using UNIX paths.) +--> + + +<device name="Chimera2000"> + <!-- title for menus --> + <title>Chimera WeirdPhone 2000</title> + + <!-- primary display characteristics --> + <display name="top" width="176" height="220" format="rgb565" refresh="30"/> + <display name="bottom" width="176" height="220" format="rgb565" refresh="30"/> + + <mode name="front"> + <!-- the "top" device looks like a Samsung phone --> + <view display="top" x="49" y="73" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <!-- (phone image is optional) --> + <image src="::/samsung-flip-2005/background-176x220.png" x="0" y="0"/> + <image src="::/samsung-flip-2005/home-background.png" x="22" y="426"/> + <image src="::/samsung-flip-2005/back-background.png" x="179" y="426"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="soft-left" src="::/samsung-flip-2005/soft-left.png" x="24" y="388"/> + <button keyCode="soft-right" src="::/samsung-flip-2005/soft-right.png" x="182" y="388"/> + <button keyCode="home" src="::/samsung-flip-2005/home.png" x="22" y="426"/> + <button keyCode="back" src="::/samsung-flip-2005/back.png" x="179" y="426"/> + <button keyCode="dpad-up" src="::/samsung-flip-2005/arrow-up.png" x="99" y="399"/> + <button keyCode="dpad-down" src="::/samsung-flip-2005/arrow-down.png" x="99" y="461"/> + <button keyCode="dpad-left" src="::/samsung-flip-2005/arrow-left.png" x="82" y="411"/> + <button keyCode="dpad-right" src="::/samsung-flip-2005/arrow-right.png" x="147" y="411"/> + <button keyCode="dpad-center" src="::/samsung-flip-2005/select.png" x="115" y="431"/> + + <button keyCode="1" src="::/samsung-flip-2005/1.png" x="24" y="533"/> + <button keyCode="2" src="::/samsung-flip-2005/2.png" x="94" y="544"/> + <button keyCode="3" src="::/samsung-flip-2005/3.png" x="173" y="534"/> + <button keyCode="4" src="::/samsung-flip-2005/4.png" x="25" y="576"/> + <button keyCode="5" src="::/samsung-flip-2005/5.png" x="94" y="581"/> + <button keyCode="6" src="::/samsung-flip-2005/6.png" x="172" y="576"/> + <button keyCode="7" src="::/samsung-flip-2005/7.png" x="27" y="619"/> + <button keyCode="8" src="::/samsung-flip-2005/8.png" x="95" y="621"/> + <button keyCode="9" src="::/samsung-flip-2005/9.png" x="168" y="620"/> + <button keyCode="0" src="::/samsung-flip-2005/0.png" x="96" y="658"/> + <button keyCode="star" src="::/samsung-flip-2005/star.png" x="31" y="659"/> + <button keyCode="pound" src="::/samsung-flip-2005/pound.png" x="169" y="659"/> + + <!-- buttons we haven't bothered to create highlight images for, + or that aren't visible on-screen --> + <button keyCode="volume-up"/> + <button keyCode="volume-down"/> + <button keyCode="power"/> + <button keyCode="clear"/> + </view> + + <!-- the "bottom" device looks like an HTC phone --> + <view display="bottom" x="36" y="73" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <!-- (phone image is optional) --> + <image src="::/htc-audiovox-5600/background.png" x="0" y="0"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="soft-left" src="::/htc-audiovox-5600/soft-left.png" x="40" y="312"/> + <button keyCode="soft-right" src="::/htc-audiovox-5600/soft-right.png" x="145" y="312"/> + <button keyCode="home" src="::/htc-audiovox-5600/home.png" x="57" y="331"/> + <button keyCode="back" src="::/htc-audiovox-5600/back.png" x="123" y="333"/> + <button keyCode="dpad-up" src="::/htc-audiovox-5600/arrow-up.png" x="59" y="364"/> + <button keyCode="dpad-down" src="::/htc-audiovox-5600/arrow-down.png" x="60" y="393"/> + <button keyCode="dpad-left"/> + <button keyCode="dpad-right"/> + <button keyCode="dpad-center" src="::/htc-audiovox-5600/select.png" x="103" y="381"/> + + <button keyCode="1" src="::/htc-audiovox-5600/1.png" x="30" y="411"/> + <button keyCode="2" src="::/htc-audiovox-5600/2.png" x="90" y="409"/> + <button keyCode="3" src="::/htc-audiovox-5600/3.png" x="159" y="413"/> + <button keyCode="4" src="::/htc-audiovox-5600/4.png" x="32" y="445"/> + <button keyCode="5" src="::/htc-audiovox-5600/5.png" x="92" y="445"/> + <button keyCode="6" src="::/htc-audiovox-5600/6.png" x="157" y="444"/> + <button keyCode="7" src="::/htc-audiovox-5600/7.png" x="28" y="476"/> + <button keyCode="8" src="::/htc-audiovox-5600/8.png" x="94" y="480"/> + <button keyCode="9" src="::/htc-audiovox-5600/9.png" x="156" y="477"/> + <button keyCode="0" src="::/htc-audiovox-5600/0.png" x="97" y="513"/> + <button keyCode="star" src="::/htc-audiovox-5600/star.png" x="45" y="509"/> + <button keyCode="pound" src="::/htc-audiovox-5600/pound.png" x="155" y="511"/> + + <!-- buttons we haven't bothered to create highlight images for, + or that aren't visible on-screen --> + <button keyCode="volume-up"/> + <button keyCode="volume-down"/> + <button keyCode="power"/> + <button keyCode="clear"/> + + </view> + </mode> + + <mode name="back"> + <!-- show the back view first, then the front view --> + <view display="bottom" x="36" y="73" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <!-- (phone image is optional) --> + <image src="::/htc-audiovox-5600/background.png" x="0" y="0"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="soft-left" src="::/htc-audiovox-5600/soft-left.png" x="40" y="312"/> + <button keyCode="soft-right" src="::/htc-audiovox-5600/soft-right.png" x="145" y="312"/> + <button keyCode="home" src="::/htc-audiovox-5600/home.png" x="57" y="331"/> + <button keyCode="back" src="::/htc-audiovox-5600/back.png" x="123" y="333"/> + <button keyCode="dpad-up" src="::/htc-audiovox-5600/arrow-up.png" x="59" y="364"/> + <button keyCode="dpad-down" src="::/htc-audiovox-5600/arrow-down.png" x="60" y="393"/> + <button keyCode="dpad-left"/> + <button keyCode="dpad-right"/> + <button keyCode="dpad-center" src="::/htc-audiovox-5600/select.png" x="103" y="381"/> + + <button keyCode="1" src="::/htc-audiovox-5600/1.png" x="30" y="411"/> + <button keyCode="2" src="::/htc-audiovox-5600/2.png" x="90" y="409"/> + <button keyCode="3" src="::/htc-audiovox-5600/3.png" x="159" y="413"/> + <button keyCode="4" src="::/htc-audiovox-5600/4.png" x="32" y="445"/> + <button keyCode="5" src="::/htc-audiovox-5600/5.png" x="92" y="445"/> + <button keyCode="6" src="::/htc-audiovox-5600/6.png" x="157" y="444"/> + <button keyCode="7" src="::/htc-audiovox-5600/7.png" x="28" y="476"/> + <button keyCode="8" src="::/htc-audiovox-5600/8.png" x="94" y="480"/> + <button keyCode="9" src="::/htc-audiovox-5600/9.png" x="156" y="477"/> + <button keyCode="0" src="::/htc-audiovox-5600/0.png" x="97" y="513"/> + <button keyCode="star" src="::/htc-audiovox-5600/star.png" x="45" y="509"/> + <button keyCode="pound" src="::/htc-audiovox-5600/pound.png" x="155" y="511"/> + + <!-- buttons we haven't bothered to create highlight images for, + or that aren't visible on-screen --> + <button keyCode="volume-up"/> + <button keyCode="volume-down"/> + <button keyCode="power"/> + <button keyCode="clear"/> + + </view> + + <!-- our view of the device that shows the main display --> + <view display="top" x="49" y="73" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <!-- (phone image is optional) --> + <image src="::/samsung-flip-2005/background-176x220.png" x="0" y="0"/> + <image src="::/samsung-flip-2005/home-background.png" x="22" y="426"/> + <image src="::/samsung-flip-2005/back-background.png" x="179" y="426"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="soft-left" src="::/samsung-flip-2005/soft-left.png" x="24" y="388"/> + <button keyCode="soft-right" src="::/samsung-flip-2005/soft-right.png" x="182" y="388"/> + <button keyCode="home" src="::/samsung-flip-2005/home.png" x="22" y="426"/> + <button keyCode="back" src="::/samsung-flip-2005/back.png" x="179" y="426"/> + <button keyCode="dpad-up" src="::/samsung-flip-2005/arrow-up.png" x="99" y="399"/> + <button keyCode="dpad-down" src="::/samsung-flip-2005/arrow-down.png" x="99" y="461"/> + <button keyCode="dpad-left" src="::/samsung-flip-2005/arrow-left.png" x="82" y="411"/> + <button keyCode="dpad-right" src="::/samsung-flip-2005/arrow-right.png" x="147" y="411"/> + <button keyCode="dpad-center" src="::/samsung-flip-2005/select.png" x="115" y="431"/> + + <button keyCode="1" src="::/samsung-flip-2005/1.png" x="24" y="533"/> + <button keyCode="2" src="::/samsung-flip-2005/2.png" x="94" y="544"/> + <button keyCode="3" src="::/samsung-flip-2005/3.png" x="173" y="534"/> + <button keyCode="4" src="::/samsung-flip-2005/4.png" x="25" y="576"/> + <button keyCode="5" src="::/samsung-flip-2005/5.png" x="94" y="581"/> + <button keyCode="6" src="::/samsung-flip-2005/6.png" x="172" y="576"/> + <button keyCode="7" src="::/samsung-flip-2005/7.png" x="27" y="619"/> + <button keyCode="8" src="::/samsung-flip-2005/8.png" x="95" y="621"/> + <button keyCode="9" src="::/samsung-flip-2005/9.png" x="168" y="620"/> + <button keyCode="0" src="::/samsung-flip-2005/0.png" x="96" y="658"/> + <button keyCode="star" src="::/samsung-flip-2005/star.png" x="31" y="659"/> + <button keyCode="pound" src="::/samsung-flip-2005/pound.png" x="169" y="659"/> + + <!-- buttons we haven't bothered to create highlight images for, + or that aren't visible on-screen --> + <button keyCode="volume-up"/> + <button keyCode="volume-down"/> + <button keyCode="power"/> + <button keyCode="clear"/> + </view> + + </mode> +</device> + diff --git a/simulator/app/assets/danger-hiptop-m1/background-open.png b/simulator/app/assets/danger-hiptop-m1/background-open.png Binary files differnew file mode 100644 index 000000000..62d2c298f --- /dev/null +++ b/simulator/app/assets/danger-hiptop-m1/background-open.png diff --git a/simulator/app/assets/danger-hiptop-m1/background.png b/simulator/app/assets/danger-hiptop-m1/background.png Binary files differnew file mode 100644 index 000000000..f8c58c724 --- /dev/null +++ b/simulator/app/assets/danger-hiptop-m1/background.png diff --git a/simulator/app/assets/danger-hiptop-m1/layout.xml b/simulator/app/assets/danger-hiptop-m1/layout.xml new file mode 100644 index 000000000..f8d7cba06 --- /dev/null +++ b/simulator/app/assets/danger-hiptop-m1/layout.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" ?> +<!-- Copyright 2005 The Android Open Source Project --> + +<device name="DangerM1"> + <!-- title for menus --> + <title>Danger hiptop M1</title> + + <display name="main" width="240" height="160" format="rgb565" refresh="30"/> + + <keyboard qwerty="1" /> + + <mode name="closed"> + <!-- hiptop when closed --> + <view display="main" x="160" y="69" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <image src="background.png" x="0" y="0"/> + + </view> + </mode> + + <mode name="open"> + <!-- hiptop when flipped open --> + <view display="main" x="172" y="60" rotate="180"> + + <image src="background-open.png" x="0" y="0"/> + </view> + </mode> + +</device> + diff --git a/simulator/app/assets/htc-audiovox-5600/0.png b/simulator/app/assets/htc-audiovox-5600/0.png Binary files differnew file mode 100644 index 000000000..1fa4b7249 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/0.png diff --git a/simulator/app/assets/htc-audiovox-5600/1.png b/simulator/app/assets/htc-audiovox-5600/1.png Binary files differnew file mode 100644 index 000000000..f9393342b --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/1.png diff --git a/simulator/app/assets/htc-audiovox-5600/2.png b/simulator/app/assets/htc-audiovox-5600/2.png Binary files differnew file mode 100644 index 000000000..09be58b53 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/2.png diff --git a/simulator/app/assets/htc-audiovox-5600/3.png b/simulator/app/assets/htc-audiovox-5600/3.png Binary files differnew file mode 100644 index 000000000..91c70c115 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/3.png diff --git a/simulator/app/assets/htc-audiovox-5600/4.png b/simulator/app/assets/htc-audiovox-5600/4.png Binary files differnew file mode 100644 index 000000000..da930ff12 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/4.png diff --git a/simulator/app/assets/htc-audiovox-5600/5.png b/simulator/app/assets/htc-audiovox-5600/5.png Binary files differnew file mode 100644 index 000000000..5c4625a19 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/5.png diff --git a/simulator/app/assets/htc-audiovox-5600/6.png b/simulator/app/assets/htc-audiovox-5600/6.png Binary files differnew file mode 100644 index 000000000..f30996070 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/6.png diff --git a/simulator/app/assets/htc-audiovox-5600/7.png b/simulator/app/assets/htc-audiovox-5600/7.png Binary files differnew file mode 100644 index 000000000..e09b08cff --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/7.png diff --git a/simulator/app/assets/htc-audiovox-5600/8.png b/simulator/app/assets/htc-audiovox-5600/8.png Binary files differnew file mode 100644 index 000000000..c8b3dcc22 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/8.png diff --git a/simulator/app/assets/htc-audiovox-5600/9.png b/simulator/app/assets/htc-audiovox-5600/9.png Binary files differnew file mode 100644 index 000000000..f28421206 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/9.png diff --git a/simulator/app/assets/htc-audiovox-5600/arrow-down.png b/simulator/app/assets/htc-audiovox-5600/arrow-down.png Binary files differnew file mode 100644 index 000000000..08a17ead6 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/arrow-down.png diff --git a/simulator/app/assets/htc-audiovox-5600/arrow-up.png b/simulator/app/assets/htc-audiovox-5600/arrow-up.png Binary files differnew file mode 100644 index 000000000..f2998b9a7 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/arrow-up.png diff --git a/simulator/app/assets/htc-audiovox-5600/back.png b/simulator/app/assets/htc-audiovox-5600/back.png Binary files differnew file mode 100644 index 000000000..4110ec7c5 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/back.png diff --git a/simulator/app/assets/htc-audiovox-5600/background-orange.png b/simulator/app/assets/htc-audiovox-5600/background-orange.png Binary files differnew file mode 100644 index 000000000..b147546a1 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/background-orange.png diff --git a/simulator/app/assets/htc-audiovox-5600/background.png b/simulator/app/assets/htc-audiovox-5600/background.png Binary files differnew file mode 100644 index 000000000..9ffd7f3e0 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/background.png diff --git a/simulator/app/assets/htc-audiovox-5600/home.png b/simulator/app/assets/htc-audiovox-5600/home.png Binary files differnew file mode 100644 index 000000000..a10d29344 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/home.png diff --git a/simulator/app/assets/htc-audiovox-5600/layout.xml b/simulator/app/assets/htc-audiovox-5600/layout.xml new file mode 100644 index 000000000..9e68c0f18 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/layout.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" ?> +<!-- Copyright 2005 The Android Open Source Project --> + +<device name="HTC5600"> + <!-- title for menus --> + <title>HTC AudioVox 5600</title> + + <!-- primary display characteristics --> + <display name="main" width="176" height="220" format="rgb565" refresh="30"/> + + <mode name="default"> + <view display="main" x="36" y="73" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <!-- (phone image is optional) --> + <image src="background.png" x="0" y="0"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="soft-left" src="soft-left.png" x="40" y="312"/> + <button keyCode="soft-right" src="soft-right.png" x="145" y="312"/> + <button keyCode="home" src="home.png" x="57" y="331"/> + <button keyCode="back" src="back.png" x="123" y="333"/> + <button keyCode="dpad-up" src="arrow-up.png" x="59" y="364"/> + <button keyCode="dpad-down" src="arrow-down.png" x="60" y="393"/> + <button keyCode="dpad-left"/> + <button keyCode="dpad-right"/> + <button keyCode="dpad-center" src="select.png" x="103" y="381"/> + + <button keyCode="1" src="1.png" x="30" y="411"/> + <button keyCode="2" src="2.png" x="90" y="409"/> + <button keyCode="3" src="3.png" x="159" y="413"/> + <button keyCode="4" src="4.png" x="32" y="445"/> + <button keyCode="5" src="5.png" x="92" y="445"/> + <button keyCode="6" src="6.png" x="157" y="444"/> + <button keyCode="7" src="7.png" x="28" y="476"/> + <button keyCode="8" src="8.png" x="94" y="480"/> + <button keyCode="9" src="9.png" x="156" y="477"/> + <button keyCode="0" src="0.png" x="97" y="513"/> + <button keyCode="star" src="star.png" x="45" y="509"/> + <button keyCode="pound" src="pound.png" x="155" y="511"/> + + <!-- buttons we haven't bothered to create highlight images for, + or that aren't visible on-screen --> + <button keyCode="volume-up"/> + <button keyCode="volume-down"/> + <button keyCode="power"/> + <button keyCode="clear"/> + + </view> + </mode> + +</device> + diff --git a/simulator/app/assets/htc-audiovox-5600/pound.png b/simulator/app/assets/htc-audiovox-5600/pound.png Binary files differnew file mode 100644 index 000000000..90df1fa48 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/pound.png diff --git a/simulator/app/assets/htc-audiovox-5600/select.png b/simulator/app/assets/htc-audiovox-5600/select.png Binary files differnew file mode 100644 index 000000000..509082c64 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/select.png diff --git a/simulator/app/assets/htc-audiovox-5600/soft-left.png b/simulator/app/assets/htc-audiovox-5600/soft-left.png Binary files differnew file mode 100644 index 000000000..ce8cc5c6e --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/soft-left.png diff --git a/simulator/app/assets/htc-audiovox-5600/soft-right.png b/simulator/app/assets/htc-audiovox-5600/soft-right.png Binary files differnew file mode 100644 index 000000000..f3c76c7b8 --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/soft-right.png diff --git a/simulator/app/assets/htc-audiovox-5600/star.png b/simulator/app/assets/htc-audiovox-5600/star.png Binary files differnew file mode 100644 index 000000000..19aa0575b --- /dev/null +++ b/simulator/app/assets/htc-audiovox-5600/star.png diff --git a/simulator/app/assets/htc-tornado/0.png b/simulator/app/assets/htc-tornado/0.png Binary files differnew file mode 100644 index 000000000..a40e89859 --- /dev/null +++ b/simulator/app/assets/htc-tornado/0.png diff --git a/simulator/app/assets/htc-tornado/1.png b/simulator/app/assets/htc-tornado/1.png Binary files differnew file mode 100644 index 000000000..92c678223 --- /dev/null +++ b/simulator/app/assets/htc-tornado/1.png diff --git a/simulator/app/assets/htc-tornado/2.png b/simulator/app/assets/htc-tornado/2.png Binary files differnew file mode 100644 index 000000000..42a79ecd5 --- /dev/null +++ b/simulator/app/assets/htc-tornado/2.png diff --git a/simulator/app/assets/htc-tornado/3.png b/simulator/app/assets/htc-tornado/3.png Binary files differnew file mode 100644 index 000000000..7e685a803 --- /dev/null +++ b/simulator/app/assets/htc-tornado/3.png diff --git a/simulator/app/assets/htc-tornado/4.png b/simulator/app/assets/htc-tornado/4.png Binary files differnew file mode 100644 index 000000000..c1b8060d3 --- /dev/null +++ b/simulator/app/assets/htc-tornado/4.png diff --git a/simulator/app/assets/htc-tornado/5.png b/simulator/app/assets/htc-tornado/5.png Binary files differnew file mode 100644 index 000000000..29d2607e7 --- /dev/null +++ b/simulator/app/assets/htc-tornado/5.png diff --git a/simulator/app/assets/htc-tornado/6.png b/simulator/app/assets/htc-tornado/6.png Binary files differnew file mode 100644 index 000000000..509aae541 --- /dev/null +++ b/simulator/app/assets/htc-tornado/6.png diff --git a/simulator/app/assets/htc-tornado/7.png b/simulator/app/assets/htc-tornado/7.png Binary files differnew file mode 100644 index 000000000..16087778b --- /dev/null +++ b/simulator/app/assets/htc-tornado/7.png diff --git a/simulator/app/assets/htc-tornado/8.png b/simulator/app/assets/htc-tornado/8.png Binary files differnew file mode 100644 index 000000000..9fac3e6ad --- /dev/null +++ b/simulator/app/assets/htc-tornado/8.png diff --git a/simulator/app/assets/htc-tornado/9.png b/simulator/app/assets/htc-tornado/9.png Binary files differnew file mode 100644 index 000000000..6a357cb03 --- /dev/null +++ b/simulator/app/assets/htc-tornado/9.png diff --git a/simulator/app/assets/htc-tornado/arrow-down.png b/simulator/app/assets/htc-tornado/arrow-down.png Binary files differnew file mode 100644 index 000000000..c00787f30 --- /dev/null +++ b/simulator/app/assets/htc-tornado/arrow-down.png diff --git a/simulator/app/assets/htc-tornado/arrow-left.png b/simulator/app/assets/htc-tornado/arrow-left.png Binary files differnew file mode 100644 index 000000000..1180a94aa --- /dev/null +++ b/simulator/app/assets/htc-tornado/arrow-left.png diff --git a/simulator/app/assets/htc-tornado/arrow-right.png b/simulator/app/assets/htc-tornado/arrow-right.png Binary files differnew file mode 100644 index 000000000..6a511d225 --- /dev/null +++ b/simulator/app/assets/htc-tornado/arrow-right.png diff --git a/simulator/app/assets/htc-tornado/arrow-up.png b/simulator/app/assets/htc-tornado/arrow-up.png Binary files differnew file mode 100644 index 000000000..da508713d --- /dev/null +++ b/simulator/app/assets/htc-tornado/arrow-up.png diff --git a/simulator/app/assets/htc-tornado/back.png b/simulator/app/assets/htc-tornado/back.png Binary files differnew file mode 100644 index 000000000..d12273d67 --- /dev/null +++ b/simulator/app/assets/htc-tornado/back.png diff --git a/simulator/app/assets/htc-tornado/background.png b/simulator/app/assets/htc-tornado/background.png Binary files differnew file mode 100644 index 000000000..09169b85c --- /dev/null +++ b/simulator/app/assets/htc-tornado/background.png diff --git a/simulator/app/assets/htc-tornado/home.png b/simulator/app/assets/htc-tornado/home.png Binary files differnew file mode 100644 index 000000000..90775a8c6 --- /dev/null +++ b/simulator/app/assets/htc-tornado/home.png diff --git a/simulator/app/assets/htc-tornado/internet.png b/simulator/app/assets/htc-tornado/internet.png Binary files differnew file mode 100644 index 000000000..47f1bb48f --- /dev/null +++ b/simulator/app/assets/htc-tornado/internet.png diff --git a/simulator/app/assets/htc-tornado/layout.xml b/simulator/app/assets/htc-tornado/layout.xml new file mode 100644 index 000000000..55c1d6546 --- /dev/null +++ b/simulator/app/assets/htc-tornado/layout.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" ?> +<!-- Copyright 2005 The Android Open Source Project --> + +<device name="HTC_Tornado"> + <!-- title for menus --> + <title>HTC Tornado</title> + + <!-- primary display characteristics --> + <display name="main" width="240" height="320" format="rgb565" refresh="30"/> + + <!-- display is 33.84mm x 45.12mm --> + + <mode name="default"> + <view display="main" x="58" y="117" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <!-- (phone image is optional) --> + <image src="background.png" x="0" y="0"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="soft-left" src="soft-left.png" x="49" y="450"/> + <button keyCode="soft-right" src="soft-right.png" x="249" y="450"/> + <button keyCode="home" src="home.png" x="115" y="450"/> + <button keyCode="back" src="back.png" x="182" y="450"/> + <button keyCode="dpad-up" src="arrow-up.png" x="159" y="542"/> + <button keyCode="dpad-down" src="arrow-down.png" x="160" y="573"/> + <button keyCode="dpad-left" src="arrow-left.png" x="152" y="549"/> + <button keyCode="dpad-right" src="arrow-right.png" x="183" y="549"/> + <button keyCode="dpad-center" src="select.png" x="167" y="556"/> + + <button keyCode="1" src="1.png" x="51" y="595"/> + <button keyCode="2" src="2.png" x="133" y="595"/> + <button keyCode="3" src="3.png" x="221" y="595"/> + <button keyCode="4" src="4.png" x="51" y="633"/> + <button keyCode="5" src="5.png" x="133" y="633"/> + <button keyCode="6" src="6.png" x="221" y="633"/> + <button keyCode="7" src="7.png" x="51" y="666"/> + <button keyCode="8" src="8.png" x="133" y="666"/> + <button keyCode="9" src="9.png" x="221" y="666"/> + <button keyCode="0" src="0.png" x="133" y="699"/> + <button keyCode="star" src="star.png" x="51" y="699"/> + <button keyCode="pound" src="pound.png" x="221" y="699"/> + + <button keyCode="phone-dial" src="phone-dial.png" x="51" y="547"/> + <button keyCode="phone-hangup" src="phone-hangup.png" x="221" y="547"/> + + <button keyCode="internet" src="internet.png" x="52" y="490"/> + <button keyCode="skip-backward" src="skip-backward.png" x="121" y="490"/> + <button keyCode="pause-play" src="pause-play.png" x="191" y="490"/> + <button keyCode="skip-forward" src="skip-forward.png" x="260" y="490"/> + + + <!-- buttons we haven't bothered to create highlight images for, + or that aren't visible on-screen --> + <button keyCode="volume-up"/> + <button keyCode="volume-down"/> + <button keyCode="power"/> + <button keyCode="clear"/> + + </view> + </mode> + +</device> + diff --git a/simulator/app/assets/htc-tornado/pause-play.png b/simulator/app/assets/htc-tornado/pause-play.png Binary files differnew file mode 100644 index 000000000..ae74d9fd7 --- /dev/null +++ b/simulator/app/assets/htc-tornado/pause-play.png diff --git a/simulator/app/assets/htc-tornado/phone-dial.png b/simulator/app/assets/htc-tornado/phone-dial.png Binary files differnew file mode 100644 index 000000000..30bdf9e0f --- /dev/null +++ b/simulator/app/assets/htc-tornado/phone-dial.png diff --git a/simulator/app/assets/htc-tornado/phone-hangup.png b/simulator/app/assets/htc-tornado/phone-hangup.png Binary files differnew file mode 100644 index 000000000..68fe47b17 --- /dev/null +++ b/simulator/app/assets/htc-tornado/phone-hangup.png diff --git a/simulator/app/assets/htc-tornado/pound.png b/simulator/app/assets/htc-tornado/pound.png Binary files differnew file mode 100644 index 000000000..92e49cb6b --- /dev/null +++ b/simulator/app/assets/htc-tornado/pound.png diff --git a/simulator/app/assets/htc-tornado/select.png b/simulator/app/assets/htc-tornado/select.png Binary files differnew file mode 100644 index 000000000..a89db9a72 --- /dev/null +++ b/simulator/app/assets/htc-tornado/select.png diff --git a/simulator/app/assets/htc-tornado/skip-backward.png b/simulator/app/assets/htc-tornado/skip-backward.png Binary files differnew file mode 100644 index 000000000..263b3d6cf --- /dev/null +++ b/simulator/app/assets/htc-tornado/skip-backward.png diff --git a/simulator/app/assets/htc-tornado/skip-forward.png b/simulator/app/assets/htc-tornado/skip-forward.png Binary files differnew file mode 100644 index 000000000..e069db144 --- /dev/null +++ b/simulator/app/assets/htc-tornado/skip-forward.png diff --git a/simulator/app/assets/htc-tornado/soft-left.png b/simulator/app/assets/htc-tornado/soft-left.png Binary files differnew file mode 100644 index 000000000..ab6dff19a --- /dev/null +++ b/simulator/app/assets/htc-tornado/soft-left.png diff --git a/simulator/app/assets/htc-tornado/soft-right.png b/simulator/app/assets/htc-tornado/soft-right.png Binary files differnew file mode 100644 index 000000000..590982e29 --- /dev/null +++ b/simulator/app/assets/htc-tornado/soft-right.png diff --git a/simulator/app/assets/htc-tornado/star.png b/simulator/app/assets/htc-tornado/star.png Binary files differnew file mode 100644 index 000000000..9b7d79a2e --- /dev/null +++ b/simulator/app/assets/htc-tornado/star.png diff --git a/simulator/app/assets/motorola-q/back.png b/simulator/app/assets/motorola-q/back.png Binary files differnew file mode 100644 index 000000000..6be251282 --- /dev/null +++ b/simulator/app/assets/motorola-q/back.png diff --git a/simulator/app/assets/motorola-q/background.png b/simulator/app/assets/motorola-q/background.png Binary files differnew file mode 100644 index 000000000..d9b30adf7 --- /dev/null +++ b/simulator/app/assets/motorola-q/background.png diff --git a/simulator/app/assets/motorola-q/down.png b/simulator/app/assets/motorola-q/down.png Binary files differnew file mode 100644 index 000000000..bbe3a188d --- /dev/null +++ b/simulator/app/assets/motorola-q/down.png diff --git a/simulator/app/assets/motorola-q/end.png b/simulator/app/assets/motorola-q/end.png Binary files differnew file mode 100644 index 000000000..daff1630e --- /dev/null +++ b/simulator/app/assets/motorola-q/end.png diff --git a/simulator/app/assets/motorola-q/home.png b/simulator/app/assets/motorola-q/home.png Binary files differnew file mode 100644 index 000000000..4f65f9123 --- /dev/null +++ b/simulator/app/assets/motorola-q/home.png diff --git a/simulator/app/assets/motorola-q/layout.xml b/simulator/app/assets/motorola-q/layout.xml new file mode 100644 index 000000000..c48bce651 --- /dev/null +++ b/simulator/app/assets/motorola-q/layout.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" ?> +<!-- Copyright 2006 The Android Open Source Project --> + +<device name="MotorolaQ"> + <!-- title for menus --> + <title>Motorola Q</title> + + <display name="main" width="320" height="240" format="rgb565" refresh="30"/> + + <keyboard qwerty="1" /> + + <mode name="default"> + <!-- hiptop when closed --> + <view display="main" x="58" y="121" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <image src="background.png" x="0" y="0"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="soft-left" src="menu-left.png" x="35" y="385"/> + <button keyCode="soft-right" src="menu-right.png" x="270" y="385"/> + <button keyCode="home" src="home.png" x="92" y="425"/> + <button keyCode="back" src="back.png" x="264" y="425"/> + <button keyCode="dpad-up" src="up.png" x="166" y="400"/> + <button keyCode="dpad-down" src="down.png" x="166" y="444"/> + <button keyCode="dpad-left" src="left.png" x="158" y="406"/> + <button keyCode="dpad-right" src="right.png" x="224" y="406"/> + <button keyCode="dpad-center" src="select.png" x="194" y="419"/> + + <button keyCode="phone-dial" src="send.png" x="35" y="421"/> + <button keyCode="phone-hangup" src="end.png" x="333" y="422"/> + </view> + </mode> + +</device> + diff --git a/simulator/app/assets/motorola-q/left.png b/simulator/app/assets/motorola-q/left.png Binary files differnew file mode 100644 index 000000000..3204f9b4f --- /dev/null +++ b/simulator/app/assets/motorola-q/left.png diff --git a/simulator/app/assets/motorola-q/menu-left.png b/simulator/app/assets/motorola-q/menu-left.png Binary files differnew file mode 100644 index 000000000..4d1bc11c2 --- /dev/null +++ b/simulator/app/assets/motorola-q/menu-left.png diff --git a/simulator/app/assets/motorola-q/menu-right.png b/simulator/app/assets/motorola-q/menu-right.png Binary files differnew file mode 100644 index 000000000..58f31c6df --- /dev/null +++ b/simulator/app/assets/motorola-q/menu-right.png diff --git a/simulator/app/assets/motorola-q/right.png b/simulator/app/assets/motorola-q/right.png Binary files differnew file mode 100644 index 000000000..b6190c9a0 --- /dev/null +++ b/simulator/app/assets/motorola-q/right.png diff --git a/simulator/app/assets/motorola-q/select.png b/simulator/app/assets/motorola-q/select.png Binary files differnew file mode 100644 index 000000000..343fb45a1 --- /dev/null +++ b/simulator/app/assets/motorola-q/select.png diff --git a/simulator/app/assets/motorola-q/send.png b/simulator/app/assets/motorola-q/send.png Binary files differnew file mode 100644 index 000000000..b3ebcd50d --- /dev/null +++ b/simulator/app/assets/motorola-q/send.png diff --git a/simulator/app/assets/motorola-q/up.png b/simulator/app/assets/motorola-q/up.png Binary files differnew file mode 100644 index 000000000..7df6736f5 --- /dev/null +++ b/simulator/app/assets/motorola-q/up.png diff --git a/simulator/app/assets/panasonic-x70/arrow-down.png b/simulator/app/assets/panasonic-x70/arrow-down.png Binary files differnew file mode 100644 index 000000000..7ab16b4a4 --- /dev/null +++ b/simulator/app/assets/panasonic-x70/arrow-down.png diff --git a/simulator/app/assets/panasonic-x70/arrow-up.png b/simulator/app/assets/panasonic-x70/arrow-up.png Binary files differnew file mode 100644 index 000000000..6fb2454bb --- /dev/null +++ b/simulator/app/assets/panasonic-x70/arrow-up.png diff --git a/simulator/app/assets/panasonic-x70/back.png b/simulator/app/assets/panasonic-x70/back.png Binary files differnew file mode 100644 index 000000000..04b91ef01 --- /dev/null +++ b/simulator/app/assets/panasonic-x70/back.png diff --git a/simulator/app/assets/panasonic-x70/background.png b/simulator/app/assets/panasonic-x70/background.png Binary files differnew file mode 100644 index 000000000..25bc3ebea --- /dev/null +++ b/simulator/app/assets/panasonic-x70/background.png diff --git a/simulator/app/assets/panasonic-x70/home.png b/simulator/app/assets/panasonic-x70/home.png Binary files differnew file mode 100644 index 000000000..12ac7bb99 --- /dev/null +++ b/simulator/app/assets/panasonic-x70/home.png diff --git a/simulator/app/assets/panasonic-x70/layout.xml b/simulator/app/assets/panasonic-x70/layout.xml new file mode 100644 index 000000000..0cb8bbdb4 --- /dev/null +++ b/simulator/app/assets/panasonic-x70/layout.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" ?> +<!-- Copyright 2005 The Android Open Source Project --> + +<device name="PanasonicX70"> + <!-- title for menus --> + <title>Panasonic X70</title> + + <display name="main" width="176" height="220" format="rgb565" refresh="30"/> + + <mode name="default"> + <!-- primary display characteristics --> + <view display="main" x="39" y="43" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <!-- (phone image is optional) --> + <image src="background.png" x="0" y="0"/> + + <button keyCode="soft-left" src="soft-left.png" x="45" y="324"/> + <button keyCode="soft-right" src="soft-right.png" x="146" y="324"/> + <button keyCode="home" src="home.png" x="21" y="345"/> + <button keyCode="back" src="back.png" x="180" y="347"/> + <button keyCode="dpad-up" src="arrow-up.png" x="88" y="348"/> + <button keyCode="dpad-down" src="arrow-down.png" x="95" y="414"/> + <button keyCode="dpad-left"/> + <button keyCode="dpad-right"/> + <button keyCode="dpad-center" src="select.png" x="101" y="377"/> + + <button keyCode="1"/> + <button keyCode="2"/> + <button keyCode="3"/> + <button keyCode="4"/> + <button keyCode="5"/> + <button keyCode="6"/> + <button keyCode="7"/> + <button keyCode="8"/> + <button keyCode="9"/> + <button keyCode="0"/> + <button keyCode="star"/> + <button keyCode="pound"/> + + <button keyCode="volume-up"/> + <button keyCode="volume-down"/> + <button keyCode="power"/> + <button keyCode="clear"/> + + </view> + </mode> + +</device> + diff --git a/simulator/app/assets/panasonic-x70/select.png b/simulator/app/assets/panasonic-x70/select.png Binary files differnew file mode 100644 index 000000000..993ecad72 --- /dev/null +++ b/simulator/app/assets/panasonic-x70/select.png diff --git a/simulator/app/assets/panasonic-x70/soft-left.png b/simulator/app/assets/panasonic-x70/soft-left.png Binary files differnew file mode 100644 index 000000000..c384bb7e5 --- /dev/null +++ b/simulator/app/assets/panasonic-x70/soft-left.png diff --git a/simulator/app/assets/panasonic-x70/soft-right.png b/simulator/app/assets/panasonic-x70/soft-right.png Binary files differnew file mode 100644 index 000000000..948e40f6f --- /dev/null +++ b/simulator/app/assets/panasonic-x70/soft-right.png diff --git a/simulator/app/assets/samsung-flip-2005/0.png b/simulator/app/assets/samsung-flip-2005/0.png Binary files differnew file mode 100644 index 000000000..839e6d1ad --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/0.png diff --git a/simulator/app/assets/samsung-flip-2005/1.png b/simulator/app/assets/samsung-flip-2005/1.png Binary files differnew file mode 100644 index 000000000..499cf6c8b --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/1.png diff --git a/simulator/app/assets/samsung-flip-2005/2.png b/simulator/app/assets/samsung-flip-2005/2.png Binary files differnew file mode 100644 index 000000000..93207cc26 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/2.png diff --git a/simulator/app/assets/samsung-flip-2005/3.png b/simulator/app/assets/samsung-flip-2005/3.png Binary files differnew file mode 100644 index 000000000..499efdb2e --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/3.png diff --git a/simulator/app/assets/samsung-flip-2005/4.png b/simulator/app/assets/samsung-flip-2005/4.png Binary files differnew file mode 100644 index 000000000..775b15ddb --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/4.png diff --git a/simulator/app/assets/samsung-flip-2005/5.png b/simulator/app/assets/samsung-flip-2005/5.png Binary files differnew file mode 100644 index 000000000..b8a57236a --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/5.png diff --git a/simulator/app/assets/samsung-flip-2005/6.png b/simulator/app/assets/samsung-flip-2005/6.png Binary files differnew file mode 100644 index 000000000..b6e0de968 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/6.png diff --git a/simulator/app/assets/samsung-flip-2005/7.png b/simulator/app/assets/samsung-flip-2005/7.png Binary files differnew file mode 100644 index 000000000..541618626 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/7.png diff --git a/simulator/app/assets/samsung-flip-2005/8.png b/simulator/app/assets/samsung-flip-2005/8.png Binary files differnew file mode 100644 index 000000000..2dfcae34e --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/8.png diff --git a/simulator/app/assets/samsung-flip-2005/9.png b/simulator/app/assets/samsung-flip-2005/9.png Binary files differnew file mode 100644 index 000000000..6c11eed87 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/9.png diff --git a/simulator/app/assets/samsung-flip-2005/abc.png b/simulator/app/assets/samsung-flip-2005/abc.png Binary files differnew file mode 100644 index 000000000..9f43afec7 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/abc.png diff --git a/simulator/app/assets/samsung-flip-2005/arrow-down.png b/simulator/app/assets/samsung-flip-2005/arrow-down.png Binary files differnew file mode 100644 index 000000000..0f7ea7b38 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/arrow-down.png diff --git a/simulator/app/assets/samsung-flip-2005/arrow-left.png b/simulator/app/assets/samsung-flip-2005/arrow-left.png Binary files differnew file mode 100644 index 000000000..ad1982f60 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/arrow-left.png diff --git a/simulator/app/assets/samsung-flip-2005/arrow-right.png b/simulator/app/assets/samsung-flip-2005/arrow-right.png Binary files differnew file mode 100644 index 000000000..bf9cf48ba --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/arrow-right.png diff --git a/simulator/app/assets/samsung-flip-2005/arrow-up.png b/simulator/app/assets/samsung-flip-2005/arrow-up.png Binary files differnew file mode 100644 index 000000000..ab62e33a3 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/arrow-up.png diff --git a/simulator/app/assets/samsung-flip-2005/back-background.png b/simulator/app/assets/samsung-flip-2005/back-background.png Binary files differnew file mode 100644 index 000000000..741db99ea --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/back-background.png diff --git a/simulator/app/assets/samsung-flip-2005/back.png b/simulator/app/assets/samsung-flip-2005/back.png Binary files differnew file mode 100644 index 000000000..9eb9a176b --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/back.png diff --git a/simulator/app/assets/samsung-flip-2005/background-176_208.png b/simulator/app/assets/samsung-flip-2005/background-176_208.png Binary files differnew file mode 100644 index 000000000..1ed124830 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/background-176_208.png diff --git a/simulator/app/assets/samsung-flip-2005/background-176x220.png b/simulator/app/assets/samsung-flip-2005/background-176x220.png Binary files differnew file mode 100644 index 000000000..361304e9e --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/background-176x220.png diff --git a/simulator/app/assets/samsung-flip-2005/clear.png b/simulator/app/assets/samsung-flip-2005/clear.png Binary files differnew file mode 100644 index 000000000..f9c421c8f --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/clear.png diff --git a/simulator/app/assets/samsung-flip-2005/end.png b/simulator/app/assets/samsung-flip-2005/end.png Binary files differnew file mode 100644 index 000000000..5d0056cb6 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/end.png diff --git a/simulator/app/assets/samsung-flip-2005/home-background.png b/simulator/app/assets/samsung-flip-2005/home-background.png Binary files differnew file mode 100644 index 000000000..3ceee9c22 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/home-background.png diff --git a/simulator/app/assets/samsung-flip-2005/home.png b/simulator/app/assets/samsung-flip-2005/home.png Binary files differnew file mode 100644 index 000000000..8510b7eb2 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/home.png diff --git a/simulator/app/assets/samsung-flip-2005/layout.xml b/simulator/app/assets/samsung-flip-2005/layout.xml new file mode 100644 index 000000000..3bc2dddc3 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/layout.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" ?> +<!-- Copyright 2005 The Android Open Source Project --> + +<device name="Samsung1234"> + <!-- title for menus --> + <title>Samsung Flip Phone</title> + + <!-- primary display characteristics --> + <display name="main" width="176" height="220" format="rgb565" refresh="30"/> + + <mode name="default"> + <!-- our view of the device that shows the main display --> + <view display="main" x="49" y="73" rotate="0"> + + <!-- surrounding device image and "patches", drawn in order --> + <!-- (phone image is optional) --> + <image src="background-176x220.png" x="0" y="0"/> + <image src="home-background.png" x="22" y="426"/> + <image src="back-background.png" x="179" y="426"/> + + <!-- buttons for which we have highlight images --> + <button keyCode="soft-left" src="soft-left.png" x="24" y="388"/> + <button keyCode="soft-right" src="soft-right.png" x="182" y="388"/> + <button keyCode="home" src="home.png" x="22" y="426"/> + <button keyCode="back" src="back.png" x="179" y="426"/> + <button keyCode="dpad-up" src="arrow-up.png" x="99" y="399"/> + <button keyCode="dpad-down" src="arrow-down.png" x="99" y="461"/> + <button keyCode="dpad-left" src="arrow-left.png" x="82" y="411"/> + <button keyCode="dpad-right" src="arrow-right.png" x="147" y="411"/> + <button keyCode="dpad-center" src="select.png" x="115" y="431"/> + + <button keyCode="1" src="1.png" x="24" y="533"/> + <button keyCode="2" src="2.png" x="94" y="544"/> + <button keyCode="3" src="3.png" x="173" y="534"/> + <button keyCode="4" src="4.png" x="25" y="576"/> + <button keyCode="5" src="5.png" x="94" y="581"/> + <button keyCode="6" src="6.png" x="172" y="576"/> + <button keyCode="7" src="7.png" x="27" y="619"/> + <button keyCode="8" src="8.png" x="95" y="621"/> + <button keyCode="9" src="9.png" x="168" y="620"/> + <button keyCode="0" src="0.png" x="96" y="658"/> + <button keyCode="star" src="star.png" x="31" y="659"/> + <button keyCode="pound" src="pound.png" x="169" y="659"/> + + <!-- buttons we haven't bothered to create highlight images for, + or that aren't visible on-screen --> + <button keyCode="volume-up"/> + <button keyCode="volume-down"/> + <button keyCode="power"/> + <button keyCode="clear"/> + + </view> + </mode> + +</device> + diff --git a/simulator/app/assets/samsung-flip-2005/menu.png b/simulator/app/assets/samsung-flip-2005/menu.png Binary files differnew file mode 100644 index 000000000..7ac077c3a --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/menu.png diff --git a/simulator/app/assets/samsung-flip-2005/pound.png b/simulator/app/assets/samsung-flip-2005/pound.png Binary files differnew file mode 100644 index 000000000..22440bc7a --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/pound.png diff --git a/simulator/app/assets/samsung-flip-2005/select.png b/simulator/app/assets/samsung-flip-2005/select.png Binary files differnew file mode 100644 index 000000000..1c735f85d --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/select.png diff --git a/simulator/app/assets/samsung-flip-2005/send.png b/simulator/app/assets/samsung-flip-2005/send.png Binary files differnew file mode 100644 index 000000000..23cea52e5 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/send.png diff --git a/simulator/app/assets/samsung-flip-2005/soft-left.png b/simulator/app/assets/samsung-flip-2005/soft-left.png Binary files differnew file mode 100644 index 000000000..224d28145 --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/soft-left.png diff --git a/simulator/app/assets/samsung-flip-2005/soft-right.png b/simulator/app/assets/samsung-flip-2005/soft-right.png Binary files differnew file mode 100644 index 000000000..a8eec973a --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/soft-right.png diff --git a/simulator/app/assets/samsung-flip-2005/star.png b/simulator/app/assets/samsung-flip-2005/star.png Binary files differnew file mode 100644 index 000000000..457348dbc --- /dev/null +++ b/simulator/app/assets/samsung-flip-2005/star.png diff --git a/simulator/app/help/unnamed.htb b/simulator/app/help/unnamed.htb Binary files differnew file mode 100644 index 000000000..3933e6955 --- /dev/null +++ b/simulator/app/help/unnamed.htb diff --git a/simulator/wrapsim/Android.mk b/simulator/wrapsim/Android.mk new file mode 100644 index 000000000..7ac8359a8 --- /dev/null +++ b/simulator/wrapsim/Android.mk @@ -0,0 +1,53 @@ +# Copyright 2007 The Android Open Source Project + +# +# Build instructions for simulator LD_PRELOAD wrapper. +# +ifneq ($(TARGET_ARCH),arm) + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + BitVector.c \ + DevAudio.c \ + DevConsoleTty.c \ + DevEvent.c \ + DevFb.c \ + DevLog.c \ + DevPower.c \ + DevVibrator.c \ + FakeDev.c \ + Init.c \ + Intercept.c \ + Log.c \ + SimMgr.c + +LOCAL_C_INCLUDES += prebuilt/common/esd + +LOCAL_MODULE := libwrapsim + +# Relying on other Android libraries is probably a bad idea, since any +# library or system calls they make could lead to recursive behavior. +# +#LOCAL_SHARED_LIBRARIES += + +LOCAL_LDLIBS += -lpthread -ldl -lesd + +include $(BUILD_SHARED_LIBRARY) + + + +# +# Build instructions for simulator runtime launch wrapper. +# +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + LaunchWrapper.c + +LOCAL_MODULE := launch-wrapper +include $(BUILD_EXECUTABLE) + +endif +# ifneq ($(TARGET_ARCH),arm) diff --git a/simulator/wrapsim/BitVector.c b/simulator/wrapsim/BitVector.c new file mode 100644 index 000000000..aaa14085e --- /dev/null +++ b/simulator/wrapsim/BitVector.c @@ -0,0 +1,102 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Simple bit vector. + */ +#include "Common.h" + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> + +#define kBitVectorGrowth 4 /* increase by 4 uint32_t when limit hit */ + + +/* + * Allocate a bit vector with enough space to hold at least the specified + * number of bits. + */ +BitVector* wsAllocBitVector(int startBits, int isExpandable) +{ + BitVector* bv; + int count; + + assert(sizeof(bv->storage[0]) == 4); /* assuming 32-bit units */ + assert(startBits > 0); + + bv = (BitVector*) malloc(sizeof(BitVector)); + + count = (startBits + 31) >> 5; + + bv->storageSize = count; + bv->isExpandable = isExpandable; + bv->storage = (uint32_t*) malloc(count * sizeof(uint32_t)); + memset(bv->storage, 0xff, count * sizeof(uint32_t)); + return bv; +} + +/* + * Free a BitVector. + */ +void wsFreeBitVector(BitVector* pBits) +{ + if (pBits == NULL) + return; + + free(pBits->storage); + free(pBits); +} + +/* + * "Allocate" the first-available bit in the bitmap. + * + * This is not synchronized. The caller is expected to hold some sort of + * lock that prevents multiple threads from executing simultaneously in + * dvmAllocBit/dvmFreeBit. + * + * The bitmap indicates which resources are free, so we use '1' to indicate + * available and '0' to indicate allocated. + */ +int wsAllocBit(BitVector* pBits) +{ + int word, bit; + +retry: + for (word = 0; word < pBits->storageSize; word++) { + if (pBits->storage[word] != 0) { + /* + * There are unallocated bits in this word. Return the first. + */ + bit = ffs(pBits->storage[word]) -1; + assert(bit >= 0 && bit < 32); + pBits->storage[word] &= ~(1 << bit); + return (word << 5) | bit; + } + } + + /* + * Ran out of space, allocate more if we're allowed to. + */ + if (!pBits->isExpandable) + return -1; + + pBits->storage = realloc(pBits->storage, + (pBits->storageSize + kBitVectorGrowth) * sizeof(uint32_t)); + memset(&pBits->storage[pBits->storageSize], 0xff, + kBitVectorGrowth * sizeof(uint32_t)); + pBits->storageSize += kBitVectorGrowth; + goto retry; +} + +/* + * Mark the specified bit as "free". + */ +void wsFreeBit(BitVector* pBits, int num) +{ + assert(num >= 0 && + num < (int) pBits->storageSize * (int)sizeof(uint32_t) * 8); + + pBits->storage[num >> 5] |= 1 << (num & 0x1f); +} + diff --git a/simulator/wrapsim/BitVector.h b/simulator/wrapsim/BitVector.h new file mode 100644 index 000000000..048cf9123 --- /dev/null +++ b/simulator/wrapsim/BitVector.h @@ -0,0 +1,34 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Simple bit vector. + */ +#ifndef _WRAPSIM_BITVECTOR_H +#define _WRAPSIM_BITVECTOR_H + +#include <stdint.h> + +/* + * Expanding bitmap, used for tracking resources. Bits are numbered starting + * from zero. + */ +typedef struct BitVector { + int isExpandable; /* expand bitmap if we run out? */ + int storageSize; /* current size, in 32-bit words */ + uint32_t* storage; +} BitVector; + +/* allocate a bit vector with enough space to hold "startBits" bits */ +BitVector* wsAllocBitVector(int startBits, int isExpandable); +void wsFreeBitVector(BitVector* pBits); + +/* + * Set/clear a single bit; assumes external synchronization. + * + * We always allocate the first possible bit. If we run out of space in + * the bitmap, and it's not marked expandable, dvmAllocBit returns -1. + */ +int wsAllocBit(BitVector* pBits); +void wsFreeBit(BitVector* pBits, int num); + +#endif /*_WRAPSIM_BITVECTOR_H*/ diff --git a/simulator/wrapsim/Common.h b/simulator/wrapsim/Common.h new file mode 100644 index 000000000..a9c3bb847 --- /dev/null +++ b/simulator/wrapsim/Common.h @@ -0,0 +1,18 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Common defines and includes. + */ +#ifndef _WRAPSIM_COMMON_H +#define _WRAPSIM_COMMON_H + +#include <unistd.h> +#include <sys/types.h> + +#include "BitVector.h" +#include "FakeDev.h" +#include "Log.h" +#include "SimMgr.h" +#include "Globals.h" + +#endif /*_WRAPSIM_COMMON_H*/ diff --git a/simulator/wrapsim/DevAudio.c b/simulator/wrapsim/DevAudio.c new file mode 100644 index 000000000..0f52b81d8 --- /dev/null +++ b/simulator/wrapsim/DevAudio.c @@ -0,0 +1,124 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Audio output device. + */ +#include "Common.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <esd.h> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/input.h> + + +/* + * Input event device state. + */ +typedef struct AudioState { + int fd; + int sourceId; + int esdVol; + int streamType; +} AudioState; + +/* + * Set some stuff up. + */ +static int configureInitialState(const char* pathName, AudioState* audioState) +{ + esd_player_info_t *pi; + audioState->fd = -1; + audioState->sourceId = -1; + audioState->esdVol = -1; + audioState->streamType = 0; + + int format = ESD_BITS16 | ESD_STEREO | ESD_STREAM | ESD_PLAY; + char namestring[] = "Android Audio XXXXXXXX"; + sprintf(namestring,"Android Audio %08x", (unsigned int)audioState); + int esd_fd = esd_play_stream_fallback(format, 44100, NULL, namestring); + if (esd_fd > 0) { + // find the source_id for this stream + int mix = esd_open_sound(NULL); + if (mix > 0) { + esd_info_t *info = esd_get_all_info(mix); + + if (info) { + for(pi = info->player_list; pi; pi = pi->next) { + if(strcmp(pi->name, namestring) == 0) { + audioState->sourceId = pi->source_id; + break; + } + } + esd_free_all_info(info); + } + esd_close(mix); + } + audioState->fd = esd_fd; + return 0; + } + printf("Couldn't open audio device. Faking it.\n"); + return 0; +} + +/* + * Return the next available input event. + * + * We just pass this through to the real "write", since "fd" is real. + */ +static ssize_t writeAudio(FakeDev* dev, int fd, const void* buf, size_t count) +{ + AudioState *state = (AudioState*)dev->state; + if (state->fd >= 0) + return _ws_write(state->fd, buf, count); + + // fake timing + usleep(count * 10000 / 441 * 4); + return count; +} + +/* + * Handle event ioctls. + */ +static int ioctlAudio(FakeDev* dev, int fd, int request, void* argp) +{ + return -1; +} + +/* + * Free up our state before closing down the fake descriptor. + */ +static int closeAudio(FakeDev* dev, int fd) +{ + AudioState *state = (AudioState*)dev->state; + close(state->fd); + free(state); + dev->state = NULL; + return 0; +} + +/* + * Open an audio output device. + */ +FakeDev* wsOpenDevAudio(const char* pathName, int flags) +{ + FakeDev* newDev = wsCreateFakeDev(pathName); + if (newDev != NULL) { + newDev->write = writeAudio; + newDev->ioctl = ioctlAudio; + newDev->close = closeAudio; + + AudioState* eventState = calloc(1, sizeof(AudioState)); + + if (configureInitialState(pathName, eventState) != 0) { + free(eventState); + return NULL; + } + newDev->state = eventState; + } + + return newDev; +} diff --git a/simulator/wrapsim/DevConsoleTty.c b/simulator/wrapsim/DevConsoleTty.c new file mode 100644 index 000000000..166d6489e --- /dev/null +++ b/simulator/wrapsim/DevConsoleTty.c @@ -0,0 +1,53 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Console tty device. + */ +#include "Common.h" + +#include <string.h> + +#include <sys/ioctl.h> +#include <linux/vt.h> + + +/* + * Handle the various console ioctls, most of which we can just ignore. + */ +static int ioctlConsoleTty(FakeDev* dev, int fd, int request, void* argp) +{ + wsLog("%s: ioctl(0x%x, %p)\n", dev->debugName, request, argp); + switch (request) { + case VT_GETSTATE: // struct vt_stat* + /* + * Looks like they want vs.v_active. This just gets fed back into + * another console ioctl, so we don't really need to do anything. + * We zero out the struct so the data will at least appear to be + * initialized. + */ + memset(argp, 0, sizeof(struct vt_stat)); + break; + case VT_OPENQRY: // int* + /* they want the console number */ + *(int*)argp = 123; + break; + default: + /* ignore anything we don't understand */ + break; + } + + return 0; +} + +/* + * Open the console TTY device, which responds to a collection of ioctl()s. + */ +FakeDev* wsOpenDevConsoleTty(const char* pathName, int flags) +{ + FakeDev* newDev = wsCreateFakeDev(pathName); + if (newDev != NULL) { + newDev->ioctl = ioctlConsoleTty; + } + return newDev; +} + diff --git a/simulator/wrapsim/DevEvent.c b/simulator/wrapsim/DevEvent.c new file mode 100644 index 000000000..692856ecd --- /dev/null +++ b/simulator/wrapsim/DevEvent.c @@ -0,0 +1,414 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Input event device. + */ +#include "Common.h" + +#include <stdlib.h> +#include <string.h> + +#include <fcntl.h> +#include <sys/ioctl.h> +#include <linux/input.h> + + +/* + * Input event device state. + */ +typedef struct EventState { + struct input_id ident; + + char* name; + char* location; + char* idstr; + int protoVersion; +} EventState; + +/* + * Key bit mask, for EVIOCGBIT(EV_KEY). + * + * (For now, just pretend to be a "goldfish" like the emulator.) + */ +static const unsigned char gKeyBitMask[64] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +/* + * Set some stuff up. + */ +static void configureInitialState(const char* pathName, EventState* eventState) +{ + /* + * Swim like a goldfish. + */ + eventState->ident.bustype = 0; + eventState->ident.vendor = 0; + eventState->ident.product = 0; + eventState->ident.version = 0; + + eventState->name = strdup(gWrapSim.keyMap); + eventState->location = strdup(""); + eventState->idstr = strdup(""); + eventState->protoVersion = 0x010000; +} + +/* + * Free up the state structure. + */ +static void freeState(EventState* eventState) +{ + if (eventState != NULL) { + free(eventState->name); + free(eventState->location); + free(eventState->idstr); + free(eventState); + } +} + +/* + * Handle one of the EVIOCGABS requests. + * + * Currently not doing much here. + */ +static void handleAbsGet(int reqIdx, void* argp) +{ + struct input_absinfo info; + + switch (reqIdx) { + case ABS_X: + wsLog(" req for abs X\n"); + break; + case ABS_Y: + wsLog(" req for abs Y\n"); + break; + case ABS_PRESSURE: + wsLog(" req for abs PRESSURE\n"); + break; + case ABS_TOOL_WIDTH: + wsLog(" req for abs TOOL_WIDTH\n"); + break; + default: + wsLog(" req for unexpected event abs 0x%02x\n", reqIdx); + break; + } + + memset(&info, 0, sizeof(info)); + memcpy(argp, &info, sizeof(struct input_absinfo)); +} + +/* + * Return the next available input event. + * + * We just pass this through to the real "read", since "fd" is real. + */ +static ssize_t readEvent(FakeDev* dev, int fd, void* buf, size_t count) +{ + return _ws_read(fd, buf, count); +} + +/* + * Somebody is trying to write to the event pipe. This can be used to set + * the state of LED. + */ +static ssize_t writeEvent(FakeDev* dev, int fd, const void* buf, size_t count) +{ + const struct input_event* piev; + + if (count == sizeof(*piev)) { + piev = (const struct input_event*) buf; + + if (piev->type == EV_LED) { + wsLog("%s: set LED code=%d value=%d\n", + dev->debugName, piev->code, piev->value); + } else { + wsLog("%s: writeEvent got %d bytes, type=%d\n", + dev->debugName, count, piev->type); + } + } else { + wsLog("%s: warning: writeEvent got %d bytes, not sure why\n", + dev->debugName, count); + } + + return count; +} + +/* + * Handle event ioctls. + */ +static int ioctlEvent(FakeDev* dev, int fd, int request, void* argp) +{ + EventState* state = (EventState*) dev->state; + unsigned int urequest = (unsigned int) request; + + wsLog("%s: ioctl(0x%x, %p)\n", dev->debugName, urequest, argp); + + if (_IOC_TYPE(urequest) != _IOC_TYPE(EVIOCGVERSION)) { + wsLog("%s: inappropriate ioctl 0x%08x\n", dev->debugName, urequest); + return -1; + } + + if (urequest == EVIOCGVERSION) { + *(int*)argp = state->protoVersion; + } else if (urequest == EVIOCGID) { + memcpy(argp, &state->ident, sizeof(struct input_id)); + } else if (_IOC_NR(urequest) == _IOC_NR(EVIOCGNAME(0))) { + int maxLen = _IOC_SIZE(urequest); + int strLen = (int) strlen(state->name); + if (strLen >= maxLen) { + errno = EINVAL; + return -1; + } + memcpy(argp, state->name, strLen+1); + return strLen; + } else if (_IOC_NR(urequest) == _IOC_NR(EVIOCGPHYS(0))) { + int maxLen = _IOC_SIZE(urequest); + int strLen = (int) strlen(state->location); + if (strLen >= maxLen) { + errno = EINVAL; + return -1; + } + memcpy(argp, state->location, strLen+1); + return strLen; + } else if (_IOC_NR(urequest) == _IOC_NR(EVIOCGUNIQ(0))) { + /* device doesn't seem to support this, neither will we */ + return -1; + } else if (_IOC_NR(urequest) == _IOC_NR(EVIOCGBIT(EV_KEY,0))) { + /* keys */ + int maxLen = _IOC_SIZE(urequest); + if (maxLen > (int) sizeof(gKeyBitMask)) + maxLen = sizeof(gKeyBitMask); + memcpy(argp, gKeyBitMask, maxLen); + } else if (_IOC_NR(urequest) == _IOC_NR(EVIOCGBIT(EV_REL,0))) { + /* relative controllers (trackball) */ + int maxLen = _IOC_SIZE(urequest); + memset(argp, 0xff, maxLen); + } else if (!getenv("NOTOUCH") && _IOC_NR(urequest) == _IOC_NR(EVIOCGBIT(EV_ABS,0))) { + // absolute controllers (touch screen) + int maxLen = _IOC_SIZE(urequest); + memset(argp, 0xff, maxLen); + + } else if (_IOC_NR(urequest) >= _IOC_NR(EVIOCGABS(ABS_X)) && + _IOC_NR(urequest) <= _IOC_NR(EVIOCGABS(ABS_MAX))) + { + /* get abs value / limits */ + int reqIdx = _IOC_NR(urequest) - _IOC_NR(EVIOCGABS(ABS_X)); + handleAbsGet(reqIdx, argp); + } else { + wsLog("GLITCH: UNKNOWN ioctl request 0x%x on %s\n", + urequest, dev->debugName); + return -1; + } + + return 0; +} + +/* + * Free up our state before closing down the fake descriptor. + */ +static int closeEvent(FakeDev* dev, int fd) +{ + freeState((EventState*)dev->state); + dev->state = NULL; + if (gWrapSim.keyInputDevice == dev) { + gWrapSim.keyInputDevice = NULL; + wsLog("Sim input device closed\n"); + } + return 0; +} + +/* + * Open an input event device. + */ +FakeDev* wsOpenDevEvent(const char* pathName, int flags) +{ + FakeDev* newDev = wsCreateRealFakeDev(pathName); + if (newDev != NULL) { + newDev->read = readEvent; + newDev->write = writeEvent; + newDev->ioctl = ioctlEvent; + newDev->close = closeEvent; + + EventState* eventState = calloc(1, sizeof(EventState)); + + configureInitialState(pathName, eventState); + newDev->state = eventState; + + /* + * First one opened becomes the place where we queue up input + * events from the simulator. This approach will fail if the + * app opens the device, then opens it a second time for input, + * then closes the first. The app doesn't currently do this (though + * it does do quick opens to fiddle with LEDs). + */ + if (gWrapSim.keyInputDevice == NULL) { + gWrapSim.keyInputDevice = newDev; + wsLog("Device %p / %d will receive sim input events\n", + newDev, newDev->fd); + } + } + + return newDev; +} + +/* + * Write a key event. + */ +static int sendKeyEvent(FakeDev* dev, int code, int isDown) +{ + struct input_event iev; + ssize_t actual; + + gettimeofday(&iev.time, NULL); + iev.type = EV_KEY; + iev.code = code; + iev.value = (isDown != 0) ? 1 : 0; + + actual = _ws_write(dev->otherFd, &iev, sizeof(iev)); + if (actual != (ssize_t) sizeof(iev)) { + wsLog("WARNING: send key event partial write (%d of %d)\n", + actual, sizeof(iev)); + return -1; + } + + return 0; +} + +/* + * Write an absolute (touch screen) event. + */ +static int sendAbsButton(FakeDev* dev, int x, int y, int isDown) +{ + struct input_event iev; + ssize_t actual; + + wsLog("absButton x=%d y=%d down=%d\n", x, y, isDown); + + gettimeofday(&iev.time, NULL); + iev.type = EV_KEY; + iev.code = BTN_TOUCH; + iev.value = (isDown != 0) ? 1 : 0; + + actual = _ws_write(dev->otherFd, &iev, sizeof(iev)); + if (actual != (ssize_t) sizeof(iev)) { + wsLog("WARNING: send touch event partial write (%d of %d)\n", + actual, sizeof(iev)); + return -1; + } + + return 0; +} + +/* + * Write an absolute (touch screen) event. + */ +static int sendAbsMovement(FakeDev* dev, int x, int y) +{ + struct input_event iev; + ssize_t actual; + + wsLog("absMove x=%d y=%d\n", x, y); + + gettimeofday(&iev.time, NULL); + iev.type = EV_ABS; + iev.code = ABS_X; + iev.value = x; + + actual = _ws_write(dev->otherFd, &iev, sizeof(iev)); + if (actual != (ssize_t) sizeof(iev)) { + wsLog("WARNING: send abs movement event partial X write (%d of %d)\n", + actual, sizeof(iev)); + return -1; + } + + iev.code = ABS_Y; + iev.value = y; + + actual = _ws_write(dev->otherFd, &iev, sizeof(iev)); + if (actual != (ssize_t) sizeof(iev)) { + wsLog("WARNING: send abs movement event partial Y write (%d of %d)\n", + actual, sizeof(iev)); + return -1; + } + + return 0; +} + +/* + * Not quite sure what this is for, but the emulator does it. + */ +static int sendAbsSyn(FakeDev* dev) +{ + struct input_event iev; + ssize_t actual; + + gettimeofday(&iev.time, NULL); + iev.type = EV_SYN; + iev.code = 0; + iev.value = 0; + + actual = _ws_write(dev->otherFd, &iev, sizeof(iev)); + if (actual != (ssize_t) sizeof(iev)) { + wsLog("WARNING: send abs movement syn (%d of %d)\n", + actual, sizeof(iev)); + return -1; + } + + return 0; +} + +/* + * Send a key event to the fake key event device. + * + * We have to translate the simulator key event into one or more device + * key events. + */ +void wsSendSimKeyEvent(int key, int isDown) +{ + FakeDev* dev; + EventState* state; + + dev = gWrapSim.keyInputDevice; + if (dev == NULL) + return; + + sendKeyEvent(dev, key, isDown); +} + +/* + * Send a touch-screen event to the fake key event device. + * + * We have to translate the simulator key event into one or more device + * key events. + */ +void wsSendSimTouchEvent(int action, int x, int y) +{ + FakeDev* dev; + EventState* state; + + dev = gWrapSim.keyInputDevice; + if (dev == NULL) + return; + + if (action == kTouchDown) { + sendAbsMovement(dev, x, y); + sendAbsButton(dev, x, y, 1); + sendAbsSyn(dev); + } else if (action == kTouchUp) { + sendAbsButton(dev, x, y, 0); + sendAbsSyn(dev); + } else if (action == kTouchDrag) { + sendAbsMovement(dev, x, y); + sendAbsSyn(dev); + } else { + wsLog("WARNING: unexpected sim touch action %d\n", action); + } +} + diff --git a/simulator/wrapsim/DevFb.c b/simulator/wrapsim/DevFb.c new file mode 100644 index 000000000..42a9d9a09 --- /dev/null +++ b/simulator/wrapsim/DevFb.c @@ -0,0 +1,267 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Fake device support. + */ +#include "Common.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <linux/fb.h> + +typedef struct FbState { + /* index into gWrapSim.display[] */ + int displayIdx; + + /* VRAM address, set by mmap() call */ + void* vramAddr; + + /* kernel data structures */ + struct fb_var_screeninfo vinfo; + struct fb_fix_screeninfo finfo; +} FbState; + + +/* + * Set up the initial values of the structs. + * + * The FbState struct is zeroed out initially, so we only need to set the + * fields that don't default to zero. + */ +static void configureInitialState(int displayIdx, FbState* fbState) +{ + int width, height; + + assert(displayIdx >= 0 && displayIdx < gWrapSim.numDisplays); + + width = gWrapSim.display[displayIdx].width; + height = gWrapSim.display[displayIdx].height; + wsLog("Configuring FbState for display %d (%dx%x key=0x%08x)\n", + displayIdx, width, height, gWrapSim.display[displayIdx].shmemKey); + + /* fb_fix_screeninfo */ + strcpy(fbState->finfo.id, "omapfb"); + fbState->finfo.smem_len = (width * 2) * height * 2; + fbState->finfo.line_length = width * 2; + + /* fb_var_screeninfo */ + fbState->vinfo.xres = width; + fbState->vinfo.yres = height; + fbState->vinfo.xres_virtual = width; + fbState->vinfo.yres_virtual = height * 2; + fbState->vinfo.bits_per_pixel = 16; + + fbState->vinfo.red.offset = 11; + fbState->vinfo.red.length = 5; + fbState->vinfo.green.offset = 5; + fbState->vinfo.green.length = 6; + fbState->vinfo.blue.offset = 0; + fbState->vinfo.blue.length = 5; + + fbState->vinfo.width = 51; // physical dimension, used for dpi + fbState->vinfo.height = 38; + + fbState->vinfo.pixclock = 103092; + fbState->vinfo.upper_margin = 3; + fbState->vinfo.lower_margin = 227; + fbState->vinfo.left_margin = 12; + fbState->vinfo.right_margin = 8; +} + +/* + * Free allocated state. + */ +static void freeState(FbState* fbState) +{ + free(fbState); +} + +/* + * Wait for our synthetic vsync to happen. + */ +static void waitForVsync(FbState* state) +{ + /* TODO: simulate a real interval */ + usleep(1000000/60); +} + +/* + * Forward pixels to the simulator. + */ +static void sendPixelsToSim(FbState* state) +{ + if (state->vramAddr == 0) { + wsLog("## not sending pixels (no addr yet)\n"); + return; + } + + //wsLog("+++ sending pixels to sim (disp=%d yoff=%d)\n", + // state->displayIdx, state->vinfo.yoffset); + + wsLockDisplay(state->displayIdx); + + uint8_t* dst = gWrapSim.display[state->displayIdx].addr; + + int l,t,r,b,w,h; + w = gWrapSim.display[state->displayIdx].width; + h = gWrapSim.display[state->displayIdx].height; + +#if 0 + /* + * TODO: surfaceflinger encodes the dirty region in vinfo.reserved[]. We + * can use that to perform a partial update. + */ + const Rect dirty(dirtyReg.bounds()); + l = dirty.left >=0 ? dirty.left : 0; + t = dirty.top >=0 ? dirty.top : 0; + r = dirty.right <=w ? dirty.right : w; + b = dirty.bottom<=h ? dirty.bottom : h; +#else + l = t = 0; + r = w; + b = h; +#endif + + /* find the right page */ + int ypage = state->vinfo.yoffset; + + int x, y; + for (y = t ; y < b ; y++) { + // no "stride" issues with this display + uint8_t* outPtr = dst + (y*w+l)*3; + const uint16_t* ptr16 = (uint16_t*)state->vramAddr + ((y+ypage)*w+l); + for (x = l; x < r; x++) { + uint16_t in = *ptr16++; + uint32_t R,G,B; + R = ((in>>8)&0xF8) | (in>>(8+5)); + G = (in & 0x7E0)>>3; + G |= G>>6; + B = (in & 0x1F)<<3; + B |= B>>5; + *outPtr++ = R; + *outPtr++ = G; + *outPtr++ = B; + } + } + + wsUnlockDisplay(state->displayIdx); + + /* notify the simulator */ + wsPostDisplayUpdate(state->displayIdx); +} + +/* + * Provide a memory-mapped region for framebuffer data. We want to use a + * real mmap() call, not fake it with a malloc, so that related calls + * (munmap, madvise) will just work. + */ +static void* mmapFb(FakeDev* dev, void* start, size_t length, int prot, + int flags, int fd, __off_t offset) +{ + FbState* state = (FbState*) dev->state; + void* map; + + /* be reasonable */ + if (length > (640*480*2)*4) { + errno = EINVAL; + return MAP_FAILED; + } + + /* this is supposed to be VRAM, so just map a chunk */ + map = mmap(start, length, prot, MAP_PRIVATE | MAP_ANON, -1, 0); + + /* update our "VRAM address"; this feels a bit fragile */ + if (state->vramAddr != NULL) { + wsLog("%s: NOTE: changing vram address from %p\n", + dev->debugName, state->vramAddr); + } + state->vramAddr = map; + + wsLog("%s: mmap %u bytes --> %p\n", dev->debugName, length, map); + return map; +} + +/* + * Handle framebuffer ioctls. + */ +static int ioctlFb(FakeDev* dev, int fd, int request, void* argp) +{ + FbState* state = (FbState*) dev->state; + + wsLog("%s: ioctl(0x%x, %p)\n", dev->debugName, request, argp); + + switch (request) { + case FBIOGET_FSCREENINFO: // struct fb_fix_screeninfo* + memcpy(argp, &state->finfo, sizeof(struct fb_fix_screeninfo)); + break; + case FBIOGET_VSCREENINFO: // struct fb_var_screeninfo* + memcpy(argp, &state->vinfo, sizeof(struct fb_var_screeninfo)); + break; + case FBIOPUT_VSCREENINFO: // struct fb_var_screeninfo* + memcpy(&state->vinfo, argp, sizeof(struct fb_var_screeninfo)); + if (state->vinfo.activate == FB_ACTIVATE_NOW) { + //wsLog("%s: activate now\n", dev->debugName); + sendPixelsToSim(state); + } else if (state->vinfo.activate == FB_ACTIVATE_VBL) { + //wsLog("%s: activate on VBL\n", dev->debugName); + sendPixelsToSim(state); + /* we wait *after* so other process gets scheduled to draw */ + waitForVsync(state); + } else { + wsLog("%s: activate value is %d\n", + dev->debugName, state->vinfo.activate); + } + break; + case FBIOGET_VBLANK: // struct fb_vblank* + /* the device doesn't actually implement this */ + //memset(argp, 0, sizeof(struct fb_vblank)); + errno = EINVAL; + return -1; + default: + /*case FBIO_WAITFORVSYNC:*/ + wsLog("GLITCH: UNKNOWN ioctl request 0x%x on %s\n", + request, dev->debugName); + return -1; + } + + return 0; +} + +/* + * Free up our state before closing down the fake descriptor. + */ +static int closeFb(FakeDev* dev, int fd) +{ + freeState((FbState*)dev->state); + dev->state = NULL; + return 0; +} + +/* + * Open the console TTY device, which responds to a collection of ioctl()s. + */ +FakeDev* wsOpenDevFb(const char* pathName, int flags) +{ + FakeDev* newDev = wsCreateFakeDev(pathName); + if (newDev != NULL) { + newDev->mmap = mmapFb; + newDev->ioctl = ioctlFb; + newDev->close = closeFb; + + FbState* fbState = calloc(1, sizeof(FbState)); + + /* establish a connection to the front-end if necessary */ + /* (also gets display configuration) */ + wsSimConnect(); + + configureInitialState(0, fbState); // always use display 0 for now + newDev->state = fbState; + } + + return newDev; +} + diff --git a/simulator/wrapsim/DevLog.c b/simulator/wrapsim/DevLog.c new file mode 100644 index 000000000..fe1144d4d --- /dev/null +++ b/simulator/wrapsim/DevLog.c @@ -0,0 +1,485 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Log devices. We want to filter and display messages, with separate + * treatment for "debug" and "event" logs. + * + * All messages are just dumped to stderr. + */ +#include "Common.h" + +#include "cutils/logd.h" + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include <fcntl.h> + +#define kMaxTagLen 16 /* from utils/Log.cpp */ + +#define kTagSetSize 16 /* arbitrary */ + +/* from utils/Log.cpp */ +typedef enum { + FORMAT_OFF = 0, + FORMAT_BRIEF, + FORMAT_PROCESS, + FORMAT_TAG, + FORMAT_THREAD, + FORMAT_RAW, + FORMAT_TIME, + FORMAT_LONG +} LogFormat; + + +/* + * Log driver state. + */ +typedef struct LogState { + /* nonzero if this is a binary log */ + int isBinary; + + /* global minimum priority */ + int globalMinPriority; + + /* output format */ + LogFormat outputFormat; + + /* tags and priorities */ + struct { + char tag[kMaxTagLen]; + int minPriority; + } tagSet[kTagSetSize]; +} LogState; + + +/* + * Configure logging based on ANDROID_LOG_TAGS environment variable. We + * need to parse a string that looks like + * + * '*:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i + * + * The tag (or '*' for the global level) comes first, followed by a colon + * and a letter indicating the minimum priority level we're expected to log. + * This can be used to reveal or conceal logs with specific tags. + * + * We also want to check ANDROID_PRINTF_LOG to determine how the output + * will look. + */ +static void configureInitialState(const char* pathName, LogState* logState) +{ + static const int kDevLogLen = 9; /* strlen("/dev/log/") */ + + /* identify binary logs */ + if (strcmp(pathName + kDevLogLen, "events") == 0) { + logState->isBinary = 1; + } + + /* global min priority defaults to "info" level */ + logState->globalMinPriority = ANDROID_LOG_INFO; + + /* + * This is based on the utils/Log.cpp code. + */ + const char* tags = getenv("ANDROID_LOG_TAGS"); + wsLog("Found ANDROID_LOG_TAGS='%s'\n", tags); + if (tags != NULL) { + int entry = 0; + + while (*tags != '\0') { + char tagName[kMaxTagLen]; + int i, minPrio; + + while (isspace(*tags)) + tags++; + + i = 0; + while (*tags != '\0' && !isspace(*tags) && *tags != ':' && + i < kMaxTagLen) + { + tagName[i++] = *tags++; + } + if (i == kMaxTagLen) { + wsLog("ERROR: env tag too long (%d chars max)\n", kMaxTagLen-1); + return; + } + tagName[i] = '\0'; + + /* default priority, if there's no ":" part; also zero out '*' */ + minPrio = ANDROID_LOG_VERBOSE; + if (tagName[0] == '*' && tagName[1] == '\0') { + minPrio = ANDROID_LOG_DEBUG; + tagName[0] = '\0'; + } + + if (*tags == ':') { + tags++; + if (*tags >= '0' && *tags <= '9') { + if (*tags >= ('0' + ANDROID_LOG_SILENT)) + minPrio = ANDROID_LOG_VERBOSE; + else + minPrio = *tags - '\0'; + } else { + switch (*tags) { + case 'v': minPrio = ANDROID_LOG_VERBOSE; break; + case 'd': minPrio = ANDROID_LOG_DEBUG; break; + case 'i': minPrio = ANDROID_LOG_INFO; break; + case 'w': minPrio = ANDROID_LOG_WARN; break; + case 'e': minPrio = ANDROID_LOG_ERROR; break; + case 'f': minPrio = ANDROID_LOG_FATAL; break; + case 's': minPrio = ANDROID_LOG_SILENT; break; + default: minPrio = ANDROID_LOG_DEFAULT; break; + } + } + + tags++; + if (*tags != '\0' && !isspace(*tags)) { + wsLog("ERROR: garbage in tag env; expected whitespace\n"); + wsLog(" env='%s'\n", tags); + return; + } + } + + if (tagName[0] == 0) { + logState->globalMinPriority = minPrio; + wsLog("+++ global min prio %d\n", logState->globalMinPriority); + } else { + logState->tagSet[entry].minPriority = minPrio; + strcpy(logState->tagSet[entry].tag, tagName); + wsLog("+++ entry %d: %s:%d\n", + entry, + logState->tagSet[entry].tag, + logState->tagSet[entry].minPriority); + entry++; + } + } + } + + + /* + * Taken from utils/Log.cpp + */ + const char* fstr = getenv("ANDROID_PRINTF_LOG"); + LogFormat format; + if (fstr == NULL) { + format = FORMAT_BRIEF; + } else { + if (strcmp(fstr, "brief") == 0) + format = FORMAT_BRIEF; + else if (strcmp(fstr, "process") == 0) + format = FORMAT_PROCESS; + else if (strcmp(fstr, "tag") == 0) + format = FORMAT_PROCESS; + else if (strcmp(fstr, "thread") == 0) + format = FORMAT_PROCESS; + else if (strcmp(fstr, "raw") == 0) + format = FORMAT_PROCESS; + else if (strcmp(fstr, "time") == 0) + format = FORMAT_PROCESS; + else if (strcmp(fstr, "long") == 0) + format = FORMAT_PROCESS; + else + format = (LogFormat) atoi(fstr); // really?! + } + + logState->outputFormat = format; +} + +/* + * Free up the state structure. + */ +static void freeState(LogState* logState) +{ + free(logState); +} + +/* + * Return a human-readable string for the priority level. Always returns + * a valid string. + */ +static const char* getPriorityString(int priority) +{ + /* the first character of each string should be unique */ + static const char* priorityStrings[] = { + "Verbose", "Debug", "Info", "Warn", "Error", "Assert" + }; + int idx; + + idx = (int) priority - (int) ANDROID_LOG_VERBOSE; + if (idx < 0 || + idx >= (int) (sizeof(priorityStrings) / sizeof(priorityStrings[0]))) + return "?unknown?"; + return priorityStrings[idx]; +} + +/* + * Show a log message. We write it to stderr and send a copy to the + * simulator front-end for the log window. + * + * Taken from utils/Log.cpp. + */ +static void showLog(FakeDev* dev, int logPrio, const char* tag, const char* msg) +{ + LogState* state = (LogState*) dev->state; + +#if defined(HAVE_LOCALTIME_R) + struct tm tmBuf; +#endif + struct tm* ptm; + char timeBuf[32]; + char prefixBuf[128], suffixBuf[128]; + char priChar; + time_t when; + pid_t pid, tid; + + //wsLog("LOG %d: %s %s", logPrio, tag, msg); + wsPostLogMessage(logPrio, tag, msg); + + priChar = getPriorityString(logPrio)[0]; + when = time(NULL); + pid = tid = getpid(); // find gettid()? + + /* + * 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. + */ +#if defined(HAVE_LOCALTIME_R) + ptm = localtime_r(&when, &tmBuf); +#else + ptm = localtime(&when); +#endif + //strftime(timeBuf, sizeof(timeBuf), "%Y-%m-%d %H:%M:%S", ptm); + strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm); + + /* + * Construct a buffer containing the log header and log message. + */ + size_t prefixLen, suffixLen; + + switch (state->outputFormat) { + case FORMAT_TAG: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%c/%-8s: ", priChar, tag); + strcpy(suffixBuf, "\n"); suffixLen = 1; + break; + case FORMAT_PROCESS: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%c(%5d) ", priChar, pid); + suffixLen = snprintf(suffixBuf, sizeof(suffixBuf), + " (%s)\n", tag); + break; + case FORMAT_THREAD: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%c(%5d:%p) ", priChar, pid, (void*)tid); + strcpy(suffixBuf, "\n"); suffixLen = 1; + break; + case FORMAT_RAW: + prefixBuf[0] = 0; prefixLen = 0; + strcpy(suffixBuf, "\n"); suffixLen = 1; + break; + case FORMAT_TIME: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%s %-8s\n\t", timeBuf, tag); + strcpy(suffixBuf, "\n"); suffixLen = 1; + break; + case FORMAT_LONG: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "[ %s %5d:%p %c/%-8s ]\n", + timeBuf, pid, (void*)tid, priChar, tag); + strcpy(suffixBuf, "\n\n"); suffixLen = 2; + break; + default: + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), + "%c/%-8s(%5d): ", priChar, tag, pid); + strcpy(suffixBuf, "\n"); suffixLen = 1; + break; + } + + /* + * Figure out how many lines there will be. + */ + const char* end = msg + strlen(msg); + size_t numLines = 0; + const char* p = msg; + while (p < end) { + if (*p++ == '\n') numLines++; + } + if (p > msg && *(p-1) != '\n') numLines++; + + /* + * Create an array of iovecs large enough to write all of + * the lines with a prefix and a suffix. + */ + const size_t INLINE_VECS = 6; + struct iovec stackVec[INLINE_VECS]; + struct iovec* vec = stackVec; + + numLines *= 3; // 3 iovecs per line. + if (numLines > INLINE_VECS) { + vec = (struct iovec*)malloc(sizeof(struct iovec)*numLines); + if (vec == NULL) { + msg = "LOG: write failed, no memory"; + numLines = 3; + } + } + + /* + * Fill in the iovec pointers. + */ + p = msg; + struct iovec* v = vec; + int totalLen = 0; + while (p < end) { + if (prefixLen > 0) { + v->iov_base = prefixBuf; + v->iov_len = prefixLen; + totalLen += prefixLen; + v++; + } + const char* start = p; + while (p < end && *p != '\n') p++; + if ((p-start) > 0) { + v->iov_base = (void*)start; + v->iov_len = p-start; + totalLen += p-start; + v++; + } + if (*p == '\n') p++; + if (suffixLen > 0) { + v->iov_base = suffixBuf; + v->iov_len = suffixLen; + totalLen += suffixLen; + v++; + } + } + + /* + * Write the entire message to the log file with a single writev() call. + * We need to use this rather than a collection of printf()s on a FILE* + * because of multi-threading and multi-process issues. + * + * If the file was not opened with O_APPEND, this will produce interleaved + * output when called on the same file from multiple processes. + * + * If the file descriptor is actually a network socket, the writev() + * call may return with a partial write. Putting the writev() call in + * a loop can result in interleaved data. This can be alleviated + * somewhat by wrapping the writev call in the Mutex. + */ + + for(;;) { + int cc; + + cc = writev(fileno(stderr), vec, v-vec); + if (cc == totalLen) break; + + if (cc < 0) { + if(errno == EINTR) continue; + + /* can't really log the failure; for now, throw out a stderr */ + fprintf(stderr, "+++ LOG: write failed (errno=%d)\n", errno); + break; + } else { + /* shouldn't happen when writing to file or tty */ + fprintf(stderr, "+++ LOG: write partial (%d of %d)\n", cc, totalLen); + break; + } + } + + /* if we allocated storage for the iovecs, free it */ + if (vec != stackVec) + free(vec); +} + + +/* + * Receive a log message. We happen to know that "vector" has three parts: + * + * priority (1 byte) + * tag (N bytes -- null-terminated ASCII string) + * message (N bytes -- null-terminated ASCII string) + */ +static ssize_t writevLog(FakeDev* dev, int fd, const struct iovec* vector, + int count) +{ + LogState* state = (LogState*) dev->state; + int ret = 0; + + if (state->isBinary) { + wsLog("%s: ignoring binary log\n", dev->debugName); + goto bail; + } + + if (count != 3) { + wsLog("%s: writevLog with count=%d not expected\n", + dev->debugName, count); + errno = EINVAL; + return -1; + } + + /* pull out the three fields */ + int logPrio = *(const char*)vector[0].iov_base; + const char* tag = (const char*) vector[1].iov_base; + const char* msg = (const char*) vector[2].iov_base; + + /* see if this log tag is configured */ + int i; + int minPrio = state->globalMinPriority; + for (i = 0; i < kTagSetSize; i++) { + if (state->tagSet[i].minPriority == ANDROID_LOG_UNKNOWN) + break; /* reached end of configured values */ + + if (strcmp(state->tagSet[i].tag, tag) == 0) { + //wsLog("MATCH tag '%s'\n", tag); + minPrio = state->tagSet[i].minPriority; + break; + } + } + + if (logPrio >= minPrio) { + showLog(dev, logPrio, tag, msg); + } else { + //wsLog("+++ NOLOG(%d): %s %s", logPrio, tag, msg); + } + +bail: + for (i = 0; i < count; i++) + ret += vector[i].iov_len; + return ret; +} + +/* + * Free up our state before closing down the fake descriptor. + */ +static int closeLog(FakeDev* dev, int fd) +{ + freeState((LogState*)dev->state); + dev->state = NULL; + return 0; +} + +/* + * Open a log output device. + */ +FakeDev* wsOpenDevLog(const char* pathName, int flags) +{ + FakeDev* newDev = wsCreateFakeDev(pathName); + if (newDev != NULL) { + newDev->writev = writevLog; + newDev->close = closeLog; + + LogState* logState = calloc(1, sizeof(LogState)); + + configureInitialState(pathName, logState); + newDev->state = logState; + } + + return newDev; +} + diff --git a/simulator/wrapsim/DevPower.c b/simulator/wrapsim/DevPower.c new file mode 100644 index 000000000..2d25704b3 --- /dev/null +++ b/simulator/wrapsim/DevPower.c @@ -0,0 +1,208 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Magic entries in /sys/android_power/. + */ +#include "Common.h" + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include <fcntl.h> +#include <sys/ioctl.h> + +#if 0 +/* + * Set of entries found in /sys/android_power. + */ +typedef enum DeviceIndex { + kPowerUnknown = 0, + + kPowerAutoOffTimeout, + kPowerBatteryLevel, + kPowerBatteryLevelLow, + kPowerBatteryLevelRaw, + kPowerBatteryLevelScale, + kPowerBatteryLowLevel, + kPowerBatteryShutdownLevel, + kPowerChargingState, + kPowerRequestState, + kPowerState, + + kPowerAcquireFullWakeLock, + kPowerAcquirePartialWakeLock, + kPowerReleaseWakeLock, +} DeviceIndex; +#endif + +/* + * Map filename to device index. + * + * [ not using DeviceIndex -- would be useful if we need to return something + * other than a static string ] + */ +static const struct { + const char* name; + //DeviceIndex idx; + const char* data; +} gDeviceMap[] = { + { "auto_off_timeout", //kPowerAutoOffTimeout, + "\n" }, + { "battery_level", //kPowerBatteryLevel, + "9\n" }, + { "battery_level_low", //kPowerBatteryLevelLow, + "0\n" }, + { "battery_level_raw", //kPowerBatteryLevelRaw, + "100\n" }, + { "battery_level_scale", //kPowerBatteryLevelScale, + "9\n" }, + { "battery_low_level", //kPowerBatteryLowLevel, + "10\n" }, + { "battery_shutdown_level", //kPowerBatteryShutdownLevel, + "5\n", }, + { "charging_state", //kPowerChargingState, + "Maintaining\n" }, + { "request_state", //kPowerRequestState, + "wake\n" }, + { "state", //kPowerState, + "0-1-0\n" }, + + { "acquire_full_wake_lock", //kPowerAcquireFullWakeLock, + "\n" }, + { "acquire_partial_wake_lock", //kPowerAcquirePartialWakeLock, + "\n" }, + { "release_wake_lock", //kPowerReleaseWakeLock, + "radio-interface PowerManagerService KeyEvents\n" }, + { "wait_for_fb_sleep", //kSleepFileName, + "" }, // this means "block forever on read" + { "wait_for_fb_wake", //kWakeFileName, + "0" }, + +}; + +/* + * Power driver state. + * + * Right now we just ignore everything written. + */ +typedef struct PowerState { + int which; +} PowerState; + + +/* + * Figure out who we are, based on "pathName". + */ +static void configureInitialState(const char* pathName, PowerState* powerState) +{ + const char* cp = pathName + strlen("/sys/android_power/"); + int i; + + powerState->which = -1; + for (i = 0; i < (int) (sizeof(gDeviceMap) / sizeof(gDeviceMap[0])); i++) { + if (strcmp(cp, gDeviceMap[i].name) == 0) { + powerState->which = i; + break; + } + } + + if (powerState->which == -1) { + wsLog("Warning: access to unknown power device '%s'\n", pathName); + return; + } +} + +/* + * Free up the state structure. + */ +static void freeState(PowerState* powerState) +{ + free(powerState); +} + +/* + * Read data from the device. + * + * We don't try to keep track of how much was read -- existing clients just + * try to read into a large buffer. + */ +static ssize_t readPower(FakeDev* dev, int fd, void* buf, size_t count) +{ + PowerState* state = (PowerState*) dev->state; + int dataLen; + + wsLog("%s: read %d\n", dev->debugName, count); + + if (state->which < 0 || state->which >= sizeof(gDeviceMap)/sizeof(gDeviceMap[0])) + return 0; + + const char* data = gDeviceMap[state->which].data; + size_t strLen = strlen(data); + + while(strLen == 0) + sleep(10); // block forever + + ssize_t copyCount = (strLen < count) ? strLen : count; + memcpy(buf, data, copyCount); + return copyCount; +} + +/* + * Ignore the request. + */ +static ssize_t writePower(FakeDev* dev, int fd, const void* buf, size_t count) +{ + wsLog("%s: write %d bytes\n", dev->debugName, count); + return count; +} + +/* + * Our Java classes want to be able to do ioctl(FIONREAD) on files. The + * battery power manager is blowing up if we get an error other than + * ENOTTY (meaning a device that doesn't understand buffering). + */ +static int ioctlPower(FakeDev* dev, int fd, int request, void* argp) +{ + if (request == FIONREAD) { + wsLog("%s: ioctl(FIONREAD, %p)\n", dev->debugName, argp); + errno = ENOTTY; + return -1; + } else { + wsLog("%s: ioctl(0x%08x, %p) ??\n", dev->debugName, request, argp); + errno = EINVAL; + return -1; + } +} + +/* + * Free up our state before closing down the fake descriptor. + */ +static int closePower(FakeDev* dev, int fd) +{ + freeState((PowerState*)dev->state); + dev->state = NULL; + return 0; +} + +/* + * Open a power device. + */ +FakeDev* wsOpenDevPower(const char* pathName, int flags) +{ + FakeDev* newDev = wsCreateFakeDev(pathName); + if (newDev != NULL) { + newDev->read = readPower; + newDev->write = writePower; + newDev->ioctl = ioctlPower; + newDev->close = closePower; + + PowerState* powerState = calloc(1, sizeof(PowerState)); + + configureInitialState(pathName, powerState); + newDev->state = powerState; + } + + return newDev; +} + diff --git a/simulator/wrapsim/DevVibrator.c b/simulator/wrapsim/DevVibrator.c new file mode 100644 index 000000000..b736f7519 --- /dev/null +++ b/simulator/wrapsim/DevVibrator.c @@ -0,0 +1,47 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Vibrating notification device. + */ +#include "Common.h" + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include <unistd.h> + + +/* + * The user will write a decimal integer indicating the time, in milliseconds, + * that the device should vibrate. In current usage, this is either -1 + * (meaning vibrate forever) or 0 (don't vibrate). + */ +static ssize_t writeVibrator(FakeDev* dev, int fd, const void* buf, + size_t count) +{ + if (count == 2 && memcmp(buf, "0\n", 2) == 0) { + wsEnableVibration(0); + } else if (count == 3 && memcmp(buf, "-1\n", 3) == 0) { + wsEnableVibration(1); + } else { + wsLog("%s: got %d bytes: '%*s'\n", + dev->debugName, count, count, (const char*) buf); + } + + return count; +} + +/* + * Open the vibration control device. + */ +FakeDev* wsOpenDevVibrator(const char* pathName, int flags) +{ + FakeDev* newDev = wsCreateFakeDev(pathName); + if (newDev != NULL) { + newDev->write = writeVibrator; + } + + return newDev; +} + diff --git a/simulator/wrapsim/FakeDev.c b/simulator/wrapsim/FakeDev.c new file mode 100644 index 000000000..75aee1648 --- /dev/null +++ b/simulator/wrapsim/FakeDev.c @@ -0,0 +1,315 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Fake device support. + */ +/* +Implementation notes: + +There are a couple of basic scenarios, exemplified by the "fb" and +"events" devices. The framebuffer driver is pretty simple, handling a +few ioctl()s and managing a stretch of memory. We can just intercept a +few calls. The input event driver can be used in a select() or poll() +call with other file descriptors, which either requires us to do some +fancy tricks with select() and poll(), or requires that we return a real +file descriptor (perhaps based on a socketpair). + +We have three basic approaches to dealing with "fake" file descriptors: + +(1) Always use real fds. We can dup() an open /dev/null to get a number + for the cases where we don't need a socketpair. +(2) Always use fake fds with absurdly high numeric values. Testing to see + if the fd is one we handle is trivial (range check). This doesn't + work for select(), which uses fd bitmaps accessed through macros. +(3) Use a mix of real and fake fds, in a high range (512-1023). Because + it's in the "real" range, we can pass real fds around for things that + are handed to poll() and select(), but because of the high numeric + value we *should* be able to get away with a trivial range check. + +Approach (1) is the most portable and least likely to break, but the +efficiencies gained in approach (2) make it more desirable. There is +a small risk of application fds wandering into our range, but we can +minimize that by asserting on a "guard zone" and/or obstructing dup2(). +(We can also dup2(/dev/null) to "reserve" our fds, but that wastes +resources.) +*/ + +#include "Common.h" + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <assert.h> +#include <fnmatch.h> + +/* + * Devices we intercept. + * + * Needed: + * /sys/android_power/battery_level_scale + * /sys/android_power/battery_level + * /sys/android_power/battery_level_raw + * /sys/android_power/charging_state + * /dev/alarm + * radio + */ +typedef FakeDev* (*wsFileHook)(const char *path, int flags); + +typedef struct FakedPath { + const char *pathexpr; + wsFileHook hook; +} FakedPath; + +FakedPath fakedpaths[] = +{ + { "/dev/graphics/fb0", wsOpenDevFb }, + { "/dev/hw3d", NULL }, + { "/dev/eac", wsOpenDevAudio }, + { "/dev/tty0", wsOpenDevConsoleTty }, + { "/dev/input/event0", wsOpenDevEvent }, + { "/dev/input/*", NULL }, + { "/dev/log/*", wsOpenDevLog }, + { "/sys/android_power/*", wsOpenDevPower }, + { "/sys/devices/platform/android-vibrator/enable", wsOpenDevVibrator }, + { "/sys/qemu_trace/*", NULL }, + { NULL, NULL } +}; + + +/* + * Generic drop-in for an unimplemented call. + * + * Returns -1, which conveniently is the same as MAP_FAILED for mmap. + */ +static int notImplemented(FakeDev* dev, const char* callName) +{ + wsLog("WARNING: unimplemented %s() on '%s' %p\n", + callName, dev->debugName, dev->state); + errno = kNoHandlerError; + return -1; +} + +/* + * Default implementations. We want to log as much information as we can + * so that we can fill in the missing implementation. + * + * TODO: for some or all of these we will want to display the full arg list. + */ +static int noClose(FakeDev* dev, ...) +{ + return 0; +} +static int noRead(FakeDev* dev, ...) +{ + return notImplemented(dev, "read"); +} +static int noReadv(FakeDev* dev, ...) +{ + return notImplemented(dev, "readv"); +} +static int noWrite(FakeDev* dev, ...) +{ + return notImplemented(dev, "write"); +} +static int noWritev(FakeDev* dev, ...) +{ + return notImplemented(dev, "writev"); +} +static int noMmap(FakeDev* dev, ...) +{ + return notImplemented(dev, "mmap"); +} +static int noIoctl(FakeDev* dev, ...) +{ + return notImplemented(dev, "ioctl"); +} + + +/* + * Create a new FakeDev entry. + * + * We mark the fd slot as "used" in the bitmap, but don't add it to the + * table yet since the entry is not fully prepared. + */ +FakeDev* wsCreateFakeDev(const char* debugName) +{ + FakeDev* newDev; + int cc; + + assert(debugName != NULL); + + newDev = (FakeDev*) calloc(1, sizeof(FakeDev)); + if (newDev == NULL) + return NULL; + + newDev->debugName = strdup(debugName); + newDev->state = NULL; + + newDev->close = (Fake_close) noClose; + newDev->read = (Fake_read) noRead; + newDev->readv = (Fake_readv) noReadv; + newDev->write = (Fake_write) noWrite; + newDev->writev = (Fake_writev) noWritev; + newDev->mmap = (Fake_mmap) noMmap; + newDev->ioctl = (Fake_ioctl) noIoctl; + + /* + * Allocate a new entry. The bit vector map is really only used as a + * performance boost in the current implementation. + */ + cc = pthread_mutex_lock(&gWrapSim.fakeFdLock); assert(cc == 0); + int newfd = wsAllocBit(gWrapSim.fakeFdMap); + cc = pthread_mutex_unlock(&gWrapSim.fakeFdLock); assert(cc == 0); + + if (newfd < 0) { + wsLog("WARNING: ran out of 'fake' file descriptors\n"); + free(newDev); + return NULL; + } + newDev->fd = newfd + kFakeFdBase; + newDev->otherFd = -1; + assert(gWrapSim.fakeFdList[newDev->fd - kFakeFdBase] == NULL); + + return newDev; +} + +/* + * Create a new FakeDev entry, and open a file descriptor that actually + * works. + */ +FakeDev* wsCreateRealFakeDev(const char* debugName) +{ + FakeDev* newDev = wsCreateFakeDev(debugName); + if (newDev == NULL) + return newDev; + + int fds[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + wsLog("socketpair() failed: %s\n", strerror(errno)); + wsFreeFakeDev(newDev); + return NULL; + } + + if (dup2(fds[0], newDev->fd) < 0) { + wsLog("dup2(%d,%d) failed: %s\n", + fds[0], newDev->fd, strerror(errno)); + wsFreeFakeDev(newDev); + return NULL; + } + close(fds[0]); + + /* okay to leave this one in the "normal" range; not visible to app */ + newDev->otherFd = fds[1]; + + return newDev; +} + +/* + * Free fake device entry. + */ +void wsFreeFakeDev(FakeDev* dev) +{ + if (dev == NULL) + return; + + wsLog("## closing/freeing '%s' (%d/%d)\n", + dev->debugName, dev->fd, dev->otherFd); + + /* + * If we assigned a file descriptor slot, free it up. + */ + if (dev->fd >= 0) { + int cc; + + gWrapSim.fakeFdList[dev->fd - kFakeFdBase] = NULL; + + cc = pthread_mutex_lock(&gWrapSim.fakeFdLock); assert(cc == 0); + wsFreeBit(gWrapSim.fakeFdMap, dev->fd - kFakeFdBase); + cc = pthread_mutex_unlock(&gWrapSim.fakeFdLock); assert(cc == 0); + } + if (dev->otherFd >= 0) + close(dev->otherFd); + + if (dev->debugName) free(dev->debugName); + free(dev); +} + +/* + * Map a file descriptor to a fake device. + * + * Returns NULL if there's no corresponding entry. + */ +FakeDev* wsFakeDevFromFd(int fd) +{ + /* quick range test */ + if (fd < kFakeFdBase || fd >= kFakeFdBase + kMaxFakeFdCount) + return NULL; + + return gWrapSim.fakeFdList[fd - kFakeFdBase]; +} + + +/* + * Check to see if we're opening a device that we want to fake out. + * + * We return a file descriptor >= 0 on success, -1 if we're not interested, + * or -2 if we explicitly want to pretend that the device doesn't exist. + */ +int wsInterceptDeviceOpen(const char* pathName, int flags) +{ + FakedPath* p = fakedpaths; + + while (p->pathexpr) { + if (fnmatch(p->pathexpr, pathName, 0) == 0) { + if (p->hook != NULL) { + FakeDev* dev = p->hook(pathName, flags); + if (dev != NULL) { + /* + * Now that the device entry is ready, add it to the list. + */ + wsLog("## created fake dev %d: '%s' %p\n", + dev->fd, dev->debugName, dev->state); + gWrapSim.fakeFdList[dev->fd - kFakeFdBase] = dev; + return dev->fd; + } + } else { + wsLog("## rejecting attempt to open %s\n", pathName); + errno = ENOENT; + return -2; + } + break; + } + p++; + } + return -1; +} + +/* + * Check to see if we're accessing a device that we want to fake out. + * Returns 0 if the device can be (fake) opened with the given mode, + * -1 if it can't, -2 if it can't and we don't want to allow fallback + * to the host-device either. + * TODO: actually check the mode. + */ +int wsInterceptDeviceAccess(const char *pathName, int mode) +{ + FakedPath *p = fakedpaths; + + while (p->pathexpr) { + if (fnmatch(p->pathexpr, pathName, 0) == 0) { + if (p->hook) { + return 0; + } else { + wsLog("## rejecting attempt to open %s\n", pathName); + errno = ENOENT; + return -2; + } + break; + } + p++; + } + errno = ENOENT; + return -1; +} diff --git a/simulator/wrapsim/FakeDev.h b/simulator/wrapsim/FakeDev.h new file mode 100644 index 000000000..eacbbf55d --- /dev/null +++ b/simulator/wrapsim/FakeDev.h @@ -0,0 +1,123 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Fake device support. + */ +#ifndef _WRAPSIM_FAKEDEV_H +#define _WRAPSIM_FAKEDEV_H + +#include <sys/types.h> +#include <sys/uio.h> +#include <errno.h> + +typedef struct FakeDev FakeDev; + +typedef int (*Fake_close)(FakeDev* dev, int); +typedef ssize_t (*Fake_read)(FakeDev* dev, int, void*, size_t); +typedef ssize_t (*Fake_readv)(FakeDev* dev, int, const struct iovec*, int); +typedef ssize_t (*Fake_write)(FakeDev* dev, int, const void*, size_t); +typedef ssize_t (*Fake_writev)(FakeDev* dev, int, const struct iovec*, int); +typedef void* (*Fake_mmap)(FakeDev* dev, void*, size_t, int, int, int, __off_t); +typedef int (*Fake_ioctl)(FakeDev* dev, int, int, void*); + +/* + * An open fake device entry. + */ +struct FakeDev { + /* string, for debugging; usually orig. device name */ + char* debugName; + + /* state bucket */ + void* state; + + /* the file descriptor we're associated with */ + int fd; + + /* in some cases we use a pair; this is the other one */ + int otherFd; + + /* + * Device functions we provide. + * + * All other file descriptor operations should fail, usually with EBADF. + */ + Fake_close close; + Fake_read read; + Fake_readv readv; + Fake_write write; + Fake_writev writev; + Fake_mmap mmap; // handles both mmap() and mmap64() + Fake_ioctl ioctl; +}; + +/* + * If a handler isn't defined for a syscall, we return EMLINK so that it's + * obvious when the error is generated by us. + */ +#define kNoHandlerError EMLINK + +/* + * Fake file descriptors begin here. This should be chosen such that no + * real descriptor is ever at or above this value. + */ +#define kFakeFdBase 512 +#define kMaxFakeFdCount 256 + +/* + * Create a new, completely fake device entry. + */ +FakeDev* wsCreateFakeDev(const char* debugName); + +/* + * Create a new, mostly fake device entry. + */ +FakeDev* wsCreateRealFakeDev(const char* debugName); + +/* + * Free a fake device entry. + */ +void wsFreeFakeDev(FakeDev* dev); + +/* + * Given a file descriptor, find the corresponding fake device. Returns + * NULL if the fd doesn't correspond to one of our entries. + */ +FakeDev* wsFakeDevFromFd(int fd); + +/* + * Check to see if this open() call is on a device we want to spoof. + * + * Returns a "fake" file descriptor on success, <0 on error. + */ +int wsInterceptDeviceOpen(const char* pathName, int flags); + +/* + * Check to see if this access() call is on a device we want to spoof. + * + * Returns 0 if the device can be fake-accessed, -1 if it can't, -2 + * if it can't and we don't want to allow fallback to the host-device. + */ +int wsInterceptDeviceAccess(const char* pathName, int flags); + +/* + * Devices. + */ +FakeDev* wsOpenDevAudio(const char* pathName, int flags); +FakeDev* wsOpenDevConsoleTty(const char* pathName, int flags); +FakeDev* wsOpenDevEvent(const char* pathName, int flags); +FakeDev* wsOpenDevFb(const char* pathName, int flags); +FakeDev* wsOpenDevLog(const char* pathName, int flags); +FakeDev* wsOpenDevPower(const char* pathName, int flags); +FakeDev* wsOpenDevVibrator(const char* pathName, int flags); + +/* + * Performs key remapping and sends the event to the global input event device. + */ +void wsSendSimKeyEvent(int key, int isDown); + +/* + * Send a touch event to the global input event device. + */ +void wsSendSimTouchEvent(int action, int x, int y); + +#endif /*_WRAPSIM_FAKEDEV_H*/ diff --git a/simulator/wrapsim/Globals.h b/simulator/wrapsim/Globals.h new file mode 100644 index 000000000..75c98d8fa --- /dev/null +++ b/simulator/wrapsim/Globals.h @@ -0,0 +1,208 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Sim wrapper global state. + */ +#ifndef _WRAPSIM_GLOBALS_H +#define _WRAPSIM_GLOBALS_H + +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/uio.h> +#include <sys/time.h> +#include <sys/vfs.h> +#include <utime.h> +#include <fcntl.h> +#include <dirent.h> +#include <pthread.h> + +/* + * Type declarations for the functions we're replacing. + * + * For syscalls this matches the syscall definition, not the libc definition, + * e.g. no varargs for open() or ioctl(). + */ +typedef int (*Func_access)(const char*, int); +typedef int (*Func_open)(const char*, int, mode_t); +typedef int (*Func_open64)(const char*, int, mode_t); + +typedef int (*Func_close)(int); +typedef ssize_t (*Func_read)(int, void*, size_t); +typedef ssize_t (*Func_readv)(int, const struct iovec*, int); +typedef ssize_t (*Func_write)(int, const void*, size_t); +typedef ssize_t (*Func_writev)(int, const struct iovec*, int); +typedef void* (*Func_mmap)(void*, size_t, int, int, int, __off_t); +typedef void* (*Func_mmap64)(void*, size_t, int, int, int, __off64_t); +typedef int (*Func_ioctl)(int, int, void*); + +typedef int (*Func_chdir)(const char*); +typedef int (*Func_chmod)(const char*, mode_t); +typedef int (*Func_chown)(const char*, uid_t, uid_t); +typedef int (*Func_creat)(const char*, mode_t); +typedef int (*Func_execve)(const char*, char* const[], char* const[]); +typedef char* (*Func_getcwd)(char* buf, size_t size); +typedef int (*Func_lchown)(const char*, uid_t, uid_t); +typedef int (*Func_link)(const char*, const char*); +typedef int (*Func_lstat)(const char*, struct stat*); +typedef int (*Func_lstat64)(const char*, struct stat*); +typedef int (*Func___lxstat)(int version, const char*, struct stat*); +typedef int (*Func___lxstat64)(int version, const char*, struct stat*); +typedef int (*Func_mkdir)(const char*, mode_t mode); +typedef ssize_t (*Func_readlink)(const char*, char*, size_t); +typedef int (*Func_rename)(const char*, const char*); +typedef int (*Func_rmdir)(const char*); +typedef int (*Func_stat)(const char*, struct stat*); +typedef int (*Func_stat64)(const char*, struct stat*); +typedef int (*Func___xstat)(int version, const char*, struct stat*); +typedef int (*Func___xstat64)(int version, const char*, struct stat*); +typedef int (*Func_statfs)(const char*, struct statfs*); +typedef int (*Func_statfs64)(const char*, struct statfs*); +typedef int (*Func_symlink)(const char*, const char*); +typedef int (*Func_unlink)(const char*); +typedef int (*Func_utime)(const char*, const struct utimbuf*); +typedef int (*Func_utimes)(const char*, const struct timeval []); + +typedef int (*Func_execl)(const char*, const char*, ...); +typedef int (*Func_execle)(const char*, const char*, ...); +typedef int (*Func_execlp)(const char*, const char*, ...); +typedef int (*Func_execv)(const char*, char* const []); +typedef int (*Func_execvp)(const char*, char* const []); +typedef FILE* (*Func_fopen)(const char*, const char*); +typedef FILE* (*Func_fopen64)(const char*, const char*); +typedef FILE* (*Func_freopen)(const char*, const char*, FILE*); +typedef int (*Func_ftw)(const char*, + int (*fn) (const char*, const struct stat*, int), + int); +typedef DIR* (*Func_opendir)(const char* path); +typedef void* (*Func_dlopen)(const char*, int); + +typedef int (*Func_setpriority)(int, int, int); +//typedef int (*Func_pipe)(int [2]); + + +/* + * Pointers to the actual implementations. + */ +#ifndef CREATE_FUNC_STORAGE +# define EXTERN_FUNC extern +#else +# define EXTERN_FUNC +#endif +EXTERN_FUNC Func_access _ws_access; +EXTERN_FUNC Func_open _ws_open; +EXTERN_FUNC Func_open64 _ws_open64; + +EXTERN_FUNC Func_close _ws_close; +EXTERN_FUNC Func_read _ws_read; +EXTERN_FUNC Func_readv _ws_readv; +EXTERN_FUNC Func_write _ws_write; +EXTERN_FUNC Func_writev _ws_writev; +EXTERN_FUNC Func_mmap _ws_mmap; +EXTERN_FUNC Func_mmap64 _ws_mmap64; +EXTERN_FUNC Func_ioctl _ws_ioctl; + +EXTERN_FUNC Func_chdir _ws_chdir; +EXTERN_FUNC Func_chmod _ws_chmod; +EXTERN_FUNC Func_chown _ws_chown; +EXTERN_FUNC Func_creat _ws_creat; +EXTERN_FUNC Func_execve _ws_execve; +EXTERN_FUNC Func_getcwd _ws_getcwd; +EXTERN_FUNC Func_lchown _ws_lchown; +EXTERN_FUNC Func_link _ws_link; +EXTERN_FUNC Func_lstat _ws_lstat; +EXTERN_FUNC Func_lstat64 _ws_lstat64; +EXTERN_FUNC Func___lxstat _ws___lxstat; +EXTERN_FUNC Func___lxstat64 _ws___lxstat64; +EXTERN_FUNC Func_mkdir _ws_mkdir; +EXTERN_FUNC Func_readlink _ws_readlink; +EXTERN_FUNC Func_rename _ws_rename; +EXTERN_FUNC Func_rmdir _ws_rmdir; +EXTERN_FUNC Func_stat _ws_stat; +EXTERN_FUNC Func_stat64 _ws_stat64; +EXTERN_FUNC Func___xstat _ws___xstat; +EXTERN_FUNC Func___xstat64 _ws___xstat64; +EXTERN_FUNC Func_statfs _ws_statfs; +EXTERN_FUNC Func_statfs64 _ws_statfs64; +EXTERN_FUNC Func_symlink _ws_symlink; +EXTERN_FUNC Func_unlink _ws_unlink; +EXTERN_FUNC Func_utime _ws_utime; +EXTERN_FUNC Func_utimes _ws_utimes; + +EXTERN_FUNC Func_execl _ws_execl; +EXTERN_FUNC Func_execle _ws_execle; +EXTERN_FUNC Func_execlp _ws_execlp; +EXTERN_FUNC Func_execv _ws_execv; +EXTERN_FUNC Func_execvp _ws_execvp; +EXTERN_FUNC Func_fopen _ws_fopen; +EXTERN_FUNC Func_fopen64 _ws_fopen64; +EXTERN_FUNC Func_freopen _ws_freopen; +EXTERN_FUNC Func_ftw _ws_ftw; +EXTERN_FUNC Func_opendir _ws_opendir; +EXTERN_FUNC Func_dlopen _ws_dlopen; + +EXTERN_FUNC Func_setpriority _ws_setpriority; +//EXTERN_FUNC Func_pipe _ws_pipe; + +#define kMaxDisplays 4 + +/* + * Global values. Must be initialized in initGlobals(), which is executed + * the first time somebody calls dlopen on the wrapper lib. + */ +struct WrapSimGlobals { + volatile int initialized; + + /* descriptor where we write log messages */ + int logFd; + + /* socket for communicating with simulator front-end */ + int simulatorFd; + + /* coordinate thread startup */ + pthread_mutex_t startLock; + pthread_cond_t startCond; + int startReady; + int simulatorInitFailed; + + /* base directory for filename remapping */ + char* remapBaseDir; + int remapBaseDirLen; + + /* + * Display characteristics. + * + * TODO: this is retrieved from the simulator during initial config. + * It needs to be visible to whatever process holds the surfaceflinger, + * which may or may not be the initial process in multi-process mode. + * We probably want to get the display config via a query, performed at + * intercepted-ioctl time, rather than a push from the sim at startup. + */ + struct { + int width; + int height; + + int shmemKey; + int shmid; + void* addr; + long length; + int semid; + } display[kMaxDisplays]; + int numDisplays; + + /* + * Input device. + */ + FakeDev* keyInputDevice; + const char *keyMap; + + /* fake file descriptor allocation map */ + pthread_mutex_t fakeFdLock; + BitVector* fakeFdMap; + FakeDev* fakeFdList[kMaxFakeFdCount]; +}; + +extern struct WrapSimGlobals gWrapSim; + +#endif /*_WRAPSIM_GLOBALS_H*/ diff --git a/simulator/wrapsim/Init.c b/simulator/wrapsim/Init.c new file mode 100644 index 000000000..eed650bfe --- /dev/null +++ b/simulator/wrapsim/Init.c @@ -0,0 +1,188 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Initialize the intercepts. + */ +#include "Common.h" + +#define __USE_GNU /* need RTLD_NEXT */ +#include <dlfcn.h> + +#include <stdlib.h> +#include <pthread.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/stat.h> +#include <sys/types.h> + + +/* + * Global state. + */ +struct WrapSimGlobals gWrapSim; +pthread_once_t gWrapSimInitialized = PTHREAD_ONCE_INIT; + +/* + * Initialize our global state. + */ +static void initGlobals(void) +{ + memset(&gWrapSim, 0xdd, sizeof(gWrapSim)); + gWrapSim.logFd = -1; + gWrapSim.keyMap = NULL; + + /* + * Find the original version of functions we override. + */ + _ws_access = dlsym(RTLD_NEXT, "access"); + _ws_open = dlsym(RTLD_NEXT, "open"); + _ws_open64 = dlsym(RTLD_NEXT, "open64"); + + _ws_close = dlsym(RTLD_NEXT, "close"); + _ws_read = dlsym(RTLD_NEXT, "read"); + _ws_readv = dlsym(RTLD_NEXT, "readv"); + _ws_write = dlsym(RTLD_NEXT, "write"); + _ws_writev = dlsym(RTLD_NEXT, "writev"); + _ws_mmap = dlsym(RTLD_NEXT, "mmap"); + _ws_mmap64 = dlsym(RTLD_NEXT, "mmap64"); + _ws_ioctl = dlsym(RTLD_NEXT, "ioctl"); + + _ws_chdir = dlsym(RTLD_NEXT, "chdir"); + _ws_chmod = dlsym(RTLD_NEXT, "chmod"); + _ws_chown = dlsym(RTLD_NEXT, "chown"); + _ws_creat = dlsym(RTLD_NEXT, "creat"); + _ws_execve = dlsym(RTLD_NEXT, "execve"); + _ws_getcwd = dlsym(RTLD_NEXT, "getcwd"); + _ws_lchown = dlsym(RTLD_NEXT, "lchown"); + _ws_link = dlsym(RTLD_NEXT, "link"); + _ws_lstat = dlsym(RTLD_NEXT, "lstat"); + _ws_lstat64 = dlsym(RTLD_NEXT, "lstat64"); + _ws___lxstat = dlsym(RTLD_NEXT, "__lxstat"); + _ws___lxstat64 = dlsym(RTLD_NEXT, "__lxstat64"); + _ws_mkdir = dlsym(RTLD_NEXT, "mkdir"); + _ws_readlink = dlsym(RTLD_NEXT, "readlink"); + _ws_rename = dlsym(RTLD_NEXT, "rename"); + _ws_rmdir = dlsym(RTLD_NEXT, "rmdir"); + _ws_stat = dlsym(RTLD_NEXT, "stat"); + _ws_stat64 = dlsym(RTLD_NEXT, "stat64"); + _ws___xstat = dlsym(RTLD_NEXT, "__xstat"); + _ws___xstat64 = dlsym(RTLD_NEXT, "__xstat64"); + _ws_statfs = dlsym(RTLD_NEXT, "statfs"); + _ws_statfs64 = dlsym(RTLD_NEXT, "statfs64"); + _ws_symlink = dlsym(RTLD_NEXT, "symlink"); + _ws_unlink = dlsym(RTLD_NEXT, "unlink"); + _ws_utime = dlsym(RTLD_NEXT, "utime"); + _ws_utimes = dlsym(RTLD_NEXT, "utimes"); + + _ws_execl = dlsym(RTLD_NEXT, "execl"); + _ws_execle = dlsym(RTLD_NEXT, "execle"); + _ws_execlp = dlsym(RTLD_NEXT, "execlp"); + _ws_execv = dlsym(RTLD_NEXT, "execv"); + _ws_execvp = dlsym(RTLD_NEXT, "execvp"); + _ws_fopen = dlsym(RTLD_NEXT, "fopen"); + _ws_fopen64 = dlsym(RTLD_NEXT, "fopen64"); + _ws_freopen = dlsym(RTLD_NEXT, "freopen"); + _ws_ftw = dlsym(RTLD_NEXT, "ftw"); + _ws_opendir = dlsym(RTLD_NEXT, "opendir"); + _ws_dlopen = dlsym(RTLD_NEXT, "dlopen"); + + _ws_setpriority = dlsym(RTLD_NEXT, "setpriority"); + //_ws_pipe = dlsym(RTLD_NEXT, "pipe"); + + const char* logFileName = getenv("WRAPSIM_LOG"); + if (logFileName != NULL ){ + gWrapSim.logFd = _ws_open(logFileName, O_WRONLY|O_APPEND|O_CREAT, 0664); + } + + /* log messages now work; say hello */ + wsLog("--- initializing sim wrapper ---\n"); + + gWrapSim.simulatorFd = -1; + + pthread_mutex_init(&gWrapSim.startLock, NULL); + pthread_cond_init(&gWrapSim.startCond, NULL); + gWrapSim.startReady = 0; + + pthread_mutex_init(&gWrapSim.fakeFdLock, NULL); + gWrapSim.fakeFdMap = wsAllocBitVector(kMaxFakeFdCount, 0); + memset(gWrapSim.fakeFdList, 0, sizeof(gWrapSim.fakeFdList)); + + gWrapSim.numDisplays = 0; + + gWrapSim.keyInputDevice = NULL; + + /* + * Get target for remapped "/system" and "/data". + * + * The ANDROID_PRODUCT_OUT env var *must* be set for rewriting to work. + */ + const char* outEnv = getenv("ANDROID_PRODUCT_OUT"); + if (outEnv == NULL) { + gWrapSim.remapBaseDir = NULL; + wsLog("--- $ANDROID_PRODUCT_OUT not set, " + "filename remapping disabled\n"); + } else { + /* grab string and append '/' -- note this never gets freed */ + gWrapSim.remapBaseDirLen = strlen(outEnv); + gWrapSim.remapBaseDir = strdup(outEnv); + wsLog("--- name remap to %s\n", gWrapSim.remapBaseDir); + } + + gWrapSim.initialized = 1; +} + +/* + * Creates a directory, or prints a log message if it fails. + */ +static int createTargetDirectory(const char *path, mode_t mode) +{ + int ret; + + ret = mkdir(path, mode); + if (ret == 0 || errno == EEXIST) { + return 0; + } + wsLog("--- could not create target directory %s: %s\n", + path, strerror(errno)); + return ret; +} + +/* + * Any setup that would normally be done by init(8). + * Note that since the syscall redirects have been installed + * at this point, we are effectively operating within the + * simulation context. + */ +static void initGeneral(void) +{ + wsLog("--- preparing system\n"); + + /* Try to make sure that certain directories exist. + * If we fail to create them, the errors will show up in the log, + * but we keep going. + */ + createTargetDirectory("/data", 0777); + createTargetDirectory("/data/dalvik-cache", 0777); +} + +/* + * Initialize all necessary state, and indicate that we're ready to go. + */ +static void initOnce(void) +{ + initGlobals(); + initGeneral(); +} + +/* + * Shared object initializer. glibc guarantees that this function is + * called before dlopen() returns. It may be called multiple times. + */ +__attribute__((constructor)) +static void initialize(void) +{ + pthread_once(&gWrapSimInitialized, initOnce); +} + + diff --git a/simulator/wrapsim/Intercept.c b/simulator/wrapsim/Intercept.c new file mode 100644 index 000000000..29b0e0074 --- /dev/null +++ b/simulator/wrapsim/Intercept.c @@ -0,0 +1,816 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Syscall and library intercepts. + */ + +/* don't remap open() to open64() */ +#undef _FILE_OFFSET_BITS + +#define CREATE_FUNC_STORAGE +#include "Common.h" + +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/uio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <utime.h> +#include <limits.h> +#include <ftw.h> +#include <assert.h> + + +#if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64 +#warning "big" +#endif + +//#define CALLTRACE(format, ...) wsLog(format, ##__VA_ARGS__) +#define CALLTRACE(format, ...) ((void)0) + +//#define CALLTRACEV(format, ...) wsLog(format, ##__VA_ARGS__) +#define CALLTRACEV(format, ...) ((void)0) + +/* +When opening certain files, we need to simulate the contents. For example, +we can pretend to open the frame buffer, and respond to ioctl()s by +returning fake data or telling the front-end to render new data. + +We want to intercept specific files in /dev. In some cases we want to +intercept and reject, e.g. to indicate that a standard Linux device does +not exist. + +Some things we're not going to intercept: + /etc/... (e.g. /etc/timezone) -- std. Linux version should be okay + /proc/... (e.g. /proc/stat) -- we're showing real pid, so real proc will work + +For the device drivers we need to intercept: + + close(), ioctl(), mmap(), open()/open64(), read(), readv(), write(), + writev() + +May also need stat(). We don't need all fd calls, e.g. fchdir() is +not likely to succeed on a device driver. The expected uses of mmap() +shouldn't require intercepting related calls like madvise() -- we will +provide an actual mapping of the requested size. In some cases we will +want to return a "real" fd so the app can poll() or select() on it. + + +We also need to consider: + getuid/setuid + variations -- fake out multi-user-id stuff + + +We also want to translate filenames, effectively performing a "chroot" +without all the baggage that comes with it. The mapping looks like: + + /system/... --> $ANDROID_PRODUCT_OUT/system/... + /data/... --> $ANDROID_PRODUCT_OUT/data/... + +Translating pathnames requires interception of additional system calls, +substituting a new path. Calls include: + + access(), chdir(), chmod(), chown(), creat(), execve(), getcwd(), + lchown(), link(), lstat()/lstat64(), mkdir(), open()/open64(), + readlink(), rename(), rmdir(), stat()/stat64(), statfs/statfs64(), + symlink(), unlink(), utimes(), + +Possibly also mknod(), mount(), umount(). + +The "at" family, notably openat(), should just work since the root comes +from an open directory fd. + +We also need these libc calls, because LD_LIBRARY_PATH substitutes at +the libc link level, not the syscall layer: + + execl(), execlp(), execle(), execv(), execvp(), fopen(), ftw(), getwd(), + opendir(), dlopen() + +It is possible for the cwd to leak out. Some possible leaks: + - /proc/[self]/exe + - /proc/[self]/cwd + - LD_LIBRARY_PATH (which may be awkward to work around) + + +To provide a replacement for the dirent functions -- only required if we +want to show "fake" directory contents -- we would need: + + closedir(), dirfd() readdir(), rewinddir(), scandir(), seekdir(), + telldir() + + +*/ + + +/* + * =========================================================================== + * Filename remapping + * =========================================================================== + */ + +/* + * If appropriate, rewrite the path to point to a different location. + * + * Returns either "pathBuf" or "origPath" depending on whether or not we + * chose to rewrite the path. "origPath" must be a buffer capable of + * holding an extended pathname; for best results use PATH_MAX. + */ +static const char* rewritePath(const char* func, char* pathBuf, + const char* origPath) +{ + /* + * Rewrite paths that start with "/system/" or "/data/" + */ + if (origPath[0] != '/') + goto skip_rewrite; + if (memcmp(origPath+1, "system", 6) == 0 && + (origPath[7] == '/' || origPath[7] == '\0')) + goto do_rewrite; + if (memcmp(origPath+1, "data", 4) == 0 && + (origPath[5] == '/' || origPath[5] == '\0')) + goto do_rewrite; + +skip_rewrite: + /* check to see if something is side-stepping the rewrite */ + if (memcmp(origPath, gWrapSim.remapBaseDir, gWrapSim.remapBaseDirLen) == 0) + { + wsLog("NOTE: full path used: %s(%s)\n", func, origPath); + } + + CALLTRACE("rewrite %s('%s') --> (not rewritten)\n", func, origPath); + return origPath; + +do_rewrite: + memcpy(pathBuf, gWrapSim.remapBaseDir, gWrapSim.remapBaseDirLen); + strcpy(pathBuf + gWrapSim.remapBaseDirLen, origPath); + CALLTRACE("rewrite %s('%s') --> '%s'\n", func, origPath, pathBuf); + return pathBuf; +} + +/* + * This works if the pathname is the first argument to the function, and + * the function returns "int". + */ +#define PASS_THROUGH_DECL(_fname, _rtype, ...) \ + _rtype _fname( __VA_ARGS__ ) +#define PASS_THROUGH_BODY(_fname, _patharg, ...) \ + { \ + CALLTRACEV("%s\n", __FUNCTION__); \ + char pathBuf[PATH_MAX]; \ + return _ws_##_fname(rewritePath(#_fname, pathBuf, _patharg), \ + ##__VA_ARGS__); \ + } + + +PASS_THROUGH_DECL(chdir, int, const char* path) +PASS_THROUGH_BODY(chdir, path) + +PASS_THROUGH_DECL(chmod, int, const char* path, mode_t mode) +PASS_THROUGH_BODY(chmod, path, mode) + +PASS_THROUGH_DECL(chown, int, const char* path, uid_t owner, gid_t group) +PASS_THROUGH_BODY(chown, path, owner, group) + +PASS_THROUGH_DECL(creat, int, const char* path, mode_t mode) +PASS_THROUGH_BODY(creat, path, mode) + +PASS_THROUGH_DECL(execve, int, const char* path, char* const argv[], + char* const envp[]) +PASS_THROUGH_BODY(execve, path, argv, envp) + +PASS_THROUGH_DECL(lchown, int, const char* path, uid_t owner, gid_t group) +PASS_THROUGH_BODY(lchown, path, owner, group) + +PASS_THROUGH_DECL(lstat, int, const char* path, struct stat* buf) +PASS_THROUGH_BODY(lstat, path, buf) + +PASS_THROUGH_DECL(lstat64, int, const char* path, struct stat* buf) +PASS_THROUGH_BODY(lstat64, path, buf) + +PASS_THROUGH_DECL(mkdir, int, const char* path, mode_t mode) +PASS_THROUGH_BODY(mkdir, path, mode) + +PASS_THROUGH_DECL(readlink, ssize_t, const char* path, char* buf, size_t bufsiz) +PASS_THROUGH_BODY(readlink, path, buf, bufsiz) + +PASS_THROUGH_DECL(rmdir, int, const char* path) +PASS_THROUGH_BODY(rmdir, path) + +PASS_THROUGH_DECL(stat, int, const char* path, struct stat* buf) +PASS_THROUGH_BODY(stat, path, buf) + +PASS_THROUGH_DECL(stat64, int, const char* path, struct stat* buf) +PASS_THROUGH_BODY(stat64, path, buf) + +PASS_THROUGH_DECL(statfs, int, const char* path, struct statfs* buf) +PASS_THROUGH_BODY(statfs, path, buf) + +PASS_THROUGH_DECL(statfs64, int, const char* path, struct statfs* buf) +PASS_THROUGH_BODY(statfs64, path, buf) + +PASS_THROUGH_DECL(unlink, int, const char* path) +PASS_THROUGH_BODY(unlink, path) + +PASS_THROUGH_DECL(utime, int, const char* path, const struct utimbuf* buf) +PASS_THROUGH_BODY(utime, path, buf) + +PASS_THROUGH_DECL(utimes, int, const char* path, const struct timeval times[2]) +PASS_THROUGH_BODY(utimes, path, times) + + +PASS_THROUGH_DECL(fopen, FILE*, const char* path, const char* mode) +PASS_THROUGH_BODY(fopen, path, mode) + +PASS_THROUGH_DECL(fopen64, FILE*, const char* path, const char* mode) +PASS_THROUGH_BODY(fopen64, path, mode) + +PASS_THROUGH_DECL(freopen, FILE*, const char* path, const char* mode, + FILE* stream) +PASS_THROUGH_BODY(freopen, path, mode, stream) + +PASS_THROUGH_DECL(ftw, int, const char* dirpath, + int (*fn) (const char* fpath, const struct stat* sb, int typeflag), + int nopenfd) +PASS_THROUGH_BODY(ftw, dirpath, fn, nopenfd) + +PASS_THROUGH_DECL(opendir, DIR*, const char* path) +PASS_THROUGH_BODY(opendir, path) + +PASS_THROUGH_DECL(dlopen, void*, const char* path, int flag) +PASS_THROUGH_BODY(dlopen, path, flag) + +/* + * Opposite of path translation -- remove prefix. + * + * It looks like BSD allows you to pass a NULL value for "buf" to inspire + * getcwd to allocate storage with malloc() (as an extension to the POSIX + * definition, which doesn't specify this). getcwd() is a system call + * under Linux, so this doesn't work, but that doesn't stop gdb from + * trying to use it anyway. + */ +char* getcwd(char* buf, size_t size) +{ + CALLTRACEV("%s %p %d\n", __FUNCTION__, buf, size); + + char* result = _ws_getcwd(buf, size); + if (buf != NULL && result != NULL) { + if (memcmp(buf, gWrapSim.remapBaseDir, + gWrapSim.remapBaseDirLen) == 0) + { + memmove(buf, buf + gWrapSim.remapBaseDirLen, + strlen(buf + gWrapSim.remapBaseDirLen)+1); + CALLTRACE("rewrite getcwd() -> %s\n", result); + } else { + CALLTRACE("not rewriting getcwd(%s)\n", result); + } + } + return result; +} + +/* + * Need to tweak both pathnames. + */ +int link(const char* oldPath, const char* newPath) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + char pathBuf1[PATH_MAX]; + char pathBuf2[PATH_MAX]; + return _ws_link(rewritePath("link-1", pathBuf1, oldPath), + rewritePath("link-2", pathBuf2, newPath)); +} + +/* + * Need to tweak both pathnames. + */ +int rename(const char* oldPath, const char* newPath) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + char pathBuf1[PATH_MAX]; + char pathBuf2[PATH_MAX]; + return _ws_rename(rewritePath("rename-1", pathBuf1, oldPath), + rewritePath("rename-2", pathBuf2, newPath)); +} + +/* + * Need to tweak both pathnames. + */ +int symlink(const char* oldPath, const char* newPath) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + char pathBuf1[PATH_MAX]; + char pathBuf2[PATH_MAX]; + return _ws_symlink(rewritePath("symlink-1", pathBuf1, oldPath), + rewritePath("symlink-2", pathBuf2, newPath)); +} + +/* + * glibc stat turns into this (32-bit). + */ +int __xstat(int version, const char* path, struct stat* sbuf) +{ + CALLTRACEV("%s\n", __FUNCTION__); + char pathBuf[PATH_MAX]; + return _ws___xstat(version, rewritePath("__xstat", pathBuf, path), + sbuf); +} + +/* + * glibc stat turns into this (64-bit). + */ +int __xstat64(int version, const char* path, struct stat* sbuf) +{ + CALLTRACEV("%s\n", __FUNCTION__); + char pathBuf[PATH_MAX]; + return _ws___xstat64(version, rewritePath("__xstat64", pathBuf, path), + sbuf); +} + +/* + * glibc lstat turns into this (32-bit). + */ +int __lxstat(int version, const char* path, struct stat* sbuf) +{ + CALLTRACEV("%s\n", __FUNCTION__); + char pathBuf[PATH_MAX]; + return _ws___lxstat(version, rewritePath("__lxstat", pathBuf, path), + sbuf); +} + +/* + * glibc lstat turns into this (64-bit). + */ +int __lxstat64(int version, const char* path, struct stat* sbuf) +{ + CALLTRACEV("%s\n", __FUNCTION__); + char pathBuf[PATH_MAX]; + return _ws___lxstat64(version, rewritePath("__lxstat64", pathBuf, path), + sbuf); +} + +/* + * Copy the argument list out of varargs for execl/execlp/execle. This + * leaves the argc value in _argc, and a NULL-terminated array of character + * pointers in _argv. We stop at the first NULL argument, so we shouldn't + * end up copying "envp" out. + * + * We could use gcc __builtin_apply_args to just pass stuff through, + * but that may not get along with the path rewriting. It's unclear + * whether we want to rewrite the first argument (i.e. the string that + * becomes argv[0]); it only makes sense if the exec'ed program is also + * getting remapped. + */ +#define COPY_EXEC_ARGLIST(_first, _argc, _argv) \ + int _argc = 0; \ + { \ + va_list vargs; \ + va_start(vargs, _first); \ + while (1) { \ + _argc++; \ + const char* val = va_arg(vargs, const char*); \ + if (val == NULL) \ + break; \ + } \ + va_end(vargs); \ + } \ + const char* _argv[_argc+1]; \ + _argv[0] = _first; \ + { \ + va_list vargs; \ + int i; \ + va_start(vargs, _first); \ + for (i = 1; i < _argc; i++) { \ + _argv[i] = va_arg(vargs, const char*); \ + } \ + va_end(vargs); \ + } \ + _argv[_argc] = NULL; + +/* + * Debug dump. + */ +static void dumpExecArgs(const char* callName, const char* path, + int argc, const char* argv[], char* const envp[]) +{ + int i; + + CALLTRACE("Calling %s '%s' (envp=%p)\n", callName, path, envp); + for (i = 0; i <= argc; i++) + CALLTRACE(" %d: %s\n", i, argv[i]); +} + +/* + * Extract varargs, convert paths, hand off to execv. + */ +int execl(const char* path, const char* arg, ...) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + char pathBuf[PATH_MAX]; + + COPY_EXEC_ARGLIST(arg, argc, argv); + dumpExecArgs("execl", path, argc, argv, NULL); + path = rewritePath("execl", pathBuf, path); + return _ws_execv(path, (char* const*) argv); +} + +/* + * Extract varargs, convert paths, hand off to execve. + * + * The execle prototype in the man page isn't valid C -- it shows the + * "envp" argument after the "...". We have to pull it out with the rest + * of the varargs. + */ +int execle(const char* path, const char* arg, ...) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + char pathBuf[PATH_MAX]; + + COPY_EXEC_ARGLIST(arg, argc, argv); + + /* run through again and find envp */ + char* const* envp; + + va_list vargs; + va_start(vargs, arg); + while (1) { + const char* val = va_arg(vargs, const char*); + if (val == NULL) { + envp = va_arg(vargs, char* const*); + break; + } + } + va_end(vargs); + + dumpExecArgs("execle", path, argc, argv, envp); + path = rewritePath("execl", pathBuf, path); + + return _ws_execve(path, (char* const*) argv, envp); +} + +/* + * Extract varargs, convert paths, hand off to execvp. + */ +int execlp(const char* file, const char* arg, ...) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + char pathBuf[PATH_MAX]; + + COPY_EXEC_ARGLIST(arg, argc, argv); + dumpExecArgs("execlp", file, argc, argv, NULL); + file = rewritePath("execlp", pathBuf, file); + return _ws_execvp(file, (char* const*) argv); +} + +/* + * Update path, forward to execv. + */ +int execv(const char* path, char* const argv[]) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + char pathBuf[PATH_MAX]; + + path = rewritePath("execv", pathBuf, path); + return _ws_execv(path, argv); +} + +/* + * Shouldn't need to do anything unless they specified a full path to execvp. + */ +int execvp(const char* file, char* const argv[]) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + char pathBuf[PATH_MAX]; + + file = rewritePath("execvp", pathBuf, file); + return _ws_execvp(file, argv); +} + + +/* + * =========================================================================== + * Device fakery + * =========================================================================== + */ + +/* + * Need to do filesystem translation and show fake devices. + */ +int access(const char* pathName, int mode) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + int status = wsInterceptDeviceAccess(pathName, mode); + if (status == 0) + return 0; + else if (status == -2) + return -1; // errno already set + else { + char pathBuf[PATH_MAX]; + return _ws_access(rewritePath("access", pathBuf, pathName), mode); + } +} + +/* + * Common handler for open(). + */ +int openCommon(const char* pathName, int flags, mode_t mode) +{ + char pathBuf[PATH_MAX]; + int fd; + + assert(gWrapSim.initialized); + + fd = wsInterceptDeviceOpen(pathName, flags); + if (fd >= 0) { + return fd; + } else if (fd == -2) { + /* errno should be set */ + return -1; + } + + if ((flags & O_CREAT) != 0) { + fd = _ws_open(rewritePath("open", pathBuf, pathName), flags, mode); + CALLTRACE("open(%s, 0x%x, 0%o) = %d\n", pathName, flags, mode, fd); + } else { + fd = _ws_open(rewritePath("open", pathBuf, pathName), flags, 0); + CALLTRACE("open(%s, 0x%x) = %d\n", pathName, flags, fd); + } + return fd; +} + +/* + * Replacement open() and variants. + * + * We have to use the vararg decl for the standard call so it matches + * the definition in fcntl.h. + */ +int open(const char* pathName, int flags, ...) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + mode_t mode = 0; + if ((flags & O_CREAT) != 0) { + va_list args; + + va_start(args, flags); + mode = va_arg(args, mode_t); + va_end(args); + } + + return openCommon(pathName, flags, mode); +} +int __open(const char* pathName, int flags, mode_t mode) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + return openCommon(pathName, flags, mode); +} + +/* + * Common handler for open64(). + */ +int open64Common(const char* pathName, int flags, mode_t mode) +{ + char pathBuf[PATH_MAX]; + int fd; + + assert(gWrapSim.initialized); + + fd = wsInterceptDeviceOpen(pathName, flags); + if (fd >= 0) { + return fd; + } + + if ((flags & O_CREAT) != 0) { + fd = _ws_open64(rewritePath("open64", pathBuf, pathName), flags, mode); + CALLTRACE("open64(%s, 0x%x, 0%o) = %d\n", pathName, flags, mode, fd); + } else { + fd = _ws_open64(rewritePath("open64", pathBuf, pathName), flags, 0); + CALLTRACE("open64(%s, 0x%x) = %d\n", pathName, flags, fd); + } + return fd; +} + +/* + * Replacement open64() and variants. + * + * We have to use the vararg decl for the standard call so it matches + * the definition in fcntl.h. + */ +int open64(const char* pathName, int flags, ...) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + mode_t mode = 0; + if ((flags & O_CREAT) != 0) { + va_list args; + + va_start(args, flags); + mode = va_arg(args, mode_t); + va_end(args); + } + return open64Common(pathName, flags, mode); +} +int __open64(const char* pathName, int flags, mode_t mode) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + return open64Common(pathName, flags, mode); +} + + +/* + * Close a file descriptor. + */ +int close(int fd) +{ + CALLTRACEV("%s(%d)\n", __FUNCTION__, fd); + + FakeDev* dev = wsFakeDevFromFd(fd); + if (dev != NULL) { + int result = dev->close(dev, fd); + wsFreeFakeDev(dev); + return result; + } else { + CALLTRACE("close(%d)\n", fd); + return _ws_close(fd); + } +} + +/* + * Map a region. + */ +void* mmap(void* start, size_t length, int prot, int flags, int fd, + __off_t offset) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + FakeDev* dev = wsFakeDevFromFd(fd); + if (dev != NULL) { + return dev->mmap(dev, start, length, prot, flags, fd, offset); + } else { + CALLTRACE("mmap(%p, %d, %d, %d, %d, %d)\n", + start, (int) length, prot, flags, fd, (int) offset); + return _ws_mmap(start, length, prot, flags, fd, offset); + } +} + +/* + * Map a region. + */ +void* mmap64(void* start, size_t length, int prot, int flags, int fd, + __off64_t offset) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + FakeDev* dev = wsFakeDevFromFd(fd); + if (dev != NULL) { + return dev->mmap(dev, start, length, prot, flags, fd, (__off_t) offset); + } else { + CALLTRACE("mmap64(%p, %d, %d, %d, %d, %d)\n", + start, (int) length, prot, flags, fd, (int) offset); + return _ws_mmap(start, length, prot, flags, fd, offset); + } +} + +/* + * The Linux headers show this with a vararg header, but as far as I can + * tell the kernel always expects 3 args. + */ +int ioctl(int fd, int request, ...) +{ + CALLTRACEV("%s(%d, %d, ...)\n", __FUNCTION__, fd, request); + + FakeDev* dev = wsFakeDevFromFd(fd); + va_list args; + void* argp; + + /* extract argp from varargs */ + va_start(args, request); + argp = va_arg(args, void*); + va_end(args); + + if (dev != NULL) { + return dev->ioctl(dev, fd, request, argp); + } else { + CALLTRACE("ioctl(%d, 0x%x, %p)\n", fd, request, argp); + return _ws_ioctl(fd, request, argp); + } +} + +/* + * Read data. + */ +ssize_t read(int fd, void* buf, size_t count) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + FakeDev* dev = wsFakeDevFromFd(fd); + if (dev != NULL) { + return dev->read(dev, fd, buf, count); + } else { + CALLTRACE("read(%d, %p, %u)\n", fd, buf, count); + return _ws_read(fd, buf, count); + } +} + +/* + * Write data. + */ +ssize_t write(int fd, const void* buf, size_t count) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + FakeDev* dev = wsFakeDevFromFd(fd); + if (dev != NULL) { + return dev->write(dev, fd, buf, count); + } else { + CALLTRACE("write(%d, %p, %u)\n", fd, buf, count); + return _ws_write(fd, buf, count); + } +} + +/* + * Read a data vector. + */ +ssize_t readv(int fd, const struct iovec* vector, int count) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + FakeDev* dev = wsFakeDevFromFd(fd); + if (dev != NULL) { + return dev->readv(dev, fd, vector, count); + } else { + CALLTRACE("readv(%d, %p, %u)\n", fd, vector, count); + return _ws_readv(fd, vector, count); + } +} + +/* + * Write a data vector. + */ +ssize_t writev(int fd, const struct iovec* vector, int count) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + FakeDev* dev = wsFakeDevFromFd(fd); + if (dev != NULL) { + return dev->writev(dev, fd, vector, count); + } else { + CALLTRACE("writev(%d, %p, %u)\n", fd, vector, count); + return _ws_writev(fd, vector, count); + } +} + +/* + * Set the scheduling priority. The sim doesn't run as root, so we have + * to fake this out. + * + * For now, do some basic verification of the which and who parameters, + * but otherwise return success. In the future we may want to track + * these so getpriority works. + */ +int setpriority(__priority_which_t which, id_t who, int what) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + if (which != PRIO_PROCESS && + which != PRIO_PGRP && + which != PRIO_USER) { + return EINVAL; + } + + if ((int)who < 0) { + return ESRCH; + } + + return 0; +} + +#if 0 +/* + * Create a pipe. (Only needed for debugging an fd leak.) + */ +int pipe(int filedes[2]) +{ + CALLTRACEV("%s\n", __FUNCTION__); + + int result = _ws_pipe(filedes); + if (result == 0) + CALLTRACE("pipe(%p) -> %d,%d\n", filedes, filedes[0], filedes[1]); + if (filedes[0] == 83) + abort(); + return result; +} +#endif diff --git a/simulator/wrapsim/LaunchWrapper.c b/simulator/wrapsim/LaunchWrapper.c new file mode 100644 index 000000000..c4f0efbb5 --- /dev/null +++ b/simulator/wrapsim/LaunchWrapper.c @@ -0,0 +1,301 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Launch the specified program and, if "-wait" was specified, wait for it + * to exit. + * + * When in "wait mode", print a message indicating the exit status, then + * wait for Ctrl-C before we exit. This is useful if we were launched + * with "xterm -e", because it lets us see the output before the xterm bails. + * + * We want to ignore signals while waiting, so Ctrl-C kills the child rather + * than us, but we need to configure the signals *after* the fork() so we + * don't block them for the child too. + */ +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <limits.h> +#include <fcntl.h> +#include <errno.h> +#include <assert.h> + +/* + * This is appended to $ANDROID_PRODUCT_OUT, + * e.g. "/work/device/out/debug/host/linux-x8x/product/sim". + */ +static const char* kWrapLib = "/system/lib/libwrapsim.so"; + + +/* + * Configure LD_PRELOAD if possible. + * + * Returns newly-allocated storage with the preload path. + */ +static char* configurePreload(void) +{ + const char* outEnv = getenv("ANDROID_PRODUCT_OUT"); + const char* preloadEnv = getenv("LD_PRELOAD"); + char* preload = NULL; + + if (preloadEnv != NULL) { + /* TODO: append our stuff to existing LD_PRELOAD string */ + fprintf(stderr, + "LW WARNING: LD_PRELOAD already set, not adding libwrapsim\n"); + } else if (outEnv == NULL || *outEnv == '\0') { + fprintf(stderr, "LW WARNING: " + "$ANDROID_PRODUCT_OUT not in env, not setting LD_PRELOAD\n"); + } else { + preload = (char*) malloc(strlen(outEnv) + strlen(kWrapLib) +1); + sprintf(preload, "%s%s", outEnv, kWrapLib); + setenv("LD_PRELOAD", preload, 1); + printf("LW: launching with LD_PRELOAD=%s\n", preload); + } + + /* Let the process know that it's executing inside this LD_PRELOAD + * wrapper. + */ + setenv("ANDROID_WRAPSIM", "1", 1); + + return preload; +} + +/* + * Configure some environment variables that the runtime wants. + * + * Returns 0 if all goes well. + */ +static int configureEnvironment() +{ + const char* outEnv = getenv("ANDROID_PRODUCT_OUT"); + char pathBuf[PATH_MAX]; + int outLen; + + if (outEnv == NULL || *outEnv == '\0') { + fprintf(stderr, "LW WARNING: " + "$ANDROID_PRODUCT_OUT not in env, not configuring environment\n"); + return 1; + } + outLen = strlen(outEnv); + assert(outLen + 64 < PATH_MAX); + memcpy(pathBuf, outEnv, outLen); + strcpy(pathBuf + outLen, "/system/lib"); + + /* + * Linux wants LD_LIBRARY_PATH + * Mac OS X wants DYLD_LIBRARY_PATH + * gdb under Mac OS X tramples on both of the above, so we added + * ANDROID_LIBRARY_PATH as a workaround. + * + * We're only supporting Linux now, so just set LD_LIBRARY_PATH. Note + * this stomps the existing value, if any. + * + * If we only needed this for System.loadLibrary() we could do it later, + * but we need it to get the runtime started. + */ + printf("LW: setting LD_LIBRARY_PATH=%s\n", pathBuf); + setenv("LD_LIBRARY_PATH", pathBuf, 1); + + /* + * Trusted certificates are found, for some bizarre reason, through + * the JAVA_HOME environment variable. We don't need to set this + * here, but it's convenient to do so. + */ + strcpy(pathBuf /*+ outLen*/, "/system"); + printf("LW: setting JAVA_HOME=%s\n", pathBuf); + setenv("JAVA_HOME", pathBuf, 1); + + return 0; +} + +/* + * Redirect stdout/stderr to the specified file. If "fileName" is NULL, + * this returns successfully without doing anything. + * + * Returns 0 on success. + */ +static int redirectStdio(const char* fileName) +{ + int fd; + + if (fileName == NULL) + return 0; + + printf("Redirecting stdio to append to '%s'\n", fileName); + fflush(stdout); + fflush(stderr); + + fd = open(fileName, O_WRONLY | O_APPEND | O_CREAT, 0666); + if (fd < 0) { + fprintf(stderr, "ERROR: unable to open '%s' for writing\n", + fileName); + return 1; + } + dup2(fd, 1); + dup2(fd, 2); + close(fd); + + return 0; +} + +/* + * Launch the requested process directly. + * + * On success this does not return (ever). + */ +static int launch(char* argv[], const char* outputFile) +{ + (void) configurePreload(); + (void) redirectStdio(outputFile); + execvp(argv[0], argv); + fprintf(stderr, "execvp %s failed: %s\n", argv[0], strerror(errno)); + return 1; +} + +/* + * Launch in a sub-process and wait for it to finish. + */ +static int launchWithWait(char* argv[], const char* outputFile) +{ + pid_t child; + + child = fork(); + if (child < 0) { + fprintf(stderr, "fork() failed: %s\n", strerror(errno)); + return 1; + } else if (child == 0) { + /* + * This is the child, set up LD_PRELOAD if possible and launch. + */ + (void) configurePreload(); + (void) redirectStdio(outputFile); + execvp(argv[0], argv); + fprintf(stderr, "execvp %s failed: %s\n", argv[0], strerror(errno)); + return 1; + } else { + pid_t result; + int status; + + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + + while (1) { + printf("LW: in pid %d (grp=%d), waiting on pid %d\n", + (int) getpid(), (int) getpgrp(), (int) child); + result = waitpid(child, &status, 0); + if (result < 0) { + if (errno == EINTR) { + printf("Hiccup!\n"); + continue; + } else { + fprintf(stderr, "waitpid failed: %s\n", strerror(errno)); + return 1; + } + } else if (result != child) { + fprintf(stderr, "bizarre: waitpid returned %d (wanted %d)\n", + result, child); + return 1; + } else { + break; + } + } + + printf("\n"); + if (WIFEXITED(status)) { + printf("LW: process exited (status=%d)", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + printf("LW: process killed by signal %d", WTERMSIG(status)); + } else { + printf("LW: process freaked out, status=0x%x\n", status); + } + if (WCOREDUMP(status)) { + printf(" (core dumped)"); + } + printf("\n"); + + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + + /* + * The underlying process may have changed process groups and pulled + * itself into the foreground. Now that it's gone, pull ourselves + * back into the foreground. + */ + signal(SIGTTOU, SIG_IGN); + if (tcsetpgrp(fileno(stdin), getpgrp()) != 0) + fprintf(stderr, "WARNING: tcsetpgrp failed\n"); + + printf("\nHit Ctrl-C or close window.\n"); + + while (1) { + sleep(10); + } + + /* not reached */ + return 0; + } +} + + +/* + * All args are passed through. + */ +int main(int argc, char** argv) +{ + int waitForChild = 0; + const char* outputFile = NULL; + int result; + + /* + * Skip past the reference to ourselves, and check for args. + */ + argv++; + argc--; + while (argc > 0) { + if (strcmp(argv[0], "-wait") == 0) { + waitForChild = 1; + } else if (strcmp(argv[0], "-output") == 0 && argc > 1) { + argv++; + argc--; + outputFile = argv[0]; + } else { + /* no more args for us */ + break; + } + + argv++; + argc--; + } + + if (argc == 0) { + fprintf(stderr, + "Usage: launch-wrapper [-wait] [-output filename] <cmd> [args...]\n"); + result = 2; + goto bail; + } + + /* + * Configure some environment variables. + */ + if (configureEnvironment() != 0) { + result = 1; + goto bail; + } + + /* + * Launch. + */ + if (waitForChild) + result = launchWithWait(argv, outputFile); + else + result = launch(argv, outputFile); + +bail: + if (result != 0) + sleep(2); + return result; +} + diff --git a/simulator/wrapsim/Log.c b/simulator/wrapsim/Log.c new file mode 100644 index 000000000..7edb6771a --- /dev/null +++ b/simulator/wrapsim/Log.c @@ -0,0 +1,74 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Debug-logging code. + */ +#include "Common.h" + +#include <stdio.h> +#include <stdarg.h> +#include <time.h> + +/* + * Write a message to our private log file. This is a little awkward since + * some or all of the system calls we want to use are being intercepted. + */ +void wsLog(const char* format, ...) +{ +#if defined(HAVE_LOCALTIME_R) + struct tm tmBuf; +#endif + struct tm* ptm; + time_t now; + char timeBuf[32]; + char prefixBuf[64]; + int prefixLen; + char msgBuf[256]; + int msgLen; + + if (gWrapSim.logFd < 0) + return; + + /* + * Create a prefix with a timestamp. + */ + now = time(NULL); +#if defined(HAVE_LOCALTIME_R) + ptm = localtime_r(&now, &tmBuf); +#else + ptm = localtime(&now); +#endif + //strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm); + strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", ptm); + + prefixLen = snprintf(prefixBuf, sizeof(prefixBuf), "%s %5d ", + timeBuf, (int) getpid()); + + /* + * Format the message into a buffer. + */ + va_list args; + + va_start(args, format); + msgLen = vsnprintf(msgBuf, sizeof(msgBuf), format, args); + va_end(args); + + /* if we overflowed, trim and annotate */ + if (msgLen >= (int) sizeof(msgBuf)) { + msgBuf[sizeof(msgBuf)-2] = '!'; + msgBuf[sizeof(msgBuf)-1] = '\n'; + msgLen = sizeof(msgBuf); + } + + /* + * Write the whole thing in one shot. The log file was opened with + * O_APPEND so we don't have to worry about clashes. + */ + struct iovec logVec[2]; + logVec[0].iov_base = prefixBuf; + logVec[0].iov_len = prefixLen; + logVec[1].iov_base = msgBuf; + logVec[1].iov_len = msgLen; + (void) _ws_writev(gWrapSim.logFd, logVec, 2); +} + diff --git a/simulator/wrapsim/Log.h b/simulator/wrapsim/Log.h new file mode 100644 index 000000000..024ba4459 --- /dev/null +++ b/simulator/wrapsim/Log.h @@ -0,0 +1,19 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Logging. + */ +#ifndef _WRAPSIM_LOG_H +#define _WRAPSIM_LOG_H + + +/* + * Log debug info. + */ +void wsLog(const char* format, ...) + #if defined(__GNUC__) + __attribute__ ((format(printf, 1, 2))) + #endif + ; + +#endif /*_WRAPSIM_LOG_H*/ diff --git a/simulator/wrapsim/README.txt b/simulator/wrapsim/README.txt new file mode 100644 index 000000000..358c06c4a --- /dev/null +++ b/simulator/wrapsim/README.txt @@ -0,0 +1,21 @@ +This shared library is used with LD_PRELOAD to wrap the Android runtime +when used with the desktop simulator. + +Because LD_PRELOAD is part of the environment when gdb and valgrind are +invoked, the wrapper can "see" activity from both of these (more so gdb +than valgrind). For this reason it needs to be very careful about which +"open" calls are intercepted. + +It's also important that none of the intercepted system or library calls +are invoked directly, or infinite recursion could result. + +Avoid creating dependencies on other libraries. + + +To debug wrapsim, set WRAPSIM_LOG to a log file before launching, e.g. + +% WRAPSIM_LOG=/tmp/wraplog.txt simulator + +For more verbose logging, you can enable the verbose forms of CALLTRACE +and CALLTRACEV in Intercept.c. + diff --git a/simulator/wrapsim/SimMgr.c b/simulator/wrapsim/SimMgr.c new file mode 100644 index 000000000..a35c7c6ad --- /dev/null +++ b/simulator/wrapsim/SimMgr.c @@ -0,0 +1,988 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Simulator interactions. + * + * TODO: for multi-process we probably need to switch to a new process + * group if we are the first process (could be runtime, could be gdb), + * rather than wait for the simulator to tell us to switch. + */ +#include "Common.h" + +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <sys/sem.h> +#include <sys/un.h> +#include <signal.h> +#include <assert.h> + +// fwd +static int connectToSim(void); +static void listenToSim(void); + +/* + * Env var to restrict who tries to talk to the front end. + */ +#define kWrapSimConnectedEnv "WRAP_SIM_CONNECTED" + + +/* + * Signal the main thread that we're ready to continue. + */ +static void signalMainThread(void) +{ + int cc; + + cc = pthread_mutex_lock(&gWrapSim.startLock); + assert(cc == 0); + + gWrapSim.startReady = 1; + + cc = pthread_cond_signal(&gWrapSim.startCond); + assert(cc == 0); + + cc = pthread_mutex_unlock(&gWrapSim.startLock); + assert(cc == 0); +} + + +/* + * Entry point for the sim management thread. + * + * Once we have established a connection to the simulator and are ready + * for other threads to send messages, we signal the main thread. + */ +static void* simThreadEntry(void* arg) +{ + wsLog("--- sim manager thread started\n"); + + /* + * Establish a connection to the simulator front-end. If we can't do + * that, we have no access to input or output devices, and we might + * as well give up. + */ + if (connectToSim() != 0) { + signalMainThread(); + return NULL; + } + + /* success! */ + wsLog("--- sim manager thread ready\n"); + gWrapSim.simulatorInitFailed = 0; + signalMainThread(); + + listenToSim(); + + wsLog("--- sim manager thread exiting\n"); + + return NULL; +} + +/* + * If we think we're not yet connected to the sim, do so now. We only + * want to do this once per process *group*, so we control access with + * an environment variable. + */ +int wsSimConnect(void) +{ + /* + * If the environment variable hasn't been set, assume we're the first + * to get here, and should attach to the simulator. We set the env + * var here whether or not we succeed in connecting to the sim. + * + * (For correctness we should wrap the getenv/setenv in a semaphore.) + */ + if (getenv(kWrapSimConnectedEnv) == NULL) { + pthread_attr_t threadAttr; + pthread_t threadHandle; + int cc; + + gWrapSim.simulatorInitFailed = 1; + setenv(kWrapSimConnectedEnv, "1", 1); + + cc = pthread_mutex_lock(&gWrapSim.startLock); + assert(cc == 0); + + pthread_attr_init(&threadAttr); + pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED); + cc = pthread_create(&threadHandle, &threadAttr, simThreadEntry, NULL); + if (cc != 0) { + wsLog("Unable to create new thread: %s\n", strerror(errno)); + abort(); + } + + while (!gWrapSim.startReady) { + cc = pthread_cond_wait(&gWrapSim.startCond, &gWrapSim.startLock); + assert(cc == 0); + } + + cc = pthread_mutex_unlock(&gWrapSim.startLock); + assert(cc == 0); + + if (gWrapSim.simulatorInitFailed) { + wsLog("Simulator initialization failed, bailing\n"); + + /* this *should* be okay to do */ + fprintf(stderr, "Fatal error:" + " unable to connect to sim front-end (not running?)\n"); + abort(); + } + } + + wsLog("+++ continuing\n"); + return 0; +} + + +/* + * =========================================================================== + * Message / MessageStream + * =========================================================================== + */ + +/* + * This is a quick & dirty rewrite of the C++ Message and MessageStream + * classes, ported to C, reduced in generality, with syscalls stubbed + * where necessary. I didn't fix the API to make it more sensible in C + * (which lacks destructors), so some of this is a little fragile or + * awkward. + */ + +/* values for message type byte; must match android::Message constants */ +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; + +/* + * Reusable message object. + */ +typedef struct Message { + MessageType mType; + unsigned char* mData; + int mLength; +} Message; + +/* magic init messages; must match android::MessageStream constants */ +enum { + kHelloMsg = 0x4e303047, // 'N00G' + kHelloAckMsg = 0x31455221, // '1ER!' +}; + + +/* + * Clear out a Message. + */ +static void Message_clear(Message* msg) +{ + memset(msg, 0, sizeof(Message)); +} + +/* + * Keep reading until we get all bytes or hit EOF/error. "fd" is expected + * to be in blocking mode. + * + * Returns 0 on success. + */ +static int readAll(int fd, void* buf, size_t count) +{ + ssize_t actual; + ssize_t have; + + have = 0; + while (have != (ssize_t) count) { + actual = _ws_read(fd, ((char*) buf) + have, count - have); + if (actual < 0) { + if (errno == EINTR) + continue; + wsLog("read %d failed: %s\n", fd, strerror(errno)); + } else if (actual == 0) { + wsLog("early EOF on %d\n", fd); + return -1; + } else { + have += actual; + } + + assert(have <= (ssize_t)count); + } + + return 0; +} + +#if 0 +/* + * Keep writing until we put all bytes or hit an error. "fd" is expected + * to be in blocking mode. + * + * Returns 0 on success. + */ +static int writeAll(int fd, const void* buf, size_t count) +{ + ssize_t actual; + ssize_t have; + + have = 0; + while (have != count) { + actual = _ws_write(fd, ((const char*) buf) + have, count - have); + if (actual < 0) { + if (errno == EINTR) + continue; + wsLog("write %d failed: %s\n", fd, strerror(errno)); + } else if (actual == 0) { + wsLog("wrote zero on %d\n", fd); + return -1; + } else { + have += actual; + } + + assert(have <= count); + } + + return 0; +} +#endif + +/* + * Read a message from the specified file descriptor. + * + * The caller must Message_release(&msg). + * + * We guarantee 32-bit alignment for msg->mData. + */ +static int Message_read(Message* msg, int fd) +{ + unsigned char header[4]; + + readAll(fd, header, 4); + + msg->mType = (MessageType) header[2]; + msg->mLength = header[0] | header[1] << 8; + msg->mLength -= 2; // we already read two of them in the header + + if (msg->mLength > 0) { + int actual; + + /* Linux malloc guarantees at least 32-bit alignment */ + msg->mData = (unsigned char*) malloc(msg->mLength); + if (msg->mData == NULL) { + wsLog("alloc %d failed\n", msg->mLength); + return -1; + } + + if (readAll(fd, msg->mData, msg->mLength) != 0) { + wsLog("failed reading message body (wanted %d)\n", msg->mLength); + return -1; + } + } else { + msg->mData = NULL; + } + + return 0; +} + +/* + * Write a message to the specified file descriptor. + * + * The caller must Message_release(&msg). + */ +static int Message_write(Message* msg, int fd) +{ + struct iovec writeVec[2]; + unsigned char header[4]; + int len, numVecs; + ssize_t actual; + + len = msg->mLength + 2; + header[0] = len & 0xff; + header[1] = (len >> 8) & 0xff; + header[2] = msg->mType; + header[3] = 0; + writeVec[0].iov_base = header; + writeVec[0].iov_len = 4; + numVecs = 1; + + if (msg->mLength > 0) { + assert(msg->mData != NULL); + writeVec[1].iov_base = msg->mData; + writeVec[1].iov_len = msg->mLength; + numVecs++; + } + + /* write it all in one shot; not worrying about partial writes for now */ + actual = _ws_writev(fd, writeVec, numVecs); + if (actual != len+2) { + wsLog("failed writing message to fd %d: %d of %d %s\n", + fd, actual, len+2, strerror(errno)); + return -1; + } + + return 0; +} + +/* + * Release storage associated with a Message. + */ +static void Message_release(Message* msg) +{ + free(msg->mData); + msg->mData = NULL; +} + +/* + * Extract a name/value pair from a message. + */ +static int getConfig(const Message* msg, const char** name, const char** val) +{ + if (msg->mLength < 2) { + wsLog("message len (%d) is too short\n", msg->mLength); + return -1; + } + const char* ptr = (const char*) msg->mData; + + *name = (const char*) ptr; + *val = (const char*) (ptr + strlen((char*)ptr) +1); + return 0; +} + +/* + * Extract a command from a message. + */ +static int getCommand(const Message* msg, int* pCmd, int* pArg) +{ + if (msg->mLength != sizeof(int) * 2) { + wsLog("message len (%d) is wrong for cmd (%d)\n", + msg->mLength, sizeof(int) * 2); + return -1; + } + + /* this assumes 32-bit alignment on mData */ + const int* ptr = (const int*) msg->mData; + + *pCmd = ptr[0]; + *pArg = ptr[1]; + + return 0; +} + +/* + * Extract an extended command from a message. + */ +static int getCommandExt(const Message* msg, int* pCmd, int* pArg0, + int* pArg1, int* pArg2) +{ + if (msg->mLength != sizeof(int) * 4) { + wsLog("message len (%d) is wrong for cmd (%d)\n", + msg->mLength, sizeof(int) * 4); + return -1; + } + + /* this assumes 32-bit alignment on mData */ + const int* ptr = (const int*) msg->mData; + + *pCmd = ptr[0]; + *pArg0 = ptr[1]; + *pArg1 = ptr[2]; + *pArg2 = ptr[3]; + + return 0; +} + +/* + * Attach 8 bytes of data with "cmd" and "arg" to "msg". + * + * "msg->mData" will need to be freed by the caller. (This approach made + * more sense when C++ destructors were available, but it's just not worth + * reworking it.) + */ +static int setCommand(Message* msg, int cmd, int arg) +{ + Message_clear(msg); + + msg->mLength = 8; + msg->mData = malloc(msg->mLength); + msg->mType = kTypeCommand; + + /* assumes 32-bit alignment on malloc blocks */ + int* pInt = (int*) msg->mData; + pInt[0] = cmd; + pInt[1] = arg; + + return 0; +} + +/* + * Construct the full path. The caller must free() the return value. + */ +static char* makeFilename(const char* name) +{ + static const char* kBasePath = "/tmp/android-"; + char* fileName; + + assert(name != NULL && name[0] != '\0'); + + fileName = (char*) malloc(strlen(kBasePath) + strlen(name) + 1); + strcpy(fileName, kBasePath); + strcat(fileName, name); + + return fileName; +} + +/* + * Attach to a SysV shared memory segment. + */ +static int attachToShmem(int key, int* pShmid, void** pAddr, long* pLength) +{ + int shmid; + + shmid = shmget(key, 0, 0); + if (shmid == -1) { + wsLog("ERROR: failed to find shmem key=%d\n", key); + return -1; + } + + void* addr = shmat(shmid, NULL, 0); + if (addr == (void*) -1) { + wsLog("ERROR: could not attach to key=%d shmid=%d\n", key, shmid); + return -1; + } + + struct shmid_ds shmids; + int cc; + + cc = shmctl(shmid, IPC_STAT, &shmids); + if (cc != 0) { + wsLog("ERROR: could not IPC_STAT shmid=%d\n", shmid); + return -1; + } + *pLength = shmids.shm_segsz; + + *pAddr = addr; + *pShmid = shmid; + return 0; +} + +/* + * Attach to a SysV semaphore. + */ +static int attachToSem(int key, int* pSemid) +{ + int semid; + + semid = semget(key, 0, 0); + if (semid == -1) { + wsLog("ERROR: failed to attach to semaphore key=%d\n", key); + return -1; + } + + *pSemid = semid; + return 0; +} + +/* + * "Adjust" a semaphore. + */ +static int adjustSem(int semid, int adj) +{ + const int wait = 1; + struct sembuf op; + int cc; + + op.sem_num = 0; + op.sem_op = adj; + op.sem_flg = SEM_UNDO; + if (!wait) + op.sem_flg |= IPC_NOWAIT; + + cc = semop(semid, &op, 1); + if (cc != 0) { + if (wait || errno != EAGAIN) { + wsLog("Warning:" + " semaphore adjust by %d failed for semid=%d (errno=%d)\n", + adj, semid, errno); + } + return -1; + } + + return 0; +} + +/* + * Acquire the semaphore associated with a display. + */ +void wsLockDisplay(int displayIdx) +{ + assert(displayIdx >= 0 && displayIdx < gWrapSim.numDisplays); + int semid = gWrapSim.display[displayIdx].semid; + + (void) adjustSem(semid, -1); +} + +/* + * Acquire the semaphore associated with a display. + */ +void wsUnlockDisplay(int displayIdx) +{ + assert(displayIdx >= 0 && displayIdx < gWrapSim.numDisplays); + int semid = gWrapSim.display[displayIdx].semid; + + (void) adjustSem(semid, 1); +} + +/* + * Process the display config from the simulator + * + * Right now this is a blob of raw data that looks like this: + * +00 magic number + * +04 #of displays + * +08 display 0: + * +00 width + * +04 height + * +08 format + * +0c refresh rate + * +10 shmem key + * +1c display 1... + */ +static int handleDisplayConfig(const int* pData, int length) +{ + int numDisplays; + + if (length < 8) { + wsLog("Bad display config: length is %d\n", length); + return -1; + } + assert(*pData == kDisplayConfigMagic); + + /* + * Pull out the #of displays. If it looks valid, configure the runtime. + */ + pData++; // skip over magic + numDisplays = *pData++; + + if (numDisplays <= 0 || numDisplays > kMaxDisplays) { + wsLog("Bizarre display count %d\n", numDisplays); + return -1; + } + if (length != 8 + numDisplays * kValuesPerDisplay * (int)sizeof(int)) { + wsLog("Bad display config: length is %d (expected %d)\n", + length, 8 + numDisplays * kValuesPerDisplay * (int)sizeof(int)); + return -1; + } + + /* + * Extract the config values. + * + * The runtime doesn't support multiple devices, so we don't either. + */ + int i; + for (i = 0; i < numDisplays; i++) { + gWrapSim.display[i].width = pData[0]; + gWrapSim.display[i].height = pData[1]; + gWrapSim.display[i].shmemKey = pData[4]; + /* format/refresh no longer needed */ + + void* addr; + int shmid, semid; + long length; + if (attachToShmem(gWrapSim.display[i].shmemKey, &shmid, &addr, + &length) != 0) + { + wsLog("Unable to connect to shared memory\n"); + return -1; + } + + if (attachToSem(gWrapSim.display[i].shmemKey, &semid) != 0) { + wsLog("Unable to attach to sempahore\n"); + return -1; + } + + gWrapSim.display[i].shmid = shmid; + gWrapSim.display[i].addr = addr; + gWrapSim.display[i].length = length; + gWrapSim.display[i].semid = semid; + + wsLog("Display %d: width=%d height=%d\n", + i, + gWrapSim.display[i].width, + gWrapSim.display[i].height); + wsLog(" shmem=0x%08x addr=%p len=%ld semid=%d\n", + gWrapSim.display[i].shmemKey, + gWrapSim.display[i].addr, + gWrapSim.display[i].length, + gWrapSim.display[i].semid); + + pData += kValuesPerDisplay; + } + gWrapSim.numDisplays = numDisplays; + + return 0; +} + + +/* + * Initialize our connection to the simulator, which will be listening on + * a UNIX domain socket. + * + * On success, this configures gWrapSim.simulatorFd and returns 0. + */ +static int openSimConnection(const char* name) +{ + int result = -1; + char* fileName = NULL; + int sock = -1; + int cc; + + assert(gWrapSim.simulatorFd == -1); + + fileName = makeFilename(name); + + struct sockaddr_un addr; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + wsLog("UNIX domain socket create failed (errno=%d)\n", errno); + goto bail; + } + + /* connect to socket; fails if file doesn't exist */ + strcpy(addr.sun_path, fileName); // 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 + wsLog("AF_UNIX connect failed for '%s': %s\n", + fileName, strerror(errno)); + goto bail; + } + + gWrapSim.simulatorFd = sock; + sock = -1; + + result = 0; + wsLog("+++ connected to '%s'\n", fileName); + +bail: + if (sock >= 0) + _ws_close(sock); + free(fileName); + return result; +} + +/* + * Prepare communication with the front end. We wait for a "hello" from + * the other side, and respond in kind. + */ +static int prepSimConnection(void) +{ + /* NOTE: this is endian-specific; we're x86 Linux only, so no problem */ + static const unsigned int hello = kHelloMsg; + static const unsigned int helloAck = kHelloAckMsg; + Message msg; + + if (Message_read(&msg, gWrapSim.simulatorFd) != 0) { + wsLog("hello read failed\n"); + return -1; + } + + if (memcmp(msg.mData, &hello, 4) != 0) { + wsLog("Got bad hello from peer\n"); + return -1; + } + + Message_release(&msg); + + msg.mType = kTypeRaw; + msg.mData = (unsigned char*) &helloAck; + msg.mLength = 4; + + if (Message_write(&msg, gWrapSim.simulatorFd) != 0) { + wsLog("hello ack write failed\n"); + return -1; + } + + return 0; +} + +/* + * Get the sim front-end configuration. We loop here until the sim claims + * to be done with us. + */ +static int getSimConfig(void) +{ + Message msg; + int joinNewGroup, grabTerminal, done; + int result = -1; + + joinNewGroup = grabTerminal = done = 0; + Message_clear(&msg); // clear out msg->mData + + wsLog("+++ awaiting hardware configuration\n"); + while (!done) { + if (Message_read(&msg, gWrapSim.simulatorFd) != 0) { + wsLog("failed receiving config from parent\n"); + goto bail; + } + + if (msg.mType == kTypeCommand) { + int cmd, arg; + + if (getCommand(&msg, &cmd, &arg) != 0) + goto bail; + + switch (cmd) { + case kCommandGoAway: + wsLog("Simulator front-end is busy\n"); + goto bail; + case kCommandNewPGroup: + joinNewGroup = 1; + grabTerminal = (arg != 0); + wsLog("Simulator wants us to be in a new pgrp (term=%d)\n", + grabTerminal); + break; + case kCommandConfigDone: + done = 1; + break; + default: + wsLog("Got unexpected command %d/%d\n", cmd, arg); + break; + } + } else if (msg.mType == kTypeRaw) { + /* assumes 32-bit alignment and identical byte ordering */ + int* pData = (int*) msg.mData; + if (msg.mLength >= 4 && *pData == kDisplayConfigMagic) { + if (handleDisplayConfig(pData, msg.mLength) != 0) + goto bail; + } + } else if (msg.mType == kTypeConfig) { + const char *name; + const char *val; + getConfig(&msg, &name, &val); + if(strcmp(name, "keycharmap") == 0) { + free((void*)gWrapSim.keyMap); + gWrapSim.keyMap = strdup(val); + } + } else { + wsLog("Unexpected msg type %d during startup\n", msg.mType); + goto bail; + } + + /* clear out the data field if necessary */ + Message_release(&msg); + } + + wsLog("Configuration received from simulator\n"); + + if (joinNewGroup) { + /* set pgid to pid */ + pid_t pgid = getpid(); + setpgid(0, pgid); + + /* + * Put our pgrp in the foreground. + * tcsetpgrp() from background process causes us to get a SIGTTOU, + * which is mostly harmless but makes tcsetpgrp() fail with EINTR. + */ + signal(SIGTTOU, SIG_IGN); + if (grabTerminal) { + if (tcsetpgrp(fileno(stdin), getpgrp()) != 0) { + wsLog("tcsetpgrp(%d, %d) failed (errno=%d)\n", + fileno(stdin), getpgrp(), errno); + } + wsLog("Set pgrp %d as foreground\n", (int) getpgrp()); + } + + /* tell the sim where we're at */ + Message msg; + setCommand(&msg, kCommandNewPGroupCreated, pgid); + Message_write(&msg, gWrapSim.simulatorFd); + Message_release(&msg); + } + + result = 0; + +bail: + /* make sure the data was freed */ + Message_release(&msg); + //wsLog("bailing, result=%d\n", result); + return result; +} + +/* + * Connect to the simulator and exchange pleasantries. + * + * Returns 0 on success. + */ +static int connectToSim(void) +{ + if (openSimConnection(kAndroidPipeName) != 0) + return -1; + + if (prepSimConnection() != 0) + return -1; + + if (getSimConfig() != 0) + return -1; + + wsLog("+++ sim is ready to go\n"); + + return 0; +} + +/* + * Listen to the sim forever or until the front end shuts down, whichever + * comes first. + * + * All we're really getting here are key events. + */ +static void listenToSim(void) +{ + wsLog("--- listening for input events from front end\n"); + + while (1) { + Message msg; + + Message_clear(&msg); + if (Message_read(&msg, gWrapSim.simulatorFd) != 0) { + wsLog("--- sim message read failed\n"); + return; + } + + if (msg.mType == kTypeCommand) { + int cmd, arg; + + if (getCommand(&msg, &cmd, &arg) != 0) { + wsLog("bad command from sim?\n"); + continue; + } + + switch (cmd) { + case kCommandQuit: + wsLog("--- sim sent us a QUIT message\n"); + return; + case kCommandKeyDown: + wsLog("KEY DOWN: %d\n", arg); + wsSendSimKeyEvent(arg, 1); + break; + case kCommandKeyUp: + wsLog("KEY UP: %d\n", arg); + wsSendSimKeyEvent(arg, 0); + break; + default: + wsLog("--- sim sent unrecognized command %d\n", cmd); + break; + } + + Message_release(&msg); + } else if (msg.mType == kTypeCommandExt) { + int cmd, arg0, arg1, arg2; + + if (getCommandExt(&msg, &cmd, &arg0, &arg1, &arg2) != 0) { + wsLog("bad ext-command from sim?\n"); + continue; + } + + switch (cmd) { + case kCommandTouch: + wsSendSimTouchEvent(arg0, arg1, arg2); + break; + } + + Message_release(&msg); + } else { + wsLog("--- sim sent non-command message, type=%d\n", msg.mType); + } + } + + assert(0); // not reached +} + + +/* + * Tell the simulator front-end that the display has been updated. + */ +void wsPostDisplayUpdate(int displayIdx) +{ + if (gWrapSim.simulatorFd < 0) { + wsLog("Not posting display update -- sim not ready\n"); + return; + } + + Message msg; + + setCommand(&msg, kCommandUpdateDisplay, displayIdx); + Message_write(&msg, gWrapSim.simulatorFd); + Message_release(&msg); +} + +/* + * Send a log message to the front-end. + */ +void wsPostLogMessage(int logPrio, const char* tag, const char* message) +{ + if (gWrapSim.simulatorFd < 0) { + wsLog("Not posting log message -- sim not ready\n"); + return; + } + + time_t when = time(NULL); + int pid = (int) getpid(); + int tagLen, messageLen, totalLen; + + tagLen = strlen(tag) +1; + messageLen = strlen(message) +1; + totalLen = sizeof(int) * 3 + tagLen + messageLen; + unsigned char outBuf[totalLen]; + unsigned char* cp = outBuf; + + /* See Message::set/getLogBundle() in simulator/MessageStream.cpp. */ + memcpy(cp, &when, sizeof(int)); + cp += sizeof(int); + memcpy(cp, &logPrio, sizeof(int)); + cp += sizeof(int); + memcpy(cp, &pid, sizeof(int)); + cp += sizeof(int); + memcpy(cp, tag, tagLen); + cp += tagLen; + memcpy(cp, message, messageLen); + cp += messageLen; + + assert(cp - outBuf == totalLen); + + Message msg; + msg.mType = kTypeLogBundle; + msg.mData = outBuf; + msg.mLength = totalLen; + Message_write(&msg, gWrapSim.simulatorFd); + + msg.mData = NULL; // don't free + Message_release(&msg); +} + +/* + * Turn the vibrating notification device on or off. + */ +void wsEnableVibration(int vibrateOn) +{ + if (gWrapSim.simulatorFd < 0) { + wsLog("Not posting vibrator update -- sim not ready\n"); + return; + } + + Message msg; + + //wsLog("+++ sending vibrate:%d\n", vibrateOn); + + setCommand(&msg, kCommandVibrate, vibrateOn); + Message_write(&msg, gWrapSim.simulatorFd); + Message_release(&msg); +} + diff --git a/simulator/wrapsim/SimMgr.h b/simulator/wrapsim/SimMgr.h new file mode 100644 index 000000000..378dfbc47 --- /dev/null +++ b/simulator/wrapsim/SimMgr.h @@ -0,0 +1,75 @@ +/* + * Copyright 2007 The Android Open Source Project + * + * Simulator interactions. + */ +#ifndef _WRAPSIM_SIMULATOR_H +#define _WRAPSIM_SIMULATOR_H + +/* + * Commands exchanged between simulator and runtime. + * + * NOTE: this is cloned from SimRuntime.h -- fix this. + */ +typedef enum SimCommand { + kCommandUnknown = 0, + + /* sent from sim to runtime */ + kCommandGoAway, // sim says: go away, I'm busy + kCommandConfigDone, // sim says: done sending config + kCommandQuit, // quit nicely + kCommandNewPGroup, // process group management + kCommandKeyDown, // key has been pressed + kCommandKeyUp, // key has been released + kCommandTouch, // finger touched/lifted/dragged + + /* sent from runtime to sim */ + kCommandNewPGroupCreated, // send process group as argument + kCommandRuntimeReady, // we're initialized and about to start + kCommandUpdateDisplay, // display has been updated + kCommandVibrate, // vibrate on or off +} SimCommand; + +/* + * Touch screen action; also clined from SimRuntime.h. + */ +typedef enum TouchMode { + kTouchDown = 0, + kTouchUp = 1, + kTouchDrag = 2 +} TouchMode; + + +/* + * Some parameters for config exchange. + */ +enum { + kDisplayConfigMagic = 0x44495350, + kValuesPerDisplay = 5, +}; + +/* + * UNIX domain socket name. + */ +#define kAndroidPipeName "runtime" + +int wsSimConnect(void); + +/* + * Display management. + */ +void wsLockDisplay(int displayIdx); +void wsUnlockDisplay(int displayIdx); +void wsPostDisplayUpdate(int displayIdx); + +/* + * Send a log message. + */ +void wsPostLogMessage(int logPrio, const char* tag, const char* msg); + +/* + * Change the state of the vibration device. + */ +void wsEnableVibration(int vibrateOn); + +#endif /*_WRAPSIM_SIMULATOR_H*/ |
