|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* vim: set ts=4 sw=4 sts=4 tw=80 et: */ |
|
3 /* Copyright 2012 Mozilla Foundation and Mozilla contributors |
|
4 * |
|
5 * Licensed under the Apache License, Version 2.0 (the "License"); |
|
6 * you may not use this file except in compliance with the License. |
|
7 * You may obtain a copy of the License at |
|
8 * |
|
9 * http://www.apache.org/licenses/LICENSE-2.0 |
|
10 * |
|
11 * Unless required by applicable law or agreed to in writing, software |
|
12 * distributed under the License is distributed on an "AS IS" BASIS, |
|
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
14 * See the License for the specific language governing permissions and |
|
15 * limitations under the License. |
|
16 */ |
|
17 |
|
18 #ifndef _GNU_SOURCE |
|
19 #define _GNU_SOURCE |
|
20 #endif |
|
21 |
|
22 #include <dirent.h> |
|
23 #include <errno.h> |
|
24 #include <fcntl.h> |
|
25 #include <signal.h> |
|
26 #include <sys/epoll.h> |
|
27 #include <sys/ioctl.h> |
|
28 #include <sys/param.h> |
|
29 #include <sys/stat.h> |
|
30 #include <sys/types.h> |
|
31 #include <unistd.h> |
|
32 #include <utils/BitSet.h> |
|
33 |
|
34 #include "base/basictypes.h" |
|
35 #include "GonkPermission.h" |
|
36 #include "nscore.h" |
|
37 #ifdef MOZ_OMX_DECODER |
|
38 #include "MediaResourceManagerService.h" |
|
39 #endif |
|
40 #include "mozilla/TouchEvents.h" |
|
41 #include "mozilla/FileUtils.h" |
|
42 #include "mozilla/Hal.h" |
|
43 #include "mozilla/MouseEvents.h" |
|
44 #include "mozilla/Mutex.h" |
|
45 #include "mozilla/Services.h" |
|
46 #include "mozilla/TextEvents.h" |
|
47 #if ANDROID_VERSION >= 18 |
|
48 #include "nativewindow/FakeSurfaceComposer.h" |
|
49 #endif |
|
50 #include "nsAppShell.h" |
|
51 #include "mozilla/dom/Touch.h" |
|
52 #include "nsGkAtoms.h" |
|
53 #include "nsIObserverService.h" |
|
54 #include "nsIScreen.h" |
|
55 #include "nsScreenManagerGonk.h" |
|
56 #include "nsThreadUtils.h" |
|
57 #include "nsWindow.h" |
|
58 #include "OrientationObserver.h" |
|
59 #include "GonkMemoryPressureMonitoring.h" |
|
60 |
|
61 #include "android/log.h" |
|
62 #include "libui/EventHub.h" |
|
63 #include "libui/InputReader.h" |
|
64 #include "libui/InputDispatcher.h" |
|
65 #include "cutils/properties.h" |
|
66 |
|
67 #ifdef MOZ_NUWA_PROCESS |
|
68 #include "ipc/Nuwa.h" |
|
69 #endif |
|
70 |
|
71 #include "GeckoProfiler.h" |
|
72 |
|
73 // Defines kKeyMapping and GetKeyNameIndex() |
|
74 #include "GonkKeyMapping.h" |
|
75 |
|
76 #define LOG(args...) \ |
|
77 __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) |
|
78 #ifdef VERBOSE_LOG_ENABLED |
|
79 # define VERBOSE_LOG(args...) \ |
|
80 __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) |
|
81 #else |
|
82 # define VERBOSE_LOG(args...) \ |
|
83 (void)0 |
|
84 #endif |
|
85 |
|
86 using namespace android; |
|
87 using namespace mozilla; |
|
88 using namespace mozilla::dom; |
|
89 using namespace mozilla::services; |
|
90 using namespace mozilla::widget; |
|
91 |
|
92 bool gDrawRequest = false; |
|
93 static nsAppShell *gAppShell = nullptr; |
|
94 static int epollfd = 0; |
|
95 static int signalfds[2] = {0}; |
|
96 static bool sDevInputAudioJack; |
|
97 static int32_t sHeadphoneState; |
|
98 static int32_t sMicrophoneState; |
|
99 |
|
100 // Amount of time in MS before an input is considered expired. |
|
101 static const uint64_t kInputExpirationThresholdMs = 1000; |
|
102 |
|
103 NS_IMPL_ISUPPORTS_INHERITED(nsAppShell, nsBaseAppShell, nsIObserver) |
|
104 |
|
105 static uint64_t |
|
106 nanosecsToMillisecs(nsecs_t nsecs) |
|
107 { |
|
108 return nsecs / 1000000; |
|
109 } |
|
110 |
|
111 namespace mozilla { |
|
112 |
|
113 bool ProcessNextEvent() |
|
114 { |
|
115 return gAppShell->ProcessNextNativeEvent(true); |
|
116 } |
|
117 |
|
118 void NotifyEvent() |
|
119 { |
|
120 gAppShell->NotifyNativeEvent(); |
|
121 } |
|
122 |
|
123 } // namespace mozilla |
|
124 |
|
125 static void |
|
126 pipeHandler(int fd, FdHandler *data) |
|
127 { |
|
128 ssize_t len; |
|
129 do { |
|
130 char tmp[32]; |
|
131 len = read(fd, tmp, sizeof(tmp)); |
|
132 } while (len > 0); |
|
133 } |
|
134 |
|
135 struct Touch { |
|
136 int32_t id; |
|
137 PointerCoords coords; |
|
138 }; |
|
139 |
|
140 struct UserInputData { |
|
141 uint64_t timeMs; |
|
142 enum { |
|
143 MOTION_DATA, |
|
144 KEY_DATA |
|
145 } type; |
|
146 int32_t action; |
|
147 int32_t flags; |
|
148 int32_t metaState; |
|
149 int32_t deviceId; |
|
150 union { |
|
151 struct { |
|
152 int32_t keyCode; |
|
153 int32_t scanCode; |
|
154 } key; |
|
155 struct { |
|
156 int32_t touchCount; |
|
157 ::Touch touches[MAX_POINTERS]; |
|
158 } motion; |
|
159 }; |
|
160 |
|
161 Modifiers DOMModifiers() const; |
|
162 }; |
|
163 |
|
164 Modifiers |
|
165 UserInputData::DOMModifiers() const |
|
166 { |
|
167 Modifiers result = 0; |
|
168 if (metaState & (AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) { |
|
169 result |= MODIFIER_ALT; |
|
170 } |
|
171 if (metaState & (AMETA_SHIFT_ON | |
|
172 AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) { |
|
173 result |= MODIFIER_SHIFT; |
|
174 } |
|
175 if (metaState & AMETA_FUNCTION_ON) { |
|
176 result |= MODIFIER_FN; |
|
177 } |
|
178 if (metaState & (AMETA_CTRL_ON | |
|
179 AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) { |
|
180 result |= MODIFIER_CONTROL; |
|
181 } |
|
182 if (metaState & (AMETA_META_ON | |
|
183 AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) { |
|
184 result |= MODIFIER_META; |
|
185 } |
|
186 if (metaState & AMETA_CAPS_LOCK_ON) { |
|
187 result |= MODIFIER_CAPSLOCK; |
|
188 } |
|
189 if (metaState & AMETA_NUM_LOCK_ON) { |
|
190 result |= MODIFIER_NUMLOCK; |
|
191 } |
|
192 if (metaState & AMETA_SCROLL_LOCK_ON) { |
|
193 result |= MODIFIER_SCROLLLOCK; |
|
194 } |
|
195 return result; |
|
196 } |
|
197 |
|
198 static void |
|
199 sendMouseEvent(uint32_t msg, UserInputData& data, bool forwardToChildren) |
|
200 { |
|
201 WidgetMouseEvent event(true, msg, nullptr, |
|
202 WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal); |
|
203 |
|
204 event.refPoint.x = data.motion.touches[0].coords.getX(); |
|
205 event.refPoint.y = data.motion.touches[0].coords.getY(); |
|
206 event.time = data.timeMs; |
|
207 event.button = WidgetMouseEvent::eLeftButton; |
|
208 event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH; |
|
209 if (msg != NS_MOUSE_MOVE) |
|
210 event.clickCount = 1; |
|
211 event.modifiers = data.DOMModifiers(); |
|
212 |
|
213 event.mFlags.mNoCrossProcessBoundaryForwarding = !forwardToChildren; |
|
214 |
|
215 nsWindow::DispatchInputEvent(event); |
|
216 } |
|
217 |
|
218 static void |
|
219 addDOMTouch(UserInputData& data, WidgetTouchEvent& event, int i) |
|
220 { |
|
221 const ::Touch& touch = data.motion.touches[i]; |
|
222 event.touches.AppendElement( |
|
223 new dom::Touch(touch.id, |
|
224 nsIntPoint(floor(touch.coords.getX() + 0.5), floor(touch.coords.getY() + 0.5)), |
|
225 nsIntPoint(touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), |
|
226 touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE)), |
|
227 0, |
|
228 touch.coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)) |
|
229 ); |
|
230 } |
|
231 |
|
232 static nsEventStatus |
|
233 sendTouchEvent(UserInputData& data, bool* captured) |
|
234 { |
|
235 uint32_t msg; |
|
236 int32_t action = data.action & AMOTION_EVENT_ACTION_MASK; |
|
237 switch (action) { |
|
238 case AMOTION_EVENT_ACTION_DOWN: |
|
239 case AMOTION_EVENT_ACTION_POINTER_DOWN: |
|
240 msg = NS_TOUCH_START; |
|
241 break; |
|
242 case AMOTION_EVENT_ACTION_MOVE: |
|
243 msg = NS_TOUCH_MOVE; |
|
244 break; |
|
245 case AMOTION_EVENT_ACTION_UP: |
|
246 case AMOTION_EVENT_ACTION_POINTER_UP: |
|
247 msg = NS_TOUCH_END; |
|
248 break; |
|
249 case AMOTION_EVENT_ACTION_OUTSIDE: |
|
250 case AMOTION_EVENT_ACTION_CANCEL: |
|
251 msg = NS_TOUCH_CANCEL; |
|
252 break; |
|
253 default: |
|
254 msg = NS_EVENT_NULL; |
|
255 break; |
|
256 } |
|
257 |
|
258 WidgetTouchEvent event(true, msg, nullptr); |
|
259 |
|
260 event.time = data.timeMs; |
|
261 event.modifiers = data.DOMModifiers(); |
|
262 |
|
263 int32_t i; |
|
264 if (msg == NS_TOUCH_END) { |
|
265 i = data.action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK; |
|
266 i >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; |
|
267 addDOMTouch(data, event, i); |
|
268 } else { |
|
269 for (i = 0; i < data.motion.touchCount; ++i) |
|
270 addDOMTouch(data, event, i); |
|
271 } |
|
272 |
|
273 return nsWindow::DispatchInputEvent(event, captured); |
|
274 } |
|
275 |
|
276 class MOZ_STACK_CLASS KeyEventDispatcher |
|
277 { |
|
278 public: |
|
279 KeyEventDispatcher(const UserInputData& aData, |
|
280 KeyCharacterMap* aKeyCharMap); |
|
281 void Dispatch(); |
|
282 |
|
283 private: |
|
284 const UserInputData& mData; |
|
285 sp<KeyCharacterMap> mKeyCharMap; |
|
286 |
|
287 char16_t mChar; |
|
288 char16_t mUnmodifiedChar; |
|
289 |
|
290 uint32_t mDOMKeyCode; |
|
291 KeyNameIndex mDOMKeyNameIndex; |
|
292 char16_t mDOMPrintableKeyValue; |
|
293 |
|
294 bool IsKeyPress() const |
|
295 { |
|
296 return mData.action == AKEY_EVENT_ACTION_DOWN; |
|
297 } |
|
298 bool IsRepeat() const |
|
299 { |
|
300 return IsKeyPress() && (mData.flags & AKEY_EVENT_FLAG_LONG_PRESS); |
|
301 } |
|
302 |
|
303 char16_t PrintableKeyValue() const; |
|
304 |
|
305 int32_t UnmodifiedMetaState() const |
|
306 { |
|
307 return mData.metaState & |
|
308 ~(AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON | |
|
309 AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON | |
|
310 AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON); |
|
311 } |
|
312 |
|
313 static bool IsControlChar(char16_t aChar) |
|
314 { |
|
315 return (aChar < ' ' || aChar == 0x7F); |
|
316 } |
|
317 |
|
318 void DispatchKeyDownEvent(); |
|
319 void DispatchKeyUpEvent(); |
|
320 nsEventStatus DispatchKeyEventInternal(uint32_t aEventMessage); |
|
321 }; |
|
322 |
|
323 KeyEventDispatcher::KeyEventDispatcher(const UserInputData& aData, |
|
324 KeyCharacterMap* aKeyCharMap) : |
|
325 mData(aData), mKeyCharMap(aKeyCharMap), mChar(0), mUnmodifiedChar(0), |
|
326 mDOMPrintableKeyValue(0) |
|
327 { |
|
328 // XXX Printable key's keyCode value should be computed with actual |
|
329 // input character. |
|
330 mDOMKeyCode = (mData.key.keyCode < (ssize_t)ArrayLength(kKeyMapping)) ? |
|
331 kKeyMapping[mData.key.keyCode] : 0; |
|
332 mDOMKeyNameIndex = GetKeyNameIndex(mData.key.keyCode); |
|
333 |
|
334 if (!mKeyCharMap.get()) { |
|
335 return; |
|
336 } |
|
337 |
|
338 mChar = mKeyCharMap->getCharacter(mData.key.keyCode, mData.metaState); |
|
339 if (IsControlChar(mChar)) { |
|
340 mChar = 0; |
|
341 } |
|
342 int32_t unmodifiedMetaState = UnmodifiedMetaState(); |
|
343 if (mData.metaState == unmodifiedMetaState) { |
|
344 mUnmodifiedChar = mChar; |
|
345 } else { |
|
346 mUnmodifiedChar = mKeyCharMap->getCharacter(mData.key.keyCode, |
|
347 unmodifiedMetaState); |
|
348 if (IsControlChar(mUnmodifiedChar)) { |
|
349 mUnmodifiedChar = 0; |
|
350 } |
|
351 } |
|
352 |
|
353 mDOMPrintableKeyValue = PrintableKeyValue(); |
|
354 } |
|
355 |
|
356 char16_t |
|
357 KeyEventDispatcher::PrintableKeyValue() const |
|
358 { |
|
359 if (mDOMKeyNameIndex != KEY_NAME_INDEX_USE_STRING) { |
|
360 return 0; |
|
361 } |
|
362 return mChar ? mChar : mUnmodifiedChar; |
|
363 } |
|
364 |
|
365 nsEventStatus |
|
366 KeyEventDispatcher::DispatchKeyEventInternal(uint32_t aEventMessage) |
|
367 { |
|
368 WidgetKeyboardEvent event(true, aEventMessage, nullptr); |
|
369 if (aEventMessage == NS_KEY_PRESS) { |
|
370 // XXX If the charCode is not a printable character, the charCode |
|
371 // should be computed without Ctrl/Alt/Meta modifiers. |
|
372 event.charCode = static_cast<uint32_t>(mChar); |
|
373 } |
|
374 if (!event.charCode) { |
|
375 event.keyCode = mDOMKeyCode; |
|
376 } |
|
377 event.isChar = !!event.charCode; |
|
378 event.mIsRepeat = IsRepeat(); |
|
379 event.mKeyNameIndex = mDOMKeyNameIndex; |
|
380 if (mDOMPrintableKeyValue) { |
|
381 event.mKeyValue = mDOMPrintableKeyValue; |
|
382 } |
|
383 event.modifiers = mData.DOMModifiers(); |
|
384 event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_MOBILE; |
|
385 event.time = mData.timeMs; |
|
386 return nsWindow::DispatchInputEvent(event); |
|
387 } |
|
388 |
|
389 void |
|
390 KeyEventDispatcher::Dispatch() |
|
391 { |
|
392 // XXX Even if unknown key is pressed, DOM key event should be |
|
393 // dispatched since Gecko for the other platforms are implemented |
|
394 // as so. |
|
395 if (!mDOMKeyCode && mDOMKeyNameIndex == KEY_NAME_INDEX_Unidentified) { |
|
396 VERBOSE_LOG("Got unknown key event code. " |
|
397 "type 0x%04x code 0x%04x value %d", |
|
398 mData.action, mData.key.keyCode, IsKeyPress()); |
|
399 return; |
|
400 } |
|
401 |
|
402 if (IsKeyPress()) { |
|
403 DispatchKeyDownEvent(); |
|
404 } else { |
|
405 DispatchKeyUpEvent(); |
|
406 } |
|
407 } |
|
408 |
|
409 void |
|
410 KeyEventDispatcher::DispatchKeyDownEvent() |
|
411 { |
|
412 nsEventStatus status = DispatchKeyEventInternal(NS_KEY_DOWN); |
|
413 if (status != nsEventStatus_eConsumeNoDefault) { |
|
414 DispatchKeyEventInternal(NS_KEY_PRESS); |
|
415 } |
|
416 } |
|
417 |
|
418 void |
|
419 KeyEventDispatcher::DispatchKeyUpEvent() |
|
420 { |
|
421 DispatchKeyEventInternal(NS_KEY_UP); |
|
422 } |
|
423 |
|
424 class SwitchEventRunnable : public nsRunnable { |
|
425 public: |
|
426 SwitchEventRunnable(hal::SwitchEvent& aEvent) : mEvent(aEvent) |
|
427 {} |
|
428 |
|
429 NS_IMETHOD Run() |
|
430 { |
|
431 hal::NotifySwitchStateFromInputDevice(mEvent.device(), |
|
432 mEvent.status()); |
|
433 return NS_OK; |
|
434 } |
|
435 private: |
|
436 hal::SwitchEvent mEvent; |
|
437 }; |
|
438 |
|
439 static void |
|
440 updateHeadphoneSwitch() |
|
441 { |
|
442 hal::SwitchEvent event; |
|
443 |
|
444 switch (sHeadphoneState) { |
|
445 case AKEY_STATE_UP: |
|
446 event.status() = hal::SWITCH_STATE_OFF; |
|
447 break; |
|
448 case AKEY_STATE_DOWN: |
|
449 event.status() = sMicrophoneState == AKEY_STATE_DOWN ? |
|
450 hal::SWITCH_STATE_HEADSET : hal::SWITCH_STATE_HEADPHONE; |
|
451 break; |
|
452 default: |
|
453 return; |
|
454 } |
|
455 |
|
456 event.device() = hal::SWITCH_HEADPHONES; |
|
457 NS_DispatchToMainThread(new SwitchEventRunnable(event)); |
|
458 } |
|
459 |
|
460 class GeckoPointerController : public PointerControllerInterface { |
|
461 float mX; |
|
462 float mY; |
|
463 int32_t mButtonState; |
|
464 InputReaderConfiguration* mConfig; |
|
465 public: |
|
466 GeckoPointerController(InputReaderConfiguration* config) |
|
467 : mX(0) |
|
468 , mY(0) |
|
469 , mButtonState(0) |
|
470 , mConfig(config) |
|
471 {} |
|
472 |
|
473 virtual bool getBounds(float* outMinX, float* outMinY, |
|
474 float* outMaxX, float* outMaxY) const; |
|
475 virtual void move(float deltaX, float deltaY); |
|
476 virtual void setButtonState(int32_t buttonState); |
|
477 virtual int32_t getButtonState() const; |
|
478 virtual void setPosition(float x, float y); |
|
479 virtual void getPosition(float* outX, float* outY) const; |
|
480 virtual void fade(Transition transition) {} |
|
481 virtual void unfade(Transition transition) {} |
|
482 virtual void setPresentation(Presentation presentation) {} |
|
483 virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, |
|
484 BitSet32 spotIdBits) {} |
|
485 virtual void clearSpots() {} |
|
486 }; |
|
487 |
|
488 bool |
|
489 GeckoPointerController::getBounds(float* outMinX, |
|
490 float* outMinY, |
|
491 float* outMaxX, |
|
492 float* outMaxY) const |
|
493 { |
|
494 DisplayViewport viewport; |
|
495 |
|
496 mConfig->getDisplayInfo(false, &viewport); |
|
497 |
|
498 *outMinX = *outMinY = 0; |
|
499 *outMaxX = viewport.logicalRight; |
|
500 *outMaxY = viewport.logicalBottom; |
|
501 return true; |
|
502 } |
|
503 |
|
504 void |
|
505 GeckoPointerController::move(float deltaX, float deltaY) |
|
506 { |
|
507 float minX, minY, maxX, maxY; |
|
508 getBounds(&minX, &minY, &maxX, &maxY); |
|
509 |
|
510 mX = clamped(mX + deltaX, minX, maxX); |
|
511 mY = clamped(mY + deltaY, minY, maxY); |
|
512 } |
|
513 |
|
514 void |
|
515 GeckoPointerController::setButtonState(int32_t buttonState) |
|
516 { |
|
517 mButtonState = buttonState; |
|
518 } |
|
519 |
|
520 int32_t |
|
521 GeckoPointerController::getButtonState() const |
|
522 { |
|
523 return mButtonState; |
|
524 } |
|
525 |
|
526 void |
|
527 GeckoPointerController::setPosition(float x, float y) |
|
528 { |
|
529 mX = x; |
|
530 mY = y; |
|
531 } |
|
532 |
|
533 void |
|
534 GeckoPointerController::getPosition(float* outX, float* outY) const |
|
535 { |
|
536 *outX = mX; |
|
537 *outY = mY; |
|
538 } |
|
539 |
|
540 class GeckoInputReaderPolicy : public InputReaderPolicyInterface { |
|
541 InputReaderConfiguration mConfig; |
|
542 public: |
|
543 GeckoInputReaderPolicy() {} |
|
544 |
|
545 virtual void getReaderConfiguration(InputReaderConfiguration* outConfig); |
|
546 virtual sp<PointerControllerInterface> obtainPointerController(int32_t |
|
547 deviceId) |
|
548 { |
|
549 return new GeckoPointerController(&mConfig); |
|
550 }; |
|
551 virtual void notifyInputDevicesChanged(const android::Vector<InputDeviceInfo>& inputDevices) {}; |
|
552 virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(const String8& inputDeviceDescriptor) |
|
553 { |
|
554 return nullptr; |
|
555 }; |
|
556 virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier) |
|
557 { |
|
558 return String8::empty(); |
|
559 }; |
|
560 |
|
561 void setDisplayInfo(); |
|
562 |
|
563 protected: |
|
564 virtual ~GeckoInputReaderPolicy() {} |
|
565 }; |
|
566 |
|
567 class GeckoInputDispatcher : public InputDispatcherInterface { |
|
568 public: |
|
569 GeckoInputDispatcher(sp<EventHub> &aEventHub) |
|
570 : mQueueLock("GeckoInputDispatcher::mQueueMutex") |
|
571 , mEventHub(aEventHub) |
|
572 , mTouchDownCount(0) |
|
573 , mKeyDownCount(0) |
|
574 , mTouchEventsFiltered(false) |
|
575 , mKeyEventsFiltered(false) |
|
576 {} |
|
577 |
|
578 virtual void dump(String8& dump); |
|
579 |
|
580 virtual void monitor() {} |
|
581 |
|
582 // Called on the main thread |
|
583 virtual void dispatchOnce(); |
|
584 |
|
585 // notify* methods are called on the InputReaderThread |
|
586 virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args); |
|
587 virtual void notifyKey(const NotifyKeyArgs* args); |
|
588 virtual void notifyMotion(const NotifyMotionArgs* args); |
|
589 virtual void notifySwitch(const NotifySwitchArgs* args); |
|
590 virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args); |
|
591 |
|
592 virtual int32_t injectInputEvent(const InputEvent* event, |
|
593 int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, |
|
594 uint32_t policyFlags); |
|
595 |
|
596 virtual void setInputWindows(const android::Vector<sp<InputWindowHandle> >& inputWindowHandles); |
|
597 virtual void setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle); |
|
598 |
|
599 virtual void setInputDispatchMode(bool enabled, bool frozen); |
|
600 virtual void setInputFilterEnabled(bool enabled) {} |
|
601 virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel, |
|
602 const sp<InputChannel>& toChannel) { return true; } |
|
603 |
|
604 virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel, |
|
605 const sp<InputWindowHandle>& inputWindowHandle, bool monitor); |
|
606 virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel); |
|
607 |
|
608 |
|
609 |
|
610 protected: |
|
611 virtual ~GeckoInputDispatcher() {} |
|
612 |
|
613 private: |
|
614 // mQueueLock should generally be locked while using mEventQueue. |
|
615 // UserInputData is pushed on on the InputReaderThread and |
|
616 // popped and dispatched on the main thread. |
|
617 mozilla::Mutex mQueueLock; |
|
618 std::queue<UserInputData> mEventQueue; |
|
619 sp<EventHub> mEventHub; |
|
620 |
|
621 int mTouchDownCount; |
|
622 int mKeyDownCount; |
|
623 bool mTouchEventsFiltered; |
|
624 bool mKeyEventsFiltered; |
|
625 BitSet32 mTouchDown; |
|
626 }; |
|
627 |
|
628 // GeckoInputReaderPolicy |
|
629 void |
|
630 GeckoInputReaderPolicy::setDisplayInfo() |
|
631 { |
|
632 static_assert(nsIScreen::ROTATION_0_DEG == |
|
633 DISPLAY_ORIENTATION_0, |
|
634 "Orientation enums not matched!"); |
|
635 static_assert(nsIScreen::ROTATION_90_DEG == |
|
636 DISPLAY_ORIENTATION_90, |
|
637 "Orientation enums not matched!"); |
|
638 static_assert(nsIScreen::ROTATION_180_DEG == |
|
639 DISPLAY_ORIENTATION_180, |
|
640 "Orientation enums not matched!"); |
|
641 static_assert(nsIScreen::ROTATION_270_DEG == |
|
642 DISPLAY_ORIENTATION_270, |
|
643 "Orientation enums not matched!"); |
|
644 |
|
645 DisplayViewport viewport; |
|
646 viewport.displayId = 0; |
|
647 viewport.orientation = nsScreenGonk::GetRotation(); |
|
648 viewport.physicalRight = viewport.deviceWidth = gScreenBounds.width; |
|
649 viewport.physicalBottom = viewport.deviceHeight = gScreenBounds.height; |
|
650 if (viewport.orientation == DISPLAY_ORIENTATION_90 || |
|
651 viewport.orientation == DISPLAY_ORIENTATION_270) { |
|
652 viewport.logicalRight = gScreenBounds.height; |
|
653 viewport.logicalBottom = gScreenBounds.width; |
|
654 } else { |
|
655 viewport.logicalRight = gScreenBounds.width; |
|
656 viewport.logicalBottom = gScreenBounds.height; |
|
657 } |
|
658 mConfig.setDisplayInfo(false, viewport); |
|
659 } |
|
660 |
|
661 void GeckoInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) |
|
662 { |
|
663 *outConfig = mConfig; |
|
664 } |
|
665 |
|
666 |
|
667 // GeckoInputDispatcher |
|
668 void |
|
669 GeckoInputDispatcher::dump(String8& dump) |
|
670 { |
|
671 } |
|
672 |
|
673 static bool |
|
674 isExpired(const UserInputData& data) |
|
675 { |
|
676 uint64_t timeNowMs = |
|
677 nanosecsToMillisecs(systemTime(SYSTEM_TIME_MONOTONIC)); |
|
678 return (timeNowMs - data.timeMs) > kInputExpirationThresholdMs; |
|
679 } |
|
680 |
|
681 void |
|
682 GeckoInputDispatcher::dispatchOnce() |
|
683 { |
|
684 UserInputData data; |
|
685 { |
|
686 MutexAutoLock lock(mQueueLock); |
|
687 if (mEventQueue.empty()) |
|
688 return; |
|
689 data = mEventQueue.front(); |
|
690 mEventQueue.pop(); |
|
691 if (!mEventQueue.empty()) |
|
692 gAppShell->NotifyNativeEvent(); |
|
693 } |
|
694 |
|
695 switch (data.type) { |
|
696 case UserInputData::MOTION_DATA: { |
|
697 if (!mTouchDownCount) { |
|
698 // No pending events, the filter state can be updated. |
|
699 mTouchEventsFiltered = isExpired(data); |
|
700 } |
|
701 |
|
702 int32_t action = data.action & AMOTION_EVENT_ACTION_MASK; |
|
703 int32_t index = data.action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK; |
|
704 index >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; |
|
705 switch (action) { |
|
706 case AMOTION_EVENT_ACTION_DOWN: |
|
707 case AMOTION_EVENT_ACTION_POINTER_DOWN: |
|
708 if (!mTouchDown.hasBit(index)) { |
|
709 mTouchDown.markBit(index); |
|
710 mTouchDownCount++; |
|
711 } |
|
712 break; |
|
713 case AMOTION_EVENT_ACTION_MOVE: |
|
714 case AMOTION_EVENT_ACTION_HOVER_MOVE: |
|
715 // No need to update the count on move. |
|
716 break; |
|
717 case AMOTION_EVENT_ACTION_UP: |
|
718 case AMOTION_EVENT_ACTION_POINTER_UP: |
|
719 case AMOTION_EVENT_ACTION_OUTSIDE: |
|
720 case AMOTION_EVENT_ACTION_CANCEL: |
|
721 if (mTouchDown.hasBit(index)) { |
|
722 mTouchDown.clearBit(index); |
|
723 mTouchDownCount--; |
|
724 } |
|
725 break; |
|
726 default: |
|
727 break; |
|
728 } |
|
729 |
|
730 if (mTouchEventsFiltered) { |
|
731 return; |
|
732 } |
|
733 |
|
734 nsEventStatus status = nsEventStatus_eIgnore; |
|
735 if (action != AMOTION_EVENT_ACTION_HOVER_MOVE) { |
|
736 bool captured; |
|
737 status = sendTouchEvent(data, &captured); |
|
738 if (captured) { |
|
739 return; |
|
740 } |
|
741 } |
|
742 |
|
743 uint32_t msg; |
|
744 switch (action) { |
|
745 case AMOTION_EVENT_ACTION_DOWN: |
|
746 msg = NS_MOUSE_BUTTON_DOWN; |
|
747 break; |
|
748 case AMOTION_EVENT_ACTION_POINTER_DOWN: |
|
749 case AMOTION_EVENT_ACTION_POINTER_UP: |
|
750 case AMOTION_EVENT_ACTION_MOVE: |
|
751 case AMOTION_EVENT_ACTION_HOVER_MOVE: |
|
752 msg = NS_MOUSE_MOVE; |
|
753 break; |
|
754 case AMOTION_EVENT_ACTION_OUTSIDE: |
|
755 case AMOTION_EVENT_ACTION_CANCEL: |
|
756 case AMOTION_EVENT_ACTION_UP: |
|
757 msg = NS_MOUSE_BUTTON_UP; |
|
758 break; |
|
759 default: |
|
760 msg = NS_EVENT_NULL; |
|
761 break; |
|
762 } |
|
763 if (msg != NS_EVENT_NULL) { |
|
764 sendMouseEvent(msg, data, |
|
765 status != nsEventStatus_eConsumeNoDefault); |
|
766 } |
|
767 break; |
|
768 } |
|
769 case UserInputData::KEY_DATA: { |
|
770 if (!mKeyDownCount) { |
|
771 // No pending events, the filter state can be updated. |
|
772 mKeyEventsFiltered = isExpired(data); |
|
773 } |
|
774 |
|
775 mKeyDownCount += (data.action == AKEY_EVENT_ACTION_DOWN) ? 1 : -1; |
|
776 if (mKeyEventsFiltered) { |
|
777 return; |
|
778 } |
|
779 |
|
780 sp<KeyCharacterMap> kcm = mEventHub->getKeyCharacterMap(data.deviceId); |
|
781 KeyEventDispatcher dispatcher(data, kcm.get()); |
|
782 dispatcher.Dispatch(); |
|
783 break; |
|
784 } |
|
785 } |
|
786 } |
|
787 |
|
788 void |
|
789 GeckoInputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs*) |
|
790 { |
|
791 } |
|
792 |
|
793 void |
|
794 GeckoInputDispatcher::notifyKey(const NotifyKeyArgs* args) |
|
795 { |
|
796 UserInputData data; |
|
797 data.timeMs = nanosecsToMillisecs(args->eventTime); |
|
798 data.type = UserInputData::KEY_DATA; |
|
799 data.action = args->action; |
|
800 data.flags = args->flags; |
|
801 data.metaState = args->metaState; |
|
802 data.deviceId = args->deviceId; |
|
803 data.key.keyCode = args->keyCode; |
|
804 data.key.scanCode = args->scanCode; |
|
805 { |
|
806 MutexAutoLock lock(mQueueLock); |
|
807 mEventQueue.push(data); |
|
808 } |
|
809 gAppShell->NotifyNativeEvent(); |
|
810 } |
|
811 |
|
812 |
|
813 void |
|
814 GeckoInputDispatcher::notifyMotion(const NotifyMotionArgs* args) |
|
815 { |
|
816 UserInputData data; |
|
817 data.timeMs = nanosecsToMillisecs(args->eventTime); |
|
818 data.type = UserInputData::MOTION_DATA; |
|
819 data.action = args->action; |
|
820 data.flags = args->flags; |
|
821 data.metaState = args->metaState; |
|
822 data.deviceId = args->deviceId; |
|
823 MOZ_ASSERT(args->pointerCount <= MAX_POINTERS); |
|
824 data.motion.touchCount = args->pointerCount; |
|
825 for (uint32_t i = 0; i < args->pointerCount; ++i) { |
|
826 ::Touch& touch = data.motion.touches[i]; |
|
827 touch.id = args->pointerProperties[i].id; |
|
828 memcpy(&touch.coords, &args->pointerCoords[i], sizeof(*args->pointerCoords)); |
|
829 } |
|
830 { |
|
831 MutexAutoLock lock(mQueueLock); |
|
832 if (!mEventQueue.empty() && |
|
833 mEventQueue.back().type == UserInputData::MOTION_DATA && |
|
834 ((mEventQueue.back().action & AMOTION_EVENT_ACTION_MASK) == |
|
835 AMOTION_EVENT_ACTION_MOVE || |
|
836 (mEventQueue.back().action & AMOTION_EVENT_ACTION_MASK) == |
|
837 AMOTION_EVENT_ACTION_HOVER_MOVE)) |
|
838 mEventQueue.back() = data; |
|
839 else |
|
840 mEventQueue.push(data); |
|
841 } |
|
842 gAppShell->NotifyNativeEvent(); |
|
843 } |
|
844 |
|
845 |
|
846 |
|
847 void GeckoInputDispatcher::notifySwitch(const NotifySwitchArgs* args) |
|
848 { |
|
849 if (!sDevInputAudioJack) |
|
850 return; |
|
851 |
|
852 bool needSwitchUpdate = false; |
|
853 |
|
854 if (args->switchMask & (1 << SW_HEADPHONE_INSERT)) { |
|
855 sHeadphoneState = (args->switchValues & (1 << SW_HEADPHONE_INSERT)) ? |
|
856 AKEY_STATE_DOWN : AKEY_STATE_UP; |
|
857 needSwitchUpdate = true; |
|
858 } |
|
859 |
|
860 if (args->switchMask & (1 << SW_MICROPHONE_INSERT)) { |
|
861 sMicrophoneState = (args->switchValues & (1 << SW_MICROPHONE_INSERT)) ? |
|
862 AKEY_STATE_DOWN : AKEY_STATE_UP; |
|
863 needSwitchUpdate = true; |
|
864 } |
|
865 |
|
866 if (needSwitchUpdate) |
|
867 updateHeadphoneSwitch(); |
|
868 } |
|
869 |
|
870 void GeckoInputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) |
|
871 { |
|
872 } |
|
873 |
|
874 int32_t GeckoInputDispatcher::injectInputEvent( |
|
875 const InputEvent* event, |
|
876 int32_t injectorPid, int32_t injectorUid, int32_t syncMode, |
|
877 int32_t timeoutMillis, uint32_t policyFlags) |
|
878 { |
|
879 return INPUT_EVENT_INJECTION_SUCCEEDED; |
|
880 } |
|
881 |
|
882 void |
|
883 GeckoInputDispatcher::setInputWindows(const android::Vector<sp<InputWindowHandle> >& inputWindowHandles) |
|
884 { |
|
885 } |
|
886 |
|
887 void |
|
888 GeckoInputDispatcher::setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle) |
|
889 { |
|
890 } |
|
891 |
|
892 void |
|
893 GeckoInputDispatcher::setInputDispatchMode(bool enabled, bool frozen) |
|
894 { |
|
895 } |
|
896 |
|
897 status_t |
|
898 GeckoInputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, |
|
899 const sp<InputWindowHandle>& inputWindowHandle, bool monitor) |
|
900 { |
|
901 return OK; |
|
902 } |
|
903 |
|
904 status_t |
|
905 GeckoInputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) |
|
906 { |
|
907 return OK; |
|
908 } |
|
909 |
|
910 nsAppShell::nsAppShell() |
|
911 : mNativeCallbackRequest(false) |
|
912 , mEnableDraw(false) |
|
913 , mHandlers() |
|
914 { |
|
915 gAppShell = this; |
|
916 } |
|
917 |
|
918 nsAppShell::~nsAppShell() |
|
919 { |
|
920 // mReaderThread and mEventHub will both be null if InitInputDevices |
|
921 // is not called. |
|
922 if (mReaderThread.get()) { |
|
923 // We separate requestExit() and join() here so we can wake the EventHub's |
|
924 // input loop, and stop it from polling for input events |
|
925 mReaderThread->requestExit(); |
|
926 mEventHub->wake(); |
|
927 |
|
928 status_t result = mReaderThread->requestExitAndWait(); |
|
929 if (result) |
|
930 LOG("Could not stop reader thread - %d", result); |
|
931 } |
|
932 gAppShell = nullptr; |
|
933 } |
|
934 |
|
935 nsresult |
|
936 nsAppShell::Init() |
|
937 { |
|
938 nsresult rv = nsBaseAppShell::Init(); |
|
939 NS_ENSURE_SUCCESS(rv, rv); |
|
940 |
|
941 epollfd = epoll_create(16); |
|
942 NS_ENSURE_TRUE(epollfd >= 0, NS_ERROR_UNEXPECTED); |
|
943 |
|
944 int ret = pipe2(signalfds, O_NONBLOCK); |
|
945 NS_ENSURE_FALSE(ret, NS_ERROR_UNEXPECTED); |
|
946 |
|
947 rv = AddFdHandler(signalfds[0], pipeHandler, ""); |
|
948 NS_ENSURE_SUCCESS(rv, rv); |
|
949 |
|
950 InitGonkMemoryPressureMonitoring(); |
|
951 |
|
952 if (XRE_GetProcessType() == GeckoProcessType_Default) { |
|
953 #ifdef MOZ_OMX_DECODER |
|
954 android::MediaResourceManagerService::instantiate(); |
|
955 #endif |
|
956 #if ANDROID_VERSION >= 18 && (defined(MOZ_OMX_DECODER) || defined(MOZ_B2G_CAMERA)) |
|
957 android::FakeSurfaceComposer::instantiate(); |
|
958 #endif |
|
959 GonkPermissionService::instantiate(); |
|
960 |
|
961 // Causes the kernel timezone to be set, which in turn causes the |
|
962 // timestamps on SD cards to have the local time rather than UTC time. |
|
963 hal::SetTimezone(hal::GetTimezone()); |
|
964 } |
|
965 |
|
966 nsCOMPtr<nsIObserverService> obsServ = GetObserverService(); |
|
967 if (obsServ) { |
|
968 obsServ->AddObserver(this, "browser-ui-startup-complete", false); |
|
969 obsServ->AddObserver(this, "network-connection-state-changed", false); |
|
970 } |
|
971 |
|
972 #ifdef MOZ_NUWA_PROCESS |
|
973 // Make sure main thread was woken up after Nuwa fork. |
|
974 NuwaAddConstructor((void (*)(void *))&NotifyEvent, nullptr); |
|
975 #endif |
|
976 |
|
977 // Delay initializing input devices until the screen has been |
|
978 // initialized (and we know the resolution). |
|
979 return rv; |
|
980 } |
|
981 |
|
982 NS_IMETHODIMP |
|
983 nsAppShell::Observe(nsISupports* aSubject, |
|
984 const char* aTopic, |
|
985 const char16_t* aData) |
|
986 { |
|
987 if (!strcmp(aTopic, "network-connection-state-changed")) { |
|
988 NS_ConvertUTF16toUTF8 type(aData); |
|
989 if (!type.IsEmpty()) { |
|
990 hal::NotifyNetworkChange(hal::NetworkInformation(atoi(type.get()), 0, 0)); |
|
991 } |
|
992 return NS_OK; |
|
993 } else if (!strcmp(aTopic, "browser-ui-startup-complete")) { |
|
994 if (sDevInputAudioJack) { |
|
995 sHeadphoneState = mReader->getSwitchState(-1, AINPUT_SOURCE_SWITCH, SW_HEADPHONE_INSERT); |
|
996 sMicrophoneState = mReader->getSwitchState(-1, AINPUT_SOURCE_SWITCH, SW_MICROPHONE_INSERT); |
|
997 updateHeadphoneSwitch(); |
|
998 } |
|
999 mEnableDraw = true; |
|
1000 NotifyEvent(); |
|
1001 return NS_OK; |
|
1002 } |
|
1003 |
|
1004 return nsBaseAppShell::Observe(aSubject, aTopic, aData); |
|
1005 } |
|
1006 |
|
1007 NS_IMETHODIMP |
|
1008 nsAppShell::Exit() |
|
1009 { |
|
1010 OrientationObserver::ShutDown(); |
|
1011 nsCOMPtr<nsIObserverService> obsServ = GetObserverService(); |
|
1012 if (obsServ) { |
|
1013 obsServ->RemoveObserver(this, "browser-ui-startup-complete"); |
|
1014 obsServ->RemoveObserver(this, "network-connection-state-changed"); |
|
1015 } |
|
1016 return nsBaseAppShell::Exit(); |
|
1017 } |
|
1018 |
|
1019 void |
|
1020 nsAppShell::InitInputDevices() |
|
1021 { |
|
1022 char value[PROPERTY_VALUE_MAX]; |
|
1023 property_get("ro.moz.devinputjack", value, "0"); |
|
1024 sDevInputAudioJack = !strcmp(value, "1"); |
|
1025 sHeadphoneState = AKEY_STATE_UNKNOWN; |
|
1026 sMicrophoneState = AKEY_STATE_UNKNOWN; |
|
1027 |
|
1028 mEventHub = new EventHub(); |
|
1029 mReaderPolicy = new GeckoInputReaderPolicy(); |
|
1030 mReaderPolicy->setDisplayInfo(); |
|
1031 mDispatcher = new GeckoInputDispatcher(mEventHub); |
|
1032 |
|
1033 mReader = new InputReader(mEventHub, mReaderPolicy, mDispatcher); |
|
1034 mReaderThread = new InputReaderThread(mReader); |
|
1035 |
|
1036 status_t result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); |
|
1037 if (result) { |
|
1038 LOG("Failed to initialize InputReader thread, bad things are going to happen..."); |
|
1039 } |
|
1040 } |
|
1041 |
|
1042 nsresult |
|
1043 nsAppShell::AddFdHandler(int fd, FdHandlerCallback handlerFunc, |
|
1044 const char* deviceName) |
|
1045 { |
|
1046 epoll_event event = { |
|
1047 EPOLLIN, |
|
1048 { 0 } |
|
1049 }; |
|
1050 |
|
1051 FdHandler *handler = mHandlers.AppendElement(); |
|
1052 handler->fd = fd; |
|
1053 strncpy(handler->name, deviceName, sizeof(handler->name) - 1); |
|
1054 handler->func = handlerFunc; |
|
1055 event.data.u32 = mHandlers.Length() - 1; |
|
1056 return epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) ? |
|
1057 NS_ERROR_UNEXPECTED : NS_OK; |
|
1058 } |
|
1059 |
|
1060 void |
|
1061 nsAppShell::ScheduleNativeEventCallback() |
|
1062 { |
|
1063 mNativeCallbackRequest = true; |
|
1064 NotifyEvent(); |
|
1065 } |
|
1066 |
|
1067 bool |
|
1068 nsAppShell::ProcessNextNativeEvent(bool mayWait) |
|
1069 { |
|
1070 PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent"); |
|
1071 epoll_event events[16] = {{ 0 }}; |
|
1072 |
|
1073 int event_count; |
|
1074 { |
|
1075 PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent::Wait"); |
|
1076 if ((event_count = epoll_wait(epollfd, events, 16, mayWait ? -1 : 0)) <= 0) |
|
1077 return true; |
|
1078 } |
|
1079 |
|
1080 for (int i = 0; i < event_count; i++) |
|
1081 mHandlers[events[i].data.u32].run(); |
|
1082 |
|
1083 if (mDispatcher.get()) |
|
1084 mDispatcher->dispatchOnce(); |
|
1085 |
|
1086 // NativeEventCallback always schedules more if it needs it |
|
1087 // so we can coalesce these. |
|
1088 // See the implementation in nsBaseAppShell.cpp for more info |
|
1089 if (mNativeCallbackRequest) { |
|
1090 mNativeCallbackRequest = false; |
|
1091 NativeEventCallback(); |
|
1092 } |
|
1093 |
|
1094 if (gDrawRequest && mEnableDraw) { |
|
1095 gDrawRequest = false; |
|
1096 nsWindow::DoDraw(); |
|
1097 } |
|
1098 |
|
1099 return true; |
|
1100 } |
|
1101 |
|
1102 void |
|
1103 nsAppShell::NotifyNativeEvent() |
|
1104 { |
|
1105 write(signalfds[1], "w", 1); |
|
1106 } |
|
1107 |
|
1108 /* static */ void |
|
1109 nsAppShell::NotifyScreenInitialized() |
|
1110 { |
|
1111 gAppShell->InitInputDevices(); |
|
1112 |
|
1113 // Getting the instance of OrientationObserver to initialize it. |
|
1114 OrientationObserver::GetInstance(); |
|
1115 } |
|
1116 |
|
1117 /* static */ void |
|
1118 nsAppShell::NotifyScreenRotation() |
|
1119 { |
|
1120 gAppShell->mReaderPolicy->setDisplayInfo(); |
|
1121 gAppShell->mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO); |
|
1122 |
|
1123 hal::NotifyScreenConfigurationChange(nsScreenGonk::GetConfiguration()); |
|
1124 } |