michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* Copyright 2012 Mozilla Foundation and Mozilla contributors michael@0: * michael@0: * Licensed under the Apache License, Version 2.0 (the "License"); michael@0: * you may not use this file except in compliance with the License. michael@0: * You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "base/message_loop.h" michael@0: michael@0: #include "Hal.h" michael@0: #include "mozilla/FileUtils.h" michael@0: #include "mozilla/RefPtr.h" michael@0: #include "mozilla/Monitor.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "UeventPoller.h" michael@0: michael@0: using namespace mozilla::hal; michael@0: michael@0: #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkSwitch" , ## args) michael@0: michael@0: #define SWITCH_HEADSET_DEVPATH "/devices/virtual/switch/h2w" michael@0: #define SWITCH_USB_DEVPATH_GB "/devices/virtual/switch/usb_configuration" michael@0: #define SWITCH_USB_DEVPATH_ICS "/devices/virtual/android_usb/android0" michael@0: michael@0: namespace mozilla { michael@0: namespace hal_impl { michael@0: /** michael@0: * The uevent for a usb on GB insertion looks like: michael@0: * michael@0: * change@/devices/virtual/switch/usb_configuration michael@0: * ACTION=change michael@0: * DEVPATH=/devices/virtual/switch/usb_configuration michael@0: * SUBSYSTEM=switch michael@0: * SWITCH_NAME=usb_configuration michael@0: * SWITCH_STATE=0 michael@0: * SEQNUM=5038 michael@0: */ michael@0: class SwitchHandler michael@0: { michael@0: public: michael@0: NS_INLINE_DECL_REFCOUNTING(SwitchHandler) michael@0: michael@0: SwitchHandler(const char* aDevPath, SwitchDevice aDevice) michael@0: : mDevPath(aDevPath), michael@0: mState(SWITCH_STATE_UNKNOWN), michael@0: mDevice(aDevice) michael@0: { michael@0: GetInitialState(); michael@0: } michael@0: michael@0: virtual ~SwitchHandler() michael@0: { michael@0: } michael@0: michael@0: bool CheckEvent(NetlinkEvent* aEvent) michael@0: { michael@0: if (strcmp(GetSubsystem(), aEvent->getSubsystem()) || michael@0: strcmp(mDevPath, aEvent->findParam("DEVPATH"))) { michael@0: return false; michael@0: } michael@0: michael@0: mState = ConvertState(GetStateString(aEvent)); michael@0: return mState != SWITCH_STATE_UNKNOWN; michael@0: } michael@0: michael@0: SwitchState GetState() michael@0: { michael@0: return mState; michael@0: } michael@0: michael@0: SwitchDevice GetType() michael@0: { michael@0: return mDevice; michael@0: } michael@0: protected: michael@0: virtual const char* GetSubsystem() michael@0: { michael@0: return "switch"; michael@0: } michael@0: michael@0: virtual const char* GetStateString(NetlinkEvent* aEvent) michael@0: { michael@0: return aEvent->findParam("SWITCH_STATE"); michael@0: } michael@0: michael@0: void GetInitialState() michael@0: { michael@0: nsPrintfCString statePath("/sys%s/state", mDevPath); michael@0: int fd = open(statePath.get(), O_RDONLY); michael@0: if (fd <= 0) { michael@0: return; michael@0: } michael@0: michael@0: ScopedClose autoClose(fd); michael@0: char state[16]; michael@0: ssize_t bytesRead = read(fd, state, sizeof(state)); michael@0: if (bytesRead < 0) { michael@0: LOG("Read data from %s fails", statePath.get()); michael@0: return; michael@0: } michael@0: michael@0: if (state[bytesRead - 1] == '\n') { michael@0: bytesRead--; michael@0: } michael@0: michael@0: state[bytesRead] = '\0'; michael@0: mState = ConvertState(state); michael@0: } michael@0: michael@0: virtual SwitchState ConvertState(const char* aState) michael@0: { michael@0: MOZ_ASSERT(aState); michael@0: return aState[0] == '0' ? SWITCH_STATE_OFF : SWITCH_STATE_ON; michael@0: } michael@0: michael@0: const char* mDevPath; michael@0: SwitchState mState; michael@0: SwitchDevice mDevice; michael@0: }; michael@0: michael@0: /** michael@0: * The uevent delivered for the USB configuration under ICS looks like, michael@0: * michael@0: * change@/devices/virtual/android_usb/android0 michael@0: * ACTION=change michael@0: * DEVPATH=/devices/virtual/android_usb/android0 michael@0: * SUBSYSTEM=android_usb michael@0: * USB_STATE=CONFIGURED michael@0: * SEQNUM=1802 michael@0: */ michael@0: class SwitchHandlerUsbIcs: public SwitchHandler michael@0: { michael@0: public: michael@0: SwitchHandlerUsbIcs(const char* aDevPath) : SwitchHandler(aDevPath, SWITCH_USB) michael@0: { michael@0: SwitchHandler::GetInitialState(); michael@0: } michael@0: michael@0: virtual ~SwitchHandlerUsbIcs() { } michael@0: michael@0: protected: michael@0: virtual const char* GetSubsystem() michael@0: { michael@0: return "android_usb"; michael@0: } michael@0: michael@0: virtual const char* GetStateString(NetlinkEvent* aEvent) michael@0: { michael@0: return aEvent->findParam("USB_STATE"); michael@0: } michael@0: michael@0: SwitchState ConvertState(const char* aState) michael@0: { michael@0: MOZ_ASSERT(aState); michael@0: return strcmp(aState, "CONFIGURED") == 0 ? SWITCH_STATE_ON : SWITCH_STATE_OFF; michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * The uevent delivered for the headset under ICS looks like, michael@0: * michael@0: * change@/devices/virtual/switch/h2w michael@0: * ACTION=change michael@0: * DEVPATH=/devices/virtual/switch/h2w michael@0: * SUBSYSTEM=switch michael@0: * SWITCH_NAME=h2w michael@0: * SWITCH_STATE=2 // Headset with no mic michael@0: * SEQNUM=2581 michael@0: * On Otoro, SWITCH_NAME could be Headset/No Device when plug/unplug. michael@0: * change@/devices/virtual/switch/h2w michael@0: * ACTION=change michael@0: * DEVPATH=/devices/virtual/switch/h2w michael@0: * SUBSYSTEM=switch michael@0: * SWITCH_NAME=Headset michael@0: * SWITCH_STATE=1 // Headset with mic michael@0: * SEQNUM=1602 michael@0: */ michael@0: class SwitchHandlerHeadphone: public SwitchHandler michael@0: { michael@0: public: michael@0: SwitchHandlerHeadphone(const char* aDevPath) : michael@0: SwitchHandler(aDevPath, SWITCH_HEADPHONES) michael@0: { michael@0: SwitchHandler::GetInitialState(); michael@0: } michael@0: michael@0: virtual ~SwitchHandlerHeadphone() { } michael@0: michael@0: protected: michael@0: SwitchState ConvertState(const char* aState) michael@0: { michael@0: MOZ_ASSERT(aState); michael@0: michael@0: return aState[0] == '0' ? SWITCH_STATE_OFF : michael@0: (aState[0] == '1' ? SWITCH_STATE_HEADSET : SWITCH_STATE_HEADPHONE); michael@0: } michael@0: }; michael@0: michael@0: michael@0: typedef nsTArray > SwitchHandlerArray; michael@0: michael@0: class SwitchEventRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: SwitchEventRunnable(SwitchEvent& aEvent) : mEvent(aEvent) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: NotifySwitchChange(mEvent); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: SwitchEvent mEvent; michael@0: }; michael@0: michael@0: class SwitchEventObserver MOZ_FINAL : public IUeventObserver michael@0: { michael@0: ~SwitchEventObserver() michael@0: { michael@0: mHandler.Clear(); michael@0: } michael@0: michael@0: public: michael@0: NS_INLINE_DECL_REFCOUNTING(SwitchEventObserver) michael@0: SwitchEventObserver() : mEnableCount(0) michael@0: { michael@0: Init(); michael@0: } michael@0: michael@0: int GetEnableCount() michael@0: { michael@0: return mEnableCount; michael@0: } michael@0: michael@0: void EnableSwitch(SwitchDevice aDevice) michael@0: { michael@0: mEventInfo[aDevice].mEnabled = true; michael@0: mEnableCount++; michael@0: } michael@0: michael@0: void DisableSwitch(SwitchDevice aDevice) michael@0: { michael@0: mEventInfo[aDevice].mEnabled = false; michael@0: mEnableCount--; michael@0: } michael@0: michael@0: void Notify(const NetlinkEvent& aEvent) michael@0: { michael@0: SwitchState currState; michael@0: michael@0: SwitchDevice device = GetEventInfo(aEvent, currState); michael@0: if (device == SWITCH_DEVICE_UNKNOWN) { michael@0: return; michael@0: } michael@0: michael@0: EventInfo& info = mEventInfo[device]; michael@0: if (currState == info.mEvent.status()) { michael@0: return; michael@0: } michael@0: michael@0: info.mEvent.status() = currState; michael@0: michael@0: if (info.mEnabled) { michael@0: NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); michael@0: } michael@0: } michael@0: michael@0: void Notify(SwitchDevice aDevice, SwitchState aState) michael@0: { michael@0: EventInfo& info = mEventInfo[aDevice]; michael@0: if (aState == info.mEvent.status()) { michael@0: return; michael@0: } michael@0: michael@0: info.mEvent.status() = aState; michael@0: michael@0: if (info.mEnabled) { michael@0: NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); michael@0: } michael@0: } michael@0: michael@0: SwitchState GetCurrentInformation(SwitchDevice aDevice) michael@0: { michael@0: return mEventInfo[aDevice].mEvent.status(); michael@0: } michael@0: michael@0: void NotifyAnEvent(SwitchDevice aDevice) michael@0: { michael@0: EventInfo& info = mEventInfo[aDevice]; michael@0: if (info.mEvent.status() != SWITCH_STATE_UNKNOWN) { michael@0: NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); michael@0: } michael@0: } michael@0: private: michael@0: class EventInfo michael@0: { michael@0: public: michael@0: EventInfo() : mEnabled(false) michael@0: { michael@0: mEvent.status() = SWITCH_STATE_UNKNOWN; michael@0: mEvent.device() = SWITCH_DEVICE_UNKNOWN; michael@0: } michael@0: SwitchEvent mEvent; michael@0: bool mEnabled; michael@0: }; michael@0: michael@0: EventInfo mEventInfo[NUM_SWITCH_DEVICE]; michael@0: size_t mEnableCount; michael@0: SwitchHandlerArray mHandler; michael@0: michael@0: void Init() michael@0: { michael@0: char value[PROPERTY_VALUE_MAX]; michael@0: property_get("ro.moz.devinputjack", value, "0"); michael@0: bool headphonesFromInputDev = !strcmp(value, "1"); michael@0: michael@0: if (!headphonesFromInputDev) { michael@0: mHandler.AppendElement(new SwitchHandlerHeadphone(SWITCH_HEADSET_DEVPATH)); michael@0: } else { michael@0: // If headphone status will be notified from input dev then initialize michael@0: // status to "off" and wait for event notification. michael@0: mEventInfo[SWITCH_HEADPHONES].mEvent.device() = SWITCH_HEADPHONES; michael@0: mEventInfo[SWITCH_HEADPHONES].mEvent.status() = SWITCH_STATE_OFF; michael@0: } michael@0: mHandler.AppendElement(new SwitchHandler(SWITCH_USB_DEVPATH_GB, SWITCH_USB)); michael@0: mHandler.AppendElement(new SwitchHandlerUsbIcs(SWITCH_USB_DEVPATH_ICS)); michael@0: michael@0: SwitchHandlerArray::index_type handlerIndex; michael@0: SwitchHandlerArray::size_type numHandlers = mHandler.Length(); michael@0: michael@0: for (handlerIndex = 0; handlerIndex < numHandlers; handlerIndex++) { michael@0: SwitchState state = mHandler[handlerIndex]->GetState(); michael@0: if (state == SWITCH_STATE_UNKNOWN) { michael@0: continue; michael@0: } michael@0: michael@0: SwitchDevice device = mHandler[handlerIndex]->GetType(); michael@0: mEventInfo[device].mEvent.device() = device; michael@0: mEventInfo[device].mEvent.status() = state; michael@0: } michael@0: } michael@0: michael@0: SwitchDevice GetEventInfo(const NetlinkEvent& aEvent, SwitchState& aState) michael@0: { michael@0: //working around the android code not being const-correct michael@0: NetlinkEvent *e = const_cast(&aEvent); michael@0: michael@0: for (size_t i = 0; i < mHandler.Length(); i++) { michael@0: if (mHandler[i]->CheckEvent(e)) { michael@0: aState = mHandler[i]->GetState(); michael@0: return mHandler[i]->GetType(); michael@0: } michael@0: } michael@0: return SWITCH_DEVICE_UNKNOWN; michael@0: } michael@0: }; michael@0: michael@0: static RefPtr sSwitchObserver; michael@0: michael@0: static void michael@0: InitializeResourceIfNeed() michael@0: { michael@0: if (!sSwitchObserver) { michael@0: sSwitchObserver = new SwitchEventObserver(); michael@0: RegisterUeventListener(sSwitchObserver); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: ReleaseResourceIfNeed() michael@0: { michael@0: if (sSwitchObserver->GetEnableCount() == 0) { michael@0: UnregisterUeventListener(sSwitchObserver); michael@0: sSwitchObserver = nullptr; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: EnableSwitchNotificationsIOThread(SwitchDevice aDevice, Monitor *aMonitor) michael@0: { michael@0: InitializeResourceIfNeed(); michael@0: sSwitchObserver->EnableSwitch(aDevice); michael@0: { michael@0: MonitorAutoLock lock(*aMonitor); michael@0: lock.Notify(); michael@0: } michael@0: michael@0: // Notify the latest state if IO thread has the information. michael@0: if (sSwitchObserver->GetEnableCount() > 1) { michael@0: sSwitchObserver->NotifyAnEvent(aDevice); michael@0: } michael@0: } michael@0: michael@0: void michael@0: EnableSwitchNotifications(SwitchDevice aDevice) michael@0: { michael@0: Monitor monitor("EnableSwitch.monitor"); michael@0: { michael@0: MonitorAutoLock lock(monitor); michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(EnableSwitchNotificationsIOThread, aDevice, &monitor)); michael@0: lock.Wait(); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: DisableSwitchNotificationsIOThread(SwitchDevice aDevice) michael@0: { michael@0: MOZ_ASSERT(sSwitchObserver->GetEnableCount()); michael@0: sSwitchObserver->DisableSwitch(aDevice); michael@0: ReleaseResourceIfNeed(); michael@0: } michael@0: michael@0: void michael@0: DisableSwitchNotifications(SwitchDevice aDevice) michael@0: { michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(DisableSwitchNotificationsIOThread, aDevice)); michael@0: } michael@0: michael@0: SwitchState michael@0: GetCurrentSwitchState(SwitchDevice aDevice) michael@0: { michael@0: MOZ_ASSERT(sSwitchObserver && sSwitchObserver->GetEnableCount()); michael@0: return sSwitchObserver->GetCurrentInformation(aDevice); michael@0: } michael@0: michael@0: static void michael@0: NotifySwitchStateIOThread(SwitchDevice aDevice, SwitchState aState) michael@0: { michael@0: sSwitchObserver->Notify(aDevice, aState); michael@0: } michael@0: michael@0: void NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState) michael@0: { michael@0: InitializeResourceIfNeed(); michael@0: XRE_GetIOMessageLoop()->PostTask( michael@0: FROM_HERE, michael@0: NewRunnableFunction(NotifySwitchStateIOThread, aDevice, aState)); michael@0: } michael@0: } // hal_impl michael@0: } //mozilla