mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-14 15:37:55 +00:00
482 lines
12 KiB
C++
482 lines
12 KiB
C++
/* -*- 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 "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();
|
|
}
|
|
|
|
virtual ~SwitchHandler()
|
|
{
|
|
}
|
|
|
|
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 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 nsRunnable
|
|
{
|
|
public:
|
|
SwitchEventRunnable(SwitchEvent& aEvent) : mEvent(aEvent)
|
|
{
|
|
}
|
|
|
|
NS_IMETHOD Run()
|
|
{
|
|
NotifySwitchChange(mEvent);
|
|
return NS_OK;
|
|
}
|
|
private:
|
|
SwitchEvent mEvent;
|
|
};
|
|
|
|
class SwitchEventObserver MOZ_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(
|
|
FROM_HERE,
|
|
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(
|
|
FROM_HERE,
|
|
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(
|
|
FROM_HERE,
|
|
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
|