diff options
Diffstat (limited to 'hal/gonk/GonkSwitch.cpp')
-rw-r--r-- | hal/gonk/GonkSwitch.cpp | 479 |
1 files changed, 479 insertions, 0 deletions
diff --git a/hal/gonk/GonkSwitch.cpp b/hal/gonk/GonkSwitch.cpp new file mode 100644 index 000000000..b2c31c973 --- /dev/null +++ b/hal/gonk/GonkSwitch.cpp @@ -0,0 +1,479 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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. + */ + +#include <fcntl.h> +#include <sysutils/NetlinkEvent.h> + +#include "base/message_loop.h" +#include "base/task.h" + +#include "Hal.h" +#include "HalLog.h" +#include "mozilla/FileUtils.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Monitor.h" +#include "nsPrintfCString.h" +#include "nsXULAppAPI.h" +#include "nsThreadUtils.h" +#include "UeventPoller.h" + +using namespace mozilla::hal; + +#define SWITCH_HEADSET_DEVPATH "/devices/virtual/switch/h2w" +#define SWITCH_USB_DEVPATH_GB "/devices/virtual/switch/usb_configuration" +#define SWITCH_USB_DEVPATH_ICS "/devices/virtual/android_usb/android0" + +namespace mozilla { +namespace hal_impl { +/** + * The uevent for a usb on GB insertion looks like: + * + * change@/devices/virtual/switch/usb_configuration + * ACTION=change + * DEVPATH=/devices/virtual/switch/usb_configuration + * SUBSYSTEM=switch + * SWITCH_NAME=usb_configuration + * SWITCH_STATE=0 + * SEQNUM=5038 + */ +class SwitchHandler +{ +public: + NS_INLINE_DECL_REFCOUNTING(SwitchHandler) + + SwitchHandler(const char* aDevPath, SwitchDevice aDevice) + : mDevPath(aDevPath), + mState(SWITCH_STATE_UNKNOWN), + mDevice(aDevice) + { + GetInitialState(); + } + + bool CheckEvent(NetlinkEvent* aEvent) + { + if (strcmp(GetSubsystem(), aEvent->getSubsystem()) || + strcmp(mDevPath, aEvent->findParam("DEVPATH"))) { + return false; + } + + mState = ConvertState(GetStateString(aEvent)); + return mState != SWITCH_STATE_UNKNOWN; + } + + SwitchState GetState() + { + return mState; + } + + SwitchDevice GetType() + { + return mDevice; + } +protected: + virtual ~SwitchHandler() + { + } + + virtual const char* GetSubsystem() + { + return "switch"; + } + + virtual const char* GetStateString(NetlinkEvent* aEvent) + { + return aEvent->findParam("SWITCH_STATE"); + } + + void GetInitialState() + { + nsPrintfCString statePath("/sys%s/state", mDevPath); + int fd = open(statePath.get(), O_RDONLY); + if (fd <= 0) { + return; + } + + ScopedClose autoClose(fd); + char state[16]; + ssize_t bytesRead = read(fd, state, sizeof(state)); + if (bytesRead < 0) { + HAL_ERR("Read data from %s fails", statePath.get()); + return; + } + + if (state[bytesRead - 1] == '\n') { + bytesRead--; + } + + state[bytesRead] = '\0'; + mState = ConvertState(state); + } + + virtual SwitchState ConvertState(const char* aState) + { + MOZ_ASSERT(aState); + return aState[0] == '0' ? SWITCH_STATE_OFF : SWITCH_STATE_ON; + } + + const char* mDevPath; + SwitchState mState; + SwitchDevice mDevice; +}; + +/** + * The uevent delivered for the USB configuration under ICS looks like, + * + * change@/devices/virtual/android_usb/android0 + * ACTION=change + * DEVPATH=/devices/virtual/android_usb/android0 + * SUBSYSTEM=android_usb + * USB_STATE=CONFIGURED + * SEQNUM=1802 + */ +class SwitchHandlerUsbIcs: public SwitchHandler +{ +public: + SwitchHandlerUsbIcs(const char* aDevPath) : SwitchHandler(aDevPath, SWITCH_USB) + { + SwitchHandler::GetInitialState(); + } + + virtual ~SwitchHandlerUsbIcs() { } + +protected: + virtual const char* GetSubsystem() + { + return "android_usb"; + } + + virtual const char* GetStateString(NetlinkEvent* aEvent) + { + return aEvent->findParam("USB_STATE"); + } + + SwitchState ConvertState(const char* aState) + { + MOZ_ASSERT(aState); + return strcmp(aState, "CONFIGURED") == 0 ? SWITCH_STATE_ON : SWITCH_STATE_OFF; + } +}; + +/** + * The uevent delivered for the headset under ICS looks like, + * + * change@/devices/virtual/switch/h2w + * ACTION=change + * DEVPATH=/devices/virtual/switch/h2w + * SUBSYSTEM=switch + * SWITCH_NAME=h2w + * SWITCH_STATE=2 // Headset with no mic + * SEQNUM=2581 + * On Otoro, SWITCH_NAME could be Headset/No Device when plug/unplug. + * change@/devices/virtual/switch/h2w + * ACTION=change + * DEVPATH=/devices/virtual/switch/h2w + * SUBSYSTEM=switch + * SWITCH_NAME=Headset + * SWITCH_STATE=1 // Headset with mic + * SEQNUM=1602 + */ +class SwitchHandlerHeadphone: public SwitchHandler +{ +public: + SwitchHandlerHeadphone(const char* aDevPath) : + SwitchHandler(aDevPath, SWITCH_HEADPHONES) + { + SwitchHandler::GetInitialState(); + } + + virtual ~SwitchHandlerHeadphone() { } + +protected: + SwitchState ConvertState(const char* aState) + { + MOZ_ASSERT(aState); + + return aState[0] == '0' ? SWITCH_STATE_OFF : + (aState[0] == '1' ? SWITCH_STATE_HEADSET : SWITCH_STATE_HEADPHONE); + } +}; + + +typedef nsTArray<RefPtr<SwitchHandler> > SwitchHandlerArray; + +class SwitchEventRunnable : public Runnable +{ +public: + SwitchEventRunnable(SwitchEvent& aEvent) : mEvent(aEvent) + { + } + + NS_IMETHOD Run() override + { + NotifySwitchChange(mEvent); + return NS_OK; + } +private: + SwitchEvent mEvent; +}; + +class SwitchEventObserver final : public IUeventObserver +{ + ~SwitchEventObserver() + { + mHandler.Clear(); + } + +public: + NS_INLINE_DECL_REFCOUNTING(SwitchEventObserver) + SwitchEventObserver() + : mEnableCount(0), + mHeadphonesFromInputDev(false) + { + Init(); + } + + int GetEnableCount() + { + return mEnableCount; + } + + void EnableSwitch(SwitchDevice aDevice) + { + mEventInfo[aDevice].mEnabled = true; + mEnableCount++; + } + + void DisableSwitch(SwitchDevice aDevice) + { + mEventInfo[aDevice].mEnabled = false; + mEnableCount--; + } + + void Notify(const NetlinkEvent& aEvent) + { + SwitchState currState; + + SwitchDevice device = GetEventInfo(aEvent, currState); + if (device == SWITCH_DEVICE_UNKNOWN) { + return; + } + + EventInfo& info = mEventInfo[device]; + if (currState == info.mEvent.status()) { + return; + } + + info.mEvent.status() = currState; + + if (info.mEnabled) { + NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); + } + } + + void Notify(SwitchDevice aDevice, SwitchState aState) + { + EventInfo& info = mEventInfo[aDevice]; + if (aState == info.mEvent.status()) { + return; + } + + info.mEvent.status() = aState; + + if (info.mEnabled) { + NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); + } + } + + SwitchState GetCurrentInformation(SwitchDevice aDevice) + { + return mEventInfo[aDevice].mEvent.status(); + } + + void NotifyAnEvent(SwitchDevice aDevice) + { + EventInfo& info = mEventInfo[aDevice]; + if (info.mEvent.status() != SWITCH_STATE_UNKNOWN) { + NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); + } + } + + bool GetHeadphonesFromInputDev() + { + return mHeadphonesFromInputDev; + } + +private: + class EventInfo + { + public: + EventInfo() : mEnabled(false) + { + mEvent.status() = SWITCH_STATE_UNKNOWN; + mEvent.device() = SWITCH_DEVICE_UNKNOWN; + } + SwitchEvent mEvent; + bool mEnabled; + }; + + EventInfo mEventInfo[NUM_SWITCH_DEVICE]; + size_t mEnableCount; + SwitchHandlerArray mHandler; + bool mHeadphonesFromInputDev; + + // This function might also get called on the main thread + // (from IsHeadphoneEventFromInputDev) + void Init() + { + RefPtr<SwitchHandlerHeadphone> switchHeadPhone = + new SwitchHandlerHeadphone(SWITCH_HEADSET_DEVPATH); + + // If the initial state is unknown, it means the headphone event is from input dev + mHeadphonesFromInputDev = switchHeadPhone->GetState() == SWITCH_STATE_UNKNOWN ? true : false; + + if (!mHeadphonesFromInputDev) { + mHandler.AppendElement(switchHeadPhone); + } else { + // If headphone status will be notified from input dev then initialize + // status to "off" and wait for event notification. + mEventInfo[SWITCH_HEADPHONES].mEvent.device() = SWITCH_HEADPHONES; + mEventInfo[SWITCH_HEADPHONES].mEvent.status() = SWITCH_STATE_OFF; + } + mHandler.AppendElement(new SwitchHandler(SWITCH_USB_DEVPATH_GB, SWITCH_USB)); + mHandler.AppendElement(new SwitchHandlerUsbIcs(SWITCH_USB_DEVPATH_ICS)); + + SwitchHandlerArray::index_type handlerIndex; + SwitchHandlerArray::size_type numHandlers = mHandler.Length(); + + for (handlerIndex = 0; handlerIndex < numHandlers; handlerIndex++) { + SwitchState state = mHandler[handlerIndex]->GetState(); + if (state == SWITCH_STATE_UNKNOWN) { + continue; + } + + SwitchDevice device = mHandler[handlerIndex]->GetType(); + mEventInfo[device].mEvent.device() = device; + mEventInfo[device].mEvent.status() = state; + } + } + + SwitchDevice GetEventInfo(const NetlinkEvent& aEvent, SwitchState& aState) + { + //working around the android code not being const-correct + NetlinkEvent *e = const_cast<NetlinkEvent*>(&aEvent); + + for (size_t i = 0; i < mHandler.Length(); i++) { + if (mHandler[i]->CheckEvent(e)) { + aState = mHandler[i]->GetState(); + return mHandler[i]->GetType(); + } + } + return SWITCH_DEVICE_UNKNOWN; + } +}; + +static RefPtr<SwitchEventObserver> sSwitchObserver; + +static void +InitializeResourceIfNeed() +{ + if (!sSwitchObserver) { + sSwitchObserver = new SwitchEventObserver(); + RegisterUeventListener(sSwitchObserver); + } +} + +static void +ReleaseResourceIfNeed() +{ + if (sSwitchObserver->GetEnableCount() == 0) { + UnregisterUeventListener(sSwitchObserver); + sSwitchObserver = nullptr; + } +} + +static void +EnableSwitchNotificationsIOThread(SwitchDevice aDevice, Monitor *aMonitor) +{ + InitializeResourceIfNeed(); + sSwitchObserver->EnableSwitch(aDevice); + { + MonitorAutoLock lock(*aMonitor); + lock.Notify(); + } + + // Notify the latest state if IO thread has the information. + if (sSwitchObserver->GetEnableCount() > 1) { + sSwitchObserver->NotifyAnEvent(aDevice); + } +} + +void +EnableSwitchNotifications(SwitchDevice aDevice) +{ + Monitor monitor("EnableSwitch.monitor"); + { + MonitorAutoLock lock(monitor); + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(EnableSwitchNotificationsIOThread, aDevice, &monitor)); + lock.Wait(); + } +} + +static void +DisableSwitchNotificationsIOThread(SwitchDevice aDevice) +{ + MOZ_ASSERT(sSwitchObserver->GetEnableCount()); + sSwitchObserver->DisableSwitch(aDevice); + ReleaseResourceIfNeed(); +} + +void +DisableSwitchNotifications(SwitchDevice aDevice) +{ + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(DisableSwitchNotificationsIOThread, aDevice)); +} + +SwitchState +GetCurrentSwitchState(SwitchDevice aDevice) +{ + MOZ_ASSERT(sSwitchObserver && sSwitchObserver->GetEnableCount()); + return sSwitchObserver->GetCurrentInformation(aDevice); +} + +static void +NotifySwitchStateIOThread(SwitchDevice aDevice, SwitchState aState) +{ + InitializeResourceIfNeed(); + sSwitchObserver->Notify(aDevice, aState); +} + +void NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState) +{ + XRE_GetIOMessageLoop()->PostTask( + NewRunnableFunction(NotifySwitchStateIOThread, aDevice, aState)); +} + +bool IsHeadphoneEventFromInputDev() +{ + // Instead of calling InitializeResourceIfNeed, create new SwitchEventObserver + // to prevent calling RegisterUeventListener in main thread. + RefPtr<SwitchEventObserver> switchObserver = new SwitchEventObserver(); + return switchObserver->GetHeadphonesFromInputDev(); +} + +} // hal_impl +} //mozilla |