diff options
Diffstat (limited to 'widget/gonk/nsAppShell.cpp')
-rw-r--r-- | widget/gonk/nsAppShell.cpp | 1087 |
1 files changed, 1087 insertions, 0 deletions
diff --git a/widget/gonk/nsAppShell.cpp b/widget/gonk/nsAppShell.cpp new file mode 100644 index 000000000..24e791b4b --- /dev/null +++ b/widget/gonk/nsAppShell.cpp @@ -0,0 +1,1087 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=4 sw=4 sts=4 tw=80 et: */ +/* Copyright 2012 Mozilla Foundation and Mozilla contributors + * + * 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 _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <hardware_legacy/power.h> +#include <signal.h> +#include <sys/epoll.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <utils/BitSet.h> + +#include "base/basictypes.h" +#include "GonkPermission.h" +#include "libdisplay/BootAnimation.h" +#include "nscore.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/FileUtils.h" +#include "mozilla/Hal.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/Mutex.h" +#include "mozilla/Services.h" +#include "mozilla/TextEvents.h" +#if ANDROID_VERSION >= 18 +#include "nativewindow/FakeSurfaceComposer.h" +#endif +#include "nsAppShell.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/Touch.h" +#include "nsGkAtoms.h" +#include "nsIObserverService.h" +#include "nsIScreen.h" +#include "nsScreenManagerGonk.h" +#include "nsThreadUtils.h" +#include "nsWindow.h" +#include "OrientationObserver.h" +#include "GonkMemoryPressureMonitoring.h" + +#include "android/log.h" +#include "libui/EventHub.h" +#include "libui/InputReader.h" +#include "libui/InputDispatcher.h" + +#include "mozilla/Preferences.h" +#include "GeckoProfiler.h" + +// Defines kKeyMapping and GetKeyNameIndex() +#include "GonkKeyMapping.h" +#include "mozilla/layers/CompositorBridgeParent.h" +#include "GeckoTouchDispatcher.h" + +#undef LOG +#define LOG(args...) \ + __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) +#ifdef VERBOSE_LOG_ENABLED +# define VERBOSE_LOG(args...) \ + __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) +#else +# define VERBOSE_LOG(args...) \ + (void)0 +#endif + +using namespace android; +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::services; +using namespace mozilla::widget; + +bool gDrawRequest = false; +static nsAppShell *gAppShell = nullptr; +static int epollfd = 0; +static int signalfds[2] = {0}; +static bool sDevInputAudioJack; +static int32_t sHeadphoneState; +static int32_t sMicrophoneState; + +// Amount of time in MS before an input is considered expired. +static const uint64_t kInputExpirationThresholdMs = 1000; +static const char kKey_WAKE_LOCK_ID[] = "GeckoKeyEvent"; + +NS_IMPL_ISUPPORTS_INHERITED(nsAppShell, nsBaseAppShell, nsIObserver) + +static uint64_t +nanosecsToMillisecs(nsecs_t nsecs) +{ + return nsecs / 1000000; +} + +namespace mozilla { + +bool ProcessNextEvent() +{ + return gAppShell->ProcessNextNativeEvent(true); +} + +void NotifyEvent() +{ + gAppShell->NotifyNativeEvent(); +} + +} // namespace mozilla + +static void +pipeHandler(int fd, FdHandler *data) +{ + ssize_t len; + do { + char tmp[32]; + len = read(fd, tmp, sizeof(tmp)); + } while (len > 0); +} + +struct Touch { + int32_t id; + PointerCoords coords; +}; + +struct UserInputData { + uint64_t timeMs; + enum { + MOTION_DATA, + KEY_DATA + } type; + int32_t action; + int32_t flags; + int32_t metaState; + int32_t deviceId; + union { + struct { + int32_t keyCode; + int32_t scanCode; + } key; + struct { + int32_t touchCount; + ::Touch touches[MAX_POINTERS]; + } motion; + }; +}; + +static mozilla::Modifiers +getDOMModifiers(int32_t metaState) +{ + mozilla::Modifiers result = 0; + if (metaState & (AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { + result |= MODIFIER_ALT; + } + if (metaState & (AMETA_SHIFT_ON | + AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { + result |= MODIFIER_SHIFT; + } + if (metaState & AMETA_FUNCTION_ON) { + result |= MODIFIER_FN; + } + if (metaState & (AMETA_CTRL_ON | + AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { + result |= MODIFIER_CONTROL; + } + if (metaState & (AMETA_META_ON | + AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) { + result |= MODIFIER_META; + } + if (metaState & AMETA_CAPS_LOCK_ON) { + result |= MODIFIER_CAPSLOCK; + } + if (metaState & AMETA_NUM_LOCK_ON) { + result |= MODIFIER_NUMLOCK; + } + if (metaState & AMETA_SCROLL_LOCK_ON) { + result |= MODIFIER_SCROLLLOCK; + } + return result; +} + +class MOZ_STACK_CLASS KeyEventDispatcher +{ +public: + KeyEventDispatcher(const UserInputData& aData, + KeyCharacterMap* aKeyCharMap); + void Dispatch(); + +private: + const UserInputData& mData; + sp<KeyCharacterMap> mKeyCharMap; + + char16_t mChar; + char16_t mUnmodifiedChar; + + uint32_t mDOMKeyCode; + uint32_t mDOMKeyLocation; + KeyNameIndex mDOMKeyNameIndex; + CodeNameIndex mDOMCodeNameIndex; + char16_t mDOMPrintableKeyValue; + + bool IsKeyPress() const + { + return mData.action == AKEY_EVENT_ACTION_DOWN; + } + bool IsRepeat() const + { + return IsKeyPress() && (mData.flags & AKEY_EVENT_FLAG_LONG_PRESS); + } + + char16_t PrintableKeyValue() const; + + int32_t UnmodifiedMetaState() const + { + return mData.metaState & + ~(AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON | + AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON | + AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON); + } + + static bool IsControlChar(char16_t aChar) + { + return (aChar < ' ' || aChar == 0x7F); + } + + void DispatchKeyDownEvent(); + void DispatchKeyUpEvent(); + nsEventStatus DispatchKeyEventInternal(EventMessage aEventMessage); +}; + +KeyEventDispatcher::KeyEventDispatcher(const UserInputData& aData, + KeyCharacterMap* aKeyCharMap) + : mData(aData) + , mKeyCharMap(aKeyCharMap) + , mChar(0) + , mUnmodifiedChar(0) + , mDOMPrintableKeyValue(0) +{ + // XXX Printable key's keyCode value should be computed with actual + // input character. + mDOMKeyCode = (mData.key.keyCode < (ssize_t)ArrayLength(kKeyMapping)) ? + kKeyMapping[mData.key.keyCode] : 0; + mDOMKeyNameIndex = GetKeyNameIndex(mData.key.keyCode); + mDOMCodeNameIndex = GetCodeNameIndex(mData.key.scanCode); + mDOMKeyLocation = + WidgetKeyboardEvent::ComputeLocationFromCodeValue(mDOMCodeNameIndex); + + if (!mKeyCharMap.get()) { + return; + } + + mChar = mKeyCharMap->getCharacter(mData.key.keyCode, mData.metaState); + if (IsControlChar(mChar)) { + mChar = 0; + } + int32_t unmodifiedMetaState = UnmodifiedMetaState(); + if (mData.metaState == unmodifiedMetaState) { + mUnmodifiedChar = mChar; + } else { + mUnmodifiedChar = mKeyCharMap->getCharacter(mData.key.keyCode, + unmodifiedMetaState); + if (IsControlChar(mUnmodifiedChar)) { + mUnmodifiedChar = 0; + } + } + + mDOMPrintableKeyValue = PrintableKeyValue(); +} + +char16_t +KeyEventDispatcher::PrintableKeyValue() const +{ + if (mDOMKeyNameIndex != KEY_NAME_INDEX_USE_STRING) { + return 0; + } + return mChar ? mChar : mUnmodifiedChar; +} + +nsEventStatus +KeyEventDispatcher::DispatchKeyEventInternal(EventMessage aEventMessage) +{ + WidgetKeyboardEvent event(true, aEventMessage, nullptr); + if (aEventMessage == eKeyPress) { + // XXX If the charCode is not a printable character, the charCode + // should be computed without Ctrl/Alt/Meta modifiers. + event.mCharCode = static_cast<uint32_t>(mChar); + } + if (!event.mCharCode) { + event.mKeyCode = mDOMKeyCode; + } + event.mIsChar = !!event.mCharCode; + event.mIsRepeat = IsRepeat(); + event.mKeyNameIndex = mDOMKeyNameIndex; + if (mDOMPrintableKeyValue) { + event.mKeyValue = mDOMPrintableKeyValue; + } + event.mCodeNameIndex = mDOMCodeNameIndex; + event.mModifiers = getDOMModifiers(mData.metaState); + event.mLocation = mDOMKeyLocation; + event.mTime = mData.timeMs; + return nsWindow::DispatchKeyInput(event); +} + +void +KeyEventDispatcher::Dispatch() +{ + // XXX Even if unknown key is pressed, DOM key event should be + // dispatched since Gecko for the other platforms are implemented + // as so. + if (!mDOMKeyCode && mDOMKeyNameIndex == KEY_NAME_INDEX_Unidentified) { + VERBOSE_LOG("Got unknown key event code. " + "type 0x%04x code 0x%04x value %d", + mData.action, mData.key.keyCode, IsKeyPress()); + return; + } + + if (IsKeyPress()) { + DispatchKeyDownEvent(); + } else { + DispatchKeyUpEvent(); + } +} + +void +KeyEventDispatcher::DispatchKeyDownEvent() +{ + nsEventStatus status = DispatchKeyEventInternal(eKeyDown); + if (status != nsEventStatus_eConsumeNoDefault) { + DispatchKeyEventInternal(eKeyPress); + } +} + +void +KeyEventDispatcher::DispatchKeyUpEvent() +{ + DispatchKeyEventInternal(eKeyUp); +} + +class SwitchEventRunnable : public mozilla::Runnable { +public: + SwitchEventRunnable(hal::SwitchEvent& aEvent) : mEvent(aEvent) + {} + + NS_IMETHOD Run() override + { + hal::NotifySwitchStateFromInputDevice(mEvent.device(), + mEvent.status()); + return NS_OK; + } +private: + hal::SwitchEvent mEvent; +}; + +static void +updateHeadphoneSwitch() +{ + hal::SwitchEvent event; + + switch (sHeadphoneState) { + case AKEY_STATE_UP: + event.status() = hal::SWITCH_STATE_OFF; + break; + case AKEY_STATE_DOWN: + event.status() = sMicrophoneState == AKEY_STATE_DOWN ? + hal::SWITCH_STATE_HEADSET : hal::SWITCH_STATE_HEADPHONE; + break; + default: + return; + } + + event.device() = hal::SWITCH_HEADPHONES; + NS_DispatchToMainThread(new SwitchEventRunnable(event)); +} + +class GeckoPointerController : public PointerControllerInterface { + float mX; + float mY; + int32_t mButtonState; + InputReaderConfiguration* mConfig; +public: + GeckoPointerController(InputReaderConfiguration* config) + : mX(0) + , mY(0) + , mButtonState(0) + , mConfig(config) + {} + + virtual bool getBounds(float* outMinX, float* outMinY, + float* outMaxX, float* outMaxY) const; + virtual void move(float deltaX, float deltaY); + virtual void setButtonState(int32_t buttonState); + virtual int32_t getButtonState() const; + virtual void setPosition(float x, float y); + virtual void getPosition(float* outX, float* outY) const; + virtual void fade(Transition transition) {} + virtual void unfade(Transition transition) {} + virtual void setPresentation(Presentation presentation) {} + virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, + BitSet32 spotIdBits) {} + virtual void clearSpots() {} +}; + +bool +GeckoPointerController::getBounds(float* outMinX, + float* outMinY, + float* outMaxX, + float* outMaxY) const +{ + DisplayViewport viewport; + + mConfig->getDisplayInfo(false, &viewport); + + *outMinX = *outMinY = 0; + *outMaxX = viewport.logicalRight; + *outMaxY = viewport.logicalBottom; + return true; +} + +void +GeckoPointerController::move(float deltaX, float deltaY) +{ + float minX, minY, maxX, maxY; + getBounds(&minX, &minY, &maxX, &maxY); + + mX = clamped(mX + deltaX, minX, maxX); + mY = clamped(mY + deltaY, minY, maxY); +} + +void +GeckoPointerController::setButtonState(int32_t buttonState) +{ + mButtonState = buttonState; +} + +int32_t +GeckoPointerController::getButtonState() const +{ + return mButtonState; +} + +void +GeckoPointerController::setPosition(float x, float y) +{ + mX = x; + mY = y; +} + +void +GeckoPointerController::getPosition(float* outX, float* outY) const +{ + *outX = mX; + *outY = mY; +} + +class GeckoInputReaderPolicy : public InputReaderPolicyInterface { + InputReaderConfiguration mConfig; +public: + GeckoInputReaderPolicy() {} + + virtual void getReaderConfiguration(InputReaderConfiguration* outConfig); + virtual sp<PointerControllerInterface> obtainPointerController(int32_t +deviceId) + { + return new GeckoPointerController(&mConfig); + }; + virtual void notifyInputDevicesChanged(const android::Vector<InputDeviceInfo>& inputDevices) {}; + virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(const String8& inputDeviceDescriptor) + { + return nullptr; + }; + virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier) + { + return String8::empty(); + }; + + void setDisplayInfo(); + +protected: + virtual ~GeckoInputReaderPolicy() {} +}; + +class GeckoInputDispatcher : public InputDispatcherInterface { +public: + GeckoInputDispatcher(sp<EventHub> &aEventHub) + : mQueueLock("GeckoInputDispatcher::mQueueMutex") + , mEventHub(aEventHub) + , mKeyDownCount(0) + , mKeyEventsFiltered(false) + , mPowerWakelock(false) + { + mTouchDispatcher = GeckoTouchDispatcher::GetInstance(); + } + + virtual void dump(String8& dump); + + virtual void monitor() {} + + // Called on the main thread + virtual void dispatchOnce(); + + // notify* methods are called on the InputReaderThread + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args); + virtual void notifyKey(const NotifyKeyArgs* args); + virtual void notifyMotion(const NotifyMotionArgs* args); + virtual void notifySwitch(const NotifySwitchArgs* args); + virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args); + + virtual int32_t injectInputEvent(const InputEvent* event, + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags); + + virtual void setInputWindows(const android::Vector<sp<InputWindowHandle> >& inputWindowHandles); + virtual void setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle); + + virtual void setInputDispatchMode(bool enabled, bool frozen); + virtual void setInputFilterEnabled(bool enabled) {} + virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, + const sp<InputChannel>& toChannel) { return true; } + + virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, + const sp<InputWindowHandle>& inputWindowHandle, bool monitor); + virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); + + + +protected: + virtual ~GeckoInputDispatcher() { } + +private: + // mQueueLock should generally be locked while using mEventQueue. + // UserInputData is pushed on on the InputReaderThread and + // popped and dispatched on the main thread. + mozilla::Mutex mQueueLock; + std::queue<UserInputData> mEventQueue; + sp<EventHub> mEventHub; + RefPtr<GeckoTouchDispatcher> mTouchDispatcher; + + int mKeyDownCount; + bool mKeyEventsFiltered; + bool mPowerWakelock; +}; + +// GeckoInputReaderPolicy +void +GeckoInputReaderPolicy::setDisplayInfo() +{ + static_assert(static_cast<int>(nsIScreen::ROTATION_0_DEG) == + static_cast<int>(DISPLAY_ORIENTATION_0), + "Orientation enums not matched!"); + static_assert(static_cast<int>(nsIScreen::ROTATION_90_DEG) == + static_cast<int>(DISPLAY_ORIENTATION_90), + "Orientation enums not matched!"); + static_assert(static_cast<int>(nsIScreen::ROTATION_180_DEG) == + static_cast<int>(DISPLAY_ORIENTATION_180), + "Orientation enums not matched!"); + static_assert(static_cast<int>(nsIScreen::ROTATION_270_DEG) == + static_cast<int>(DISPLAY_ORIENTATION_270), + "Orientation enums not matched!"); + + RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen(); + + uint32_t rotation = nsIScreen::ROTATION_0_DEG; + DebugOnly<nsresult> rv = screen->GetRotation(&rotation); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + LayoutDeviceIntRect screenBounds = screen->GetNaturalBounds(); + + DisplayViewport viewport; + viewport.displayId = 0; + viewport.orientation = rotation; + viewport.physicalRight = viewport.deviceWidth = screenBounds.width; + viewport.physicalBottom = viewport.deviceHeight = screenBounds.height; + if (viewport.orientation == DISPLAY_ORIENTATION_90 || + viewport.orientation == DISPLAY_ORIENTATION_270) { + viewport.logicalRight = screenBounds.height; + viewport.logicalBottom = screenBounds.width; + } else { + viewport.logicalRight = screenBounds.width; + viewport.logicalBottom = screenBounds.height; + } + mConfig.setDisplayInfo(false, viewport); +} + +void GeckoInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) +{ + *outConfig = mConfig; +} + + +// GeckoInputDispatcher +void +GeckoInputDispatcher::dump(String8& dump) +{ +} + +static bool +isExpired(const UserInputData& data) +{ + uint64_t timeNowMs = + nanosecsToMillisecs(systemTime(SYSTEM_TIME_MONOTONIC)); + return (timeNowMs - data.timeMs) > kInputExpirationThresholdMs; +} + +void +GeckoInputDispatcher::dispatchOnce() +{ + UserInputData data; + { + MutexAutoLock lock(mQueueLock); + if (mEventQueue.empty()) + return; + data = mEventQueue.front(); + mEventQueue.pop(); + if (!mEventQueue.empty()) + gAppShell->NotifyNativeEvent(); + } + + switch (data.type) { + case UserInputData::MOTION_DATA: { + MOZ_ASSERT_UNREACHABLE("Should not dispatch touch events here anymore"); + break; + } + case UserInputData::KEY_DATA: { + if (!mKeyDownCount) { + // No pending events, the filter state can be updated. + mKeyEventsFiltered = isExpired(data); + } + + mKeyDownCount += (data.action == AKEY_EVENT_ACTION_DOWN) ? 1 : -1; + if (mKeyEventsFiltered) { + return; + } + + sp<KeyCharacterMap> kcm = mEventHub->getKeyCharacterMap(data.deviceId); + KeyEventDispatcher dispatcher(data, kcm.get()); + dispatcher.Dispatch(); + break; + } + } + MutexAutoLock lock(mQueueLock); + if (mPowerWakelock && mEventQueue.empty()) { + release_wake_lock(kKey_WAKE_LOCK_ID); + mPowerWakelock = false; + } +} + +void +GeckoInputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs*) +{ + gAppShell->CheckPowerKey(); +} + +void +GeckoInputDispatcher::notifyKey(const NotifyKeyArgs* args) +{ + UserInputData data; + data.timeMs = nanosecsToMillisecs(args->eventTime); + data.type = UserInputData::KEY_DATA; + data.action = args->action; + data.flags = args->flags; + data.metaState = args->metaState; + data.deviceId = args->deviceId; + data.key.keyCode = args->keyCode; + data.key.scanCode = args->scanCode; + { + MutexAutoLock lock(mQueueLock); + mEventQueue.push(data); + if (!mPowerWakelock) { + mPowerWakelock = + acquire_wake_lock(PARTIAL_WAKE_LOCK, kKey_WAKE_LOCK_ID); + } + } + gAppShell->NotifyNativeEvent(); +} + +static void +addMultiTouch(MultiTouchInput& aMultiTouch, + const NotifyMotionArgs* args, int aIndex) +{ + int32_t id = args->pointerProperties[aIndex].id; + PointerCoords coords = args->pointerCoords[aIndex]; + float force = coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE); + + float orientation = coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION); + float rotationAngle = orientation * 180 / M_PI; + if (rotationAngle == 90) { + rotationAngle = -90; + } + + float radiusX, radiusY; + if (rotationAngle < 0) { + radiusX = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR) / 2; + radiusY = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR) / 2; + rotationAngle += 90; + } else { + radiusX = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR) / 2; + radiusY = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR) / 2; + } + + ScreenIntPoint point = ScreenIntPoint::Round(coords.getX(), + coords.getY()); + + SingleTouchData touchData(id, point, ScreenSize(radiusX, radiusY), + rotationAngle, force); + + aMultiTouch.mTouches.AppendElement(touchData); +} + +void +GeckoInputDispatcher::notifyMotion(const NotifyMotionArgs* args) +{ + uint32_t time = nanosecsToMillisecs(args->eventTime); + int32_t action = args->action & AMOTION_EVENT_ACTION_MASK; + int touchCount = args->pointerCount; + MOZ_ASSERT(touchCount <= MAX_POINTERS); + TimeStamp timestamp = mozilla::TimeStamp::FromSystemTime(args->eventTime); + Modifiers modifiers = getDOMModifiers(args->metaState); + + MultiTouchInput::MultiTouchType touchType = MultiTouchInput::MULTITOUCH_CANCEL; + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + touchType = MultiTouchInput::MULTITOUCH_START; + break; + case AMOTION_EVENT_ACTION_MOVE: + touchType = MultiTouchInput::MULTITOUCH_MOVE; + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + touchType = MultiTouchInput::MULTITOUCH_END; + break; + case AMOTION_EVENT_ACTION_OUTSIDE: + case AMOTION_EVENT_ACTION_CANCEL: + touchType = MultiTouchInput::MULTITOUCH_CANCEL; + break; + case AMOTION_EVENT_ACTION_HOVER_EXIT: + case AMOTION_EVENT_ACTION_HOVER_ENTER: + case AMOTION_EVENT_ACTION_HOVER_MOVE: + NS_WARNING("Ignoring hover touch events"); + return; + default: + MOZ_ASSERT_UNREACHABLE("Could not assign a touch type"); + break; + } + + MultiTouchInput touchData(touchType, time, timestamp, modifiers); + + // For touch ends, we have to filter out which finger is actually + // the touch end since the touch array has all fingers, not just the touch + // that we want to end + if (touchType == MultiTouchInput::MULTITOUCH_END) { + int touchIndex = args->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK; + touchIndex >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + addMultiTouch(touchData, args, touchIndex); + } else { + for (int32_t i = 0; i < touchCount; ++i) { + addMultiTouch(touchData, args, i); + } + } + + mTouchDispatcher->NotifyTouch(touchData, timestamp); +} + +void GeckoInputDispatcher::notifySwitch(const NotifySwitchArgs* args) +{ + if (!sDevInputAudioJack) + return; + + bool needSwitchUpdate = false; + + if (args->switchMask & (1 << SW_HEADPHONE_INSERT)) { + sHeadphoneState = (args->switchValues & (1 << SW_HEADPHONE_INSERT)) ? + AKEY_STATE_DOWN : AKEY_STATE_UP; + needSwitchUpdate = true; + } + + if (args->switchMask & (1 << SW_MICROPHONE_INSERT)) { + sMicrophoneState = (args->switchValues & (1 << SW_MICROPHONE_INSERT)) ? + AKEY_STATE_DOWN : AKEY_STATE_UP; + needSwitchUpdate = true; + } + + if (needSwitchUpdate) + updateHeadphoneSwitch(); +} + +void GeckoInputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) +{ +} + +int32_t GeckoInputDispatcher::injectInputEvent( + const InputEvent* event, + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, + int32_t timeoutMillis, uint32_t policyFlags) +{ + return INPUT_EVENT_INJECTION_SUCCEEDED; +} + +void +GeckoInputDispatcher::setInputWindows(const android::Vector<sp<InputWindowHandle> >& inputWindowHandles) +{ +} + +void +GeckoInputDispatcher::setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle) +{ +} + +void +GeckoInputDispatcher::setInputDispatchMode(bool enabled, bool frozen) +{ +} + +status_t +GeckoInputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, + const sp<InputWindowHandle>& inputWindowHandle, bool monitor) +{ + return OK; +} + +status_t +GeckoInputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) +{ + return OK; +} + +nsAppShell::nsAppShell() + : mNativeCallbackRequest(false) + , mEnableDraw(false) + , mHandlers() + , mPowerKeyChecked(false) +{ + gAppShell = this; + if (XRE_IsParentProcess()) { + Preferences::SetCString("b2g.safe_mode", "unset"); + } +} + +nsAppShell::~nsAppShell() +{ + // mReaderThread and mEventHub will both be null if InitInputDevices + // is not called. + if (mReaderThread.get()) { + // We separate requestExit() and join() here so we can wake the EventHub's + // input loop, and stop it from polling for input events + mReaderThread->requestExit(); + mEventHub->wake(); + + status_t result = mReaderThread->requestExitAndWait(); + if (result) + LOG("Could not stop reader thread - %d", result); + } + gAppShell = nullptr; +} + +nsresult +nsAppShell::Init() +{ + nsresult rv = nsBaseAppShell::Init(); + NS_ENSURE_SUCCESS(rv, rv); + + epollfd = epoll_create(16); + NS_ENSURE_TRUE(epollfd >= 0, NS_ERROR_UNEXPECTED); + + int ret = pipe2(signalfds, O_NONBLOCK); + NS_ENSURE_FALSE(ret, NS_ERROR_UNEXPECTED); + + rv = AddFdHandler(signalfds[0], pipeHandler, ""); + NS_ENSURE_SUCCESS(rv, rv); + + InitGonkMemoryPressureMonitoring(); + + if (XRE_IsParentProcess()) { + printf("*****************************************************************\n"); + printf("***\n"); + printf("*** This is stdout. Most of the useful output will be in logcat.\n"); + printf("***\n"); + printf("*****************************************************************\n"); + GonkPermissionService::instantiate(); + + // Causes the kernel timezone to be set, which in turn causes the + // timestamps on SD cards to have the local time rather than UTC time. + hal::SetTimezone(hal::GetTimezone()); + } + + nsCOMPtr<nsIObserverService> obsServ = GetObserverService(); + if (obsServ) { + obsServ->AddObserver(this, "browser-ui-startup-complete", false); + obsServ->AddObserver(this, "network-connection-state-changed", false); + } + + // Delay initializing input devices until the screen has been + // initialized (and we know the resolution). + return rv; +} + +void +nsAppShell::CheckPowerKey() +{ + if (mPowerKeyChecked) { + return; + } + + uint32_t deviceId = 0; + int32_t powerState = AKEY_STATE_UNKNOWN; + + // EventHub doesn't report the number of devices. + while (powerState != AKEY_STATE_DOWN && deviceId < 32) { + powerState = mEventHub->getKeyCodeState(deviceId++, AKEYCODE_POWER); + } + + // If Power is pressed while we startup, mark safe mode. + // Consumers of the b2g.safe_mode preference need to listen on this + // preference change to prevent startup races. + nsCOMPtr<nsIRunnable> prefSetter = + NS_NewRunnableFunction([powerState] () -> void { + Preferences::SetCString("b2g.safe_mode", + (powerState == AKEY_STATE_DOWN) ? "yes" : "no"); + }); + NS_DispatchToMainThread(prefSetter.forget()); + + mPowerKeyChecked = true; +} + +NS_IMETHODIMP +nsAppShell::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + if (!strcmp(aTopic, "network-connection-state-changed")) { + NS_ConvertUTF16toUTF8 type(aData); + if (!type.IsEmpty()) { + hal::NotifyNetworkChange(hal::NetworkInformation(atoi(type.get()), 0, 0)); + } + return NS_OK; + } else if (!strcmp(aTopic, "browser-ui-startup-complete")) { + if (sDevInputAudioJack) { + sHeadphoneState = mReader->getSwitchState(-1, AINPUT_SOURCE_SWITCH, SW_HEADPHONE_INSERT); + sMicrophoneState = mReader->getSwitchState(-1, AINPUT_SOURCE_SWITCH, SW_MICROPHONE_INSERT); + updateHeadphoneSwitch(); + } + mEnableDraw = true; + + // System is almost booting up. Stop the bootAnim now. + StopBootAnimation(); + + NotifyEvent(); + return NS_OK; + } + + return nsBaseAppShell::Observe(aSubject, aTopic, aData); +} + +NS_IMETHODIMP +nsAppShell::Exit() +{ + OrientationObserver::ShutDown(); + nsCOMPtr<nsIObserverService> obsServ = GetObserverService(); + if (obsServ) { + obsServ->RemoveObserver(this, "browser-ui-startup-complete"); + obsServ->RemoveObserver(this, "network-connection-state-changed"); + } + return nsBaseAppShell::Exit(); +} + +void +nsAppShell::InitInputDevices() +{ + sDevInputAudioJack = hal::IsHeadphoneEventFromInputDev(); + sHeadphoneState = AKEY_STATE_UNKNOWN; + sMicrophoneState = AKEY_STATE_UNKNOWN; + + mEventHub = new EventHub(); + mReaderPolicy = new GeckoInputReaderPolicy(); + mReaderPolicy->setDisplayInfo(); + mDispatcher = new GeckoInputDispatcher(mEventHub); + + mReader = new InputReader(mEventHub, mReaderPolicy, mDispatcher); + mReaderThread = new InputReaderThread(mReader); + + status_t result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); + if (result) { + LOG("Failed to initialize InputReader thread, bad things are going to happen..."); + } +} + +nsresult +nsAppShell::AddFdHandler(int fd, FdHandlerCallback handlerFunc, + const char* deviceName) +{ + epoll_event event = { + EPOLLIN, + { 0 } + }; + + FdHandler *handler = mHandlers.AppendElement(); + handler->fd = fd; + strncpy(handler->name, deviceName, sizeof(handler->name) - 1); + handler->func = handlerFunc; + event.data.u32 = mHandlers.Length() - 1; + return epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) ? + NS_ERROR_UNEXPECTED : NS_OK; +} + +void +nsAppShell::ScheduleNativeEventCallback() +{ + mNativeCallbackRequest = true; + NotifyEvent(); +} + +bool +nsAppShell::ProcessNextNativeEvent(bool mayWait) +{ + PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent", + js::ProfileEntry::Category::EVENTS); + + epoll_event events[16] = {{ 0 }}; + + int event_count; + { + PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent::Wait", + js::ProfileEntry::Category::EVENTS); + + if ((event_count = epoll_wait(epollfd, events, 16, mayWait ? -1 : 0)) <= 0) + return true; + } + + for (int i = 0; i < event_count; i++) + mHandlers[events[i].data.u32].run(); + + if (mDispatcher.get()) + mDispatcher->dispatchOnce(); + + // NativeEventCallback always schedules more if it needs it + // so we can coalesce these. + // See the implementation in nsBaseAppShell.cpp for more info + if (mNativeCallbackRequest) { + mNativeCallbackRequest = false; + NativeEventCallback(); + } + + if (gDrawRequest && mEnableDraw) { + gDrawRequest = false; + nsWindow::DoDraw(); + } + + return true; +} + +void +nsAppShell::NotifyNativeEvent() +{ + write(signalfds[1], "w", 1); +} + +/* static */ void +nsAppShell::NotifyScreenInitialized() +{ + gAppShell->InitInputDevices(); + + // Getting the instance of OrientationObserver to initialize it. + OrientationObserver::GetInstance(); +} + +/* static */ void +nsAppShell::NotifyScreenRotation() +{ + gAppShell->mReaderPolicy->setDisplayInfo(); + gAppShell->mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO); + + RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen(); + hal::NotifyScreenConfigurationChange(screen->GetConfiguration()); +} |