/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #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" #ifdef MOZ_NUWA_PROCESS #include "ipc/Nuwa.h" #endif #include "mozilla/Preferences.h" #include "GeckoProfiler.h" // Defines kKeyMapping and GetKeyNameIndex() #include "GonkKeyMapping.h" #include "mozilla/layers/CompositorParent.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 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.charCode = static_cast(mChar); } if (!event.charCode) { event.keyCode = mDOMKeyCode; } event.isChar = !!event.charCode; event.mIsRepeat = IsRepeat(); event.mKeyNameIndex = mDOMKeyNameIndex; if (mDOMPrintableKeyValue) { event.mKeyValue = mDOMPrintableKeyValue; } event.mCodeNameIndex = mDOMCodeNameIndex; event.modifiers = getDOMModifiers(mData.metaState); event.location = mDOMKeyLocation; event.time = 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 nsRunnable { public: SwitchEventRunnable(hal::SwitchEvent& aEvent) : mEvent(aEvent) {} NS_IMETHOD Run() { 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 obtainPointerController(int32_t deviceId) { return new GeckoPointerController(&mConfig); }; virtual void notifyInputDevicesChanged(const android::Vector& inputDevices) {}; virtual sp 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 &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 >& inputWindowHandles); virtual void setFocusedApplication(const sp& inputApplicationHandle); virtual void setInputDispatchMode(bool enabled, bool frozen); virtual void setInputFilterEnabled(bool enabled) {} virtual bool transferTouchFocus(const sp& fromChannel, const sp& toChannel) { return true; } virtual status_t registerInputChannel(const sp& inputChannel, const sp& inputWindowHandle, bool monitor); virtual status_t unregisterInputChannel(const sp& 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 mEventQueue; sp mEventHub; RefPtr mTouchDispatcher; int mKeyDownCount; bool mKeyEventsFiltered; bool mPowerWakelock; }; // GeckoInputReaderPolicy void GeckoInputReaderPolicy::setDisplayInfo() { static_assert(static_cast(nsIScreen::ROTATION_0_DEG) == static_cast(DISPLAY_ORIENTATION_0), "Orientation enums not matched!"); static_assert(static_cast(nsIScreen::ROTATION_90_DEG) == static_cast(DISPLAY_ORIENTATION_90), "Orientation enums not matched!"); static_assert(static_cast(nsIScreen::ROTATION_180_DEG) == static_cast(DISPLAY_ORIENTATION_180), "Orientation enums not matched!"); static_assert(static_cast(nsIScreen::ROTATION_270_DEG) == static_cast(DISPLAY_ORIENTATION_270), "Orientation enums not matched!"); RefPtr screen = nsScreenManagerGonk::GetPrimaryScreen(); uint32_t rotation = nsIScreen::ROTATION_0_DEG; DebugOnly 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 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(floor(coords.getX() + 0.5), floor(coords.getY() + 0.5)); 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 >& inputWindowHandles) { } void GeckoInputDispatcher::setFocusedApplication(const sp& inputApplicationHandle) { } void GeckoInputDispatcher::setInputDispatchMode(bool enabled, bool frozen) { } status_t GeckoInputDispatcher::registerInputChannel(const sp& inputChannel, const sp& inputWindowHandle, bool monitor) { return OK; } status_t GeckoInputDispatcher::unregisterInputChannel(const sp& 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"); #if ANDROID_VERSION >= 18 && (defined(MOZ_OMX_DECODER) || defined(MOZ_B2G_CAMERA)) android::FakeSurfaceComposer::instantiate(); #endif 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 obsServ = GetObserverService(); if (obsServ) { obsServ->AddObserver(this, "browser-ui-startup-complete", false); obsServ->AddObserver(this, "network-connection-state-changed", false); } #ifdef MOZ_NUWA_PROCESS // Make sure main thread was woken up after Nuwa fork. NuwaAddConstructor((void (*)(void *))&NotifyEvent, nullptr); #endif // 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 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 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 screen = nsScreenManagerGonk::GetPrimaryScreen(); hal::NotifyScreenConfigurationChange(screen->GetConfiguration()); }