|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* Copyright 2012 Mozilla Foundation and Mozilla contributors |
|
3 * |
|
4 * Licensed under the Apache License, Version 2.0 (the "License"); |
|
5 * you may not use this file except in compliance with the License. |
|
6 * You may obtain a copy of the License at |
|
7 * |
|
8 * http://www.apache.org/licenses/LICENSE-2.0 |
|
9 * |
|
10 * Unless required by applicable law or agreed to in writing, software |
|
11 * distributed under the License is distributed on an "AS IS" BASIS, |
|
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
13 * See the License for the specific language governing permissions and |
|
14 * limitations under the License. |
|
15 */ |
|
16 |
|
17 #include <android/log.h> |
|
18 #include <fcntl.h> |
|
19 #include <sysutils/NetlinkEvent.h> |
|
20 #include <cutils/properties.h> |
|
21 |
|
22 #include "base/message_loop.h" |
|
23 |
|
24 #include "Hal.h" |
|
25 #include "mozilla/FileUtils.h" |
|
26 #include "mozilla/RefPtr.h" |
|
27 #include "mozilla/Monitor.h" |
|
28 #include "nsPrintfCString.h" |
|
29 #include "nsXULAppAPI.h" |
|
30 #include "nsThreadUtils.h" |
|
31 #include "UeventPoller.h" |
|
32 |
|
33 using namespace mozilla::hal; |
|
34 |
|
35 #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "GonkSwitch" , ## args) |
|
36 |
|
37 #define SWITCH_HEADSET_DEVPATH "/devices/virtual/switch/h2w" |
|
38 #define SWITCH_USB_DEVPATH_GB "/devices/virtual/switch/usb_configuration" |
|
39 #define SWITCH_USB_DEVPATH_ICS "/devices/virtual/android_usb/android0" |
|
40 |
|
41 namespace mozilla { |
|
42 namespace hal_impl { |
|
43 /** |
|
44 * The uevent for a usb on GB insertion looks like: |
|
45 * |
|
46 * change@/devices/virtual/switch/usb_configuration |
|
47 * ACTION=change |
|
48 * DEVPATH=/devices/virtual/switch/usb_configuration |
|
49 * SUBSYSTEM=switch |
|
50 * SWITCH_NAME=usb_configuration |
|
51 * SWITCH_STATE=0 |
|
52 * SEQNUM=5038 |
|
53 */ |
|
54 class SwitchHandler |
|
55 { |
|
56 public: |
|
57 NS_INLINE_DECL_REFCOUNTING(SwitchHandler) |
|
58 |
|
59 SwitchHandler(const char* aDevPath, SwitchDevice aDevice) |
|
60 : mDevPath(aDevPath), |
|
61 mState(SWITCH_STATE_UNKNOWN), |
|
62 mDevice(aDevice) |
|
63 { |
|
64 GetInitialState(); |
|
65 } |
|
66 |
|
67 virtual ~SwitchHandler() |
|
68 { |
|
69 } |
|
70 |
|
71 bool CheckEvent(NetlinkEvent* aEvent) |
|
72 { |
|
73 if (strcmp(GetSubsystem(), aEvent->getSubsystem()) || |
|
74 strcmp(mDevPath, aEvent->findParam("DEVPATH"))) { |
|
75 return false; |
|
76 } |
|
77 |
|
78 mState = ConvertState(GetStateString(aEvent)); |
|
79 return mState != SWITCH_STATE_UNKNOWN; |
|
80 } |
|
81 |
|
82 SwitchState GetState() |
|
83 { |
|
84 return mState; |
|
85 } |
|
86 |
|
87 SwitchDevice GetType() |
|
88 { |
|
89 return mDevice; |
|
90 } |
|
91 protected: |
|
92 virtual const char* GetSubsystem() |
|
93 { |
|
94 return "switch"; |
|
95 } |
|
96 |
|
97 virtual const char* GetStateString(NetlinkEvent* aEvent) |
|
98 { |
|
99 return aEvent->findParam("SWITCH_STATE"); |
|
100 } |
|
101 |
|
102 void GetInitialState() |
|
103 { |
|
104 nsPrintfCString statePath("/sys%s/state", mDevPath); |
|
105 int fd = open(statePath.get(), O_RDONLY); |
|
106 if (fd <= 0) { |
|
107 return; |
|
108 } |
|
109 |
|
110 ScopedClose autoClose(fd); |
|
111 char state[16]; |
|
112 ssize_t bytesRead = read(fd, state, sizeof(state)); |
|
113 if (bytesRead < 0) { |
|
114 LOG("Read data from %s fails", statePath.get()); |
|
115 return; |
|
116 } |
|
117 |
|
118 if (state[bytesRead - 1] == '\n') { |
|
119 bytesRead--; |
|
120 } |
|
121 |
|
122 state[bytesRead] = '\0'; |
|
123 mState = ConvertState(state); |
|
124 } |
|
125 |
|
126 virtual SwitchState ConvertState(const char* aState) |
|
127 { |
|
128 MOZ_ASSERT(aState); |
|
129 return aState[0] == '0' ? SWITCH_STATE_OFF : SWITCH_STATE_ON; |
|
130 } |
|
131 |
|
132 const char* mDevPath; |
|
133 SwitchState mState; |
|
134 SwitchDevice mDevice; |
|
135 }; |
|
136 |
|
137 /** |
|
138 * The uevent delivered for the USB configuration under ICS looks like, |
|
139 * |
|
140 * change@/devices/virtual/android_usb/android0 |
|
141 * ACTION=change |
|
142 * DEVPATH=/devices/virtual/android_usb/android0 |
|
143 * SUBSYSTEM=android_usb |
|
144 * USB_STATE=CONFIGURED |
|
145 * SEQNUM=1802 |
|
146 */ |
|
147 class SwitchHandlerUsbIcs: public SwitchHandler |
|
148 { |
|
149 public: |
|
150 SwitchHandlerUsbIcs(const char* aDevPath) : SwitchHandler(aDevPath, SWITCH_USB) |
|
151 { |
|
152 SwitchHandler::GetInitialState(); |
|
153 } |
|
154 |
|
155 virtual ~SwitchHandlerUsbIcs() { } |
|
156 |
|
157 protected: |
|
158 virtual const char* GetSubsystem() |
|
159 { |
|
160 return "android_usb"; |
|
161 } |
|
162 |
|
163 virtual const char* GetStateString(NetlinkEvent* aEvent) |
|
164 { |
|
165 return aEvent->findParam("USB_STATE"); |
|
166 } |
|
167 |
|
168 SwitchState ConvertState(const char* aState) |
|
169 { |
|
170 MOZ_ASSERT(aState); |
|
171 return strcmp(aState, "CONFIGURED") == 0 ? SWITCH_STATE_ON : SWITCH_STATE_OFF; |
|
172 } |
|
173 }; |
|
174 |
|
175 /** |
|
176 * The uevent delivered for the headset under ICS looks like, |
|
177 * |
|
178 * change@/devices/virtual/switch/h2w |
|
179 * ACTION=change |
|
180 * DEVPATH=/devices/virtual/switch/h2w |
|
181 * SUBSYSTEM=switch |
|
182 * SWITCH_NAME=h2w |
|
183 * SWITCH_STATE=2 // Headset with no mic |
|
184 * SEQNUM=2581 |
|
185 * On Otoro, SWITCH_NAME could be Headset/No Device when plug/unplug. |
|
186 * change@/devices/virtual/switch/h2w |
|
187 * ACTION=change |
|
188 * DEVPATH=/devices/virtual/switch/h2w |
|
189 * SUBSYSTEM=switch |
|
190 * SWITCH_NAME=Headset |
|
191 * SWITCH_STATE=1 // Headset with mic |
|
192 * SEQNUM=1602 |
|
193 */ |
|
194 class SwitchHandlerHeadphone: public SwitchHandler |
|
195 { |
|
196 public: |
|
197 SwitchHandlerHeadphone(const char* aDevPath) : |
|
198 SwitchHandler(aDevPath, SWITCH_HEADPHONES) |
|
199 { |
|
200 SwitchHandler::GetInitialState(); |
|
201 } |
|
202 |
|
203 virtual ~SwitchHandlerHeadphone() { } |
|
204 |
|
205 protected: |
|
206 SwitchState ConvertState(const char* aState) |
|
207 { |
|
208 MOZ_ASSERT(aState); |
|
209 |
|
210 return aState[0] == '0' ? SWITCH_STATE_OFF : |
|
211 (aState[0] == '1' ? SWITCH_STATE_HEADSET : SWITCH_STATE_HEADPHONE); |
|
212 } |
|
213 }; |
|
214 |
|
215 |
|
216 typedef nsTArray<RefPtr<SwitchHandler> > SwitchHandlerArray; |
|
217 |
|
218 class SwitchEventRunnable : public nsRunnable |
|
219 { |
|
220 public: |
|
221 SwitchEventRunnable(SwitchEvent& aEvent) : mEvent(aEvent) |
|
222 { |
|
223 } |
|
224 |
|
225 NS_IMETHOD Run() |
|
226 { |
|
227 NotifySwitchChange(mEvent); |
|
228 return NS_OK; |
|
229 } |
|
230 private: |
|
231 SwitchEvent mEvent; |
|
232 }; |
|
233 |
|
234 class SwitchEventObserver MOZ_FINAL : public IUeventObserver |
|
235 { |
|
236 ~SwitchEventObserver() |
|
237 { |
|
238 mHandler.Clear(); |
|
239 } |
|
240 |
|
241 public: |
|
242 NS_INLINE_DECL_REFCOUNTING(SwitchEventObserver) |
|
243 SwitchEventObserver() : mEnableCount(0) |
|
244 { |
|
245 Init(); |
|
246 } |
|
247 |
|
248 int GetEnableCount() |
|
249 { |
|
250 return mEnableCount; |
|
251 } |
|
252 |
|
253 void EnableSwitch(SwitchDevice aDevice) |
|
254 { |
|
255 mEventInfo[aDevice].mEnabled = true; |
|
256 mEnableCount++; |
|
257 } |
|
258 |
|
259 void DisableSwitch(SwitchDevice aDevice) |
|
260 { |
|
261 mEventInfo[aDevice].mEnabled = false; |
|
262 mEnableCount--; |
|
263 } |
|
264 |
|
265 void Notify(const NetlinkEvent& aEvent) |
|
266 { |
|
267 SwitchState currState; |
|
268 |
|
269 SwitchDevice device = GetEventInfo(aEvent, currState); |
|
270 if (device == SWITCH_DEVICE_UNKNOWN) { |
|
271 return; |
|
272 } |
|
273 |
|
274 EventInfo& info = mEventInfo[device]; |
|
275 if (currState == info.mEvent.status()) { |
|
276 return; |
|
277 } |
|
278 |
|
279 info.mEvent.status() = currState; |
|
280 |
|
281 if (info.mEnabled) { |
|
282 NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); |
|
283 } |
|
284 } |
|
285 |
|
286 void Notify(SwitchDevice aDevice, SwitchState aState) |
|
287 { |
|
288 EventInfo& info = mEventInfo[aDevice]; |
|
289 if (aState == info.mEvent.status()) { |
|
290 return; |
|
291 } |
|
292 |
|
293 info.mEvent.status() = aState; |
|
294 |
|
295 if (info.mEnabled) { |
|
296 NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); |
|
297 } |
|
298 } |
|
299 |
|
300 SwitchState GetCurrentInformation(SwitchDevice aDevice) |
|
301 { |
|
302 return mEventInfo[aDevice].mEvent.status(); |
|
303 } |
|
304 |
|
305 void NotifyAnEvent(SwitchDevice aDevice) |
|
306 { |
|
307 EventInfo& info = mEventInfo[aDevice]; |
|
308 if (info.mEvent.status() != SWITCH_STATE_UNKNOWN) { |
|
309 NS_DispatchToMainThread(new SwitchEventRunnable(info.mEvent)); |
|
310 } |
|
311 } |
|
312 private: |
|
313 class EventInfo |
|
314 { |
|
315 public: |
|
316 EventInfo() : mEnabled(false) |
|
317 { |
|
318 mEvent.status() = SWITCH_STATE_UNKNOWN; |
|
319 mEvent.device() = SWITCH_DEVICE_UNKNOWN; |
|
320 } |
|
321 SwitchEvent mEvent; |
|
322 bool mEnabled; |
|
323 }; |
|
324 |
|
325 EventInfo mEventInfo[NUM_SWITCH_DEVICE]; |
|
326 size_t mEnableCount; |
|
327 SwitchHandlerArray mHandler; |
|
328 |
|
329 void Init() |
|
330 { |
|
331 char value[PROPERTY_VALUE_MAX]; |
|
332 property_get("ro.moz.devinputjack", value, "0"); |
|
333 bool headphonesFromInputDev = !strcmp(value, "1"); |
|
334 |
|
335 if (!headphonesFromInputDev) { |
|
336 mHandler.AppendElement(new SwitchHandlerHeadphone(SWITCH_HEADSET_DEVPATH)); |
|
337 } else { |
|
338 // If headphone status will be notified from input dev then initialize |
|
339 // status to "off" and wait for event notification. |
|
340 mEventInfo[SWITCH_HEADPHONES].mEvent.device() = SWITCH_HEADPHONES; |
|
341 mEventInfo[SWITCH_HEADPHONES].mEvent.status() = SWITCH_STATE_OFF; |
|
342 } |
|
343 mHandler.AppendElement(new SwitchHandler(SWITCH_USB_DEVPATH_GB, SWITCH_USB)); |
|
344 mHandler.AppendElement(new SwitchHandlerUsbIcs(SWITCH_USB_DEVPATH_ICS)); |
|
345 |
|
346 SwitchHandlerArray::index_type handlerIndex; |
|
347 SwitchHandlerArray::size_type numHandlers = mHandler.Length(); |
|
348 |
|
349 for (handlerIndex = 0; handlerIndex < numHandlers; handlerIndex++) { |
|
350 SwitchState state = mHandler[handlerIndex]->GetState(); |
|
351 if (state == SWITCH_STATE_UNKNOWN) { |
|
352 continue; |
|
353 } |
|
354 |
|
355 SwitchDevice device = mHandler[handlerIndex]->GetType(); |
|
356 mEventInfo[device].mEvent.device() = device; |
|
357 mEventInfo[device].mEvent.status() = state; |
|
358 } |
|
359 } |
|
360 |
|
361 SwitchDevice GetEventInfo(const NetlinkEvent& aEvent, SwitchState& aState) |
|
362 { |
|
363 //working around the android code not being const-correct |
|
364 NetlinkEvent *e = const_cast<NetlinkEvent*>(&aEvent); |
|
365 |
|
366 for (size_t i = 0; i < mHandler.Length(); i++) { |
|
367 if (mHandler[i]->CheckEvent(e)) { |
|
368 aState = mHandler[i]->GetState(); |
|
369 return mHandler[i]->GetType(); |
|
370 } |
|
371 } |
|
372 return SWITCH_DEVICE_UNKNOWN; |
|
373 } |
|
374 }; |
|
375 |
|
376 static RefPtr<SwitchEventObserver> sSwitchObserver; |
|
377 |
|
378 static void |
|
379 InitializeResourceIfNeed() |
|
380 { |
|
381 if (!sSwitchObserver) { |
|
382 sSwitchObserver = new SwitchEventObserver(); |
|
383 RegisterUeventListener(sSwitchObserver); |
|
384 } |
|
385 } |
|
386 |
|
387 static void |
|
388 ReleaseResourceIfNeed() |
|
389 { |
|
390 if (sSwitchObserver->GetEnableCount() == 0) { |
|
391 UnregisterUeventListener(sSwitchObserver); |
|
392 sSwitchObserver = nullptr; |
|
393 } |
|
394 } |
|
395 |
|
396 static void |
|
397 EnableSwitchNotificationsIOThread(SwitchDevice aDevice, Monitor *aMonitor) |
|
398 { |
|
399 InitializeResourceIfNeed(); |
|
400 sSwitchObserver->EnableSwitch(aDevice); |
|
401 { |
|
402 MonitorAutoLock lock(*aMonitor); |
|
403 lock.Notify(); |
|
404 } |
|
405 |
|
406 // Notify the latest state if IO thread has the information. |
|
407 if (sSwitchObserver->GetEnableCount() > 1) { |
|
408 sSwitchObserver->NotifyAnEvent(aDevice); |
|
409 } |
|
410 } |
|
411 |
|
412 void |
|
413 EnableSwitchNotifications(SwitchDevice aDevice) |
|
414 { |
|
415 Monitor monitor("EnableSwitch.monitor"); |
|
416 { |
|
417 MonitorAutoLock lock(monitor); |
|
418 XRE_GetIOMessageLoop()->PostTask( |
|
419 FROM_HERE, |
|
420 NewRunnableFunction(EnableSwitchNotificationsIOThread, aDevice, &monitor)); |
|
421 lock.Wait(); |
|
422 } |
|
423 } |
|
424 |
|
425 static void |
|
426 DisableSwitchNotificationsIOThread(SwitchDevice aDevice) |
|
427 { |
|
428 MOZ_ASSERT(sSwitchObserver->GetEnableCount()); |
|
429 sSwitchObserver->DisableSwitch(aDevice); |
|
430 ReleaseResourceIfNeed(); |
|
431 } |
|
432 |
|
433 void |
|
434 DisableSwitchNotifications(SwitchDevice aDevice) |
|
435 { |
|
436 XRE_GetIOMessageLoop()->PostTask( |
|
437 FROM_HERE, |
|
438 NewRunnableFunction(DisableSwitchNotificationsIOThread, aDevice)); |
|
439 } |
|
440 |
|
441 SwitchState |
|
442 GetCurrentSwitchState(SwitchDevice aDevice) |
|
443 { |
|
444 MOZ_ASSERT(sSwitchObserver && sSwitchObserver->GetEnableCount()); |
|
445 return sSwitchObserver->GetCurrentInformation(aDevice); |
|
446 } |
|
447 |
|
448 static void |
|
449 NotifySwitchStateIOThread(SwitchDevice aDevice, SwitchState aState) |
|
450 { |
|
451 sSwitchObserver->Notify(aDevice, aState); |
|
452 } |
|
453 |
|
454 void NotifySwitchStateFromInputDevice(SwitchDevice aDevice, SwitchState aState) |
|
455 { |
|
456 InitializeResourceIfNeed(); |
|
457 XRE_GetIOMessageLoop()->PostTask( |
|
458 FROM_HERE, |
|
459 NewRunnableFunction(NotifySwitchStateIOThread, aDevice, aState)); |
|
460 } |
|
461 } // hal_impl |
|
462 } //mozilla |