summaryrefslogtreecommitdiff
path: root/widget/gonk
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gonk')
-rw-r--r--widget/gonk/GeckoTouchDispatcher.cpp358
-rw-r--r--widget/gonk/GeckoTouchDispatcher.h99
-rw-r--r--widget/gonk/GfxInfo.cpp194
-rw-r--r--widget/gonk/GfxInfo.h69
-rw-r--r--widget/gonk/GonkClipboardData.cpp75
-rw-r--r--widget/gonk/GonkClipboardData.h49
-rw-r--r--widget/gonk/GonkKeyMapping.h301
-rw-r--r--widget/gonk/GonkMemoryPressureMonitoring.cpp359
-rw-r--r--widget/gonk/GonkMemoryPressureMonitoring.h14
-rw-r--r--widget/gonk/GonkPermission.cpp195
-rw-r--r--widget/gonk/GonkPermission.h86
-rw-r--r--widget/gonk/HwcComposer2D.cpp971
-rw-r--r--widget/gonk/HwcComposer2D.h123
-rw-r--r--widget/gonk/HwcUtils.cpp169
-rw-r--r--widget/gonk/HwcUtils.h135
-rw-r--r--widget/gonk/OrientationObserver.cpp332
-rw-r--r--widget/gonk/OrientationObserver.h71
-rw-r--r--widget/gonk/ProcessOrientation.cpp519
-rw-r--r--widget/gonk/ProcessOrientation.h111
-rw-r--r--widget/gonk/WidgetTraceEvent.cpp96
-rw-r--r--widget/gonk/hwchal/HwcHAL.cpp214
-rw-r--r--widget/gonk/hwchal/HwcHAL.h70
-rw-r--r--widget/gonk/hwchal/HwcHALBase.h134
-rw-r--r--widget/gonk/libdisplay/BootAnimation.cpp726
-rw-r--r--widget/gonk/libdisplay/BootAnimation.h30
-rw-r--r--widget/gonk/libdisplay/DisplaySurface.h112
-rw-r--r--widget/gonk/libdisplay/FramebufferSurface.cpp207
-rw-r--r--widget/gonk/libdisplay/FramebufferSurface.h93
-rw-r--r--widget/gonk/libdisplay/GonkDisplay.h91
-rw-r--r--widget/gonk/libdisplay/GonkDisplayJB.cpp461
-rw-r--r--widget/gonk/libdisplay/GonkDisplayJB.h85
-rw-r--r--widget/gonk/libdisplay/GraphicBufferAlloc.cpp53
-rw-r--r--widget/gonk/libdisplay/GraphicBufferAlloc.h44
-rw-r--r--widget/gonk/libdisplay/VirtualDisplaySurface.cpp635
-rw-r--r--widget/gonk/libdisplay/VirtualDisplaySurface.h250
-rw-r--r--widget/gonk/libdisplay/moz.build59
-rw-r--r--widget/gonk/libui/EventHub.cpp1549
-rw-r--r--widget/gonk/libui/EventHub.h435
-rw-r--r--widget/gonk/libui/Input.cpp635
-rw-r--r--widget/gonk/libui/Input.h622
-rw-r--r--widget/gonk/libui/InputApplication.cpp42
-rw-r--r--widget/gonk/libui/InputApplication.h83
-rw-r--r--widget/gonk/libui/InputDevice.cpp184
-rw-r--r--widget/gonk/libui/InputDevice.h156
-rw-r--r--widget/gonk/libui/InputDispatcher.cpp4430
-rw-r--r--widget/gonk/libui/InputDispatcher.h1117
-rw-r--r--widget/gonk/libui/InputListener.cpp182
-rw-r--r--widget/gonk/libui/InputListener.h196
-rw-r--r--widget/gonk/libui/InputManager.cpp93
-rw-r--r--widget/gonk/libui/InputManager.h109
-rw-r--r--widget/gonk/libui/InputReader.cpp6510
-rw-r--r--widget/gonk/libui/InputReader.h1811
-rw-r--r--widget/gonk/libui/InputTransport.cpp957
-rw-r--r--widget/gonk/libui/InputTransport.h443
-rw-r--r--widget/gonk/libui/InputWindow.cpp64
-rw-r--r--widget/gonk/libui/InputWindow.h205
-rw-r--r--widget/gonk/libui/KeyCharacterMap.cpp1153
-rw-r--r--widget/gonk/libui/KeyCharacterMap.h257
-rw-r--r--widget/gonk/libui/KeyLayoutMap.cpp446
-rw-r--r--widget/gonk/libui/KeyLayoutMap.h117
-rw-r--r--widget/gonk/libui/Keyboard.cpp300
-rw-r--r--widget/gonk/libui/Keyboard.h122
-rw-r--r--widget/gonk/libui/KeycodeLabels.h380
-rw-r--r--widget/gonk/libui/PointerController.cpp604
-rw-r--r--widget/gonk/libui/PointerController.h266
-rw-r--r--widget/gonk/libui/PowerManager.h33
-rw-r--r--widget/gonk/libui/SpriteController.cpp515
-rw-r--r--widget/gonk/libui/SpriteController.h319
-rw-r--r--widget/gonk/libui/Tokenizer.cpp175
-rw-r--r--widget/gonk/libui/Tokenizer.h136
-rw-r--r--widget/gonk/libui/Trace.h64
-rw-r--r--widget/gonk/libui/VelocityControl.cpp110
-rw-r--r--widget/gonk/libui/VelocityControl.h107
-rw-r--r--widget/gonk/libui/VelocityTracker.cpp929
-rw-r--r--widget/gonk/libui/VelocityTracker.h269
-rw-r--r--widget/gonk/libui/VirtualKeyMap.cpp171
-rw-r--r--widget/gonk/libui/VirtualKeyMap.h81
-rw-r--r--widget/gonk/libui/android_input.h850
-rw-r--r--widget/gonk/libui/android_keycodes.h315
-rw-r--r--widget/gonk/libui/cutils_log.h569
-rw-r--r--widget/gonk/libui/cutils_trace.h276
-rw-r--r--widget/gonk/libui/linux_input.h1029
-rw-r--r--widget/gonk/libui/sha1.c289
-rw-r--r--widget/gonk/libui/sha1.h31
-rw-r--r--widget/gonk/moz.build96
-rw-r--r--widget/gonk/nativewindow/FakeSurfaceComposer.cpp703
-rw-r--r--widget/gonk/nativewindow/FakeSurfaceComposer.h175
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueue.h22
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueJB.cpp1036
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueJB.h653
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueKK.cpp1265
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueKK.h583
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.cpp193
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.h101
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.cpp559
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.h173
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.cpp243
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.h251
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueDefs.h36
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.cpp96
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.h94
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.cpp886
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.h216
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.cpp32
-rw-r--r--widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.h132
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseJB.cpp245
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseJB.h239
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseKK.cpp252
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseKK.h240
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseLL.cpp257
-rw-r--r--widget/gonk/nativewindow/GonkConsumerBaseLL.h245
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindow.h22
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowJB.cpp180
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowJB.h133
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowKK.cpp182
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowKK.h135
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowLL.cpp204
-rw-r--r--widget/gonk/nativewindow/GonkNativeWindowLL.h133
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumer.h20
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.cpp480
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.h228
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.cpp565
-rw-r--r--widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.h337
-rw-r--r--widget/gonk/nativewindow/moz.build104
-rw-r--r--widget/gonk/nsAppShell.cpp1087
-rw-r--r--widget/gonk/nsAppShell.h112
-rw-r--r--widget/gonk/nsClipboard.cpp366
-rw-r--r--widget/gonk/nsClipboard.h27
-rw-r--r--widget/gonk/nsIdleServiceGonk.cpp33
-rw-r--r--widget/gonk/nsIdleServiceGonk.h47
-rw-r--r--widget/gonk/nsLookAndFeel.cpp465
-rw-r--r--widget/gonk/nsLookAndFeel.h40
-rw-r--r--widget/gonk/nsScreenManagerGonk.cpp1081
-rw-r--r--widget/gonk/nsScreenManagerGonk.h228
-rw-r--r--widget/gonk/nsWidgetFactory.cpp128
-rw-r--r--widget/gonk/nsWindow.cpp744
-rw-r--r--widget/gonk/nsWindow.h154
137 files changed, 53584 insertions, 0 deletions
diff --git a/widget/gonk/GeckoTouchDispatcher.cpp b/widget/gonk/GeckoTouchDispatcher.cpp
new file mode 100644
index 0000000000..0b18c91a13
--- /dev/null
+++ b/widget/gonk/GeckoTouchDispatcher.cpp
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* Copyright 2014 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 "FrameMetrics.h"
+#include "GeckoProfiler.h"
+#include "GeckoTouchDispatcher.h"
+#include "InputData.h"
+#include "ProfilerMarkers.h"
+#include "base/basictypes.h"
+#include "gfxPrefs.h"
+#include "libui/Input.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "nsAppShell.h"
+#include "nsDebug.h"
+#include "nsThreadUtils.h"
+#include "nsWindow.h"
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/Timers.h>
+
+#undef LOG
+#define LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+
+// uncomment to print log resample data
+// #define LOG_RESAMPLE_DATA 1
+
+namespace mozilla {
+
+// Amount of time in MS before an input is considered expired.
+static const uint64_t kInputExpirationThresholdMs = 1000;
+
+static StaticRefPtr<GeckoTouchDispatcher> sTouchDispatcher;
+
+/* static */ GeckoTouchDispatcher*
+GeckoTouchDispatcher::GetInstance()
+{
+ if (!sTouchDispatcher) {
+ sTouchDispatcher = new GeckoTouchDispatcher();
+ ClearOnShutdown(&sTouchDispatcher);
+ }
+ return sTouchDispatcher;
+}
+
+GeckoTouchDispatcher::GeckoTouchDispatcher()
+ : mTouchQueueLock("GeckoTouchDispatcher::mTouchQueueLock")
+ , mHavePendingTouchMoves(false)
+ , mInflightNonMoveEvents(0)
+ , mTouchEventsFiltered(false)
+{
+ // Since GeckoTouchDispatcher is initialized when input is initialized
+ // and reads gfxPrefs, it is the first thing to touch gfxPrefs.
+ // The first thing to touch gfxPrefs MUST occur on the main thread and init
+ // the singleton
+ MOZ_ASSERT(sTouchDispatcher == nullptr);
+ MOZ_ASSERT(NS_IsMainThread());
+ gfxPrefs::GetSingleton();
+
+ mEnabledUniformityInfo = gfxPrefs::UniformityInfo();
+ mVsyncAdjust = TimeDuration::FromMilliseconds(gfxPrefs::TouchVsyncSampleAdjust());
+ mMaxPredict = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMaxPredict());
+ mMinDelta = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleMinDelta());
+ mOldTouchThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleOldTouchThreshold());
+ mDelayedVsyncThreshold = TimeDuration::FromMilliseconds(gfxPrefs::TouchResampleVsyncDelayThreshold());
+}
+
+void
+GeckoTouchDispatcher::SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler *aObserver)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ // We assume on b2g that there is only 1 CompositorBridgeParent
+ MOZ_ASSERT(mCompositorVsyncScheduler == nullptr);
+ mCompositorVsyncScheduler = aObserver;
+}
+
+void
+GeckoTouchDispatcher::NotifyVsync(TimeStamp aVsyncTimestamp)
+{
+ layers::APZThreadUtils::AssertOnControllerThread();
+ DispatchTouchMoveEvents(aVsyncTimestamp);
+}
+
+// Touch data timestamps are in milliseconds, aEventTime is in nanoseconds
+void
+GeckoTouchDispatcher::NotifyTouch(MultiTouchInput& aTouch, TimeStamp aEventTime)
+{
+ if (mCompositorVsyncScheduler) {
+ mCompositorVsyncScheduler->SetNeedsComposite();
+ }
+
+ if (aTouch.mType == MultiTouchInput::MULTITOUCH_MOVE) {
+ MutexAutoLock lock(mTouchQueueLock);
+ if (mInflightNonMoveEvents > 0) {
+ // If we have any pending non-move events, we shouldn't resample the
+ // move events because we might end up dispatching events out of order.
+ // Instead, fall back to a non-resampling in-order dispatch until we're
+ // done processing the non-move events.
+ layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod<MultiTouchInput>(
+ this, &GeckoTouchDispatcher::DispatchTouchEvent, aTouch));
+ return;
+ }
+
+ mTouchMoveEvents.push_back(aTouch);
+ mHavePendingTouchMoves = true;
+ } else {
+ { // scope lock
+ MutexAutoLock lock(mTouchQueueLock);
+ mInflightNonMoveEvents++;
+ }
+ layers::APZThreadUtils::RunOnControllerThread(NewRunnableMethod<MultiTouchInput>(
+ this, &GeckoTouchDispatcher::DispatchTouchNonMoveEvent, aTouch));
+ }
+}
+
+void
+GeckoTouchDispatcher::DispatchTouchNonMoveEvent(MultiTouchInput aInput)
+{
+ layers::APZThreadUtils::AssertOnControllerThread();
+
+ // Flush pending touch move events, if there are any
+ // (DispatchTouchMoveEvents will check the mHavePendingTouchMoves flag and
+ // bail out if there's nothing to be done).
+ NotifyVsync(TimeStamp::Now());
+ DispatchTouchEvent(aInput);
+
+ { // scope lock
+ MutexAutoLock lock(mTouchQueueLock);
+ mInflightNonMoveEvents--;
+ MOZ_ASSERT(mInflightNonMoveEvents >= 0);
+ }
+}
+
+void
+GeckoTouchDispatcher::DispatchTouchMoveEvents(TimeStamp aVsyncTime)
+{
+ MultiTouchInput touchMove;
+
+ {
+ MutexAutoLock lock(mTouchQueueLock);
+ if (!mHavePendingTouchMoves) {
+ return;
+ }
+ mHavePendingTouchMoves = false;
+
+ int touchCount = mTouchMoveEvents.size();
+ TimeDuration vsyncTouchDiff = aVsyncTime - mTouchMoveEvents.back().mTimeStamp;
+ // The delay threshold is a positive pref, but we're testing to see if the
+ // vsync time is delayed from the touch, so add a negative sign.
+ bool isDelayedVsyncEvent = vsyncTouchDiff < -mDelayedVsyncThreshold;
+ bool isOldTouch = vsyncTouchDiff > mOldTouchThreshold;
+ bool resample = (touchCount > 1) && !isDelayedVsyncEvent && !isOldTouch;
+
+ if (!resample) {
+ touchMove = mTouchMoveEvents.back();
+ mTouchMoveEvents.clear();
+ if (!isDelayedVsyncEvent && !isOldTouch) {
+ mTouchMoveEvents.push_back(touchMove);
+ }
+ } else {
+ ResampleTouchMoves(touchMove, aVsyncTime);
+ }
+ }
+
+ DispatchTouchEvent(touchMove);
+}
+
+static int
+Interpolate(int start, int end, TimeDuration aFrameDiff, TimeDuration aTouchDiff)
+{
+ return start + (((end - start) * aFrameDiff.ToMicroseconds()) / aTouchDiff.ToMicroseconds());
+}
+
+static const SingleTouchData&
+GetTouchByID(const SingleTouchData& aCurrentTouch, MultiTouchInput& aOtherTouch)
+{
+ int32_t index = aOtherTouch.IndexOfTouch(aCurrentTouch.mIdentifier);
+ if (index < 0) {
+ // We can have situations where a previous touch event had 2 fingers
+ // and we lift 1 finger off. In those cases, we won't find the touch event
+ // with given id, so just return the current touch, which will be resampled
+ // without modification and dispatched.
+ return aCurrentTouch;
+ }
+ return aOtherTouch.mTouches[index];
+}
+
+
+// aTouchDiff is the duration between the base and current touch times
+// aFrameDiff is the duration between the base and the time we're resampling to
+static void
+ResampleTouch(MultiTouchInput& aOutTouch,
+ MultiTouchInput& aBase, MultiTouchInput& aCurrent,
+ TimeDuration aFrameDiff, TimeDuration aTouchDiff)
+{
+ aOutTouch = aCurrent;
+
+ // Make sure we only resample the correct finger.
+ for (size_t i = 0; i < aOutTouch.mTouches.Length(); i++) {
+ const SingleTouchData& current = aCurrent.mTouches[i];
+ const SingleTouchData& base = GetTouchByID(current, aBase);
+
+ const ScreenIntPoint& baseTouchPoint = base.mScreenPoint;
+ const ScreenIntPoint& currentTouchPoint = current.mScreenPoint;
+
+ ScreenIntPoint newSamplePoint;
+ newSamplePoint.x = Interpolate(baseTouchPoint.x, currentTouchPoint.x, aFrameDiff, aTouchDiff);
+ newSamplePoint.y = Interpolate(baseTouchPoint.y, currentTouchPoint.y, aFrameDiff, aTouchDiff);
+
+ aOutTouch.mTouches[i].mScreenPoint = newSamplePoint;
+
+#ifdef LOG_RESAMPLE_DATA
+ const char* type = "extrapolate";
+ if (aFrameDiff < aTouchDiff) {
+ type = "interpolate";
+ }
+
+ float alpha = aFrameDiff / aTouchDiff;
+ LOG("%s base (%d, %d), current (%d, %d) to (%d, %d) alpha %f, touch diff %d, frame diff %d\n",
+ type,
+ baseTouchPoint.x, baseTouchPoint.y,
+ currentTouchPoint.x, currentTouchPoint.y,
+ newSamplePoint.x, newSamplePoint.y,
+ alpha, (int)aTouchDiff.ToMilliseconds(), (int)aFrameDiff.ToMilliseconds());
+#endif
+ }
+}
+
+/*
+ * +> Base touch (The touch before current touch)
+ * |
+ * | +> Current touch (Latest touch)
+ * | |
+ * | | +> Maximum resample time
+ * | | |
+ * +-----+------+--------------------> Time
+ * ^ ^
+ * | |
+ * +------+--> Potential vsync events which the touches are resampled to
+ * | |
+ * | +> Extrapolation
+ * |
+ * +> Interpolation
+ */
+
+void
+GeckoTouchDispatcher::ResampleTouchMoves(MultiTouchInput& aOutTouch, TimeStamp aVsyncTime)
+{
+ MOZ_RELEASE_ASSERT(mTouchMoveEvents.size() >= 2);
+ mTouchQueueLock.AssertCurrentThreadOwns();
+
+ MultiTouchInput currentTouch = mTouchMoveEvents.back();
+ mTouchMoveEvents.pop_back();
+ MultiTouchInput baseTouch = mTouchMoveEvents.back();
+ mTouchMoveEvents.clear();
+ mTouchMoveEvents.push_back(currentTouch);
+
+ TimeStamp sampleTime = aVsyncTime - mVsyncAdjust;
+ TimeDuration touchDiff = currentTouch.mTimeStamp - baseTouch.mTimeStamp;
+
+ if (touchDiff < mMinDelta) {
+ aOutTouch = currentTouch;
+ #ifdef LOG_RESAMPLE_DATA
+ LOG("The touches are too close, skip resampling\n");
+ #endif
+ return;
+ }
+
+ if (currentTouch.mTimeStamp < sampleTime) {
+ TimeDuration maxResampleTime = std::min(touchDiff / int64_t(2), mMaxPredict);
+ TimeStamp maxTimestamp = currentTouch.mTimeStamp + maxResampleTime;
+ if (sampleTime > maxTimestamp) {
+ sampleTime = maxTimestamp;
+ #ifdef LOG_RESAMPLE_DATA
+ LOG("Overshot extrapolation time, adjusting sample time\n");
+ #endif
+ }
+ }
+
+ ResampleTouch(aOutTouch, baseTouch, currentTouch, sampleTime - baseTouch.mTimeStamp, touchDiff);
+
+ // Both mTimeStamp and mTime are being updated to sampleTime here.
+ // mTime needs to be updated using a delta since TimeStamp doesn't
+ // provide a way to obtain a raw value.
+ aOutTouch.mTime += (sampleTime - aOutTouch.mTimeStamp).ToMilliseconds();
+ aOutTouch.mTimeStamp = sampleTime;
+}
+
+static bool
+IsExpired(const MultiTouchInput& aTouch)
+{
+ // No pending events, the filter state can be updated.
+ uint64_t timeNowMs = systemTime(SYSTEM_TIME_MONOTONIC) / 1000000;
+ return (timeNowMs - aTouch.mTime) > kInputExpirationThresholdMs;
+}
+void
+GeckoTouchDispatcher::DispatchTouchEvent(MultiTouchInput aMultiTouch)
+{
+ if ((aMultiTouch.mType == MultiTouchInput::MULTITOUCH_END ||
+ aMultiTouch.mType == MultiTouchInput::MULTITOUCH_CANCEL) &&
+ aMultiTouch.mTouches.Length() == 1) {
+ MutexAutoLock lock(mTouchQueueLock);
+ mTouchMoveEvents.clear();
+ } else if (aMultiTouch.mType == MultiTouchInput::MULTITOUCH_START &&
+ aMultiTouch.mTouches.Length() == 1) {
+ mTouchEventsFiltered = IsExpired(aMultiTouch);
+ }
+
+ if (mTouchEventsFiltered) {
+ return;
+ }
+
+ nsWindow::DispatchTouchInput(aMultiTouch);
+
+ if (mEnabledUniformityInfo && profiler_is_active()) {
+ const char* touchAction = "Invalid";
+ switch (aMultiTouch.mType) {
+ case MultiTouchInput::MULTITOUCH_START:
+ touchAction = "Touch_Event_Down";
+ break;
+ case MultiTouchInput::MULTITOUCH_MOVE:
+ touchAction = "Touch_Event_Move";
+ break;
+ case MultiTouchInput::MULTITOUCH_END:
+ case MultiTouchInput::MULTITOUCH_CANCEL:
+ touchAction = "Touch_Event_Up";
+ break;
+ case MultiTouchInput::MULTITOUCH_SENTINEL:
+ MOZ_ASSERT_UNREACHABLE("Invalid MultTouchInput.");
+ break;
+ }
+
+ const ScreenIntPoint& touchPoint = aMultiTouch.mTouches[0].mScreenPoint;
+ TouchDataPayload* payload = new TouchDataPayload(touchPoint);
+ PROFILER_MARKER_PAYLOAD(touchAction, payload);
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/GeckoTouchDispatcher.h b/widget/gonk/GeckoTouchDispatcher.h
new file mode 100644
index 0000000000..3c7acd0e31
--- /dev/null
+++ b/widget/gonk/GeckoTouchDispatcher.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* Copyright 2014 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.
+ */
+
+#ifndef GECKO_TOUCH_INPUT_DISPATCHER_h
+#define GECKO_TOUCH_INPUT_DISPATCHER_h
+
+#include "InputData.h"
+#include "Units.h"
+#include "mozilla/Mutex.h"
+#include <vector>
+#include "mozilla/RefPtr.h"
+
+class nsIWidget;
+
+namespace mozilla {
+namespace layers {
+class CompositorVsyncScheduler;
+}
+
+// Used to resample touch events whenever a vsync event occurs. It batches
+// touch moves and on every vsync, resamples the touch position to create smooth
+// scrolls. We use the Android touch resample algorithm. It uses a combination of
+// extrapolation and interpolation. The algorithm takes the vsync time and
+// subtracts mVsyncAdjust time in ms and creates a sample time. All touch events are
+// relative to this sample time. If the last touch event occurs AFTER this
+// sample time, interpolate the last two touch events. If the last touch event occurs BEFORE
+// this sample time, we extrapolate the last two touch events to the sample
+// time. The magic numbers defined as constants are taken from android
+// InputTransport.cpp.
+class GeckoTouchDispatcher final
+{
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoTouchDispatcher)
+
+public:
+ static GeckoTouchDispatcher* GetInstance();
+ void NotifyTouch(MultiTouchInput& aTouch, TimeStamp aEventTime);
+ void DispatchTouchEvent(MultiTouchInput aMultiTouch);
+ void DispatchTouchNonMoveEvent(MultiTouchInput aInput);
+ void DispatchTouchMoveEvents(TimeStamp aVsyncTime);
+ void NotifyVsync(TimeStamp aVsyncTimestamp);
+ void SetCompositorVsyncScheduler(layers::CompositorVsyncScheduler* aObserver);
+
+protected:
+ ~GeckoTouchDispatcher() {}
+
+private:
+ GeckoTouchDispatcher();
+ void ResampleTouchMoves(MultiTouchInput& aOutTouch, TimeStamp vsyncTime);
+ void SendTouchEvent(MultiTouchInput& aData);
+ void DispatchMouseEvent(MultiTouchInput& aMultiTouch,
+ bool aForwardToChildren);
+
+ // mTouchQueueLock is used to protect the vector and state below
+ // as it is accessed on multiple threads.
+ Mutex mTouchQueueLock;
+ std::vector<MultiTouchInput> mTouchMoveEvents;
+ bool mHavePendingTouchMoves;
+ int mInflightNonMoveEvents;
+ // end stuff protected by mTouchQueueLock
+
+ bool mResamplingEnabled;
+ bool mTouchEventsFiltered;
+ bool mEnabledUniformityInfo;
+
+ // All times below are in nanoseconds
+ TimeDuration mVsyncAdjust; // Time from vsync we create sample times from
+ TimeDuration mMaxPredict; // How far into the future we're allowed to extrapolate
+ TimeDuration mMinDelta; // Minimal time difference between touches for resampling
+
+ // Amount of time between vsync and the last event that is required before we
+ // resample
+ TimeDuration mMinResampleTime;
+
+ // Threshold if a vsync event runs too far behind touch events
+ TimeDuration mDelayedVsyncThreshold;
+
+ // How far ahead can vsync events get ahead of touch events.
+ TimeDuration mOldTouchThreshold;
+
+ RefPtr<layers::CompositorVsyncScheduler> mCompositorVsyncScheduler;
+};
+
+} // namespace mozilla
+
+#endif // GECKO_TOUCH_INPUT_DISPATCHER_h
diff --git a/widget/gonk/GfxInfo.cpp b/widget/gonk/GfxInfo.cpp
new file mode 100644
index 0000000000..7ddd390389
--- /dev/null
+++ b/widget/gonk/GfxInfo.cpp
@@ -0,0 +1,194 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GfxInfo.h"
+
+using namespace mozilla::widget;
+
+/* GetD2DEnabled and GetDwriteEnabled shouldn't be called until after gfxPlatform initialization
+ * has occurred because they depend on it for information. (See bug 591561) */
+nsresult
+GfxInfo::GetD2DEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+GfxInfo::GetDWriteEnabled(bool *aEnabled)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString & aDwriteVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString & aCleartypeParams)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString & aAdapterDescription)
+{
+ aAdapterDescription.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString & aAdapterDescription)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(nsAString & aAdapterRAM)
+{
+ aAdapterRAM.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(nsAString & aAdapterRAM)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString & aAdapterDriver)
+{
+ aAdapterDriver.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString & aAdapterDriver)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString & aAdapterDriverVersion)
+{
+ aAdapterDriverVersion.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString & aAdapterDriverDate)
+{
+ aAdapterDriverDate.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString & aAdapterDriverDate)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString & aAdapterVendorID)
+{
+ aAdapterVendorID.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString & aAdapterVendorID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString & aAdapterDeviceID)
+{
+ aAdapterDeviceID.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString & aAdapterDeviceID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString & aAdapterSubsysID)
+{
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active)
+{
+ return NS_ERROR_FAILURE;
+}
+
+const nsTArray<GfxDriverInfo>&
+GfxInfo::GetGfxDriverInfo()
+{
+ return *mDriverInfo;
+}
+
+uint32_t GfxInfo::OperatingSystemVersion()
+{
+ return 0;
+}
+
+nsresult
+GfxInfo::GetFeatureStatusImpl(int32_t /*aFeature*/,
+ int32_t *aStatus,
+ nsAString & /*aSuggestedDriverVersion*/,
+ const nsTArray<GfxDriverInfo>& /*aDriverInfo*/,
+ nsACString& aFailureId,
+ OperatingSystem* /*aOS*/ /* = nullptr */)
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+
+ return NS_OK;
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString &)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString &)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString &)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t)
+{
+ return NS_OK;
+}
+
+#endif
diff --git a/widget/gonk/GfxInfo.h b/widget/gonk/GfxInfo.h
new file mode 100644
index 0000000000..61494713fc
--- /dev/null
+++ b/widget/gonk/GfxInfo.h
@@ -0,0 +1,69 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __mozilla_widget_GfxInfo_h__
+#define __mozilla_widget_GfxInfo_h__
+
+#include "GfxInfoBase.h"
+#include "GfxDriverInfo.h"
+
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo : public GfxInfoBase
+{
+public:
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool *aD2DEnabled);
+ NS_IMETHOD GetDWriteEnabled(bool *aDWriteEnabled);
+ NS_IMETHOD GetDWriteVersion(nsAString & aDwriteVersion);
+ NS_IMETHOD GetCleartypeParameters(nsAString & aCleartypeParams);
+ NS_IMETHOD GetAdapterDescription(nsAString & aAdapterDescription);
+ NS_IMETHOD GetAdapterDriver(nsAString & aAdapterDriver);
+ NS_IMETHOD GetAdapterVendorID(nsAString & aAdapterVendorID);
+ NS_IMETHOD GetAdapterDeviceID(nsAString & aAdapterDeviceID);
+ NS_IMETHOD GetAdapterSubsysID(nsAString & aAdapterSubsysID);
+ NS_IMETHOD GetAdapterRAM(nsAString & aAdapterRAM);
+ NS_IMETHOD GetAdapterDriverVersion(nsAString & aAdapterDriverVersion);
+ NS_IMETHOD GetAdapterDriverDate(nsAString & aAdapterDriverDate);
+ NS_IMETHOD GetAdapterDescription2(nsAString & aAdapterDescription);
+ NS_IMETHOD GetAdapterDriver2(nsAString & aAdapterDriver);
+ NS_IMETHOD GetAdapterVendorID2(nsAString & aAdapterVendorID);
+ NS_IMETHOD GetAdapterDeviceID2(nsAString & aAdapterDeviceID);
+ NS_IMETHOD GetAdapterSubsysID2(nsAString & aAdapterSubsysID);
+ NS_IMETHOD GetAdapterRAM2(nsAString & aAdapterRAM);
+ NS_IMETHOD GetAdapterDriverVersion2(nsAString & aAdapterDriverVersion);
+ NS_IMETHOD GetAdapterDriverDate2(nsAString & aAdapterDriverDate);
+ NS_IMETHOD GetIsGPU2Active(bool *aIsGPU2Active);
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+ using GfxInfoBase::GetWebGLParameter;
+
+ virtual uint32_t OperatingSystemVersion() override;
+
+#ifdef DEBUG
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+protected:
+
+ virtual nsresult GetFeatureStatusImpl(int32_t aFeature,
+ int32_t *aStatus,
+ nsAString & aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo,
+ nsACString& aFailureId,
+ OperatingSystem* aOS = nullptr);
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __mozilla_widget_GfxInfo_h__ */
diff --git a/widget/gonk/GonkClipboardData.cpp b/widget/gonk/GonkClipboardData.cpp
new file mode 100644
index 0000000000..ced6422a59
--- /dev/null
+++ b/widget/gonk/GonkClipboardData.cpp
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GonkClipboardData.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+
+namespace mozilla {
+
+void
+GonkClipboardData::SetText(const nsAString &aText)
+{
+ mPlain = aText;
+}
+
+bool
+GonkClipboardData::HasText() const
+{
+ return !mPlain.IsEmpty();
+}
+
+const nsAString&
+GonkClipboardData::GetText() const
+{
+ return mPlain;
+}
+
+void
+GonkClipboardData::SetHTML(const nsAString &aHTML)
+{
+ mHTML = aHTML;
+}
+
+bool
+GonkClipboardData::HasHTML() const
+{
+ return !mHTML.IsEmpty();
+}
+
+const nsAString&
+GonkClipboardData::GetHTML() const
+{
+ return mHTML;
+}
+
+void
+GonkClipboardData::SetImage(gfx::DataSourceSurface* aDataSource)
+{
+ // Clone a new DataSourceSurface and store it.
+ mImage = gfx::CreateDataSourceSurfaceByCloning(aDataSource);
+}
+
+bool
+GonkClipboardData::HasImage() const
+{
+ return static_cast<bool>(mImage);
+}
+
+already_AddRefed<gfx::DataSourceSurface>
+GonkClipboardData::GetImage() const
+{
+ // Return cloned DataSourceSurface.
+ RefPtr<gfx::DataSourceSurface> cloned = gfx::CreateDataSourceSurfaceByCloning(mImage);
+ return cloned.forget();
+}
+
+void
+GonkClipboardData::Clear()
+{
+ mPlain.Truncate(0);
+ mHTML.Truncate(0);
+ mImage = nullptr;
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/GonkClipboardData.h b/widget/gonk/GonkClipboardData.h
new file mode 100644
index 0000000000..8bc1f1c9c4
--- /dev/null
+++ b/widget/gonk/GonkClipboardData.h
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GonkClipboardData
+#define mozilla_GonkClipboardData
+
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+namespace gfx {
+class DataSourceSurface;
+}
+
+class GonkClipboardData final
+{
+public:
+ explicit GonkClipboardData() = default;
+ ~GonkClipboardData() = default;
+
+ // For text/plain
+ void SetText(const nsAString &aText);
+ bool HasText() const;
+ const nsAString& GetText() const;
+
+ // For text/html
+ void SetHTML(const nsAString &aHTML);
+ bool HasHTML() const;
+ const nsAString& GetHTML() const;
+
+ // For images
+ void SetImage(gfx::DataSourceSurface* aDataSource);
+ bool HasImage() const;
+ already_AddRefed<gfx::DataSourceSurface> GetImage() const;
+
+ // For other APIs
+ void Clear();
+
+private:
+ nsAutoString mPlain;
+ nsAutoString mHTML;
+ RefPtr<gfx::DataSourceSurface> mImage;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_GonkClipboardData
diff --git a/widget/gonk/GonkKeyMapping.h b/widget/gonk/GonkKeyMapping.h
new file mode 100644
index 0000000000..d5d4e7a0b7
--- /dev/null
+++ b/widget/gonk/GonkKeyMapping.h
@@ -0,0 +1,301 @@
+/* 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.
+ */
+
+#ifndef GONKKEYMAPPING_H
+#define GONKKEYMAPPING_H
+
+#include "libui/android_keycodes.h"
+#include "mozilla/EventForwards.h"
+
+namespace mozilla {
+namespace widget {
+
+/* See libui/KeycodeLabels.h for the mapping */
+static const unsigned long kKeyMapping[] = {
+ 0,
+ 0, // SOFT_LEFT
+ 0, // SOFT_RIGHT
+ NS_VK_HOME, // HOME
+ NS_VK_ESCAPE, // BACK
+ 0, // CALL
+ NS_VK_SLEEP, // ENDCALL
+ NS_VK_0,
+ NS_VK_1,
+ NS_VK_2,
+ NS_VK_3,
+ NS_VK_4,
+ NS_VK_5,
+ NS_VK_6,
+ NS_VK_7,
+ NS_VK_8,
+ NS_VK_9,
+ NS_VK_ASTERISK,
+ NS_VK_HASH,
+ NS_VK_UP,
+ NS_VK_DOWN,
+ NS_VK_LEFT,
+ NS_VK_RIGHT,
+ NS_VK_RETURN,
+ NS_VK_VOLUME_UP,
+ NS_VK_VOLUME_DOWN,
+ NS_VK_SLEEP, // POWER
+ NS_VK_PRINTSCREEN, // CAMERA
+ NS_VK_CLEAR,
+ NS_VK_A,
+ NS_VK_B,
+ NS_VK_C,
+ NS_VK_D,
+ NS_VK_E,
+ NS_VK_F,
+ NS_VK_G,
+ NS_VK_H,
+ NS_VK_I,
+ NS_VK_J,
+ NS_VK_K,
+ NS_VK_L,
+ NS_VK_M,
+ NS_VK_N,
+ NS_VK_O,
+ NS_VK_P,
+ NS_VK_Q,
+ NS_VK_R,
+ NS_VK_S,
+ NS_VK_T,
+ NS_VK_U,
+ NS_VK_V,
+ NS_VK_W,
+ NS_VK_X,
+ NS_VK_Y,
+ NS_VK_Z,
+ NS_VK_COMMA,
+ NS_VK_PERIOD,
+ 0,
+ 0,
+ 0,
+ 0,
+ NS_VK_TAB,
+ NS_VK_SPACE,
+ NS_VK_META, // SYM
+ 0, // EXPLORER
+ 0, // ENVELOPE
+ NS_VK_RETURN, // ENTER
+ NS_VK_BACK,
+ NS_VK_BACK_QUOTE, // GRAVE
+ NS_VK_HYPHEN_MINUS,
+ NS_VK_EQUALS,
+ NS_VK_OPEN_BRACKET,
+ NS_VK_CLOSE_BRACKET,
+ NS_VK_BACK_SLASH,
+ NS_VK_SEMICOLON,
+ NS_VK_QUOTE,
+ NS_VK_SLASH,
+ NS_VK_AT,
+ 0, // NUM
+ NS_VK_F1, // HEADSETHOOK
+ 0, // FOCUS
+ NS_VK_PLUS,
+ NS_VK_CONTEXT_MENU,
+ 0, // NOTIFICATION
+ NS_VK_F5, // SEARCH
+ 0, // MEDIA_PLAY_PAUSE
+ 0, // MEDIA_STOP
+ 0, // MEDIA_NEXT
+ 0, // MEDIA_PREVIOUS
+ 0, // MEDIA_REWIND
+ 0, // MEDIA_FAST_FORWARD
+ 0, // MUTE
+ NS_VK_PAGE_UP,
+ NS_VK_PAGE_DOWN,
+ 0, // PICTSYMBOLS
+ 0, // SWITCH_CHARSET
+ 0, // BUTTON_A
+ 0, // BUTTON_B
+ 0, // BUTTON_C
+ 0, // BUTTON_X
+ 0, // BUTTON_Y
+ 0, // BUTTON_Z
+ 0, // BUTTON_L1
+ 0, // BUTTON_R1
+ 0, // BUTTON_L2
+ 0, // BUTTON_R2
+ 0, // BUTTON_THUMBL
+ 0, // BUTTON_THUMBR
+ 0, // BUTTON_START
+ 0, // BUTTON_SELECT
+ 0, // BUTTON_MODE
+ NS_VK_ESCAPE,
+ NS_VK_DELETE,
+ 0, // CTRL_LEFT
+ 0, // CTRL_RIGHT
+ NS_VK_CAPS_LOCK,
+ NS_VK_SCROLL_LOCK,
+ 0, // META_LEFT
+ 0, // META_RIGHT
+ 0, // FUNCTION
+ 0, // SYSRQ
+ 0, // BREAK
+ NS_VK_HOME, // MOVE_HOME
+ NS_VK_END,
+ NS_VK_INSERT,
+ 0, // FORWARD
+ 0, // MEDIA_PLAY
+ 0, // MEDIA_PAUSE
+ 0, // MEDIA_CLOSE
+ 0, // MEDIA_EJECT
+ 0, // MEDIA_RECORD
+ NS_VK_F1,
+ NS_VK_F2,
+ NS_VK_F3,
+ NS_VK_F4,
+ NS_VK_F5,
+ NS_VK_F6,
+ NS_VK_F7,
+ NS_VK_F8,
+ NS_VK_F9,
+ NS_VK_F10,
+ NS_VK_F11,
+ NS_VK_F12,
+ NS_VK_NUM_LOCK,
+ NS_VK_NUMPAD0,
+ NS_VK_NUMPAD1,
+ NS_VK_NUMPAD2,
+ NS_VK_NUMPAD3,
+ NS_VK_NUMPAD4,
+ NS_VK_NUMPAD5,
+ NS_VK_NUMPAD6,
+ NS_VK_NUMPAD7,
+ NS_VK_NUMPAD8,
+ NS_VK_NUMPAD9,
+ NS_VK_DIVIDE,
+ NS_VK_MULTIPLY,
+ NS_VK_SUBTRACT,
+ NS_VK_ADD,
+ NS_VK_PERIOD,
+ NS_VK_COMMA,
+ NS_VK_RETURN,
+ NS_VK_EQUALS,
+ 0, // NUMPAD_LEFT_PAREN
+ 0, // NUMPAD_RIGHT_PAREN
+ NS_VK_VOLUME_MUTE,
+ // There are more but we don't map them
+};
+
+static KeyNameIndex GetKeyNameIndex(int aKeyCode)
+{
+ switch (aKeyCode) {
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ case AKEYCODE_0:
+ case AKEYCODE_1:
+ case AKEYCODE_2:
+ case AKEYCODE_3:
+ case AKEYCODE_4:
+ case AKEYCODE_5:
+ case AKEYCODE_6:
+ case AKEYCODE_7:
+ case AKEYCODE_8:
+ case AKEYCODE_9:
+ case AKEYCODE_STAR:
+ case AKEYCODE_POUND:
+ case AKEYCODE_A:
+ case AKEYCODE_B:
+ case AKEYCODE_C:
+ case AKEYCODE_D:
+ case AKEYCODE_E:
+ case AKEYCODE_F:
+ case AKEYCODE_G:
+ case AKEYCODE_H:
+ case AKEYCODE_I:
+ case AKEYCODE_J:
+ case AKEYCODE_K:
+ case AKEYCODE_L:
+ case AKEYCODE_M:
+ case AKEYCODE_N:
+ case AKEYCODE_O:
+ case AKEYCODE_P:
+ case AKEYCODE_Q:
+ case AKEYCODE_R:
+ case AKEYCODE_S:
+ case AKEYCODE_T:
+ case AKEYCODE_U:
+ case AKEYCODE_V:
+ case AKEYCODE_W:
+ case AKEYCODE_X:
+ case AKEYCODE_Y:
+ case AKEYCODE_Z:
+ case AKEYCODE_COMMA:
+ case AKEYCODE_PERIOD:
+ case AKEYCODE_SPACE:
+ case AKEYCODE_GRAVE:
+ case AKEYCODE_MINUS:
+ case AKEYCODE_EQUALS:
+ case AKEYCODE_LEFT_BRACKET:
+ case AKEYCODE_RIGHT_BRACKET:
+ case AKEYCODE_BACKSLASH:
+ case AKEYCODE_SEMICOLON:
+ case AKEYCODE_APOSTROPHE:
+ case AKEYCODE_SLASH:
+ case AKEYCODE_AT:
+ case AKEYCODE_PLUS:
+ case AKEYCODE_NUMPAD_0:
+ case AKEYCODE_NUMPAD_1:
+ case AKEYCODE_NUMPAD_2:
+ case AKEYCODE_NUMPAD_3:
+ case AKEYCODE_NUMPAD_4:
+ case AKEYCODE_NUMPAD_5:
+ case AKEYCODE_NUMPAD_6:
+ case AKEYCODE_NUMPAD_7:
+ case AKEYCODE_NUMPAD_8:
+ case AKEYCODE_NUMPAD_9:
+ case AKEYCODE_NUMPAD_DIVIDE:
+ case AKEYCODE_NUMPAD_MULTIPLY:
+ case AKEYCODE_NUMPAD_SUBTRACT:
+ case AKEYCODE_NUMPAD_ADD:
+ case AKEYCODE_NUMPAD_DOT:
+ case AKEYCODE_NUMPAD_COMMA:
+ case AKEYCODE_NUMPAD_EQUALS:
+ case AKEYCODE_NUMPAD_LEFT_PAREN:
+ case AKEYCODE_NUMPAD_RIGHT_PAREN:
+ return KEY_NAME_INDEX_USE_STRING;
+
+ default:
+ return KEY_NAME_INDEX_Unidentified;
+ }
+}
+
+static CodeNameIndex GetCodeNameIndex(int aScanCode)
+{
+ switch (aScanCode) {
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ return CODE_NAME_INDEX_UNKNOWN;
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* GONKKEYMAPPING_H */
diff --git a/widget/gonk/GonkMemoryPressureMonitoring.cpp b/widget/gonk/GonkMemoryPressureMonitoring.cpp
new file mode 100644
index 0000000000..0fafb37cf5
--- /dev/null
+++ b/widget/gonk/GonkMemoryPressureMonitoring.cpp
@@ -0,0 +1,359 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <android/log.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/sysinfo.h>
+
+#include "GonkMemoryPressureMonitoring.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProcessPriorityManager.h"
+#include "mozilla/Services.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsMemoryPressure.h"
+#include "nsPrintfCString.h"
+#include "nsThreadUtils.h"
+
+#define LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "GonkMemoryPressure" , ## args)
+
+using namespace mozilla;
+
+namespace {
+
+/**
+ * MemoryPressureWatcher watches sysfs from its own thread to notice when the
+ * system is under memory pressure. When we observe memory pressure, we use
+ * MemoryPressureRunnable to notify observers that they should release memory.
+ *
+ * When the system is under memory pressure, we don't want to constantly fire
+ * memory-pressure events. So instead, we try to detect when sysfs indicates
+ * that we're no longer under memory pressure, and only then start firing events
+ * again.
+ *
+ * (This is a bit problematic because we can't poll() to detect when we're no
+ * longer under memory pressure; instead we have to periodically read the sysfs
+ * node. If we remain under memory pressure for a long time, this means we'll
+ * continue waking up to read from the node for a long time, potentially wasting
+ * battery life. Hopefully we don't hit this case in practice! We write to
+ * logcat each time we go around this loop so it's at least noticable.)
+ *
+ * Shutting down safely is a bit of a chore. XPCOM won't shut down until all
+ * threads exit, so we need to exit the Run() method below on shutdown. But our
+ * thread might be blocked in one of two situations: We might be poll()'ing the
+ * sysfs node waiting for memory pressure to occur, or we might be asleep
+ * waiting to read() the sysfs node to see if we're no longer under memory
+ * pressure.
+ *
+ * To let us wake up from the poll(), we poll() not just the sysfs node but also
+ * a pipe, which we write to on shutdown. To let us wake up from sleeping
+ * between read()s, we sleep by Wait()'ing on a monitor, which we notify on
+ * shutdown.
+ */
+class MemoryPressureWatcher final
+ : public nsIRunnable
+ , public nsIObserver
+{
+public:
+ MemoryPressureWatcher()
+ : mMonitor("MemoryPressureWatcher")
+ , mLowMemTriggerKB(0)
+ , mPageSize(0)
+ , mShuttingDown(false)
+ , mTriggerFd(-1)
+ , mShutdownPipeRead(-1)
+ , mShutdownPipeWrite(-1)
+ {
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsresult Init()
+ {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ NS_ENSURE_STATE(os);
+
+ // The observer service holds us alive.
+ os->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, /* ownsWeak */ false);
+
+ // Initialize the internal state
+ mPageSize = sysconf(_SC_PAGESIZE);
+ ReadPrefs();
+ nsresult rv = OpenFiles();
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetLowMemTrigger(mSoftLowMemTriggerKB);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+ {
+ MOZ_ASSERT(strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0);
+ LOG("Observed XPCOM shutdown.");
+
+ MonitorAutoLock lock(mMonitor);
+ mShuttingDown = true;
+ mMonitor.Notify();
+
+ int rv;
+ do {
+ // Write something to the pipe; doesn't matter what.
+ uint32_t dummy = 0;
+ rv = write(mShutdownPipeWrite, &dummy, sizeof(dummy));
+ } while(rv == -1 && errno == EINTR);
+
+ return NS_OK;
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ int triggerResetTimeout = -1;
+ bool memoryPressure;
+ nsresult rv = CheckForMemoryPressure(&memoryPressure);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (true) {
+ // Wait for a notification on mTriggerFd or for data to be written to
+ // mShutdownPipeWrite. (poll(mTriggerFd, POLLPRI) blocks until we're
+ // under memory pressure or until we time out, the time out is used
+ // to adjust the trigger level after a memory pressure event.)
+ struct pollfd pollfds[2];
+ pollfds[0].fd = mTriggerFd;
+ pollfds[0].events = POLLPRI;
+ pollfds[1].fd = mShutdownPipeRead;
+ pollfds[1].events = POLLIN;
+
+ int pollRv = MOZ_TEMP_FAILURE_RETRY(
+ poll(pollfds, ArrayLength(pollfds), triggerResetTimeout)
+ );
+
+ if (pollRv == 0) {
+ // Timed out, adjust the trigger and update the timeout.
+ triggerResetTimeout = AdjustTrigger(triggerResetTimeout);
+ continue;
+ }
+
+ if (pollfds[1].revents) {
+ // Something was written to our shutdown pipe; we're outta here.
+ LOG("shutting down (1)");
+ return NS_OK;
+ }
+
+ // If pollfds[1] isn't happening, pollfds[0] ought to be!
+ if (!(pollfds[0].revents & POLLPRI)) {
+ LOG("Unexpected revents value after poll(): %d. "
+ "Shutting down GonkMemoryPressureMonitoring.", pollfds[0].revents);
+ return NS_ERROR_FAILURE;
+ }
+
+ // POLLPRI on mTriggerFd indicates that we're in a low-memory situation.
+ // We could read lowMemFd to double-check, but we've observed that the
+ // read sometimes completes after the memory-pressure event is over, so
+ // let's just believe the result of poll().
+ rv = DispatchMemoryPressure(MemPressure_New);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Move to the hard level if we're on the soft one.
+ if (mLowMemTriggerKB > mHardLowMemTriggerKB) {
+ SetLowMemTrigger(mHardLowMemTriggerKB);
+ }
+
+ // Manually check mTriggerFd until we observe that memory pressure is
+ // over. We won't fire any more low-memory events until we observe that
+ // we're no longer under pressure. Instead, we fire low-memory-ongoing
+ // events, which cause processes to keep flushing caches but will not
+ // trigger expensive GCs and other attempts to save memory that are
+ // likely futile at this point.
+ do {
+ {
+ MonitorAutoLock lock(mMonitor);
+
+ // We need to check mShuttingDown before we wait here, in order to
+ // catch a shutdown signal sent after we poll()'ed mShutdownPipeRead
+ // above but before we started waiting on the monitor. But we don't
+ // need to check after we wait, because we'll either do another
+ // iteration of this inner loop, in which case we'll check
+ // mShuttingDown, or we'll exit this loop and do another iteration
+ // of the outer loop, in which case we'll check the shutdown pipe.
+ if (mShuttingDown) {
+ LOG("shutting down (2)");
+ return NS_OK;
+ }
+ mMonitor.Wait(PR_MillisecondsToInterval(mPollMS));
+ }
+
+ LOG("Checking to see if memory pressure is over.");
+ rv = CheckForMemoryPressure(&memoryPressure);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (memoryPressure) {
+ rv = DispatchMemoryPressure(MemPressure_Ongoing);
+ NS_ENSURE_SUCCESS(rv, rv);
+ continue;
+ }
+ } while (false);
+
+ if (XRE_IsParentProcess()) {
+ // The main process will try to adjust the trigger.
+ triggerResetTimeout = mPollMS * 2;
+ }
+
+ LOG("Memory pressure is over.");
+ }
+
+ return NS_OK;
+ }
+
+protected:
+ ~MemoryPressureWatcher() {}
+
+private:
+ void ReadPrefs() {
+ // While we're under memory pressure, we periodically read()
+ // notify_trigger_active to try and see when we're no longer under memory
+ // pressure. mPollMS indicates how many milliseconds we wait between those
+ // read()s.
+ Preferences::AddUintVarCache(&mPollMS,
+ "gonk.systemMemoryPressureRecoveryPollMS", /* default */ 5000);
+
+ // We have two values for the notify trigger, a soft one which is triggered
+ // before we start killing background applications and an hard one which is
+ // after we've killed background applications but before we start killing
+ // foreground ones.
+ Preferences::AddUintVarCache(&mSoftLowMemTriggerKB,
+ "gonk.notifySoftLowMemUnderKB", /* default */ 43008);
+ Preferences::AddUintVarCache(&mHardLowMemTriggerKB,
+ "gonk.notifyHardLowMemUnderKB", /* default */ 14336);
+ }
+
+ nsresult OpenFiles() {
+ mTriggerFd = open("/sys/kernel/mm/lowmemkiller/notify_trigger_active",
+ O_RDONLY | O_CLOEXEC);
+ NS_ENSURE_STATE(mTriggerFd != -1);
+
+ int pipes[2];
+ NS_ENSURE_STATE(!pipe(pipes));
+ mShutdownPipeRead = pipes[0];
+ mShutdownPipeWrite = pipes[1];
+ return NS_OK;
+ }
+
+ /**
+ * Set the low memory trigger to the specified value, this can be done by
+ * the main process alone.
+ */
+ void SetLowMemTrigger(uint32_t aValue) {
+ if (XRE_IsParentProcess()) {
+ nsPrintfCString str("%ld", (aValue * 1024) / mPageSize);
+ if (WriteSysFile("/sys/module/lowmemorykiller/parameters/notify_trigger",
+ str.get())) {
+ mLowMemTriggerKB = aValue;
+ }
+ }
+ }
+
+ /**
+ * Read from the trigger file descriptor and determine whether we're
+ * currently under memory pressure.
+ *
+ * We don't expect this method to block.
+ */
+ nsresult CheckForMemoryPressure(bool* aOut)
+ {
+ *aOut = false;
+
+ lseek(mTriggerFd, 0, SEEK_SET);
+
+ char buf[2];
+ int nread = MOZ_TEMP_FAILURE_RETRY(read(mTriggerFd, buf, sizeof(buf)));
+ NS_ENSURE_STATE(nread == 2);
+
+ // The notify_trigger_active sysfs node should contain either "0\n" or
+ // "1\n". The latter indicates memory pressure.
+ *aOut = (buf[0] == '1');
+ return NS_OK;
+ }
+
+ int AdjustTrigger(int timeout)
+ {
+ if (!XRE_IsParentProcess()) {
+ return -1; // Only the main process can adjust the trigger.
+ }
+
+ struct sysinfo info;
+ int rv = sysinfo(&info);
+ if (rv < 0) {
+ return -1; // Without system information we're blind, bail out.
+ }
+
+ size_t freeMemory = (info.freeram * info.mem_unit) / 1024;
+
+ if (freeMemory > mSoftLowMemTriggerKB) {
+ SetLowMemTrigger(mSoftLowMemTriggerKB);
+ return -1; // Trigger adjusted, wait indefinitely.
+ }
+
+ // Wait again but double the duration, max once per day.
+ return std::min(86400000, timeout * 2);
+ }
+
+ /**
+ * Dispatch the specified memory pressure event unless a high-priority
+ * process is present. If a high-priority process is present then it's likely
+ * responding to an urgent event (an incoming call or message for example) so
+ * avoid wasting CPU time responding to low-memory events.
+ */
+ nsresult DispatchMemoryPressure(MemoryPressureState state)
+ {
+ if (ProcessPriorityManager::AnyProcessHasHighPriority()) {
+ return NS_OK;
+ }
+
+ return NS_DispatchMemoryPressure(state);
+ }
+
+ Monitor mMonitor;
+ uint32_t mPollMS; // Ongoing pressure poll delay
+ uint32_t mSoftLowMemTriggerKB; // Soft memory pressure level
+ uint32_t mHardLowMemTriggerKB; // Hard memory pressure level
+ uint32_t mLowMemTriggerKB; // Current value of the trigger
+ size_t mPageSize;
+ bool mShuttingDown;
+
+ ScopedClose mTriggerFd;
+ ScopedClose mShutdownPipeRead;
+ ScopedClose mShutdownPipeWrite;
+};
+
+NS_IMPL_ISUPPORTS(MemoryPressureWatcher, nsIRunnable, nsIObserver);
+
+} // namespace
+
+namespace mozilla {
+
+void
+InitGonkMemoryPressureMonitoring()
+{
+ // memoryPressureWatcher is held alive by the observer service.
+ RefPtr<MemoryPressureWatcher> memoryPressureWatcher =
+ new MemoryPressureWatcher();
+ NS_ENSURE_SUCCESS_VOID(memoryPressureWatcher->Init());
+
+ nsCOMPtr<nsIThread> thread;
+ NS_NewNamedThread("MemoryPressure", getter_AddRefs(thread),
+ memoryPressureWatcher);
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/GonkMemoryPressureMonitoring.h b/widget/gonk/GonkMemoryPressureMonitoring.h
new file mode 100644
index 0000000000..4d5149cd65
--- /dev/null
+++ b/widget/gonk/GonkMemoryPressureMonitoring.h
@@ -0,0 +1,14 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GonkMemoryPressureMonitoring_h_
+#define mozilla_GonkMemoryPressureMonitoring_h_
+
+namespace mozilla {
+void InitGonkMemoryPressureMonitoring();
+}
+
+#endif /* mozilla_GonkMemoryPressureMonitoring_h_ */
diff --git a/widget/gonk/GonkPermission.cpp b/widget/gonk/GonkPermission.cpp
new file mode 100644
index 0000000000..8ebc43de84
--- /dev/null
+++ b/widget/gonk/GonkPermission.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * 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 "GonkPermission.h"
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <binder/IPermissionController.h>
+
+#ifndef HAVE_ANDROID_OS
+#define HAVE_ANDROID_OS 1
+#endif
+#include <private/android_filesystem_config.h>
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
+#include "nsThreadUtils.h"
+
+#undef LOG
+#include <android/log.h>
+#undef ALOGE
+#define ALOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "gonkperm" , ## args)
+
+using namespace android;
+using namespace mozilla;
+
+// Checking permissions needs to happen on the main thread, but the
+// binder callback is called on a special binder thread, so we use
+// this runnable for that.
+class GonkPermissionChecker : public Runnable {
+ int32_t mPid;
+ bool mCanUseCamera;
+
+ explicit GonkPermissionChecker(int32_t pid)
+ : mPid(pid)
+ , mCanUseCamera(false)
+ {
+ }
+
+public:
+ static already_AddRefed<GonkPermissionChecker> Inspect(int32_t pid)
+ {
+ RefPtr<GonkPermissionChecker> that = new GonkPermissionChecker(pid);
+ nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+ MOZ_ASSERT(mainThread);
+ SyncRunnable::DispatchToThread(mainThread, that);
+ return that.forget();
+ }
+
+ bool CanUseCamera()
+ {
+ return mCanUseCamera;
+ }
+
+ NS_IMETHOD Run();
+};
+
+NS_IMETHODIMP
+GonkPermissionChecker::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Find our ContentParent.
+ dom::ContentParent *contentParent = nullptr;
+ {
+ nsTArray<dom::ContentParent*> parents;
+ dom::ContentParent::GetAll(parents);
+ for (uint32_t i = 0; i < parents.Length(); ++i) {
+ if (parents[i]->Pid() == mPid) {
+ contentParent = parents[i];
+ break;
+ }
+ }
+ }
+ if (!contentParent) {
+ ALOGE("pid=%d denied: can't find ContentParent", mPid);
+ return NS_OK;
+ }
+
+ // Now iterate its apps...
+ const ManagedContainer<dom::PBrowserParent>& browsers =
+ contentParent->ManagedPBrowserParent();
+ for (auto iter = browsers.ConstIter(); !iter.Done(); iter.Next()) {
+ dom::TabParent *tabParent =
+ static_cast<dom::TabParent*>(iter.Get()->GetKey());
+ nsCOMPtr<mozIApplication> mozApp = tabParent->GetOwnOrContainingApp();
+ if (!mozApp) {
+ continue;
+ }
+
+ // ...and check if any of them has camera access.
+ bool appCanUseCamera;
+ nsresult rv = mozApp->HasPermission("camera", &appCanUseCamera);
+ if (NS_SUCCEEDED(rv) && appCanUseCamera) {
+ mCanUseCamera = true;
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+bool
+GonkPermissionService::checkPermission(const String16& permission, int32_t pid,
+ int32_t uid)
+{
+ // root can do anything.
+ if (0 == uid) {
+ return true;
+ }
+
+ String8 perm8(permission);
+
+ // Some ril implementations need android.permission.MODIFY_AUDIO_SETTINGS
+ if ((uid == AID_SYSTEM || uid == AID_RADIO || uid == AID_BLUETOOTH) &&
+ perm8 == "android.permission.MODIFY_AUDIO_SETTINGS") {
+ return true;
+ }
+
+ // No other permissions apply to non-app processes.
+ if (uid < AID_APP) {
+ ALOGE("%s for pid=%d,uid=%d denied: not an app",
+ String8(permission).string(), pid, uid);
+ return false;
+ }
+
+ // Only these permissions can be granted to apps through this service.
+ if (perm8 != "android.permission.CAMERA" &&
+ perm8 != "android.permission.RECORD_AUDIO") {
+ ALOGE("%s for pid=%d,uid=%d denied: unsupported permission",
+ String8(permission).string(), pid, uid);
+ return false;
+ }
+
+ // Users granted the permission through a prompt dialog.
+ // Before permission managment of gUM is done, app cannot remember the
+ // permission.
+ PermissionGrant permGrant(perm8.string(), pid);
+ if (nsTArray<PermissionGrant>::NoIndex != mGrantArray.IndexOf(permGrant)) {
+ mGrantArray.RemoveElement(permGrant);
+ return true;
+ }
+
+ // Camera/audio record permissions are allowed for apps with the
+ // "camera" permission.
+ RefPtr<GonkPermissionChecker> checker =
+ GonkPermissionChecker::Inspect(pid);
+ bool canUseCamera = checker->CanUseCamera();
+ if (!canUseCamera) {
+ ALOGE("%s for pid=%d,uid=%d denied: not granted by user or app manifest",
+ String8(permission).string(), pid, uid);
+ }
+ return canUseCamera;
+}
+
+static GonkPermissionService* gGonkPermissionService = NULL;
+
+/* static */
+void
+GonkPermissionService::instantiate()
+{
+ defaultServiceManager()->addService(String16(getServiceName()),
+ GetInstance());
+}
+
+/* static */
+GonkPermissionService*
+GonkPermissionService::GetInstance()
+{
+ if (!gGonkPermissionService) {
+ gGonkPermissionService = new GonkPermissionService();
+ }
+ return gGonkPermissionService;
+}
+
+void
+GonkPermissionService::addGrantInfo(const char* permission, int32_t pid)
+{
+ mGrantArray.AppendElement(PermissionGrant(permission, pid));
+}
diff --git a/widget/gonk/GonkPermission.h b/widget/gonk/GonkPermission.h
new file mode 100644
index 0000000000..d34fcd8ac1
--- /dev/null
+++ b/widget/gonk/GonkPermission.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+#ifndef GONKPERMISSION_H
+#define GONKPERMISSION_H
+
+#include <binder/BinderService.h>
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+class PermissionGrant
+{
+public:
+ PermissionGrant(const char* perm, int32_t p) : mPid(p)
+ {
+ mPermission.Assign(perm);
+ }
+
+ PermissionGrant(const nsACString& permission, int32_t pid) : mPid(pid),
+ mPermission(permission)
+ {
+ }
+
+ bool operator==(const PermissionGrant& other) const
+ {
+ return (mPid == other.pid() && mPermission.Equals(other.permission()));
+ }
+
+ int32_t pid() const
+ {
+ return mPid;
+ }
+
+ const nsACString& permission() const
+ {
+ return mPermission;
+ }
+
+private:
+ int32_t mPid;
+ nsCString mPermission;
+};
+
+class PermissionGrant;
+
+class GonkPermissionService :
+ public android::BinderService<GonkPermissionService>,
+ public android::BnPermissionController
+{
+public:
+ virtual ~GonkPermissionService() {}
+ static GonkPermissionService* GetInstance();
+ static const char *getServiceName() {
+ return "permission";
+ }
+
+ static void instantiate();
+
+ virtual android::status_t dump(int fd, const android::Vector<android::String16>& args) {
+ return android::NO_ERROR;
+ }
+ virtual bool checkPermission(const android::String16& permission, int32_t pid,
+ int32_t uid);
+
+ void addGrantInfo(const char* permission, int32_t pid);
+private:
+ GonkPermissionService(): android::BnPermissionController() {}
+ nsTArray<PermissionGrant> mGrantArray;
+};
+
+} // namespace mozilla
+
+#endif // GONKPERMISSION_H
diff --git a/widget/gonk/HwcComposer2D.cpp b/widget/gonk/HwcComposer2D.cpp
new file mode 100644
index 0000000000..6b4c7a1cc3
--- /dev/null
+++ b/widget/gonk/HwcComposer2D.cpp
@@ -0,0 +1,971 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2012, 2013 The Linux Foundation. All rights reserved.
+ *
+ * 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 <android/log.h>
+#include <string.h>
+
+#include "gfxPrefs.h"
+#include "ImageLayers.h"
+#include "libdisplay/GonkDisplay.h"
+#include "HwcComposer2D.h"
+#include "LayerScope.h"
+#include "Units.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/LayerManagerComposite.h"
+#include "mozilla/layers/PLayerTransaction.h"
+#include "mozilla/layers/ShadowLayerUtilsGralloc.h"
+#include "mozilla/layers/TextureHostOGL.h" // for TextureHostOGL
+#include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+#include "cutils/properties.h"
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "VsyncSource.h"
+#include "nsScreenManagerGonk.h"
+#include "nsWindow.h"
+
+#if ANDROID_VERSION >= 17
+#include "libdisplay/DisplaySurface.h"
+#endif
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "HWComposer"
+
+/*
+ * By default the debug message of hwcomposer (LOG_DEBUG level) are undefined,
+ * but can be enabled by uncommenting HWC_DEBUG below.
+ */
+//#define HWC_DEBUG
+
+#ifdef HWC_DEBUG
+#define LOGD(args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, ## args)
+#else
+#define LOGD(args...) ((void)0)
+#endif
+
+#define LOGI(args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, ## args)
+
+#define LAYER_COUNT_INCREMENTS 5
+
+using namespace android;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace mozilla {
+
+static void
+HookInvalidate(const struct hwc_procs* aProcs)
+{
+ HwcComposer2D::GetInstance()->Invalidate();
+}
+
+static void
+HookVsync(const struct hwc_procs* aProcs, int aDisplay,
+ int64_t aTimestamp)
+{
+ HwcComposer2D::GetInstance()->Vsync(aDisplay, aTimestamp);
+}
+
+static void
+HookHotplug(const struct hwc_procs* aProcs, int aDisplay,
+ int aConnected)
+{
+ HwcComposer2D::GetInstance()->Hotplug(aDisplay, aConnected);
+}
+
+static StaticRefPtr<HwcComposer2D> sInstance;
+
+HwcComposer2D::HwcComposer2D()
+ : mList(nullptr)
+ , mMaxLayerCount(0)
+ , mColorFill(false)
+ , mRBSwapSupport(false)
+ , mPrepared(false)
+ , mHasHWVsync(false)
+ , mLock("mozilla.HwcComposer2D.mLock")
+{
+ mHal = HwcHALBase::CreateHwcHAL();
+ if (!mHal->HasHwc()) {
+ LOGD("no hwc support");
+ return;
+ }
+
+ RegisterHwcEventCallback();
+
+ nsIntSize screenSize;
+
+ GonkDisplay::NativeData data = GetGonkDisplay()->GetNativeData(GonkDisplay::DISPLAY_PRIMARY);
+ ANativeWindow *win = data.mNativeWindow.get();
+ win->query(win, NATIVE_WINDOW_WIDTH, &screenSize.width);
+ win->query(win, NATIVE_WINDOW_HEIGHT, &screenSize.height);
+ mScreenRect = gfx::IntRect(gfx::IntPoint(0, 0), screenSize);
+
+ mColorFill = mHal->Query(HwcHALBase::QueryType::COLOR_FILL);
+ mRBSwapSupport = mHal->Query(HwcHALBase::QueryType::RB_SWAP);
+}
+
+HwcComposer2D::~HwcComposer2D()
+{
+ free(mList);
+}
+
+HwcComposer2D*
+HwcComposer2D::GetInstance()
+{
+ if (!sInstance) {
+#ifdef HWC_DEBUG
+ // Make sure only create once
+ static int timesCreated = 0;
+ ++timesCreated;
+ MOZ_ASSERT(timesCreated == 1);
+#endif
+ LOGI("Creating new instance");
+ sInstance = new HwcComposer2D();
+
+ // If anyone uses the compositor thread to create HwcComposer2D,
+ // we just skip this function.
+ // If ClearOnShutdown() can handle objects in other threads
+ // in the future, we can remove this check.
+ if (NS_IsMainThread()) {
+ // If we create HwcComposer2D by the main thread, we can use
+ // ClearOnShutdown() to make sure it will be nullified properly.
+ ClearOnShutdown(&sInstance);
+ }
+ }
+ return sInstance;
+}
+
+bool
+HwcComposer2D::EnableVsync(bool aEnable)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mHasHWVsync) {
+ return false;
+ }
+ return mHal->EnableVsync(aEnable) && aEnable;
+}
+
+bool
+HwcComposer2D::RegisterHwcEventCallback()
+{
+ const HwcHALProcs_t cHWCProcs = {
+ &HookInvalidate, // 1st: void (*invalidate)(...)
+ &HookVsync, // 2nd: void (*vsync)(...)
+ &HookHotplug // 3rd: void (*hotplug)(...)
+ };
+ mHasHWVsync = mHal->RegisterHwcEventCallback(cHWCProcs);
+ return mHasHWVsync;
+}
+
+void
+HwcComposer2D::Vsync(int aDisplay, nsecs_t aVsyncTimestamp)
+{
+ // Only support hardware vsync on kitkat, L and up due to inaccurate timings
+ // with JellyBean.
+#if (ANDROID_VERSION == 19 || ANDROID_VERSION >= 21)
+ TimeStamp vsyncTime = mozilla::TimeStamp::FromSystemTime(aVsyncTimestamp);
+ gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay().NotifyVsync(vsyncTime);
+#else
+ // If this device doesn't support vsync, this function should not be used.
+ MOZ_ASSERT(false);
+#endif
+}
+
+// Called on the "invalidator" thread (run from HAL).
+void
+HwcComposer2D::Invalidate()
+{
+ if (!mHal->HasHwc()) {
+ LOGE("HwcComposer2D::Invalidate failed!");
+ return;
+ }
+
+ MutexAutoLock lock(mLock);
+ if (mCompositorBridgeParent) {
+ mCompositorBridgeParent->ScheduleRenderOnCompositorThread();
+ }
+}
+
+namespace {
+class HotplugEvent : public Runnable {
+public:
+ HotplugEvent(GonkDisplay::DisplayType aType, bool aConnected)
+ : mType(aType)
+ , mConnected(aConnected)
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ RefPtr<nsScreenManagerGonk> screenManager =
+ nsScreenManagerGonk::GetInstance();
+ if (mConnected) {
+ screenManager->AddScreen(mType);
+ } else {
+ screenManager->RemoveScreen(mType);
+ }
+ return NS_OK;
+ }
+private:
+ GonkDisplay::DisplayType mType;
+ bool mConnected;
+};
+} // namespace
+
+void
+HwcComposer2D::Hotplug(int aDisplay, int aConnected)
+{
+ NS_DispatchToMainThread(new HotplugEvent(GonkDisplay::DISPLAY_EXTERNAL,
+ aConnected));
+}
+
+void
+HwcComposer2D::SetCompositorBridgeParent(CompositorBridgeParent* aCompositorBridgeParent)
+{
+ MutexAutoLock lock(mLock);
+ mCompositorBridgeParent = aCompositorBridgeParent;
+}
+
+bool
+HwcComposer2D::ReallocLayerList()
+{
+ int size = sizeof(HwcList) +
+ ((mMaxLayerCount + LAYER_COUNT_INCREMENTS) * sizeof(HwcLayer));
+
+ HwcList* listrealloc = (HwcList*)realloc(mList, size);
+
+ if (!listrealloc) {
+ return false;
+ }
+
+ if (!mList) {
+ //first alloc, initialize
+ listrealloc->numHwLayers = 0;
+ listrealloc->flags = 0;
+ }
+
+ mList = listrealloc;
+ mMaxLayerCount += LAYER_COUNT_INCREMENTS;
+ return true;
+}
+
+bool
+HwcComposer2D::PrepareLayerList(Layer* aLayer,
+ const nsIntRect& aClip,
+ const Matrix& aParentTransform,
+ bool aFindSidebandStreams)
+{
+ // NB: we fall off this path whenever there are container layers
+ // that require intermediate surfaces. That means all the
+ // GetEffective*() coordinates are relative to the framebuffer.
+
+ bool fillColor = false;
+
+ const nsIntRegion visibleRegion = aLayer->GetLocalVisibleRegion().ToUnknownRegion();
+ if (visibleRegion.IsEmpty()) {
+ return true;
+ }
+
+ uint8_t opacity = std::min(0xFF, (int)(aLayer->GetEffectiveOpacity() * 256.0));
+ if (opacity == 0) {
+ LOGD("%s Layer has zero opacity; skipping", aLayer->Name());
+ return true;
+ }
+
+ if (!mHal->SupportTransparency() && opacity < 0xFF && !aFindSidebandStreams) {
+ LOGD("%s Layer has planar semitransparency which is unsupported by hwcomposer", aLayer->Name());
+ return false;
+ }
+
+ if (aLayer->GetMaskLayer() && !aFindSidebandStreams) {
+ LOGD("%s Layer has MaskLayer which is unsupported by hwcomposer", aLayer->Name());
+ return false;
+ }
+
+ nsIntRect clip;
+ nsIntRect layerClip = aLayer->GetLocalClipRect().valueOr(ParentLayerIntRect()).ToUnknownRect();
+ nsIntRect* layerClipPtr = aLayer->GetLocalClipRect() ? &layerClip : nullptr;
+ if (!HwcUtils::CalculateClipRect(aParentTransform,
+ layerClipPtr,
+ aClip,
+ &clip))
+ {
+ LOGD("%s Clip rect is empty. Skip layer", aLayer->Name());
+ return true;
+ }
+
+ // HWC supports only the following 2D transformations:
+ //
+ // Scaling via the sourceCrop and displayFrame in HwcLayer
+ // Translation via the sourceCrop and displayFrame in HwcLayer
+ // Rotation (in square angles only) via the HWC_TRANSFORM_ROT_* flags
+ // Reflection (horizontal and vertical) via the HWC_TRANSFORM_FLIP_* flags
+ //
+ // A 2D transform with PreservesAxisAlignedRectangles() has all the attributes
+ // above
+ Matrix layerTransform;
+ if (!aLayer->GetEffectiveTransform().Is2D(&layerTransform) ||
+ !layerTransform.PreservesAxisAlignedRectangles()) {
+ LOGD("Layer EffectiveTransform has a 3D transform or a non-square angle rotation");
+ return false;
+ }
+
+ Matrix layerBufferTransform;
+ if (!aLayer->GetEffectiveTransformForBuffer().Is2D(&layerBufferTransform) ||
+ !layerBufferTransform.PreservesAxisAlignedRectangles()) {
+ LOGD("Layer EffectiveTransformForBuffer has a 3D transform or a non-square angle rotation");
+ return false;
+ }
+
+ if (ContainerLayer* container = aLayer->AsContainerLayer()) {
+ if (container->UseIntermediateSurface() && !aFindSidebandStreams) {
+ LOGD("Container layer needs intermediate surface");
+ return false;
+ }
+ AutoTArray<Layer*, 12> children;
+ container->SortChildrenBy3DZOrder(children);
+
+ for (uint32_t i = 0; i < children.Length(); i++) {
+ if (!PrepareLayerList(children[i], clip, layerTransform, aFindSidebandStreams) &&
+ !aFindSidebandStreams) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ LayerRenderState state = aLayer->GetRenderState();
+
+#if ANDROID_VERSION >= 21
+ if (!state.GetGrallocBuffer() && !state.GetSidebandStream().IsValid()) {
+#else
+ if (!state.GetGrallocBuffer()) {
+#endif
+ if (aLayer->AsColorLayer() && mColorFill) {
+ fillColor = true;
+ } else {
+ LOGD("%s Layer doesn't have a gralloc buffer", aLayer->Name());
+ return false;
+ }
+ }
+
+ nsIntRect visibleRect = visibleRegion.GetBounds();
+
+ nsIntRect bufferRect;
+ if (fillColor) {
+ bufferRect = nsIntRect(visibleRect);
+ } else {
+ nsIntRect layerRect;
+ if (state.mHasOwnOffset) {
+ bufferRect = nsIntRect(state.mOffset.x, state.mOffset.y,
+ state.mSize.width, state.mSize.height);
+ layerRect = bufferRect;
+ } else {
+ //Since the buffer doesn't have its own offset, assign the whole
+ //surface size as its buffer bounds
+ bufferRect = nsIntRect(0, 0, state.mSize.width, state.mSize.height);
+ layerRect = bufferRect;
+ if (aLayer->GetType() == Layer::TYPE_IMAGE) {
+ ImageLayer* imageLayer = static_cast<ImageLayer*>(aLayer);
+ if(imageLayer->GetScaleMode() != ScaleMode::SCALE_NONE) {
+ layerRect = nsIntRect(0, 0, imageLayer->GetScaleToSize().width, imageLayer->GetScaleToSize().height);
+ }
+ }
+ }
+ // In some cases the visible rect assigned to the layer can be larger
+ // than the layer's surface, e.g., an ImageLayer with a small Image
+ // in it.
+ visibleRect.IntersectRect(visibleRect, layerRect);
+ }
+
+ // Buffer rotation is not to be confused with the angled rotation done by a transform matrix
+ // It's a fancy PaintedLayer feature used for scrolling
+ if (state.BufferRotated()) {
+ LOGD("%s Layer has a rotated buffer", aLayer->Name());
+ return false;
+ }
+
+ const bool needsYFlip = state.OriginBottomLeft() ? true
+ : false;
+
+ hwc_rect_t sourceCrop, displayFrame;
+ if(!HwcUtils::PrepareLayerRects(visibleRect,
+ layerTransform,
+ layerBufferTransform,
+ clip,
+ bufferRect,
+ needsYFlip,
+ &(sourceCrop),
+ &(displayFrame)))
+ {
+ return true;
+ }
+
+ // OK! We can compose this layer with hwc.
+ int current = mList ? mList->numHwLayers : 0;
+
+ // Do not compose any layer below full-screen Opaque layer
+ // Note: It can be generalized to non-fullscreen Opaque layers.
+ bool isOpaque = opacity == 0xFF &&
+ (state.mFlags & LayerRenderStateFlags::OPAQUE);
+ // Currently we perform opacity calculation using the *bounds* of the layer.
+ // We can only make this assumption if we're not dealing with a complex visible region.
+ bool isSimpleVisibleRegion = visibleRegion.Contains(visibleRect);
+ if (current && isOpaque && isSimpleVisibleRegion) {
+ nsIntRect displayRect = nsIntRect(displayFrame.left, displayFrame.top,
+ displayFrame.right - displayFrame.left, displayFrame.bottom - displayFrame.top);
+ if (displayRect.Contains(mScreenRect)) {
+ // In z-order, all previous layers are below
+ // the current layer. We can ignore them now.
+ mList->numHwLayers = current = 0;
+ mHwcLayerMap.Clear();
+ }
+ }
+
+ if (!mList || current >= mMaxLayerCount) {
+ if (!ReallocLayerList() || current >= mMaxLayerCount) {
+ LOGE("PrepareLayerList failed! Could not increase the maximum layer count");
+ return false;
+ }
+ }
+
+ HwcLayer& hwcLayer = mList->hwLayers[current];
+ hwcLayer.displayFrame = displayFrame;
+ mHal->SetCrop(hwcLayer, sourceCrop);
+ buffer_handle_t handle = nullptr;
+#if ANDROID_VERSION >= 21
+ if (state.GetSidebandStream().IsValid()) {
+ handle = state.GetSidebandStream().GetRawNativeHandle();
+ } else if (state.GetGrallocBuffer()) {
+ handle = state.GetGrallocBuffer()->getNativeBuffer()->handle;
+ }
+#else
+ if (state.GetGrallocBuffer()) {
+ handle = state.GetGrallocBuffer()->getNativeBuffer()->handle;
+ }
+#endif
+ hwcLayer.handle = handle;
+
+ hwcLayer.flags = 0;
+ hwcLayer.hints = 0;
+ hwcLayer.blending = isOpaque ? HWC_BLENDING_NONE : HWC_BLENDING_PREMULT;
+#if ANDROID_VERSION >= 17
+ hwcLayer.compositionType = HWC_FRAMEBUFFER;
+#if ANDROID_VERSION >= 21
+ if (state.GetSidebandStream().IsValid()) {
+ hwcLayer.compositionType = HWC_SIDEBAND;
+ }
+#endif
+ hwcLayer.acquireFenceFd = -1;
+ hwcLayer.releaseFenceFd = -1;
+#if ANDROID_VERSION >= 18
+ hwcLayer.planeAlpha = opacity;
+#endif
+#else
+ hwcLayer.compositionType = HwcUtils::HWC_USE_COPYBIT;
+#endif
+
+ if (!fillColor) {
+ if (state.FormatRBSwapped()) {
+ if (!mRBSwapSupport) {
+ LOGD("No R/B swap support in H/W Composer");
+ return false;
+ }
+ hwcLayer.flags |= HwcUtils::HWC_FORMAT_RB_SWAP;
+ }
+
+ // Translation and scaling have been addressed in PrepareLayerRects().
+ // Given the above and that we checked for PreservesAxisAlignedRectangles()
+ // the only possible transformations left to address are
+ // square angle rotation and horizontal/vertical reflection.
+ //
+ // The rotation and reflection permutations total 16 but can be
+ // reduced to 8 transformations after eliminating redundancies.
+ //
+ // All matrices represented here are in the form
+ //
+ // | xx xy |
+ // | yx yy |
+ //
+ // And ignore scaling.
+ //
+ // Reflection is applied before rotation
+ gfx::Matrix rotation = layerTransform;
+ // Compute fuzzy zero like PreservesAxisAlignedRectangles()
+ if (fabs(rotation._11) < 1e-6) {
+ if (rotation._21 < 0) {
+ if (rotation._12 > 0) {
+ // 90 degree rotation
+ //
+ // | 0 -1 |
+ // | 1 0 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_90;
+ LOGD("Layer rotated 90 degrees");
+ }
+ else {
+ // Horizontal reflection then 90 degree rotation
+ //
+ // | 0 -1 | | -1 0 | = | 0 -1 |
+ // | 1 0 | | 0 1 | | -1 0 |
+ //
+ // same as vertical reflection then 270 degree rotation
+ //
+ // | 0 1 | | 1 0 | = | 0 -1 |
+ // | -1 0 | | 0 -1 | | -1 0 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_90 | HWC_TRANSFORM_FLIP_H;
+ LOGD("Layer vertically reflected then rotated 270 degrees");
+ }
+ } else {
+ if (rotation._12 < 0) {
+ // 270 degree rotation
+ //
+ // | 0 1 |
+ // | -1 0 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_270;
+ LOGD("Layer rotated 270 degrees");
+ }
+ else {
+ // Vertical reflection then 90 degree rotation
+ //
+ // | 0 1 | | -1 0 | = | 0 1 |
+ // | -1 0 | | 0 1 | | 1 0 |
+ //
+ // Same as horizontal reflection then 270 degree rotation
+ //
+ // | 0 -1 | | 1 0 | = | 0 1 |
+ // | 1 0 | | 0 -1 | | 1 0 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_90 | HWC_TRANSFORM_FLIP_V;
+ LOGD("Layer horizontally reflected then rotated 270 degrees");
+ }
+ }
+ } else if (rotation._11 < 0) {
+ if (rotation._22 > 0) {
+ // Horizontal reflection
+ //
+ // | -1 0 |
+ // | 0 1 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_FLIP_H;
+ LOGD("Layer rotated 180 degrees");
+ }
+ else {
+ // 180 degree rotation
+ //
+ // | -1 0 |
+ // | 0 -1 |
+ //
+ // Same as horizontal and vertical reflection
+ //
+ // | -1 0 | | 1 0 | = | -1 0 |
+ // | 0 1 | | 0 -1 | | 0 -1 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_ROT_180;
+ LOGD("Layer rotated 180 degrees");
+ }
+ } else {
+ if (rotation._22 < 0) {
+ // Vertical reflection
+ //
+ // | 1 0 |
+ // | 0 -1 |
+ //
+ hwcLayer.transform = HWC_TRANSFORM_FLIP_V;
+ LOGD("Layer rotated 180 degrees");
+ }
+ else {
+ // No rotation or reflection
+ //
+ // | 1 0 |
+ // | 0 1 |
+ //
+ hwcLayer.transform = 0;
+ }
+ }
+
+ const bool needsYFlip = state.OriginBottomLeft() ? true
+ : false;
+
+ if (needsYFlip) {
+ // Invert vertical reflection flag if it was already set
+ hwcLayer.transform ^= HWC_TRANSFORM_FLIP_V;
+ }
+ hwc_region_t region;
+ if (visibleRegion.GetNumRects() > 1) {
+ mVisibleRegions.push_back(HwcUtils::RectVector());
+ HwcUtils::RectVector* visibleRects = &(mVisibleRegions.back());
+ bool isVisible = false;
+ if(!HwcUtils::PrepareVisibleRegion(visibleRegion,
+ layerTransform,
+ layerBufferTransform,
+ clip,
+ bufferRect,
+ visibleRects,
+ isVisible)) {
+ LOGD("A region of layer is too small to be rendered by HWC");
+ return false;
+ }
+ if (!isVisible) {
+ // Layer is not visible, no need to render it
+ return true;
+ }
+ region.numRects = visibleRects->size();
+ region.rects = &((*visibleRects)[0]);
+ } else {
+ region.numRects = 1;
+ region.rects = &(hwcLayer.displayFrame);
+ }
+ hwcLayer.visibleRegionScreen = region;
+ } else {
+ hwcLayer.flags |= HwcUtils::HWC_COLOR_FILL;
+ ColorLayer* colorLayer = aLayer->AsColorLayer();
+ if (colorLayer->GetColor().a < 1.0) {
+ LOGD("Color layer has semitransparency which is unsupported");
+ return false;
+ }
+ hwcLayer.transform = colorLayer->GetColor().ToABGR();
+ }
+
+#if ANDROID_VERSION >= 21
+ if (aFindSidebandStreams && hwcLayer.compositionType == HWC_SIDEBAND) {
+ mCachedSidebandLayers.AppendElement(hwcLayer);
+ }
+#endif
+
+ mHwcLayerMap.AppendElement(static_cast<LayerComposite*>(aLayer->ImplData()));
+ mList->numHwLayers++;
+ return true;
+}
+
+
+#if ANDROID_VERSION >= 17
+bool
+HwcComposer2D::TryHwComposition(nsScreenGonk* aScreen)
+{
+ DisplaySurface* dispSurface = aScreen->GetDisplaySurface();
+
+ if (!(dispSurface && dispSurface->lastHandle)) {
+ LOGD("H/W Composition failed. DispSurface not initialized.");
+ return false;
+ }
+
+ // Add FB layer
+ int idx = mList->numHwLayers++;
+ if (idx >= mMaxLayerCount) {
+ if (!ReallocLayerList() || idx >= mMaxLayerCount) {
+ LOGE("TryHwComposition failed! Could not add FB layer");
+ return false;
+ }
+ }
+
+ Prepare(dispSurface->lastHandle, -1, aScreen);
+
+ /* Possible composition paths, after hwc prepare:
+ 1. GPU Composition
+ 2. BLIT Composition
+ 3. Full OVERLAY Composition
+ 4. Partial OVERLAY Composition (GPU + OVERLAY) */
+
+ bool gpuComposite = false;
+ bool blitComposite = false;
+ bool overlayComposite = true;
+
+ for (int j=0; j < idx; j++) {
+ if (mList->hwLayers[j].compositionType == HWC_FRAMEBUFFER ||
+ mList->hwLayers[j].compositionType == HWC_BLIT) {
+ // Full OVERLAY composition is not possible on this frame
+ // It is either GPU / BLIT / partial OVERLAY composition.
+ overlayComposite = false;
+ break;
+ }
+ }
+
+ if (!overlayComposite) {
+ for (int k=0; k < idx; k++) {
+ switch (mList->hwLayers[k].compositionType) {
+ case HWC_FRAMEBUFFER:
+ gpuComposite = true;
+ break;
+ case HWC_BLIT:
+ blitComposite = true;
+ break;
+#if ANDROID_VERSION >= 21
+ case HWC_SIDEBAND:
+#endif
+ case HWC_OVERLAY: {
+ // HWC will compose HWC_OVERLAY layers in partial
+ // Overlay Composition, set layer composition flag
+ // on mapped LayerComposite to skip GPU composition
+ mHwcLayerMap[k]->SetLayerComposited(true);
+
+ uint8_t opacity = std::min(0xFF, (int)(mHwcLayerMap[k]->GetLayer()->GetEffectiveOpacity() * 256.0));
+ if ((mList->hwLayers[k].hints & HWC_HINT_CLEAR_FB) &&
+ (opacity == 0xFF)) {
+ // Clear visible rect on FB with transparent pixels.
+ hwc_rect_t r = mList->hwLayers[k].displayFrame;
+ mHwcLayerMap[k]->SetClearRect(nsIntRect(r.left, r.top,
+ r.right - r.left,
+ r.bottom - r.top));
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (gpuComposite) {
+ // GPU or partial OVERLAY Composition
+ return false;
+ } else if (blitComposite) {
+ // BLIT Composition, flip DispSurface target
+ GetGonkDisplay()->UpdateDispSurface(aScreen->GetEGLDisplay(), aScreen->GetEGLSurface());
+ DisplaySurface* dispSurface = aScreen->GetDisplaySurface();
+ if (!dispSurface) {
+ LOGE("H/W Composition failed. NULL DispSurface.");
+ return false;
+ }
+ mList->hwLayers[idx].handle = dispSurface->lastHandle;
+ mList->hwLayers[idx].acquireFenceFd = dispSurface->GetPrevDispAcquireFd();
+ }
+ }
+
+ // BLIT or full OVERLAY Composition
+ return Commit(aScreen);
+}
+
+bool
+HwcComposer2D::Render(nsIWidget* aWidget)
+{
+ nsScreenGonk* screen = static_cast<nsWindow*>(aWidget)->GetScreen();
+
+ // HWC module does not exist or mList is not created yet.
+ if (!mHal->HasHwc() || !mList) {
+ return GetGonkDisplay()->SwapBuffers(screen->GetEGLDisplay(), screen->GetEGLSurface());
+ } else if (!mList && !ReallocLayerList()) {
+ LOGE("Cannot realloc layer list");
+ return false;
+ }
+
+ DisplaySurface* dispSurface = screen->GetDisplaySurface();
+ if (!dispSurface) {
+ LOGE("H/W Composition failed. DispSurface not initialized.");
+ return false;
+ }
+
+ if (mPrepared) {
+ // No mHwc prepare, if already prepared in current draw cycle
+ mList->hwLayers[mList->numHwLayers - 1].handle = dispSurface->lastHandle;
+ mList->hwLayers[mList->numHwLayers - 1].acquireFenceFd = dispSurface->GetPrevDispAcquireFd();
+ } else {
+ // Update screen rect to handle a case that TryRenderWithHwc() is not called.
+ mScreenRect = screen->GetNaturalBounds().ToUnknownRect();
+
+ mList->flags = HWC_GEOMETRY_CHANGED;
+ mList->numHwLayers = 2;
+ mList->hwLayers[0].hints = 0;
+ mList->hwLayers[0].compositionType = HWC_FRAMEBUFFER;
+ mList->hwLayers[0].flags = HWC_SKIP_LAYER;
+ mList->hwLayers[0].backgroundColor = {0};
+ mList->hwLayers[0].acquireFenceFd = -1;
+ mList->hwLayers[0].releaseFenceFd = -1;
+ mList->hwLayers[0].displayFrame = {0, 0, mScreenRect.width, mScreenRect.height};
+
+#if ANDROID_VERSION >= 21
+ // Prepare layers for sideband streams
+ const uint32_t len = mCachedSidebandLayers.Length();
+ for (uint32_t i = 0; i < len; ++i) {
+ ++mList->numHwLayers;
+ mList->hwLayers[i+1] = mCachedSidebandLayers[i];
+ }
+#endif
+ Prepare(dispSurface->lastHandle, dispSurface->GetPrevDispAcquireFd(), screen);
+ }
+
+ // GPU or partial HWC Composition
+ return Commit(screen);
+}
+
+void
+HwcComposer2D::Prepare(buffer_handle_t dispHandle, int fence, nsScreenGonk* screen)
+{
+ if (mPrepared) {
+ LOGE("Multiple hwc prepare calls!");
+ }
+ hwc_rect_t dispRect = {0, 0, mScreenRect.width, mScreenRect.height};
+ mHal->Prepare(mList, screen->GetDisplayType(), dispRect, dispHandle, fence);
+ mPrepared = true;
+}
+
+bool
+HwcComposer2D::Commit(nsScreenGonk* aScreen)
+{
+ for (uint32_t j=0; j < (mList->numHwLayers - 1); j++) {
+ mList->hwLayers[j].acquireFenceFd = -1;
+ if (mHwcLayerMap.IsEmpty() ||
+ (mList->hwLayers[j].compositionType == HWC_FRAMEBUFFER)) {
+ continue;
+ }
+ LayerRenderState state = mHwcLayerMap[j]->GetLayer()->GetRenderState();
+ if (!state.mTexture) {
+ continue;
+ }
+ FenceHandle fence = state.mTexture->GetAndResetAcquireFenceHandle();
+ if (fence.IsValid()) {
+ RefPtr<FenceHandle::FdObj> fdObj = fence.GetAndResetFdObj();
+ mList->hwLayers[j].acquireFenceFd = fdObj->GetAndResetFd();
+ }
+ }
+
+ int err = mHal->Set(mList, aScreen->GetDisplayType());
+
+ mPrevRetireFence.TransferToAnotherFenceHandle(mPrevDisplayFence);
+
+ for (uint32_t j=0; j < (mList->numHwLayers - 1); j++) {
+ if (mList->hwLayers[j].releaseFenceFd >= 0) {
+ int fd = mList->hwLayers[j].releaseFenceFd;
+ mList->hwLayers[j].releaseFenceFd = -1;
+ RefPtr<FenceHandle::FdObj> fdObj = new FenceHandle::FdObj(fd);
+ FenceHandle fence(fdObj);
+
+ LayerRenderState state = mHwcLayerMap[j]->GetLayer()->GetRenderState();
+ if (!state.mTexture) {
+ continue;
+ }
+ state.mTexture->SetReleaseFenceHandle(fence);
+ }
+ }
+
+ if (mList->retireFenceFd >= 0) {
+ mPrevRetireFence = FenceHandle(new FenceHandle::FdObj(mList->retireFenceFd));
+ }
+
+ // Set DisplaySurface layer fence
+ DisplaySurface* displaySurface = aScreen->GetDisplaySurface();
+ displaySurface->setReleaseFenceFd(mList->hwLayers[mList->numHwLayers - 1].releaseFenceFd);
+ mList->hwLayers[mList->numHwLayers - 1].releaseFenceFd = -1;
+
+ mPrepared = false;
+ return !err;
+}
+#else
+bool
+HwcComposer2D::TryHwComposition(nsScreenGonk* aScreen)
+{
+ mHal->SetEGLInfo(aScreen->GetEGLDisplay(), aScreen->GetEGLSurface());
+ return !mHal->Set(mList, aScreen->GetDisplayType());
+}
+
+bool
+HwcComposer2D::Render(nsIWidget* aWidget)
+{
+ nsScreenGonk* screen = static_cast<nsWindow*>(aWidget)->GetScreen();
+ return GetGonkDisplay()->SwapBuffers(screen->GetEGLDisplay(), screen->GetEGLSurface());
+}
+#endif
+
+bool
+HwcComposer2D::TryRenderWithHwc(Layer* aRoot,
+ nsIWidget* aWidget,
+ bool aGeometryChanged,
+ bool aHasImageHostOverlays)
+{
+ if (!mHal->HasHwc()) {
+ return false;
+ }
+
+ nsScreenGonk* screen = static_cast<nsWindow*>(aWidget)->GetScreen();
+
+ if (mList) {
+ mList->flags = mHal->GetGeometryChangedFlag(aGeometryChanged);
+ mList->numHwLayers = 0;
+ mHwcLayerMap.Clear();
+ }
+
+ if (mPrepared) {
+ mHal->ResetHwc();
+ mPrepared = false;
+ }
+
+ // XXX: The clear() below means all rect vectors will be have to be
+ // reallocated. We may want to avoid this if possible
+ mVisibleRegions.clear();
+
+ mScreenRect = screen->GetNaturalBounds().ToUnknownRect();
+ MOZ_ASSERT(mHwcLayerMap.IsEmpty());
+ mCachedSidebandLayers.Clear();
+ if (!PrepareLayerList(aRoot,
+ mScreenRect,
+ gfx::Matrix(),
+ /* aFindSidebandStreams */ false))
+ {
+ mHwcLayerMap.Clear();
+ LOGD("Render aborted. Fallback to GPU Composition");
+ if (aHasImageHostOverlays) {
+ LOGD("Prepare layers of SidebandStreams");
+ // Failed to create a layer list for hwc. But we need the list
+ // only for handling sideband streams. Traverse layer tree without
+ // some early returns to make sure we can find all the layers.
+ // It is the best wrong thing that we can do.
+ PrepareLayerList(aRoot,
+ mScreenRect,
+ gfx::Matrix(),
+ /* aFindSidebandStreams */ true);
+ // Reset mPrepared to false, since we already fell back to
+ // gpu composition.
+ mPrepared = false;
+ }
+ return false;
+ }
+
+ // Send data to LayerScope for debugging
+ SendtoLayerScope();
+
+ if (!TryHwComposition(screen)) {
+ LOGD("Full HWC Composition failed. Fallback to GPU Composition or partial OVERLAY Composition");
+ LayerScope::CleanLayer();
+ return false;
+ }
+
+ LOGD("Frame rendered");
+ return true;
+}
+
+void
+HwcComposer2D::SendtoLayerScope()
+{
+ if (!LayerScope::CheckSendable()) {
+ return;
+ }
+
+ const int len = mList->numHwLayers;
+ for (int i = 0; i < len; ++i) {
+ LayerComposite* layer = mHwcLayerMap[i];
+ const hwc_rect_t r = mList->hwLayers[i].displayFrame;
+ LayerScope::SendLayer(layer, r.right - r.left, r.bottom - r.top);
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/HwcComposer2D.h b/widget/gonk/HwcComposer2D.h
new file mode 100644
index 0000000000..56c1d1ec18
--- /dev/null
+++ b/widget/gonk/HwcComposer2D.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef mozilla_HwcComposer2D
+#define mozilla_HwcComposer2D
+
+#include "Composer2D.h"
+#include "hwchal/HwcHALBase.h" // for HwcHAL
+#include "HwcUtils.h" // for RectVector
+#include "Layers.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/layers/FenceUtils.h" // for FenceHandle
+#include "mozilla/UniquePtr.h" // for HwcHAL
+
+#include <vector>
+#include <list>
+
+#include <utils/Timers.h>
+
+class nsScreenGonk;
+
+namespace mozilla {
+
+namespace gl {
+ class GLContext;
+}
+
+namespace layers {
+class CompositorBridgeParent;
+class Layer;
+}
+
+/*
+ * HwcComposer2D provides a way for gecko to render frames
+ * using hwcomposer.h in the AOSP HAL.
+ *
+ * hwcomposer.h defines an interface for display composition
+ * using dedicated hardware. This hardware is usually faster
+ * or more power efficient than the GPU. However, in exchange
+ * for better performance, generality has to be sacrificed:
+ * no 3d transforms, no intermediate surfaces, no special shader effects,
+ * and loss of other goodies depending on the platform.
+ *
+ * In general, when hwc is enabled gecko tries to compose
+ * its frames using HwcComposer2D first. Then if HwcComposer2D is
+ * unable to compose a frame then it falls back to compose it
+ * using the GPU with OpenGL.
+ *
+ */
+class HwcComposer2D : public mozilla::layers::Composer2D {
+public:
+ HwcComposer2D();
+ virtual ~HwcComposer2D();
+
+ static HwcComposer2D* GetInstance();
+
+ // Returns TRUE if the container has been succesfully rendered
+ // Returns FALSE if the container cannot be fully rendered
+ // by this composer so nothing was rendered at all
+ virtual bool TryRenderWithHwc(layers::Layer* aRoot,
+ nsIWidget* aWidget,
+ bool aGeometryChanged,
+ bool aHasImageHostOverlays) override;
+
+ virtual bool Render(nsIWidget* aWidget) override;
+
+ virtual bool HasHwc() override { return mHal->HasHwc(); }
+
+ bool EnableVsync(bool aEnable);
+ bool RegisterHwcEventCallback();
+ void Vsync(int aDisplay, int64_t aTimestamp);
+ void Invalidate();
+ void Hotplug(int aDisplay, int aConnected);
+ void SetCompositorBridgeParent(layers::CompositorBridgeParent* aCompositorBridgeParent);
+
+private:
+ void Reset();
+ void Prepare(buffer_handle_t dispHandle, int fence, nsScreenGonk* screen);
+ bool Commit(nsScreenGonk* aScreen);
+ bool TryHwComposition(nsScreenGonk* aScreen);
+ bool ReallocLayerList();
+ bool PrepareLayerList(layers::Layer* aContainer, const nsIntRect& aClip,
+ const gfx::Matrix& aParentTransform,
+ bool aFindSidebandStreams);
+ void SendtoLayerScope();
+
+ UniquePtr<HwcHALBase> mHal;
+ HwcList* mList;
+ nsIntRect mScreenRect;
+ int mMaxLayerCount;
+ bool mColorFill;
+ bool mRBSwapSupport;
+ //Holds all the dynamically allocated RectVectors needed
+ //to render the current frame
+ std::list<HwcUtils::RectVector> mVisibleRegions;
+ layers::FenceHandle mPrevRetireFence;
+ layers::FenceHandle mPrevDisplayFence;
+ nsTArray<HwcLayer> mCachedSidebandLayers;
+ nsTArray<layers::LayerComposite*> mHwcLayerMap;
+ bool mPrepared;
+ bool mHasHWVsync;
+ layers::CompositorBridgeParent* mCompositorBridgeParent;
+ Mutex mLock;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_HwcComposer2D
diff --git a/widget/gonk/HwcUtils.cpp b/widget/gonk/HwcUtils.cpp
new file mode 100644
index 0000000000..a8f030f3c0
--- /dev/null
+++ b/widget/gonk/HwcUtils.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 2013 The Linux Foundation. All rights reserved.
+ *
+ * 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 <android/log.h>
+#include "HwcUtils.h"
+#include "gfxUtils.h"
+#include "gfx2DGlue.h"
+
+#define LOG_TAG "HwcUtils"
+
+#if (LOG_NDEBUG == 0)
+#define LOGD(args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, ## args)
+#else
+#define LOGD(args...) ((void)0)
+#endif
+
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, ## args)
+
+
+namespace mozilla {
+
+/* Utility functions for HwcComposer */
+
+
+
+/* static */ bool
+HwcUtils::PrepareLayerRects(nsIntRect aVisible,
+ const gfx::Matrix& aLayerTransform,
+ const gfx::Matrix& aLayerBufferTransform,
+ nsIntRect aClip, nsIntRect aBufferRect,
+ bool aYFlipped,
+ hwc_rect_t* aSourceCrop, hwc_rect_t* aVisibleRegionScreen) {
+
+ gfxMatrix aTransform = gfx::ThebesMatrix(aLayerTransform);
+ gfxRect visibleRect(ThebesRect(aVisible));
+ gfxRect clip(ThebesRect(aClip));
+ gfxRect visibleRectScreen = aTransform.TransformBounds(visibleRect);
+ // |clip| is guaranteed to be integer
+ visibleRectScreen.IntersectRect(visibleRectScreen, clip);
+
+ if (visibleRectScreen.IsEmpty()) {
+ return false;
+ }
+
+ gfxMatrix inverse = gfx::ThebesMatrix(aLayerBufferTransform);
+ inverse.Invert();
+ gfxRect crop = inverse.TransformBounds(visibleRectScreen);
+
+ //clip to buffer size
+ crop.IntersectRect(crop, ThebesRect(aBufferRect));
+ crop.Round();
+
+ if (crop.IsEmpty()) {
+ return false;
+ }
+
+ //propagate buffer clipping back to visible rect
+ gfxMatrix layerBufferTransform = gfx::ThebesMatrix(aLayerBufferTransform);
+ visibleRectScreen = layerBufferTransform.TransformBounds(crop);
+ visibleRectScreen.Round();
+
+ // Map from layer space to buffer space
+ crop -= aBufferRect.TopLeft();
+ if (aYFlipped) {
+ crop.y = aBufferRect.height - (crop.y + crop.height);
+ }
+
+ aSourceCrop->left = crop.x;
+ aSourceCrop->top = crop.y;
+ aSourceCrop->right = crop.x + crop.width;
+ aSourceCrop->bottom = crop.y + crop.height;
+
+ aVisibleRegionScreen->left = visibleRectScreen.x;
+ aVisibleRegionScreen->top = visibleRectScreen.y;
+ aVisibleRegionScreen->right = visibleRectScreen.x + visibleRectScreen.width;
+ aVisibleRegionScreen->bottom = visibleRectScreen.y + visibleRectScreen.height;
+
+ return true;
+}
+
+/* static */ bool
+HwcUtils::PrepareVisibleRegion(const nsIntRegion& aVisible,
+ const gfx::Matrix& aLayerTransform,
+ const gfx::Matrix& aLayerBufferTransform,
+ nsIntRect aClip, nsIntRect aBufferRect,
+ RectVector* aVisibleRegionScreen,
+ bool& aIsVisible) {
+ const float MIN_SRC_WIDTH = 2.f;
+ const float MIN_SRC_HEIGHT = 2.f;
+
+ gfxMatrix layerTransform = gfx::ThebesMatrix(aLayerTransform);
+ gfxMatrix layerBufferTransform = gfx::ThebesMatrix(aLayerBufferTransform);
+ gfxRect bufferRect =
+ layerBufferTransform.TransformBounds(ThebesRect(aBufferRect));
+ gfxMatrix inverse = gfx::ThebesMatrix(aLayerBufferTransform);
+ inverse.Invert();
+ aIsVisible = false;
+
+ for (auto iter = aVisible.RectIter(); !iter.Done(); iter.Next()) {
+ gfxRect screenRect =
+ layerTransform.TransformBounds(ThebesRect(iter.Get()));
+ screenRect.IntersectRect(screenRect, bufferRect);
+ screenRect.IntersectRect(screenRect, ThebesRect(aClip));
+ screenRect.Round();
+ if (screenRect.IsEmpty()) {
+ continue;
+ }
+
+ hwc_rect_t visibleRectScreen;
+ visibleRectScreen.left = screenRect.x;
+ visibleRectScreen.top = screenRect.y;
+ visibleRectScreen.right = screenRect.XMost();
+ visibleRectScreen.bottom = screenRect.YMost();
+
+ gfxRect srcCrop = inverse.TransformBounds(screenRect);
+ // When src crop is very small, HWC could not render correctly in some cases.
+ // See Bug 1169093
+ if(srcCrop.Width() < MIN_SRC_WIDTH || srcCrop.Height() < MIN_SRC_HEIGHT) {
+ return false;
+ }
+
+ aVisibleRegionScreen->push_back(visibleRectScreen);
+ aIsVisible = true;
+ }
+
+ return true;
+}
+
+/* static */ bool
+HwcUtils::CalculateClipRect(const gfx::Matrix& transform,
+ const nsIntRect* aLayerClip,
+ nsIntRect aParentClip, nsIntRect* aRenderClip) {
+
+ gfxMatrix aTransform = gfx::ThebesMatrix(transform);
+ *aRenderClip = aParentClip;
+
+ if (!aLayerClip) {
+ return true;
+ }
+
+ if (aLayerClip->IsEmpty()) {
+ return false;
+ }
+
+ nsIntRect clip = *aLayerClip;
+
+ gfxRect r = ThebesRect(clip);
+ gfxRect trClip = aTransform.TransformBounds(r);
+ trClip.Round();
+ gfxUtils::GfxRectToIntRect(trClip, &clip);
+
+ aRenderClip->IntersectRect(*aRenderClip, clip);
+ return true;
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/HwcUtils.h b/widget/gonk/HwcUtils.h
new file mode 100644
index 0000000000..876ff8e99c
--- /dev/null
+++ b/widget/gonk/HwcUtils.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef mozilla_HwcUtils
+#define mozilla_HwcUtils
+
+#include "Layers.h"
+#include <vector>
+#include "hardware/hwcomposer.h"
+
+namespace mozilla {
+
+namespace gfx {
+class Matrix;
+}
+
+class HwcUtils {
+public:
+
+enum {
+ HWC_USE_GPU = HWC_FRAMEBUFFER,
+ HWC_USE_OVERLAY = HWC_OVERLAY,
+ HWC_USE_COPYBIT
+};
+
+// HWC layer flags
+enum {
+ // Draw a solid color rectangle
+ // The color should be set on the transform member of the hwc_layer_t struct
+ // The expected format is a 32 bit ABGR with 8 bits per component
+ HWC_COLOR_FILL = 0x8,
+ // Swap the RB pixels of gralloc buffer, like RGBA<->BGRA or RGBX<->BGRX
+ // The flag will be set inside LayerRenderState
+ HWC_FORMAT_RB_SWAP = 0x40
+};
+
+typedef std::vector<hwc_rect_t> RectVector;
+
+/* Utility functions - implemented in HwcUtils.cpp */
+
+/**
+ * Calculates the layer's clipping rectangle
+ *
+ * @param aTransform Input. A transformation matrix
+ * It transforms the clip rect to screen space
+ * @param aLayerClip Input. The layer's internal clipping rectangle.
+ * This may be NULL which means the layer has no internal clipping
+ * The origin is the top-left corner of the layer
+ * @param aParentClip Input. The parent layer's rendering clipping rectangle
+ * The origin is the top-left corner of the screen
+ * @param aRenderClip Output. The layer's rendering clipping rectangle
+ * The origin is the top-left corner of the screen
+ * @return true if the layer should be rendered.
+ * false if the layer can be skipped
+ */
+static bool CalculateClipRect(const gfx::Matrix& aTransform,
+ const nsIntRect* aLayerClip,
+ nsIntRect aParentClip, nsIntRect* aRenderClip);
+
+
+/**
+ * Prepares hwc layer visible region required for hwc composition
+ *
+ * @param aVisible Input. Layer's unclipped visible region
+ * The origin is the top-left corner of the layer
+ * @param aLayerTransform Input. Layer's transformation matrix
+ * It transforms from layer space to screen space
+ * @param aLayerBufferTransform Input. Layer buffer's transformation matrix
+ * It transforms from layer buffer's space to screen space
+ * @param aClip Input. A clipping rectangle.
+ * The origin is the top-left corner of the screen
+ * @param aBufferRect Input. The layer's buffer bounds
+ * The origin is the top-left corner of the layer
+ * @param aVisibleRegionScreen Output. Visible region in screen space.
+ * The origin is the top-left corner of the screen
+ * @param aIsVisible Output. true if region is visible
+ * false if region is not visible
+ * @return true if region can be rendered by HWC.
+ * false if region should not be rendered by HWC
+ */
+static bool PrepareVisibleRegion(const nsIntRegion& aVisible,
+ const gfx::Matrix& aLayerTransform,
+ const gfx::Matrix& aLayerBufferTransform,
+ nsIntRect aClip, nsIntRect aBufferRect,
+ RectVector* aVisibleRegionScreen,
+ bool& aIsVisible);
+
+
+/**
+ * Sets hwc layer rectangles required for hwc composition
+ *
+ * @param aVisible Input. Layer's unclipped visible rectangle
+ * The origin is the top-left corner of the layer
+ * @param aLayerTransform Input. Layer's transformation matrix
+ * It transforms from layer space to screen space
+ * @param aLayerBufferTransform Input. Layer buffer's transformation matrix
+ * It transforms from layer buffer's space to screen space
+ * @param aClip Input. A clipping rectangle.
+ * The origin is the top-left corner of the screen
+ * @param aBufferRect Input. The layer's buffer bounds
+ * The origin is the top-left corner of the layer
+ * @param aYFlipped Input. true if the buffer is rendered as Y flipped
+ * @param aSurceCrop Output. Area of the source to consider,
+ * the origin is the top-left corner of the buffer
+ * @param aVisibleRegionScreen Output. Visible region in screen space.
+ * The origin is the top-left corner of the screen
+ * @return true if the layer should be rendered.
+ * false if the layer can be skipped
+ */
+static bool PrepareLayerRects(nsIntRect aVisible,
+ const gfx::Matrix& aLayerTransform,
+ const gfx::Matrix& aLayerBufferTransform,
+ nsIntRect aClip, nsIntRect aBufferRect,
+ bool aYFlipped,
+ hwc_rect_t* aSourceCrop,
+ hwc_rect_t* aVisibleRegionScreen);
+
+};
+
+} // namespace mozilla
+
+#endif // mozilla_HwcUtils
diff --git a/widget/gonk/OrientationObserver.cpp b/widget/gonk/OrientationObserver.cpp
new file mode 100644
index 0000000000..9096404cfb
--- /dev/null
+++ b/widget/gonk/OrientationObserver.cpp
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* 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 "base/basictypes.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Hal.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "OrientationObserver.h"
+#include "mozilla/HalSensor.h"
+#include "ProcessOrientation.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+using namespace dom;
+
+namespace {
+
+struct OrientationMapping {
+ uint32_t mScreenRotation;
+ ScreenOrientationInternal mDomOrientation;
+};
+
+static OrientationMapping sOrientationMappings[] = {
+ {nsIScreen::ROTATION_0_DEG, eScreenOrientation_PortraitPrimary},
+ {nsIScreen::ROTATION_180_DEG, eScreenOrientation_PortraitSecondary},
+ {nsIScreen::ROTATION_90_DEG, eScreenOrientation_LandscapePrimary},
+ {nsIScreen::ROTATION_270_DEG, eScreenOrientation_LandscapeSecondary},
+};
+
+const static uint32_t sDefaultLandscape = 2;
+const static uint32_t sDefaultPortrait = 0;
+
+static uint32_t sOrientationOffset = 0;
+
+static already_AddRefed<nsIScreen>
+GetPrimaryScreen()
+{
+ nsCOMPtr<nsIScreenManager> screenMgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ NS_ENSURE_TRUE(screenMgr, nullptr);
+
+ nsCOMPtr<nsIScreen> screen;
+ screenMgr->GetPrimaryScreen(getter_AddRefs(screen));
+ return screen.forget();
+}
+
+static void
+DetectDefaultOrientation()
+{
+ nsCOMPtr<nsIScreen> screen = GetPrimaryScreen();
+ if (!screen) {
+ return;
+ }
+
+ int32_t left, top, width, height;
+ if (NS_FAILED(screen->GetRect(&left, &top, &width, &height))) {
+ return;
+ }
+
+ uint32_t rotation;
+ if (NS_FAILED(screen->GetRotation(&rotation))) {
+ return;
+ }
+
+ if (width < height) {
+ if (rotation == nsIScreen::ROTATION_0_DEG ||
+ rotation == nsIScreen::ROTATION_180_DEG) {
+ sOrientationOffset = sDefaultPortrait;
+ } else {
+ sOrientationOffset = sDefaultLandscape;
+ }
+ } else {
+ if (rotation == nsIScreen::ROTATION_0_DEG ||
+ rotation == nsIScreen::ROTATION_180_DEG) {
+ sOrientationOffset = sDefaultLandscape;
+ } else {
+ sOrientationOffset = sDefaultPortrait;
+ }
+ }
+}
+
+/**
+ * Converts DOM orientation to nsIScreen rotation. Portrait and Landscape are
+ * treated as PortraitPrimary and LandscapePrimary, respectively, during
+ * conversion.
+ *
+ * @param aOrientation DOM orientation e.g.
+ * dom::eScreenOrientation_PortraitPrimary.
+ * @param aResult output nsIScreen rotation e.g. nsIScreen::ROTATION_0_DEG.
+ * @return NS_OK on success. NS_ILLEGAL_VALUE on failure.
+ */
+static nsresult
+ConvertToScreenRotation(ScreenOrientationInternal aOrientation, uint32_t *aResult)
+{
+ for (uint32_t i = 0; i < ArrayLength(sOrientationMappings); i++) {
+ if (aOrientation & sOrientationMappings[i].mDomOrientation) {
+ // Shift the mappings in sOrientationMappings so devices with default
+ // landscape orientation map landscape-primary to 0 degree and so forth.
+ int adjusted = (i + sOrientationOffset) %
+ ArrayLength(sOrientationMappings);
+ *aResult = sOrientationMappings[adjusted].mScreenRotation;
+ return NS_OK;
+ }
+ }
+
+ *aResult = nsIScreen::ROTATION_0_DEG;
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+/**
+ * Converts nsIScreen rotation to DOM orientation.
+ *
+ * @param aRotation nsIScreen rotation e.g. nsIScreen::ROTATION_0_DEG.
+ * @param aResult output DOM orientation e.g.
+ * dom::eScreenOrientation_PortraitPrimary.
+ * @return NS_OK on success. NS_ILLEGAL_VALUE on failure.
+ */
+nsresult
+ConvertToDomOrientation(uint32_t aRotation, ScreenOrientationInternal *aResult)
+{
+ for (uint32_t i = 0; i < ArrayLength(sOrientationMappings); i++) {
+ if (aRotation == sOrientationMappings[i].mScreenRotation) {
+ // Shift the mappings in sOrientationMappings so devices with default
+ // landscape orientation map 0 degree to landscape-primary and so forth.
+ int adjusted = (i + sOrientationOffset) %
+ ArrayLength(sOrientationMappings);
+ *aResult = sOrientationMappings[adjusted].mDomOrientation;
+ return NS_OK;
+ }
+ }
+
+ *aResult = eScreenOrientation_None;
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+// Note that all operations with sOrientationSensorObserver
+// should be on the main thread.
+static StaticAutoPtr<OrientationObserver> sOrientationSensorObserver;
+
+} // namespace
+
+OrientationObserver*
+OrientationObserver::GetInstance()
+{
+ if (!sOrientationSensorObserver) {
+ sOrientationSensorObserver = new OrientationObserver();
+ ClearOnShutdown(&sOrientationSensorObserver);
+ }
+
+ return sOrientationSensorObserver;
+}
+
+OrientationObserver::OrientationObserver()
+ : mAutoOrientationEnabled(false)
+ , mAllowedOrientations(sDefaultOrientations)
+ , mOrientation(new mozilla::ProcessOrientation())
+{
+ DetectDefaultOrientation();
+
+ EnableAutoOrientation();
+}
+
+OrientationObserver::~OrientationObserver()
+{
+ if (mAutoOrientationEnabled) {
+ DisableAutoOrientation();
+ }
+}
+
+/* static */ void
+OrientationObserver::ShutDown()
+{
+ if (!sOrientationSensorObserver) {
+ return;
+ }
+
+ if (sOrientationSensorObserver->mAutoOrientationEnabled) {
+ sOrientationSensorObserver->DisableAutoOrientation();
+ }
+}
+
+void
+OrientationObserver::Notify(const hal::SensorData& aSensorData)
+{
+ // Sensor will call us on the main thread.
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aSensorData.sensor() == hal::SensorType::SENSOR_ACCELERATION);
+
+ nsCOMPtr<nsIScreen> screen = GetPrimaryScreen();
+ if (!screen) {
+ return;
+ }
+
+ uint32_t currRotation;
+ if(NS_FAILED(screen->GetRotation(&currRotation))) {
+ return;
+ }
+
+ int rotation = mOrientation->OnSensorChanged(aSensorData, static_cast<int>(currRotation));
+ if (rotation < 0 || uint32_t(rotation) == currRotation) {
+ return;
+ }
+
+ ScreenOrientationInternal orientation;
+ if (NS_FAILED(ConvertToDomOrientation(rotation, &orientation))) {
+ return;
+ }
+
+ if ((mAllowedOrientations & orientation) == eScreenOrientation_None) {
+ // The orientation from sensor is not allowed.
+ return;
+ }
+
+ if (NS_FAILED(screen->SetRotation(static_cast<uint32_t>(rotation)))) {
+ // Don't notify dom on rotation failure.
+ return;
+ }
+}
+
+/**
+ * Register the observer. Note that the observer shouldn't be registered.
+ */
+void
+OrientationObserver::EnableAutoOrientation()
+{
+ MOZ_ASSERT(NS_IsMainThread() && !mAutoOrientationEnabled);
+
+ mOrientation->Reset();
+ hal::RegisterSensorObserver(hal::SENSOR_ACCELERATION, this);
+ mAutoOrientationEnabled = true;
+}
+
+/**
+ * Unregister the observer. Note that the observer should already be registered.
+ */
+void
+OrientationObserver::DisableAutoOrientation()
+{
+ MOZ_ASSERT(NS_IsMainThread() && mAutoOrientationEnabled);
+
+ hal::UnregisterSensorObserver(hal::SENSOR_ACCELERATION, this);
+ mAutoOrientationEnabled = false;
+}
+
+bool
+OrientationObserver::LockScreenOrientation(ScreenOrientationInternal aOrientation)
+{
+ MOZ_ASSERT(aOrientation | (eScreenOrientation_PortraitPrimary |
+ eScreenOrientation_PortraitSecondary |
+ eScreenOrientation_LandscapePrimary |
+ eScreenOrientation_LandscapeSecondary |
+ eScreenOrientation_Default));
+
+ if (aOrientation == eScreenOrientation_Default) {
+ aOrientation = (sOrientationOffset == sDefaultPortrait) ?
+ eScreenOrientation_PortraitPrimary :
+ eScreenOrientation_LandscapePrimary;
+ }
+
+ // If there are multiple orientations allowed, we should enable the
+ // auto-rotation.
+ if (aOrientation != eScreenOrientation_LandscapePrimary &&
+ aOrientation != eScreenOrientation_LandscapeSecondary &&
+ aOrientation != eScreenOrientation_PortraitPrimary &&
+ aOrientation != eScreenOrientation_PortraitSecondary) {
+ if (!mAutoOrientationEnabled) {
+ EnableAutoOrientation();
+ }
+ } else if (mAutoOrientationEnabled) {
+ DisableAutoOrientation();
+ }
+
+ mAllowedOrientations = aOrientation;
+
+ nsCOMPtr<nsIScreen> screen = GetPrimaryScreen();
+ NS_ENSURE_TRUE(screen, false);
+
+ uint32_t currRotation;
+ nsresult rv = screen->GetRotation(&currRotation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ ScreenOrientationInternal currOrientation = eScreenOrientation_None;
+ rv = ConvertToDomOrientation(currRotation, &currOrientation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Don't rotate if the current orientation matches one of the
+ // requested orientations.
+ if (currOrientation & aOrientation) {
+ return true;
+ }
+
+ // Return false on invalid orientation value.
+ uint32_t rotation;
+ rv = ConvertToScreenRotation(aOrientation, &rotation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = screen->SetRotation(rotation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // This conversion will disambiguate aOrientation.
+ ScreenOrientationInternal orientation;
+ rv = ConvertToDomOrientation(rotation, &orientation);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+void
+OrientationObserver::UnlockScreenOrientation()
+{
+ if (!mAutoOrientationEnabled) {
+ EnableAutoOrientation();
+ }
+
+ mAllowedOrientations = sDefaultOrientations;
+}
diff --git a/widget/gonk/OrientationObserver.h b/widget/gonk/OrientationObserver.h
new file mode 100644
index 0000000000..c841ea8781
--- /dev/null
+++ b/widget/gonk/OrientationObserver.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* 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.
+ */
+
+#ifndef OrientationObserver_h
+#define OrientationObserver_h
+
+#include "mozilla/Observer.h"
+#include "mozilla/dom/ScreenOrientation.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+class ProcessOrientation;
+namespace hal {
+class SensorData;
+typedef mozilla::Observer<SensorData> ISensorObserver;
+} // namespace hal
+} // namespace mozilla
+
+using mozilla::hal::ISensorObserver;
+using mozilla::hal::SensorData;
+using mozilla::dom::ScreenOrientationInternal;
+
+class OrientationObserver : public ISensorObserver {
+public:
+ OrientationObserver();
+ ~OrientationObserver();
+
+ // Call DisableAutoOrientation on the existing OrientatiOnobserver singleton,
+ // if it exists. If no OrientationObserver exists, do nothing.
+ static void ShutDown();
+
+ // Notification from sensor.
+ void Notify(const SensorData& aSensorData);
+
+ // Methods to enable/disable automatic orientation.
+ void EnableAutoOrientation();
+ void DisableAutoOrientation();
+
+ // Methods called by methods in hal_impl namespace.
+ bool LockScreenOrientation(ScreenOrientationInternal aOrientation);
+ void UnlockScreenOrientation();
+
+ static OrientationObserver* GetInstance();
+
+private:
+ bool mAutoOrientationEnabled;
+ uint32_t mAllowedOrientations;
+ mozilla::UniquePtr<mozilla::ProcessOrientation> mOrientation;
+
+ static const uint32_t sDefaultOrientations =
+ mozilla::dom::eScreenOrientation_PortraitPrimary |
+ mozilla::dom::eScreenOrientation_PortraitSecondary |
+ mozilla::dom::eScreenOrientation_LandscapePrimary |
+ mozilla::dom::eScreenOrientation_LandscapeSecondary;
+};
+
+#endif
diff --git a/widget/gonk/ProcessOrientation.cpp b/widget/gonk/ProcessOrientation.cpp
new file mode 100644
index 0000000000..bbdcface8a
--- /dev/null
+++ b/widget/gonk/ProcessOrientation.cpp
@@ -0,0 +1,519 @@
+/*
+ * Copyright (c) 2013, Linux Foundation. All rights reserved
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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 "base/basictypes.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Unused.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "OrientationObserver.h"
+#include "ProcessOrientation.h"
+#include "mozilla/HalSensor.h"
+#include "math.h"
+#include "limits.h"
+#include "android/log.h"
+
+#if 0
+#define LOGD(args...) __android_log_print(ANDROID_LOG_DEBUG, "ProcessOrientation" , ## args)
+#else
+#define LOGD(args...)
+#endif
+
+namespace mozilla {
+
+// We work with all angles in degrees in this class.
+#define RADIANS_TO_DEGREES (180/M_PI)
+
+// Number of nanoseconds per millisecond.
+#define NANOS_PER_MS 1000000
+
+// Indices into SensorEvent.values for the accelerometer sensor.
+#define ACCELEROMETER_DATA_X 0
+#define ACCELEROMETER_DATA_Y 1
+#define ACCELEROMETER_DATA_Z 2
+
+// The minimum amount of time that a predicted rotation must be stable before
+// it is accepted as a valid rotation proposal. This value can be quite small
+// because the low-pass filter already suppresses most of the noise so we're
+// really just looking for quick confirmation that the last few samples are in
+// agreement as to the desired orientation.
+#define PROPOSAL_SETTLE_TIME_NANOS (40*NANOS_PER_MS)
+
+// The minimum amount of time that must have elapsed since the device last
+// exited the flat state (time since it was picked up) before the proposed
+// rotation can change.
+#define PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS (500*NANOS_PER_MS)
+
+// The minimum amount of time that must have elapsed since the device stopped
+// swinging (time since device appeared to be in the process of being put down
+// or put away into a pocket) before the proposed rotation can change.
+#define PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS (300*NANOS_PER_MS)
+
+// The minimum amount of time that must have elapsed since the device stopped
+// undergoing external acceleration before the proposed rotation can change.
+#define PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS (500*NANOS_PER_MS)
+
+// If the tilt angle remains greater than the specified angle for a minimum of
+// the specified time, then the device is deemed to be lying flat
+// (just chillin' on a table).
+#define FLAT_ANGLE 75
+#define FLAT_TIME_NANOS (1000*NANOS_PER_MS)
+
+// If the tilt angle has increased by at least delta degrees within the
+// specified amount of time, then the device is deemed to be swinging away
+// from the user down towards flat (tilt = 90).
+#define SWING_AWAY_ANGLE_DELTA 20
+#define SWING_TIME_NANOS (300*NANOS_PER_MS)
+
+// The maximum sample inter-arrival time in milliseconds. If the acceleration
+// samples are further apart than this amount in time, we reset the state of
+// the low-pass filter and orientation properties. This helps to handle
+// boundary conditions when the device is turned on, wakes from suspend or
+// there is a significant gap in samples.
+#define MAX_FILTER_DELTA_TIME_NANOS (1000*NANOS_PER_MS)
+
+// The acceleration filter time constant.
+//
+// This time constant is used to tune the acceleration filter such that
+// impulses and vibrational noise (think car dock) is suppressed before we try
+// to calculate the tilt and orientation angles.
+//
+// The filter time constant is related to the filter cutoff frequency, which
+// is the frequency at which signals are attenuated by 3dB (half the passband
+// power). Each successive octave beyond this frequency is attenuated by an
+// additional 6dB.
+//
+// Given a time constant t in seconds, the filter cutoff frequency Fc in Hertz
+// is given by Fc = 1 / (2pi * t).
+//
+// The higher the time constant, the lower the cutoff frequency, so more noise
+// will be suppressed.
+//
+// Filtering adds latency proportional the time constant (inversely
+// proportional to the cutoff frequency) so we don't want to make the time
+// constant too large or we can lose responsiveness. Likewise we don't want
+// to make it too small or we do a poor job suppressing acceleration spikes.
+// Empirically, 100ms seems to be too small and 500ms is too large. Android
+// default is 200.
+#define FILTER_TIME_CONSTANT_MS 200.0f
+
+// State for orientation detection. Thresholds for minimum and maximum
+// allowable deviation from gravity.
+//
+// If the device is undergoing external acceleration (being bumped, in a car
+// that is turning around a corner or a plane taking off) then the magnitude
+// may be substantially more or less than gravity. This can skew our
+// orientation detection by making us think that up is pointed in a different
+// direction.
+//
+// Conversely, if the device is in freefall, then there will be no gravity to
+// measure at all. This is problematic because we cannot detect the orientation
+// without gravity to tell us which way is up. A magnitude near 0 produces
+// singularities in the tilt and orientation calculations.
+//
+// In both cases, we postpone choosing an orientation.
+//
+// However, we need to tolerate some acceleration because the angular momentum
+// of turning the device can skew the observed acceleration for a short period
+// of time.
+#define NEAR_ZERO_MAGNITUDE 1 // m/s^2
+#define ACCELERATION_TOLERANCE 4 // m/s^2
+#define STANDARD_GRAVITY 9.80665f
+#define MIN_ACCELERATION_MAGNITUDE (STANDARD_GRAVITY-ACCELERATION_TOLERANCE)
+#define MAX_ACCELERATION_MAGNITUDE (STANDARD_GRAVITY+ACCELERATION_TOLERANCE)
+
+// Maximum absolute tilt angle at which to consider orientation data. Beyond
+// this (i.e. when screen is facing the sky or ground), we completely ignore
+// orientation data.
+#define MAX_TILT 75
+
+// The gap angle in degrees between adjacent orientation angles for
+// hysteresis.This creates a "dead zone" between the current orientation and a
+// proposed adjacent orientation. No orientation proposal is made when the
+// orientation angle is within the gap between the current orientation and the
+// adjacent orientation.
+#define ADJACENT_ORIENTATION_ANGLE_GAP 45
+
+const int
+ProcessOrientation::tiltTolerance[][4] = {
+ {-25, 70}, // ROTATION_0
+ {-25, 65}, // ROTATION_90
+ {-25, 60}, // ROTATION_180
+ {-25, 65} // ROTATION_270
+};
+
+int
+ProcessOrientation::GetProposedRotation()
+{
+ return mProposedRotation;
+}
+
+int
+ProcessOrientation::OnSensorChanged(const SensorData& event,
+ int deviceCurrentRotation)
+{
+ // The vector given in the SensorEvent points straight up (towards the sky)
+ // under ideal conditions (the phone is not accelerating). I'll call this up
+ // vector elsewhere.
+ const InfallibleTArray<float>& values = event.values();
+ float x = values[ACCELEROMETER_DATA_X];
+ float y = values[ACCELEROMETER_DATA_Y];
+ float z = values[ACCELEROMETER_DATA_Z];
+
+ LOGD
+ ("ProcessOrientation: Raw acceleration vector: x = %f, y = %f, z = %f,"
+ "magnitude = %f\n", x, y, z, sqrt(x * x + y * y + z * z));
+ // Apply a low-pass filter to the acceleration up vector in cartesian space.
+ // Reset the orientation listener state if the samples are too far apart in
+ // time or when we see values of (0, 0, 0) which indicates that we polled the
+ // accelerometer too soon after turning it on and we don't have any data yet.
+ const int64_t now = (int64_t) event.timestamp();
+ const int64_t then = mLastFilteredTimestampNanos;
+ const float timeDeltaMS = (now - then) * 0.000001f;
+ bool skipSample = false;
+ if (now < then
+ || now > then + MAX_FILTER_DELTA_TIME_NANOS
+ || (x == 0 && y == 0 && z == 0)) {
+ LOGD
+ ("ProcessOrientation: Resetting orientation listener.");
+ Reset();
+ skipSample = true;
+ } else {
+ const float alpha = timeDeltaMS / (FILTER_TIME_CONSTANT_MS + timeDeltaMS);
+ x = alpha * (x - mLastFilteredX) + mLastFilteredX;
+ y = alpha * (y - mLastFilteredY) + mLastFilteredY;
+ z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
+ LOGD
+ ("ProcessOrientation: Filtered acceleration vector: x=%f, y=%f, z=%f,"
+ "magnitude=%f", z, y, z, sqrt(x * x + y * y + z * z));
+ skipSample = false;
+ }
+ mLastFilteredTimestampNanos = now;
+ mLastFilteredX = x;
+ mLastFilteredY = y;
+ mLastFilteredZ = z;
+
+ bool isAccelerating = false;
+ bool isFlat = false;
+ bool isSwinging = false;
+ if (skipSample) {
+ return -1;
+ }
+
+ // Calculate the magnitude of the acceleration vector.
+ const float magnitude = sqrt(x * x + y * y + z * z);
+ if (magnitude < NEAR_ZERO_MAGNITUDE) {
+ LOGD
+ ("ProcessOrientation: Ignoring sensor data, magnitude too close to"
+ " zero.");
+ ClearPredictedRotation();
+ } else {
+ // Determine whether the device appears to be undergoing external
+ // acceleration.
+ if (this->IsAccelerating(magnitude)) {
+ isAccelerating = true;
+ mAccelerationTimestampNanos = now;
+ }
+ // Calculate the tilt angle. This is the angle between the up vector and
+ // the x-y plane (the plane of the screen) in a range of [-90, 90]
+ // degrees.
+ // -90 degrees: screen horizontal and facing the ground (overhead)
+ // 0 degrees: screen vertical
+ // 90 degrees: screen horizontal and facing the sky (on table)
+ const int tiltAngle =
+ static_cast<int>(roundf(asin(z / magnitude) * RADIANS_TO_DEGREES));
+ AddTiltHistoryEntry(now, tiltAngle);
+
+ // Determine whether the device appears to be flat or swinging.
+ if (this->IsFlat(now)) {
+ isFlat = true;
+ mFlatTimestampNanos = now;
+ }
+ if (this->IsSwinging(now, tiltAngle)) {
+ isSwinging = true;
+ mSwingTimestampNanos = now;
+ }
+ // If the tilt angle is too close to horizontal then we cannot determine
+ // the orientation angle of the screen.
+ if (abs(tiltAngle) > MAX_TILT) {
+ LOGD
+ ("ProcessOrientation: Ignoring sensor data, tilt angle too high:"
+ " tiltAngle=%d", tiltAngle);
+ ClearPredictedRotation();
+ } else {
+ // Calculate the orientation angle.
+ // This is the angle between the x-y projection of the up vector onto
+ // the +y-axis, increasing clockwise in a range of [0, 360] degrees.
+ int orientationAngle =
+ static_cast<int>(roundf(-atan2f(-x, y) * RADIANS_TO_DEGREES));
+ if (orientationAngle < 0) {
+ // atan2 returns [-180, 180]; normalize to [0, 360]
+ orientationAngle += 360;
+ }
+ // Find the nearest rotation.
+ int nearestRotation = (orientationAngle + 45) / 90;
+ if (nearestRotation == 4) {
+ nearestRotation = 0;
+ }
+ // Determine the predicted orientation.
+ if (IsTiltAngleAcceptable(nearestRotation, tiltAngle)
+ &&
+ IsOrientationAngleAcceptable
+ (nearestRotation, orientationAngle, deviceCurrentRotation)) {
+ UpdatePredictedRotation(now, nearestRotation);
+ LOGD
+ ("ProcessOrientation: Predicted: tiltAngle=%d, orientationAngle=%d,"
+ " predictedRotation=%d, predictedRotationAgeMS=%f",
+ tiltAngle,
+ orientationAngle,
+ mPredictedRotation,
+ ((now - mPredictedRotationTimestampNanos) * 0.000001f));
+ } else {
+ LOGD
+ ("ProcessOrientation: Ignoring sensor data, no predicted rotation:"
+ " tiltAngle=%d, orientationAngle=%d",
+ tiltAngle,
+ orientationAngle);
+ ClearPredictedRotation();
+ }
+ }
+ }
+
+ // Determine new proposed rotation.
+ const int oldProposedRotation = mProposedRotation;
+ if (mPredictedRotation < 0 || IsPredictedRotationAcceptable(now)) {
+ mProposedRotation = mPredictedRotation;
+ }
+ // Write final statistics about where we are in the orientation detection
+ // process.
+ LOGD
+ ("ProcessOrientation: Result: oldProposedRotation=%d,currentRotation=%d, "
+ "proposedRotation=%d, predictedRotation=%d, timeDeltaMS=%f, "
+ "isAccelerating=%d, isFlat=%d, isSwinging=%d, timeUntilSettledMS=%f, "
+ "timeUntilAccelerationDelayExpiredMS=%f, timeUntilFlatDelayExpiredMS=%f, "
+ "timeUntilSwingDelayExpiredMS=%f",
+ oldProposedRotation,
+ deviceCurrentRotation, mProposedRotation,
+ mPredictedRotation, timeDeltaMS, isAccelerating, isFlat,
+ isSwinging, RemainingMS(now,
+ mPredictedRotationTimestampNanos +
+ PROPOSAL_SETTLE_TIME_NANOS),
+ RemainingMS(now,
+ mAccelerationTimestampNanos +
+ PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS),
+ RemainingMS(now,
+ mFlatTimestampNanos +
+ PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS),
+ RemainingMS(now,
+ mSwingTimestampNanos +
+ PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS));
+
+ // Avoid unused-but-set compile warnings for these variables, when LOGD is
+ // a no-op, as it is by default:
+ Unused << isAccelerating;
+ Unused << isFlat;
+ Unused << isSwinging;
+
+ // Tell the listener.
+ if (mProposedRotation != oldProposedRotation && mProposedRotation >= 0) {
+ LOGD
+ ("ProcessOrientation: Proposed rotation changed! proposedRotation=%d, "
+ "oldProposedRotation=%d",
+ mProposedRotation,
+ oldProposedRotation);
+ return mProposedRotation;
+ }
+ // Don't rotate screen
+ return -1;
+}
+
+bool
+ProcessOrientation::IsTiltAngleAcceptable(int rotation, int tiltAngle)
+{
+ return (tiltAngle >= tiltTolerance[rotation][0]
+ && tiltAngle <= tiltTolerance[rotation][1]);
+}
+
+bool
+ProcessOrientation::IsOrientationAngleAcceptable(int rotation,
+ int orientationAngle,
+ int currentRotation)
+{
+ // If there is no current rotation, then there is no gap.
+ // The gap is used only to introduce hysteresis among advertised orientation
+ // changes to avoid flapping.
+ if (currentRotation < 0) {
+ return true;
+ }
+ // If the specified rotation is the same or is counter-clockwise adjacent
+ // to the current rotation, then we set a lower bound on the orientation
+ // angle. For example, if currentRotation is ROTATION_0 and proposed is
+ // ROTATION_90, then we want to check orientationAngle > 45 + GAP / 2.
+ if (rotation == currentRotation || rotation == (currentRotation + 1) % 4) {
+ int lowerBound = rotation * 90 - 45 + ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (rotation == 0) {
+ if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
+ return false;
+ }
+ } else {
+ if (orientationAngle < lowerBound) {
+ return false;
+ }
+ }
+ }
+ // If the specified rotation is the same or is clockwise adjacent, then we
+ // set an upper bound on the orientation angle. For example, if
+ // currentRotation is ROTATION_0 and rotation is ROTATION_270, then we want
+ // to check orientationAngle < 315 - GAP / 2.
+ if (rotation == currentRotation || rotation == (currentRotation + 3) % 4) {
+ int upperBound = rotation * 90 + 45 - ADJACENT_ORIENTATION_ANGLE_GAP / 2;
+ if (rotation == 0) {
+ if (orientationAngle <= 45 && orientationAngle > upperBound) {
+ return false;
+ }
+ } else {
+ if (orientationAngle > upperBound) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool
+ProcessOrientation::IsPredictedRotationAcceptable(int64_t now)
+{
+ // The predicted rotation must have settled long enough.
+ if (now < mPredictedRotationTimestampNanos + PROPOSAL_SETTLE_TIME_NANOS) {
+ return false;
+ }
+ // The last flat state (time since picked up) must have been sufficiently long
+ // ago.
+ if (now < mFlatTimestampNanos + PROPOSAL_MIN_TIME_SINCE_FLAT_ENDED_NANOS) {
+ return false;
+ }
+ // The last swing state (time since last movement to put down) must have been
+ // sufficiently long ago.
+ if (now < mSwingTimestampNanos + PROPOSAL_MIN_TIME_SINCE_SWING_ENDED_NANOS) {
+ return false;
+ }
+ // The last acceleration state must have been sufficiently long ago.
+ if (now < mAccelerationTimestampNanos
+ + PROPOSAL_MIN_TIME_SINCE_ACCELERATION_ENDED_NANOS) {
+ return false;
+ }
+ // Looks good!
+ return true;
+}
+
+int
+ProcessOrientation::Reset()
+{
+ mLastFilteredTimestampNanos = std::numeric_limits<int64_t>::min();
+ mProposedRotation = -1;
+ mFlatTimestampNanos = std::numeric_limits<int64_t>::min();
+ mSwingTimestampNanos = std::numeric_limits<int64_t>::min();
+ mAccelerationTimestampNanos = std::numeric_limits<int64_t>::min();
+ ClearPredictedRotation();
+ ClearTiltHistory();
+ return -1;
+}
+
+void
+ProcessOrientation::ClearPredictedRotation()
+{
+ mPredictedRotation = -1;
+ mPredictedRotationTimestampNanos = std::numeric_limits<int64_t>::min();
+}
+
+void
+ProcessOrientation::UpdatePredictedRotation(int64_t now, int rotation)
+{
+ if (mPredictedRotation != rotation) {
+ mPredictedRotation = rotation;
+ mPredictedRotationTimestampNanos = now;
+ }
+}
+
+bool
+ProcessOrientation::IsAccelerating(float magnitude)
+{
+ return magnitude < MIN_ACCELERATION_MAGNITUDE
+ || magnitude > MAX_ACCELERATION_MAGNITUDE;
+}
+
+void
+ProcessOrientation::ClearTiltHistory()
+{
+ mTiltHistory.history[0].timestampNanos = std::numeric_limits<int64_t>::min();
+ mTiltHistory.index = 1;
+}
+
+void
+ProcessOrientation::AddTiltHistoryEntry(int64_t now, float tilt)
+{
+ mTiltHistory.history[mTiltHistory.index].tiltAngle = tilt;
+ mTiltHistory.history[mTiltHistory.index].timestampNanos = now;
+ mTiltHistory.index = (mTiltHistory.index + 1) % TILT_HISTORY_SIZE;
+ mTiltHistory.history[mTiltHistory.index].timestampNanos = std::numeric_limits<int64_t>::min();
+}
+
+bool
+ProcessOrientation::IsFlat(int64_t now)
+{
+ for (int i = mTiltHistory.index; (i = NextTiltHistoryIndex(i)) >= 0;) {
+ if (mTiltHistory.history[i].tiltAngle < FLAT_ANGLE) {
+ break;
+ }
+ if (mTiltHistory.history[i].timestampNanos + FLAT_TIME_NANOS <= now) {
+ // Tilt has remained greater than FLAT_TILT_ANGLE for FLAT_TIME_NANOS.
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+ProcessOrientation::IsSwinging(int64_t now, float tilt)
+{
+ for (int i = mTiltHistory.index; (i = NextTiltHistoryIndex(i)) >= 0;) {
+ if (mTiltHistory.history[i].timestampNanos + SWING_TIME_NANOS < now) {
+ break;
+ }
+ if (mTiltHistory.history[i].tiltAngle + SWING_AWAY_ANGLE_DELTA <= tilt) {
+ // Tilted away by SWING_AWAY_ANGLE_DELTA within SWING_TIME_NANOS.
+ return true;
+ }
+ }
+ return false;
+}
+
+int
+ProcessOrientation::NextTiltHistoryIndex(int index)
+{
+ index = (index == 0 ? TILT_HISTORY_SIZE : index) - 1;
+ return mTiltHistory.history[index].timestampNanos != std::numeric_limits<int64_t>::min() ? index : -1;
+}
+
+float
+ProcessOrientation::RemainingMS(int64_t now, int64_t until)
+{
+ return now >= until ? 0 : (until - now) * 0.000001f;
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/ProcessOrientation.h b/widget/gonk/ProcessOrientation.h
new file mode 100644
index 0000000000..d6d4bc3b6c
--- /dev/null
+++ b/widget/gonk/ProcessOrientation.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2013, Linux Foundation. All rights reserved
+ *
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef ProcessOrientation_h
+#define ProcessOrientation_h
+
+#include "mozilla/Hal.h"
+
+namespace mozilla {
+
+// History of observed tilt angles.
+#define TILT_HISTORY_SIZE 40
+
+class ProcessOrientation {
+public:
+ ProcessOrientation() {};
+ ~ProcessOrientation() {};
+
+ int OnSensorChanged(const mozilla::hal::SensorData& event, int deviceCurrentRotation);
+ int Reset();
+
+private:
+ int GetProposedRotation();
+
+ // Returns true if the tilt angle is acceptable for a given predicted
+ // rotation.
+ bool IsTiltAngleAcceptable(int rotation, int tiltAngle);
+
+ // Returns true if the orientation angle is acceptable for a given predicted
+ // rotation. This function takes into account the gap between adjacent
+ // orientations for hysteresis.
+ bool IsOrientationAngleAcceptable(int rotation, int orientationAngle,
+ int currentRotation);
+
+ // Returns true if the predicted rotation is ready to be advertised as a
+ // proposed rotation.
+ bool IsPredictedRotationAcceptable(int64_t now);
+
+ void ClearPredictedRotation();
+ void UpdatePredictedRotation(int64_t now, int rotation);
+ bool IsAccelerating(float magnitude);
+ void ClearTiltHistory();
+ void AddTiltHistoryEntry(int64_t now, float tilt);
+ bool IsFlat(int64_t now);
+ bool IsSwinging(int64_t now, float tilt);
+ int NextTiltHistoryIndex(int index);
+ float RemainingMS(int64_t now, int64_t until);
+
+ // The tilt angle range in degrees for each orientation. Beyond these tilt
+ // angles, we don't even consider transitioning into the specified orientation.
+ // We place more stringent requirements on unnatural orientations than natural
+ // ones to make it less likely to accidentally transition into those states.
+ // The first value of each pair is negative so it applies a limit when the
+ // device is facing down (overhead reading in bed). The second value of each
+ // pair is positive so it applies a limit when the device is facing up
+ // (resting on a table). The ideal tilt angle is 0 (when the device is vertical)
+ // so the limits establish how close to vertical the device must be in order
+ // to change orientation.
+ static const int tiltTolerance[][4];
+
+ // Timestamp and value of the last accelerometer sample.
+ int64_t mLastFilteredTimestampNanos;
+ float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
+
+ // The last proposed rotation, -1 if unknown.
+ int mProposedRotation;
+
+ // Value of the current predicted rotation, -1 if unknown.
+ int mPredictedRotation;
+
+ // Timestamp of when the predicted rotation most recently changed.
+ int64_t mPredictedRotationTimestampNanos;
+
+ // Timestamp when the device last appeared to be flat for sure (the flat delay
+ // elapsed).
+ int64_t mFlatTimestampNanos;
+
+ // Timestamp when the device last appeared to be swinging.
+ int64_t mSwingTimestampNanos;
+
+ // Timestamp when the device last appeared to be undergoing external
+ // acceleration.
+ int64_t mAccelerationTimestampNanos;
+
+ struct {
+ struct {
+ float tiltAngle;
+ int64_t timestampNanos;
+ } history[TILT_HISTORY_SIZE];
+ int index;
+ } mTiltHistory;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/widget/gonk/WidgetTraceEvent.cpp b/widget/gonk/WidgetTraceEvent.cpp
new file mode 100644
index 0000000000..558d9313e3
--- /dev/null
+++ b/widget/gonk/WidgetTraceEvent.cpp
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/WidgetTraceEvent.h"
+#include "mozilla/StaticPtr.h"
+#include "nsThreadUtils.h"
+#include <mozilla/CondVar.h>
+#include <mozilla/Mutex.h>
+
+using mozilla::CondVar;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+
+namespace mozilla {
+ class TracerRunnable : public Runnable {
+ public:
+ TracerRunnable() {
+ mTracerLock = new Mutex("TracerRunnable");
+ mTracerCondVar = new CondVar(*mTracerLock, "TracerRunnable");
+ mMainThread = do_GetMainThread();
+ }
+
+ ~TracerRunnable() {
+ delete mTracerCondVar;
+ delete mTracerLock;
+ mTracerLock = nullptr;
+ mTracerCondVar = nullptr;
+ }
+
+ virtual nsresult Run() {
+ MutexAutoLock lock(*mTracerLock);
+ mHasRun = true;
+ mTracerCondVar->Notify();
+ return NS_OK;
+ }
+
+ bool Fire() {
+ if (!mTracerLock || !mTracerCondVar) {
+ return false;
+ }
+
+ MutexAutoLock lock(*mTracerLock);
+ mHasRun = false;
+ mMainThread->Dispatch(this, NS_DISPATCH_NORMAL);
+ while (!mHasRun) {
+ mTracerCondVar->Wait();
+ }
+ return true;
+ }
+
+ void Signal() {
+ MutexAutoLock lock(*mTracerLock);
+ mHasRun = true;
+ mTracerCondVar->Notify();
+ }
+
+ private:
+ Mutex* mTracerLock;
+ CondVar* mTracerCondVar;
+ bool mHasRun;
+ nsCOMPtr<nsIThread> mMainThread;
+ };
+
+ StaticRefPtr<TracerRunnable> sTracerRunnable;
+
+ bool InitWidgetTracing()
+ {
+ if (!sTracerRunnable) {
+ sTracerRunnable = new TracerRunnable();
+ }
+ return true;
+ }
+
+ void CleanUpWidgetTracing()
+ {
+ sTracerRunnable = nullptr;
+ }
+
+ bool FireAndWaitForTracerEvent()
+ {
+ if (sTracerRunnable) {
+ return sTracerRunnable->Fire();
+ }
+
+ return false;
+ }
+
+ void SignalTracerThread()
+ {
+ if (sTracerRunnable) {
+ return sTracerRunnable->Signal();
+ }
+ }
+} // namespace mozilla
+
diff --git a/widget/gonk/hwchal/HwcHAL.cpp b/widget/gonk/hwchal/HwcHAL.cpp
new file mode 100644
index 0000000000..1793b75e65
--- /dev/null
+++ b/widget/gonk/hwchal/HwcHAL.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2015 The Linux Foundation. All rights reserved.
+ *
+ * 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 "HwcHAL.h"
+#include "libdisplay/GonkDisplay.h"
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+
+HwcHAL::HwcHAL()
+ : HwcHALBase()
+{
+ // Some HALs don't want to open hwc twice.
+ // If GetDisplay already load hwc module, we don't need to load again
+ mHwc = (HwcDevice*)GetGonkDisplay()->GetHWCDevice();
+ if (!mHwc) {
+ printf_stderr("HwcHAL Error: Cannot load hwcomposer");
+ return;
+ }
+}
+
+HwcHAL::~HwcHAL()
+{
+ mHwc = nullptr;
+}
+
+bool
+HwcHAL::Query(QueryType aType)
+{
+ if (!mHwc || !mHwc->query) {
+ return false;
+ }
+
+ bool value = false;
+ int supported = 0;
+ if (mHwc->query(mHwc, static_cast<int>(aType), &supported) == 0/*android::NO_ERROR*/) {
+ value = !!supported;
+ }
+ return value;
+}
+
+int
+HwcHAL::Set(HwcList *aList,
+ uint32_t aDisp)
+{
+ MOZ_ASSERT(mHwc);
+ if (!mHwc) {
+ return -1;
+ }
+
+ HwcList *displays[HWC_NUM_DISPLAY_TYPES] = { nullptr };
+ displays[aDisp] = aList;
+ return mHwc->set(mHwc, HWC_NUM_DISPLAY_TYPES, displays);
+}
+
+int
+HwcHAL::ResetHwc()
+{
+ return Set(nullptr, HWC_DISPLAY_PRIMARY);
+}
+
+int
+HwcHAL::Prepare(HwcList *aList,
+ uint32_t aDisp,
+ hwc_rect_t aDispRect,
+ buffer_handle_t aHandle,
+ int aFenceFd)
+{
+ MOZ_ASSERT(mHwc);
+ if (!mHwc) {
+ printf_stderr("HwcHAL Error: HwcDevice doesn't exist. A fence might be leaked.");
+ return -1;
+ }
+
+ HwcList *displays[HWC_NUM_DISPLAY_TYPES] = { nullptr };
+ displays[aDisp] = aList;
+#if ANDROID_VERSION >= 18
+ aList->outbufAcquireFenceFd = -1;
+ aList->outbuf = nullptr;
+#endif
+ aList->retireFenceFd = -1;
+
+ const auto idx = aList->numHwLayers - 1;
+ aList->hwLayers[idx].hints = 0;
+ aList->hwLayers[idx].flags = 0;
+ aList->hwLayers[idx].transform = 0;
+ aList->hwLayers[idx].handle = aHandle;
+ aList->hwLayers[idx].blending = HWC_BLENDING_PREMULT;
+ aList->hwLayers[idx].compositionType = HWC_FRAMEBUFFER_TARGET;
+ SetCrop(aList->hwLayers[idx], aDispRect);
+ aList->hwLayers[idx].displayFrame = aDispRect;
+ aList->hwLayers[idx].visibleRegionScreen.numRects = 1;
+ aList->hwLayers[idx].visibleRegionScreen.rects = &aList->hwLayers[idx].displayFrame;
+ aList->hwLayers[idx].acquireFenceFd = aFenceFd;
+ aList->hwLayers[idx].releaseFenceFd = -1;
+#if ANDROID_VERSION >= 18
+ aList->hwLayers[idx].planeAlpha = 0xFF;
+#endif
+ return mHwc->prepare(mHwc, HWC_NUM_DISPLAY_TYPES, displays);
+}
+
+bool
+HwcHAL::SupportTransparency() const
+{
+#if ANDROID_VERSION >= 18
+ return true;
+#endif
+ return false;
+}
+
+uint32_t
+HwcHAL::GetGeometryChangedFlag(bool aGeometryChanged) const
+{
+#if ANDROID_VERSION >= 19
+ return aGeometryChanged ? HWC_GEOMETRY_CHANGED : 0;
+#else
+ return HWC_GEOMETRY_CHANGED;
+#endif
+}
+
+void
+HwcHAL::SetCrop(HwcLayer &aLayer,
+ const hwc_rect_t &aSrcCrop) const
+{
+ if (GetAPIVersion() >= HwcAPIVersion(1, 3)) {
+#if ANDROID_VERSION >= 19
+ aLayer.sourceCropf.left = aSrcCrop.left;
+ aLayer.sourceCropf.top = aSrcCrop.top;
+ aLayer.sourceCropf.right = aSrcCrop.right;
+ aLayer.sourceCropf.bottom = aSrcCrop.bottom;
+#endif
+ } else {
+ aLayer.sourceCrop = aSrcCrop;
+ }
+}
+
+bool
+HwcHAL::EnableVsync(bool aEnable)
+{
+ // Only support hardware vsync on kitkat, L and up due to inaccurate timings
+ // with JellyBean.
+#if (ANDROID_VERSION == 19 || ANDROID_VERSION >= 21)
+ if (!mHwc) {
+ return false;
+ }
+ return !mHwc->eventControl(mHwc,
+ HWC_DISPLAY_PRIMARY,
+ HWC_EVENT_VSYNC,
+ aEnable);
+#else
+ return false;
+#endif
+}
+
+bool
+HwcHAL::RegisterHwcEventCallback(const HwcHALProcs_t &aProcs)
+{
+ if (!mHwc || !mHwc->registerProcs) {
+ printf_stderr("Failed to get hwc\n");
+ return false;
+ }
+
+ // Disable Vsync first, and then register callback functions.
+ mHwc->eventControl(mHwc,
+ HWC_DISPLAY_PRIMARY,
+ HWC_EVENT_VSYNC,
+ false);
+ static const hwc_procs_t sHwcJBProcs = {aProcs.invalidate,
+ aProcs.vsync,
+ aProcs.hotplug};
+ mHwc->registerProcs(mHwc, &sHwcJBProcs);
+
+ // Only support hardware vsync on kitkat, L and up due to inaccurate timings
+ // with JellyBean.
+#if (ANDROID_VERSION == 19 || ANDROID_VERSION >= 21)
+ return true;
+#else
+ return false;
+#endif
+}
+
+uint32_t
+HwcHAL::GetAPIVersion() const
+{
+ if (!mHwc) {
+ // default value: HWC_MODULE_API_VERSION_0_1
+ return 1;
+ }
+ return mHwc->common.version;
+}
+
+// Create HwcHAL
+UniquePtr<HwcHALBase>
+HwcHALBase::CreateHwcHAL()
+{
+ return Move(MakeUnique<HwcHAL>());
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/hwchal/HwcHAL.h b/widget/gonk/hwchal/HwcHAL.h
new file mode 100644
index 0000000000..05cb6a45f1
--- /dev/null
+++ b/widget/gonk/hwchal/HwcHAL.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2015 The Linux Foundation. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef mozilla_HwcHAL
+#define mozilla_HwcHAL
+
+#include "HwcHALBase.h"
+
+namespace mozilla {
+
+class HwcHAL final : public HwcHALBase {
+public:
+ explicit HwcHAL();
+
+ virtual ~HwcHAL();
+
+ virtual bool HasHwc() const override { return static_cast<bool>(mHwc); }
+
+ virtual void SetEGLInfo(hwc_display_t aDpy,
+ hwc_surface_t aSur) override { }
+
+ virtual bool Query(QueryType aType) override;
+
+ virtual int Set(HwcList *aList,
+ uint32_t aDisp) override;
+
+ virtual int ResetHwc() override;
+
+ virtual int Prepare(HwcList *aList,
+ uint32_t aDisp,
+ hwc_rect_t aDispRect,
+ buffer_handle_t aHandle,
+ int aFenceFd) override;
+
+ virtual bool SupportTransparency() const override;
+
+ virtual uint32_t GetGeometryChangedFlag(bool aGeometryChanged) const override;
+
+ virtual void SetCrop(HwcLayer &aLayer,
+ const hwc_rect_t &aSrcCrop) const override;
+
+ virtual bool EnableVsync(bool aEnable) override;
+
+ virtual bool RegisterHwcEventCallback(const HwcHALProcs_t &aProcs) override;
+
+private:
+ uint32_t GetAPIVersion() const;
+
+private:
+ HwcDevice *mHwc = nullptr;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_HwcHAL
diff --git a/widget/gonk/hwchal/HwcHALBase.h b/widget/gonk/hwchal/HwcHALBase.h
new file mode 100644
index 0000000000..0ef00a3250
--- /dev/null
+++ b/widget/gonk/hwchal/HwcHALBase.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set ts=4 sw=4 sts=4 et: */
+/*
+ * Copyright (c) 2015 The Linux Foundation. All rights reserved.
+ *
+ * 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.
+ */
+
+#ifndef mozilla_HwcHALBase
+#define mozilla_HwcHALBase
+
+#include "mozilla/UniquePtr.h"
+#include "nsRect.h"
+
+#include <hardware/hwcomposer.h>
+
+#ifndef HWC_BLIT
+#if ANDROID_VERSION >= 21
+#define HWC_BLIT 0xFF
+#elif ANDROID_VERSION >= 17
+#define HWC_BLIT (HWC_FRAMEBUFFER_TARGET + 1)
+#else
+// ICS didn't support this. However, we define this
+// for passing compilation
+#define HWC_BLIT 0xFF
+#endif // #if ANDROID_VERSION
+#endif // #ifndef HWC_BLIT
+
+namespace mozilla {
+
+#if ANDROID_VERSION >= 17
+using HwcDevice = hwc_composer_device_1_t;
+using HwcList = hwc_display_contents_1_t;
+using HwcLayer = hwc_layer_1_t;
+#else
+using HwcDevice = hwc_composer_device_t;
+using HwcList = hwc_layer_list_t;
+using HwcLayer = hwc_layer_t;
+#endif
+
+// HwcHAL definition for HwcEvent callback types
+// Note: hwc_procs is different between ICS and later,
+// and the signature of invalidate is also different.
+// Use this wrap struct to hide the detail. BTW,
+// we don't have to register callback functions on ICS, so
+// there is no callbacks for ICS in HwcHALProcs.
+typedef struct HwcHALProcs {
+ void (*invalidate)(const struct hwc_procs* procs);
+ void (*vsync)(const struct hwc_procs* procs, int disp, int64_t timestamp);
+ void (*hotplug)(const struct hwc_procs* procs, int disp, int connected);
+} HwcHALProcs_t;
+
+// HwcHAL class
+// This class handle all the HAL related work
+// The purpose of HwcHAL is to make HwcComposer2D simpler.
+class HwcHALBase {
+
+public:
+ // Query Types. We can add more types easily in the future
+ enum class QueryType {
+ COLOR_FILL = 0x8,
+ RB_SWAP = 0x40
+ };
+
+public:
+ explicit HwcHALBase() = default;
+
+ virtual ~HwcHALBase() {}
+
+ // Create HwcHAL module, Only HwcComposer2D calls this.
+ // If other modules want to use HwcHAL, please use APIs in
+ // HwcComposer2D
+ static UniquePtr<HwcHALBase> CreateHwcHAL();
+
+ // Check if mHwc exists
+ virtual bool HasHwc() const = 0;
+
+ // Set EGL info (only ICS need this info)
+ virtual void SetEGLInfo(hwc_display_t aEGLDisplay,
+ hwc_surface_t aEGLSurface) = 0;
+
+ // HwcDevice query properties
+ virtual bool Query(QueryType aType) = 0;
+
+ // HwcDevice set
+ virtual int Set(HwcList *aList,
+ uint32_t aDisp) = 0;
+
+ // Reset HwcDevice
+ virtual int ResetHwc() = 0;
+
+ // HwcDevice prepare
+ virtual int Prepare(HwcList *aList,
+ uint32_t aDisp,
+ hwc_rect_t aDispRect,
+ buffer_handle_t aHandle,
+ int aFenceFd) = 0;
+
+ // Check transparency support
+ virtual bool SupportTransparency() const = 0;
+
+ // Get a geometry change flag
+ virtual uint32_t GetGeometryChangedFlag(bool aGeometryChanged) const = 0;
+
+ // Set crop help
+ virtual void SetCrop(HwcLayer &aLayer,
+ const hwc_rect_t &aSrcCrop) const = 0;
+
+ // Enable HW Vsync
+ virtual bool EnableVsync(bool aEnable) = 0;
+
+ // Register HW event callback functions
+ virtual bool RegisterHwcEventCallback(const HwcHALProcs_t &aProcs) = 0;
+
+protected:
+ constexpr static uint32_t HwcAPIVersion(uint32_t aMaj, uint32_t aMin) {
+ // HARDWARE_MAKE_API_VERSION_2, from Android hardware.h
+ return (((aMaj & 0xff) << 24) | ((aMin & 0xff) << 16) | (1 & 0xffff));
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_HwcHALBase
diff --git a/widget/gonk/libdisplay/BootAnimation.cpp b/widget/gonk/libdisplay/BootAnimation.cpp
new file mode 100644
index 0000000000..c275179fc8
--- /dev/null
+++ b/widget/gonk/libdisplay/BootAnimation.cpp
@@ -0,0 +1,726 @@
+/* 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 <algorithm>
+#include <endian.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <string>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <vector>
+#include "mozilla/FileUtils.h"
+#include "png.h"
+
+#include "android/log.h"
+#include "GonkDisplay.h"
+#include "hardware/gralloc.h"
+
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "Gonk", ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "Gonk", ## args)
+
+using namespace mozilla;
+using namespace std;
+
+static pthread_t sAnimationThread;
+static bool sRunAnimation;
+
+/* See http://www.pkware.com/documents/casestudies/APPNOTE.TXT */
+struct local_file_header {
+ uint32_t signature;
+ uint16_t min_version;
+ uint16_t general_flag;
+ uint16_t compression;
+ uint16_t lastmod_time;
+ uint16_t lastmod_date;
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+ uint16_t filename_size;
+ uint16_t extra_field_size;
+ char data[0];
+
+ uint32_t GetDataSize() const
+ {
+ return letoh32(uncompressed_size);
+ }
+
+ uint32_t GetSize() const
+ {
+ /* XXX account for data descriptor */
+ return sizeof(local_file_header) + letoh16(filename_size) +
+ letoh16(extra_field_size) + GetDataSize();
+ }
+
+ const char * GetData() const
+ {
+ return data + letoh16(filename_size) + letoh16(extra_field_size);
+ }
+} __attribute__((__packed__));
+
+struct data_descriptor {
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+} __attribute__((__packed__));
+
+struct cdir_entry {
+ uint32_t signature;
+ uint16_t creator_version;
+ uint16_t min_version;
+ uint16_t general_flag;
+ uint16_t compression;
+ uint16_t lastmod_time;
+ uint16_t lastmod_date;
+ uint32_t crc32;
+ uint32_t compressed_size;
+ uint32_t uncompressed_size;
+ uint16_t filename_size;
+ uint16_t extra_field_size;
+ uint16_t file_comment_size;
+ uint16_t disk_num;
+ uint16_t internal_attr;
+ uint32_t external_attr;
+ uint32_t offset;
+ char data[0];
+
+ uint32_t GetDataSize() const
+ {
+ return letoh32(compressed_size);
+ }
+
+ uint32_t GetSize() const
+ {
+ return sizeof(cdir_entry) + letoh16(filename_size) +
+ letoh16(extra_field_size) + letoh16(file_comment_size);
+ }
+
+ bool Valid() const
+ {
+ return signature == htole32(0x02014b50);
+ }
+} __attribute__((__packed__));
+
+struct cdir_end {
+ uint32_t signature;
+ uint16_t disk_num;
+ uint16_t cdir_disk;
+ uint16_t disk_entries;
+ uint16_t cdir_entries;
+ uint32_t cdir_size;
+ uint32_t cdir_offset;
+ uint16_t comment_size;
+ char comment[0];
+
+ bool Valid() const
+ {
+ return signature == htole32(0x06054b50);
+ }
+} __attribute__((__packed__));
+
+/* We don't have access to libjar and the zip reader in android
+ * doesn't quite fit what we want to do. */
+class ZipReader {
+ const char *mBuf;
+ const cdir_end *mEnd;
+ const char *mCdir_limit;
+ uint32_t mBuflen;
+
+public:
+ ZipReader() : mBuf(nullptr) {}
+ ~ZipReader() {
+ if (mBuf)
+ munmap((void *)mBuf, mBuflen);
+ }
+
+ bool OpenArchive(const char *path)
+ {
+ int fd;
+ do {
+ fd = open(path, O_RDONLY);
+ } while (fd == -1 && errno == EINTR);
+ if (fd == -1)
+ return false;
+
+ struct stat sb;
+ if (fstat(fd, &sb) == -1 || sb.st_size < sizeof(cdir_end)) {
+ close(fd);
+ return false;
+ }
+
+ mBuflen = sb.st_size;
+ mBuf = (char *)mmap(nullptr, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ close(fd);
+
+ if (!mBuf) {
+ return false;
+ }
+
+ madvise(mBuf, sb.st_size, MADV_SEQUENTIAL);
+
+ mEnd = (cdir_end *)(mBuf + mBuflen - sizeof(cdir_end));
+ while (!mEnd->Valid() &&
+ (char *)mEnd > mBuf) {
+ mEnd = (cdir_end *)((char *)mEnd - 1);
+ }
+
+ mCdir_limit = mBuf + letoh32(mEnd->cdir_offset) + letoh32(mEnd->cdir_size);
+
+ if (!mEnd->Valid() || mCdir_limit > (char *)mEnd) {
+ munmap((void *)mBuf, mBuflen);
+ mBuf = nullptr;
+ return false;
+ }
+
+ return true;
+ }
+
+ /* Pass null to get the first cdir entry */
+ const cdir_entry * GetNextEntry(const cdir_entry *prev)
+ {
+ const cdir_entry *entry;
+ if (prev)
+ entry = (cdir_entry *)((char *)prev + prev->GetSize());
+ else
+ entry = (cdir_entry *)(mBuf + letoh32(mEnd->cdir_offset));
+
+ if (((char *)entry + entry->GetSize()) > mCdir_limit ||
+ !entry->Valid())
+ return nullptr;
+ return entry;
+ }
+
+ string GetEntryName(const cdir_entry *entry)
+ {
+ uint16_t len = letoh16(entry->filename_size);
+
+ string name;
+ name.append(entry->data, len);
+ return name;
+ }
+
+ const local_file_header * GetLocalEntry(const cdir_entry *entry)
+ {
+ const local_file_header * data =
+ (local_file_header *)(mBuf + letoh32(entry->offset));
+ if (((char *)data + data->GetSize()) > (char *)mEnd)
+ return nullptr;
+ return data;
+ }
+};
+
+struct AnimationFrame {
+ char path[256];
+ png_color_16 bgcolor;
+ char *buf;
+ const local_file_header *file;
+ uint32_t width;
+ uint32_t height;
+ uint16_t bytepp;
+ bool has_bgcolor;
+
+ AnimationFrame() : buf(nullptr) {}
+ AnimationFrame(const AnimationFrame &frame) : buf(nullptr) {
+ strncpy(path, frame.path, sizeof(path));
+ file = frame.file;
+ }
+ ~AnimationFrame()
+ {
+ if (buf)
+ free(buf);
+ }
+
+ bool operator<(const AnimationFrame &other) const
+ {
+ return strcmp(path, other.path) < 0;
+ }
+
+ void ReadPngFrame(int outputFormat);
+};
+
+struct AnimationPart {
+ int32_t count;
+ int32_t pause;
+ // If you alter the size of the path, change ReadFromString() as well.
+ char path[256];
+ vector<AnimationFrame> frames;
+
+ bool
+ ReadFromString(const char* aLine)
+ {
+ MOZ_ASSERT(aLine);
+ // this 255 value must be in sync with AnimationPart::path.
+ return sscanf(aLine, "p %d %d %255s", &count, &pause, path) == 3;
+ }
+};
+
+struct RawReadState {
+ const char *start;
+ uint32_t offset;
+ uint32_t length;
+};
+
+static void
+RawReader(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ RawReadState *state = (RawReadState *)png_get_io_ptr(png_ptr);
+ if (length > (state->length - state->offset))
+ png_error(png_ptr, "PNG read overrun");
+
+ memcpy(data, state->start + state->offset, length);
+ state->offset += length;
+}
+
+static void
+TransformTo565(png_structp png_ptr, png_row_infop row_info, png_bytep data)
+{
+ uint16_t *outbuf = (uint16_t *)data;
+ uint8_t *inbuf = (uint8_t *)data;
+ for (uint32_t i = 0; i < row_info->rowbytes; i += 3) {
+ *outbuf++ = ((inbuf[i] & 0xF8) << 8) |
+ ((inbuf[i + 1] & 0xFC) << 3) |
+ ((inbuf[i + 2] ) >> 3);
+ }
+}
+
+static uint16_t
+GetFormatBPP(int aFormat)
+{
+ uint16_t bpp = 0;
+
+ switch (aFormat) {
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ bpp = 4;
+ break;
+ case HAL_PIXEL_FORMAT_RGB_888:
+ bpp = 3;
+ break;
+ default:
+ LOGW("Unknown pixel format %d. Assuming RGB 565.", aFormat);
+ // FALL THROUGH
+ case HAL_PIXEL_FORMAT_RGB_565:
+ bpp = 2;
+ break;
+ }
+
+ return bpp;
+}
+
+void
+AnimationFrame::ReadPngFrame(int outputFormat)
+{
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ static const png_byte unused_chunks[] =
+ { 99, 72, 82, 77, '\0', /* cHRM */
+ 104, 73, 83, 84, '\0', /* hIST */
+ 105, 67, 67, 80, '\0', /* iCCP */
+ 105, 84, 88, 116, '\0', /* iTXt */
+ 111, 70, 70, 115, '\0', /* oFFs */
+ 112, 67, 65, 76, '\0', /* pCAL */
+ 115, 67, 65, 76, '\0', /* sCAL */
+ 112, 72, 89, 115, '\0', /* pHYs */
+ 115, 66, 73, 84, '\0', /* sBIT */
+ 115, 80, 76, 84, '\0', /* sPLT */
+ 116, 69, 88, 116, '\0', /* tEXt */
+ 116, 73, 77, 69, '\0', /* tIME */
+ 122, 84, 88, 116, '\0'}; /* zTXt */
+ static const png_byte tRNS_chunk[] =
+ {116, 82, 78, 83, '\0'}; /* tRNS */
+#endif
+
+ png_structp pngread = png_create_read_struct(PNG_LIBPNG_VER_STRING,
+ nullptr, nullptr, nullptr);
+
+ if (!pngread)
+ return;
+
+ png_infop pnginfo = png_create_info_struct(pngread);
+
+ if (!pnginfo) {
+ png_destroy_read_struct(&pngread, &pnginfo, nullptr);
+ return;
+ }
+
+ if (setjmp(png_jmpbuf(pngread))) {
+ // libpng reported an error and longjumped here. Clean up and return.
+ png_destroy_read_struct(&pngread, &pnginfo, nullptr);
+ return;
+ }
+
+ RawReadState state;
+ state.start = file->GetData();
+ state.length = file->GetDataSize();
+ state.offset = 0;
+
+ png_set_read_fn(pngread, &state, RawReader);
+
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+ /* Ignore unused chunks */
+ png_set_keep_unknown_chunks(pngread, 1, unused_chunks,
+ (int)sizeof(unused_chunks)/5);
+
+ /* Ignore the tRNS chunk if we only want opaque output */
+ if (outputFormat == HAL_PIXEL_FORMAT_RGB_888 ||
+ outputFormat == HAL_PIXEL_FORMAT_RGB_565) {
+ png_set_keep_unknown_chunks(pngread, 1, tRNS_chunk, 1);
+ }
+#endif
+
+ png_read_info(pngread, pnginfo);
+
+ png_color_16p colorp;
+ has_bgcolor = (PNG_INFO_bKGD == png_get_bKGD(pngread, pnginfo, &colorp));
+ bgcolor = has_bgcolor ? *colorp : png_color_16();
+ width = png_get_image_width(pngread, pnginfo);
+ height = png_get_image_height(pngread, pnginfo);
+
+ LOG("Decoded %s: %d x %d frame with bgcolor? %s (%#x, %#x, %#x; gray:%#x)",
+ path, width, height, has_bgcolor ? "yes" : "no",
+ bgcolor.red, bgcolor.green, bgcolor.blue, bgcolor.gray);
+
+ bytepp = GetFormatBPP(outputFormat);
+
+ switch (outputFormat) {
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ png_set_bgr(pngread);
+ // FALL THROUGH
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ png_set_filler(pngread, 0xFF, PNG_FILLER_AFTER);
+ break;
+ case HAL_PIXEL_FORMAT_RGB_888:
+ png_set_strip_alpha(pngread);
+ break;
+ default:
+ LOGW("Unknown pixel format %d. Assuming RGB 565.", outputFormat);
+ // FALL THROUGH
+ case HAL_PIXEL_FORMAT_RGB_565:
+ png_set_strip_alpha(pngread);
+ png_set_read_user_transform_fn(pngread, TransformTo565);
+ break;
+ }
+
+ // An extra row is added to give libpng enough space when
+ // decoding 3/4 bytepp inputs for 2 bytepp output surfaces
+ buf = (char *)malloc(width * (height + 1) * bytepp);
+
+ vector<char *> rows(height + 1);
+ uint32_t stride = width * bytepp;
+ for (uint32_t i = 0; i < height; i++) {
+ rows[i] = buf + (stride * i);
+ }
+ rows[height] = nullptr;
+ png_set_strip_16(pngread);
+ png_set_palette_to_rgb(pngread);
+ png_set_gray_to_rgb(pngread);
+ png_read_image(pngread, (png_bytepp)&rows.front());
+ png_destroy_read_struct(&pngread, &pnginfo, nullptr);
+}
+
+/**
+ * Return a wchar_t that when used to |wmemset()| an image buffer will
+ * fill it with the color defined by |color16|. The packed wchar_t
+ * may comprise one or two pixels depending on |outputFormat|.
+ */
+static wchar_t
+AsBackgroundFill(const png_color_16& color16, int outputFormat)
+{
+ static_assert(sizeof(wchar_t) == sizeof(uint32_t),
+ "TODO: support 2-byte wchar_t");
+ union {
+ uint32_t r8g8b8;
+ struct {
+ uint8_t b8;
+ uint8_t g8;
+ uint8_t r8;
+ uint8_t x8;
+ };
+ } color;
+ color.b8 = color16.blue;
+ color.g8 = color16.green;
+ color.r8 = color16.red;
+ color.x8 = 0xFF;
+
+ switch (outputFormat) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ return color.r8g8b8;
+
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ swap(color.r8, color.b8);
+ return color.r8g8b8;
+
+ case HAL_PIXEL_FORMAT_RGB_565: {
+ // NB: we could do a higher-quality downsample here, but we
+ // want the results to be a pixel-perfect match with the fast
+ // downsample in TransformTo565().
+ uint16_t color565 = ((color.r8 & 0xF8) << 8) |
+ ((color.g8 & 0xFC) << 3) |
+ ((color.b8 ) >> 3);
+ return (color565 << 16) | color565;
+ }
+ default:
+ LOGW("Unhandled pixel format %d; falling back on black", outputFormat);
+ return 0;
+ }
+}
+
+void
+ShowSolidColorFrame(GonkDisplay *aDisplay,
+ const gralloc_module_t *grallocModule,
+ int32_t aFormat)
+{
+ LOGW("Show solid color frame for bootAnim");
+
+ ANativeWindowBuffer *buffer = aDisplay->DequeueBuffer();
+ void *mappedAddress = nullptr;
+
+ if (!buffer) {
+ LOGW("Failed to get an ANativeWindowBuffer");
+ return;
+ }
+
+ if (!grallocModule->lock(grallocModule, buffer->handle,
+ GRALLOC_USAGE_SW_READ_NEVER |
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_FB,
+ 0, 0, buffer->width, buffer->height, &mappedAddress)) {
+ // Just show a black solid color frame.
+ memset(mappedAddress, 0, buffer->height * buffer->stride * GetFormatBPP(aFormat));
+ grallocModule->unlock(grallocModule, buffer->handle);
+ }
+
+ aDisplay->QueueBuffer(buffer);
+}
+
+static void *
+AnimationThread(void *)
+{
+ GonkDisplay *display = GetGonkDisplay();
+ int32_t format = display->surfaceformat;
+
+ const hw_module_t *module = nullptr;
+ if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module)) {
+ LOGW("Could not get gralloc module");
+ return nullptr;
+ }
+ const gralloc_module_t *grmodule =
+ reinterpret_cast<gralloc_module_t const*>(module);
+
+ ZipReader reader;
+ if (!reader.OpenArchive("/system/media/bootanimation.zip")) {
+ LOGW("Could not open boot animation");
+ ShowSolidColorFrame(display, grmodule, format);
+ return nullptr;
+ }
+
+ const cdir_entry *entry = nullptr;
+ const local_file_header *file = nullptr;
+ while ((entry = reader.GetNextEntry(entry))) {
+ string name = reader.GetEntryName(entry);
+ if (!name.compare("desc.txt")) {
+ file = reader.GetLocalEntry(entry);
+ break;
+ }
+ }
+
+ if (!file) {
+ LOGW("Could not find desc.txt in boot animation");
+ ShowSolidColorFrame(display, grmodule, format);
+ return nullptr;
+ }
+
+ string descCopy;
+ descCopy.append(file->GetData(), entry->GetDataSize());
+ int32_t width, height, fps;
+ const char *line = descCopy.c_str();
+ const char *end;
+ bool headerRead = true;
+ vector<AnimationPart> parts;
+ bool animPlayed = false;
+
+ /*
+ * bootanimation.zip
+ *
+ * This is the boot animation file format that Android uses.
+ * It's a zip file with a directories containing png frames
+ * and a desc.txt that describes how they should be played.
+ *
+ * desc.txt contains two types of lines
+ * 1. [width] [height] [fps]
+ * There is one of these lines per bootanimation.
+ * If the width and height are smaller than the screen,
+ * the frames are centered on a black background.
+ * XXX: Currently we stretch instead of centering the frame.
+ * 2. p [count] [pause] [path]
+ * This describes one animation part.
+ * Each animation part is played in sequence.
+ * An animation part contains all the files/frames in the
+ * directory specified in [path]
+ * [count] indicates the number of times this part repeats.
+ * [pause] indicates the number of frames that this part
+ * should pause for after playing the full sequence but
+ * before repeating.
+ */
+
+ do {
+ end = strstr(line, "\n");
+
+ AnimationPart part;
+ if (headerRead &&
+ sscanf(line, "%d %d %d", &width, &height, &fps) == 3) {
+ headerRead = false;
+ } else if (part.ReadFromString(line)) {
+ parts.push_back(part);
+ }
+ } while (end && *(line = end + 1));
+
+ for (uint32_t i = 0; i < parts.size(); i++) {
+ AnimationPart &part = parts[i];
+ entry = nullptr;
+ char search[256];
+ snprintf(search, sizeof(search), "%s/", part.path);
+ while ((entry = reader.GetNextEntry(entry))) {
+ string name = reader.GetEntryName(entry);
+ if (name.find(search) ||
+ !entry->GetDataSize() ||
+ name.length() >= 256)
+ continue;
+
+ part.frames.resize(part.frames.size() + 1);
+ AnimationFrame &frame = part.frames.back();
+ strcpy(frame.path, name.c_str());
+ frame.file = reader.GetLocalEntry(entry);
+ }
+
+ sort(part.frames.begin(), part.frames.end());
+ }
+
+ long int frameDelayUs = 1000000 / fps;
+
+ for (uint32_t i = 0; i < parts.size(); i++) {
+ AnimationPart &part = parts[i];
+
+ int32_t j = 0;
+ while (sRunAnimation && (!part.count || j++ < part.count)) {
+ for (uint32_t k = 0; k < part.frames.size(); k++) {
+ struct timeval tv1, tv2;
+ gettimeofday(&tv1, nullptr);
+ AnimationFrame &frame = part.frames[k];
+ if (!frame.buf) {
+ frame.ReadPngFrame(format);
+ }
+
+ ANativeWindowBuffer *buf = display->DequeueBuffer();
+ if (!buf) {
+ LOGW("Failed to get an ANativeWindowBuffer");
+ break;
+ }
+
+ void *vaddr;
+ if (grmodule->lock(grmodule, buf->handle,
+ GRALLOC_USAGE_SW_READ_NEVER |
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_FB,
+ 0, 0, width, height, &vaddr)) {
+ LOGW("Failed to lock buffer_handle_t");
+ display->QueueBuffer(buf);
+ break;
+ }
+
+ if (frame.has_bgcolor) {
+ wchar_t bgfill = AsBackgroundFill(frame.bgcolor, format);
+ wmemset((wchar_t*)vaddr, bgfill,
+ (buf->height * buf->stride * frame.bytepp) / sizeof(wchar_t));
+ }
+
+ if ((uint32_t)buf->height == frame.height && (uint32_t)buf->stride == frame.width) {
+ memcpy(vaddr, frame.buf,
+ frame.width * frame.height * frame.bytepp);
+ } else if ((uint32_t)buf->height >= frame.height &&
+ (uint32_t)buf->width >= frame.width) {
+ int startx = (buf->width - frame.width) / 2;
+ int starty = (buf->height - frame.height) / 2;
+
+ int src_stride = frame.width * frame.bytepp;
+ int dst_stride = buf->stride * frame.bytepp;
+
+ char *src = frame.buf;
+ char *dst = (char *) vaddr + starty * dst_stride + startx * frame.bytepp;
+
+ for (uint32_t i = 0; i < frame.height; i++) {
+ memcpy(dst, src, src_stride);
+ src += src_stride;
+ dst += dst_stride;
+ }
+ }
+ grmodule->unlock(grmodule, buf->handle);
+
+ gettimeofday(&tv2, nullptr);
+
+ timersub(&tv2, &tv1, &tv2);
+
+ if (tv2.tv_usec < frameDelayUs) {
+ usleep(frameDelayUs - tv2.tv_usec);
+ } else {
+ LOGW("Frame delay is %ld us but decoding took %ld us",
+ frameDelayUs, tv2.tv_usec);
+ }
+
+ animPlayed = true;
+ display->QueueBuffer(buf);
+
+ if (part.count && j >= part.count) {
+ free(frame.buf);
+ frame.buf = nullptr;
+ }
+ }
+ usleep(frameDelayUs * part.pause);
+ }
+ }
+
+ if (!animPlayed) {
+ ShowSolidColorFrame(display, grmodule, format);
+ }
+
+ return nullptr;
+}
+
+namespace mozilla {
+
+__attribute__ ((visibility ("default")))
+void
+StartBootAnimation()
+{
+ GetGonkDisplay(); // Ensure GonkDisplay exist
+ sRunAnimation = true;
+ pthread_create(&sAnimationThread, nullptr, AnimationThread, nullptr);
+}
+
+__attribute__ ((visibility ("default")))
+void
+StopBootAnimation()
+{
+ if (sRunAnimation) {
+ sRunAnimation = false;
+ pthread_join(sAnimationThread, nullptr);
+ GetGonkDisplay()->NotifyBootAnimationStopped();
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/libdisplay/BootAnimation.h b/widget/gonk/libdisplay/BootAnimation.h
new file mode 100644
index 0000000000..9fdc20eca9
--- /dev/null
+++ b/widget/gonk/libdisplay/BootAnimation.h
@@ -0,0 +1,30 @@
+/* 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.
+ */
+
+#ifndef BOOTANIMATION_H
+#define BOOTANIMATION_H
+
+namespace mozilla {
+
+MOZ_EXPORT __attribute__ ((weak))
+void StartBootAnimation();
+
+/* Stop the boot animation if it's still running. */
+MOZ_EXPORT __attribute__ ((weak))
+void StopBootAnimation();
+
+} // namespace mozilla
+
+#endif /* BOOTANIMATION_H */
diff --git a/widget/gonk/libdisplay/DisplaySurface.h b/widget/gonk/libdisplay/DisplaySurface.h
new file mode 100644
index 0000000000..398541c49b
--- /dev/null
+++ b/widget/gonk/libdisplay/DisplaySurface.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef ANDROID_SF_DISPLAY_SURFACE_H
+#define ANDROID_SF_DISPLAY_SURFACE_H
+
+#include <gui/ConsumerBase.h>
+#include <system/window.h>
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+class IGraphicBufferProducer;
+class String8;
+
+#if ANDROID_VERSION >= 21
+typedef IGraphicBufferConsumer StreamConsumer;
+#else
+typedef BufferQueue StreamConsumer;
+#endif
+
+class DisplaySurface : public ConsumerBase {
+public:
+ // beginFrame is called at the beginning of the composition loop, before
+ // the configuration is known. The DisplaySurface should do anything it
+ // needs to do to enable HWComposer to decide how to compose the frame.
+ // We pass in mustRecompose so we can keep VirtualDisplaySurface's state
+ // machine happy without actually queueing a buffer if nothing has changed.
+ virtual status_t beginFrame(bool mustRecompose) = 0;
+
+ // prepareFrame is called after the composition configuration is known but
+ // before composition takes place. The DisplaySurface can use the
+ // composition type to decide how to manage the flow of buffers between
+ // GLES and HWC for this frame.
+ enum CompositionType {
+ COMPOSITION_UNKNOWN = 0,
+ COMPOSITION_GLES = 1,
+ COMPOSITION_HWC = 2,
+ COMPOSITION_MIXED = COMPOSITION_GLES | COMPOSITION_HWC
+ };
+ virtual status_t prepareFrame(CompositionType compositionType) = 0;
+
+ // Should be called when composition rendering is complete for a frame (but
+ // eglSwapBuffers hasn't necessarily been called). Required by certain
+ // older drivers for synchronization.
+ // TODO: Remove this when we drop support for HWC 1.0.
+ virtual status_t compositionComplete() = 0;
+
+ // Inform the surface that GLES composition is complete for this frame, and
+ // the surface should make sure that HWComposer has the correct buffer for
+ // this frame. Some implementations may only push a new buffer to
+ // HWComposer if GLES composition took place, others need to push a new
+ // buffer on every frame.
+ //
+ // advanceFrame must be followed by a call to onFrameCommitted before
+ // advanceFrame may be called again.
+ virtual status_t advanceFrame() = 0;
+
+ // onFrameCommitted is called after the frame has been committed to the
+ // hardware composer. The surface collects the release fence for this
+ // frame's buffer.
+ virtual void onFrameCommitted() = 0;
+
+ virtual void resizeBuffers(const uint32_t w, const uint32_t h) = 0;
+
+ // setReleaseFenceFd stores a fence file descriptor that will signal when the
+ // current buffer is no longer being read. This fence will be returned to
+ // the producer when the current buffer is released by updateTexImage().
+ // Multiple fences can be set for a given buffer; they will be merged into
+ // a single union fence. The SurfaceTexture will close the file descriptor
+ // when finished with it.
+ virtual status_t setReleaseFenceFd(int fenceFd) = 0;
+
+ virtual int GetPrevDispAcquireFd() = 0;
+
+ buffer_handle_t lastHandle;
+
+protected:
+ DisplaySurface(const sp<StreamConsumer>& sc)
+#if ANDROID_VERSION >= 19
+ : ConsumerBase(sc, true)
+#else
+ : ConsumerBase(sc)
+#endif
+ , lastHandle(0)
+ { }
+ virtual ~DisplaySurface() {}
+};
+
+// ---------------------------------------------------------------------------
+} // namespace android
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_SF_DISPLAY_SURFACE_H
+
diff --git a/widget/gonk/libdisplay/FramebufferSurface.cpp b/widget/gonk/libdisplay/FramebufferSurface.cpp
new file mode 100644
index 0000000000..a289acbb8b
--- /dev/null
+++ b/widget/gonk/libdisplay/FramebufferSurface.cpp
@@ -0,0 +1,207 @@
+/*
+ **
+ ** Copyright 2012 The Android Open Source Project
+ **
+ ** 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <cutils/log.h>
+
+#include <utils/String8.h>
+
+#include <ui/Rect.h>
+
+#include <EGL/egl.h>
+
+#include <hardware/hardware.h>
+#if ANDROID_VERSION == 17
+#include <gui/SurfaceTextureClient.h>
+#endif
+#include <ui/GraphicBuffer.h>
+
+#include "FramebufferSurface.h"
+#include "GraphicBufferAlloc.h"
+
+#ifndef NUM_FRAMEBUFFER_SURFACE_BUFFERS
+#define NUM_FRAMEBUFFER_SURFACE_BUFFERS (2)
+#endif
+
+// ----------------------------------------------------------------------------
+namespace android {
+// ----------------------------------------------------------------------------
+
+/*
+ * This implements the (main) framebuffer management. This class
+ * was adapted from the version in SurfaceFlinger
+ */
+FramebufferSurface::FramebufferSurface(int disp,
+ uint32_t width,
+ uint32_t height,
+ uint32_t format,
+ const sp<StreamConsumer>& sc)
+ : DisplaySurface(sc)
+ , mDisplayType(disp)
+ , mCurrentBufferSlot(-1)
+ , mCurrentBuffer(0)
+{
+ mName = "FramebufferSurface";
+
+#if ANDROID_VERSION >= 19
+ sp<IGraphicBufferConsumer> consumer = mConsumer;
+#else
+ sp<BufferQueue> consumer = mBufferQueue;
+ consumer->setSynchronousMode(true);
+#endif
+ consumer->setConsumerName(mName);
+ consumer->setConsumerUsageBits(GRALLOC_USAGE_HW_FB |
+ GRALLOC_USAGE_HW_RENDER |
+ GRALLOC_USAGE_HW_COMPOSER);
+ consumer->setDefaultBufferFormat(format);
+ consumer->setDefaultBufferSize(width, height);
+ consumer->setDefaultMaxBufferCount(NUM_FRAMEBUFFER_SURFACE_BUFFERS);
+}
+
+status_t FramebufferSurface::beginFrame(bool /*mustRecompose*/) {
+ return NO_ERROR;
+}
+
+status_t FramebufferSurface::prepareFrame(CompositionType /*compositionType*/) {
+ return NO_ERROR;
+}
+
+status_t FramebufferSurface::advanceFrame() {
+ // Once we remove FB HAL support, we can call nextBuffer() from here
+ // instead of using onFrameAvailable(). No real benefit, except it'll be
+ // more like VirtualDisplaySurface.
+ return NO_ERROR;
+}
+
+status_t FramebufferSurface::nextBuffer(sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence) {
+ Mutex::Autolock lock(mMutex);
+
+ BufferQueue::BufferItem item;
+#if ANDROID_VERSION >= 19
+ status_t err = acquireBufferLocked(&item, 0);
+#else
+ status_t err = acquireBufferLocked(&item);
+#endif
+ if (err == BufferQueue::NO_BUFFER_AVAILABLE) {
+ outBuffer = mCurrentBuffer;
+ return NO_ERROR;
+ } else if (err != NO_ERROR) {
+ ALOGE("error acquiring buffer: %s (%d)", strerror(-err), err);
+ return err;
+ }
+
+ // If the BufferQueue has freed and reallocated a buffer in mCurrentSlot
+ // then we may have acquired the slot we already own. If we had released
+ // our current buffer before we call acquireBuffer then that release call
+ // would have returned STALE_BUFFER_SLOT, and we would have called
+ // freeBufferLocked on that slot. Because the buffer slot has already
+ // been overwritten with the new buffer all we have to do is skip the
+ // releaseBuffer call and we should be in the same state we'd be in if we
+ // had released the old buffer first.
+ if (mCurrentBufferSlot != BufferQueue::INVALID_BUFFER_SLOT &&
+ item.mBuf != mCurrentBufferSlot) {
+ // Release the previous buffer.
+#if ANDROID_VERSION >= 19
+ err = releaseBufferLocked(mCurrentBufferSlot, mCurrentBuffer,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
+#else
+ err = releaseBufferLocked(mCurrentBufferSlot, EGL_NO_DISPLAY,
+ EGL_NO_SYNC_KHR);
+#endif
+ if (err != NO_ERROR && err != StreamConsumer::STALE_BUFFER_SLOT) {
+ ALOGE("error releasing buffer: %s (%d)", strerror(-err), err);
+ return err;
+ }
+ }
+ mCurrentBufferSlot = item.mBuf;
+ mCurrentBuffer = mSlots[mCurrentBufferSlot].mGraphicBuffer;
+ outFence = item.mFence;
+ outBuffer = mCurrentBuffer;
+ return NO_ERROR;
+}
+
+// Overrides ConsumerBase::onFrameAvailable(), does not call base class impl.
+#if ANDROID_VERSION >= 22
+void FramebufferSurface::onFrameAvailable(const ::android::BufferItem &item) {
+#else
+void FramebufferSurface::onFrameAvailable() {
+#endif
+ sp<GraphicBuffer> buf;
+ sp<Fence> acquireFence;
+ status_t err = nextBuffer(buf, acquireFence);
+ if (err != NO_ERROR) {
+ ALOGE("error latching nnext FramebufferSurface buffer: %s (%d)",
+ strerror(-err), err);
+ return;
+ }
+ if (acquireFence.get() && acquireFence->isValid())
+ mPrevFBAcquireFence = new Fence(acquireFence->dup());
+ else
+ mPrevFBAcquireFence = Fence::NO_FENCE;
+
+ lastHandle = buf->handle;
+}
+
+void FramebufferSurface::freeBufferLocked(int slotIndex) {
+ ConsumerBase::freeBufferLocked(slotIndex);
+ if (slotIndex == mCurrentBufferSlot) {
+ mCurrentBufferSlot = BufferQueue::INVALID_BUFFER_SLOT;
+ }
+}
+
+status_t FramebufferSurface::setReleaseFenceFd(int fenceFd) {
+ status_t err = NO_ERROR;
+ if (fenceFd >= 0) {
+ sp<Fence> fence(new Fence(fenceFd));
+ if (mCurrentBufferSlot != BufferQueue::INVALID_BUFFER_SLOT) {
+#if ANDROID_VERSION >= 19
+ status_t err = addReleaseFence(mCurrentBufferSlot, mCurrentBuffer, fence);
+#else
+ status_t err = addReleaseFence(mCurrentBufferSlot, fence);
+#endif
+ ALOGE_IF(err, "setReleaseFenceFd: failed to add the fence: %s (%d)",
+ strerror(-err), err);
+ }
+ }
+ return err;
+}
+
+int FramebufferSurface::GetPrevDispAcquireFd() {
+ if (mPrevFBAcquireFence.get() && mPrevFBAcquireFence->isValid()) {
+ return mPrevFBAcquireFence->dup();
+ }
+ return -1;
+}
+
+void FramebufferSurface::onFrameCommitted() {
+ // XXX This role is almost same to setReleaseFenceFd().
+}
+
+status_t FramebufferSurface::compositionComplete()
+{
+ // Actual implementaiton is in GonkDisplay::SwapBuffers()
+ // XXX need to move that to here.
+ return NO_ERROR;
+}
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+// ----------------------------------------------------------------------------
diff --git a/widget/gonk/libdisplay/FramebufferSurface.h b/widget/gonk/libdisplay/FramebufferSurface.h
new file mode 100644
index 0000000000..c1cc84272d
--- /dev/null
+++ b/widget/gonk/libdisplay/FramebufferSurface.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef ANDROID_SF_FRAMEBUFFER_SURFACE_H
+#define ANDROID_SF_FRAMEBUFFER_SURFACE_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "DisplaySurface.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+class Rect;
+class String8;
+
+// ---------------------------------------------------------------------------
+
+class FramebufferSurface : public DisplaySurface {
+public:
+ FramebufferSurface(int disp, uint32_t width, uint32_t height, uint32_t format, const sp<StreamConsumer>& sc);
+
+ // From DisplaySurface
+ virtual status_t beginFrame(bool mustRecompose);
+ virtual status_t prepareFrame(CompositionType compositionType);
+ virtual status_t compositionComplete();
+ virtual status_t advanceFrame();
+ virtual void onFrameCommitted();
+ // Cannot resize a buffers in a FramebufferSurface. Only works with virtual
+ // displays.
+ virtual void resizeBuffers(const uint32_t /*w*/, const uint32_t /*h*/) { };
+
+ // setReleaseFenceFd stores a fence file descriptor that will signal when the
+ // current buffer is no longer being read. This fence will be returned to
+ // the producer when the current buffer is released by updateTexImage().
+ // Multiple fences can be set for a given buffer; they will be merged into
+ // a single union fence. The SurfaceTexture will close the file descriptor
+ // when finished with it.
+ status_t setReleaseFenceFd(int fenceFd);
+
+ virtual int GetPrevDispAcquireFd();
+
+private:
+ virtual ~FramebufferSurface() { }; // this class cannot be overloaded
+
+#if ANDROID_VERSION >= 22
+ virtual void onFrameAvailable(const ::android::BufferItem &item);
+#else
+ virtual void onFrameAvailable();
+#endif
+ virtual void freeBufferLocked(int slotIndex);
+
+ // nextBuffer waits for and then latches the next buffer from the
+ // BufferQueue and releases the previously latched buffer to the
+ // BufferQueue. The new buffer is returned in the 'buffer' argument.
+ status_t nextBuffer(sp<GraphicBuffer>& outBuffer, sp<Fence>& outFence);
+
+ // mDisplayType must match one of the HWC display types
+ int mDisplayType;
+
+ // mCurrentBufferIndex is the slot index of the current buffer or
+ // INVALID_BUFFER_SLOT to indicate that either there is no current buffer
+ // or the buffer is not associated with a slot.
+ int mCurrentBufferSlot;
+
+ // mCurrentBuffer is the current buffer or NULL to indicate that there is
+ // no current buffer.
+ sp<GraphicBuffer> mCurrentBuffer;
+
+ android::sp<android::Fence> mPrevFBAcquireFence;
+};
+
+// ---------------------------------------------------------------------------
+}; // namespace android
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_SF_FRAMEBUFFER_SURFACE_H
+
diff --git a/widget/gonk/libdisplay/GonkDisplay.h b/widget/gonk/libdisplay/GonkDisplay.h
new file mode 100644
index 0000000000..96978a6e93
--- /dev/null
+++ b/widget/gonk/libdisplay/GonkDisplay.h
@@ -0,0 +1,91 @@
+/* Copyright 2013 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.
+ */
+
+#ifndef GONKDISPLAY_H
+#define GONKDISPLAY_H
+
+#include <system/window.h>
+#include <utils/StrongPointer.h>
+#include "mozilla/Types.h"
+
+namespace android {
+class DisplaySurface;
+class IGraphicBufferProducer;
+}
+
+namespace mozilla {
+
+typedef void * EGLDisplay;
+typedef void * EGLSurface;
+
+class MOZ_EXPORT GonkDisplay {
+public:
+ /**
+ * This enum is for types of display. DISPLAY_PRIMARY refers to the default
+ * built-in display, DISPLAY_EXTERNAL refers to displays connected with
+ * HDMI, and DISPLAY_VIRTUAL are displays which makes composited output
+ * available within the system. Currently, displays of external are detected
+ * via the hotplug detection in HWC, and displays of virtual are connected
+ * via Wifi Display.
+ */
+ enum DisplayType {
+ DISPLAY_PRIMARY,
+ DISPLAY_EXTERNAL,
+ DISPLAY_VIRTUAL,
+ NUM_DISPLAY_TYPES
+ };
+
+ struct NativeData {
+ android::sp<ANativeWindow> mNativeWindow;
+#if ANDROID_VERSION >= 17
+ android::sp<android::DisplaySurface> mDisplaySurface;
+#endif
+ float mXdpi;
+ };
+
+ virtual void SetEnabled(bool enabled) = 0;
+
+ typedef void (*OnEnabledCallbackType)(bool enabled);
+
+ virtual void OnEnabled(OnEnabledCallbackType callback) = 0;
+
+ virtual void* GetHWCDevice() = 0;
+
+ /**
+ * Only GonkDisplayICS uses arguments.
+ */
+ virtual bool SwapBuffers(EGLDisplay dpy, EGLSurface sur) = 0;
+
+ virtual ANativeWindowBuffer* DequeueBuffer() = 0;
+
+ virtual bool QueueBuffer(ANativeWindowBuffer* buf) = 0;
+
+ virtual void UpdateDispSurface(EGLDisplay dpy, EGLSurface sur) = 0;
+
+ virtual NativeData GetNativeData(
+ GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink = nullptr) = 0;
+
+ virtual void NotifyBootAnimationStopped() = 0;
+
+ float xdpi;
+ int32_t surfaceformat;
+};
+
+MOZ_EXPORT __attribute__ ((weak))
+GonkDisplay* GetGonkDisplay();
+
+}
+#endif /* GONKDISPLAY_H */
diff --git a/widget/gonk/libdisplay/GonkDisplayJB.cpp b/widget/gonk/libdisplay/GonkDisplayJB.cpp
new file mode 100644
index 0000000000..197b85a479
--- /dev/null
+++ b/widget/gonk/libdisplay/GonkDisplayJB.cpp
@@ -0,0 +1,461 @@
+/* Copyright 2013 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 "GonkDisplayJB.h"
+#if ANDROID_VERSION == 17
+#include <gui/SurfaceTextureClient.h>
+#else
+#include <gui/Surface.h>
+#include <gui/GraphicBufferAlloc.h>
+#endif
+
+#include <hardware/hardware.h>
+#include <hardware/hwcomposer.h>
+#include <hardware/power.h>
+#include <suspend/autosuspend.h>
+
+#if ANDROID_VERSION >= 19
+#include "VirtualDisplaySurface.h"
+#endif
+#include "FramebufferSurface.h"
+#if ANDROID_VERSION == 17
+#include "GraphicBufferAlloc.h"
+#endif
+#include "mozilla/Assertions.h"
+
+#define DEFAULT_XDPI 75.0
+
+using namespace android;
+
+namespace mozilla {
+
+static GonkDisplayJB* sGonkDisplay = nullptr;
+
+GonkDisplayJB::GonkDisplayJB()
+ : mModule(nullptr)
+ , mFBModule(nullptr)
+ , mHwc(nullptr)
+ , mFBDevice(nullptr)
+ , mPowerModule(nullptr)
+ , mList(nullptr)
+ , mWidth(0)
+ , mHeight(0)
+ , mEnabledCallback(nullptr)
+{
+ int err = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &mFBModule);
+ ALOGW_IF(err, "%s module not found", GRALLOC_HARDWARE_MODULE_ID);
+ if (!err) {
+ err = framebuffer_open(mFBModule, &mFBDevice);
+ ALOGW_IF(err, "could not open framebuffer");
+ }
+
+ if (!err && mFBDevice) {
+ mWidth = mFBDevice->width;
+ mHeight = mFBDevice->height;
+ xdpi = mFBDevice->xdpi;
+ /* The emulator actually reports RGBA_8888, but EGL doesn't return
+ * any matching configuration. We force RGBX here to fix it. */
+ surfaceformat = HAL_PIXEL_FORMAT_RGBX_8888;
+ }
+
+ err = hw_get_module(HWC_HARDWARE_MODULE_ID, &mModule);
+ ALOGW_IF(err, "%s module not found", HWC_HARDWARE_MODULE_ID);
+ if (!err) {
+ err = hwc_open_1(mModule, &mHwc);
+ ALOGE_IF(err, "%s device failed to initialize (%s)",
+ HWC_HARDWARE_COMPOSER, strerror(-err));
+ }
+
+ /* Fallback on the FB rendering path instead of trying to support HWC 1.0 */
+ if (!err && mHwc->common.version == HWC_DEVICE_API_VERSION_1_0) {
+ hwc_close_1(mHwc);
+ mHwc = nullptr;
+ }
+
+ if (!err && mHwc) {
+ if (mFBDevice) {
+ framebuffer_close(mFBDevice);
+ mFBDevice = nullptr;
+ }
+
+ int32_t values[3];
+ const uint32_t attrs[] = {
+ HWC_DISPLAY_WIDTH,
+ HWC_DISPLAY_HEIGHT,
+ HWC_DISPLAY_DPI_X,
+ HWC_DISPLAY_NO_ATTRIBUTE
+ };
+ mHwc->getDisplayAttributes(mHwc, 0, 0, attrs, values);
+
+ mWidth = values[0];
+ mHeight = values[1];
+ xdpi = values[2] / 1000.0f;
+ surfaceformat = HAL_PIXEL_FORMAT_RGBA_8888;
+ }
+
+ err = hw_get_module(POWER_HARDWARE_MODULE_ID,
+ (hw_module_t const**)&mPowerModule);
+ if (!err)
+ mPowerModule->init(mPowerModule);
+ ALOGW_IF(err, "Couldn't load %s module (%s)", POWER_HARDWARE_MODULE_ID, strerror(-err));
+
+ mAlloc = new GraphicBufferAlloc();
+
+ CreateFramebufferSurface(mSTClient, mDispSurface, mWidth, mHeight);
+
+ mList = (hwc_display_contents_1_t *)calloc(1, sizeof(*mList) + (sizeof(hwc_layer_1_t)*2));
+
+ uint32_t usage = GRALLOC_USAGE_HW_FB | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_COMPOSER;
+ if (mFBDevice) {
+ // If device uses fb, they can not use single buffer for boot animation
+ mSTClient->perform(mSTClient.get(), NATIVE_WINDOW_SET_BUFFER_COUNT, 2);
+ mSTClient->perform(mSTClient.get(), NATIVE_WINDOW_SET_USAGE, usage);
+ } else if (mHwc) {
+ PowerOnDisplay(HWC_DISPLAY_PRIMARY);
+ // For devices w/ hwc v1.0 or no hwc, this buffer can not be created,
+ // only create this buffer for devices w/ hwc version > 1.0.
+ CreateFramebufferSurface(mBootAnimSTClient, mBootAnimDispSurface, mWidth, mHeight);
+ }
+}
+
+GonkDisplayJB::~GonkDisplayJB()
+{
+ if (mHwc)
+ hwc_close_1(mHwc);
+ if (mFBDevice)
+ framebuffer_close(mFBDevice);
+ free(mList);
+}
+
+void
+GonkDisplayJB::CreateFramebufferSurface(android::sp<ANativeWindow>& aNativeWindow,
+ android::sp<android::DisplaySurface>& aDisplaySurface,
+ uint32_t aWidth,
+ uint32_t aHeight)
+{
+#if ANDROID_VERSION >= 21
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer, mAlloc);
+#elif ANDROID_VERSION >= 19
+ sp<BufferQueue> consumer = new BufferQueue(mAlloc);
+ sp<IGraphicBufferProducer> producer = consumer;
+#elif ANDROID_VERSION >= 18
+ sp<BufferQueue> consumer = new BufferQueue(true, mAlloc);
+ sp<IGraphicBufferProducer> producer = consumer;
+#else
+ sp<BufferQueue> consumer = new BufferQueue(true, mAlloc);
+#endif
+
+ aDisplaySurface = new FramebufferSurface(0, aWidth, aHeight, surfaceformat, consumer);
+
+#if ANDROID_VERSION == 17
+ aNativeWindow = new SurfaceTextureClient(
+ static_cast<sp<ISurfaceTexture>>(aDisplaySurface->getBufferQueue()));
+#else
+ aNativeWindow = new Surface(producer);
+#endif
+}
+
+void
+GonkDisplayJB::CreateVirtualDisplaySurface(android::IGraphicBufferProducer* aSink,
+ android::sp<ANativeWindow>& aNativeWindow,
+ android::sp<android::DisplaySurface>& aDisplaySurface)
+{
+#if ANDROID_VERSION >= 21
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer, mAlloc);
+#elif ANDROID_VERSION >= 19
+ sp<BufferQueue> consumer = new BufferQueue(mAlloc);
+ sp<IGraphicBufferProducer> producer = consumer;
+#endif
+
+#if ANDROID_VERSION >= 19
+ sp<VirtualDisplaySurface> virtualDisplay;
+ virtualDisplay = new VirtualDisplaySurface(-1, aSink, producer, consumer, String8("VirtualDisplaySurface"));
+ aDisplaySurface = virtualDisplay;
+ aNativeWindow = new Surface(virtualDisplay);
+#endif
+}
+
+void
+GonkDisplayJB::SetEnabled(bool enabled)
+{
+ if (enabled) {
+ autosuspend_disable();
+ mPowerModule->setInteractive(mPowerModule, true);
+ }
+
+ if (!enabled && mEnabledCallback) {
+ mEnabledCallback(enabled);
+ }
+
+#if ANDROID_VERSION >= 21
+ if (mHwc) {
+ if (mHwc->common.version >= HWC_DEVICE_API_VERSION_1_4) {
+ mHwc->setPowerMode(mHwc, HWC_DISPLAY_PRIMARY,
+ (enabled ? HWC_POWER_MODE_NORMAL : HWC_POWER_MODE_OFF));
+ } else {
+ mHwc->blank(mHwc, HWC_DISPLAY_PRIMARY, !enabled);
+ }
+ } else if (mFBDevice && mFBDevice->enableScreen) {
+ mFBDevice->enableScreen(mFBDevice, enabled);
+ }
+#else
+ if (mHwc && mHwc->blank) {
+ mHwc->blank(mHwc, HWC_DISPLAY_PRIMARY, !enabled);
+ } else if (mFBDevice && mFBDevice->enableScreen) {
+ mFBDevice->enableScreen(mFBDevice, enabled);
+ }
+#endif
+
+ if (enabled && mEnabledCallback) {
+ mEnabledCallback(enabled);
+ }
+
+ if (!enabled) {
+ autosuspend_enable();
+ mPowerModule->setInteractive(mPowerModule, false);
+ }
+}
+
+void
+GonkDisplayJB::OnEnabled(OnEnabledCallbackType callback)
+{
+ mEnabledCallback = callback;
+}
+
+void*
+GonkDisplayJB::GetHWCDevice()
+{
+ return mHwc;
+}
+
+bool
+GonkDisplayJB::SwapBuffers(EGLDisplay dpy, EGLSurface sur)
+{
+ // Should be called when composition rendering is complete for a frame.
+ // Only HWC v1.0 needs this call.
+ // HWC > v1.0 case, do not call compositionComplete().
+ // mFBDevice is present only when HWC is v1.0.
+ if (mFBDevice && mFBDevice->compositionComplete) {
+ mFBDevice->compositionComplete(mFBDevice);
+ }
+ return Post(mDispSurface->lastHandle, mDispSurface->GetPrevDispAcquireFd());
+}
+
+bool
+GonkDisplayJB::Post(buffer_handle_t buf, int fence)
+{
+ if (!mHwc) {
+ if (fence >= 0)
+ close(fence);
+ return !mFBDevice->post(mFBDevice, buf);
+ }
+
+ hwc_display_contents_1_t *displays[HWC_NUM_DISPLAY_TYPES] = {NULL};
+ const hwc_rect_t r = { 0, 0, static_cast<int>(mWidth), static_cast<int>(mHeight) };
+ displays[HWC_DISPLAY_PRIMARY] = mList;
+ mList->retireFenceFd = -1;
+ mList->numHwLayers = 2;
+ mList->flags = HWC_GEOMETRY_CHANGED;
+#if ANDROID_VERSION >= 18
+ mList->outbuf = nullptr;
+ mList->outbufAcquireFenceFd = -1;
+#endif
+ mList->hwLayers[0].compositionType = HWC_FRAMEBUFFER;
+ mList->hwLayers[0].hints = 0;
+ /* Skip this layer so the hwc module doesn't complain about null handles */
+ mList->hwLayers[0].flags = HWC_SKIP_LAYER;
+ mList->hwLayers[0].backgroundColor = {0};
+ mList->hwLayers[0].acquireFenceFd = -1;
+ mList->hwLayers[0].releaseFenceFd = -1;
+ /* hwc module checks displayFrame even though it shouldn't */
+ mList->hwLayers[0].displayFrame = r;
+ mList->hwLayers[1].compositionType = HWC_FRAMEBUFFER_TARGET;
+ mList->hwLayers[1].hints = 0;
+ mList->hwLayers[1].flags = 0;
+ mList->hwLayers[1].handle = buf;
+ mList->hwLayers[1].transform = 0;
+ mList->hwLayers[1].blending = HWC_BLENDING_NONE;
+#if ANDROID_VERSION >= 19
+ if (mHwc->common.version >= HWC_DEVICE_API_VERSION_1_3) {
+ mList->hwLayers[1].sourceCropf.left = 0;
+ mList->hwLayers[1].sourceCropf.top = 0;
+ mList->hwLayers[1].sourceCropf.right = mWidth;
+ mList->hwLayers[1].sourceCropf.bottom = mHeight;
+ } else {
+ mList->hwLayers[1].sourceCrop = r;
+ }
+#else
+ mList->hwLayers[1].sourceCrop = r;
+#endif
+ mList->hwLayers[1].displayFrame = r;
+ mList->hwLayers[1].visibleRegionScreen.numRects = 1;
+ mList->hwLayers[1].visibleRegionScreen.rects = &mList->hwLayers[1].displayFrame;
+ mList->hwLayers[1].acquireFenceFd = fence;
+ mList->hwLayers[1].releaseFenceFd = -1;
+#if ANDROID_VERSION >= 18
+ mList->hwLayers[1].planeAlpha = 0xFF;
+#endif
+ mHwc->prepare(mHwc, HWC_NUM_DISPLAY_TYPES, displays);
+ int err = mHwc->set(mHwc, HWC_NUM_DISPLAY_TYPES, displays);
+
+ if (!mBootAnimDispSurface.get()) {
+ mDispSurface->setReleaseFenceFd(mList->hwLayers[1].releaseFenceFd);
+ } else {
+ mBootAnimDispSurface->setReleaseFenceFd(mList->hwLayers[1].releaseFenceFd);
+ }
+
+ if (mList->retireFenceFd >= 0)
+ close(mList->retireFenceFd);
+ return !err;
+}
+
+ANativeWindowBuffer*
+GonkDisplayJB::DequeueBuffer()
+{
+ // Check for bootAnim or normal display flow.
+ sp<ANativeWindow> nativeWindow =
+ !mBootAnimSTClient.get() ? mSTClient : mBootAnimSTClient;
+
+ ANativeWindowBuffer *buf;
+ int fenceFd = -1;
+ nativeWindow->dequeueBuffer(nativeWindow.get(), &buf, &fenceFd);
+ sp<Fence> fence(new Fence(fenceFd));
+#if ANDROID_VERSION == 17
+ fence->waitForever(1000, "GonkDisplayJB_DequeueBuffer");
+ // 1000 is what Android uses. It is a warning timeout in ms.
+ // This timeout was removed in ANDROID_VERSION 18.
+#else
+ fence->waitForever("GonkDisplayJB_DequeueBuffer");
+#endif
+ return buf;
+}
+
+bool
+GonkDisplayJB::QueueBuffer(ANativeWindowBuffer* buf)
+{
+ bool success = false;
+ int error = DoQueueBuffer(buf);
+ // Check for bootAnim or normal display flow.
+ if (!mBootAnimSTClient.get()) {
+ success = Post(mDispSurface->lastHandle, mDispSurface->GetPrevDispAcquireFd());
+ } else {
+ success = Post(mBootAnimDispSurface->lastHandle, mBootAnimDispSurface->GetPrevDispAcquireFd());
+ }
+ return error == 0 && success;
+}
+
+int
+GonkDisplayJB::DoQueueBuffer(ANativeWindowBuffer* buf)
+{
+ int error = 0;
+ // Check for bootAnim or normal display flow.
+ if (!mBootAnimSTClient.get()) {
+ error = mSTClient->queueBuffer(mSTClient.get(), buf, -1);
+ } else {
+ error = mBootAnimSTClient->queueBuffer(mBootAnimSTClient.get(), buf, -1);
+ }
+ return error;
+}
+
+void
+GonkDisplayJB::UpdateDispSurface(EGLDisplay dpy, EGLSurface sur)
+{
+ if (sur != EGL_NO_SURFACE) {
+ eglSwapBuffers(dpy, sur);
+ } else {
+ // When BasicCompositor is used as Compositor,
+ // EGLSurface does not exit.
+ ANativeWindowBuffer* buf = DequeueBuffer();
+ DoQueueBuffer(buf);
+ }
+}
+
+void
+GonkDisplayJB::NotifyBootAnimationStopped()
+{
+ if (mBootAnimSTClient.get()) {
+ mBootAnimSTClient = nullptr;
+ mBootAnimDispSurface = nullptr;
+ }
+}
+
+void
+GonkDisplayJB::PowerOnDisplay(int aDpy)
+{
+ MOZ_ASSERT(mHwc);
+#if ANDROID_VERSION >= 21
+ if (mHwc->common.version >= HWC_DEVICE_API_VERSION_1_4) {
+ mHwc->setPowerMode(mHwc, aDpy, HWC_POWER_MODE_NORMAL);
+ } else {
+ mHwc->blank(mHwc, aDpy, 0);
+ }
+#else
+ mHwc->blank(mHwc, aDpy, 0);
+#endif
+}
+
+GonkDisplay::NativeData
+GonkDisplayJB::GetNativeData(GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink)
+{
+ NativeData data;
+
+ if (aDisplayType == DISPLAY_PRIMARY) {
+ data.mNativeWindow = mSTClient;
+ data.mDisplaySurface = mDispSurface;
+ data.mXdpi = xdpi;
+ } else if (aDisplayType == DISPLAY_EXTERNAL) {
+ int32_t values[3];
+ const uint32_t attrs[] = {
+ HWC_DISPLAY_WIDTH,
+ HWC_DISPLAY_HEIGHT,
+ HWC_DISPLAY_DPI_X,
+ HWC_DISPLAY_NO_ATTRIBUTE
+ };
+ mHwc->getDisplayAttributes(mHwc, aDisplayType, 0, attrs, values);
+ int width = values[0];
+ int height = values[1];
+ // FIXME!! values[2] returns 0 for external display, which doesn't
+ // sound right, Bug 1169176 is the follow-up bug for this issue.
+ data.mXdpi = values[2] ? values[2] / 1000.f : DEFAULT_XDPI;
+ PowerOnDisplay(HWC_DISPLAY_EXTERNAL);
+ CreateFramebufferSurface(data.mNativeWindow,
+ data.mDisplaySurface,
+ width,
+ height);
+ } else if (aDisplayType == DISPLAY_VIRTUAL) {
+ data.mXdpi = xdpi;
+ CreateVirtualDisplaySurface(aSink,
+ data.mNativeWindow,
+ data.mDisplaySurface);
+ }
+
+ return data;
+}
+
+__attribute__ ((visibility ("default")))
+GonkDisplay*
+GetGonkDisplay()
+{
+ if (!sGonkDisplay)
+ sGonkDisplay = new GonkDisplayJB();
+ return sGonkDisplay;
+}
+
+} // namespace mozilla
diff --git a/widget/gonk/libdisplay/GonkDisplayJB.h b/widget/gonk/libdisplay/GonkDisplayJB.h
new file mode 100644
index 0000000000..60bcdffc42
--- /dev/null
+++ b/widget/gonk/libdisplay/GonkDisplayJB.h
@@ -0,0 +1,85 @@
+/* Copyright 2013 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.
+ */
+
+#ifndef GONKDISPLAYJB_H
+#define GONKDISPLAYJB_H
+
+#include "DisplaySurface.h"
+#include "GonkDisplay.h"
+#include "hardware/hwcomposer.h"
+#include "hardware/power.h"
+#include "ui/Fence.h"
+#include "utils/RefBase.h"
+
+namespace mozilla {
+
+class MOZ_EXPORT GonkDisplayJB : public GonkDisplay {
+public:
+ GonkDisplayJB();
+ ~GonkDisplayJB();
+
+ virtual void SetEnabled(bool enabled);
+
+ virtual void OnEnabled(OnEnabledCallbackType callback);
+
+ virtual void* GetHWCDevice();
+
+ virtual bool SwapBuffers(EGLDisplay dpy, EGLSurface sur);
+
+ virtual ANativeWindowBuffer* DequeueBuffer();
+
+ virtual bool QueueBuffer(ANativeWindowBuffer* buf);
+
+ virtual void UpdateDispSurface(EGLDisplay dpy, EGLSurface sur);
+
+ bool Post(buffer_handle_t buf, int fence);
+
+ virtual NativeData GetNativeData(
+ GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink = nullptr);
+
+ virtual void NotifyBootAnimationStopped();
+
+private:
+ void CreateFramebufferSurface(android::sp<ANativeWindow>& aNativeWindow,
+ android::sp<android::DisplaySurface>& aDisplaySurface,
+ uint32_t aWidth, uint32_t aHeight);
+ void CreateVirtualDisplaySurface(android::IGraphicBufferProducer* aSink,
+ android::sp<ANativeWindow>& aNativeWindow,
+ android::sp<android::DisplaySurface>& aDisplaySurface);
+
+ void PowerOnDisplay(int aDpy);
+
+ int DoQueueBuffer(ANativeWindowBuffer* buf);
+
+ hw_module_t const* mModule;
+ hw_module_t const* mFBModule;
+ hwc_composer_device_1_t* mHwc;
+ framebuffer_device_t* mFBDevice;
+ power_module_t* mPowerModule;
+ android::sp<android::DisplaySurface> mDispSurface;
+ android::sp<ANativeWindow> mSTClient;
+ android::sp<android::DisplaySurface> mBootAnimDispSurface;
+ android::sp<ANativeWindow> mBootAnimSTClient;
+ android::sp<android::IGraphicBufferAlloc> mAlloc;
+ hwc_display_contents_1_t* mList;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ OnEnabledCallbackType mEnabledCallback;
+};
+
+}
+
+#endif /* GONKDISPLAYJB_H */
diff --git a/widget/gonk/libdisplay/GraphicBufferAlloc.cpp b/widget/gonk/libdisplay/GraphicBufferAlloc.cpp
new file mode 100644
index 0000000000..5722b7fe3c
--- /dev/null
+++ b/widget/gonk/libdisplay/GraphicBufferAlloc.cpp
@@ -0,0 +1,53 @@
+/*
+ **
+ ** Copyright 2012 The Android Open Source Project
+ **
+ ** 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 <cutils/log.h>
+
+#include <ui/GraphicBuffer.h>
+
+#include "GraphicBufferAlloc.h"
+
+// ----------------------------------------------------------------------------
+namespace android {
+// ----------------------------------------------------------------------------
+
+GraphicBufferAlloc::GraphicBufferAlloc() {
+}
+
+GraphicBufferAlloc::~GraphicBufferAlloc() {
+}
+
+sp<GraphicBuffer> GraphicBufferAlloc::createGraphicBuffer(uint32_t w, uint32_t h,
+ PixelFormat format, uint32_t usage, status_t* error) {
+ sp<GraphicBuffer> graphicBuffer(new GraphicBuffer(w, h, format, usage));
+ status_t err = graphicBuffer->initCheck();
+ *error = err;
+ if (err != 0 || graphicBuffer->handle == 0) {
+ if (err == NO_MEMORY) {
+ GraphicBuffer::dumpAllocationsToSystemLog();
+ }
+ ALOGE("GraphicBufferAlloc::createGraphicBuffer(w=%d, h=%d) "
+ "failed (%s), handle=%p",
+ w, h, strerror(-err), graphicBuffer->handle);
+ return 0;
+ }
+ return graphicBuffer;
+}
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+// ----------------------------------------------------------------------------
diff --git a/widget/gonk/libdisplay/GraphicBufferAlloc.h b/widget/gonk/libdisplay/GraphicBufferAlloc.h
new file mode 100644
index 0000000000..b08750c2fb
--- /dev/null
+++ b/widget/gonk/libdisplay/GraphicBufferAlloc.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef ANDROID_SF_GRAPHIC_BUFFER_ALLOC_H
+#define ANDROID_SF_GRAPHIC_BUFFER_ALLOC_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <gui/IGraphicBufferAlloc.h>
+#include <ui/PixelFormat.h>
+#include <utils/Errors.h>
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+class GraphicBuffer;
+
+class GraphicBufferAlloc : public BnGraphicBufferAlloc {
+public:
+ GraphicBufferAlloc();
+ virtual ~GraphicBufferAlloc();
+ virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h,
+ PixelFormat format, uint32_t usage, status_t* error);
+};
+
+
+// ---------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_SF_GRAPHIC_BUFFER_ALLOC_H
diff --git a/widget/gonk/libdisplay/VirtualDisplaySurface.cpp b/widget/gonk/libdisplay/VirtualDisplaySurface.cpp
new file mode 100644
index 0000000000..7467078852
--- /dev/null
+++ b/widget/gonk/libdisplay/VirtualDisplaySurface.cpp
@@ -0,0 +1,635 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+
+// #define LOG_NDEBUG 0
+#include "VirtualDisplaySurface.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+#if defined(FORCE_HWC_COPY_FOR_VIRTUAL_DISPLAYS)
+static const bool sForceHwcCopy = true;
+#else
+static const bool sForceHwcCopy = false;
+#endif
+
+#define VDS_LOGE(msg, ...) ALOGE("[%s] " msg, \
+ mDisplayName.string(), ##__VA_ARGS__)
+#define VDS_LOGW_IF(cond, msg, ...) ALOGW_IF(cond, "[%s] " msg, \
+ mDisplayName.string(), ##__VA_ARGS__)
+#define VDS_LOGV(msg, ...) ALOGV("[%s] " msg, \
+ mDisplayName.string(), ##__VA_ARGS__)
+
+__attribute__((unused))
+static const char* dbgCompositionTypeStr(DisplaySurface::CompositionType type) {
+ switch (type) {
+ case DisplaySurface::COMPOSITION_UNKNOWN: return "UNKNOWN";
+ case DisplaySurface::COMPOSITION_GLES: return "GLES";
+ case DisplaySurface::COMPOSITION_HWC: return "HWC";
+ case DisplaySurface::COMPOSITION_MIXED: return "MIXED";
+ default: return "<INVALID>";
+ }
+}
+
+VirtualDisplaySurface::VirtualDisplaySurface(int32_t dispId,
+ const sp<IGraphicBufferProducer>& sink,
+ const sp<IGraphicBufferProducer>& bqProducer,
+ const sp<StreamConsumer>& bqConsumer,
+ const String8& name)
+: DisplaySurface(bqConsumer),
+ mDisplayId(dispId),
+ mDisplayName(name),
+ mOutputUsage(GRALLOC_USAGE_HW_COMPOSER),
+ mProducerSlotSource(0),
+ mDbgState(DBG_STATE_IDLE),
+ mDbgLastCompositionType(COMPOSITION_UNKNOWN),
+ mMustRecompose(false)
+{
+ mSource[SOURCE_SINK] = sink;
+ mSource[SOURCE_SCRATCH] = bqProducer;
+
+ resetPerFrameState();
+
+ int sinkWidth, sinkHeight;
+ sink->query(NATIVE_WINDOW_WIDTH, &sinkWidth);
+ sink->query(NATIVE_WINDOW_HEIGHT, &sinkHeight);
+ mSinkBufferWidth = sinkWidth;
+ mSinkBufferHeight = sinkHeight;
+
+ // Pick the buffer format to request from the sink when not rendering to it
+ // with GLES. If the consumer needs CPU access, use the default format
+ // set by the consumer. Otherwise allow gralloc to decide the format based
+ // on usage bits.
+ int sinkUsage;
+ sink->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &sinkUsage);
+ if (sinkUsage & (GRALLOC_USAGE_SW_READ_MASK | GRALLOC_USAGE_SW_WRITE_MASK)) {
+ int sinkFormat;
+ sink->query(NATIVE_WINDOW_FORMAT, &sinkFormat);
+ mDefaultOutputFormat = sinkFormat;
+ } else {
+ mDefaultOutputFormat = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
+ }
+ mOutputFormat = mDefaultOutputFormat;
+
+ ConsumerBase::mName = String8::format("VDS: %s", mDisplayName.string());
+ mConsumer->setConsumerName(ConsumerBase::mName);
+ mConsumer->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER);
+ mConsumer->setDefaultBufferSize(sinkWidth, sinkHeight);
+ mConsumer->setDefaultMaxBufferCount(2);
+}
+
+VirtualDisplaySurface::~VirtualDisplaySurface() {
+}
+
+status_t VirtualDisplaySurface::beginFrame(bool mustRecompose) {
+ if (mDisplayId < 0)
+ return NO_ERROR;
+
+ mMustRecompose = mustRecompose;
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_IDLE,
+ "Unexpected beginFrame() in %s state", dbgStateStr());
+ mDbgState = DBG_STATE_BEGUN;
+
+ return refreshOutputBuffer();
+}
+
+status_t VirtualDisplaySurface::prepareFrame(CompositionType compositionType) {
+ if (mDisplayId < 0)
+ return NO_ERROR;
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_BEGUN,
+ "Unexpected prepareFrame() in %s state", dbgStateStr());
+ mDbgState = DBG_STATE_PREPARED;
+
+ mCompositionType = compositionType;
+ if (sForceHwcCopy && mCompositionType == COMPOSITION_GLES) {
+ // Some hardware can do RGB->YUV conversion more efficiently in hardware
+ // controlled by HWC than in hardware controlled by the video encoder.
+ // Forcing GLES-composed frames to go through an extra copy by the HWC
+ // allows the format conversion to happen there, rather than passing RGB
+ // directly to the consumer.
+ //
+ // On the other hand, when the consumer prefers RGB or can consume RGB
+ // inexpensively, this forces an unnecessary copy.
+ mCompositionType = COMPOSITION_MIXED;
+ }
+
+ if (mCompositionType != mDbgLastCompositionType) {
+ VDS_LOGV("prepareFrame: composition type changed to %s",
+ dbgCompositionTypeStr(mCompositionType));
+ mDbgLastCompositionType = mCompositionType;
+ }
+
+ if (mCompositionType != COMPOSITION_GLES &&
+ (mOutputFormat != mDefaultOutputFormat ||
+ mOutputUsage != GRALLOC_USAGE_HW_COMPOSER)) {
+ // We must have just switched from GLES-only to MIXED or HWC
+ // composition. Stop using the format and usage requested by the GLES
+ // driver; they may be suboptimal when HWC is writing to the output
+ // buffer. For example, if the output is going to a video encoder, and
+ // HWC can write directly to YUV, some hardware can skip a
+ // memory-to-memory RGB-to-YUV conversion step.
+ //
+ // If we just switched *to* GLES-only mode, we'll change the
+ // format/usage and get a new buffer when the GLES driver calls
+ // dequeueBuffer().
+ mOutputFormat = mDefaultOutputFormat;
+ mOutputUsage = GRALLOC_USAGE_HW_COMPOSER;
+ refreshOutputBuffer();
+ }
+
+ return NO_ERROR;
+}
+
+status_t VirtualDisplaySurface::compositionComplete() {
+ return NO_ERROR;
+}
+
+status_t VirtualDisplaySurface::advanceFrame() {
+ return NO_ERROR;
+
+// XXX Add HWC support
+
+#if 0
+ if (mDisplayId < 0)
+ return NO_ERROR;
+
+ if (mCompositionType == COMPOSITION_HWC) {
+ VDS_LOGW_IF(mDbgState != DBG_STATE_PREPARED,
+ "Unexpected advanceFrame() in %s state on HWC frame",
+ dbgStateStr());
+ } else {
+ VDS_LOGW_IF(mDbgState != DBG_STATE_GLES_DONE,
+ "Unexpected advanceFrame() in %s state on GLES/MIXED frame",
+ dbgStateStr());
+ }
+ mDbgState = DBG_STATE_HWC;
+
+ if (mOutputProducerSlot < 0 ||
+ (mCompositionType != COMPOSITION_HWC && mFbProducerSlot < 0)) {
+ // Last chance bailout if something bad happened earlier. For example,
+ // in a GLES configuration, if the sink disappears then dequeueBuffer
+ // will fail, the GLES driver won't queue a buffer, but SurfaceFlinger
+ // will soldier on. So we end up here without a buffer. There should
+ // be lots of scary messages in the log just before this.
+ VDS_LOGE("advanceFrame: no buffer, bailing out");
+ return NO_MEMORY;
+ }
+
+ sp<GraphicBuffer> fbBuffer = mFbProducerSlot >= 0 ?
+ mProducerBuffers[mFbProducerSlot] : sp<GraphicBuffer>(NULL);
+ sp<GraphicBuffer> outBuffer = mProducerBuffers[mOutputProducerSlot];
+ VDS_LOGV("advanceFrame: fb=%d(%p) out=%d(%p)",
+ mFbProducerSlot, fbBuffer.get(),
+ mOutputProducerSlot, outBuffer.get());
+
+ // At this point we know the output buffer acquire fence,
+ // so update HWC state with it.
+ mHwc.setOutputBuffer(mDisplayId, mOutputFence, outBuffer);
+
+ status_t result = NO_ERROR;
+ if (fbBuffer != NULL) {
+ result = mHwc.fbPost(mDisplayId, mFbFence, fbBuffer);
+ }
+
+ return result;
+#endif
+}
+
+void VirtualDisplaySurface::onFrameCommitted() {
+ return;
+
+// XXX Add HWC support
+
+#if 0
+ if (mDisplayId < 0)
+ return;
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_HWC,
+ "Unexpected onFrameCommitted() in %s state", dbgStateStr());
+ mDbgState = DBG_STATE_IDLE;
+
+ sp<Fence> fbFence = mHwc.getAndResetReleaseFence(mDisplayId);
+ if (mCompositionType == COMPOSITION_MIXED && mFbProducerSlot >= 0) {
+ // release the scratch buffer back to the pool
+ Mutex::Autolock lock(mMutex);
+ int sslot = mapProducer2SourceSlot(SOURCE_SCRATCH, mFbProducerSlot);
+ VDS_LOGV("onFrameCommitted: release scratch sslot=%d", sslot);
+ addReleaseFenceLocked(sslot, mProducerBuffers[mFbProducerSlot], fbFence);
+ releaseBufferLocked(sslot, mProducerBuffers[mFbProducerSlot],
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
+ }
+
+ if (mOutputProducerSlot >= 0) {
+ int sslot = mapProducer2SourceSlot(SOURCE_SINK, mOutputProducerSlot);
+ QueueBufferOutput qbo;
+ sp<Fence> outFence = mHwc.getLastRetireFence(mDisplayId);
+ VDS_LOGV("onFrameCommitted: queue sink sslot=%d", sslot);
+ if (mMustRecompose) {
+ status_t result = mSource[SOURCE_SINK]->queueBuffer(sslot,
+ QueueBufferInput(
+ systemTime(), false /* isAutoTimestamp */,
+ Rect(mSinkBufferWidth, mSinkBufferHeight),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0 /* transform */,
+ true /* async*/,
+ outFence),
+ &qbo);
+ if (result == NO_ERROR) {
+ updateQueueBufferOutput(qbo);
+ }
+ } else {
+ // If the surface hadn't actually been updated, then we only went
+ // through the motions of updating the display to keep our state
+ // machine happy. We cancel the buffer to avoid triggering another
+ // re-composition and causing an infinite loop.
+ mSource[SOURCE_SINK]->cancelBuffer(sslot, outFence);
+ }
+ }
+
+ resetPerFrameState();
+#endif
+}
+
+void VirtualDisplaySurface::resizeBuffers(const uint32_t w, const uint32_t h) {
+ uint32_t tmpW, tmpH, transformHint, numPendingBuffers;
+ mQueueBufferOutput.deflate(&tmpW, &tmpH, &transformHint, &numPendingBuffers);
+ mQueueBufferOutput.inflate(w, h, transformHint, numPendingBuffers);
+
+ mSinkBufferWidth = w;
+ mSinkBufferHeight = h;
+}
+
+status_t VirtualDisplaySurface::requestBuffer(int pslot,
+ sp<GraphicBuffer>* outBuf) {
+ if (mDisplayId < 0)
+ return mSource[SOURCE_SINK]->requestBuffer(pslot, outBuf);
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
+ "Unexpected requestBuffer pslot=%d in %s state",
+ pslot, dbgStateStr());
+
+ *outBuf = mProducerBuffers[pslot];
+ return NO_ERROR;
+}
+
+status_t VirtualDisplaySurface::setBufferCount(int bufferCount) {
+ return mSource[SOURCE_SINK]->setBufferCount(bufferCount);
+}
+
+status_t VirtualDisplaySurface::dequeueBuffer(Source source,
+ uint32_t format, uint32_t usage, int* sslot, sp<Fence>* fence) {
+ LOG_FATAL_IF(mDisplayId < 0, "mDisplayId=%d but should not be < 0.", mDisplayId);
+ // Don't let a slow consumer block us
+ bool async = (source == SOURCE_SINK);
+
+ status_t result = mSource[source]->dequeueBuffer(sslot, fence, async,
+ mSinkBufferWidth, mSinkBufferHeight, format, usage);
+ if (result < 0)
+ return result;
+ int pslot = mapSource2ProducerSlot(source, *sslot);
+ VDS_LOGV("dequeueBuffer(%s): sslot=%d pslot=%d result=%d",
+ dbgSourceStr(source), *sslot, pslot, result);
+ uint64_t sourceBit = static_cast<uint64_t>(source) << pslot;
+
+ if ((mProducerSlotSource & (1ULL << pslot)) != sourceBit) {
+ // This slot was previously dequeued from the other source; must
+ // re-request the buffer.
+ result |= BUFFER_NEEDS_REALLOCATION;
+ mProducerSlotSource &= ~(1ULL << pslot);
+ mProducerSlotSource |= sourceBit;
+ }
+
+ if (result & RELEASE_ALL_BUFFERS) {
+ for (uint32_t i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if ((mProducerSlotSource & (1ULL << i)) == sourceBit)
+ mProducerBuffers[i].clear();
+ }
+ }
+ if (result & BUFFER_NEEDS_REALLOCATION) {
+ result = mSource[source]->requestBuffer(*sslot, &mProducerBuffers[pslot]);
+ if (result < 0) {
+ mProducerBuffers[pslot].clear();
+ mSource[source]->cancelBuffer(*sslot, *fence);
+ return result;
+ }
+ VDS_LOGV("dequeueBuffer(%s): buffers[%d]=%p fmt=%d usage=%#x",
+ dbgSourceStr(source), pslot, mProducerBuffers[pslot].get(),
+ mProducerBuffers[pslot]->getPixelFormat(),
+ mProducerBuffers[pslot]->getUsage());
+ }
+
+ return result;
+}
+
+status_t VirtualDisplaySurface::dequeueBuffer(int* pslot, sp<Fence>* fence, bool async,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
+ if (mDisplayId < 0)
+ return mSource[SOURCE_SINK]->dequeueBuffer(pslot, fence, async, w, h, format, usage);
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_PREPARED,
+ "Unexpected dequeueBuffer() in %s state", dbgStateStr());
+ mDbgState = DBG_STATE_GLES;
+
+ VDS_LOGW_IF(!async, "EGL called dequeueBuffer with !async despite eglSwapInterval(0)");
+ VDS_LOGV("dequeueBuffer %dx%d fmt=%d usage=%#x", w, h, format, usage);
+
+ status_t result = NO_ERROR;
+ Source source = fbSourceForCompositionType(mCompositionType);
+
+ if (source == SOURCE_SINK) {
+
+ if (mOutputProducerSlot < 0) {
+ // Last chance bailout if something bad happened earlier. For example,
+ // in a GLES configuration, if the sink disappears then dequeueBuffer
+ // will fail, the GLES driver won't queue a buffer, but SurfaceFlinger
+ // will soldier on. So we end up here without a buffer. There should
+ // be lots of scary messages in the log just before this.
+ VDS_LOGE("dequeueBuffer: no buffer, bailing out");
+ return NO_MEMORY;
+ }
+
+ // We already dequeued the output buffer. If the GLES driver wants
+ // something incompatible, we have to cancel and get a new one. This
+ // will mean that HWC will see a different output buffer between
+ // prepare and set, but since we're in GLES-only mode already it
+ // shouldn't matter.
+
+ usage |= GRALLOC_USAGE_HW_COMPOSER;
+ const sp<GraphicBuffer>& buf = mProducerBuffers[mOutputProducerSlot];
+ if ((usage & ~buf->getUsage()) != 0 ||
+ (format != 0 && format != (uint32_t)buf->getPixelFormat()) ||
+ (w != 0 && w != mSinkBufferWidth) ||
+ (h != 0 && h != mSinkBufferHeight)) {
+ VDS_LOGV("dequeueBuffer: dequeueing new output buffer: "
+ "want %dx%d fmt=%d use=%#x, "
+ "have %dx%d fmt=%d use=%#x",
+ w, h, format, usage,
+ mSinkBufferWidth, mSinkBufferHeight,
+ buf->getPixelFormat(), buf->getUsage());
+ mOutputFormat = format;
+ mOutputUsage = usage;
+ result = refreshOutputBuffer();
+ if (result < 0)
+ return result;
+ }
+ }
+
+ if (source == SOURCE_SINK) {
+ *pslot = mOutputProducerSlot;
+ *fence = mOutputFence;
+ } else {
+ int sslot;
+ result = dequeueBuffer(source, format, usage, &sslot, fence);
+ if (result >= 0) {
+ *pslot = mapSource2ProducerSlot(source, sslot);
+ }
+ }
+ return result;
+}
+
+status_t VirtualDisplaySurface::detachBuffer(int /* slot */) {
+ VDS_LOGE("detachBuffer is not available for VirtualDisplaySurface");
+ return INVALID_OPERATION;
+}
+
+status_t VirtualDisplaySurface::detachNextBuffer(
+ sp<GraphicBuffer>* /* outBuffer */, sp<Fence>* /* outFence */) {
+ VDS_LOGE("detachNextBuffer is not available for VirtualDisplaySurface");
+ return INVALID_OPERATION;
+}
+
+status_t VirtualDisplaySurface::attachBuffer(int* /* outSlot */,
+ const sp<GraphicBuffer>& /* buffer */) {
+ VDS_LOGE("attachBuffer is not available for VirtualDisplaySurface");
+ return INVALID_OPERATION;
+}
+
+status_t VirtualDisplaySurface::queueBuffer(int pslot,
+ const QueueBufferInput& input, QueueBufferOutput* output) {
+ if (mDisplayId < 0)
+ return mSource[SOURCE_SINK]->queueBuffer(pslot, input, output);
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
+ "Unexpected queueBuffer(pslot=%d) in %s state", pslot,
+ dbgStateStr());
+ mDbgState = DBG_STATE_GLES_DONE;
+
+ VDS_LOGV("queueBuffer pslot=%d", pslot);
+
+ status_t result;
+ if (mCompositionType == COMPOSITION_MIXED) {
+ // Queue the buffer back into the scratch pool
+ QueueBufferOutput scratchQBO;
+ int sslot = mapProducer2SourceSlot(SOURCE_SCRATCH, pslot);
+ result = mSource[SOURCE_SCRATCH]->queueBuffer(sslot, input, &scratchQBO);
+ if (result != NO_ERROR)
+ return result;
+
+ // Now acquire the buffer from the scratch pool -- should be the same
+ // slot and fence as we just queued.
+ Mutex::Autolock lock(mMutex);
+ BufferQueue::BufferItem item;
+ result = acquireBufferLocked(&item, 0);
+ if (result != NO_ERROR)
+ return result;
+ VDS_LOGW_IF(item.mBuf != sslot,
+ "queueBuffer: acquired sslot %d from SCRATCH after queueing sslot %d",
+ item.mBuf, sslot);
+ mFbProducerSlot = mapSource2ProducerSlot(SOURCE_SCRATCH, item.mBuf);
+ mFbFence = mSlots[item.mBuf].mFence;
+
+ } else {
+ LOG_FATAL_IF(mCompositionType != COMPOSITION_GLES,
+ "Unexpected queueBuffer in state %s for compositionType %s",
+ dbgStateStr(), dbgCompositionTypeStr(mCompositionType));
+
+ // Extract the GLES release fence for HWC to acquire
+ int64_t timestamp;
+ bool isAutoTimestamp;
+ Rect crop;
+ int scalingMode;
+ uint32_t transform;
+ bool async;
+ input.deflate(&timestamp, &isAutoTimestamp, &crop, &scalingMode,
+ &transform, &async, &mFbFence);
+
+ mFbProducerSlot = pslot;
+ mOutputFence = mFbFence;
+ }
+
+ *output = mQueueBufferOutput;
+ return NO_ERROR;
+}
+
+void VirtualDisplaySurface::cancelBuffer(int pslot, const sp<Fence>& fence) {
+ if (mDisplayId < 0)
+ return mSource[SOURCE_SINK]->cancelBuffer(mapProducer2SourceSlot(SOURCE_SINK, pslot), fence);
+
+ VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
+ "Unexpected cancelBuffer(pslot=%d) in %s state", pslot,
+ dbgStateStr());
+ VDS_LOGV("cancelBuffer pslot=%d", pslot);
+ Source source = fbSourceForCompositionType(mCompositionType);
+ return mSource[source]->cancelBuffer(
+ mapProducer2SourceSlot(source, pslot), fence);
+}
+
+int VirtualDisplaySurface::query(int what, int* value) {
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ *value = mSinkBufferWidth;
+ break;
+ case NATIVE_WINDOW_HEIGHT:
+ *value = mSinkBufferHeight;
+ break;
+ default:
+ return mSource[SOURCE_SINK]->query(what, value);
+ }
+ return NO_ERROR;
+}
+
+#if ANDROID_VERSION >= 21
+status_t VirtualDisplaySurface::connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp,
+ QueueBufferOutput* output) {
+ QueueBufferOutput qbo;
+ status_t result = mSource[SOURCE_SINK]->connect(listener, api,
+ producerControlledByApp, &qbo);
+ if (result == NO_ERROR) {
+ updateQueueBufferOutput(qbo);
+ *output = mQueueBufferOutput;
+ }
+ return result;
+}
+#else
+status_t VirtualDisplaySurface::connect(const sp<IBinder>& token,
+ int api, bool producerControlledByApp,
+ QueueBufferOutput* output) {
+ QueueBufferOutput qbo;
+ status_t result = mSource[SOURCE_SINK]->connect(token, api, producerControlledByApp, &qbo);
+ if (result == NO_ERROR) {
+ updateQueueBufferOutput(qbo);
+ *output = mQueueBufferOutput;
+ }
+ return result;
+}
+#endif
+
+status_t VirtualDisplaySurface::disconnect(int api) {
+ return mSource[SOURCE_SINK]->disconnect(api);
+}
+
+#if ANDROID_VERSION >= 21
+status_t VirtualDisplaySurface::setSidebandStream(const sp<NativeHandle>& /*stream*/) {
+ return INVALID_OPERATION;
+}
+#endif
+
+void VirtualDisplaySurface::allocateBuffers(bool /* async */,
+ uint32_t /* width */, uint32_t /* height */, uint32_t /* format */,
+ uint32_t /* usage */) {
+ // TODO: Should we actually allocate buffers for a virtual display?
+}
+
+void VirtualDisplaySurface::updateQueueBufferOutput(
+ const QueueBufferOutput& qbo) {
+ uint32_t w, h, transformHint, numPendingBuffers;
+ qbo.deflate(&w, &h, &transformHint, &numPendingBuffers);
+ mQueueBufferOutput.inflate(w, h, 0, numPendingBuffers);
+}
+
+void VirtualDisplaySurface::resetPerFrameState() {
+ mCompositionType = COMPOSITION_UNKNOWN;
+ mFbFence = Fence::NO_FENCE;
+ mOutputFence = Fence::NO_FENCE;
+ mOutputProducerSlot = -1;
+ mFbProducerSlot = -1;
+}
+
+status_t VirtualDisplaySurface::refreshOutputBuffer() {
+
+ return INVALID_OPERATION;
+
+// XXX Add HWC support
+
+#if 0
+ if (mOutputProducerSlot >= 0) {
+ mSource[SOURCE_SINK]->cancelBuffer(
+ mapProducer2SourceSlot(SOURCE_SINK, mOutputProducerSlot),
+ mOutputFence);
+ }
+
+ int sslot;
+ status_t result = dequeueBuffer(SOURCE_SINK, mOutputFormat, mOutputUsage,
+ &sslot, &mOutputFence);
+ if (result < 0)
+ return result;
+ mOutputProducerSlot = mapSource2ProducerSlot(SOURCE_SINK, sslot);
+
+ // On GLES-only frames, we don't have the right output buffer acquire fence
+ // until after GLES calls queueBuffer(). So here we just set the buffer
+ // (for use in HWC prepare) but not the fence; we'll call this again with
+ // the proper fence once we have it.
+ result = mHwc.setOutputBuffer(mDisplayId, Fence::NO_FENCE,
+ mProducerBuffers[mOutputProducerSlot]);
+
+ return result;
+#endif
+}
+
+// This slot mapping function is its own inverse, so two copies are unnecessary.
+// Both are kept to make the intent clear where the function is called, and for
+// the (unlikely) chance that we switch to a different mapping function.
+int VirtualDisplaySurface::mapSource2ProducerSlot(Source source, int sslot) {
+ if (source == SOURCE_SCRATCH) {
+ return BufferQueue::NUM_BUFFER_SLOTS - sslot - 1;
+ } else {
+ return sslot;
+ }
+}
+int VirtualDisplaySurface::mapProducer2SourceSlot(Source source, int pslot) {
+ return mapSource2ProducerSlot(source, pslot);
+}
+
+VirtualDisplaySurface::Source
+VirtualDisplaySurface::fbSourceForCompositionType(CompositionType type) {
+ return type == COMPOSITION_MIXED ? SOURCE_SCRATCH : SOURCE_SINK;
+}
+
+const char* VirtualDisplaySurface::dbgStateStr() const {
+ switch (mDbgState) {
+ case DBG_STATE_IDLE: return "IDLE";
+ case DBG_STATE_PREPARED: return "PREPARED";
+ case DBG_STATE_GLES: return "GLES";
+ case DBG_STATE_GLES_DONE: return "GLES_DONE";
+ case DBG_STATE_HWC: return "HWC";
+ default: return "INVALID";
+ }
+}
+
+const char* VirtualDisplaySurface::dbgSourceStr(Source s) {
+ switch (s) {
+ case SOURCE_SINK: return "SINK";
+ case SOURCE_SCRATCH: return "SCRATCH";
+ default: return "INVALID";
+ }
+}
+
+// ---------------------------------------------------------------------------
+} // namespace android
+// ---------------------------------------------------------------------------
diff --git a/widget/gonk/libdisplay/VirtualDisplaySurface.h b/widget/gonk/libdisplay/VirtualDisplaySurface.h
new file mode 100644
index 0000000000..9125d87515
--- /dev/null
+++ b/widget/gonk/libdisplay/VirtualDisplaySurface.h
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
+#define ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
+
+#include <gui/IGraphicBufferProducer.h>
+
+#include "DisplaySurface.h"
+
+// ---------------------------------------------------------------------------
+namespace android {
+// ---------------------------------------------------------------------------
+
+class HWComposer;
+class IProducerListener;
+
+/* This DisplaySurface implementation supports virtual displays, where GLES
+ * and/or HWC compose into a buffer that is then passed to an arbitrary
+ * consumer (the sink) running in another process.
+ *
+ * The simplest case is when the virtual display will never use the h/w
+ * composer -- either the h/w composer doesn't support writing to buffers, or
+ * there are more virtual displays than it supports simultaneously. In this
+ * case, the GLES driver works directly with the output buffer queue, and
+ * calls to the VirtualDisplay from SurfaceFlinger and DisplayHardware do
+ * nothing.
+ *
+ * If h/w composer might be used, then each frame will fall into one of three
+ * configurations: GLES-only, HWC-only, and MIXED composition. In all of these,
+ * we must provide a FB target buffer and output buffer for the HWC set() call.
+ *
+ * In GLES-only composition, the GLES driver is given a buffer from the sink to
+ * render into. When the GLES driver queues the buffer to the
+ * VirtualDisplaySurface, the VirtualDisplaySurface holds onto it instead of
+ * immediately queueing it to the sink. The buffer is used as both the FB
+ * target and output buffer for HWC, though on these frames the HWC doesn't
+ * do any work for this display and doesn't write to the output buffer. After
+ * composition is complete, the buffer is queued to the sink.
+ *
+ * In HWC-only composition, the VirtualDisplaySurface dequeues a buffer from
+ * the sink and passes it to HWC as both the FB target buffer and output
+ * buffer. The HWC doesn't need to read from the FB target buffer, but does
+ * write to the output buffer. After composition is complete, the buffer is
+ * queued to the sink.
+ *
+ * On MIXED frames, things become more complicated, since some h/w composer
+ * implementations can't read from and write to the same buffer. This class has
+ * an internal BufferQueue that it uses as a scratch buffer pool. The GLES
+ * driver is given a scratch buffer to render into. When it finishes rendering,
+ * the buffer is queued and then immediately acquired by the
+ * VirtualDisplaySurface. The scratch buffer is then used as the FB target
+ * buffer for HWC, and a separate buffer is dequeued from the sink and used as
+ * the HWC output buffer. When HWC composition is complete, the scratch buffer
+ * is released and the output buffer is queued to the sink.
+ */
+class VirtualDisplaySurface : public DisplaySurface,
+ public BnGraphicBufferProducer {
+public:
+ VirtualDisplaySurface(int32_t dispId,
+ const sp<IGraphicBufferProducer>& sink,
+ const sp<IGraphicBufferProducer>& bqProducer,
+ const sp<StreamConsumer>& bqConsumer,
+ const String8& name);
+
+ //
+ // DisplaySurface interface
+ //
+ virtual status_t beginFrame(bool mustRecompose);
+ virtual status_t prepareFrame(CompositionType compositionType);
+ virtual status_t compositionComplete();
+ virtual status_t advanceFrame();
+ virtual void onFrameCommitted();
+ virtual void resizeBuffers(const uint32_t w, const uint32_t h);
+
+ virtual status_t setReleaseFenceFd(int fenceFd) { return INVALID_OPERATION; }
+ virtual int GetPrevDispAcquireFd() { return -1; };
+
+private:
+ enum Source {SOURCE_SINK = 0, SOURCE_SCRATCH = 1};
+
+ virtual ~VirtualDisplaySurface();
+
+ //
+ // IGraphicBufferProducer interface, used by the GLES driver.
+ //
+ virtual status_t requestBuffer(int pslot, sp<GraphicBuffer>* outBuf);
+ virtual status_t setBufferCount(int bufferCount);
+ virtual status_t dequeueBuffer(int* pslot, sp<Fence>* fence, bool async,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
+ virtual status_t detachBuffer(int slot);
+ virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence);
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer);
+ virtual status_t queueBuffer(int pslot,
+ const QueueBufferInput& input, QueueBufferOutput* output);
+ virtual void cancelBuffer(int pslot, const sp<Fence>& fence);
+ virtual int query(int what, int* value);
+#if ANDROID_VERSION >= 21
+ virtual status_t connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp, QueueBufferOutput* output);
+#else
+ virtual status_t connect(const sp<IBinder>& token,
+ int api, bool producerControlledByApp, QueueBufferOutput* output);
+#endif
+ virtual status_t disconnect(int api);
+#if ANDROID_VERSION >= 21
+ virtual status_t setSidebandStream(const sp<NativeHandle>& stream);
+#endif
+ virtual void allocateBuffers(bool async, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t usage);
+
+ //
+ // Utility methods
+ //
+ static Source fbSourceForCompositionType(CompositionType type);
+ status_t dequeueBuffer(Source source, uint32_t format, uint32_t usage,
+ int* sslot, sp<Fence>* fence);
+ void updateQueueBufferOutput(const QueueBufferOutput& qbo);
+ void resetPerFrameState();
+ status_t refreshOutputBuffer();
+
+ // Both the sink and scratch buffer pools have their own set of slots
+ // ("source slots", or "sslot"). We have to merge these into the single
+ // set of slots used by the GLES producer ("producer slots" or "pslot") and
+ // internally in the VirtualDisplaySurface. To minimize the number of times
+ // a producer slot switches which source it comes from, we map source slot
+ // numbers to producer slot numbers differently for each source.
+ static int mapSource2ProducerSlot(Source source, int sslot);
+ static int mapProducer2SourceSlot(Source source, int pslot);
+
+ //
+ // Immutable after construction
+ //
+ const int32_t mDisplayId;
+ const String8 mDisplayName;
+ sp<IGraphicBufferProducer> mSource[2]; // indexed by SOURCE_*
+ uint32_t mDefaultOutputFormat;
+
+ //
+ // Inter-frame state
+ //
+
+ // To avoid buffer reallocations, we track the buffer usage and format
+ // we used on the previous frame and use it again on the new frame. If
+ // the composition type changes or the GLES driver starts requesting
+ // different usage/format, we'll get a new buffer.
+ uint32_t mOutputFormat;
+ uint32_t mOutputUsage;
+
+ // Since we present a single producer interface to the GLES driver, but
+ // are internally muxing between the sink and scratch producers, we have
+ // to keep track of which source last returned each producer slot from
+ // dequeueBuffer. Each bit in mProducerSlotSource corresponds to a producer
+ // slot. Both mProducerSlotSource and mProducerBuffers are indexed by a
+ // "producer slot"; see the mapSlot*() functions.
+ uint64_t mProducerSlotSource;
+ sp<GraphicBuffer> mProducerBuffers[BufferQueue::NUM_BUFFER_SLOTS];
+
+ // The QueueBufferOutput with the latest info from the sink, and with the
+ // transform hint cleared. Since we defer queueBuffer from the GLES driver
+ // to the sink, we have to return the previous version.
+ QueueBufferOutput mQueueBufferOutput;
+
+ // Details of the current sink buffer. These become valid when a buffer is
+ // dequeued from the sink, and are used when queueing the buffer.
+ uint32_t mSinkBufferWidth, mSinkBufferHeight;
+
+ //
+ // Intra-frame state
+ //
+
+ // Composition type and GLES buffer source for the current frame.
+ // Valid after prepareFrame(), cleared in onFrameCommitted.
+ CompositionType mCompositionType;
+
+ // mFbFence is the fence HWC should wait for before reading the framebuffer
+ // target buffer.
+ sp<Fence> mFbFence;
+
+ // mOutputFence is the fence HWC should wait for before writing to the
+ // output buffer.
+ sp<Fence> mOutputFence;
+
+ // Producer slot numbers for the buffers to use for HWC framebuffer target
+ // and output.
+ int mFbProducerSlot;
+ int mOutputProducerSlot;
+
+ // Debug only -- track the sequence of events in each frame so we can make
+ // sure they happen in the order we expect. This class implicitly models
+ // a state machine; this enum/variable makes it explicit.
+ //
+ // +-----------+-------------------+-------------+
+ // | State | Event || Next State |
+ // +-----------+-------------------+-------------+
+ // | IDLE | beginFrame || BEGUN |
+ // | BEGUN | prepareFrame || PREPARED |
+ // | PREPARED | dequeueBuffer [1] || GLES |
+ // | PREPARED | advanceFrame [2] || HWC |
+ // | GLES | queueBuffer || GLES_DONE |
+ // | GLES_DONE | advanceFrame || HWC |
+ // | HWC | onFrameCommitted || IDLE |
+ // +-----------+-------------------++------------+
+ // [1] COMPOSITION_GLES and COMPOSITION_MIXED frames.
+ // [2] COMPOSITION_HWC frames.
+ //
+ enum DbgState {
+ // no buffer dequeued, don't know anything about the next frame
+ DBG_STATE_IDLE,
+ // output buffer dequeued, framebuffer source not yet known
+ DBG_STATE_BEGUN,
+ // output buffer dequeued, framebuffer source known but not provided
+ // to GLES yet.
+ DBG_STATE_PREPARED,
+ // GLES driver has a buffer dequeued
+ DBG_STATE_GLES,
+ // GLES driver has queued the buffer, we haven't sent it to HWC yet
+ DBG_STATE_GLES_DONE,
+ // HWC has the buffer for this frame
+ DBG_STATE_HWC,
+ };
+ DbgState mDbgState;
+ CompositionType mDbgLastCompositionType;
+
+ const char* dbgStateStr() const;
+ static const char* dbgSourceStr(Source s);
+
+ bool mMustRecompose;
+};
+
+// ---------------------------------------------------------------------------
+} // namespace android
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_SF_VIRTUAL_DISPLAY_SURFACE_H
+
diff --git a/widget/gonk/libdisplay/moz.build b/widget/gonk/libdisplay/moz.build
new file mode 100644
index 0000000000..917320592c
--- /dev/null
+++ b/widget/gonk/libdisplay/moz.build
@@ -0,0 +1,59 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# Copyright 2013 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.
+
+SOURCES += [
+ 'BootAnimation.cpp',
+]
+
+if CONFIG['ANDROID_VERSION'] >= '19':
+ SOURCES += [
+ 'FramebufferSurface.cpp',
+ 'GonkDisplayJB.cpp',
+ 'VirtualDisplaySurface.cpp',
+ ]
+elif CONFIG['ANDROID_VERSION'] == '18':
+ SOURCES += [
+ 'FramebufferSurface.cpp',
+ 'GonkDisplayJB.cpp',
+ ]
+elif CONFIG['ANDROID_VERSION'] == '17':
+ SOURCES += [
+ 'FramebufferSurface.cpp',
+ 'GonkDisplayJB.cpp',
+ 'GraphicBufferAlloc.cpp',
+ ]
+elif CONFIG['ANDROID_VERSION'] and CONFIG['COMPILE_ENVIRONMENT']:
+ error('Unsupported platform version: %s' % (CONFIG['ANDROID_VERSION']))
+
+Library('display')
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FORCE_STATIC_LIB = True
+
+DEFINES['XPCOM_GLUE'] = True
+
+DISABLE_STL_WRAPPING = True
+
+LOCAL_INCLUDES += [
+ '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
+ 'frameworks/native/include/gui',
+ 'frameworks/native/opengl/include',
+ 'hardware/libhardware/include',
+ 'hardware/libhardware_legacy/include',
+ 'system/core/libsuspend/include',
+ ]
+]
diff --git a/widget/gonk/libui/EventHub.cpp b/widget/gonk/libui/EventHub.cpp
new file mode 100644
index 0000000000..9da29bbeb5
--- /dev/null
+++ b/widget/gonk/libui/EventHub.cpp
@@ -0,0 +1,1549 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "EventHub"
+
+// #define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+#include "EventHub.h"
+
+#include <hardware_legacy/power.h>
+
+#include <cutils/properties.h>
+#include "cutils_log.h"
+#include <utils/Timers.h>
+#include <utils/threads.h>
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "KeyLayoutMap.h"
+#include "KeyCharacterMap.h"
+#include "VirtualKeyMap.h"
+
+#include <string.h>
+#include <stdint.h>
+#include <dirent.h>
+
+#include <sys/inotify.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/limits.h>
+#include "sha1.h"
+
+/* this macro is used to tell if "bit" is set in "array"
+ * it selects a byte from the array, and does a boolean AND
+ * operation with a byte that only has the relevant bit set.
+ * eg. to check for the 12th bit, we do (array[1] & 1<<4)
+ */
+#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
+
+/* this macro computes the number of bytes needed to represent a bit array of the specified size */
+#define sizeof_bit_array(bits) ((bits + 7) / 8)
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+
+namespace android {
+
+static const char *WAKE_LOCK_ID = "KeyEvents";
+static const char *DEVICE_PATH = "/dev/input";
+
+/* return the larger integer */
+static inline int max(int v1, int v2)
+{
+ return (v1 > v2) ? v1 : v2;
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static String8 sha1(const String8& in) {
+ SHA1_CTX ctx;
+ SHA1Init(&ctx);
+ SHA1Update(&ctx, reinterpret_cast<const u_char*>(in.string()), in.size());
+ u_char digest[SHA1_DIGEST_LENGTH];
+ SHA1Final(digest, &ctx);
+
+ String8 out;
+ for (size_t i = 0; i < SHA1_DIGEST_LENGTH; i++) {
+ out.appendFormat("%02x", digest[i]);
+ }
+ return out;
+}
+
+static void setDescriptor(InputDeviceIdentifier& identifier) {
+ // Compute a device descriptor that uniquely identifies the device.
+ // The descriptor is assumed to be a stable identifier. Its value should not
+ // change between reboots, reconnections, firmware updates or new releases of Android.
+ // Ideally, we also want the descriptor to be short and relatively opaque.
+ String8 rawDescriptor;
+ rawDescriptor.appendFormat(":%04x:%04x:", identifier.vendor, identifier.product);
+ if (!identifier.uniqueId.isEmpty()) {
+ rawDescriptor.append("uniqueId:");
+ rawDescriptor.append(identifier.uniqueId);
+ } if (identifier.vendor == 0 && identifier.product == 0) {
+ // If we don't know the vendor and product id, then the device is probably
+ // built-in so we need to rely on other information to uniquely identify
+ // the input device. Usually we try to avoid relying on the device name or
+ // location but for built-in input device, they are unlikely to ever change.
+ if (!identifier.name.isEmpty()) {
+ rawDescriptor.append("name:");
+ rawDescriptor.append(identifier.name);
+ } else if (!identifier.location.isEmpty()) {
+ rawDescriptor.append("location:");
+ rawDescriptor.append(identifier.location);
+ }
+ }
+ identifier.descriptor = sha1(rawDescriptor);
+ ALOGV("Created descriptor: raw=%s, cooked=%s", rawDescriptor.string(),
+ identifier.descriptor.string());
+}
+
+// --- Global Functions ---
+
+uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses) {
+ // Touch devices get dibs on touch-related axes.
+ if (deviceClasses & INPUT_DEVICE_CLASS_TOUCH) {
+ switch (axis) {
+ case ABS_X:
+ case ABS_Y:
+ case ABS_PRESSURE:
+ case ABS_TOOL_WIDTH:
+ case ABS_DISTANCE:
+ case ABS_TILT_X:
+ case ABS_TILT_Y:
+ case ABS_MT_SLOT:
+ case ABS_MT_TOUCH_MAJOR:
+ case ABS_MT_TOUCH_MINOR:
+ case ABS_MT_WIDTH_MAJOR:
+ case ABS_MT_WIDTH_MINOR:
+ case ABS_MT_ORIENTATION:
+ case ABS_MT_POSITION_X:
+ case ABS_MT_POSITION_Y:
+ case ABS_MT_TOOL_TYPE:
+ case ABS_MT_BLOB_ID:
+ case ABS_MT_TRACKING_ID:
+ case ABS_MT_PRESSURE:
+ case ABS_MT_DISTANCE:
+ return INPUT_DEVICE_CLASS_TOUCH;
+ }
+ }
+
+ // Joystick devices get the rest.
+ return deviceClasses & INPUT_DEVICE_CLASS_JOYSTICK;
+}
+
+// --- EventHub::Device ---
+
+EventHub::Device::Device(int fd, int32_t id, const String8& path,
+ const InputDeviceIdentifier& identifier) :
+ next(NULL),
+ fd(fd), id(id), path(path), identifier(identifier),
+ classes(0), configuration(NULL), virtualKeyMap(NULL),
+ ffEffectPlaying(false), ffEffectId(-1),
+ timestampOverrideSec(0), timestampOverrideUsec(0) {
+ memset(keyBitmask, 0, sizeof(keyBitmask));
+ memset(absBitmask, 0, sizeof(absBitmask));
+ memset(relBitmask, 0, sizeof(relBitmask));
+ memset(swBitmask, 0, sizeof(swBitmask));
+ memset(ledBitmask, 0, sizeof(ledBitmask));
+ memset(ffBitmask, 0, sizeof(ffBitmask));
+ memset(propBitmask, 0, sizeof(propBitmask));
+}
+
+EventHub::Device::~Device() {
+ close();
+ delete configuration;
+ delete virtualKeyMap;
+}
+
+void EventHub::Device::close() {
+ if (fd >= 0) {
+ ::close(fd);
+ fd = -1;
+ }
+}
+
+
+// --- EventHub ---
+
+const uint32_t EventHub::EPOLL_ID_INOTIFY;
+const uint32_t EventHub::EPOLL_ID_WAKE;
+const int EventHub::EPOLL_SIZE_HINT;
+const int EventHub::EPOLL_MAX_EVENTS;
+
+EventHub::EventHub(void) :
+ mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1),
+ mOpeningDevices(0), mClosingDevices(0),
+ mNeedToSendFinishedDeviceScan(false),
+ mNeedToReopenDevices(false), mNeedToScanDevices(true),
+ mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+
+ mEpollFd = epoll_create(EPOLL_SIZE_HINT);
+ LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
+
+ mINotifyFd = inotify_init();
+ int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
+ LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s. errno=%d",
+ DEVICE_PATH, errno);
+
+ struct epoll_event eventItem;
+ memset(&eventItem, 0, sizeof(eventItem));
+ eventItem.events = EPOLLIN;
+ eventItem.data.u32 = EPOLL_ID_INOTIFY;
+ result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
+
+ int wakeFds[2];
+ result = pipe(wakeFds);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
+
+ mWakeReadPipeFd = wakeFds[0];
+ mWakeWritePipeFd = wakeFds[1];
+
+ result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
+ errno);
+
+ result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
+ errno);
+
+ eventItem.data.u32 = EPOLL_ID_WAKE;
+ result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
+ errno);
+}
+
+EventHub::~EventHub(void) {
+ closeAllDevicesLocked();
+
+ while (mClosingDevices) {
+ Device* device = mClosingDevices;
+ mClosingDevices = device->next;
+ delete device;
+ }
+
+ ::close(mEpollFd);
+ ::close(mINotifyFd);
+ ::close(mWakeReadPipeFd);
+ ::close(mWakeWritePipeFd);
+
+ release_wake_lock(WAKE_LOCK_ID);
+}
+
+InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return InputDeviceIdentifier();
+ return device->identifier;
+}
+
+uint32_t EventHub::getDeviceClasses(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return 0;
+ return device->classes;
+}
+
+void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->configuration) {
+ *outConfiguration = *device->configuration;
+ } else {
+ outConfiguration->clear();
+ }
+}
+
+status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const {
+ outAxisInfo->clear();
+
+ if (axis >= 0 && axis <= ABS_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) {
+ struct input_absinfo info;
+ if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
+ ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
+ axis, device->identifier.name.string(), device->fd, errno);
+ return -errno;
+ }
+
+ if (info.minimum != info.maximum) {
+ outAxisInfo->valid = true;
+ outAxisInfo->minValue = info.minimum;
+ outAxisInfo->maxValue = info.maximum;
+ outAxisInfo->flat = info.flat;
+ outAxisInfo->fuzz = info.fuzz;
+ outAxisInfo->resolution = info.resolution;
+ }
+ return OK;
+ }
+ }
+ return -1;
+}
+
+bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
+ if (axis >= 0 && axis <= REL_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ return test_bit(axis, device->relBitmask);
+ }
+ }
+ return false;
+}
+
+bool EventHub::hasInputProperty(int32_t deviceId, int property) const {
+ if (property >= 0 && property <= INPUT_PROP_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ return test_bit(property, device->propBitmask);
+ }
+ }
+ return false;
+}
+
+int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+ if (scanCode >= 0 && scanCode <= KEY_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(scanCode, device->keyBitmask)) {
+ uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)];
+ memset(keyState, 0, sizeof(keyState));
+ if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) {
+ return test_bit(scanCode, keyState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && device->keyMap.haveKeyLayout()) {
+ Vector<int32_t> scanCodes;
+ device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode, &scanCodes);
+ if (scanCodes.size() != 0) {
+ uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)];
+ memset(keyState, 0, sizeof(keyState));
+ if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) {
+ for (size_t i = 0; i < scanCodes.size(); i++) {
+ int32_t sc = scanCodes.itemAt(i);
+ if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, keyState)) {
+ return AKEY_STATE_DOWN;
+ }
+ }
+ return AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
+ if (sw >= 0 && sw <= SW_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(sw, device->swBitmask)) {
+ uint8_t swState[sizeof_bit_array(SW_MAX + 1)];
+ memset(swState, 0, sizeof(swState));
+ if (ioctl(device->fd, EVIOCGSW(sizeof(swState)), swState) >= 0) {
+ return test_bit(sw, swState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
+ *outValue = 0;
+
+ if (axis >= 0 && axis <= ABS_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) {
+ struct input_absinfo info;
+ if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
+ ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
+ axis, device->identifier.name.string(), device->fd, errno);
+ return -errno;
+ }
+
+ *outValue = info.value;
+ return OK;
+ }
+ }
+ return -1;
+}
+
+bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->keyMap.haveKeyLayout()) {
+ Vector<int32_t> scanCodes;
+ for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) {
+ scanCodes.clear();
+
+ status_t err = device->keyMap.keyLayoutMap->findScanCodesForKey(
+ keyCodes[codeIndex], &scanCodes);
+ if (! err) {
+ // check the possible scan codes identified by the layout map against the
+ // map of codes actually emitted by the driver
+ for (size_t sc = 0; sc < scanCodes.size(); sc++) {
+ if (test_bit(scanCodes[sc], device->keyBitmask)) {
+ outFlags[codeIndex] = 1;
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+
+ if (device) {
+ // Check the key character map first.
+ sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();
+ if (kcm != NULL) {
+ if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
+ *outFlags = 0;
+ return NO_ERROR;
+ }
+ }
+
+ // Check the key layout next.
+ if (device->keyMap.haveKeyLayout()) {
+ if (!device->keyMap.keyLayoutMap->mapKey(
+ scanCode, usageCode, outKeycode, outFlags)) {
+ return NO_ERROR;
+ }
+ }
+ }
+
+ *outKeycode = 0;
+ *outFlags = 0;
+ return NAME_NOT_FOUND;
+}
+
+status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+
+ if (device && device->keyMap.haveKeyLayout()) {
+ status_t err = device->keyMap.keyLayoutMap->mapAxis(scanCode, outAxisInfo);
+ if (err == NO_ERROR) {
+ return NO_ERROR;
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+void EventHub::setExcludedDevices(const Vector<String8>& devices) {
+ AutoMutex _l(mLock);
+
+ mExcludedDevices = devices;
+}
+
+bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && scanCode >= 0 && scanCode <= KEY_MAX) {
+ if (test_bit(scanCode, device->keyBitmask)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && led >= 0 && led <= LED_MAX) {
+ if (test_bit(led, device->ledBitmask)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && led >= 0 && led <= LED_MAX) {
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_LED;
+ ev.code = led;
+ ev.value = on ? 1 : 0;
+
+ ssize_t nWrite;
+ do {
+ nWrite = write(device->fd, &ev, sizeof(struct input_event));
+ } while (nWrite == -1 && errno == EINTR);
+ }
+}
+
+void EventHub::getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const {
+ outVirtualKeys.clear();
+
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->virtualKeyMap) {
+ outVirtualKeys.appendVector(device->virtualKeyMap->getVirtualKeys());
+ }
+}
+
+sp<KeyCharacterMap> EventHub::getKeyCharacterMap(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ return device->getKeyCharacterMap();
+ }
+ return NULL;
+}
+
+bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId,
+ const sp<KeyCharacterMap>& map) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ if (map != device->overlayKeyMap) {
+ device->overlayKeyMap = map;
+ device->combinedKeyMap = KeyCharacterMap::combine(
+ device->keyMap.keyCharacterMap, map);
+ return true;
+ }
+ }
+ return false;
+}
+
+void EventHub::vibrate(int32_t deviceId, nsecs_t duration) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ ff_effect effect;
+ memset(&effect, 0, sizeof(effect));
+ effect.type = FF_RUMBLE;
+ effect.id = device->ffEffectId;
+ effect.u.rumble.strong_magnitude = 0xc000;
+ effect.u.rumble.weak_magnitude = 0xc000;
+ effect.replay.length = (duration + 999999LL) / 1000000LL;
+ effect.replay.delay = 0;
+ if (ioctl(device->fd, EVIOCSFF, &effect)) {
+ ALOGW("Could not upload force feedback effect to device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectId = effect.id;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 1;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not start force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectPlaying = true;
+ }
+}
+
+void EventHub::cancelVibrate(int32_t deviceId) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ if (device->ffEffectPlaying) {
+ device->ffEffectPlaying = false;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 0;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not stop force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ }
+ }
+}
+
+EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
+ if (deviceId == BUILT_IN_KEYBOARD_ID) {
+ deviceId = mBuiltInKeyboardId;
+ }
+ ssize_t index = mDevices.indexOfKey(deviceId);
+ return index >= 0 ? mDevices.valueAt(index) : NULL;
+}
+
+EventHub::Device* EventHub::getDeviceByPathLocked(const char* devicePath) const {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ Device* device = mDevices.valueAt(i);
+ if (device->path == devicePath) {
+ return device;
+ }
+ }
+ return NULL;
+}
+
+size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
+ ALOG_ASSERT(bufferSize >= 1);
+
+ AutoMutex _l(mLock);
+
+ struct input_event readBuffer[bufferSize];
+
+ RawEvent* event = buffer;
+ size_t capacity = bufferSize;
+ bool awoken = false;
+ for (;;) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ // Reopen input devices if needed.
+ if (mNeedToReopenDevices) {
+ mNeedToReopenDevices = false;
+
+ ALOGI("Reopening all input devices due to a configuration change.");
+
+ closeAllDevicesLocked();
+ mNeedToScanDevices = true;
+ break; // return to the caller before we actually rescan
+ }
+
+ // Report any devices that had last been added/removed.
+ while (mClosingDevices) {
+ Device* device = mClosingDevices;
+ ALOGV("Reporting device closed: id=%d, name=%s\n",
+ device->id, device->path.string());
+ mClosingDevices = device->next;
+ event->when = now;
+ event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;
+ event->type = DEVICE_REMOVED;
+ event += 1;
+ delete device;
+ mNeedToSendFinishedDeviceScan = true;
+ if (--capacity == 0) {
+ break;
+ }
+ }
+
+ if (mNeedToScanDevices) {
+ mNeedToScanDevices = false;
+ scanDevicesLocked();
+ mNeedToSendFinishedDeviceScan = true;
+ }
+
+ while (mOpeningDevices != NULL) {
+ Device* device = mOpeningDevices;
+ ALOGV("Reporting device opened: id=%d, name=%s\n",
+ device->id, device->path.string());
+ mOpeningDevices = device->next;
+ event->when = now;
+ event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
+ event->type = DEVICE_ADDED;
+ event += 1;
+ mNeedToSendFinishedDeviceScan = true;
+ if (--capacity == 0) {
+ break;
+ }
+ }
+
+ if (mNeedToSendFinishedDeviceScan) {
+ mNeedToSendFinishedDeviceScan = false;
+ event->when = now;
+ event->type = FINISHED_DEVICE_SCAN;
+ event += 1;
+ if (--capacity == 0) {
+ break;
+ }
+ }
+
+ // Grab the next input event.
+ bool deviceChanged = false;
+ while (mPendingEventIndex < mPendingEventCount) {
+ const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
+ if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
+ if (eventItem.events & EPOLLIN) {
+ mPendingINotify = true;
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
+ }
+ continue;
+ }
+
+ if (eventItem.data.u32 == EPOLL_ID_WAKE) {
+ if (eventItem.events & EPOLLIN) {
+ ALOGV("awoken after wake()");
+ awoken = true;
+ char buffer[16];
+ ssize_t nRead;
+ do {
+ nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
+ } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
+ eventItem.events);
+ }
+ continue;
+ }
+
+ ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
+ if (deviceIndex < 0) {
+ ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.",
+ eventItem.events, eventItem.data.u32);
+ continue;
+ }
+
+ Device* device = mDevices.valueAt(deviceIndex);
+ if (eventItem.events & EPOLLIN) {
+ int32_t readSize = read(device->fd, readBuffer,
+ sizeof(struct input_event) * capacity);
+ if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
+ // Device was removed before INotify noticed.
+ ALOGW("could not get event, removed? (fd: %d size: %d bufferSize: %d "
+ "capacity: %d errno: %d)\n",
+ device->fd, readSize, bufferSize, capacity, errno);
+ deviceChanged = true;
+ closeDeviceLocked(device);
+ } else if (readSize < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ ALOGW("could not get event (errno=%d)", errno);
+ }
+ } else if ((readSize % sizeof(struct input_event)) != 0) {
+ ALOGE("could not get event (wrong size: %d)", readSize);
+ } else {
+ int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
+
+ size_t count = size_t(readSize) / sizeof(struct input_event);
+ for (size_t i = 0; i < count; i++) {
+ struct input_event& iev = readBuffer[i];
+ ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",
+ device->path.string(),
+ (int) iev.time.tv_sec, (int) iev.time.tv_usec,
+ iev.type, iev.code, iev.value);
+
+ // Some input devices may have a better concept of the time
+ // when an input event was actually generated than the kernel
+ // which simply timestamps all events on entry to evdev.
+ // This is a custom Android extension of the input protocol
+ // mainly intended for use with uinput based device drivers.
+ if (iev.type == EV_MSC) {
+ if (iev.code == MSC_ANDROID_TIME_SEC) {
+ device->timestampOverrideSec = iev.value;
+ continue;
+ } else if (iev.code == MSC_ANDROID_TIME_USEC) {
+ device->timestampOverrideUsec = iev.value;
+ continue;
+ }
+ }
+ if (device->timestampOverrideSec || device->timestampOverrideUsec) {
+ iev.time.tv_sec = device->timestampOverrideSec;
+ iev.time.tv_usec = device->timestampOverrideUsec;
+ if (iev.type == EV_SYN && iev.code == SYN_REPORT) {
+ device->timestampOverrideSec = 0;
+ device->timestampOverrideUsec = 0;
+ }
+ ALOGV("applied override time %d.%06d",
+ int(iev.time.tv_sec), int(iev.time.tv_usec));
+ }
+
+#ifdef HAVE_POSIX_CLOCKS
+ // Use the time specified in the event instead of the current time
+ // so that downstream code can get more accurate estimates of
+ // event dispatch latency from the time the event is enqueued onto
+ // the evdev client buffer.
+ //
+ // The event's timestamp fortuitously uses the same monotonic clock
+ // time base as the rest of Android. The kernel event device driver
+ // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
+ // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
+ // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
+ // system call that also queries ktime_get_ts().
+ event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
+ + nsecs_t(iev.time.tv_usec) * 1000LL;
+ ALOGV("event time %lld, now %lld", event->when, now);
+
+ // Bug 7291243: Add a guard in case the kernel generates timestamps
+ // that appear to be far into the future because they were generated
+ // using the wrong clock source.
+ //
+ // This can happen because when the input device is initially opened
+ // it has a default clock source of CLOCK_REALTIME. Any input events
+ // enqueued right after the device is opened will have timestamps
+ // generated using CLOCK_REALTIME. We later set the clock source
+ // to CLOCK_MONOTONIC but it is already too late.
+ //
+ // Invalid input event timestamps can result in ANRs, crashes and
+ // and other issues that are hard to track down. We must not let them
+ // propagate through the system.
+ //
+ // Log a warning so that we notice the problem and recover gracefully.
+ if (event->when >= now + 10 * 1000000000LL) {
+ // Double-check. Time may have moved on.
+ nsecs_t time = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (event->when > time) {
+ ALOGW("An input event from %s has a timestamp that appears to "
+ "have been generated using the wrong clock source "
+ "(expected CLOCK_MONOTONIC): "
+ "event time %lld, current time %lld, call time %lld. "
+ "Using current time instead.",
+ device->path.string(), event->when, time, now);
+ event->when = time;
+ } else {
+ ALOGV("Event time is ok but failed the fast path and required "
+ "an extra call to systemTime: "
+ "event time %lld, current time %lld, call time %lld.",
+ event->when, time, now);
+ }
+ }
+#else
+ event->when = now;
+#endif
+ event->deviceId = deviceId;
+ event->type = iev.type;
+ event->code = iev.code;
+ event->value = iev.value;
+ event += 1;
+ capacity -= 1;
+ }
+ if (capacity == 0) {
+ // The result buffer is full. Reset the pending event index
+ // so we will try to read the device again on the next iteration.
+ mPendingEventIndex -= 1;
+ break;
+ }
+ }
+ } else if (eventItem.events & EPOLLHUP) {
+ ALOGI("Removing device %s due to epoll hang-up event.",
+ device->identifier.name.string());
+ deviceChanged = true;
+ closeDeviceLocked(device);
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for device %s.",
+ eventItem.events, device->identifier.name.string());
+ }
+ }
+
+ // readNotify() will modify the list of devices so this must be done after
+ // processing all other events to ensure that we read all remaining events
+ // before closing the devices.
+ if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
+ mPendingINotify = false;
+ readNotifyLocked();
+ deviceChanged = true;
+ }
+
+ // Report added or removed devices immediately.
+ if (deviceChanged) {
+ continue;
+ }
+
+ // Return now if we have collected any events or if we were explicitly awoken.
+ if (event != buffer || awoken) {
+ break;
+ }
+
+ // Poll for events. Mind the wake lock dance!
+ // We hold a wake lock at all times except during epoll_wait(). This works due to some
+ // subtle choreography. When a device driver has pending (unread) events, it acquires
+ // a kernel wake lock. However, once the last pending event has been read, the device
+ // driver will release the kernel wake lock. To prevent the system from going to sleep
+ // when this happens, the EventHub holds onto its own user wake lock while the client
+ // is processing events. Thus the system can only sleep if there are no events
+ // pending or currently being processed.
+ //
+ // The timeout is advisory only. If the device is asleep, it will not wake just to
+ // service the timeout.
+ mPendingEventIndex = 0;
+
+ mLock.unlock(); // release lock before poll, must be before release_wake_lock
+ release_wake_lock(WAKE_LOCK_ID);
+
+ int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
+
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+ mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock
+
+ if (pollResult == 0) {
+ // Timed out.
+ mPendingEventCount = 0;
+ break;
+ }
+
+ if (pollResult < 0) {
+ // An error occurred.
+ mPendingEventCount = 0;
+
+ // Sleep after errors to avoid locking up the system.
+ // Hopefully the error is transient.
+ if (errno != EINTR) {
+ ALOGW("poll failed (errno=%d)\n", errno);
+ usleep(100000);
+ }
+ } else {
+ // Some events occurred.
+ mPendingEventCount = size_t(pollResult);
+ }
+ }
+
+ // All done, return the number of events we read.
+ return event - buffer;
+}
+
+void EventHub::wake() {
+ ALOGV("wake() called");
+
+ ssize_t nWrite;
+ do {
+ nWrite = write(mWakeWritePipeFd, "W", 1);
+ } while (nWrite == -1 && errno == EINTR);
+
+ if (nWrite != 1 && errno != EAGAIN) {
+ ALOGW("Could not write wake signal, errno=%d", errno);
+ }
+}
+
+void EventHub::scanDevicesLocked() {
+ status_t res = scanDirLocked(DEVICE_PATH);
+ if(res < 0) {
+ ALOGE("scan dir failed for %s\n", DEVICE_PATH);
+ }
+ if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
+ createVirtualKeyboardLocked();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+static bool containsNonZeroByte(const uint8_t* array, uint32_t startIndex, uint32_t endIndex) {
+ const uint8_t* end = array + endIndex;
+ array += startIndex;
+ while (array != end) {
+ if (*(array++) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static const int32_t GAMEPAD_KEYCODES[] = {
+ AKEYCODE_BUTTON_A, AKEYCODE_BUTTON_B, AKEYCODE_BUTTON_C,
+ AKEYCODE_BUTTON_X, AKEYCODE_BUTTON_Y, AKEYCODE_BUTTON_Z,
+ AKEYCODE_BUTTON_L1, AKEYCODE_BUTTON_R1,
+ AKEYCODE_BUTTON_L2, AKEYCODE_BUTTON_R2,
+ AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR,
+ AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE,
+ AKEYCODE_BUTTON_1, AKEYCODE_BUTTON_2, AKEYCODE_BUTTON_3, AKEYCODE_BUTTON_4,
+ AKEYCODE_BUTTON_5, AKEYCODE_BUTTON_6, AKEYCODE_BUTTON_7, AKEYCODE_BUTTON_8,
+ AKEYCODE_BUTTON_9, AKEYCODE_BUTTON_10, AKEYCODE_BUTTON_11, AKEYCODE_BUTTON_12,
+ AKEYCODE_BUTTON_13, AKEYCODE_BUTTON_14, AKEYCODE_BUTTON_15, AKEYCODE_BUTTON_16,
+};
+
+status_t EventHub::openDeviceLocked(const char *devicePath) {
+ char buffer[80];
+
+ ALOGV("Opening device: %s", devicePath);
+
+ int fd = open(devicePath, O_RDWR | O_CLOEXEC);
+ if(fd < 0) {
+ ALOGE("could not open %s, %s\n", devicePath, strerror(errno));
+ return -1;
+ }
+
+ InputDeviceIdentifier identifier;
+
+ // Get device name.
+ if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.name.setTo(buffer);
+ }
+
+ // Check to see if the device is on our excluded list
+ for (size_t i = 0; i < mExcludedDevices.size(); i++) {
+ const String8& item = mExcludedDevices.itemAt(i);
+ if (identifier.name == item) {
+ ALOGI("ignoring event id %s driver %s\n", devicePath, item.string());
+ close(fd);
+ return -1;
+ }
+ }
+
+ // Get device driver version.
+ int driverVersion;
+ if(ioctl(fd, EVIOCGVERSION, &driverVersion)) {
+ ALOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ // Get device identifier.
+ struct input_id inputId;
+ if(ioctl(fd, EVIOCGID, &inputId)) {
+ ALOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno));
+ close(fd);
+ return -1;
+ }
+ identifier.bus = inputId.bustype;
+ identifier.product = inputId.product;
+ identifier.vendor = inputId.vendor;
+ identifier.version = inputId.version;
+
+ // Get device physical location.
+ if(ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.location.setTo(buffer);
+ }
+
+ // Get device unique id.
+ if(ioctl(fd, EVIOCGUNIQ(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get idstring for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.uniqueId.setTo(buffer);
+ }
+
+ // Fill in the descriptor.
+ setDescriptor(identifier);
+
+ // Make file descriptor non-blocking for use with poll().
+ if (fcntl(fd, F_SETFL, O_NONBLOCK)) {
+ ALOGE("Error %d making device file descriptor non-blocking.", errno);
+ close(fd);
+ return -1;
+ }
+
+ // Allocate device. (The device object takes ownership of the fd at this point.)
+ int32_t deviceId = mNextDeviceId++;
+ Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
+
+ ALOGV("add device %d: %s\n", deviceId, devicePath);
+ ALOGV(" bus: %04x\n"
+ " vendor %04x\n"
+ " product %04x\n"
+ " version %04x\n",
+ identifier.bus, identifier.vendor, identifier.product, identifier.version);
+ ALOGV(" name: \"%s\"\n", identifier.name.string());
+ ALOGV(" location: \"%s\"\n", identifier.location.string());
+ ALOGV(" unique id: \"%s\"\n", identifier.uniqueId.string());
+ ALOGV(" descriptor: \"%s\"\n", identifier.descriptor.string());
+ ALOGV(" driver: v%d.%d.%d\n",
+ driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff);
+
+ // Load the configuration file for the device.
+ loadConfigurationLocked(device);
+
+ // Figure out the kinds of events the device reports.
+ ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
+ ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
+ ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
+ ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
+ ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
+ ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
+ ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
+
+ // See if this is a keyboard. Ignore everything in the button range except for
+ // joystick and gamepad buttons which are handled like keyboards for the most part.
+ bool haveKeyboardKeys = containsNonZeroByte(device->keyBitmask, 0, sizeof_bit_array(BTN_MISC))
+ || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(KEY_OK),
+ sizeof_bit_array(KEY_MAX + 1));
+ bool haveGamepadButtons = containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_MISC),
+ sizeof_bit_array(BTN_MOUSE))
+ || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_JOYSTICK),
+ sizeof_bit_array(BTN_DIGI));
+ if (haveKeyboardKeys || haveGamepadButtons) {
+ device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+ }
+
+ // See if this is a cursor device such as a trackball or mouse.
+ if (test_bit(BTN_MOUSE, device->keyBitmask)
+ && test_bit(REL_X, device->relBitmask)
+ && test_bit(REL_Y, device->relBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_CURSOR;
+ }
+
+ // See if this is a touch pad.
+ // Is this a new modern multi-touch driver?
+ if (test_bit(ABS_MT_POSITION_X, device->absBitmask)
+ && test_bit(ABS_MT_POSITION_Y, device->absBitmask)) {
+ // Some joysticks such as the PS3 controller report axes that conflict
+ // with the ABS_MT range. Try to confirm that the device really is
+ // a touch screen.
+ if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) {
+ device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;
+ }
+ // Is this an old style single-touch driver?
+ } else if (test_bit(BTN_TOUCH, device->keyBitmask)
+ && test_bit(ABS_X, device->absBitmask)
+ && test_bit(ABS_Y, device->absBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_TOUCH;
+ }
+
+ // See if this device is a joystick.
+ // Assumes that joysticks always have gamepad buttons in order to distinguish them
+ // from other devices such as accelerometers that also have absolute axes.
+ if (haveGamepadButtons) {
+ uint32_t assumedClasses = device->classes | INPUT_DEVICE_CLASS_JOYSTICK;
+ for (int i = 0; i <= ABS_MAX; i++) {
+ if (test_bit(i, device->absBitmask)
+ && (getAbsAxisUsage(i, assumedClasses) & INPUT_DEVICE_CLASS_JOYSTICK)) {
+ device->classes = assumedClasses;
+ break;
+ }
+ }
+ }
+
+ // Check whether this device has switches.
+ for (int i = 0; i <= SW_MAX; i++) {
+ if (test_bit(i, device->swBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_SWITCH;
+ break;
+ }
+ }
+
+ // Check whether this device supports the vibrator.
+ if (test_bit(FF_RUMBLE, device->ffBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_VIBRATOR;
+ }
+
+ // Configure virtual keys.
+ if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) {
+ // Load the virtual keys for the touch screen, if any.
+ // We do this now so that we can make sure to load the keymap if necessary.
+ status_t status = loadVirtualKeyMapLocked(device);
+ if (!status) {
+ device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+ }
+ }
+
+ // Load the key map.
+ // We need to do this for joysticks too because the key layout may specify axes.
+ status_t keyMapStatus = NAME_NOT_FOUND;
+ if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
+ // Load the keymap for the device.
+ keyMapStatus = loadKeyMapLocked(device);
+ }
+
+ // Configure the keyboard, gamepad or virtual keyboard.
+ if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+ // Register the keyboard as a built-in keyboard if it is eligible.
+ if (!keyMapStatus
+ && mBuiltInKeyboardId == NO_BUILT_IN_KEYBOARD
+ && isEligibleBuiltInKeyboard(device->identifier,
+ device->configuration, &device->keyMap)) {
+ mBuiltInKeyboardId = device->id;
+ }
+
+ // 'Q' key support = cheap test of whether this is an alpha-capable kbd
+ if (hasKeycodeLocked(device, AKEYCODE_Q)) {
+ device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
+ }
+
+ // See if this device has a DPAD.
+ if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) {
+ device->classes |= INPUT_DEVICE_CLASS_DPAD;
+ }
+
+ // See if this device has a gamepad.
+ for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) {
+ if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
+ device->classes |= INPUT_DEVICE_CLASS_GAMEPAD;
+ break;
+ }
+ }
+
+ // Disable kernel key repeat since we handle it ourselves
+ unsigned int repeatRate[] = {0,0};
+ if (ioctl(fd, EVIOCSREP, repeatRate)) {
+ ALOGW("Unable to disable kernel key repeat for %s: %s", devicePath, strerror(errno));
+ }
+ }
+
+ // If the device isn't recognized as something we handle, don't monitor it.
+ if (device->classes == 0) {
+ ALOGV("Dropping device: id=%d, path='%s', name='%s'",
+ deviceId, devicePath, device->identifier.name.string());
+ delete device;
+ return -1;
+ }
+
+ // Determine whether the device is external or internal.
+ if (isExternalDeviceLocked(device)) {
+ device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;
+ }
+
+ // Register with epoll.
+ struct epoll_event eventItem;
+ memset(&eventItem, 0, sizeof(eventItem));
+ eventItem.events = EPOLLIN;
+ eventItem.data.u32 = deviceId;
+ if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
+ ALOGE("Could not add device fd to epoll instance. errno=%d", errno);
+ delete device;
+ return -1;
+ }
+
+ // Enable wake-lock behavior on kernels that support it.
+ // TODO: Only need this for devices that can really wake the system.
+ bool usingSuspendBlockIoctl = !ioctl(fd, EVIOCSSUSPENDBLOCK, 1);
+
+ // Tell the kernel that we want to use the monotonic clock for reporting timestamps
+ // associated with input events. This is important because the input system
+ // uses the timestamps extensively and assumes they were recorded using the monotonic
+ // clock.
+ //
+ // In older kernel, before Linux 3.4, there was no way to tell the kernel which
+ // clock to use to input event timestamps. The standard kernel behavior was to
+ // record a real time timestamp, which isn't what we want. Android kernels therefore
+ // contained a patch to the evdev_event() function in drivers/input/evdev.c to
+ // replace the call to do_gettimeofday() with ktime_get_ts() to cause the monotonic
+ // clock to be used instead of the real time clock.
+ //
+ // As of Linux 3.4, there is a new EVIOCSCLOCKID ioctl to set the desired clock.
+ // Therefore, we no longer require the Android-specific kernel patch described above
+ // as long as we make sure to set select the monotonic clock. We do that here.
+ int clockId = CLOCK_MONOTONIC;
+ bool usingClockIoctl = !ioctl(fd, EVIOCSCLOCKID, &clockId);
+
+ ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, "
+ "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, "
+ "usingSuspendBlockIoctl=%s, usingClockIoctl=%s",
+ deviceId, fd, devicePath, device->identifier.name.string(),
+ device->classes,
+ device->configurationFile.string(),
+ device->keyMap.keyLayoutFile.string(),
+ device->keyMap.keyCharacterMapFile.string(),
+ toString(mBuiltInKeyboardId == deviceId),
+ toString(usingSuspendBlockIoctl), toString(usingClockIoctl));
+
+ addDeviceLocked(device);
+ return 0;
+}
+
+void EventHub::createVirtualKeyboardLocked() {
+ InputDeviceIdentifier identifier;
+ identifier.name = "Virtual";
+ identifier.uniqueId = "<virtual>";
+ setDescriptor(identifier);
+
+ Device* device = new Device(-1, VIRTUAL_KEYBOARD_ID, String8("<virtual>"), identifier);
+ device->classes = INPUT_DEVICE_CLASS_KEYBOARD
+ | INPUT_DEVICE_CLASS_ALPHAKEY
+ | INPUT_DEVICE_CLASS_DPAD
+ | INPUT_DEVICE_CLASS_VIRTUAL;
+ loadKeyMapLocked(device);
+ addDeviceLocked(device);
+}
+
+void EventHub::addDeviceLocked(Device* device) {
+ mDevices.add(device->id, device);
+ device->next = mOpeningDevices;
+ mOpeningDevices = device;
+}
+
+void EventHub::loadConfigurationLocked(Device* device) {
+ device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
+ device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
+ if (device->configurationFile.isEmpty()) {
+ ALOGD("No input device configuration file found for device '%s'.",
+ device->identifier.name.string());
+ } else {
+ status_t status = PropertyMap::load(device->configurationFile,
+ &device->configuration);
+ if (status) {
+ ALOGE("Error loading input device configuration file for device '%s'. "
+ "Using default configuration.",
+ device->identifier.name.string());
+ }
+ }
+}
+
+status_t EventHub::loadVirtualKeyMapLocked(Device* device) {
+ // The virtual key map is supplied by the kernel as a system board property file.
+ String8 path;
+ path.append("/sys/board_properties/virtualkeys.");
+ path.append(device->identifier.name);
+ if (access(path.string(), R_OK)) {
+ return NAME_NOT_FOUND;
+ }
+ return VirtualKeyMap::load(path, &device->virtualKeyMap);
+}
+
+status_t EventHub::loadKeyMapLocked(Device* device) {
+ return device->keyMap.load(device->identifier, device->configuration);
+}
+
+bool EventHub::isExternalDeviceLocked(Device* device) {
+ if (device->configuration) {
+ bool value;
+ if (device->configuration->tryGetProperty(String8("device.internal"), value)) {
+ return !value;
+ }
+ }
+ return device->identifier.bus == BUS_USB || device->identifier.bus == BUS_BLUETOOTH;
+}
+
+bool EventHub::hasKeycodeLocked(Device* device, int keycode) const {
+ if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) {
+ return false;
+ }
+
+ Vector<int32_t> scanCodes;
+ device->keyMap.keyLayoutMap->findScanCodesForKey(keycode, &scanCodes);
+ const size_t N = scanCodes.size();
+ for (size_t i=0; i<N && i<=KEY_MAX; i++) {
+ int32_t sc = scanCodes.itemAt(i);
+ if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+status_t EventHub::closeDeviceByPathLocked(const char *devicePath) {
+ Device* device = getDeviceByPathLocked(devicePath);
+ if (device) {
+ closeDeviceLocked(device);
+ return 0;
+ }
+ ALOGV("Remove device: %s not found, device may already have been removed.", devicePath);
+ return -1;
+}
+
+void EventHub::closeAllDevicesLocked() {
+ while (mDevices.size() > 0) {
+ closeDeviceLocked(mDevices.valueAt(mDevices.size() - 1));
+ }
+}
+
+void EventHub::closeDeviceLocked(Device* device) {
+ ALOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x\n",
+ device->path.string(), device->identifier.name.string(), device->id,
+ device->fd, device->classes);
+
+ if (device->id == mBuiltInKeyboardId) {
+ ALOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this",
+ device->path.string(), mBuiltInKeyboardId);
+ mBuiltInKeyboardId = NO_BUILT_IN_KEYBOARD;
+ }
+
+ if (!device->isVirtual()) {
+ if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, device->fd, NULL)) {
+ ALOGW("Could not remove device fd from epoll instance. errno=%d", errno);
+ }
+ }
+
+ mDevices.removeItem(device->id);
+ device->close();
+
+ // Unlink for opening devices list if it is present.
+ Device* pred = NULL;
+ bool found = false;
+ for (Device* entry = mOpeningDevices; entry != NULL; ) {
+ if (entry == device) {
+ found = true;
+ break;
+ }
+ pred = entry;
+ entry = entry->next;
+ }
+ if (found) {
+ // Unlink the device from the opening devices list then delete it.
+ // We don't need to tell the client that the device was closed because
+ // it does not even know it was opened in the first place.
+ ALOGI("Device %s was immediately closed after opening.", device->path.string());
+ if (pred) {
+ pred->next = device->next;
+ } else {
+ mOpeningDevices = device->next;
+ }
+ delete device;
+ } else {
+ // Link into closing devices list.
+ // The device will be deleted later after we have informed the client.
+ device->next = mClosingDevices;
+ mClosingDevices = device;
+ }
+}
+
+status_t EventHub::readNotifyLocked() {
+ int res;
+ char devname[PATH_MAX];
+ char *filename;
+ char event_buf[512];
+ int event_size;
+ int event_pos = 0;
+ struct inotify_event *event;
+
+ ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd);
+ res = read(mINotifyFd, event_buf, sizeof(event_buf));
+ if(res < (int)sizeof(*event)) {
+ if(errno == EINTR)
+ return 0;
+ ALOGW("could not get event, %s\n", strerror(errno));
+ return -1;
+ }
+ //printf("got %d bytes of event information\n", res);
+
+ strcpy(devname, DEVICE_PATH);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+
+ while(res >= (int)sizeof(*event)) {
+ event = (struct inotify_event *)(event_buf + event_pos);
+ //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
+ if(event->len) {
+ strcpy(filename, event->name);
+ if(event->mask & IN_CREATE) {
+ openDeviceLocked(devname);
+ } else {
+ ALOGI("Removing device '%s' due to inotify event\n", devname);
+ closeDeviceByPathLocked(devname);
+ }
+ }
+ event_size = sizeof(*event) + event->len;
+ res -= event_size;
+ event_pos += event_size;
+ }
+ return 0;
+}
+
+status_t EventHub::scanDirLocked(const char *dirname)
+{
+ char devname[PATH_MAX];
+ char *filename;
+ DIR *dir;
+ struct dirent *de;
+ dir = opendir(dirname);
+ if(dir == NULL)
+ return -1;
+ strcpy(devname, dirname);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+ while((de = readdir(dir))) {
+ if(de->d_name[0] == '.' &&
+ (de->d_name[1] == '\0' ||
+ (de->d_name[1] == '.' && de->d_name[2] == '\0')))
+ continue;
+ strcpy(filename, de->d_name);
+ openDeviceLocked(devname);
+ }
+ closedir(dir);
+ return 0;
+}
+
+void EventHub::requestReopenDevices() {
+ ALOGV("requestReopenDevices() called");
+
+ AutoMutex _l(mLock);
+ mNeedToReopenDevices = true;
+}
+
+void EventHub::dump(String8& dump) {
+ dump.append("Event Hub State:\n");
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ dump.appendFormat(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId);
+
+ dump.append(INDENT "Devices:\n");
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ const Device* device = mDevices.valueAt(i);
+ if (mBuiltInKeyboardId == device->id) {
+ dump.appendFormat(INDENT2 "%d: %s (aka device 0 - built-in keyboard)\n",
+ device->id, device->identifier.name.string());
+ } else {
+ dump.appendFormat(INDENT2 "%d: %s\n", device->id,
+ device->identifier.name.string());
+ }
+ dump.appendFormat(INDENT3 "Classes: 0x%08x\n", device->classes);
+ dump.appendFormat(INDENT3 "Path: %s\n", device->path.string());
+ dump.appendFormat(INDENT3 "Descriptor: %s\n", device->identifier.descriptor.string());
+ dump.appendFormat(INDENT3 "Location: %s\n", device->identifier.location.string());
+ dump.appendFormat(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.string());
+ dump.appendFormat(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, "
+ "product=0x%04x, version=0x%04x\n",
+ device->identifier.bus, device->identifier.vendor,
+ device->identifier.product, device->identifier.version);
+ dump.appendFormat(INDENT3 "KeyLayoutFile: %s\n",
+ device->keyMap.keyLayoutFile.string());
+ dump.appendFormat(INDENT3 "KeyCharacterMapFile: %s\n",
+ device->keyMap.keyCharacterMapFile.string());
+ dump.appendFormat(INDENT3 "ConfigurationFile: %s\n",
+ device->configurationFile.string());
+ dump.appendFormat(INDENT3 "HaveKeyboardLayoutOverlay: %s\n",
+ toString(device->overlayKeyMap != NULL));
+ }
+ } // release lock
+}
+
+void EventHub::monitor() {
+ // Acquire and release the lock to ensure that the event hub has not deadlocked.
+ mLock.lock();
+ mLock.unlock();
+}
+
+
+}; // namespace android
diff --git a/widget/gonk/libui/EventHub.h b/widget/gonk/libui/EventHub.h
new file mode 100644
index 0000000000..e4e658b212
--- /dev/null
+++ b/widget/gonk/libui/EventHub.h
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * 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.
+ */
+
+//
+#ifndef _RUNTIME_EVENT_HUB_H
+#define _RUNTIME_EVENT_HUB_H
+
+#include "Input.h"
+#include "InputDevice.h"
+#include "Keyboard.h"
+#include "KeyLayoutMap.h"
+#include "KeyCharacterMap.h"
+#include "VirtualKeyMap.h"
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include "cutils_log.h"
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include <utils/PropertyMap.h>
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+
+#include "linux_input.h"
+#include <sys/epoll.h>
+
+/* Convenience constants. */
+
+#define BTN_FIRST 0x100 // first button code
+#define BTN_LAST 0x15f // last button code
+
+/*
+ * These constants are used privately in Android to pass raw timestamps
+ * through evdev from uinput device drivers because there is currently no
+ * other way to transfer this information. The evdev driver automatically
+ * timestamps all input events with the time they were posted and clobbers
+ * whatever information was passed in.
+ *
+ * For the purposes of this hack, the timestamp is specified in the
+ * CLOCK_MONOTONIC timebase and is split into two EV_MSC events specifying
+ * seconds and microseconds.
+ */
+#define MSC_ANDROID_TIME_SEC 0x6
+#define MSC_ANDROID_TIME_USEC 0x7
+
+namespace android {
+
+enum {
+ // Device id of a special "virtual" keyboard that is always present.
+ VIRTUAL_KEYBOARD_ID = -1,
+ // Device id of the "built-in" keyboard if there is one.
+ BUILT_IN_KEYBOARD_ID = 0,
+};
+
+/*
+ * A raw event as retrieved from the EventHub.
+ */
+struct RawEvent {
+ nsecs_t when;
+ int32_t deviceId;
+ int32_t type;
+ int32_t code;
+ int32_t value;
+};
+
+/* Describes an absolute axis. */
+struct RawAbsoluteAxisInfo {
+ bool valid; // true if the information is valid, false otherwise
+
+ int32_t minValue; // minimum value
+ int32_t maxValue; // maximum value
+ int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8
+ int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
+ int32_t resolution; // resolution in units per mm or radians per mm
+
+ inline void clear() {
+ valid = false;
+ minValue = 0;
+ maxValue = 0;
+ flat = 0;
+ fuzz = 0;
+ resolution = 0;
+ }
+};
+
+/*
+ * Input device classes.
+ */
+enum {
+ /* The input device is a keyboard or has buttons. */
+ INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001,
+
+ /* The input device is an alpha-numeric keyboard (not just a dial pad). */
+ INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002,
+
+ /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */
+ INPUT_DEVICE_CLASS_TOUCH = 0x00000004,
+
+ /* The input device is a cursor device such as a trackball or mouse. */
+ INPUT_DEVICE_CLASS_CURSOR = 0x00000008,
+
+ /* The input device is a multi-touch touchscreen. */
+ INPUT_DEVICE_CLASS_TOUCH_MT = 0x00000010,
+
+ /* The input device is a directional pad (implies keyboard, has DPAD keys). */
+ INPUT_DEVICE_CLASS_DPAD = 0x00000020,
+
+ /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
+ INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040,
+
+ /* The input device has switches. */
+ INPUT_DEVICE_CLASS_SWITCH = 0x00000080,
+
+ /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
+ INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100,
+
+ /* The input device has a vibrator (supports FF_RUMBLE). */
+ INPUT_DEVICE_CLASS_VIBRATOR = 0x00000200,
+
+ /* The input device is virtual (not a real device, not part of UI configuration). */
+ INPUT_DEVICE_CLASS_VIRTUAL = 0x40000000,
+
+ /* The input device is external (not built-in). */
+ INPUT_DEVICE_CLASS_EXTERNAL = 0x80000000,
+};
+
+/*
+ * Gets the class that owns an axis, in cases where multiple classes might claim
+ * the same axis for different purposes.
+ */
+extern uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses);
+
+/*
+ * Grand Central Station for events.
+ *
+ * The event hub aggregates input events received across all known input
+ * devices on the system, including devices that may be emulated by the simulator
+ * environment. In addition, the event hub generates fake input events to indicate
+ * when devices are added or removed.
+ *
+ * The event hub provides a stream of input events (via the getEvent function).
+ * It also supports querying the current actual state of input devices such as identifying
+ * which keys are currently down. Finally, the event hub keeps track of the capabilities of
+ * individual input devices, such as their class and the set of key codes that they support.
+ */
+class EventHubInterface : public virtual RefBase {
+protected:
+ EventHubInterface() { }
+ virtual ~EventHubInterface() { }
+
+public:
+ // Synthetic raw event type codes produced when devices are added or removed.
+ enum {
+ // Sent when a device is added.
+ DEVICE_ADDED = 0x10000000,
+ // Sent when a device is removed.
+ DEVICE_REMOVED = 0x20000000,
+ // Sent when all added/removed devices from the most recent scan have been reported.
+ // This event is always sent at least once.
+ FINISHED_DEVICE_SCAN = 0x30000000,
+
+ FIRST_SYNTHETIC_EVENT = DEVICE_ADDED,
+ };
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
+
+ virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const = 0;
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0;
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
+
+ virtual bool hasInputProperty(int32_t deviceId, int property) const = 0;
+
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const = 0;
+
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
+ AxisInfo* outAxisInfo) const = 0;
+
+ // Sets devices that are excluded from opening.
+ // This can be used to ignore input devices for sensors.
+ virtual void setExcludedDevices(const Vector<String8>& devices) = 0;
+
+ /*
+ * Wait for events to become available and returns them.
+ * After returning, the EventHub holds onto a wake lock until the next call to getEvent.
+ * This ensures that the device will not go to sleep while the event is being processed.
+ * If the device needs to remain awake longer than that, then the caller is responsible
+ * for taking care of it (say, by poking the power manager user activity timer).
+ *
+ * The timeout is advisory only. If the device is asleep, it will not wake just to
+ * service the timeout.
+ *
+ * Returns the number of events obtained, or 0 if the timeout expired.
+ */
+ virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0;
+
+ /*
+ * Query current input state.
+ */
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
+ virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
+ int32_t* outValue) const = 0;
+
+ /*
+ * Examine key input devices for specific framework keycode support
+ */
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+ uint8_t* outFlags) const = 0;
+
+ virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0;
+ virtual bool hasLed(int32_t deviceId, int32_t led) const = 0;
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on) = 0;
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const = 0;
+
+ virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const = 0;
+ virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map) = 0;
+
+ /* Control the vibrator. */
+ virtual void vibrate(int32_t deviceId, nsecs_t duration) = 0;
+ virtual void cancelVibrate(int32_t deviceId) = 0;
+
+ /* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
+ virtual void requestReopenDevices() = 0;
+
+ /* Wakes up getEvents() if it is blocked on a read. */
+ virtual void wake() = 0;
+
+ /* Dump EventHub state to a string. */
+ virtual void dump(String8& dump) = 0;
+
+ /* Called by the heatbeat to ensures that the reader has not deadlocked. */
+ virtual void monitor() = 0;
+};
+
+class EventHub : public EventHubInterface
+{
+public:
+ EventHub();
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const;
+
+ virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const;
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const;
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const;
+
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const;
+
+ virtual bool hasInputProperty(int32_t deviceId, int property) const;
+
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const;
+
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
+ AxisInfo* outAxisInfo) const;
+
+ virtual void setExcludedDevices(const Vector<String8>& devices);
+
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const;
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const;
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const;
+ virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const;
+
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const;
+
+ virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize);
+
+ virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const;
+ virtual bool hasLed(int32_t deviceId, int32_t led) const;
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on);
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const;
+
+ virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const;
+ virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map);
+
+ virtual void vibrate(int32_t deviceId, nsecs_t duration);
+ virtual void cancelVibrate(int32_t deviceId);
+
+ virtual void requestReopenDevices();
+
+ virtual void wake();
+
+ virtual void dump(String8& dump);
+ virtual void monitor();
+
+protected:
+ virtual ~EventHub();
+
+private:
+ struct Device {
+ Device* next;
+
+ int fd; // may be -1 if device is virtual
+ const int32_t id;
+ const String8 path;
+ const InputDeviceIdentifier identifier;
+
+ uint32_t classes;
+
+ uint8_t keyBitmask[(KEY_MAX + 1) / 8];
+ uint8_t absBitmask[(ABS_MAX + 1) / 8];
+ uint8_t relBitmask[(REL_MAX + 1) / 8];
+ uint8_t swBitmask[(SW_MAX + 1) / 8];
+ uint8_t ledBitmask[(LED_MAX + 1) / 8];
+ uint8_t ffBitmask[(FF_MAX + 1) / 8];
+ uint8_t propBitmask[(INPUT_PROP_MAX + 1) / 8];
+
+ String8 configurationFile;
+ PropertyMap* configuration;
+ VirtualKeyMap* virtualKeyMap;
+ KeyMap keyMap;
+
+ sp<KeyCharacterMap> overlayKeyMap;
+ sp<KeyCharacterMap> combinedKeyMap;
+
+ bool ffEffectPlaying;
+ int16_t ffEffectId; // initially -1
+
+ int32_t timestampOverrideSec;
+ int32_t timestampOverrideUsec;
+
+ Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier);
+ ~Device();
+
+ void close();
+
+ inline bool isVirtual() const { return fd < 0; }
+
+ const sp<KeyCharacterMap>& getKeyCharacterMap() const {
+ if (combinedKeyMap != NULL) {
+ return combinedKeyMap;
+ }
+ return keyMap.keyCharacterMap;
+ }
+ };
+
+ status_t openDeviceLocked(const char *devicePath);
+ void createVirtualKeyboardLocked();
+ void addDeviceLocked(Device* device);
+
+ status_t closeDeviceByPathLocked(const char *devicePath);
+ void closeDeviceLocked(Device* device);
+ void closeAllDevicesLocked();
+
+ status_t scanDirLocked(const char *dirname);
+ void scanDevicesLocked();
+ status_t readNotifyLocked();
+
+ Device* getDeviceLocked(int32_t deviceId) const;
+ Device* getDeviceByPathLocked(const char* devicePath) const;
+
+ bool hasKeycodeLocked(Device* device, int keycode) const;
+
+ void loadConfigurationLocked(Device* device);
+ status_t loadVirtualKeyMapLocked(Device* device);
+ status_t loadKeyMapLocked(Device* device);
+
+ bool isExternalDeviceLocked(Device* device);
+
+ // Protect all internal state.
+ mutable Mutex mLock;
+
+ // The actual id of the built-in keyboard, or NO_BUILT_IN_KEYBOARD if none.
+ // EventHub remaps the built-in keyboard to id 0 externally as required by the API.
+ enum {
+ // Must not conflict with any other assigned device ids, including
+ // the virtual keyboard id (-1).
+ NO_BUILT_IN_KEYBOARD = -2,
+ };
+ int32_t mBuiltInKeyboardId;
+
+ int32_t mNextDeviceId;
+
+ KeyedVector<int32_t, Device*> mDevices;
+
+ Device *mOpeningDevices;
+ Device *mClosingDevices;
+
+ bool mNeedToSendFinishedDeviceScan;
+ bool mNeedToReopenDevices;
+ bool mNeedToScanDevices;
+ Vector<String8> mExcludedDevices;
+
+ int mEpollFd;
+ int mINotifyFd;
+ int mWakeReadPipeFd;
+ int mWakeWritePipeFd;
+
+ // Ids used for epoll notifications not associated with devices.
+ static const uint32_t EPOLL_ID_INOTIFY = 0x80000001;
+ static const uint32_t EPOLL_ID_WAKE = 0x80000002;
+
+ // Epoll FD list size hint.
+ static const int EPOLL_SIZE_HINT = 8;
+
+ // Maximum number of signalled FDs to handle at a time.
+ static const int EPOLL_MAX_EVENTS = 16;
+
+ // The array of pending epoll events and the index of the next event to be handled.
+ struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];
+ size_t mPendingEventCount;
+ size_t mPendingEventIndex;
+ bool mPendingINotify;
+};
+
+}; // namespace android
+
+#endif // _RUNTIME_EVENT_HUB_H
diff --git a/widget/gonk/libui/Input.cpp b/widget/gonk/libui/Input.cpp
new file mode 100644
index 0000000000..2208191e65
--- /dev/null
+++ b/widget/gonk/libui/Input.cpp
@@ -0,0 +1,635 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "Input"
+//#define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+#include <math.h>
+#include <limits.h>
+
+#include "Input.h"
+
+#ifdef HAVE_ANDROID_OS
+#include <binder/Parcel.h>
+
+#include "SkPoint.h"
+#include "SkMatrix.h"
+#include "SkScalar.h"
+#endif
+
+namespace android {
+
+// --- InputEvent ---
+
+void InputEvent::initialize(int32_t deviceId, int32_t source) {
+ mDeviceId = deviceId;
+ mSource = source;
+}
+
+void InputEvent::initialize(const InputEvent& from) {
+ mDeviceId = from.mDeviceId;
+ mSource = from.mSource;
+}
+
+// --- KeyEvent ---
+
+bool KeyEvent::hasDefaultAction(int32_t keyCode) {
+ switch (keyCode) {
+ case AKEYCODE_HOME:
+ case AKEYCODE_BACK:
+ case AKEYCODE_CALL:
+ case AKEYCODE_ENDCALL:
+ case AKEYCODE_VOLUME_UP:
+ case AKEYCODE_VOLUME_DOWN:
+ case AKEYCODE_VOLUME_MUTE:
+ case AKEYCODE_POWER:
+ case AKEYCODE_CAMERA:
+ case AKEYCODE_HEADSETHOOK:
+ case AKEYCODE_MENU:
+ case AKEYCODE_NOTIFICATION:
+ case AKEYCODE_FOCUS:
+ case AKEYCODE_SEARCH:
+ case AKEYCODE_MEDIA_PLAY:
+ case AKEYCODE_MEDIA_PAUSE:
+ case AKEYCODE_MEDIA_PLAY_PAUSE:
+ case AKEYCODE_MEDIA_STOP:
+ case AKEYCODE_MEDIA_NEXT:
+ case AKEYCODE_MEDIA_PREVIOUS:
+ case AKEYCODE_MEDIA_REWIND:
+ case AKEYCODE_MEDIA_RECORD:
+ case AKEYCODE_MEDIA_FAST_FORWARD:
+ case AKEYCODE_MUTE:
+ case AKEYCODE_BRIGHTNESS_DOWN:
+ case AKEYCODE_BRIGHTNESS_UP:
+ return true;
+ }
+
+ return false;
+}
+
+bool KeyEvent::hasDefaultAction() const {
+ return hasDefaultAction(getKeyCode());
+}
+
+bool KeyEvent::isSystemKey(int32_t keyCode) {
+ switch (keyCode) {
+ case AKEYCODE_MENU:
+ case AKEYCODE_SOFT_RIGHT:
+ case AKEYCODE_HOME:
+ case AKEYCODE_BACK:
+ case AKEYCODE_CALL:
+ case AKEYCODE_ENDCALL:
+ case AKEYCODE_VOLUME_UP:
+ case AKEYCODE_VOLUME_DOWN:
+ case AKEYCODE_VOLUME_MUTE:
+ case AKEYCODE_MUTE:
+ case AKEYCODE_POWER:
+ case AKEYCODE_HEADSETHOOK:
+ case AKEYCODE_MEDIA_PLAY:
+ case AKEYCODE_MEDIA_PAUSE:
+ case AKEYCODE_MEDIA_PLAY_PAUSE:
+ case AKEYCODE_MEDIA_STOP:
+ case AKEYCODE_MEDIA_NEXT:
+ case AKEYCODE_MEDIA_PREVIOUS:
+ case AKEYCODE_MEDIA_REWIND:
+ case AKEYCODE_MEDIA_RECORD:
+ case AKEYCODE_MEDIA_FAST_FORWARD:
+ case AKEYCODE_CAMERA:
+ case AKEYCODE_FOCUS:
+ case AKEYCODE_SEARCH:
+ case AKEYCODE_BRIGHTNESS_DOWN:
+ case AKEYCODE_BRIGHTNESS_UP:
+ return true;
+ }
+
+ return false;
+}
+
+bool KeyEvent::isSystemKey() const {
+ return isSystemKey(getKeyCode());
+}
+
+void KeyEvent::initialize(
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t keyCode,
+ int32_t scanCode,
+ int32_t metaState,
+ int32_t repeatCount,
+ nsecs_t downTime,
+ nsecs_t eventTime) {
+ InputEvent::initialize(deviceId, source);
+ mAction = action;
+ mFlags = flags;
+ mKeyCode = keyCode;
+ mScanCode = scanCode;
+ mMetaState = metaState;
+ mRepeatCount = repeatCount;
+ mDownTime = downTime;
+ mEventTime = eventTime;
+}
+
+void KeyEvent::initialize(const KeyEvent& from) {
+ InputEvent::initialize(from);
+ mAction = from.mAction;
+ mFlags = from.mFlags;
+ mKeyCode = from.mKeyCode;
+ mScanCode = from.mScanCode;
+ mMetaState = from.mMetaState;
+ mRepeatCount = from.mRepeatCount;
+ mDownTime = from.mDownTime;
+ mEventTime = from.mEventTime;
+}
+
+
+// --- PointerCoords ---
+
+float PointerCoords::getAxisValue(int32_t axis) const {
+ if (axis < 0 || axis > 63) {
+ return 0;
+ }
+
+ uint64_t axisBit = 1LL << axis;
+ if (!(bits & axisBit)) {
+ return 0;
+ }
+ uint32_t index = __builtin_popcountll(bits & (axisBit - 1LL));
+ return values[index];
+}
+
+status_t PointerCoords::setAxisValue(int32_t axis, float value) {
+ if (axis < 0 || axis > 63) {
+ return NAME_NOT_FOUND;
+ }
+
+ uint64_t axisBit = 1LL << axis;
+ uint32_t index = __builtin_popcountll(bits & (axisBit - 1LL));
+ if (!(bits & axisBit)) {
+ if (value == 0) {
+ return OK; // axes with value 0 do not need to be stored
+ }
+ uint32_t count = __builtin_popcountll(bits);
+ if (count >= MAX_AXES) {
+ tooManyAxes(axis);
+ return NO_MEMORY;
+ }
+ bits |= axisBit;
+ for (uint32_t i = count; i > index; i--) {
+ values[i] = values[i - 1];
+ }
+ }
+ values[index] = value;
+ return OK;
+}
+
+static inline void scaleAxisValue(PointerCoords& c, int axis, float scaleFactor) {
+ float value = c.getAxisValue(axis);
+ if (value != 0) {
+ c.setAxisValue(axis, value * scaleFactor);
+ }
+}
+
+void PointerCoords::scale(float scaleFactor) {
+ // No need to scale pressure or size since they are normalized.
+ // No need to scale orientation since it is meaningless to do so.
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_X, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_Y, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MAJOR, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOUCH_MINOR, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MAJOR, scaleFactor);
+ scaleAxisValue(*this, AMOTION_EVENT_AXIS_TOOL_MINOR, scaleFactor);
+}
+
+#ifdef HAVE_ANDROID_OS
+status_t PointerCoords::readFromParcel(Parcel* parcel) {
+ bits = parcel->readInt64();
+
+ uint32_t count = __builtin_popcountll(bits);
+ if (count > MAX_AXES) {
+ return BAD_VALUE;
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ values[i] = parcel->readFloat();
+ }
+ return OK;
+}
+
+status_t PointerCoords::writeToParcel(Parcel* parcel) const {
+ parcel->writeInt64(bits);
+
+ uint32_t count = __builtin_popcountll(bits);
+ for (uint32_t i = 0; i < count; i++) {
+ parcel->writeFloat(values[i]);
+ }
+ return OK;
+}
+#endif
+
+void PointerCoords::tooManyAxes(int axis) {
+ ALOGW("Could not set value for axis %d because the PointerCoords structure is full and "
+ "cannot contain more than %d axis values.", axis, int(MAX_AXES));
+}
+
+bool PointerCoords::operator==(const PointerCoords& other) const {
+ if (bits != other.bits) {
+ return false;
+ }
+ uint32_t count = __builtin_popcountll(bits);
+ for (uint32_t i = 0; i < count; i++) {
+ if (values[i] != other.values[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void PointerCoords::copyFrom(const PointerCoords& other) {
+ bits = other.bits;
+ uint32_t count = __builtin_popcountll(bits);
+ for (uint32_t i = 0; i < count; i++) {
+ values[i] = other.values[i];
+ }
+}
+
+
+// --- PointerProperties ---
+
+bool PointerProperties::operator==(const PointerProperties& other) const {
+ return id == other.id
+ && toolType == other.toolType;
+}
+
+void PointerProperties::copyFrom(const PointerProperties& other) {
+ id = other.id;
+ toolType = other.toolType;
+}
+
+
+// --- MotionEvent ---
+
+void MotionEvent::initialize(
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t edgeFlags,
+ int32_t metaState,
+ int32_t buttonState,
+ float xOffset,
+ float yOffset,
+ float xPrecision,
+ float yPrecision,
+ nsecs_t downTime,
+ nsecs_t eventTime,
+ size_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords) {
+ InputEvent::initialize(deviceId, source);
+ mAction = action;
+ mFlags = flags;
+ mEdgeFlags = edgeFlags;
+ mMetaState = metaState;
+ mButtonState = buttonState;
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+ mXPrecision = xPrecision;
+ mYPrecision = yPrecision;
+ mDownTime = downTime;
+ mPointerProperties.clear();
+ mPointerProperties.appendArray(pointerProperties, pointerCount);
+ mSampleEventTimes.clear();
+ mSamplePointerCoords.clear();
+ addSample(eventTime, pointerCoords);
+}
+
+void MotionEvent::copyFrom(const MotionEvent* other, bool keepHistory) {
+ InputEvent::initialize(other->mDeviceId, other->mSource);
+ mAction = other->mAction;
+ mFlags = other->mFlags;
+ mEdgeFlags = other->mEdgeFlags;
+ mMetaState = other->mMetaState;
+ mButtonState = other->mButtonState;
+ mXOffset = other->mXOffset;
+ mYOffset = other->mYOffset;
+ mXPrecision = other->mXPrecision;
+ mYPrecision = other->mYPrecision;
+ mDownTime = other->mDownTime;
+ mPointerProperties = other->mPointerProperties;
+
+ if (keepHistory) {
+ mSampleEventTimes = other->mSampleEventTimes;
+ mSamplePointerCoords = other->mSamplePointerCoords;
+ } else {
+ mSampleEventTimes.clear();
+ mSampleEventTimes.push(other->getEventTime());
+ mSamplePointerCoords.clear();
+ size_t pointerCount = other->getPointerCount();
+ size_t historySize = other->getHistorySize();
+ mSamplePointerCoords.appendArray(other->mSamplePointerCoords.array()
+ + (historySize * pointerCount), pointerCount);
+ }
+}
+
+void MotionEvent::addSample(
+ int64_t eventTime,
+ const PointerCoords* pointerCoords) {
+ mSampleEventTimes.push(eventTime);
+ mSamplePointerCoords.appendArray(pointerCoords, getPointerCount());
+}
+
+const PointerCoords* MotionEvent::getRawPointerCoords(size_t pointerIndex) const {
+ return &mSamplePointerCoords[getHistorySize() * getPointerCount() + pointerIndex];
+}
+
+float MotionEvent::getRawAxisValue(int32_t axis, size_t pointerIndex) const {
+ return getRawPointerCoords(pointerIndex)->getAxisValue(axis);
+}
+
+float MotionEvent::getAxisValue(int32_t axis, size_t pointerIndex) const {
+ float value = getRawPointerCoords(pointerIndex)->getAxisValue(axis);
+ switch (axis) {
+ case AMOTION_EVENT_AXIS_X:
+ return value + mXOffset;
+ case AMOTION_EVENT_AXIS_Y:
+ return value + mYOffset;
+ }
+ return value;
+}
+
+const PointerCoords* MotionEvent::getHistoricalRawPointerCoords(
+ size_t pointerIndex, size_t historicalIndex) const {
+ return &mSamplePointerCoords[historicalIndex * getPointerCount() + pointerIndex];
+}
+
+float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
+ size_t historicalIndex) const {
+ return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
+}
+
+float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
+ size_t historicalIndex) const {
+ float value = getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->getAxisValue(axis);
+ switch (axis) {
+ case AMOTION_EVENT_AXIS_X:
+ return value + mXOffset;
+ case AMOTION_EVENT_AXIS_Y:
+ return value + mYOffset;
+ }
+ return value;
+}
+
+ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
+ size_t pointerCount = mPointerProperties.size();
+ for (size_t i = 0; i < pointerCount; i++) {
+ if (mPointerProperties.itemAt(i).id == pointerId) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void MotionEvent::offsetLocation(float xOffset, float yOffset) {
+ mXOffset += xOffset;
+ mYOffset += yOffset;
+}
+
+void MotionEvent::scale(float scaleFactor) {
+ mXOffset *= scaleFactor;
+ mYOffset *= scaleFactor;
+ mXPrecision *= scaleFactor;
+ mYPrecision *= scaleFactor;
+
+ size_t numSamples = mSamplePointerCoords.size();
+ for (size_t i = 0; i < numSamples; i++) {
+ mSamplePointerCoords.editItemAt(i).scale(scaleFactor);
+ }
+}
+
+#ifdef HAVE_ANDROID_OS
+static inline float transformAngle(const SkMatrix* matrix, float angleRadians) {
+ // Construct and transform a vector oriented at the specified clockwise angle from vertical.
+ // Coordinate system: down is increasing Y, right is increasing X.
+ SkPoint vector;
+ vector.fX = SkFloatToScalar(sinf(angleRadians));
+ vector.fY = SkFloatToScalar(-cosf(angleRadians));
+ matrix->mapVectors(& vector, 1);
+
+ // Derive the transformed vector's clockwise angle from vertical.
+ float result = atan2f(SkScalarToFloat(vector.fX), SkScalarToFloat(-vector.fY));
+ if (result < - M_PI_2) {
+ result += M_PI;
+ } else if (result > M_PI_2) {
+ result -= M_PI;
+ }
+ return result;
+}
+
+void MotionEvent::transform(const SkMatrix* matrix) {
+ float oldXOffset = mXOffset;
+ float oldYOffset = mYOffset;
+
+ // The tricky part of this implementation is to preserve the value of
+ // rawX and rawY. So we apply the transformation to the first point
+ // then derive an appropriate new X/Y offset that will preserve rawX and rawY.
+ SkPoint point;
+ float rawX = getRawX(0);
+ float rawY = getRawY(0);
+ matrix->mapXY(SkFloatToScalar(rawX + oldXOffset), SkFloatToScalar(rawY + oldYOffset),
+ & point);
+ float newX = SkScalarToFloat(point.fX);
+ float newY = SkScalarToFloat(point.fY);
+ float newXOffset = newX - rawX;
+ float newYOffset = newY - rawY;
+
+ mXOffset = newXOffset;
+ mYOffset = newYOffset;
+
+ // Apply the transformation to all samples.
+ size_t numSamples = mSamplePointerCoords.size();
+ for (size_t i = 0; i < numSamples; i++) {
+ PointerCoords& c = mSamplePointerCoords.editItemAt(i);
+ float x = c.getAxisValue(AMOTION_EVENT_AXIS_X) + oldXOffset;
+ float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y) + oldYOffset;
+ matrix->mapXY(SkFloatToScalar(x), SkFloatToScalar(y), &point);
+ c.setAxisValue(AMOTION_EVENT_AXIS_X, SkScalarToFloat(point.fX) - newXOffset);
+ c.setAxisValue(AMOTION_EVENT_AXIS_Y, SkScalarToFloat(point.fY) - newYOffset);
+
+ float orientation = c.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+ c.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, transformAngle(matrix, orientation));
+ }
+}
+
+status_t MotionEvent::readFromParcel(Parcel* parcel) {
+ size_t pointerCount = parcel->readInt32();
+ size_t sampleCount = parcel->readInt32();
+ if (pointerCount == 0 || pointerCount > MAX_POINTERS || sampleCount == 0) {
+ return BAD_VALUE;
+ }
+
+ mDeviceId = parcel->readInt32();
+ mSource = parcel->readInt32();
+ mAction = parcel->readInt32();
+ mFlags = parcel->readInt32();
+ mEdgeFlags = parcel->readInt32();
+ mMetaState = parcel->readInt32();
+ mButtonState = parcel->readInt32();
+ mXOffset = parcel->readFloat();
+ mYOffset = parcel->readFloat();
+ mXPrecision = parcel->readFloat();
+ mYPrecision = parcel->readFloat();
+ mDownTime = parcel->readInt64();
+
+ mPointerProperties.clear();
+ mPointerProperties.setCapacity(pointerCount);
+ mSampleEventTimes.clear();
+ mSampleEventTimes.setCapacity(sampleCount);
+ mSamplePointerCoords.clear();
+ mSamplePointerCoords.setCapacity(sampleCount * pointerCount);
+
+ for (size_t i = 0; i < pointerCount; i++) {
+ mPointerProperties.push();
+ PointerProperties& properties = mPointerProperties.editTop();
+ properties.id = parcel->readInt32();
+ properties.toolType = parcel->readInt32();
+ }
+
+ while (sampleCount-- > 0) {
+ mSampleEventTimes.push(parcel->readInt64());
+ for (size_t i = 0; i < pointerCount; i++) {
+ mSamplePointerCoords.push();
+ status_t status = mSamplePointerCoords.editTop().readFromParcel(parcel);
+ if (status) {
+ return status;
+ }
+ }
+ }
+ return OK;
+}
+
+status_t MotionEvent::writeToParcel(Parcel* parcel) const {
+ size_t pointerCount = mPointerProperties.size();
+ size_t sampleCount = mSampleEventTimes.size();
+
+ parcel->writeInt32(pointerCount);
+ parcel->writeInt32(sampleCount);
+
+ parcel->writeInt32(mDeviceId);
+ parcel->writeInt32(mSource);
+ parcel->writeInt32(mAction);
+ parcel->writeInt32(mFlags);
+ parcel->writeInt32(mEdgeFlags);
+ parcel->writeInt32(mMetaState);
+ parcel->writeInt32(mButtonState);
+ parcel->writeFloat(mXOffset);
+ parcel->writeFloat(mYOffset);
+ parcel->writeFloat(mXPrecision);
+ parcel->writeFloat(mYPrecision);
+ parcel->writeInt64(mDownTime);
+
+ for (size_t i = 0; i < pointerCount; i++) {
+ const PointerProperties& properties = mPointerProperties.itemAt(i);
+ parcel->writeInt32(properties.id);
+ parcel->writeInt32(properties.toolType);
+ }
+
+ const PointerCoords* pc = mSamplePointerCoords.array();
+ for (size_t h = 0; h < sampleCount; h++) {
+ parcel->writeInt64(mSampleEventTimes.itemAt(h));
+ for (size_t i = 0; i < pointerCount; i++) {
+ status_t status = (pc++)->writeToParcel(parcel);
+ if (status) {
+ return status;
+ }
+ }
+ }
+ return OK;
+}
+#endif
+
+bool MotionEvent::isTouchEvent(int32_t source, int32_t action) {
+ if (source & AINPUT_SOURCE_CLASS_POINTER) {
+ // Specifically excludes HOVER_MOVE and SCROLL.
+ switch (action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_MOVE:
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ return true;
+ }
+ }
+ return false;
+}
+
+
+// --- PooledInputEventFactory ---
+
+PooledInputEventFactory::PooledInputEventFactory(size_t maxPoolSize) :
+ mMaxPoolSize(maxPoolSize) {
+}
+
+PooledInputEventFactory::~PooledInputEventFactory() {
+ for (size_t i = 0; i < mKeyEventPool.size(); i++) {
+ delete mKeyEventPool.itemAt(i);
+ }
+ for (size_t i = 0; i < mMotionEventPool.size(); i++) {
+ delete mMotionEventPool.itemAt(i);
+ }
+}
+
+KeyEvent* PooledInputEventFactory::createKeyEvent() {
+ if (!mKeyEventPool.isEmpty()) {
+ KeyEvent* event = mKeyEventPool.top();
+ mKeyEventPool.pop();
+ return event;
+ }
+ return new KeyEvent();
+}
+
+MotionEvent* PooledInputEventFactory::createMotionEvent() {
+ if (!mMotionEventPool.isEmpty()) {
+ MotionEvent* event = mMotionEventPool.top();
+ mMotionEventPool.pop();
+ return event;
+ }
+ return new MotionEvent();
+}
+
+void PooledInputEventFactory::recycle(InputEvent* event) {
+ switch (event->getType()) {
+ case AINPUT_EVENT_TYPE_KEY:
+ if (mKeyEventPool.size() < mMaxPoolSize) {
+ mKeyEventPool.push(static_cast<KeyEvent*>(event));
+ return;
+ }
+ break;
+ case AINPUT_EVENT_TYPE_MOTION:
+ if (mMotionEventPool.size() < mMaxPoolSize) {
+ mMotionEventPool.push(static_cast<MotionEvent*>(event));
+ return;
+ }
+ break;
+ }
+ delete event;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/Input.h b/widget/gonk/libui/Input.h
new file mode 100644
index 0000000000..3d958bfabb
--- /dev/null
+++ b/widget/gonk/libui/Input.h
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_INPUT_H
+#define _ANDROIDFW_INPUT_H
+
+/**
+ * Native input event structures.
+ */
+
+#include "android_input.h"
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+#ifdef HAVE_ANDROID_OS
+class SkMatrix;
+#endif
+
+/*
+ * Additional private constants not defined in ndk/ui/input.h.
+ */
+enum {
+ /* Signifies that the key is being predispatched */
+ AKEY_EVENT_FLAG_PREDISPATCH = 0x20000000,
+
+ /* Private control to determine when an app is tracking a key sequence. */
+ AKEY_EVENT_FLAG_START_TRACKING = 0x40000000,
+
+ /* Key event is inconsistent with previously sent key events. */
+ AKEY_EVENT_FLAG_TAINTED = 0x80000000,
+};
+
+enum {
+ /* Motion event is inconsistent with previously sent motion events. */
+ AMOTION_EVENT_FLAG_TAINTED = 0x80000000,
+};
+
+enum {
+ /* Used when a motion event is not associated with any display.
+ * Typically used for non-pointer events. */
+ ADISPLAY_ID_NONE = -1,
+
+ /* The default display id. */
+ ADISPLAY_ID_DEFAULT = 0,
+};
+
+enum {
+ /*
+ * Indicates that an input device has switches.
+ * This input source flag is hidden from the API because switches are only used by the system
+ * and applications have no way to interact with them.
+ */
+ AINPUT_SOURCE_SWITCH = 0x80000000,
+};
+
+/*
+ * SystemUiVisibility constants from View.
+ */
+enum {
+ ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE = 0,
+ ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN = 0x00000001,
+};
+
+/*
+ * Maximum number of pointers supported per motion event.
+ * Smallest number of pointers is 1.
+ * (We want at least 10 but some touch controllers obstensibly configured for 10 pointers
+ * will occasionally emit 11. There is not much harm making this constant bigger.)
+ */
+#define MAX_POINTERS 16
+
+/*
+ * Maximum pointer id value supported in a motion event.
+ * Smallest pointer id is 0.
+ * (This is limited by our use of BitSet32 to track pointer assignments.)
+ */
+#define MAX_POINTER_ID 31
+
+/*
+ * Declare a concrete type for the NDK's input event forward declaration.
+ */
+struct AInputEvent {
+ virtual ~AInputEvent() { }
+};
+
+/*
+ * Declare a concrete type for the NDK's input device forward declaration.
+ */
+struct AInputDevice {
+ virtual ~AInputDevice() { }
+};
+
+
+namespace android {
+
+#ifdef HAVE_ANDROID_OS
+class Parcel;
+#endif
+
+/*
+ * Flags that flow alongside events in the input dispatch system to help with certain
+ * policy decisions such as waking from device sleep.
+ *
+ * These flags are also defined in frameworks/base/core/java/android/view/WindowManagerPolicy.java.
+ */
+enum {
+ /* These flags originate in RawEvents and are generally set in the key map.
+ * NOTE: If you edit these flags, also edit labels in KeycodeLabels.h. */
+
+ POLICY_FLAG_WAKE = 0x00000001,
+ POLICY_FLAG_WAKE_DROPPED = 0x00000002,
+ POLICY_FLAG_SHIFT = 0x00000004,
+ POLICY_FLAG_CAPS_LOCK = 0x00000008,
+ POLICY_FLAG_ALT = 0x00000010,
+ POLICY_FLAG_ALT_GR = 0x00000020,
+ POLICY_FLAG_MENU = 0x00000040,
+ POLICY_FLAG_LAUNCHER = 0x00000080,
+ POLICY_FLAG_VIRTUAL = 0x00000100,
+ POLICY_FLAG_FUNCTION = 0x00000200,
+
+ POLICY_FLAG_RAW_MASK = 0x0000ffff,
+
+ /* These flags are set by the input dispatcher. */
+
+ // Indicates that the input event was injected.
+ POLICY_FLAG_INJECTED = 0x01000000,
+
+ // Indicates that the input event is from a trusted source such as a directly attached
+ // input device or an application with system-wide event injection permission.
+ POLICY_FLAG_TRUSTED = 0x02000000,
+
+ // Indicates that the input event has passed through an input filter.
+ POLICY_FLAG_FILTERED = 0x04000000,
+
+ // Disables automatic key repeating behavior.
+ POLICY_FLAG_DISABLE_KEY_REPEAT = 0x08000000,
+
+ /* These flags are set by the input reader policy as it intercepts each event. */
+
+ // Indicates that the screen was off when the event was received and the event
+ // should wake the device.
+ POLICY_FLAG_WOKE_HERE = 0x10000000,
+
+ // Indicates that the screen was dim when the event was received and the event
+ // should brighten the device.
+ POLICY_FLAG_BRIGHT_HERE = 0x20000000,
+
+ // Indicates that the event should be dispatched to applications.
+ // The input event should still be sent to the InputDispatcher so that it can see all
+ // input events received include those that it will not deliver.
+ POLICY_FLAG_PASS_TO_USER = 0x40000000,
+};
+
+/*
+ * Pointer coordinate data.
+ */
+struct PointerCoords {
+ enum { MAX_AXES = 14 }; // 14 so that sizeof(PointerCoords) == 64
+
+ // Bitfield of axes that are present in this structure.
+ uint64_t bits;
+
+ // Values of axes that are stored in this structure packed in order by axis id
+ // for each axis that is present in the structure according to 'bits'.
+ float values[MAX_AXES];
+
+ inline void clear() {
+ bits = 0;
+ }
+
+ float getAxisValue(int32_t axis) const;
+ status_t setAxisValue(int32_t axis, float value);
+
+ void scale(float scale);
+
+ inline float getX() const {
+ return getAxisValue(AMOTION_EVENT_AXIS_X);
+ }
+
+ inline float getY() const {
+ return getAxisValue(AMOTION_EVENT_AXIS_Y);
+ }
+
+#ifdef HAVE_ANDROID_OS
+ status_t readFromParcel(Parcel* parcel);
+ status_t writeToParcel(Parcel* parcel) const;
+#endif
+
+ bool operator==(const PointerCoords& other) const;
+ inline bool operator!=(const PointerCoords& other) const {
+ return !(*this == other);
+ }
+
+ void copyFrom(const PointerCoords& other);
+
+private:
+ void tooManyAxes(int axis);
+};
+
+/*
+ * Pointer property data.
+ */
+struct PointerProperties {
+ // The id of the pointer.
+ int32_t id;
+
+ // The pointer tool type.
+ int32_t toolType;
+
+ inline void clear() {
+ id = -1;
+ toolType = 0;
+ }
+
+ bool operator==(const PointerProperties& other) const;
+ inline bool operator!=(const PointerProperties& other) const {
+ return !(*this == other);
+ }
+
+ void copyFrom(const PointerProperties& other);
+};
+
+/*
+ * Input events.
+ */
+class InputEvent : public AInputEvent {
+public:
+ virtual ~InputEvent() { }
+
+ virtual int32_t getType() const = 0;
+
+ inline int32_t getDeviceId() const { return mDeviceId; }
+
+ inline int32_t getSource() const { return mSource; }
+
+ inline void setSource(int32_t source) { mSource = source; }
+
+protected:
+ void initialize(int32_t deviceId, int32_t source);
+ void initialize(const InputEvent& from);
+
+ int32_t mDeviceId;
+ int32_t mSource;
+};
+
+/*
+ * Key events.
+ */
+class KeyEvent : public InputEvent {
+public:
+ virtual ~KeyEvent() { }
+
+ virtual int32_t getType() const { return AINPUT_EVENT_TYPE_KEY; }
+
+ inline int32_t getAction() const { return mAction; }
+
+ inline int32_t getFlags() const { return mFlags; }
+
+ inline void setFlags(int32_t flags) { mFlags = flags; }
+
+ inline int32_t getKeyCode() const { return mKeyCode; }
+
+ inline int32_t getScanCode() const { return mScanCode; }
+
+ inline int32_t getMetaState() const { return mMetaState; }
+
+ inline int32_t getRepeatCount() const { return mRepeatCount; }
+
+ inline nsecs_t getDownTime() const { return mDownTime; }
+
+ inline nsecs_t getEventTime() const { return mEventTime; }
+
+ // Return true if this event may have a default action implementation.
+ static bool hasDefaultAction(int32_t keyCode);
+ bool hasDefaultAction() const;
+
+ // Return true if this event represents a system key.
+ static bool isSystemKey(int32_t keyCode);
+ bool isSystemKey() const;
+
+ void initialize(
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t keyCode,
+ int32_t scanCode,
+ int32_t metaState,
+ int32_t repeatCount,
+ nsecs_t downTime,
+ nsecs_t eventTime);
+ void initialize(const KeyEvent& from);
+
+protected:
+ int32_t mAction;
+ int32_t mFlags;
+ int32_t mKeyCode;
+ int32_t mScanCode;
+ int32_t mMetaState;
+ int32_t mRepeatCount;
+ nsecs_t mDownTime;
+ nsecs_t mEventTime;
+};
+
+/*
+ * Motion events.
+ */
+class MotionEvent : public InputEvent {
+public:
+ virtual ~MotionEvent() { }
+
+ virtual int32_t getType() const { return AINPUT_EVENT_TYPE_MOTION; }
+
+ inline int32_t getAction() const { return mAction; }
+
+ inline int32_t getActionMasked() const { return mAction & AMOTION_EVENT_ACTION_MASK; }
+
+ inline int32_t getActionIndex() const {
+ return (mAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ inline void setAction(int32_t action) { mAction = action; }
+
+ inline int32_t getFlags() const { return mFlags; }
+
+ inline void setFlags(int32_t flags) { mFlags = flags; }
+
+ inline int32_t getEdgeFlags() const { return mEdgeFlags; }
+
+ inline void setEdgeFlags(int32_t edgeFlags) { mEdgeFlags = edgeFlags; }
+
+ inline int32_t getMetaState() const { return mMetaState; }
+
+ inline void setMetaState(int32_t metaState) { mMetaState = metaState; }
+
+ inline int32_t getButtonState() const { return mButtonState; }
+
+ inline float getXOffset() const { return mXOffset; }
+
+ inline float getYOffset() const { return mYOffset; }
+
+ inline float getXPrecision() const { return mXPrecision; }
+
+ inline float getYPrecision() const { return mYPrecision; }
+
+ inline nsecs_t getDownTime() const { return mDownTime; }
+
+ inline void setDownTime(nsecs_t downTime) { mDownTime = downTime; }
+
+ inline size_t getPointerCount() const { return mPointerProperties.size(); }
+
+ inline const PointerProperties* getPointerProperties(size_t pointerIndex) const {
+ return &mPointerProperties[pointerIndex];
+ }
+
+ inline int32_t getPointerId(size_t pointerIndex) const {
+ return mPointerProperties[pointerIndex].id;
+ }
+
+ inline int32_t getToolType(size_t pointerIndex) const {
+ return mPointerProperties[pointerIndex].toolType;
+ }
+
+ inline nsecs_t getEventTime() const { return mSampleEventTimes[getHistorySize()]; }
+
+ const PointerCoords* getRawPointerCoords(size_t pointerIndex) const;
+
+ float getRawAxisValue(int32_t axis, size_t pointerIndex) const;
+
+ inline float getRawX(size_t pointerIndex) const {
+ return getRawAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex);
+ }
+
+ inline float getRawY(size_t pointerIndex) const {
+ return getRawAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex);
+ }
+
+ float getAxisValue(int32_t axis, size_t pointerIndex) const;
+
+ inline float getX(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_X, pointerIndex);
+ }
+
+ inline float getY(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_Y, pointerIndex);
+ }
+
+ inline float getPressure(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pointerIndex);
+ }
+
+ inline float getSize(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_SIZE, pointerIndex);
+ }
+
+ inline float getTouchMajor(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex);
+ }
+
+ inline float getTouchMinor(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex);
+ }
+
+ inline float getToolMajor(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex);
+ }
+
+ inline float getToolMinor(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex);
+ }
+
+ inline float getOrientation(size_t pointerIndex) const {
+ return getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex);
+ }
+
+ inline size_t getHistorySize() const { return mSampleEventTimes.size() - 1; }
+
+ inline nsecs_t getHistoricalEventTime(size_t historicalIndex) const {
+ return mSampleEventTimes[historicalIndex];
+ }
+
+ const PointerCoords* getHistoricalRawPointerCoords(
+ size_t pointerIndex, size_t historicalIndex) const;
+
+ float getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
+ size_t historicalIndex) const;
+
+ inline float getHistoricalRawX(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalRawAxisValue(
+ AMOTION_EVENT_AXIS_X, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalRawY(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalRawAxisValue(
+ AMOTION_EVENT_AXIS_Y, pointerIndex, historicalIndex);
+ }
+
+ float getHistoricalAxisValue(int32_t axis, size_t pointerIndex, size_t historicalIndex) const;
+
+ inline float getHistoricalX(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_X, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalY(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_Y, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalPressure(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_PRESSURE, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalSize(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_SIZE, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalTouchMajor(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_TOUCH_MAJOR, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalTouchMinor(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_TOUCH_MINOR, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalToolMajor(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_TOOL_MAJOR, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalToolMinor(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_TOOL_MINOR, pointerIndex, historicalIndex);
+ }
+
+ inline float getHistoricalOrientation(size_t pointerIndex, size_t historicalIndex) const {
+ return getHistoricalAxisValue(
+ AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex);
+ }
+
+ ssize_t findPointerIndex(int32_t pointerId) const;
+
+ void initialize(
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t edgeFlags,
+ int32_t metaState,
+ int32_t buttonState,
+ float xOffset,
+ float yOffset,
+ float xPrecision,
+ float yPrecision,
+ nsecs_t downTime,
+ nsecs_t eventTime,
+ size_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords);
+
+ void copyFrom(const MotionEvent* other, bool keepHistory);
+
+ void addSample(
+ nsecs_t eventTime,
+ const PointerCoords* pointerCoords);
+
+ void offsetLocation(float xOffset, float yOffset);
+
+ void scale(float scaleFactor);
+
+#ifdef HAVE_ANDROID_OS
+ void transform(const SkMatrix* matrix);
+
+ status_t readFromParcel(Parcel* parcel);
+ status_t writeToParcel(Parcel* parcel) const;
+#endif
+
+ static bool isTouchEvent(int32_t source, int32_t action);
+ inline bool isTouchEvent() const {
+ return isTouchEvent(mSource, mAction);
+ }
+
+ // Low-level accessors.
+ inline const PointerProperties* getPointerProperties() const {
+ return mPointerProperties.array();
+ }
+ inline const nsecs_t* getSampleEventTimes() const { return mSampleEventTimes.array(); }
+ inline const PointerCoords* getSamplePointerCoords() const {
+ return mSamplePointerCoords.array();
+ }
+
+protected:
+ int32_t mAction;
+ int32_t mFlags;
+ int32_t mEdgeFlags;
+ int32_t mMetaState;
+ int32_t mButtonState;
+ float mXOffset;
+ float mYOffset;
+ float mXPrecision;
+ float mYPrecision;
+ nsecs_t mDownTime;
+ Vector<PointerProperties> mPointerProperties;
+ Vector<nsecs_t> mSampleEventTimes;
+ Vector<PointerCoords> mSamplePointerCoords;
+};
+
+/*
+ * Input event factory.
+ */
+class InputEventFactoryInterface {
+protected:
+ virtual ~InputEventFactoryInterface() { }
+
+public:
+ InputEventFactoryInterface() { }
+
+ virtual KeyEvent* createKeyEvent() = 0;
+ virtual MotionEvent* createMotionEvent() = 0;
+};
+
+/*
+ * A simple input event factory implementation that uses a single preallocated instance
+ * of each type of input event that are reused for each request.
+ */
+class PreallocatedInputEventFactory : public InputEventFactoryInterface {
+public:
+ PreallocatedInputEventFactory() { }
+ virtual ~PreallocatedInputEventFactory() { }
+
+ virtual KeyEvent* createKeyEvent() { return & mKeyEvent; }
+ virtual MotionEvent* createMotionEvent() { return & mMotionEvent; }
+
+private:
+ KeyEvent mKeyEvent;
+ MotionEvent mMotionEvent;
+};
+
+/*
+ * An input event factory implementation that maintains a pool of input events.
+ */
+class PooledInputEventFactory : public InputEventFactoryInterface {
+public:
+ PooledInputEventFactory(size_t maxPoolSize = 20);
+ virtual ~PooledInputEventFactory();
+
+ virtual KeyEvent* createKeyEvent();
+ virtual MotionEvent* createMotionEvent();
+
+ void recycle(InputEvent* event);
+
+private:
+ const size_t mMaxPoolSize;
+
+ Vector<KeyEvent*> mKeyEventPool;
+ Vector<MotionEvent*> mMotionEventPool;
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_INPUT_H
diff --git a/widget/gonk/libui/InputApplication.cpp b/widget/gonk/libui/InputApplication.cpp
new file mode 100644
index 0000000000..ce432356b0
--- /dev/null
+++ b/widget/gonk/libui/InputApplication.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "InputApplication"
+
+#include "InputApplication.h"
+
+#include "cutils_log.h"
+
+namespace android {
+
+// --- InputApplicationHandle ---
+
+InputApplicationHandle::InputApplicationHandle() :
+ mInfo(NULL) {
+}
+
+InputApplicationHandle::~InputApplicationHandle() {
+ delete mInfo;
+}
+
+void InputApplicationHandle::releaseInfo() {
+ if (mInfo) {
+ delete mInfo;
+ mInfo = NULL;
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputApplication.h b/widget/gonk/libui/InputApplication.h
new file mode 100644
index 0000000000..ba789559c9
--- /dev/null
+++ b/widget/gonk/libui/InputApplication.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _UI_INPUT_APPLICATION_H
+#define _UI_INPUT_APPLICATION_H
+
+#include "Input.h"
+
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * Describes the properties of an application that can receive input.
+ */
+struct InputApplicationInfo {
+ String8 name;
+ nsecs_t dispatchingTimeout;
+};
+
+
+/*
+ * Handle for an application that can receive input.
+ *
+ * Used by the native input dispatcher as a handle for the window manager objects
+ * that describe an application.
+ */
+class InputApplicationHandle : public RefBase {
+public:
+ inline const InputApplicationInfo* getInfo() const {
+ return mInfo;
+ }
+
+ inline String8 getName() const {
+ return mInfo ? mInfo->name : String8("<invalid>");
+ }
+
+ inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const {
+ return mInfo ? mInfo->dispatchingTimeout : defaultValue;
+ }
+
+ /**
+ * Requests that the state of this object be updated to reflect
+ * the most current available information about the application.
+ *
+ * This method should only be called from within the input dispatcher's
+ * critical section.
+ *
+ * Returns true on success, or false if the handle is no longer valid.
+ */
+ virtual bool updateInfo() = 0;
+
+ /**
+ * Releases the storage used by the associated information when it is
+ * no longer needed.
+ */
+ void releaseInfo();
+
+protected:
+ InputApplicationHandle();
+ virtual ~InputApplicationHandle();
+
+ InputApplicationInfo* mInfo;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_APPLICATION_H
diff --git a/widget/gonk/libui/InputDevice.cpp b/widget/gonk/libui/InputDevice.cpp
new file mode 100644
index 0000000000..01a437dd4a
--- /dev/null
+++ b/widget/gonk/libui/InputDevice.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "InputDevice"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "InputDevice.h"
+
+namespace android {
+
+static const char* CONFIGURATION_FILE_DIR[] = {
+ "idc/",
+ "keylayout/",
+ "keychars/",
+};
+
+static const char* CONFIGURATION_FILE_EXTENSION[] = {
+ ".idc",
+ ".kl",
+ ".kcm",
+};
+
+static bool isValidNameChar(char ch) {
+ return isascii(ch) && (isdigit(ch) || isalpha(ch) || ch == '-' || ch == '_');
+}
+
+static void appendInputDeviceConfigurationFileRelativePath(String8& path,
+ const String8& name, InputDeviceConfigurationFileType type) {
+ path.append(CONFIGURATION_FILE_DIR[type]);
+ for (size_t i = 0; i < name.length(); i++) {
+ char ch = name[i];
+ if (!isValidNameChar(ch)) {
+ ch = '_';
+ }
+ path.append(&ch, 1);
+ }
+ path.append(CONFIGURATION_FILE_EXTENSION[type]);
+}
+
+String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
+ const InputDeviceIdentifier& deviceIdentifier,
+ InputDeviceConfigurationFileType type) {
+ if (deviceIdentifier.vendor !=0 && deviceIdentifier.product != 0) {
+ if (deviceIdentifier.version != 0) {
+ // Try vendor product version.
+ String8 versionPath(getInputDeviceConfigurationFilePathByName(
+ String8::format("Vendor_%04x_Product_%04x_Version_%04x",
+ deviceIdentifier.vendor, deviceIdentifier.product,
+ deviceIdentifier.version),
+ type));
+ if (!versionPath.isEmpty()) {
+ return versionPath;
+ }
+ }
+
+ // Try vendor product.
+ String8 productPath(getInputDeviceConfigurationFilePathByName(
+ String8::format("Vendor_%04x_Product_%04x",
+ deviceIdentifier.vendor, deviceIdentifier.product),
+ type));
+ if (!productPath.isEmpty()) {
+ return productPath;
+ }
+ }
+
+ // Try device name.
+ return getInputDeviceConfigurationFilePathByName(deviceIdentifier.name, type);
+}
+
+String8 getInputDeviceConfigurationFilePathByName(
+ const String8& name, InputDeviceConfigurationFileType type) {
+ // Search system repository.
+ String8 path;
+ path.setTo(getenv("ANDROID_ROOT"));
+ path.append("/usr/");
+ appendInputDeviceConfigurationFileRelativePath(path, name, type);
+#if DEBUG_PROBE
+ ALOGD("Probing for system provided input device configuration file: path='%s'", path.string());
+#endif
+ if (!access(path.string(), R_OK)) {
+#if DEBUG_PROBE
+ ALOGD("Found");
+#endif
+ return path;
+ }
+
+ // Search user repository.
+ // TODO Should only look here if not in safe mode.
+ path.setTo(getenv("ANDROID_DATA"));
+ path.append("/system/devices/");
+ appendInputDeviceConfigurationFileRelativePath(path, name, type);
+#if DEBUG_PROBE
+ ALOGD("Probing for system user input device configuration file: path='%s'", path.string());
+#endif
+ if (!access(path.string(), R_OK)) {
+#if DEBUG_PROBE
+ ALOGD("Found");
+#endif
+ return path;
+ }
+
+ // Not found.
+#if DEBUG_PROBE
+ ALOGD("Probe failed to find input device configuration file: name='%s', type=%d",
+ name.string(), type);
+#endif
+ return String8();
+}
+
+
+// --- InputDeviceInfo ---
+
+InputDeviceInfo::InputDeviceInfo() {
+ initialize(-1, -1, InputDeviceIdentifier(), String8(), false);
+}
+
+InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
+ mId(other.mId), mGeneration(other.mGeneration), mIdentifier(other.mIdentifier),
+ mAlias(other.mAlias), mIsExternal(other.mIsExternal), mSources(other.mSources),
+ mKeyboardType(other.mKeyboardType),
+ mKeyCharacterMap(other.mKeyCharacterMap),
+ mHasVibrator(other.mHasVibrator),
+ mMotionRanges(other.mMotionRanges) {
+}
+
+InputDeviceInfo::~InputDeviceInfo() {
+}
+
+void InputDeviceInfo::initialize(int32_t id, int32_t generation,
+ const InputDeviceIdentifier& identifier, const String8& alias, bool isExternal) {
+ mId = id;
+ mGeneration = generation;
+ mIdentifier = identifier;
+ mAlias = alias;
+ mIsExternal = isExternal;
+ mSources = 0;
+ mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+ mHasVibrator = false;
+ mMotionRanges.clear();
+}
+
+const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(
+ int32_t axis, uint32_t source) const {
+ size_t numRanges = mMotionRanges.size();
+ for (size_t i = 0; i < numRanges; i++) {
+ const MotionRange& range = mMotionRanges.itemAt(i);
+ if (range.axis == axis && range.source == source) {
+ return &range;
+ }
+ }
+ return NULL;
+}
+
+void InputDeviceInfo::addSource(uint32_t source) {
+ mSources |= source;
+}
+
+void InputDeviceInfo::addMotionRange(int32_t axis, uint32_t source, float min, float max,
+ float flat, float fuzz, float resolution) {
+ MotionRange range = { axis, source, min, max, flat, fuzz, resolution };
+ mMotionRanges.add(range);
+}
+
+void InputDeviceInfo::addMotionRange(const MotionRange& range) {
+ mMotionRanges.add(range);
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputDevice.h b/widget/gonk/libui/InputDevice.h
new file mode 100644
index 0000000000..0ab5863c93
--- /dev/null
+++ b/widget/gonk/libui/InputDevice.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_INPUT_DEVICE_H
+#define _ANDROIDFW_INPUT_DEVICE_H
+
+#include "Input.h"
+#include "KeyCharacterMap.h"
+
+namespace android {
+
+/*
+ * Identifies a device.
+ */
+struct InputDeviceIdentifier {
+ inline InputDeviceIdentifier() :
+ bus(0), vendor(0), product(0), version(0) {
+ }
+
+ // Information provided by the kernel.
+ String8 name;
+ String8 location;
+ String8 uniqueId;
+ uint16_t bus;
+ uint16_t vendor;
+ uint16_t product;
+ uint16_t version;
+
+ // A composite input device descriptor string that uniquely identifies the device
+ // even across reboots or reconnections. The value of this field is used by
+ // upper layers of the input system to associate settings with individual devices.
+ // It is hashed from whatever kernel provided information is available.
+ // Ideally, the way this value is computed should not change between Android releases
+ // because that would invalidate persistent settings that rely on it.
+ String8 descriptor;
+};
+
+/*
+ * Describes the characteristics and capabilities of an input device.
+ */
+class InputDeviceInfo {
+public:
+ InputDeviceInfo();
+ InputDeviceInfo(const InputDeviceInfo& other);
+ ~InputDeviceInfo();
+
+ struct MotionRange {
+ int32_t axis;
+ uint32_t source;
+ float min;
+ float max;
+ float flat;
+ float fuzz;
+ float resolution;
+ };
+
+ void initialize(int32_t id, int32_t generation, const InputDeviceIdentifier& identifier,
+ const String8& alias, bool isExternal);
+
+ inline int32_t getId() const { return mId; }
+ inline int32_t getGeneration() const { return mGeneration; }
+ inline const InputDeviceIdentifier& getIdentifier() const { return mIdentifier; }
+ inline const String8& getAlias() const { return mAlias; }
+ inline const String8& getDisplayName() const {
+ return mAlias.isEmpty() ? mIdentifier.name : mAlias;
+ }
+ inline bool isExternal() const { return mIsExternal; }
+ inline uint32_t getSources() const { return mSources; }
+
+ const MotionRange* getMotionRange(int32_t axis, uint32_t source) const;
+
+ void addSource(uint32_t source);
+ void addMotionRange(int32_t axis, uint32_t source,
+ float min, float max, float flat, float fuzz, float resolution);
+ void addMotionRange(const MotionRange& range);
+
+ inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
+ inline int32_t getKeyboardType() const { return mKeyboardType; }
+
+ inline void setKeyCharacterMap(const sp<KeyCharacterMap>& value) {
+ mKeyCharacterMap = value;
+ }
+
+ inline sp<KeyCharacterMap> getKeyCharacterMap() const {
+ return mKeyCharacterMap;
+ }
+
+ inline void setVibrator(bool hasVibrator) { mHasVibrator = hasVibrator; }
+ inline bool hasVibrator() const { return mHasVibrator; }
+
+ inline const Vector<MotionRange>& getMotionRanges() const {
+ return mMotionRanges;
+ }
+
+private:
+ int32_t mId;
+ int32_t mGeneration;
+ InputDeviceIdentifier mIdentifier;
+ String8 mAlias;
+ bool mIsExternal;
+ uint32_t mSources;
+ int32_t mKeyboardType;
+ sp<KeyCharacterMap> mKeyCharacterMap;
+ bool mHasVibrator;
+
+ Vector<MotionRange> mMotionRanges;
+};
+
+/* Types of input device configuration files. */
+enum InputDeviceConfigurationFileType {
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION = 0, /* .idc file */
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT = 1, /* .kl file */
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP = 2, /* .kcm file */
+};
+
+/*
+ * Gets the path of an input device configuration file, if one is available.
+ * Considers both system provided and user installed configuration files.
+ *
+ * The device identifier is used to construct several default configuration file
+ * names to try based on the device name, vendor, product, and version.
+ *
+ * Returns an empty string if not found.
+ */
+extern String8 getInputDeviceConfigurationFilePathByDeviceIdentifier(
+ const InputDeviceIdentifier& deviceIdentifier,
+ InputDeviceConfigurationFileType type);
+
+/*
+ * Gets the path of an input device configuration file, if one is available.
+ * Considers both system provided and user installed configuration files.
+ *
+ * The name is case-sensitive and is used to construct the filename to resolve.
+ * All characters except 'a'-'z', 'A'-'Z', '0'-'9', '-', and '_' are replaced by underscores.
+ *
+ * Returns an empty string if not found.
+ */
+extern String8 getInputDeviceConfigurationFilePathByName(
+ const String8& name, InputDeviceConfigurationFileType type);
+
+} // namespace android
+
+#endif // _ANDROIDFW_INPUT_DEVICE_H
diff --git a/widget/gonk/libui/InputDispatcher.cpp b/widget/gonk/libui/InputDispatcher.cpp
new file mode 100644
index 0000000000..7adaa19989
--- /dev/null
+++ b/widget/gonk/libui/InputDispatcher.cpp
@@ -0,0 +1,4430 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "InputDispatcher"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+//#define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+// Log detailed debug messages about each inbound event notification to the dispatcher.
+#define DEBUG_INBOUND_EVENT_DETAILS 0
+
+// Log detailed debug messages about each outbound event processed by the dispatcher.
+#define DEBUG_OUTBOUND_EVENT_DETAILS 0
+
+// Log debug messages about the dispatch cycle.
+#define DEBUG_DISPATCH_CYCLE 0
+
+// Log debug messages about registrations.
+#define DEBUG_REGISTRATION 0
+
+// Log debug messages about input event injection.
+#define DEBUG_INJECTION 0
+
+// Log debug messages about input focus tracking.
+#define DEBUG_FOCUS 0
+
+// Log debug messages about the app switch latency optimization.
+#define DEBUG_APP_SWITCH 0
+
+// Log debug messages about hover events.
+#define DEBUG_HOVER 0
+
+#include "InputDispatcher.h"
+
+#include "Trace.h"
+#include "PowerManager.h"
+
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <time.h>
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+#define INDENT4 " "
+
+namespace android {
+
+// Default input dispatching timeout if there is no focused application or paused window
+// from which to determine an appropriate dispatching timeout.
+const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
+
+// Amount of time to allow for all pending events to be processed when an app switch
+// key is on the way. This is used to preempt input dispatch and drop input events
+// when an application takes too long to respond and the user has pressed an app switch key.
+const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
+// Amount of time to allow for an event to be dispatched (measured since its eventTime)
+// before considering it stale and dropping it.
+const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec
+
+// Amount of time to allow touch events to be streamed out to a connection before requiring
+// that the first event be finished. This value extends the ANR timeout by the specified
+// amount. For example, if streaming is allowed to get ahead by one second relative to the
+// queue of waiting unfinished events, then ANRs will similarly be delayed by one second.
+const nsecs_t STREAM_AHEAD_EVENT_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
+// Log a warning when an event takes longer than this to process, even if an ANR does not occur.
+const nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec
+
+
+static inline nsecs_t now() {
+ return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static inline int32_t getMotionEventActionPointerIndex(int32_t action) {
+ return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+}
+
+static bool isValidKeyAction(int32_t action) {
+ switch (action) {
+ case AKEY_EVENT_ACTION_DOWN:
+ case AKEY_EVENT_ACTION_UP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool validateKeyEvent(int32_t action) {
+ if (! isValidKeyAction(action)) {
+ ALOGE("Key event has invalid action code 0x%x", action);
+ return false;
+ }
+ return true;
+}
+
+static bool isValidMotionAction(int32_t action, size_t pointerCount) {
+ switch (action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_MOVE:
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ case AMOTION_EVENT_ACTION_HOVER_EXIT:
+ case AMOTION_EVENT_ACTION_SCROLL:
+ return true;
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP: {
+ int32_t index = getMotionEventActionPointerIndex(action);
+ return index >= 0 && size_t(index) < pointerCount;
+ }
+ default:
+ return false;
+ }
+}
+
+static bool validateMotionEvent(int32_t action, size_t pointerCount,
+ const PointerProperties* pointerProperties) {
+ if (! isValidMotionAction(action, pointerCount)) {
+ ALOGE("Motion event has invalid action code 0x%x", action);
+ return false;
+ }
+ if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
+ ALOGE("Motion event has invalid pointer count %d; value must be between 1 and %d.",
+ pointerCount, MAX_POINTERS);
+ return false;
+ }
+ BitSet32 pointerIdBits;
+ for (size_t i = 0; i < pointerCount; i++) {
+ int32_t id = pointerProperties[i].id;
+ if (id < 0 || id > MAX_POINTER_ID) {
+ ALOGE("Motion event has invalid pointer id %d; value must be between 0 and %d",
+ id, MAX_POINTER_ID);
+ return false;
+ }
+ if (pointerIdBits.hasBit(id)) {
+ ALOGE("Motion event has duplicate pointer id %d", id);
+ return false;
+ }
+ pointerIdBits.markBit(id);
+ }
+ return true;
+}
+
+static bool isMainDisplay(int32_t displayId) {
+ return displayId == ADISPLAY_ID_DEFAULT || displayId == ADISPLAY_ID_NONE;
+}
+
+static void dumpRegion(String8& dump, const SkRegion& region) {
+ if (region.isEmpty()) {
+ dump.append("<empty>");
+ return;
+ }
+
+ bool first = true;
+ for (SkRegion::Iterator it(region); !it.done(); it.next()) {
+ if (first) {
+ first = false;
+ } else {
+ dump.append("|");
+ }
+ const SkIRect& rect = it.rect();
+ dump.appendFormat("[%d,%d][%d,%d]", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom);
+ }
+}
+
+
+// --- InputDispatcher ---
+
+InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
+ mPolicy(policy),
+ mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
+ mNextUnblockedEvent(NULL),
+ mDispatchEnabled(false), mDispatchFrozen(false), mInputFilterEnabled(false),
+ mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
+ mLooper = new Looper(false);
+
+ mKeyRepeatState.lastKeyEntry = NULL;
+
+ policy->getDispatcherConfiguration(&mConfig);
+}
+
+InputDispatcher::~InputDispatcher() {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ resetKeyRepeatLocked();
+ releasePendingEventLocked();
+ drainInboundQueueLocked();
+ }
+
+ while (mConnectionsByFd.size() != 0) {
+ unregisterInputChannel(mConnectionsByFd.valueAt(0)->inputChannel);
+ }
+}
+
+void InputDispatcher::dispatchOnce() {
+ nsecs_t nextWakeupTime = LONG_LONG_MAX;
+ { // acquire lock
+ AutoMutex _l(mLock);
+ mDispatcherIsAliveCondition.broadcast();
+
+ // Run a dispatch loop if there are no pending commands.
+ // The dispatch loop might enqueue commands to run afterwards.
+ if (!haveCommandsLocked()) {
+ dispatchOnceInnerLocked(&nextWakeupTime);
+ }
+
+ // Run all pending commands if there are any.
+ // If any commands were run then force the next poll to wake up immediately.
+ if (runCommandsLockedInterruptible()) {
+ nextWakeupTime = LONG_LONG_MIN;
+ }
+ } // release lock
+
+ // Wait for callback or timeout or wake. (make sure we round up, not down)
+ nsecs_t currentTime = now();
+ int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
+ mLooper->pollOnce(timeoutMillis);
+}
+
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
+ nsecs_t currentTime = now();
+
+ // Reset the key repeat timer whenever we disallow key events, even if the next event
+ // is not a key. This is to ensure that we abort a key repeat if the device is just coming
+ // out of sleep.
+ if (!mPolicy->isKeyRepeatEnabled()) {
+ resetKeyRepeatLocked();
+ }
+
+ // If dispatching is frozen, do not process timeouts or try to deliver any new events.
+ if (mDispatchFrozen) {
+#if DEBUG_FOCUS
+ ALOGD("Dispatch frozen. Waiting some more.");
+#endif
+ return;
+ }
+
+ // Optimize latency of app switches.
+ // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
+ // been pressed. When it expires, we preempt dispatch and drop all other pending events.
+ bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
+ if (mAppSwitchDueTime < *nextWakeupTime) {
+ *nextWakeupTime = mAppSwitchDueTime;
+ }
+
+ // Ready to start a new event.
+ // If we don't already have a pending event, go grab one.
+ if (! mPendingEvent) {
+ if (mInboundQueue.isEmpty()) {
+ if (isAppSwitchDue) {
+ // The inbound queue is empty so the app switch key we were waiting
+ // for will never arrive. Stop waiting for it.
+ resetPendingAppSwitchLocked(false);
+ isAppSwitchDue = false;
+ }
+
+ // Synthesize a key repeat if appropriate.
+ if (mKeyRepeatState.lastKeyEntry) {
+ if (currentTime >= mKeyRepeatState.nextRepeatTime) {
+ mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
+ } else {
+ if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
+ *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
+ }
+ }
+ }
+
+ // Nothing to do if there is no pending event.
+ if (!mPendingEvent) {
+ return;
+ }
+ } else {
+ // Inbound queue has at least one entry.
+ mPendingEvent = mInboundQueue.dequeueAtHead();
+ traceInboundQueueLengthLocked();
+ }
+
+ // Poke user activity for this event.
+ if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+ pokeUserActivityLocked(mPendingEvent);
+ }
+
+ // Get ready to dispatch the event.
+ resetANRTimeoutsLocked();
+ }
+
+ // Now we have an event to dispatch.
+ // All events are eventually dequeued and processed this way, even if we intend to drop them.
+ ALOG_ASSERT(mPendingEvent != NULL);
+ bool done = false;
+ DropReason dropReason = DROP_REASON_NOT_DROPPED;
+ if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+ dropReason = DROP_REASON_POLICY;
+ } else if (!mDispatchEnabled) {
+ dropReason = DROP_REASON_DISABLED;
+ }
+
+ if (mNextUnblockedEvent == mPendingEvent) {
+ mNextUnblockedEvent = NULL;
+ }
+
+ switch (mPendingEvent->type) {
+ case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+ ConfigurationChangedEntry* typedEntry =
+ static_cast<ConfigurationChangedEntry*>(mPendingEvent);
+ done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+ dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped
+ break;
+ }
+
+ case EventEntry::TYPE_DEVICE_RESET: {
+ DeviceResetEntry* typedEntry =
+ static_cast<DeviceResetEntry*>(mPendingEvent);
+ done = dispatchDeviceResetLocked(currentTime, typedEntry);
+ dropReason = DROP_REASON_NOT_DROPPED; // device resets are never dropped
+ break;
+ }
+
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
+ if (isAppSwitchDue) {
+ if (isAppSwitchKeyEventLocked(typedEntry)) {
+ resetPendingAppSwitchLocked(true);
+ isAppSwitchDue = false;
+ } else if (dropReason == DROP_REASON_NOT_DROPPED) {
+ dropReason = DROP_REASON_APP_SWITCH;
+ }
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
+ done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
+ if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
+ dropReason = DROP_REASON_APP_SWITCH;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
+ done = dispatchMotionLocked(currentTime, typedEntry,
+ &dropReason, nextWakeupTime);
+ break;
+ }
+
+ default:
+ ALOG_ASSERT(false);
+ break;
+ }
+
+ if (done) {
+ if (dropReason != DROP_REASON_NOT_DROPPED) {
+ dropInboundEventLocked(mPendingEvent, dropReason);
+ }
+
+ releasePendingEventLocked();
+ *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
+ }
+}
+
+bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
+ bool needWake = mInboundQueue.isEmpty();
+ mInboundQueue.enqueueAtTail(entry);
+ traceInboundQueueLengthLocked();
+
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY: {
+ // Optimize app switch latency.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the app switch key.
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
+ if (isAppSwitchKeyEventLocked(keyEntry)) {
+ if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
+ mAppSwitchSawKeyDown = true;
+ } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
+ if (mAppSwitchSawKeyDown) {
+#if DEBUG_APP_SWITCH
+ ALOGD("App switch is pending!");
+#endif
+ mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
+ mAppSwitchSawKeyDown = false;
+ needWake = true;
+ }
+ }
+ }
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ // Optimize case where the current application is unresponsive and the user
+ // decides to touch a window in a different application.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the touch into the other window.
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN
+ && (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
+ && mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
+ && mInputTargetWaitApplicationHandle != NULL) {
+ int32_t displayId = motionEntry->displayId;
+ int32_t x = int32_t(motionEntry->pointerCoords[0].
+ getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = int32_t(motionEntry->pointerCoords[0].
+ getAxisValue(AMOTION_EVENT_AXIS_Y));
+ sp<InputWindowHandle> touchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y);
+ if (touchedWindowHandle != NULL
+ && touchedWindowHandle->inputApplicationHandle
+ != mInputTargetWaitApplicationHandle) {
+ // User touched a different application than the one we are waiting on.
+ // Flag the event, and start pruning the input queue.
+ mNextUnblockedEvent = motionEntry;
+ needWake = true;
+ }
+ }
+ break;
+ }
+ }
+
+ return needWake;
+}
+
+sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId,
+ int32_t x, int32_t y) {
+ // Traverse windows from front to back to find touched window.
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ if (windowInfo->displayId == displayId) {
+ int32_t flags = windowInfo->layoutParamsFlags;
+
+ if (windowInfo->visible) {
+ if (!(flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
+ bool isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
+ | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
+ // Found window.
+ return windowHandle;
+ }
+ }
+ }
+
+ if (flags & InputWindowInfo::FLAG_SYSTEM_ERROR) {
+ // Error window is on top but not visible, so touch is dropped.
+ return NULL;
+ }
+ }
+ }
+ return NULL;
+}
+
+void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) {
+ const char* reason;
+ switch (dropReason) {
+ case DROP_REASON_POLICY:
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("Dropped event because policy consumed it.");
+#endif
+ reason = "inbound event was dropped because the policy consumed it";
+ break;
+ case DROP_REASON_DISABLED:
+ ALOGI("Dropped event because input dispatch is disabled.");
+ reason = "inbound event was dropped because input dispatch is disabled";
+ break;
+ case DROP_REASON_APP_SWITCH:
+ ALOGI("Dropped event because of pending overdue app switch.");
+ reason = "inbound event was dropped because of pending overdue app switch";
+ break;
+ case DROP_REASON_BLOCKED:
+ ALOGI("Dropped event because the current application is not responding and the user "
+ "has started interacting with a different application.");
+ reason = "inbound event was dropped because the current application is not responding "
+ "and the user has started interacting with a different application";
+ break;
+ case DROP_REASON_STALE:
+ ALOGI("Dropped event because it is stale.");
+ reason = "inbound event was dropped because it is stale";
+ break;
+ default:
+ ALOG_ASSERT(false);
+ return;
+ }
+
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY: {
+ CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ break;
+ }
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ } else {
+ CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ }
+ break;
+ }
+ }
+}
+
+bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) {
+ return keyCode == AKEYCODE_HOME
+ || keyCode == AKEYCODE_ENDCALL
+ || keyCode == AKEYCODE_APP_SWITCH;
+}
+
+bool InputDispatcher::isAppSwitchKeyEventLocked(KeyEntry* keyEntry) {
+ return ! (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
+ && isAppSwitchKeyCode(keyEntry->keyCode)
+ && (keyEntry->policyFlags & POLICY_FLAG_TRUSTED)
+ && (keyEntry->policyFlags & POLICY_FLAG_PASS_TO_USER);
+}
+
+bool InputDispatcher::isAppSwitchPendingLocked() {
+ return mAppSwitchDueTime != LONG_LONG_MAX;
+}
+
+void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
+ mAppSwitchDueTime = LONG_LONG_MAX;
+
+#if DEBUG_APP_SWITCH
+ if (handled) {
+ ALOGD("App switch has arrived.");
+ } else {
+ ALOGD("App switch was abandoned.");
+ }
+#endif
+}
+
+bool InputDispatcher::isStaleEventLocked(nsecs_t currentTime, EventEntry* entry) {
+ return currentTime - entry->eventTime >= STALE_EVENT_TIMEOUT;
+}
+
+bool InputDispatcher::haveCommandsLocked() const {
+ return !mCommandQueue.isEmpty();
+}
+
+bool InputDispatcher::runCommandsLockedInterruptible() {
+ if (mCommandQueue.isEmpty()) {
+ return false;
+ }
+
+ do {
+ CommandEntry* commandEntry = mCommandQueue.dequeueAtHead();
+
+ Command command = commandEntry->command;
+ (this->*command)(commandEntry); // commands are implicitly 'LockedInterruptible'
+
+ commandEntry->connection.clear();
+ delete commandEntry;
+ } while (! mCommandQueue.isEmpty());
+ return true;
+}
+
+InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
+ CommandEntry* commandEntry = new CommandEntry(command);
+ mCommandQueue.enqueueAtTail(commandEntry);
+ return commandEntry;
+}
+
+void InputDispatcher::drainInboundQueueLocked() {
+ while (! mInboundQueue.isEmpty()) {
+ EventEntry* entry = mInboundQueue.dequeueAtHead();
+ releaseInboundEventLocked(entry);
+ }
+ traceInboundQueueLengthLocked();
+}
+
+void InputDispatcher::releasePendingEventLocked() {
+ if (mPendingEvent) {
+ resetANRTimeoutsLocked();
+ releaseInboundEventLocked(mPendingEvent);
+ mPendingEvent = NULL;
+ }
+}
+
+void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState && injectionState->injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("Injected inbound event was dropped.");
+#endif
+ setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+ }
+ if (entry == mNextUnblockedEvent) {
+ mNextUnblockedEvent = NULL;
+ }
+ entry->release();
+}
+
+void InputDispatcher::resetKeyRepeatLocked() {
+ if (mKeyRepeatState.lastKeyEntry) {
+ mKeyRepeatState.lastKeyEntry->release();
+ mKeyRepeatState.lastKeyEntry = NULL;
+ }
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked(nsecs_t currentTime) {
+ KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
+
+ // Reuse the repeated key entry if it is otherwise unreferenced.
+ uint32_t policyFlags = (entry->policyFlags & POLICY_FLAG_RAW_MASK)
+ | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_TRUSTED;
+ if (entry->refCount == 1) {
+ entry->recycle();
+ entry->eventTime = currentTime;
+ entry->policyFlags = policyFlags;
+ entry->repeatCount += 1;
+ } else {
+ KeyEntry* newEntry = new KeyEntry(currentTime,
+ entry->deviceId, entry->source, policyFlags,
+ entry->action, entry->flags, entry->keyCode, entry->scanCode,
+ entry->metaState, entry->repeatCount + 1, entry->downTime);
+
+ mKeyRepeatState.lastKeyEntry = newEntry;
+ entry->release();
+
+ entry = newEntry;
+ }
+ entry->syntheticRepeat = true;
+
+ // Increment reference count since we keep a reference to the event in
+ // mKeyRepeatState.lastKeyEntry in addition to the one we return.
+ entry->refCount += 1;
+
+ mKeyRepeatState.nextRepeatTime = currentTime + mConfig.keyRepeatDelay;
+ return entry;
+}
+
+bool InputDispatcher::dispatchConfigurationChangedLocked(
+ nsecs_t currentTime, ConfigurationChangedEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime);
+#endif
+
+ // Reset key repeating in case a keyboard device was added or removed or something.
+ resetKeyRepeatLocked();
+
+ // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyConfigurationChangedInterruptible);
+ commandEntry->eventTime = entry->eventTime;
+ return true;
+}
+
+bool InputDispatcher::dispatchDeviceResetLocked(
+ nsecs_t currentTime, DeviceResetEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("dispatchDeviceReset - eventTime=%lld, deviceId=%d", entry->eventTime, entry->deviceId);
+#endif
+
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
+ "device was reset");
+ options.deviceId = entry->deviceId;
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ return true;
+}
+
+bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ if (entry->repeatCount == 0
+ && entry->action == AKEY_EVENT_ACTION_DOWN
+ && (entry->policyFlags & POLICY_FLAG_TRUSTED)
+ && (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) {
+ if (mKeyRepeatState.lastKeyEntry
+ && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
+ // We have seen two identical key downs in a row which indicates that the device
+ // driver is automatically generating key repeats itself. We take note of the
+ // repeat here, but we disable our own next key repeat timer since it is clear that
+ // we will not need to synthesize key repeats ourselves.
+ entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
+ resetKeyRepeatLocked();
+ mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+ } else {
+ // Not a repeat. Save key down state in case we do see a repeat later.
+ resetKeyRepeatLocked();
+ mKeyRepeatState.nextRepeatTime = entry->eventTime + mConfig.keyRepeatTimeout;
+ }
+ mKeyRepeatState.lastKeyEntry = entry;
+ entry->refCount += 1;
+ } else if (! entry->syntheticRepeat) {
+ resetKeyRepeatLocked();
+ }
+
+ if (entry->repeatCount == 1) {
+ entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
+ } else {
+ entry->flags &= ~AKEY_EVENT_FLAG_LONG_PRESS;
+ }
+
+ entry->dispatchInProgress = true;
+
+ logOutboundKeyDetailsLocked("dispatchKey - ", entry);
+ }
+
+ // Handle case where the policy asked us to try again later last time.
+ if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
+ if (currentTime < entry->interceptKeyWakeupTime) {
+ if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
+ *nextWakeupTime = entry->interceptKeyWakeupTime;
+ }
+ return false; // wait until next wakeup
+ }
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+ entry->interceptKeyWakeupTime = 0;
+ }
+
+ // Give the policy a chance to intercept the key.
+ if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+ if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
+ if (mFocusedWindowHandle != NULL) {
+ commandEntry->inputWindowHandle = mFocusedWindowHandle;
+ }
+ commandEntry->keyEntry = entry;
+ entry->refCount += 1;
+ return false; // wait for the command to run
+ } else {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ }
+ } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+ if (*dropReason == DROP_REASON_NOT_DROPPED) {
+ *dropReason = DROP_REASON_POLICY;
+ }
+ }
+
+ // Clean up if dropping the event.
+ if (*dropReason != DROP_REASON_NOT_DROPPED) {
+ setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+ ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+ return true;
+ }
+
+ // Identify targets.
+ Vector<InputTarget> inputTargets;
+ int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
+ entry, inputTargets, nextWakeupTime);
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
+
+ addMonitoringTargetsLocked(inputTargets);
+
+ // Dispatch the key.
+ dispatchEventLocked(currentTime, entry, inputTargets);
+ return true;
+}
+
+void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
+ "repeatCount=%d, downTime=%lld",
+ prefix,
+ entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+ entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
+ entry->repeatCount, entry->downTime);
+#endif
+}
+
+bool InputDispatcher::dispatchMotionLocked(
+ nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ entry->dispatchInProgress = true;
+
+ logOutboundMotionDetailsLocked("dispatchMotion - ", entry);
+ }
+
+ // Clean up if dropping the event.
+ if (*dropReason != DROP_REASON_NOT_DROPPED) {
+ setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+ ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+ return true;
+ }
+
+ bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
+
+ // Identify targets.
+ Vector<InputTarget> inputTargets;
+
+ bool conflictingPointerActions = false;
+ int32_t injectionResult;
+ if (isPointerEvent) {
+ // Pointer event. (eg. touchscreen)
+ injectionResult = findTouchedWindowTargetsLocked(currentTime,
+ entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
+ } else {
+ // Non touch event. (eg. trackball)
+ injectionResult = findFocusedWindowTargetsLocked(currentTime,
+ entry, inputTargets, nextWakeupTime);
+ }
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
+
+ // TODO: support sending secondary display events to input monitors
+ if (isMainDisplay(entry->displayId)) {
+ addMonitoringTargetsLocked(inputTargets);
+ }
+
+ // Dispatch the motion.
+ if (conflictingPointerActions) {
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ "conflicting pointer actions");
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ }
+ dispatchEventLocked(currentTime, entry, inputTargets);
+ return true;
+}
+
+
+void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, "
+ "metaState=0x%x, buttonState=0x%x, "
+ "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
+ prefix,
+ entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+ entry->action, entry->flags,
+ entry->metaState, entry->buttonState,
+ entry->edgeFlags, entry->xPrecision, entry->yPrecision,
+ entry->downTime);
+
+ for (uint32_t i = 0; i < entry->pointerCount; i++) {
+ ALOGD(" Pointer %d: id=%d, toolType=%d, "
+ "x=%f, y=%f, pressure=%f, size=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+ "orientation=%f",
+ i, entry->pointerProperties[i].id,
+ entry->pointerProperties[i].toolType,
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+ }
+#endif
+}
+
+void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
+ EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("dispatchEventToCurrentInputTargets");
+#endif
+
+ ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
+
+ pokeUserActivityLocked(eventEntry);
+
+ for (size_t i = 0; i < inputTargets.size(); i++) {
+ const InputTarget& inputTarget = inputTargets.itemAt(i);
+
+ ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
+ } else {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event delivery to target with channel '%s' because it "
+ "is no longer registered with the input dispatcher.",
+ inputTarget.inputChannel->getName().string());
+#endif
+ }
+ }
+}
+
+int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
+ const EventEntry* entry,
+ const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t* nextWakeupTime, const char* reason) {
+ if (applicationHandle == NULL && windowHandle == NULL) {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
+#if DEBUG_FOCUS
+ ALOGD("Waiting for system to become ready for input. Reason: %s", reason);
+#endif
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
+ mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplicationHandle.clear();
+ }
+ } else {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+#if DEBUG_FOCUS
+ ALOGD("Waiting for application to become ready for input: %s. Reason: %s",
+ getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
+ reason);
+#endif
+ nsecs_t timeout;
+ if (windowHandle != NULL) {
+ timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
+ } else if (applicationHandle != NULL) {
+ timeout = applicationHandle->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT);
+ } else {
+ timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+ }
+
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = currentTime + timeout;
+ mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplicationHandle.clear();
+
+ if (windowHandle != NULL) {
+ mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
+ }
+ if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
+ mInputTargetWaitApplicationHandle = applicationHandle;
+ }
+ }
+ }
+
+ if (mInputTargetWaitTimeoutExpired) {
+ return INPUT_EVENT_INJECTION_TIMED_OUT;
+ }
+
+ if (currentTime >= mInputTargetWaitTimeoutTime) {
+ onANRLocked(currentTime, applicationHandle, windowHandle,
+ entry->eventTime, mInputTargetWaitStartTime, reason);
+
+ // Force poll loop to wake up immediately on next iteration once we get the
+ // ANR response back from the policy.
+ *nextWakeupTime = LONG_LONG_MIN;
+ return INPUT_EVENT_INJECTION_PENDING;
+ } else {
+ // Force poll loop to wake up when timeout is due.
+ if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
+ *nextWakeupTime = mInputTargetWaitTimeoutTime;
+ }
+ return INPUT_EVENT_INJECTION_PENDING;
+ }
+}
+
+void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+ const sp<InputChannel>& inputChannel) {
+ if (newTimeout > 0) {
+ // Extend the timeout.
+ mInputTargetWaitTimeoutTime = now() + newTimeout;
+ } else {
+ // Give up.
+ mInputTargetWaitTimeoutExpired = true;
+
+ // Input state will not be realistic. Mark it out of sync.
+ if (inputChannel.get()) {
+ ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ sp<InputWindowHandle> windowHandle = connection->inputWindowHandle;
+
+ if (windowHandle != NULL) {
+ mTouchState.removeWindow(windowHandle);
+ }
+
+ if (connection->status == Connection::STATUS_NORMAL) {
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
+ "application not responding");
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
+ }
+ }
+ }
+ }
+}
+
+nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationLocked(
+ nsecs_t currentTime) {
+ if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+ return currentTime - mInputTargetWaitStartTime;
+ }
+ return 0;
+}
+
+void InputDispatcher::resetANRTimeoutsLocked() {
+#if DEBUG_FOCUS
+ ALOGD("Resetting ANR timeouts.");
+#endif
+
+ // Reset input target wait timeout.
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+ mInputTargetWaitApplicationHandle.clear();
+}
+
+int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
+ const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
+ int32_t injectionResult;
+
+ // If there is no currently focused window and no focused application
+ // then drop the event.
+ if (mFocusedWindowHandle == NULL) {
+ if (mFocusedApplicationHandle != NULL) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, NULL, nextWakeupTime,
+ "Waiting because no window has focus but there is a "
+ "focused application that may eventually add a window "
+ "when it finishes starting up.");
+ goto Unresponsive;
+ }
+
+ ALOGI("Dropping event because there is no focused window or focused application.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Check permissions.
+ if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+
+ // If the currently focused window is paused then keep waiting.
+ if (mFocusedWindowHandle->getInfo()->paused) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime,
+ "Waiting because the focused window is paused.");
+ goto Unresponsive;
+ }
+
+ // If the currently focused window is still working on previous events then keep waiting.
+ if (!isWindowReadyForMoreInputLocked(currentTime, mFocusedWindowHandle, entry)) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime,
+ "Waiting because the focused window has not finished "
+ "processing the input events that were previously delivered to it.");
+ goto Unresponsive;
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ addWindowTargetLocked(mFocusedWindowHandle,
+ InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
+ inputTargets);
+
+ // Done.
+Failed:
+Unresponsive:
+ nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+ updateDispatchStatisticsLocked(currentTime, entry,
+ injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+ ALOGD("findFocusedWindow finished: injectionResult=%d, "
+ "timeSpentWaitingForApplication=%0.1fms",
+ injectionResult, timeSpentWaitingForApplication / 1000000.0);
+#endif
+ return injectionResult;
+}
+
+int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
+ const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
+ bool* outConflictingPointerActions) {
+ enum InjectionPermission {
+ INJECTION_PERMISSION_UNKNOWN,
+ INJECTION_PERMISSION_GRANTED,
+ INJECTION_PERMISSION_DENIED
+ };
+
+ // For security reasons, we defer updating the touch state until we are sure that
+ // event injection will be allowed.
+ //
+ // FIXME In the original code, screenWasOff could never be set to true.
+ // The reason is that the POLICY_FLAG_WOKE_HERE
+ // and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw
+ // EV_KEY, EV_REL and EV_ABS events. As it happens, the touch event was
+ // actually enqueued using the policyFlags that appeared in the final EV_SYN
+ // events upon which no preprocessing took place. So policyFlags was always 0.
+ // In the new native input dispatcher we're a bit more careful about event
+ // preprocessing so the touches we receive can actually have non-zero policyFlags.
+ // Unfortunately we obtain undesirable behavior.
+ //
+ // Here's what happens:
+ //
+ // When the device dims in anticipation of going to sleep, touches
+ // in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause
+ // the device to brighten and reset the user activity timer.
+ // Touches on other windows (such as the launcher window)
+ // are dropped. Then after a moment, the device goes to sleep. Oops.
+ //
+ // Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE
+ // instead of POLICY_FLAG_WOKE_HERE...
+ //
+ bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE;
+
+ int32_t displayId = entry->displayId;
+ int32_t action = entry->action;
+ int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+
+ // Update the touch state as needed based on the properties of the touch event.
+ int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING;
+ InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ sp<InputWindowHandle> newHoverWindowHandle;
+
+ bool isSplit = mTouchState.split;
+ bool switchedDevice = mTouchState.deviceId >= 0 && mTouchState.displayId >= 0
+ && (mTouchState.deviceId != entry->deviceId
+ || mTouchState.source != entry->source
+ || mTouchState.displayId != displayId);
+ bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
+ bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ || maskedAction == AMOTION_EVENT_ACTION_SCROLL
+ || isHoverAction);
+ bool wrongDevice = false;
+ if (newGesture) {
+ bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
+ if (switchedDevice && mTouchState.down && !down) {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event because a pointer for a different device is already down.");
+#endif
+ mTempTouchState.copyFrom(mTouchState);
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ switchedDevice = false;
+ wrongDevice = true;
+ goto Failed;
+ }
+ mTempTouchState.reset();
+ mTempTouchState.down = down;
+ mTempTouchState.deviceId = entry->deviceId;
+ mTempTouchState.source = entry->source;
+ mTempTouchState.displayId = displayId;
+ isSplit = false;
+ } else {
+ mTempTouchState.copyFrom(mTouchState);
+ }
+
+ if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
+ /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
+
+ int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+ int32_t x = int32_t(entry->pointerCoords[pointerIndex].
+ getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = int32_t(entry->pointerCoords[pointerIndex].
+ getAxisValue(AMOTION_EVENT_AXIS_Y));
+ sp<InputWindowHandle> newTouchedWindowHandle;
+ sp<InputWindowHandle> topErrorWindowHandle;
+ bool isTouchModal = false;
+
+ // Traverse windows from front to back to find touched window and outside targets.
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ if (windowInfo->displayId != displayId) {
+ continue; // wrong display
+ }
+
+ int32_t flags = windowInfo->layoutParamsFlags;
+ if (flags & InputWindowInfo::FLAG_SYSTEM_ERROR) {
+ if (topErrorWindowHandle == NULL) {
+ topErrorWindowHandle = windowHandle;
+ }
+ }
+
+ if (windowInfo->visible) {
+ if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
+ isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
+ | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
+ if (! screenWasOff
+ || (flags & InputWindowInfo::FLAG_TOUCHABLE_WHEN_WAKING)) {
+ newTouchedWindowHandle = windowHandle;
+ }
+ break; // found touched window, exit window loop
+ }
+ }
+
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
+ int32_t outsideTargetFlags = InputTarget::FLAG_DISPATCH_AS_OUTSIDE;
+ if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
+ outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ mTempTouchState.addOrUpdateWindow(
+ windowHandle, outsideTargetFlags, BitSet32(0));
+ }
+ }
+ }
+
+ // If there is an error window but it is not taking focus (typically because
+ // it is invisible) then wait for it. Any other focused window may in
+ // fact be in ANR state.
+ if (topErrorWindowHandle != NULL && newTouchedWindowHandle != topErrorWindowHandle) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, NULL, nextWakeupTime,
+ "Waiting because a system error window is about to be displayed.");
+ injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ goto Unresponsive;
+ }
+
+ // Figure out whether splitting will be allowed for this window.
+ if (newTouchedWindowHandle != NULL
+ && newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
+ // New window supports splitting.
+ isSplit = true;
+ } else if (isSplit) {
+ // New window does not support splitting but we have already split events.
+ // Ignore the new window.
+ newTouchedWindowHandle = NULL;
+ }
+
+ // Handle the case where we did not find a window.
+ if (newTouchedWindowHandle == NULL) {
+ // Try to assign the pointer to the first foreground window we find, if there is one.
+ newTouchedWindowHandle = mTempTouchState.getFirstForegroundWindowHandle();
+ if (newTouchedWindowHandle == NULL) {
+ // There is no touched window. If this is an initial down event
+ // then wait for a window to appear that will handle the touch. This is
+ // to ensure that we report an ANR in the case where an application has started
+ // but not yet put up a window and the user is starting to get impatient.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ && mFocusedApplicationHandle != NULL) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, NULL, nextWakeupTime,
+ "Waiting because there is no touchable window that can "
+ "handle the event but there is focused application that may "
+ "eventually add a new window when it finishes starting up.");
+ goto Unresponsive;
+ }
+
+ ALOGI("Dropping event because there is no touched window.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+ }
+
+ // Set target flags.
+ int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS;
+ if (isSplit) {
+ targetFlags |= InputTarget::FLAG_SPLIT;
+ }
+ if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ // Update hover state.
+ if (isHoverAction) {
+ newHoverWindowHandle = newTouchedWindowHandle;
+ } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
+ newHoverWindowHandle = mLastHoverWindowHandle;
+ }
+
+ // Update the temporary touch state.
+ BitSet32 pointerIds;
+ if (isSplit) {
+ uint32_t pointerId = entry->pointerProperties[pointerIndex].id;
+ pointerIds.markBit(pointerId);
+ }
+ mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+ } else {
+ /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
+
+ // If the pointer is not currently down, then ignore the event.
+ if (! mTempTouchState.down) {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event because the pointer is not down or we previously "
+ "dropped the pointer down event.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Check whether touches should slip outside of the current foreground window.
+ if (maskedAction == AMOTION_EVENT_ACTION_MOVE
+ && entry->pointerCount == 1
+ && mTempTouchState.isSlippery()) {
+ int32_t x = int32_t(entry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = int32_t(entry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
+
+ sp<InputWindowHandle> oldTouchedWindowHandle =
+ mTempTouchState.getFirstForegroundWindowHandle();
+ sp<InputWindowHandle> newTouchedWindowHandle =
+ findTouchedWindowAtLocked(displayId, x, y);
+ if (oldTouchedWindowHandle != newTouchedWindowHandle
+ && newTouchedWindowHandle != NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Touch is slipping out of window %s into window %s.",
+ oldTouchedWindowHandle->getName().string(),
+ newTouchedWindowHandle->getName().string());
+#endif
+ // Make a slippery exit from the old window.
+ mTempTouchState.addOrUpdateWindow(oldTouchedWindowHandle,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT, BitSet32(0));
+
+ // Make a slippery entrance into the new window.
+ if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
+ isSplit = true;
+ }
+
+ int32_t targetFlags = InputTarget::FLAG_FOREGROUND
+ | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER;
+ if (isSplit) {
+ targetFlags |= InputTarget::FLAG_SPLIT;
+ }
+ if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ BitSet32 pointerIds;
+ if (isSplit) {
+ pointerIds.markBit(entry->pointerProperties[0].id);
+ }
+ mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+ }
+ }
+ }
+
+ if (newHoverWindowHandle != mLastHoverWindowHandle) {
+ // Let the previous window know that the hover sequence is over.
+ if (mLastHoverWindowHandle != NULL) {
+#if DEBUG_HOVER
+ ALOGD("Sending hover exit event to window %s.",
+ mLastHoverWindowHandle->getName().string());
+#endif
+ mTempTouchState.addOrUpdateWindow(mLastHoverWindowHandle,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0));
+ }
+
+ // Let the new window know that the hover sequence is starting.
+ if (newHoverWindowHandle != NULL) {
+#if DEBUG_HOVER
+ ALOGD("Sending hover enter event to window %s.",
+ newHoverWindowHandle->getName().string());
+#endif
+ mTempTouchState.addOrUpdateWindow(newHoverWindowHandle,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER, BitSet32(0));
+ }
+ }
+
+ // Check permission to inject into all touched foreground windows and ensure there
+ // is at least one touched foreground window.
+ {
+ bool haveForegroundWindow = false;
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ haveForegroundWindow = true;
+ if (! checkInjectionPermission(touchedWindow.windowHandle,
+ entry->injectionState)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+ }
+ }
+ if (! haveForegroundWindow) {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event because there is no touched foreground window to receive it.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Permission granted to injection into all touched foreground windows.
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ }
+
+ // Check whether windows listening for outside touches are owned by the same UID. If it is
+ // set the policy flag that we will not reveal coordinate information to this window.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ sp<InputWindowHandle> foregroundWindowHandle =
+ mTempTouchState.getFirstForegroundWindowHandle();
+ const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+ sp<InputWindowHandle> inputWindowHandle = touchedWindow.windowHandle;
+ if (inputWindowHandle->getInfo()->ownerUid != foregroundWindowUid) {
+ mTempTouchState.addOrUpdateWindow(inputWindowHandle,
+ InputTarget::FLAG_ZERO_COORDS, BitSet32(0));
+ }
+ }
+ }
+ }
+
+ // Ensure all touched foreground windows are ready for new input.
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ // If the touched window is paused then keep waiting.
+ if (touchedWindow.windowHandle->getInfo()->paused) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, touchedWindow.windowHandle, nextWakeupTime,
+ "Waiting because the touched window is paused.");
+ goto Unresponsive;
+ }
+
+ // If the touched window is still working on previous events then keep waiting.
+ if (!isWindowReadyForMoreInputLocked(currentTime, touchedWindow.windowHandle, entry)) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, touchedWindow.windowHandle, nextWakeupTime,
+ "Waiting because the touched window has not finished "
+ "processing the input events that were previously delivered to it.");
+ goto Unresponsive;
+ }
+ }
+ }
+
+ // If this is the first pointer going down and the touched window has a wallpaper
+ // then also add the touched wallpaper windows so they are locked in for the duration
+ // of the touch gesture.
+ // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
+ // engine only supports touch events. We would need to add a mechanism similar
+ // to View.onGenericMotionEvent to enable wallpapers to handle these events.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ sp<InputWindowHandle> foregroundWindowHandle =
+ mTempTouchState.getFirstForegroundWindowHandle();
+ if (foregroundWindowHandle->getInfo()->hasWallpaper) {
+ for (size_t i = 0; i < mWindowHandles.size(); i++) {
+ sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* info = windowHandle->getInfo();
+ if (info->displayId == displayId
+ && windowHandle->getInfo()->layoutParamsType
+ == InputWindowInfo::TYPE_WALLPAPER) {
+ mTempTouchState.addOrUpdateWindow(windowHandle,
+ InputTarget::FLAG_WINDOW_IS_OBSCURED
+ | InputTarget::FLAG_DISPATCH_AS_IS,
+ BitSet32(0));
+ }
+ }
+ }
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
+ addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+ touchedWindow.pointerIds, inputTargets);
+ }
+
+ // Drop the outside or hover touch windows since we will not care about them
+ // in the next iteration.
+ mTempTouchState.filterNonAsIsTouchWindows();
+
+Failed:
+ // Check injection permission once and for all.
+ if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
+ if (checkInjectionPermission(NULL, entry->injectionState)) {
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ } else {
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ }
+ }
+
+ // Update final pieces of touch state if the injector had permission.
+ if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
+ if (!wrongDevice) {
+ if (switchedDevice) {
+#if DEBUG_FOCUS
+ ALOGD("Conflicting pointer actions: Switched to a different device.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+
+ if (isHoverAction) {
+ // Started hovering, therefore no longer down.
+ if (mTouchState.down) {
+#if DEBUG_FOCUS
+ ALOGD("Conflicting pointer actions: Hover received while pointer was down.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+ mTouchState.reset();
+ if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+ mTouchState.deviceId = entry->deviceId;
+ mTouchState.source = entry->source;
+ mTouchState.displayId = displayId;
+ }
+ } else if (maskedAction == AMOTION_EVENT_ACTION_UP
+ || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ // All pointers up or canceled.
+ mTouchState.reset();
+ } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ // First pointer went down.
+ if (mTouchState.down) {
+#if DEBUG_FOCUS
+ ALOGD("Conflicting pointer actions: Down received while already down.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+ mTouchState.copyFrom(mTempTouchState);
+ } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+ // One pointer went up.
+ if (isSplit) {
+ int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+ uint32_t pointerId = entry->pointerProperties[pointerIndex].id;
+
+ for (size_t i = 0; i < mTempTouchState.windows.size(); ) {
+ TouchedWindow& touchedWindow = mTempTouchState.windows.editItemAt(i);
+ if (touchedWindow.targetFlags & InputTarget::FLAG_SPLIT) {
+ touchedWindow.pointerIds.clearBit(pointerId);
+ if (touchedWindow.pointerIds.isEmpty()) {
+ mTempTouchState.windows.removeAt(i);
+ continue;
+ }
+ }
+ i += 1;
+ }
+ }
+ mTouchState.copyFrom(mTempTouchState);
+ } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
+ // Discard temporary touch state since it was only valid for this action.
+ } else {
+ // Save changes to touch state as-is for all other actions.
+ mTouchState.copyFrom(mTempTouchState);
+ }
+
+ // Update hover state.
+ mLastHoverWindowHandle = newHoverWindowHandle;
+ }
+ } else {
+#if DEBUG_FOCUS
+ ALOGD("Not updating touch focus because injection was denied.");
+#endif
+ }
+
+Unresponsive:
+ // Reset temporary touch state to ensure we release unnecessary references to input channels.
+ mTempTouchState.reset();
+
+ nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+ updateDispatchStatisticsLocked(currentTime, entry,
+ injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+ ALOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, "
+ "timeSpentWaitingForApplication=%0.1fms",
+ injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0);
+#endif
+ return injectionResult;
+}
+
+void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {
+ inputTargets.push();
+
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ InputTarget& target = inputTargets.editTop();
+ target.inputChannel = windowInfo->inputChannel;
+ target.flags = targetFlags;
+ target.xOffset = - windowInfo->frameLeft;
+ target.yOffset = - windowInfo->frameTop;
+ target.scaleFactor = windowInfo->scaleFactor;
+ target.pointerIds = pointerIds;
+}
+
+void InputDispatcher::addMonitoringTargetsLocked(Vector<InputTarget>& inputTargets) {
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ inputTargets.push();
+
+ InputTarget& target = inputTargets.editTop();
+ target.inputChannel = mMonitoringChannels[i];
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+ target.xOffset = 0;
+ target.yOffset = 0;
+ target.pointerIds.clear();
+ target.scaleFactor = 1.0f;
+ }
+}
+
+bool InputDispatcher::checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
+ const InjectionState* injectionState) {
+ if (injectionState
+ && (windowHandle == NULL
+ || windowHandle->getInfo()->ownerUid != injectionState->injectorUid)
+ && !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
+ if (windowHandle != NULL) {
+ ALOGW("Permission denied: injecting event from pid %d uid %d to window %s "
+ "owned by uid %d",
+ injectionState->injectorPid, injectionState->injectorUid,
+ windowHandle->getName().string(),
+ windowHandle->getInfo()->ownerUid);
+ } else {
+ ALOGW("Permission denied: injecting event from pid %d uid %d",
+ injectionState->injectorPid, injectionState->injectorUid);
+ }
+ return false;
+ }
+ return true;
+}
+
+bool InputDispatcher::isWindowObscuredAtPointLocked(
+ const sp<InputWindowHandle>& windowHandle, int32_t x, int32_t y) const {
+ int32_t displayId = windowHandle->getInfo()->displayId;
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ sp<InputWindowHandle> otherHandle = mWindowHandles.itemAt(i);
+ if (otherHandle == windowHandle) {
+ break;
+ }
+
+ const InputWindowInfo* otherInfo = otherHandle->getInfo();
+ if (otherInfo->displayId == displayId
+ && otherInfo->visible && !otherInfo->isTrustedOverlay()
+ && otherInfo->frameContainsPoint(x, y)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::isWindowReadyForMoreInputLocked(nsecs_t currentTime,
+ const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry) {
+ ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ if (connection->inputPublisherBlocked) {
+ return false;
+ }
+ if (eventEntry->type == EventEntry::TYPE_KEY) {
+ // If the event is a key event, then we must wait for all previous events to
+ // complete before delivering it because previous events may have the
+ // side-effect of transferring focus to a different window and we want to
+ // ensure that the following keys are sent to the new window.
+ //
+ // Suppose the user touches a button in a window then immediately presses "A".
+ // If the button causes a pop-up window to appear then we want to ensure that
+ // the "A" key is delivered to the new pop-up window. This is because users
+ // often anticipate pending UI changes when typing on a keyboard.
+ // To obtain this behavior, we must serialize key events with respect to all
+ // prior input events.
+ return connection->outboundQueue.isEmpty()
+ && connection->waitQueue.isEmpty();
+ }
+ // Touch events can always be sent to a window immediately because the user intended
+ // to touch whatever was visible at the time. Even if focus changes or a new
+ // window appears moments later, the touch event was meant to be delivered to
+ // whatever window happened to be on screen at the time.
+ //
+ // Generic motion events, such as trackball or joystick events are a little trickier.
+ // Like key events, generic motion events are delivered to the focused window.
+ // Unlike key events, generic motion events don't tend to transfer focus to other
+ // windows and it is not important for them to be serialized. So we prefer to deliver
+ // generic motion events as soon as possible to improve efficiency and reduce lag
+ // through batching.
+ //
+ // The one case where we pause input event delivery is when the wait queue is piling
+ // up with lots of events because the application is not responding.
+ // This condition ensures that ANRs are detected reliably.
+ if (!connection->waitQueue.isEmpty()
+ && currentTime >= connection->waitQueue.head->eventEntry->eventTime
+ + STREAM_AHEAD_EVENT_TIMEOUT) {
+ return false;
+ }
+ }
+ return true;
+}
+
+String8 InputDispatcher::getApplicationWindowLabelLocked(
+ const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle) {
+ if (applicationHandle != NULL) {
+ if (windowHandle != NULL) {
+ String8 label(applicationHandle->getName());
+ label.append(" - ");
+ label.append(windowHandle->getName());
+ return label;
+ } else {
+ return applicationHandle->getName();
+ }
+ } else if (windowHandle != NULL) {
+ return windowHandle->getName();
+ } else {
+ return String8("<unknown application or window>");
+ }
+}
+
+void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) {
+ if (mFocusedWindowHandle != NULL) {
+ const InputWindowInfo* info = mFocusedWindowHandle->getInfo();
+ if (info->inputFeatures & InputWindowInfo::INPUT_FEATURE_DISABLE_USER_ACTIVITY) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("Not poking user activity: disabled by window '%s'.", info->name.string());
+#endif
+ return;
+ }
+ }
+
+ int32_t eventType = USER_ACTIVITY_EVENT_OTHER;
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_MOTION: {
+ const MotionEntry* motionEntry = static_cast<const MotionEntry*>(eventEntry);
+ if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) {
+ return;
+ }
+
+ if (MotionEvent::isTouchEvent(motionEntry->source, motionEntry->action)) {
+ eventType = USER_ACTIVITY_EVENT_TOUCH;
+ }
+ break;
+ }
+ case EventEntry::TYPE_KEY: {
+ const KeyEntry* keyEntry = static_cast<const KeyEntry*>(eventEntry);
+ if (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) {
+ return;
+ }
+ eventType = USER_ACTIVITY_EVENT_BUTTON;
+ break;
+ }
+ }
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doPokeUserActivityLockedInterruptible);
+ commandEntry->eventTime = eventEntry->eventTime;
+ commandEntry->userActivityEventType = eventType;
+}
+
+void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, "
+ "xOffset=%f, yOffset=%f, scaleFactor=%f, "
+ "pointerIds=0x%x",
+ connection->getInputChannelName(), inputTarget->flags,
+ inputTarget->xOffset, inputTarget->yOffset,
+ inputTarget->scaleFactor, inputTarget->pointerIds.value);
+#endif
+
+ // Skip this event if the connection status is not normal.
+ // We don't want to enqueue additional outbound events if the connection is broken.
+ if (connection->status != Connection::STATUS_NORMAL) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Dropping event because the channel status is %s",
+ connection->getInputChannelName(), connection->getStatusLabel());
+#endif
+ return;
+ }
+
+ // Split a motion event if needed.
+ if (inputTarget->flags & InputTarget::FLAG_SPLIT) {
+ ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION);
+
+ MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry);
+ if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) {
+ MotionEntry* splitMotionEntry = splitMotionEvent(
+ originalMotionEntry, inputTarget->pointerIds);
+ if (!splitMotionEntry) {
+ return; // split event was dropped
+ }
+#if DEBUG_FOCUS
+ ALOGD("channel '%s' ~ Split motion event.",
+ connection->getInputChannelName());
+ logOutboundMotionDetailsLocked(" ", splitMotionEntry);
+#endif
+ enqueueDispatchEntriesLocked(currentTime, connection,
+ splitMotionEntry, inputTarget);
+ splitMotionEntry->release();
+ return;
+ }
+ }
+
+ // Not splitting. Enqueue dispatch entries for the event as is.
+ enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
+}
+
+void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
+ bool wasEmpty = connection->outboundQueue.isEmpty();
+
+ // Enqueue dispatch entries for the requested modes.
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_IS);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
+
+ // If the outbound queue was previously empty, start the dispatch cycle going.
+ if (wasEmpty && !connection->outboundQueue.isEmpty()) {
+ startDispatchCycleLocked(currentTime, connection);
+ }
+}
+
+void InputDispatcher::enqueueDispatchEntryLocked(
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
+ int32_t dispatchMode) {
+ int32_t inputTargetFlags = inputTarget->flags;
+ if (!(inputTargetFlags & dispatchMode)) {
+ return;
+ }
+ inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
+
+ // This is a new event.
+ // Enqueue a new dispatch entry onto the outbound queue for this connection.
+ DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref
+ inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
+ inputTarget->scaleFactor);
+
+ // Apply target flags and update the connection's input state.
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+ dispatchEntry->resolvedAction = keyEntry->action;
+ dispatchEntry->resolvedFlags = keyEntry->flags;
+
+ if (!connection->inputState.trackKey(keyEntry,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
+ connection->getInputChannelName());
+#endif
+ delete dispatchEntry;
+ return; // skip the inconsistent event
+ }
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+ if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
+ } else {
+ dispatchEntry->resolvedAction = motionEntry->action;
+ }
+ if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
+ && !connection->inputState.isHovering(
+ motionEntry->deviceId, motionEntry->source, motionEntry->displayId)) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover enter event",
+ connection->getInputChannelName());
+#endif
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ }
+
+ dispatchEntry->resolvedFlags = motionEntry->flags;
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+ dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ if (!connection->inputState.trackMotion(motionEntry,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion event",
+ connection->getInputChannelName());
+#endif
+ delete dispatchEntry;
+ return; // skip the inconsistent event
+ }
+ break;
+ }
+ }
+
+ // Remember that we are waiting for this dispatch to complete.
+ if (dispatchEntry->hasForegroundTarget()) {
+ incrementPendingForegroundDispatchesLocked(eventEntry);
+ }
+
+ // Enqueue the dispatch entry.
+ connection->outboundQueue.enqueueAtTail(dispatchEntry);
+ traceOutboundQueueLengthLocked(connection);
+}
+
+void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ startDispatchCycle",
+ connection->getInputChannelName());
+#endif
+
+ while (connection->status == Connection::STATUS_NORMAL
+ && !connection->outboundQueue.isEmpty()) {
+ DispatchEntry* dispatchEntry = connection->outboundQueue.head;
+ dispatchEntry->deliveryTime = currentTime;
+
+ // Publish the event.
+ status_t status;
+ EventEntry* eventEntry = dispatchEntry->eventEntry;
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+
+ // Publish the key event.
+ status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
+ keyEntry->deviceId, keyEntry->source,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+ keyEntry->keyCode, keyEntry->scanCode,
+ keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
+ keyEntry->eventTime);
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+
+ PointerCoords scaledCoords[MAX_POINTERS];
+ const PointerCoords* usingCoords = motionEntry->pointerCoords;
+
+ // Set the X and Y offset depending on the input source.
+ float xOffset, yOffset, scaleFactor;
+ if ((motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
+ && !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
+ scaleFactor = dispatchEntry->scaleFactor;
+ xOffset = dispatchEntry->xOffset * scaleFactor;
+ yOffset = dispatchEntry->yOffset * scaleFactor;
+ if (scaleFactor != 1.0f) {
+ for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+ scaledCoords[i] = motionEntry->pointerCoords[i];
+ scaledCoords[i].scale(scaleFactor);
+ }
+ usingCoords = scaledCoords;
+ }
+ } else {
+ xOffset = 0.0f;
+ yOffset = 0.0f;
+ scaleFactor = 1.0f;
+
+ // We don't want the dispatch target to know.
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) {
+ for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+ scaledCoords[i].clear();
+ }
+ usingCoords = scaledCoords;
+ }
+ }
+
+ // Publish the motion event.
+ status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,
+ motionEntry->deviceId, motionEntry->source,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+ motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState,
+ xOffset, yOffset,
+ motionEntry->xPrecision, motionEntry->yPrecision,
+ motionEntry->downTime, motionEntry->eventTime,
+ motionEntry->pointerCount, motionEntry->pointerProperties,
+ usingCoords);
+ break;
+ }
+
+ default:
+ ALOG_ASSERT(false);
+ return;
+ }
+
+ // Check the result.
+ if (status) {
+ if (status == WOULD_BLOCK) {
+ if (connection->waitQueue.isEmpty()) {
+ ALOGE("channel '%s' ~ Could not publish event because the pipe is full. "
+ "This is unexpected because the wait queue is empty, so the pipe "
+ "should be empty and we shouldn't have any problems writing an "
+ "event to it, status=%d", connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ } else {
+ // Pipe is full and we are waiting for the app to finish process some events
+ // before sending more events to it.
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Could not publish event because the pipe is full, "
+ "waiting for the application to catch up",
+ connection->getInputChannelName());
+#endif
+ connection->inputPublisherBlocked = true;
+ }
+ } else {
+ ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, "
+ "status=%d", connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ }
+ return;
+ }
+
+ // Re-enqueue the event on the wait queue.
+ connection->outboundQueue.dequeue(dispatchEntry);
+ traceOutboundQueueLengthLocked(connection);
+ connection->waitQueue.enqueueAtTail(dispatchEntry);
+ traceWaitQueueLengthLocked(connection);
+ }
+}
+
+void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, uint32_t seq, bool handled) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s",
+ connection->getInputChannelName(), seq, toString(handled));
+#endif
+
+ connection->inputPublisherBlocked = false;
+
+ if (connection->status == Connection::STATUS_BROKEN
+ || connection->status == Connection::STATUS_ZOMBIE) {
+ return;
+ }
+
+ // Notify other system components and prepare to start the next dispatch cycle.
+ onDispatchCycleFinishedLocked(currentTime, connection, seq, handled);
+}
+
+void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, bool notify) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s",
+ connection->getInputChannelName(), toString(notify));
+#endif
+
+ // Clear the dispatch queues.
+ drainDispatchQueueLocked(&connection->outboundQueue);
+ traceOutboundQueueLengthLocked(connection);
+ drainDispatchQueueLocked(&connection->waitQueue);
+ traceWaitQueueLengthLocked(connection);
+
+ // The connection appears to be unrecoverably broken.
+ // Ignore already broken or zombie connections.
+ if (connection->status == Connection::STATUS_NORMAL) {
+ connection->status = Connection::STATUS_BROKEN;
+
+ if (notify) {
+ // Notify other system components.
+ onDispatchCycleBrokenLocked(currentTime, connection);
+ }
+ }
+}
+
+void InputDispatcher::drainDispatchQueueLocked(Queue<DispatchEntry>* queue) {
+ while (!queue->isEmpty()) {
+ DispatchEntry* dispatchEntry = queue->dequeueAtHead();
+ releaseDispatchEntryLocked(dispatchEntry);
+ }
+}
+
+void InputDispatcher::releaseDispatchEntryLocked(DispatchEntry* dispatchEntry) {
+ if (dispatchEntry->hasForegroundTarget()) {
+ decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
+ }
+ delete dispatchEntry;
+}
+
+int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
+ InputDispatcher* d = static_cast<InputDispatcher*>(data);
+
+ { // acquire lock
+ AutoMutex _l(d->mLock);
+
+ ssize_t connectionIndex = d->mConnectionsByFd.indexOfKey(fd);
+ if (connectionIndex < 0) {
+ ALOGE("Received spurious receive callback for unknown input channel. "
+ "fd=%d, events=0x%x", fd, events);
+ return 0; // remove the callback
+ }
+
+ bool notify;
+ sp<Connection> connection = d->mConnectionsByFd.valueAt(connectionIndex);
+ if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
+ if (!(events & ALOOPER_EVENT_INPUT)) {
+ ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ return 1;
+ }
+
+ nsecs_t currentTime = now();
+ bool gotOne = false;
+ status_t status;
+ for (;;) {
+ uint32_t seq;
+ bool handled;
+ status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);
+ if (status) {
+ break;
+ }
+ d->finishDispatchCycleLocked(currentTime, connection, seq, handled);
+ gotOne = true;
+ }
+ if (gotOne) {
+ d->runCommandsLockedInterruptible();
+ if (status == WOULD_BLOCK) {
+ return 1;
+ }
+ }
+
+ notify = status != DEAD_OBJECT || !connection->monitor;
+ if (notify) {
+ ALOGE("channel '%s' ~ Failed to receive finished signal. status=%d",
+ connection->getInputChannelName(), status);
+ }
+ } else {
+ // Monitor channels are never explicitly unregistered.
+ // We do it automatically when the remote endpoint is closed so don't warn
+ // about them.
+ notify = !connection->monitor;
+ if (notify) {
+ ALOGW("channel '%s' ~ Consumer closed input channel or an error occurred. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ }
+ }
+
+ // Unregister the channel.
+ d->unregisterInputChannelLocked(connection->inputChannel, notify);
+ return 0; // remove the callback
+ } // release lock
+}
+
+void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked(
+ const CancelationOptions& options) {
+ for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByFd.valueAt(i), options);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
+ const sp<InputChannel>& channel, const CancelationOptions& options) {
+ ssize_t index = getConnectionIndexLocked(channel);
+ if (index >= 0) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByFd.valueAt(index), options);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
+ const sp<Connection>& connection, const CancelationOptions& options) {
+ if (connection->status == Connection::STATUS_BROKEN) {
+ return;
+ }
+
+ nsecs_t currentTime = now();
+
+ Vector<EventEntry*> cancelationEvents;
+ connection->inputState.synthesizeCancelationEvents(currentTime,
+ cancelationEvents, options);
+
+ if (!cancelationEvents.isEmpty()) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("channel '%s' ~ Synthesized %d cancelation events to bring channel back in sync "
+ "with reality: %s, mode=%d.",
+ connection->getInputChannelName(), cancelationEvents.size(),
+ options.reason, options.mode);
+#endif
+ for (size_t i = 0; i < cancelationEvents.size(); i++) {
+ EventEntry* cancelationEventEntry = cancelationEvents.itemAt(i);
+ switch (cancelationEventEntry->type) {
+ case EventEntry::TYPE_KEY:
+ logOutboundKeyDetailsLocked("cancel - ",
+ static_cast<KeyEntry*>(cancelationEventEntry));
+ break;
+ case EventEntry::TYPE_MOTION:
+ logOutboundMotionDetailsLocked("cancel - ",
+ static_cast<MotionEntry*>(cancelationEventEntry));
+ break;
+ }
+
+ InputTarget target;
+ sp<InputWindowHandle> windowHandle = getWindowHandleLocked(connection->inputChannel);
+ if (windowHandle != NULL) {
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ target.xOffset = -windowInfo->frameLeft;
+ target.yOffset = -windowInfo->frameTop;
+ target.scaleFactor = windowInfo->scaleFactor;
+ } else {
+ target.xOffset = 0;
+ target.yOffset = 0;
+ target.scaleFactor = 1.0f;
+ }
+ target.inputChannel = connection->inputChannel;
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+
+ enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
+ &target, InputTarget::FLAG_DISPATCH_AS_IS);
+
+ cancelationEventEntry->release();
+ }
+
+ startDispatchCycleLocked(currentTime, connection);
+ }
+}
+
+InputDispatcher::MotionEntry*
+InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) {
+ ALOG_ASSERT(pointerIds.value != 0);
+
+ PointerProperties splitPointerProperties[MAX_POINTERS];
+ PointerCoords splitPointerCoords[MAX_POINTERS];
+
+ uint32_t originalPointerCount = originalMotionEntry->pointerCount;
+ uint32_t splitPointerCount = 0;
+
+ for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
+ originalPointerIndex++) {
+ const PointerProperties& pointerProperties =
+ originalMotionEntry->pointerProperties[originalPointerIndex];
+ uint32_t pointerId = uint32_t(pointerProperties.id);
+ if (pointerIds.hasBit(pointerId)) {
+ splitPointerProperties[splitPointerCount].copyFrom(pointerProperties);
+ splitPointerCoords[splitPointerCount].copyFrom(
+ originalMotionEntry->pointerCoords[originalPointerIndex]);
+ splitPointerCount += 1;
+ }
+ }
+
+ if (splitPointerCount != pointerIds.count()) {
+ // This is bad. We are missing some of the pointers that we expected to deliver.
+ // Most likely this indicates that we received an ACTION_MOVE events that has
+ // different pointer ids than we expected based on the previous ACTION_DOWN
+ // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
+ // in this way.
+ ALOGW("Dropping split motion event because the pointer count is %d but "
+ "we expected there to be %d pointers. This probably means we received "
+ "a broken sequence of pointer ids from the input device.",
+ splitPointerCount, pointerIds.count());
+ return NULL;
+ }
+
+ int32_t action = originalMotionEntry->action;
+ int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+ if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+ || maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+ int32_t originalPointerIndex = getMotionEventActionPointerIndex(action);
+ const PointerProperties& pointerProperties =
+ originalMotionEntry->pointerProperties[originalPointerIndex];
+ uint32_t pointerId = uint32_t(pointerProperties.id);
+ if (pointerIds.hasBit(pointerId)) {
+ if (pointerIds.count() == 1) {
+ // The first/last pointer went down/up.
+ action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+ ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+ } else {
+ // A secondary pointer went down/up.
+ uint32_t splitPointerIndex = 0;
+ while (pointerId != uint32_t(splitPointerProperties[splitPointerIndex].id)) {
+ splitPointerIndex += 1;
+ }
+ action = maskedAction | (splitPointerIndex
+ << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ }
+ } else {
+ // An unrelated pointer changed.
+ action = AMOTION_EVENT_ACTION_MOVE;
+ }
+ }
+
+ MotionEntry* splitMotionEntry = new MotionEntry(
+ originalMotionEntry->eventTime,
+ originalMotionEntry->deviceId,
+ originalMotionEntry->source,
+ originalMotionEntry->policyFlags,
+ action,
+ originalMotionEntry->flags,
+ originalMotionEntry->metaState,
+ originalMotionEntry->buttonState,
+ originalMotionEntry->edgeFlags,
+ originalMotionEntry->xPrecision,
+ originalMotionEntry->yPrecision,
+ originalMotionEntry->downTime,
+ originalMotionEntry->displayId,
+ splitPointerCount, splitPointerProperties, splitPointerCoords);
+
+ if (originalMotionEntry->injectionState) {
+ splitMotionEntry->injectionState = originalMotionEntry->injectionState;
+ splitMotionEntry->injectionState->refCount += 1;
+ }
+
+ return splitMotionEntry;
+}
+
+void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyConfigurationChanged - eventTime=%lld", args->eventTime);
+#endif
+
+ bool needWake;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ ConfigurationChangedEntry* newEntry = new ConfigurationChangedEntry(args->eventTime);
+ needWake = enqueueInboundEventLocked(newEntry);
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyKey - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, action=0x%x, "
+ "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
+ args->eventTime, args->deviceId, args->source, args->policyFlags,
+ args->action, args->flags, args->keyCode, args->scanCode,
+ args->metaState, args->downTime);
+#endif
+ if (!validateKeyEvent(args->action)) {
+ return;
+ }
+
+ uint32_t policyFlags = args->policyFlags;
+ int32_t flags = args->flags;
+ int32_t metaState = args->metaState;
+ if ((policyFlags & POLICY_FLAG_VIRTUAL) || (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY)) {
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+ flags |= AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY;
+ }
+ if (policyFlags & POLICY_FLAG_ALT) {
+ metaState |= AMETA_ALT_ON | AMETA_ALT_LEFT_ON;
+ }
+ if (policyFlags & POLICY_FLAG_ALT_GR) {
+ metaState |= AMETA_ALT_ON | AMETA_ALT_RIGHT_ON;
+ }
+ if (policyFlags & POLICY_FLAG_SHIFT) {
+ metaState |= AMETA_SHIFT_ON | AMETA_SHIFT_LEFT_ON;
+ }
+ if (policyFlags & POLICY_FLAG_CAPS_LOCK) {
+ metaState |= AMETA_CAPS_LOCK_ON;
+ }
+ if (policyFlags & POLICY_FLAG_FUNCTION) {
+ metaState |= AMETA_FUNCTION_ON;
+ }
+
+ policyFlags |= POLICY_FLAG_TRUSTED;
+
+ KeyEvent event;
+ event.initialize(args->deviceId, args->source, args->action,
+ flags, args->keyCode, args->scanCode, metaState, 0,
+ args->downTime, args->eventTime);
+
+ mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
+
+ if (policyFlags & POLICY_FLAG_WOKE_HERE) {
+ flags |= AKEY_EVENT_FLAG_WOKE_HERE;
+ }
+
+ bool needWake;
+ { // acquire lock
+ mLock.lock();
+
+ if (shouldSendKeyToInputFilterLocked(args)) {
+ mLock.unlock();
+
+ policyFlags |= POLICY_FLAG_FILTERED;
+ if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+ return; // event was consumed by the filter
+ }
+
+ mLock.lock();
+ }
+
+ int32_t repeatCount = 0;
+ KeyEntry* newEntry = new KeyEntry(args->eventTime,
+ args->deviceId, args->source, policyFlags,
+ args->action, flags, args->keyCode, args->scanCode,
+ metaState, repeatCount, args->downTime);
+
+ needWake = enqueueInboundEventLocked(newEntry);
+ mLock.unlock();
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) {
+ return mInputFilterEnabled;
+}
+
+void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyMotion - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, edgeFlags=0x%x, "
+ "xPrecision=%f, yPrecision=%f, downTime=%lld",
+ args->eventTime, args->deviceId, args->source, args->policyFlags,
+ args->action, args->flags, args->metaState, args->buttonState,
+ args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime);
+ for (uint32_t i = 0; i < args->pointerCount; i++) {
+ ALOGD(" Pointer %d: id=%d, toolType=%d, "
+ "x=%f, y=%f, pressure=%f, size=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+ "orientation=%f",
+ i, args->pointerProperties[i].id,
+ args->pointerProperties[i].toolType,
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+ }
+#endif
+ if (!validateMotionEvent(args->action, args->pointerCount, args->pointerProperties)) {
+ return;
+ }
+
+ uint32_t policyFlags = args->policyFlags;
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags);
+
+ bool needWake;
+ { // acquire lock
+ mLock.lock();
+
+ if (shouldSendMotionToInputFilterLocked(args)) {
+ mLock.unlock();
+
+ MotionEvent event;
+ event.initialize(args->deviceId, args->source, args->action, args->flags,
+ args->edgeFlags, args->metaState, args->buttonState, 0, 0,
+ args->xPrecision, args->yPrecision,
+ args->downTime, args->eventTime,
+ args->pointerCount, args->pointerProperties, args->pointerCoords);
+
+ policyFlags |= POLICY_FLAG_FILTERED;
+ if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+ return; // event was consumed by the filter
+ }
+
+ mLock.lock();
+ }
+
+ // Just enqueue a new motion event.
+ MotionEntry* newEntry = new MotionEntry(args->eventTime,
+ args->deviceId, args->source, policyFlags,
+ args->action, args->flags, args->metaState, args->buttonState,
+ args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
+ args->displayId,
+ args->pointerCount, args->pointerProperties, args->pointerCoords);
+
+ needWake = enqueueInboundEventLocked(newEntry);
+ mLock.unlock();
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) {
+ // TODO: support sending secondary display events to input filter
+ return mInputFilterEnabled && isMainDisplay(args->displayId);
+}
+
+void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifySwitch - eventTime=%lld, policyFlags=0x%x, switchValues=0x%08x, switchMask=0x%08x",
+ args->eventTime, args->policyFlags,
+ args->switchValues, args->switchMask);
+#endif
+
+ uint32_t policyFlags = args->policyFlags;
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ mPolicy->notifySwitch(args->eventTime,
+ args->switchValues, args->switchMask, policyFlags);
+}
+
+void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyDeviceReset - eventTime=%lld, deviceId=%d",
+ args->eventTime, args->deviceId);
+#endif
+
+ bool needWake;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ DeviceResetEntry* newEntry = new DeviceResetEntry(args->eventTime, args->deviceId);
+ needWake = enqueueInboundEventLocked(newEntry);
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
+ "syncMode=%d, timeoutMillis=%d, policyFlags=0x%08x",
+ event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis, policyFlags);
+#endif
+
+ nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
+
+ policyFlags |= POLICY_FLAG_INJECTED;
+ if (hasInjectionPermission(injectorPid, injectorUid)) {
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ }
+
+ EventEntry* firstInjectedEntry;
+ EventEntry* lastInjectedEntry;
+ switch (event->getType()) {
+ case AINPUT_EVENT_TYPE_KEY: {
+ const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
+ int32_t action = keyEvent->getAction();
+ if (! validateKeyEvent(action)) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ int32_t flags = keyEvent->getFlags();
+ if (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY) {
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+ }
+
+ if (!(policyFlags & POLICY_FLAG_FILTERED)) {
+ mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
+ }
+
+ if (policyFlags & POLICY_FLAG_WOKE_HERE) {
+ flags |= AKEY_EVENT_FLAG_WOKE_HERE;
+ }
+
+ mLock.lock();
+ firstInjectedEntry = new KeyEntry(keyEvent->getEventTime(),
+ keyEvent->getDeviceId(), keyEvent->getSource(),
+ policyFlags, action, flags,
+ keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
+ keyEvent->getRepeatCount(), keyEvent->getDownTime());
+ lastInjectedEntry = firstInjectedEntry;
+ break;
+ }
+
+ case AINPUT_EVENT_TYPE_MOTION: {
+ const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
+ int32_t displayId = ADISPLAY_ID_DEFAULT;
+ int32_t action = motionEvent->getAction();
+ size_t pointerCount = motionEvent->getPointerCount();
+ const PointerProperties* pointerProperties = motionEvent->getPointerProperties();
+ if (! validateMotionEvent(action, pointerCount, pointerProperties)) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ if (!(policyFlags & POLICY_FLAG_FILTERED)) {
+ nsecs_t eventTime = motionEvent->getEventTime();
+ mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags);
+ }
+
+ mLock.lock();
+ const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
+ const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
+ firstInjectedEntry = new MotionEntry(*sampleEventTimes,
+ motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
+ action, motionEvent->getFlags(),
+ motionEvent->getMetaState(), motionEvent->getButtonState(),
+ motionEvent->getEdgeFlags(),
+ motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+ motionEvent->getDownTime(), displayId,
+ uint32_t(pointerCount), pointerProperties, samplePointerCoords);
+ lastInjectedEntry = firstInjectedEntry;
+ for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
+ sampleEventTimes += 1;
+ samplePointerCoords += pointerCount;
+ MotionEntry* nextInjectedEntry = new MotionEntry(*sampleEventTimes,
+ motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
+ action, motionEvent->getFlags(),
+ motionEvent->getMetaState(), motionEvent->getButtonState(),
+ motionEvent->getEdgeFlags(),
+ motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+ motionEvent->getDownTime(), displayId,
+ uint32_t(pointerCount), pointerProperties, samplePointerCoords);
+ lastInjectedEntry->next = nextInjectedEntry;
+ lastInjectedEntry = nextInjectedEntry;
+ }
+ break;
+ }
+
+ default:
+ ALOGW("Cannot inject event of type %d", event->getType());
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ InjectionState* injectionState = new InjectionState(injectorPid, injectorUid);
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionState->injectionIsAsync = true;
+ }
+
+ injectionState->refCount += 1;
+ lastInjectedEntry->injectionState = injectionState;
+
+ bool needWake = false;
+ for (EventEntry* entry = firstInjectedEntry; entry != NULL; ) {
+ EventEntry* nextEntry = entry->next;
+ needWake |= enqueueInboundEventLocked(entry);
+ entry = nextEntry;
+ }
+
+ mLock.unlock();
+
+ if (needWake) {
+ mLooper->wake();
+ }
+
+ int32_t injectionResult;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ } else {
+ for (;;) {
+ injectionResult = injectionState->injectionResult;
+ if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
+ break;
+ }
+
+ nsecs_t remainingTimeout = endTime - now();
+ if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Timed out waiting for injection result "
+ "to become available.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+ break;
+ }
+
+ mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
+ }
+
+ if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED
+ && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) {
+ while (injectionState->pendingForegroundDispatches != 0) {
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
+ injectionState->pendingForegroundDispatches);
+#endif
+ nsecs_t remainingTimeout = endTime - now();
+ if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Timed out waiting for pending foreground "
+ "dispatches to finish.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+ break;
+ }
+
+ mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout);
+ }
+ }
+ }
+
+ injectionState->release();
+ } // release lock
+
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Finished with result %d. "
+ "injectorPid=%d, injectorUid=%d",
+ injectionResult, injectorPid, injectorUid);
+#endif
+
+ return injectionResult;
+}
+
+bool InputDispatcher::hasInjectionPermission(int32_t injectorPid, int32_t injectorUid) {
+ return injectorUid == 0
+ || mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid);
+}
+
+void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+#if DEBUG_INJECTION
+ ALOGD("Setting input event injection result to %d. "
+ "injectorPid=%d, injectorUid=%d",
+ injectionResult, injectionState->injectorPid, injectionState->injectorUid);
+#endif
+
+ if (injectionState->injectionIsAsync
+ && !(entry->policyFlags & POLICY_FLAG_FILTERED)) {
+ // Log the outcome since the injector did not wait for the injection result.
+ switch (injectionResult) {
+ case INPUT_EVENT_INJECTION_SUCCEEDED:
+ ALOGV("Asynchronous input event injection succeeded.");
+ break;
+ case INPUT_EVENT_INJECTION_FAILED:
+ ALOGW("Asynchronous input event injection failed.");
+ break;
+ case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
+ ALOGW("Asynchronous input event injection permission denied.");
+ break;
+ case INPUT_EVENT_INJECTION_TIMED_OUT:
+ ALOGW("Asynchronous input event injection timed out.");
+ break;
+ }
+ }
+
+ injectionState->injectionResult = injectionResult;
+ mInjectionResultAvailableCondition.broadcast();
+ }
+}
+
+void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+ injectionState->pendingForegroundDispatches += 1;
+ }
+}
+
+void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+ injectionState->pendingForegroundDispatches -= 1;
+
+ if (injectionState->pendingForegroundDispatches == 0) {
+ mInjectionSyncFinishedCondition.broadcast();
+ }
+ }
+}
+
+sp<InputWindowHandle> InputDispatcher::getWindowHandleLocked(
+ const sp<InputChannel>& inputChannel) const {
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ if (windowHandle->getInputChannel() == inputChannel) {
+ return windowHandle;
+ }
+ }
+ return NULL;
+}
+
+bool InputDispatcher::hasWindowHandleLocked(
+ const sp<InputWindowHandle>& windowHandle) const {
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ if (mWindowHandles.itemAt(i) == windowHandle) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
+#if DEBUG_FOCUS
+ ALOGD("setInputWindows");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ Vector<sp<InputWindowHandle> > oldWindowHandles = mWindowHandles;
+ mWindowHandles = inputWindowHandles;
+
+ sp<InputWindowHandle> newFocusedWindowHandle;
+ bool foundHoveredWindow = false;
+ for (size_t i = 0; i < mWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) {
+ mWindowHandles.removeAt(i--);
+ continue;
+ }
+ if (windowHandle->getInfo()->hasFocus) {
+ newFocusedWindowHandle = windowHandle;
+ }
+ if (windowHandle == mLastHoverWindowHandle) {
+ foundHoveredWindow = true;
+ }
+ }
+
+ if (!foundHoveredWindow) {
+ mLastHoverWindowHandle = NULL;
+ }
+
+ if (mFocusedWindowHandle != newFocusedWindowHandle) {
+ if (mFocusedWindowHandle != NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Focus left window: %s",
+ mFocusedWindowHandle->getName().string());
+#endif
+ sp<InputChannel> focusedInputChannel = mFocusedWindowHandle->getInputChannel();
+ if (focusedInputChannel != NULL) {
+ CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
+ "focus left window");
+ synthesizeCancelationEventsForInputChannelLocked(
+ focusedInputChannel, options);
+ }
+ }
+ if (newFocusedWindowHandle != NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Focus entered window: %s",
+ newFocusedWindowHandle->getName().string());
+#endif
+ }
+ mFocusedWindowHandle = newFocusedWindowHandle;
+ }
+
+ for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+ TouchedWindow& touchedWindow = mTouchState.windows.editItemAt(i);
+ if (!hasWindowHandleLocked(touchedWindow.windowHandle)) {
+#if DEBUG_FOCUS
+ ALOGD("Touched window was removed: %s",
+ touchedWindow.windowHandle->getName().string());
+#endif
+ sp<InputChannel> touchedInputChannel =
+ touchedWindow.windowHandle->getInputChannel();
+ if (touchedInputChannel != NULL) {
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ "touched window was removed");
+ synthesizeCancelationEventsForInputChannelLocked(
+ touchedInputChannel, options);
+ }
+ mTouchState.windows.removeAt(i--);
+ }
+ }
+
+ // Release information for windows that are no longer present.
+ // This ensures that unused input channels are released promptly.
+ // Otherwise, they might stick around until the window handle is destroyed
+ // which might not happen until the next GC.
+ for (size_t i = 0; i < oldWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& oldWindowHandle = oldWindowHandles.itemAt(i);
+ if (!hasWindowHandleLocked(oldWindowHandle)) {
+#if DEBUG_FOCUS
+ ALOGD("Window went away: %s", oldWindowHandle->getName().string());
+#endif
+ oldWindowHandle->releaseInfo();
+ }
+ }
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+}
+
+void InputDispatcher::setFocusedApplication(
+ const sp<InputApplicationHandle>& inputApplicationHandle) {
+#if DEBUG_FOCUS
+ ALOGD("setFocusedApplication");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (inputApplicationHandle != NULL && inputApplicationHandle->updateInfo()) {
+ if (mFocusedApplicationHandle != inputApplicationHandle) {
+ if (mFocusedApplicationHandle != NULL) {
+ resetANRTimeoutsLocked();
+ mFocusedApplicationHandle->releaseInfo();
+ }
+ mFocusedApplicationHandle = inputApplicationHandle;
+ }
+ } else if (mFocusedApplicationHandle != NULL) {
+ resetANRTimeoutsLocked();
+ mFocusedApplicationHandle->releaseInfo();
+ mFocusedApplicationHandle.clear();
+ }
+
+#if DEBUG_FOCUS
+ //logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+}
+
+void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
+#if DEBUG_FOCUS
+ ALOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
+#endif
+
+ bool changed;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
+ if (mDispatchFrozen && !frozen) {
+ resetANRTimeoutsLocked();
+ }
+
+ if (mDispatchEnabled && !enabled) {
+ resetAndDropEverythingLocked("dispatcher is being disabled");
+ }
+
+ mDispatchEnabled = enabled;
+ mDispatchFrozen = frozen;
+ changed = true;
+ } else {
+ changed = false;
+ }
+
+#if DEBUG_FOCUS
+ //logDispatchStateLocked();
+#endif
+ } // release lock
+
+ if (changed) {
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+ }
+}
+
+void InputDispatcher::setInputFilterEnabled(bool enabled) {
+#if DEBUG_FOCUS
+ ALOGD("setInputFilterEnabled: enabled=%d", enabled);
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mInputFilterEnabled == enabled) {
+ return;
+ }
+
+ mInputFilterEnabled = enabled;
+ resetAndDropEverythingLocked("input filter is being enabled or disabled");
+ } // release lock
+
+ // Wake up poll loop since there might be work to do to drop everything.
+ mLooper->wake();
+}
+
+bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) {
+#if DEBUG_FOCUS
+ ALOGD("transferTouchFocus: fromChannel=%s, toChannel=%s",
+ fromChannel->getName().string(), toChannel->getName().string());
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ sp<InputWindowHandle> fromWindowHandle = getWindowHandleLocked(fromChannel);
+ sp<InputWindowHandle> toWindowHandle = getWindowHandleLocked(toChannel);
+ if (fromWindowHandle == NULL || toWindowHandle == NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Cannot transfer focus because from or to window not found.");
+#endif
+ return false;
+ }
+ if (fromWindowHandle == toWindowHandle) {
+#if DEBUG_FOCUS
+ ALOGD("Trivial transfer to same window.");
+#endif
+ return true;
+ }
+ if (fromWindowHandle->getInfo()->displayId != toWindowHandle->getInfo()->displayId) {
+#if DEBUG_FOCUS
+ ALOGD("Cannot transfer focus because windows are on different displays.");
+#endif
+ return false;
+ }
+
+ bool found = false;
+ for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTouchState.windows[i];
+ if (touchedWindow.windowHandle == fromWindowHandle) {
+ int32_t oldTargetFlags = touchedWindow.targetFlags;
+ BitSet32 pointerIds = touchedWindow.pointerIds;
+
+ mTouchState.windows.removeAt(i);
+
+ int32_t newTargetFlags = oldTargetFlags
+ & (InputTarget::FLAG_FOREGROUND
+ | InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
+ mTouchState.addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds);
+
+ found = true;
+ break;
+ }
+ }
+
+ if (! found) {
+#if DEBUG_FOCUS
+ ALOGD("Focus transfer failed because from window did not have focus.");
+#endif
+ return false;
+ }
+
+ ssize_t fromConnectionIndex = getConnectionIndexLocked(fromChannel);
+ ssize_t toConnectionIndex = getConnectionIndexLocked(toChannel);
+ if (fromConnectionIndex >= 0 && toConnectionIndex >= 0) {
+ sp<Connection> fromConnection = mConnectionsByFd.valueAt(fromConnectionIndex);
+ sp<Connection> toConnection = mConnectionsByFd.valueAt(toConnectionIndex);
+
+ fromConnection->inputState.copyPointerStateTo(toConnection->inputState);
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ "transferring touch focus from this window to another window");
+ synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
+ }
+
+#if DEBUG_FOCUS
+ logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+ return true;
+}
+
+void InputDispatcher::resetAndDropEverythingLocked(const char* reason) {
+#if DEBUG_FOCUS
+ ALOGD("Resetting and dropping all events (%s).", reason);
+#endif
+
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+
+ resetKeyRepeatLocked();
+ releasePendingEventLocked();
+ drainInboundQueueLocked();
+ resetANRTimeoutsLocked();
+
+ mTouchState.reset();
+ mLastHoverWindowHandle.clear();
+}
+
+void InputDispatcher::logDispatchStateLocked() {
+ String8 dump;
+ dumpDispatchStateLocked(dump);
+
+ char* text = dump.lockBuffer(dump.size());
+ char* start = text;
+ while (*start != '\0') {
+ char* end = strchr(start, '\n');
+ if (*end == '\n') {
+ *(end++) = '\0';
+ }
+ ALOGD("%s", start);
+ start = end;
+ }
+}
+
+void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
+ dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled);
+ dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen);
+
+ if (mFocusedApplicationHandle != NULL) {
+ dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
+ mFocusedApplicationHandle->getName().string(),
+ mFocusedApplicationHandle->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT) / 1000000.0);
+ } else {
+ dump.append(INDENT "FocusedApplication: <null>\n");
+ }
+ dump.appendFormat(INDENT "FocusedWindow: name='%s'\n",
+ mFocusedWindowHandle != NULL ? mFocusedWindowHandle->getName().string() : "<null>");
+
+ dump.appendFormat(INDENT "TouchDown: %s\n", toString(mTouchState.down));
+ dump.appendFormat(INDENT "TouchSplit: %s\n", toString(mTouchState.split));
+ dump.appendFormat(INDENT "TouchDeviceId: %d\n", mTouchState.deviceId);
+ dump.appendFormat(INDENT "TouchSource: 0x%08x\n", mTouchState.source);
+ dump.appendFormat(INDENT "TouchDisplayId: %d\n", mTouchState.displayId);
+ if (!mTouchState.windows.isEmpty()) {
+ dump.append(INDENT "TouchedWindows:\n");
+ for (size_t i = 0; i < mTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTouchState.windows[i];
+ dump.appendFormat(INDENT2 "%d: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
+ i, touchedWindow.windowHandle->getName().string(),
+ touchedWindow.pointerIds.value,
+ touchedWindow.targetFlags);
+ }
+ } else {
+ dump.append(INDENT "TouchedWindows: <none>\n");
+ }
+
+ if (!mWindowHandles.isEmpty()) {
+ dump.append(INDENT "Windows:\n");
+ for (size_t i = 0; i < mWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+
+ dump.appendFormat(INDENT2 "%d: name='%s', displayId=%d, "
+ "paused=%s, hasFocus=%s, hasWallpaper=%s, "
+ "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
+ "frame=[%d,%d][%d,%d], scale=%f, "
+ "touchableRegion=",
+ i, windowInfo->name.string(), windowInfo->displayId,
+ toString(windowInfo->paused),
+ toString(windowInfo->hasFocus),
+ toString(windowInfo->hasWallpaper),
+ toString(windowInfo->visible),
+ toString(windowInfo->canReceiveKeys),
+ windowInfo->layoutParamsFlags, windowInfo->layoutParamsType,
+ windowInfo->layer,
+ windowInfo->frameLeft, windowInfo->frameTop,
+ windowInfo->frameRight, windowInfo->frameBottom,
+ windowInfo->scaleFactor);
+ dumpRegion(dump, windowInfo->touchableRegion);
+ dump.appendFormat(", inputFeatures=0x%08x", windowInfo->inputFeatures);
+ dump.appendFormat(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+ windowInfo->ownerPid, windowInfo->ownerUid,
+ windowInfo->dispatchingTimeout / 1000000.0);
+ }
+ } else {
+ dump.append(INDENT "Windows: <none>\n");
+ }
+
+ if (!mMonitoringChannels.isEmpty()) {
+ dump.append(INDENT "MonitoringChannels:\n");
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ const sp<InputChannel>& channel = mMonitoringChannels[i];
+ dump.appendFormat(INDENT2 "%d: '%s'\n", i, channel->getName().string());
+ }
+ } else {
+ dump.append(INDENT "MonitoringChannels: <none>\n");
+ }
+
+ nsecs_t currentTime = now();
+
+ if (!mInboundQueue.isEmpty()) {
+ dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count());
+ for (EventEntry* entry = mInboundQueue.head; entry; entry = entry->next) {
+ dump.append(INDENT2);
+ entry->appendDescription(dump);
+ dump.appendFormat(", age=%0.1fms\n",
+ (currentTime - entry->eventTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT "InboundQueue: <empty>\n");
+ }
+
+ if (!mConnectionsByFd.isEmpty()) {
+ dump.append(INDENT "Connections:\n");
+ for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
+ const sp<Connection>& connection = mConnectionsByFd.valueAt(i);
+ dump.appendFormat(INDENT2 "%d: channelName='%s', windowName='%s', "
+ "status=%s, monitor=%s, inputPublisherBlocked=%s\n",
+ i, connection->getInputChannelName(), connection->getWindowName(),
+ connection->getStatusLabel(), toString(connection->monitor),
+ toString(connection->inputPublisherBlocked));
+
+ if (!connection->outboundQueue.isEmpty()) {
+ dump.appendFormat(INDENT3 "OutboundQueue: length=%u\n",
+ connection->outboundQueue.count());
+ for (DispatchEntry* entry = connection->outboundQueue.head; entry;
+ entry = entry->next) {
+ dump.append(INDENT4);
+ entry->eventEntry->appendDescription(dump);
+ dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, age=%0.1fms\n",
+ entry->targetFlags, entry->resolvedAction,
+ (currentTime - entry->eventEntry->eventTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT3 "OutboundQueue: <empty>\n");
+ }
+
+ if (!connection->waitQueue.isEmpty()) {
+ dump.appendFormat(INDENT3 "WaitQueue: length=%u\n",
+ connection->waitQueue.count());
+ for (DispatchEntry* entry = connection->waitQueue.head; entry;
+ entry = entry->next) {
+ dump.append(INDENT4);
+ entry->eventEntry->appendDescription(dump);
+ dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, "
+ "age=%0.1fms, wait=%0.1fms\n",
+ entry->targetFlags, entry->resolvedAction,
+ (currentTime - entry->eventEntry->eventTime) * 0.000001f,
+ (currentTime - entry->deliveryTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT3 "WaitQueue: <empty>\n");
+ }
+ }
+ } else {
+ dump.append(INDENT "Connections: <none>\n");
+ }
+
+ if (isAppSwitchPendingLocked()) {
+ dump.appendFormat(INDENT "AppSwitch: pending, due in %0.1fms\n",
+ (mAppSwitchDueTime - now()) / 1000000.0);
+ } else {
+ dump.append(INDENT "AppSwitch: not pending\n");
+ }
+
+ dump.append(INDENT "Configuration:\n");
+ dump.appendFormat(INDENT2 "KeyRepeatDelay: %0.1fms\n",
+ mConfig.keyRepeatDelay * 0.000001f);
+ dump.appendFormat(INDENT2 "KeyRepeatTimeout: %0.1fms\n",
+ mConfig.keyRepeatTimeout * 0.000001f);
+}
+
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
+#if DEBUG_REGISTRATION
+ ALOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
+ toString(monitor));
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (getConnectionIndexLocked(inputChannel) >= 0) {
+ ALOGW("Attempted to register already registered input channel '%s'",
+ inputChannel->getName().string());
+ return BAD_VALUE;
+ }
+
+ sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
+
+ int fd = inputChannel->getFd();
+ mConnectionsByFd.add(fd, connection);
+
+ if (monitor) {
+ mMonitoringChannels.push(inputChannel);
+ }
+
+ mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
+ } // release lock
+
+ // Wake the looper because some connections have changed.
+ mLooper->wake();
+ return OK;
+}
+
+status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+#if DEBUG_REGISTRATION
+ ALOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string());
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ status_t status = unregisterInputChannelLocked(inputChannel, false /*notify*/);
+ if (status) {
+ return status;
+ }
+ } // release lock
+
+ // Wake the poll loop because removing the connection may have changed the current
+ // synchronization state.
+ mLooper->wake();
+ return OK;
+}
+
+status_t InputDispatcher::unregisterInputChannelLocked(const sp<InputChannel>& inputChannel,
+ bool notify) {
+ ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+ if (connectionIndex < 0) {
+ ALOGW("Attempted to unregister already unregistered input channel '%s'",
+ inputChannel->getName().string());
+ return BAD_VALUE;
+ }
+
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ mConnectionsByFd.removeItemsAt(connectionIndex);
+
+ if (connection->monitor) {
+ removeMonitorChannelLocked(inputChannel);
+ }
+
+ mLooper->removeFd(inputChannel->getFd());
+
+ nsecs_t currentTime = now();
+ abortBrokenDispatchCycleLocked(currentTime, connection, notify);
+
+ connection->status = Connection::STATUS_ZOMBIE;
+ return OK;
+}
+
+void InputDispatcher::removeMonitorChannelLocked(const sp<InputChannel>& inputChannel) {
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ if (mMonitoringChannels[i] == inputChannel) {
+ mMonitoringChannels.removeAt(i);
+ break;
+ }
+ }
+}
+
+ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
+ ssize_t connectionIndex = mConnectionsByFd.indexOfKey(inputChannel->getFd());
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ if (connection->inputChannel.get() == inputChannel.get()) {
+ return connectionIndex;
+ }
+ }
+
+ return -1;
+}
+
+void InputDispatcher::onDispatchCycleFinishedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
+ commandEntry->connection = connection;
+ commandEntry->eventTime = currentTime;
+ commandEntry->seq = seq;
+ commandEntry->handled = handled;
+}
+
+void InputDispatcher::onDispatchCycleBrokenLocked(
+ nsecs_t currentTime, const sp<Connection>& connection) {
+ ALOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
+ connection->getInputChannelName());
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
+ commandEntry->connection = connection;
+}
+
+void InputDispatcher::onANRLocked(
+ nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) {
+ float dispatchLatency = (currentTime - eventTime) * 0.000001f;
+ float waitDuration = (currentTime - waitStartTime) * 0.000001f;
+ ALOGI("Application is not responding: %s. "
+ "It has been %0.1fms since event, %0.1fms since wait started. Reason: %s",
+ getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
+ dispatchLatency, waitDuration, reason);
+
+ // Capture a record of the InputDispatcher state at the time of the ANR.
+ time_t t = time(NULL);
+ struct tm tm;
+ localtime_r(&t, &tm);
+ char timestr[64];
+ strftime(timestr, sizeof(timestr), "%F %T", &tm);
+ mLastANRState.clear();
+ mLastANRState.append(INDENT "ANR:\n");
+ mLastANRState.appendFormat(INDENT2 "Time: %s\n", timestr);
+ mLastANRState.appendFormat(INDENT2 "Window: %s\n",
+ getApplicationWindowLabelLocked(applicationHandle, windowHandle).string());
+ mLastANRState.appendFormat(INDENT2 "DispatchLatency: %0.1fms\n", dispatchLatency);
+ mLastANRState.appendFormat(INDENT2 "WaitDuration: %0.1fms\n", waitDuration);
+ mLastANRState.appendFormat(INDENT2 "Reason: %s\n", reason);
+ dumpDispatchStateLocked(mLastANRState);
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyANRLockedInterruptible);
+ commandEntry->inputApplicationHandle = applicationHandle;
+ commandEntry->inputWindowHandle = windowHandle;
+}
+
+void InputDispatcher::doNotifyConfigurationChangedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
+
+ mLock.lock();
+}
+
+void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
+ CommandEntry* commandEntry) {
+ sp<Connection> connection = commandEntry->connection;
+
+ if (connection->status != Connection::STATUS_ZOMBIE) {
+ mLock.unlock();
+
+ mPolicy->notifyInputChannelBroken(connection->inputWindowHandle);
+
+ mLock.lock();
+ }
+}
+
+void InputDispatcher::doNotifyANRLockedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ nsecs_t newTimeout = mPolicy->notifyANR(
+ commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle);
+
+ mLock.lock();
+
+ resumeAfterTargetsNotReadyTimeoutLocked(newTimeout,
+ commandEntry->inputWindowHandle != NULL
+ ? commandEntry->inputWindowHandle->getInputChannel() : NULL);
+}
+
+void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
+ CommandEntry* commandEntry) {
+ KeyEntry* entry = commandEntry->keyEntry;
+
+ KeyEvent event;
+ initializeKeyEvent(&event, entry);
+
+ mLock.unlock();
+
+ nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
+ &event, entry->policyFlags);
+
+ mLock.lock();
+
+ if (delay < 0) {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
+ } else if (!delay) {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ } else {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
+ entry->interceptKeyWakeupTime = now() + delay;
+ }
+ entry->release();
+}
+
+void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
+ CommandEntry* commandEntry) {
+ sp<Connection> connection = commandEntry->connection;
+ nsecs_t finishTime = commandEntry->eventTime;
+ uint32_t seq = commandEntry->seq;
+ bool handled = commandEntry->handled;
+
+ // Handle post-event policy actions.
+ DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);
+ if (dispatchEntry) {
+ nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
+ if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
+ String8 msg;
+ msg.appendFormat("Window '%s' spent %0.1fms processing the last input event: ",
+ connection->getWindowName(), eventDuration * 0.000001f);
+ dispatchEntry->eventEntry->appendDescription(msg);
+ ALOGI("%s", msg.string());
+ }
+
+ bool restartEvent;
+ if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
+ restartEvent = afterKeyEventLockedInterruptible(connection,
+ dispatchEntry, keyEntry, handled);
+ } else if (dispatchEntry->eventEntry->type == EventEntry::TYPE_MOTION) {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry);
+ restartEvent = afterMotionEventLockedInterruptible(connection,
+ dispatchEntry, motionEntry, handled);
+ } else {
+ restartEvent = false;
+ }
+
+ // Dequeue the event and start the next cycle.
+ // Note that because the lock might have been released, it is possible that the
+ // contents of the wait queue to have been drained, so we need to double-check
+ // a few things.
+ if (dispatchEntry == connection->findWaitQueueEntry(seq)) {
+ connection->waitQueue.dequeue(dispatchEntry);
+ traceWaitQueueLengthLocked(connection);
+ if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
+ connection->outboundQueue.enqueueAtHead(dispatchEntry);
+ traceOutboundQueueLengthLocked(connection);
+ } else {
+ releaseDispatchEntryLocked(dispatchEntry);
+ }
+ }
+
+ // Start the next dispatch cycle for this connection.
+ startDispatchCycleLocked(now(), connection);
+ }
+}
+
+bool InputDispatcher::afterKeyEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, KeyEntry* keyEntry, bool handled) {
+ if (!(keyEntry->flags & AKEY_EVENT_FLAG_FALLBACK)) {
+ // Get the fallback key state.
+ // Clear it out after dispatching the UP.
+ int32_t originalKeyCode = keyEntry->keyCode;
+ int32_t fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode);
+ if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
+ connection->inputState.removeFallbackKey(originalKeyCode);
+ }
+
+ if (handled || !dispatchEntry->hasForegroundTarget()) {
+ // If the application handles the original key for which we previously
+ // generated a fallback or if the window is not a foreground window,
+ // then cancel the associated fallback key, if any.
+ if (fallbackKeyCode != -1) {
+ // Dispatch the unhandled key to the policy with the cancel flag.
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Asking policy to cancel fallback action. "
+ "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+ keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount,
+ keyEntry->policyFlags);
+#endif
+ KeyEvent event;
+ initializeKeyEvent(&event, keyEntry);
+ event.setFlags(event.getFlags() | AKEY_EVENT_FLAG_CANCELED);
+
+ mLock.unlock();
+
+ mPolicy->dispatchUnhandledKey(connection->inputWindowHandle,
+ &event, keyEntry->policyFlags, &event);
+
+ mLock.lock();
+
+ // Cancel the fallback key.
+ if (fallbackKeyCode != AKEYCODE_UNKNOWN) {
+ CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+ "application handled the original non-fallback key "
+ "or is no longer a foreground target, "
+ "canceling previously dispatched fallback key");
+ options.keyCode = fallbackKeyCode;
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
+ }
+ connection->inputState.removeFallbackKey(originalKeyCode);
+ }
+ } else {
+ // If the application did not handle a non-fallback key, first check
+ // that we are in a good state to perform unhandled key event processing
+ // Then ask the policy what to do with it.
+ bool initialDown = keyEntry->action == AKEY_EVENT_ACTION_DOWN
+ && keyEntry->repeatCount == 0;
+ if (fallbackKeyCode == -1 && !initialDown) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Skipping unhandled key event processing "
+ "since this is not an initial down. "
+ "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+ originalKeyCode, keyEntry->action, keyEntry->repeatCount,
+ keyEntry->policyFlags);
+#endif
+ return false;
+ }
+
+ // Dispatch the unhandled key to the policy.
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Asking policy to perform fallback action. "
+ "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+ keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount,
+ keyEntry->policyFlags);
+#endif
+ KeyEvent event;
+ initializeKeyEvent(&event, keyEntry);
+
+ mLock.unlock();
+
+ bool fallback = mPolicy->dispatchUnhandledKey(connection->inputWindowHandle,
+ &event, keyEntry->policyFlags, &event);
+
+ mLock.lock();
+
+ if (connection->status != Connection::STATUS_NORMAL) {
+ connection->inputState.removeFallbackKey(originalKeyCode);
+ return false;
+ }
+
+ // Latch the fallback keycode for this key on an initial down.
+ // The fallback keycode cannot change at any other point in the lifecycle.
+ if (initialDown) {
+ if (fallback) {
+ fallbackKeyCode = event.getKeyCode();
+ } else {
+ fallbackKeyCode = AKEYCODE_UNKNOWN;
+ }
+ connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode);
+ }
+
+ ALOG_ASSERT(fallbackKeyCode != -1);
+
+ // Cancel the fallback key if the policy decides not to send it anymore.
+ // We will continue to dispatch the key to the policy but we will no
+ // longer dispatch a fallback key to the application.
+ if (fallbackKeyCode != AKEYCODE_UNKNOWN
+ && (!fallback || fallbackKeyCode != event.getKeyCode())) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ if (fallback) {
+ ALOGD("Unhandled key event: Policy requested to send key %d"
+ "as a fallback for %d, but on the DOWN it had requested "
+ "to send %d instead. Fallback canceled.",
+ event.getKeyCode(), originalKeyCode, fallbackKeyCode);
+ } else {
+ ALOGD("Unhandled key event: Policy did not request fallback for %d, "
+ "but on the DOWN it had requested to send %d. "
+ "Fallback canceled.",
+ originalKeyCode, fallbackKeyCode);
+ }
+#endif
+
+ CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+ "canceling fallback, policy no longer desires it");
+ options.keyCode = fallbackKeyCode;
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
+
+ fallback = false;
+ fallbackKeyCode = AKEYCODE_UNKNOWN;
+ if (keyEntry->action != AKEY_EVENT_ACTION_UP) {
+ connection->inputState.setFallbackKey(originalKeyCode,
+ fallbackKeyCode);
+ }
+ }
+
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ {
+ String8 msg;
+ const KeyedVector<int32_t, int32_t>& fallbackKeys =
+ connection->inputState.getFallbackKeys();
+ for (size_t i = 0; i < fallbackKeys.size(); i++) {
+ msg.appendFormat(", %d->%d", fallbackKeys.keyAt(i),
+ fallbackKeys.valueAt(i));
+ }
+ ALOGD("Unhandled key event: %d currently tracked fallback keys%s.",
+ fallbackKeys.size(), msg.string());
+ }
+#endif
+
+ if (fallback) {
+ // Restart the dispatch cycle using the fallback key.
+ keyEntry->eventTime = event.getEventTime();
+ keyEntry->deviceId = event.getDeviceId();
+ keyEntry->source = event.getSource();
+ keyEntry->flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK;
+ keyEntry->keyCode = fallbackKeyCode;
+ keyEntry->scanCode = event.getScanCode();
+ keyEntry->metaState = event.getMetaState();
+ keyEntry->repeatCount = event.getRepeatCount();
+ keyEntry->downTime = event.getDownTime();
+ keyEntry->syntheticRepeat = false;
+
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Dispatching fallback key. "
+ "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
+ originalKeyCode, fallbackKeyCode, keyEntry->metaState);
+#endif
+ return true; // restart the event
+ } else {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: No fallback key.");
+#endif
+ }
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::afterMotionEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, MotionEntry* motionEntry, bool handled) {
+ return false;
+}
+
+void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType);
+
+ mLock.lock();
+}
+
+void InputDispatcher::initializeKeyEvent(KeyEvent* event, const KeyEntry* entry) {
+ event->initialize(entry->deviceId, entry->source, entry->action, entry->flags,
+ entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+ entry->downTime, entry->eventTime);
+}
+
+void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+ int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) {
+ // TODO Write some statistics about how long we spend waiting.
+}
+
+void InputDispatcher::traceInboundQueueLengthLocked() {
+#ifdef HAVE_ANDROID_OS
+ if (ATRACE_ENABLED()) {
+ ATRACE_INT("iq", mInboundQueue.count());
+ }
+#endif
+}
+
+void InputDispatcher::traceOutboundQueueLengthLocked(const sp<Connection>& connection) {
+#ifdef HAVE_ANDROID_OS
+ if (ATRACE_ENABLED()) {
+ char counterName[40];
+ snprintf(counterName, sizeof(counterName), "oq:%s", connection->getWindowName());
+ ATRACE_INT(counterName, connection->outboundQueue.count());
+ }
+#endif
+}
+
+void InputDispatcher::traceWaitQueueLengthLocked(const sp<Connection>& connection) {
+#ifdef HAVE_ANDROID_OS
+ if (ATRACE_ENABLED()) {
+ char counterName[40];
+ snprintf(counterName, sizeof(counterName), "wq:%s", connection->getWindowName());
+ ATRACE_INT(counterName, connection->waitQueue.count());
+ }
+#endif
+}
+
+void InputDispatcher::dump(String8& dump) {
+ AutoMutex _l(mLock);
+
+ dump.append("Input Dispatcher State:\n");
+ dumpDispatchStateLocked(dump);
+
+ if (!mLastANRState.isEmpty()) {
+ dump.append("\nInput Dispatcher State at time of last ANR:\n");
+ dump.append(mLastANRState);
+ }
+}
+
+void InputDispatcher::monitor() {
+ // Acquire and release the lock to ensure that the dispatcher has not deadlocked.
+ mLock.lock();
+ mLooper->wake();
+ mDispatcherIsAliveCondition.wait(mLock);
+ mLock.unlock();
+}
+
+
+// --- InputDispatcher::Queue ---
+
+template <typename T>
+uint32_t InputDispatcher::Queue<T>::count() const {
+ uint32_t result = 0;
+ for (const T* entry = head; entry; entry = entry->next) {
+ result += 1;
+ }
+ return result;
+}
+
+
+// --- InputDispatcher::InjectionState ---
+
+InputDispatcher::InjectionState::InjectionState(int32_t injectorPid, int32_t injectorUid) :
+ refCount(1),
+ injectorPid(injectorPid), injectorUid(injectorUid),
+ injectionResult(INPUT_EVENT_INJECTION_PENDING), injectionIsAsync(false),
+ pendingForegroundDispatches(0) {
+}
+
+InputDispatcher::InjectionState::~InjectionState() {
+}
+
+void InputDispatcher::InjectionState::release() {
+ refCount -= 1;
+ if (refCount == 0) {
+ delete this;
+ } else {
+ ALOG_ASSERT(refCount > 0);
+ }
+}
+
+
+// --- InputDispatcher::EventEntry ---
+
+InputDispatcher::EventEntry::EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags) :
+ refCount(1), type(type), eventTime(eventTime), policyFlags(policyFlags),
+ injectionState(NULL), dispatchInProgress(false) {
+}
+
+InputDispatcher::EventEntry::~EventEntry() {
+ releaseInjectionState();
+}
+
+void InputDispatcher::EventEntry::release() {
+ refCount -= 1;
+ if (refCount == 0) {
+ delete this;
+ } else {
+ ALOG_ASSERT(refCount > 0);
+ }
+}
+
+void InputDispatcher::EventEntry::releaseInjectionState() {
+ if (injectionState) {
+ injectionState->release();
+ injectionState = NULL;
+ }
+}
+
+
+// --- InputDispatcher::ConfigurationChangedEntry ---
+
+InputDispatcher::ConfigurationChangedEntry::ConfigurationChangedEntry(nsecs_t eventTime) :
+ EventEntry(TYPE_CONFIGURATION_CHANGED, eventTime, 0) {
+}
+
+InputDispatcher::ConfigurationChangedEntry::~ConfigurationChangedEntry() {
+}
+
+void InputDispatcher::ConfigurationChangedEntry::appendDescription(String8& msg) const {
+ msg.append("ConfigurationChangedEvent()");
+}
+
+
+// --- InputDispatcher::DeviceResetEntry ---
+
+InputDispatcher::DeviceResetEntry::DeviceResetEntry(nsecs_t eventTime, int32_t deviceId) :
+ EventEntry(TYPE_DEVICE_RESET, eventTime, 0),
+ deviceId(deviceId) {
+}
+
+InputDispatcher::DeviceResetEntry::~DeviceResetEntry() {
+}
+
+void InputDispatcher::DeviceResetEntry::appendDescription(String8& msg) const {
+ msg.appendFormat("DeviceResetEvent(deviceId=%d)", deviceId);
+}
+
+
+// --- InputDispatcher::KeyEntry ---
+
+InputDispatcher::KeyEntry::KeyEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+ int32_t repeatCount, nsecs_t downTime) :
+ EventEntry(TYPE_KEY, eventTime, policyFlags),
+ deviceId(deviceId), source(source), action(action), flags(flags),
+ keyCode(keyCode), scanCode(scanCode), metaState(metaState),
+ repeatCount(repeatCount), downTime(downTime),
+ syntheticRepeat(false), interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN),
+ interceptKeyWakeupTime(0) {
+}
+
+InputDispatcher::KeyEntry::~KeyEntry() {
+}
+
+void InputDispatcher::KeyEntry::appendDescription(String8& msg) const {
+ msg.appendFormat("KeyEvent(action=%d, deviceId=%d, source=0x%08x)",
+ action, deviceId, source);
+}
+
+void InputDispatcher::KeyEntry::recycle() {
+ releaseInjectionState();
+
+ dispatchInProgress = false;
+ syntheticRepeat = false;
+ interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+ interceptKeyWakeupTime = 0;
+}
+
+
+// --- InputDispatcher::MotionEntry ---
+
+InputDispatcher::MotionEntry::MotionEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags, float xPrecision, float yPrecision,
+ nsecs_t downTime, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) :
+ EventEntry(TYPE_MOTION, eventTime, policyFlags),
+ eventTime(eventTime),
+ deviceId(deviceId), source(source), action(action), flags(flags),
+ metaState(metaState), buttonState(buttonState), edgeFlags(edgeFlags),
+ xPrecision(xPrecision), yPrecision(yPrecision),
+ downTime(downTime), displayId(displayId), pointerCount(pointerCount) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ this->pointerProperties[i].copyFrom(pointerProperties[i]);
+ this->pointerCoords[i].copyFrom(pointerCoords[i]);
+ }
+}
+
+InputDispatcher::MotionEntry::~MotionEntry() {
+}
+
+void InputDispatcher::MotionEntry::appendDescription(String8& msg) const {
+ msg.appendFormat("MotionEvent(action=%d, deviceId=%d, source=0x%08x, displayId=%d)",
+ action, deviceId, source, displayId);
+}
+
+
+// --- InputDispatcher::DispatchEntry ---
+
+volatile int32_t InputDispatcher::DispatchEntry::sNextSeqAtomic;
+
+InputDispatcher::DispatchEntry::DispatchEntry(EventEntry* eventEntry,
+ int32_t targetFlags, float xOffset, float yOffset, float scaleFactor) :
+ seq(nextSeq()),
+ eventEntry(eventEntry), targetFlags(targetFlags),
+ xOffset(xOffset), yOffset(yOffset), scaleFactor(scaleFactor),
+ deliveryTime(0), resolvedAction(0), resolvedFlags(0) {
+ eventEntry->refCount += 1;
+}
+
+InputDispatcher::DispatchEntry::~DispatchEntry() {
+ eventEntry->release();
+}
+
+uint32_t InputDispatcher::DispatchEntry::nextSeq() {
+ // Sequence number 0 is reserved and will never be returned.
+ uint32_t seq;
+ do {
+ seq = android_atomic_inc(&sNextSeqAtomic);
+ } while (!seq);
+ return seq;
+}
+
+
+// --- InputDispatcher::InputState ---
+
+InputDispatcher::InputState::InputState() {
+}
+
+InputDispatcher::InputState::~InputState() {
+}
+
+bool InputDispatcher::InputState::isNeutral() const {
+ return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
+}
+
+bool InputDispatcher::InputState::isHovering(int32_t deviceId, uint32_t source,
+ int32_t displayId) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.deviceId == deviceId
+ && memento.source == source
+ && memento.displayId == displayId
+ && memento.hovering) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::InputState::trackKey(const KeyEntry* entry,
+ int32_t action, int32_t flags) {
+ switch (action) {
+ case AKEY_EVENT_ACTION_UP: {
+ if (entry->flags & AKEY_EVENT_FLAG_FALLBACK) {
+ for (size_t i = 0; i < mFallbackKeys.size(); ) {
+ if (mFallbackKeys.valueAt(i) == entry->keyCode) {
+ mFallbackKeys.removeItemsAt(i);
+ } else {
+ i += 1;
+ }
+ }
+ }
+ ssize_t index = findKeyMemento(entry);
+ if (index >= 0) {
+ mKeyMementos.removeAt(index);
+ return true;
+ }
+ /* FIXME: We can't just drop the key up event because that prevents creating
+ * popup windows that are automatically shown when a key is held and then
+ * dismissed when the key is released. The problem is that the popup will
+ * not have received the original key down, so the key up will be considered
+ * to be inconsistent with its observed state. We could perhaps handle this
+ * by synthesizing a key down but that will cause other problems.
+ *
+ * So for now, allow inconsistent key up events to be dispatched.
+ *
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, "
+ "keyCode=%d, scanCode=%d",
+ entry->deviceId, entry->source, entry->keyCode, entry->scanCode);
+#endif
+ return false;
+ */
+ return true;
+ }
+
+ case AKEY_EVENT_ACTION_DOWN: {
+ ssize_t index = findKeyMemento(entry);
+ if (index >= 0) {
+ mKeyMementos.removeAt(index);
+ }
+ addKeyMemento(entry, flags);
+ return true;
+ }
+
+ default:
+ return true;
+ }
+}
+
+bool InputDispatcher::InputState::trackMotion(const MotionEntry* entry,
+ int32_t action, int32_t flags) {
+ int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+ switch (actionMasked) {
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent motion up or cancel event: deviceId=%d, source=%08x, "
+ "actionMasked=%d",
+ entry->deviceId, entry->source, actionMasked);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_DOWN: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ }
+ addMotionMemento(entry, flags, false /*hovering*/);
+ return true;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_MOVE: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ MotionMemento& memento = mMotionMementos.editItemAt(index);
+ memento.setPointers(entry);
+ return true;
+ }
+ if (actionMasked == AMOTION_EVENT_ACTION_MOVE
+ && (entry->source & (AINPUT_SOURCE_CLASS_JOYSTICK
+ | AINPUT_SOURCE_CLASS_NAVIGATION))) {
+ // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP.
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent motion pointer up/down or move event: "
+ "deviceId=%d, source=%08x, actionMasked=%d",
+ entry->deviceId, entry->source, actionMasked);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+ ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent motion hover exit event: deviceId=%d, source=%08x",
+ entry->deviceId, entry->source);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE: {
+ ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ }
+ addMotionMemento(entry, flags, true /*hovering*/);
+ return true;
+ }
+
+ default:
+ return true;
+ }
+}
+
+ssize_t InputDispatcher::InputState::findKeyMemento(const KeyEntry* entry) const {
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
+ const KeyMemento& memento = mKeyMementos.itemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source
+ && memento.keyCode == entry->keyCode
+ && memento.scanCode == entry->scanCode) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+ssize_t InputDispatcher::InputState::findMotionMemento(const MotionEntry* entry,
+ bool hovering) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source
+ && memento.displayId == entry->displayId
+ && memento.hovering == hovering) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t flags) {
+ mKeyMementos.push();
+ KeyMemento& memento = mKeyMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.keyCode = entry->keyCode;
+ memento.scanCode = entry->scanCode;
+ memento.metaState = entry->metaState;
+ memento.flags = flags;
+ memento.downTime = entry->downTime;
+ memento.policyFlags = entry->policyFlags;
+}
+
+void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry,
+ int32_t flags, bool hovering) {
+ mMotionMementos.push();
+ MotionMemento& memento = mMotionMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.flags = flags;
+ memento.xPrecision = entry->xPrecision;
+ memento.yPrecision = entry->yPrecision;
+ memento.downTime = entry->downTime;
+ memento.displayId = entry->displayId;
+ memento.setPointers(entry);
+ memento.hovering = hovering;
+ memento.policyFlags = entry->policyFlags;
+}
+
+void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
+ pointerCount = entry->pointerCount;
+ for (uint32_t i = 0; i < entry->pointerCount; i++) {
+ pointerProperties[i].copyFrom(entry->pointerProperties[i]);
+ pointerCoords[i].copyFrom(entry->pointerCoords[i]);
+ }
+}
+
+void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime,
+ Vector<EventEntry*>& outEvents, const CancelationOptions& options) {
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
+ const KeyMemento& memento = mKeyMementos.itemAt(i);
+ if (shouldCancelKey(memento, options)) {
+ outEvents.push(new KeyEntry(currentTime,
+ memento.deviceId, memento.source, memento.policyFlags,
+ AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
+ memento.keyCode, memento.scanCode, memento.metaState, 0, memento.downTime));
+ }
+ }
+
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (shouldCancelMotion(memento, options)) {
+ outEvents.push(new MotionEntry(currentTime,
+ memento.deviceId, memento.source, memento.policyFlags,
+ memento.hovering
+ ? AMOTION_EVENT_ACTION_HOVER_EXIT
+ : AMOTION_EVENT_ACTION_CANCEL,
+ memento.flags, 0, 0, 0,
+ memento.xPrecision, memento.yPrecision, memento.downTime,
+ memento.displayId,
+ memento.pointerCount, memento.pointerProperties, memento.pointerCoords));
+ }
+ }
+}
+
+void InputDispatcher::InputState::clear() {
+ mKeyMementos.clear();
+ mMotionMementos.clear();
+ mFallbackKeys.clear();
+}
+
+void InputDispatcher::InputState::copyPointerStateTo(InputState& other) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
+ for (size_t j = 0; j < other.mMotionMementos.size(); ) {
+ const MotionMemento& otherMemento = other.mMotionMementos.itemAt(j);
+ if (memento.deviceId == otherMemento.deviceId
+ && memento.source == otherMemento.source
+ && memento.displayId == otherMemento.displayId) {
+ other.mMotionMementos.removeAt(j);
+ } else {
+ j += 1;
+ }
+ }
+ other.mMotionMementos.push(memento);
+ }
+ }
+}
+
+int32_t InputDispatcher::InputState::getFallbackKey(int32_t originalKeyCode) {
+ ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
+ return index >= 0 ? mFallbackKeys.valueAt(index) : -1;
+}
+
+void InputDispatcher::InputState::setFallbackKey(int32_t originalKeyCode,
+ int32_t fallbackKeyCode) {
+ ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
+ if (index >= 0) {
+ mFallbackKeys.replaceValueAt(index, fallbackKeyCode);
+ } else {
+ mFallbackKeys.add(originalKeyCode, fallbackKeyCode);
+ }
+}
+
+void InputDispatcher::InputState::removeFallbackKey(int32_t originalKeyCode) {
+ mFallbackKeys.removeItem(originalKeyCode);
+}
+
+bool InputDispatcher::InputState::shouldCancelKey(const KeyMemento& memento,
+ const CancelationOptions& options) {
+ if (options.keyCode != -1 && memento.keyCode != options.keyCode) {
+ return false;
+ }
+
+ if (options.deviceId != -1 && memento.deviceId != options.deviceId) {
+ return false;
+ }
+
+ switch (options.mode) {
+ case CancelationOptions::CANCEL_ALL_EVENTS:
+ case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+ return true;
+ case CancelationOptions::CANCEL_FALLBACK_EVENTS:
+ return memento.flags & AKEY_EVENT_FLAG_FALLBACK;
+ default:
+ return false;
+ }
+}
+
+bool InputDispatcher::InputState::shouldCancelMotion(const MotionMemento& memento,
+ const CancelationOptions& options) {
+ if (options.deviceId != -1 && memento.deviceId != options.deviceId) {
+ return false;
+ }
+
+ switch (options.mode) {
+ case CancelationOptions::CANCEL_ALL_EVENTS:
+ return true;
+ case CancelationOptions::CANCEL_POINTER_EVENTS:
+ return memento.source & AINPUT_SOURCE_CLASS_POINTER;
+ case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+ return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
+ default:
+ return false;
+ }
+}
+
+
+// --- InputDispatcher::Connection ---
+
+InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) :
+ status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle),
+ monitor(monitor),
+ inputPublisher(inputChannel), inputPublisherBlocked(false) {
+}
+
+InputDispatcher::Connection::~Connection() {
+}
+
+const char* InputDispatcher::Connection::getWindowName() const {
+ if (inputWindowHandle != NULL) {
+ return inputWindowHandle->getName().string();
+ }
+ if (monitor) {
+ return "monitor";
+ }
+ return "?";
+}
+
+const char* InputDispatcher::Connection::getStatusLabel() const {
+ switch (status) {
+ case STATUS_NORMAL:
+ return "NORMAL";
+
+ case STATUS_BROKEN:
+ return "BROKEN";
+
+ case STATUS_ZOMBIE:
+ return "ZOMBIE";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+InputDispatcher::DispatchEntry* InputDispatcher::Connection::findWaitQueueEntry(uint32_t seq) {
+ for (DispatchEntry* entry = waitQueue.head; entry != NULL; entry = entry->next) {
+ if (entry->seq == seq) {
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+
+// --- InputDispatcher::CommandEntry ---
+
+InputDispatcher::CommandEntry::CommandEntry(Command command) :
+ command(command), eventTime(0), keyEntry(NULL), userActivityEventType(0),
+ seq(0), handled(false) {
+}
+
+InputDispatcher::CommandEntry::~CommandEntry() {
+}
+
+
+// --- InputDispatcher::TouchState ---
+
+InputDispatcher::TouchState::TouchState() :
+ down(false), split(false), deviceId(-1), source(0), displayId(-1) {
+}
+
+InputDispatcher::TouchState::~TouchState() {
+}
+
+void InputDispatcher::TouchState::reset() {
+ down = false;
+ split = false;
+ deviceId = -1;
+ source = 0;
+ displayId = -1;
+ windows.clear();
+}
+
+void InputDispatcher::TouchState::copyFrom(const TouchState& other) {
+ down = other.down;
+ split = other.split;
+ deviceId = other.deviceId;
+ source = other.source;
+ displayId = other.displayId;
+ windows = other.windows;
+}
+
+void InputDispatcher::TouchState::addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds) {
+ if (targetFlags & InputTarget::FLAG_SPLIT) {
+ split = true;
+ }
+
+ for (size_t i = 0; i < windows.size(); i++) {
+ TouchedWindow& touchedWindow = windows.editItemAt(i);
+ if (touchedWindow.windowHandle == windowHandle) {
+ touchedWindow.targetFlags |= targetFlags;
+ if (targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+ touchedWindow.targetFlags &= ~InputTarget::FLAG_DISPATCH_AS_IS;
+ }
+ touchedWindow.pointerIds.value |= pointerIds.value;
+ return;
+ }
+ }
+
+ windows.push();
+
+ TouchedWindow& touchedWindow = windows.editTop();
+ touchedWindow.windowHandle = windowHandle;
+ touchedWindow.targetFlags = targetFlags;
+ touchedWindow.pointerIds = pointerIds;
+}
+
+void InputDispatcher::TouchState::removeWindow(const sp<InputWindowHandle>& windowHandle) {
+ for (size_t i = 0; i < windows.size(); i++) {
+ if (windows.itemAt(i).windowHandle == windowHandle) {
+ windows.removeAt(i);
+ return;
+ }
+ }
+}
+
+void InputDispatcher::TouchState::filterNonAsIsTouchWindows() {
+ for (size_t i = 0 ; i < windows.size(); ) {
+ TouchedWindow& window = windows.editItemAt(i);
+ if (window.targetFlags & (InputTarget::FLAG_DISPATCH_AS_IS
+ | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER)) {
+ window.targetFlags &= ~InputTarget::FLAG_DISPATCH_MASK;
+ window.targetFlags |= InputTarget::FLAG_DISPATCH_AS_IS;
+ i += 1;
+ } else {
+ windows.removeAt(i);
+ }
+ }
+}
+
+sp<InputWindowHandle> InputDispatcher::TouchState::getFirstForegroundWindowHandle() const {
+ for (size_t i = 0; i < windows.size(); i++) {
+ const TouchedWindow& window = windows.itemAt(i);
+ if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ return window.windowHandle;
+ }
+ }
+ return NULL;
+}
+
+bool InputDispatcher::TouchState::isSlippery() const {
+ // Must have exactly one foreground window.
+ bool haveSlipperyForegroundWindow = false;
+ for (size_t i = 0; i < windows.size(); i++) {
+ const TouchedWindow& window = windows.itemAt(i);
+ if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ if (haveSlipperyForegroundWindow
+ || !(window.windowHandle->getInfo()->layoutParamsFlags
+ & InputWindowInfo::FLAG_SLIPPERY)) {
+ return false;
+ }
+ haveSlipperyForegroundWindow = true;
+ }
+ }
+ return haveSlipperyForegroundWindow;
+}
+
+
+// --- InputDispatcherThread ---
+
+InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
+ Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
+}
+
+InputDispatcherThread::~InputDispatcherThread() {
+}
+
+bool InputDispatcherThread::threadLoop() {
+ mDispatcher->dispatchOnce();
+ return true;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputDispatcher.h b/widget/gonk/libui/InputDispatcher.h
new file mode 100644
index 0000000000..5453421f62
--- /dev/null
+++ b/widget/gonk/libui/InputDispatcher.h
@@ -0,0 +1,1117 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _UI_INPUT_DISPATCHER_H
+#define _UI_INPUT_DISPATCHER_H
+
+#include "Input.h"
+#include "InputTransport.h"
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Looper.h>
+#include <utils/BitSet.h>
+#include <cutils/atomic.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "InputWindow.h"
+#include "InputApplication.h"
+#include "InputListener.h"
+
+
+namespace android {
+
+/*
+ * Constants used to report the outcome of input event injection.
+ */
+enum {
+ /* (INTERNAL USE ONLY) Specifies that injection is pending and its outcome is unknown. */
+ INPUT_EVENT_INJECTION_PENDING = -1,
+
+ /* Injection succeeded. */
+ INPUT_EVENT_INJECTION_SUCCEEDED = 0,
+
+ /* Injection failed because the injector did not have permission to inject
+ * into the application with input focus. */
+ INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1,
+
+ /* Injection failed because there were no available input targets. */
+ INPUT_EVENT_INJECTION_FAILED = 2,
+
+ /* Injection failed due to a timeout. */
+ INPUT_EVENT_INJECTION_TIMED_OUT = 3
+};
+
+/*
+ * Constants used to determine the input event injection synchronization mode.
+ */
+enum {
+ /* Injection is asynchronous and is assumed always to be successful. */
+ INPUT_EVENT_INJECTION_SYNC_NONE = 0,
+
+ /* Waits for previous events to be dispatched so that the input dispatcher can determine
+ * whether input event injection willbe permitted based on the current input focus.
+ * Does not wait for the input event to finish processing. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1,
+
+ /* Waits for the input event to be completely processed. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED = 2,
+};
+
+
+/*
+ * An input target specifies how an input event is to be dispatched to a particular window
+ * including the window's input channel, control flags, a timeout, and an X / Y offset to
+ * be added to input event coordinates to compensate for the absolute position of the
+ * window area.
+ */
+struct InputTarget {
+ enum {
+ /* This flag indicates that the event is being delivered to a foreground application. */
+ FLAG_FOREGROUND = 1 << 0,
+
+ /* This flag indicates that the target of a MotionEvent is partly or wholly
+ * obscured by another visible window above it. The motion event should be
+ * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+ FLAG_WINDOW_IS_OBSCURED = 1 << 1,
+
+ /* This flag indicates that a motion event is being split across multiple windows. */
+ FLAG_SPLIT = 1 << 2,
+
+ /* This flag indicates that the pointer coordinates dispatched to the application
+ * will be zeroed out to avoid revealing information to an application. This is
+ * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
+ * the same UID from watching all touches. */
+ FLAG_ZERO_COORDS = 1 << 3,
+
+ /* This flag indicates that the event should be sent as is.
+ * Should always be set unless the event is to be transmuted. */
+ FLAG_DISPATCH_AS_IS = 1 << 8,
+
+ /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
+ * of the area of this target and so should instead be delivered as an
+ * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
+ FLAG_DISPATCH_AS_OUTSIDE = 1 << 9,
+
+ /* This flag indicates that a hover sequence is starting in the given window.
+ * The event is transmuted into ACTION_HOVER_ENTER. */
+ FLAG_DISPATCH_AS_HOVER_ENTER = 1 << 10,
+
+ /* This flag indicates that a hover event happened outside of a window which handled
+ * previous hover events, signifying the end of the current hover sequence for that
+ * window.
+ * The event is transmuted into ACTION_HOVER_ENTER. */
+ FLAG_DISPATCH_AS_HOVER_EXIT = 1 << 11,
+
+ /* This flag indicates that the event should be canceled.
+ * It is used to transmute ACTION_MOVE into ACTION_CANCEL when a touch slips
+ * outside of a window. */
+ FLAG_DISPATCH_AS_SLIPPERY_EXIT = 1 << 12,
+
+ /* This flag indicates that the event should be dispatched as an initial down.
+ * It is used to transmute ACTION_MOVE into ACTION_DOWN when a touch slips
+ * into a new window. */
+ FLAG_DISPATCH_AS_SLIPPERY_ENTER = 1 << 13,
+
+ /* Mask for all dispatch modes. */
+ FLAG_DISPATCH_MASK = FLAG_DISPATCH_AS_IS
+ | FLAG_DISPATCH_AS_OUTSIDE
+ | FLAG_DISPATCH_AS_HOVER_ENTER
+ | FLAG_DISPATCH_AS_HOVER_EXIT
+ | FLAG_DISPATCH_AS_SLIPPERY_EXIT
+ | FLAG_DISPATCH_AS_SLIPPERY_ENTER,
+ };
+
+ // The input channel to be targeted.
+ sp<InputChannel> inputChannel;
+
+ // Flags for the input target.
+ int32_t flags;
+
+ // The x and y offset to add to a MotionEvent as it is delivered.
+ // (ignored for KeyEvents)
+ float xOffset, yOffset;
+
+ // Scaling factor to apply to MotionEvent as it is delivered.
+ // (ignored for KeyEvents)
+ float scaleFactor;
+
+ // The subset of pointer ids to include in motion events dispatched to this input target
+ // if FLAG_SPLIT is set.
+ BitSet32 pointerIds;
+};
+
+
+/*
+ * Input dispatcher configuration.
+ *
+ * Specifies various options that modify the behavior of the input dispatcher.
+ * The values provided here are merely defaults. The actual values will come from ViewConfiguration
+ * and are passed into the dispatcher during initialization.
+ */
+struct InputDispatcherConfiguration {
+ // The key repeat initial timeout.
+ nsecs_t keyRepeatTimeout;
+
+ // The key repeat inter-key delay.
+ nsecs_t keyRepeatDelay;
+
+ InputDispatcherConfiguration() :
+ keyRepeatTimeout(500 * 1000000LL),
+ keyRepeatDelay(50 * 1000000LL) { }
+};
+
+
+/*
+ * Input dispatcher policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class InputDispatcherPolicyInterface : public virtual RefBase {
+protected:
+ InputDispatcherPolicyInterface() { }
+ virtual ~InputDispatcherPolicyInterface() { }
+
+public:
+ /* Notifies the system that a configuration change has occurred. */
+ virtual void notifyConfigurationChanged(nsecs_t when) = 0;
+
+ /* Notifies the system that an application is not responding.
+ * Returns a new timeout to continue waiting, or 0 to abort dispatch. */
+ virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+ const sp<InputWindowHandle>& inputWindowHandle) = 0;
+
+ /* Notifies the system that an input channel is unrecoverably broken. */
+ virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) = 0;
+
+ /* Gets the input dispatcher configuration. */
+ virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) = 0;
+
+ /* Returns true if automatic key repeating is enabled. */
+ virtual bool isKeyRepeatEnabled() = 0;
+
+ /* Filters an input event.
+ * Return true to dispatch the event unmodified, false to consume the event.
+ * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED
+ * to injectInputEvent.
+ */
+ virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0;
+
+ /* Intercepts a key event immediately before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0;
+
+ /* Intercepts a touch, trackball or other motion event before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0;
+
+ /* Allows the policy a chance to intercept a key before dispatching. */
+ virtual nsecs_t interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+
+ /* Allows the policy a chance to perform default processing for an unhandled key.
+ * Returns an alternate keycode to redispatch as a fallback, or 0 to give up. */
+ virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0;
+
+ /* Notifies the policy about switch events.
+ */
+ virtual void notifySwitch(nsecs_t when,
+ uint32_t switchValues, uint32_t switchMask, uint32_t policyFlags) = 0;
+
+ /* Poke user activity for an event dispatched to a window. */
+ virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0;
+
+ /* Checks whether a given application pid/uid has permission to inject input events
+ * into other applications.
+ *
+ * This method is special in that its implementation promises to be non-reentrant and
+ * is safe to call while holding other locks. (Most other methods make no such guarantees!)
+ */
+ virtual bool checkInjectEventsPermissionNonReentrant(
+ int32_t injectorPid, int32_t injectorUid) = 0;
+};
+
+
+/* Notifies the system about input events generated by the input reader.
+ * The dispatcher is expected to be mostly asynchronous. */
+class InputDispatcherInterface : public virtual RefBase, public InputListenerInterface {
+protected:
+ InputDispatcherInterface() { }
+ virtual ~InputDispatcherInterface() { }
+
+public:
+ /* Dumps the state of the input dispatcher.
+ *
+ * This method may be called on any thread (usually by the input manager). */
+ virtual void dump(String8& dump) = 0;
+
+ /* Called by the heatbeat to ensures that the dispatcher has not deadlocked. */
+ virtual void monitor() = 0;
+
+ /* Runs a single iteration of the dispatch loop.
+ * Nominally processes one queued event, a timeout, or a response from an input consumer.
+ *
+ * This method should only be called on the input dispatcher thread.
+ */
+ virtual void dispatchOnce() = 0;
+
+ /* Injects an input event and optionally waits for sync.
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
+ * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual int32_t injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags) = 0;
+
+ /* Sets the list of input windows.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) = 0;
+
+ /* Sets the focused application.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setFocusedApplication(
+ const sp<InputApplicationHandle>& inputApplicationHandle) = 0;
+
+ /* Sets the input dispatching mode.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setInputDispatchMode(bool enabled, bool frozen) = 0;
+
+ /* Sets whether input event filtering is enabled.
+ * When enabled, incoming input events are sent to the policy's filterInputEvent
+ * method instead of being dispatched. The filter is expected to use
+ * injectInputEvent to inject the events it would like to have dispatched.
+ * It should include POLICY_FLAG_FILTERED in the policy flags during injection.
+ */
+ virtual void setInputFilterEnabled(bool enabled) = 0;
+
+ /* Transfers touch focus from the window associated with one channel to the
+ * window associated with the other channel.
+ *
+ * Returns true on success. False if the window did not actually have touch focus.
+ */
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) = 0;
+
+ /* Registers or unregister input channels that may be used as targets for input events.
+ * If monitor is true, the channel will receive a copy of all input events.
+ *
+ * These methods may be called on any thread (usually by the input manager).
+ */
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) = 0;
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
+};
+
+/* Dispatches events to input targets. Some functions of the input dispatcher, such as
+ * identifying input targets, are controlled by a separate policy object.
+ *
+ * IMPORTANT INVARIANT:
+ * Because the policy can potentially block or cause re-entrance into the input dispatcher,
+ * the input dispatcher never calls into the policy while holding its internal locks.
+ * The implementation is also carefully designed to recover from scenarios such as an
+ * input channel becoming unregistered while identifying input targets or processing timeouts.
+ *
+ * Methods marked 'Locked' must be called with the lock acquired.
+ *
+ * Methods marked 'LockedInterruptible' must be called with the lock acquired but
+ * may during the course of their execution release the lock, call into the policy, and
+ * then reacquire the lock. The caller is responsible for recovering gracefully.
+ *
+ * A 'LockedInterruptible' method may called a 'Locked' method, but NOT vice-versa.
+ */
+class InputDispatcher : public InputDispatcherInterface {
+protected:
+ virtual ~InputDispatcher();
+
+public:
+ explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
+
+ virtual void dump(String8& dump);
+ virtual void monitor();
+
+ virtual void dispatchOnce();
+
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
+ virtual void notifyKey(const NotifyKeyArgs* args);
+ virtual void notifyMotion(const NotifyMotionArgs* args);
+ virtual void notifySwitch(const NotifySwitchArgs* args);
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
+
+ virtual int32_t injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags);
+
+ virtual void setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles);
+ virtual void setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle);
+ virtual void setInputDispatchMode(bool enabled, bool frozen);
+ virtual void setInputFilterEnabled(bool enabled);
+
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel);
+
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
+
+private:
+ template <typename T>
+ struct Link {
+ T* next;
+ T* prev;
+
+ protected:
+ inline Link() : next(NULL), prev(NULL) { }
+ };
+
+ struct InjectionState {
+ mutable int32_t refCount;
+
+ int32_t injectorPid;
+ int32_t injectorUid;
+ int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING
+ bool injectionIsAsync; // set to true if injection is not waiting for the result
+ int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
+
+ InjectionState(int32_t injectorPid, int32_t injectorUid);
+ void release();
+
+ private:
+ ~InjectionState();
+ };
+
+ struct EventEntry : Link<EventEntry> {
+ enum {
+ TYPE_CONFIGURATION_CHANGED,
+ TYPE_DEVICE_RESET,
+ TYPE_KEY,
+ TYPE_MOTION
+ };
+
+ mutable int32_t refCount;
+ int32_t type;
+ nsecs_t eventTime;
+ uint32_t policyFlags;
+ InjectionState* injectionState;
+
+ bool dispatchInProgress; // initially false, set to true while dispatching
+
+ inline bool isInjected() const { return injectionState != NULL; }
+
+ void release();
+
+ virtual void appendDescription(String8& msg) const = 0;
+
+ protected:
+ EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags);
+ virtual ~EventEntry();
+ void releaseInjectionState();
+ };
+
+ struct ConfigurationChangedEntry : EventEntry {
+ ConfigurationChangedEntry(nsecs_t eventTime);
+ virtual void appendDescription(String8& msg) const;
+
+ protected:
+ virtual ~ConfigurationChangedEntry();
+ };
+
+ struct DeviceResetEntry : EventEntry {
+ int32_t deviceId;
+
+ DeviceResetEntry(nsecs_t eventTime, int32_t deviceId);
+ virtual void appendDescription(String8& msg) const;
+
+ protected:
+ virtual ~DeviceResetEntry();
+ };
+
+ struct KeyEntry : EventEntry {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ int32_t repeatCount;
+ nsecs_t downTime;
+
+ bool syntheticRepeat; // set to true for synthetic key repeats
+
+ enum InterceptKeyResult {
+ INTERCEPT_KEY_RESULT_UNKNOWN,
+ INTERCEPT_KEY_RESULT_SKIP,
+ INTERCEPT_KEY_RESULT_CONTINUE,
+ INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,
+ };
+ InterceptKeyResult interceptKeyResult; // set based on the interception result
+ nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
+
+ KeyEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+ int32_t repeatCount, nsecs_t downTime);
+ virtual void appendDescription(String8& msg) const;
+ void recycle();
+
+ protected:
+ virtual ~KeyEntry();
+ };
+
+ struct MotionEntry : EventEntry {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t buttonState;
+ int32_t edgeFlags;
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+ int32_t displayId;
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+
+ MotionEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags,
+ int32_t action, int32_t flags,
+ int32_t metaState, int32_t buttonState, int32_t edgeFlags,
+ float xPrecision, float yPrecision,
+ nsecs_t downTime, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
+ virtual void appendDescription(String8& msg) const;
+
+ protected:
+ virtual ~MotionEntry();
+ };
+
+ // Tracks the progress of dispatching a particular event to a particular connection.
+ struct DispatchEntry : Link<DispatchEntry> {
+ const uint32_t seq; // unique sequence number, never 0
+
+ EventEntry* eventEntry; // the event to dispatch
+ int32_t targetFlags;
+ float xOffset;
+ float yOffset;
+ float scaleFactor;
+ nsecs_t deliveryTime; // time when the event was actually delivered
+
+ // Set to the resolved action and flags when the event is enqueued.
+ int32_t resolvedAction;
+ int32_t resolvedFlags;
+
+ DispatchEntry(EventEntry* eventEntry,
+ int32_t targetFlags, float xOffset, float yOffset, float scaleFactor);
+ ~DispatchEntry();
+
+ inline bool hasForegroundTarget() const {
+ return targetFlags & InputTarget::FLAG_FOREGROUND;
+ }
+
+ inline bool isSplit() const {
+ return targetFlags & InputTarget::FLAG_SPLIT;
+ }
+
+ private:
+ static volatile int32_t sNextSeqAtomic;
+
+ static uint32_t nextSeq();
+ };
+
+ // A command entry captures state and behavior for an action to be performed in the
+ // dispatch loop after the initial processing has taken place. It is essentially
+ // a kind of continuation used to postpone sensitive policy interactions to a point
+ // in the dispatch loop where it is safe to release the lock (generally after finishing
+ // the critical parts of the dispatch cycle).
+ //
+ // The special thing about commands is that they can voluntarily release and reacquire
+ // the dispatcher lock at will. Initially when the command starts running, the
+ // dispatcher lock is held. However, if the command needs to call into the policy to
+ // do some work, it can release the lock, do the work, then reacquire the lock again
+ // before returning.
+ //
+ // This mechanism is a bit clunky but it helps to preserve the invariant that the dispatch
+ // never calls into the policy while holding its lock.
+ //
+ // Commands are implicitly 'LockedInterruptible'.
+ struct CommandEntry;
+ typedef void (InputDispatcher::*Command)(CommandEntry* commandEntry);
+
+ class Connection;
+ struct CommandEntry : Link<CommandEntry> {
+ CommandEntry(Command command);
+ ~CommandEntry();
+
+ Command command;
+
+ // parameters for the command (usage varies by command)
+ sp<Connection> connection;
+ nsecs_t eventTime;
+ KeyEntry* keyEntry;
+ sp<InputApplicationHandle> inputApplicationHandle;
+ sp<InputWindowHandle> inputWindowHandle;
+ int32_t userActivityEventType;
+ uint32_t seq;
+ bool handled;
+ };
+
+ // Generic queue implementation.
+ template <typename T>
+ struct Queue {
+ T* head;
+ T* tail;
+
+ inline Queue() : head(NULL), tail(NULL) {
+ }
+
+ inline bool isEmpty() const {
+ return !head;
+ }
+
+ inline void enqueueAtTail(T* entry) {
+ entry->prev = tail;
+ if (tail) {
+ tail->next = entry;
+ } else {
+ head = entry;
+ }
+ entry->next = NULL;
+ tail = entry;
+ }
+
+ inline void enqueueAtHead(T* entry) {
+ entry->next = head;
+ if (head) {
+ head->prev = entry;
+ } else {
+ tail = entry;
+ }
+ entry->prev = NULL;
+ head = entry;
+ }
+
+ inline void dequeue(T* entry) {
+ if (entry->prev) {
+ entry->prev->next = entry->next;
+ } else {
+ head = entry->next;
+ }
+ if (entry->next) {
+ entry->next->prev = entry->prev;
+ } else {
+ tail = entry->prev;
+ }
+ }
+
+ inline T* dequeueAtHead() {
+ T* entry = head;
+ head = entry->next;
+ if (head) {
+ head->prev = NULL;
+ } else {
+ tail = NULL;
+ }
+ return entry;
+ }
+
+ uint32_t count() const;
+ };
+
+ /* Specifies which events are to be canceled and why. */
+ struct CancelationOptions {
+ enum Mode {
+ CANCEL_ALL_EVENTS = 0,
+ CANCEL_POINTER_EVENTS = 1,
+ CANCEL_NON_POINTER_EVENTS = 2,
+ CANCEL_FALLBACK_EVENTS = 3,
+ };
+
+ // The criterion to use to determine which events should be canceled.
+ Mode mode;
+
+ // Descriptive reason for the cancelation.
+ const char* reason;
+
+ // The specific keycode of the key event to cancel, or -1 to cancel any key event.
+ int32_t keyCode;
+
+ // The specific device id of events to cancel, or -1 to cancel events from any device.
+ int32_t deviceId;
+
+ CancelationOptions(Mode mode, const char* reason) :
+ mode(mode), reason(reason), keyCode(-1), deviceId(-1) { }
+ };
+
+ /* Tracks dispatched key and motion event state so that cancelation events can be
+ * synthesized when events are dropped. */
+ class InputState {
+ public:
+ InputState();
+ ~InputState();
+
+ // Returns true if there is no state to be canceled.
+ bool isNeutral() const;
+
+ // Returns true if the specified source is known to have received a hover enter
+ // motion event.
+ bool isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const;
+
+ // Records tracking information for a key event that has just been published.
+ // Returns true if the event should be delivered, false if it is inconsistent
+ // and should be skipped.
+ bool trackKey(const KeyEntry* entry, int32_t action, int32_t flags);
+
+ // Records tracking information for a motion event that has just been published.
+ // Returns true if the event should be delivered, false if it is inconsistent
+ // and should be skipped.
+ bool trackMotion(const MotionEntry* entry, int32_t action, int32_t flags);
+
+ // Synthesizes cancelation events for the current state and resets the tracked state.
+ void synthesizeCancelationEvents(nsecs_t currentTime,
+ Vector<EventEntry*>& outEvents, const CancelationOptions& options);
+
+ // Clears the current state.
+ void clear();
+
+ // Copies pointer-related parts of the input state to another instance.
+ void copyPointerStateTo(InputState& other) const;
+
+ // Gets the fallback key associated with a keycode.
+ // Returns -1 if none.
+ // Returns AKEYCODE_UNKNOWN if we are only dispatching the unhandled key to the policy.
+ int32_t getFallbackKey(int32_t originalKeyCode);
+
+ // Sets the fallback key for a particular keycode.
+ void setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode);
+
+ // Removes the fallback key for a particular keycode.
+ void removeFallbackKey(int32_t originalKeyCode);
+
+ inline const KeyedVector<int32_t, int32_t>& getFallbackKeys() const {
+ return mFallbackKeys;
+ }
+
+ private:
+ struct KeyMemento {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ int32_t flags;
+ nsecs_t downTime;
+ uint32_t policyFlags;
+ };
+
+ struct MotionMemento {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t flags;
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+ int32_t displayId;
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ bool hovering;
+ uint32_t policyFlags;
+
+ void setPointers(const MotionEntry* entry);
+ };
+
+ Vector<KeyMemento> mKeyMementos;
+ Vector<MotionMemento> mMotionMementos;
+ KeyedVector<int32_t, int32_t> mFallbackKeys;
+
+ ssize_t findKeyMemento(const KeyEntry* entry) const;
+ ssize_t findMotionMemento(const MotionEntry* entry, bool hovering) const;
+
+ void addKeyMemento(const KeyEntry* entry, int32_t flags);
+ void addMotionMemento(const MotionEntry* entry, int32_t flags, bool hovering);
+
+ static bool shouldCancelKey(const KeyMemento& memento,
+ const CancelationOptions& options);
+ static bool shouldCancelMotion(const MotionMemento& memento,
+ const CancelationOptions& options);
+ };
+
+ /* Manages the dispatch state associated with a single input channel. */
+ class Connection : public RefBase {
+ protected:
+ virtual ~Connection();
+
+ public:
+ enum Status {
+ // Everything is peachy.
+ STATUS_NORMAL,
+ // An unrecoverable communication error has occurred.
+ STATUS_BROKEN,
+ // The input channel has been unregistered.
+ STATUS_ZOMBIE
+ };
+
+ Status status;
+ sp<InputChannel> inputChannel; // never null
+ sp<InputWindowHandle> inputWindowHandle; // may be null
+ bool monitor;
+ InputPublisher inputPublisher;
+ InputState inputState;
+
+ // True if the socket is full and no further events can be published until
+ // the application consumes some of the input.
+ bool inputPublisherBlocked;
+
+ // Queue of events that need to be published to the connection.
+ Queue<DispatchEntry> outboundQueue;
+
+ // Queue of events that have been published to the connection but that have not
+ // yet received a "finished" response from the application.
+ Queue<DispatchEntry> waitQueue;
+
+ explicit Connection(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+
+ inline const char* getInputChannelName() const { return inputChannel->getName().string(); }
+
+ const char* getWindowName() const;
+ const char* getStatusLabel() const;
+
+ DispatchEntry* findWaitQueueEntry(uint32_t seq);
+ };
+
+ enum DropReason {
+ DROP_REASON_NOT_DROPPED = 0,
+ DROP_REASON_POLICY = 1,
+ DROP_REASON_APP_SWITCH = 2,
+ DROP_REASON_DISABLED = 3,
+ DROP_REASON_BLOCKED = 4,
+ DROP_REASON_STALE = 5,
+ };
+
+ sp<InputDispatcherPolicyInterface> mPolicy;
+ InputDispatcherConfiguration mConfig;
+
+ Mutex mLock;
+
+ Condition mDispatcherIsAliveCondition;
+
+ sp<Looper> mLooper;
+
+ EventEntry* mPendingEvent;
+ Queue<EventEntry> mInboundQueue;
+ Queue<CommandEntry> mCommandQueue;
+
+ void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime);
+
+ // Enqueues an inbound event. Returns true if mLooper->wake() should be called.
+ bool enqueueInboundEventLocked(EventEntry* entry);
+
+ // Cleans up input state when dropping an inbound event.
+ void dropInboundEventLocked(EventEntry* entry, DropReason dropReason);
+
+ // App switch latency optimization.
+ bool mAppSwitchSawKeyDown;
+ nsecs_t mAppSwitchDueTime;
+
+ static bool isAppSwitchKeyCode(int32_t keyCode);
+ bool isAppSwitchKeyEventLocked(KeyEntry* keyEntry);
+ bool isAppSwitchPendingLocked();
+ void resetPendingAppSwitchLocked(bool handled);
+
+ // Stale event latency optimization.
+ static bool isStaleEventLocked(nsecs_t currentTime, EventEntry* entry);
+
+ // Blocked event latency optimization. Drops old events when the user intends
+ // to transfer focus to a new application.
+ EventEntry* mNextUnblockedEvent;
+
+ sp<InputWindowHandle> findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y);
+
+ // All registered connections mapped by channel file descriptor.
+ KeyedVector<int, sp<Connection> > mConnectionsByFd;
+
+ ssize_t getConnectionIndexLocked(const sp<InputChannel>& inputChannel);
+
+ // Input channels that will receive a copy of all input events.
+ Vector<sp<InputChannel> > mMonitoringChannels;
+
+ // Event injection and synchronization.
+ Condition mInjectionResultAvailableCondition;
+ bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid);
+ void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
+
+ Condition mInjectionSyncFinishedCondition;
+ void incrementPendingForegroundDispatchesLocked(EventEntry* entry);
+ void decrementPendingForegroundDispatchesLocked(EventEntry* entry);
+
+ // Key repeat tracking.
+ struct KeyRepeatState {
+ KeyEntry* lastKeyEntry; // or null if no repeat
+ nsecs_t nextRepeatTime;
+ } mKeyRepeatState;
+
+ void resetKeyRepeatLocked();
+ KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime);
+
+ // Deferred command processing.
+ bool haveCommandsLocked() const;
+ bool runCommandsLockedInterruptible();
+ CommandEntry* postCommandLocked(Command command);
+
+ // Input filter processing.
+ bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args);
+ bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args);
+
+ // Inbound event processing.
+ void drainInboundQueueLocked();
+ void releasePendingEventLocked();
+ void releaseInboundEventLocked(EventEntry* entry);
+
+ // Dispatch state.
+ bool mDispatchEnabled;
+ bool mDispatchFrozen;
+ bool mInputFilterEnabled;
+
+ Vector<sp<InputWindowHandle> > mWindowHandles;
+
+ sp<InputWindowHandle> getWindowHandleLocked(const sp<InputChannel>& inputChannel) const;
+ bool hasWindowHandleLocked(const sp<InputWindowHandle>& windowHandle) const;
+
+ // Focus tracking for keys, trackball, etc.
+ sp<InputWindowHandle> mFocusedWindowHandle;
+
+ // Focus tracking for touch.
+ struct TouchedWindow {
+ sp<InputWindowHandle> windowHandle;
+ int32_t targetFlags;
+ BitSet32 pointerIds; // zero unless target flag FLAG_SPLIT is set
+ };
+ struct TouchState {
+ bool down;
+ bool split;
+ int32_t deviceId; // id of the device that is currently down, others are rejected
+ uint32_t source; // source of the device that is current down, others are rejected
+ int32_t displayId; // id to the display that currently has a touch, others are rejected
+ Vector<TouchedWindow> windows;
+
+ TouchState();
+ ~TouchState();
+ void reset();
+ void copyFrom(const TouchState& other);
+ void addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds);
+ void removeWindow(const sp<InputWindowHandle>& windowHandle);
+ void filterNonAsIsTouchWindows();
+ sp<InputWindowHandle> getFirstForegroundWindowHandle() const;
+ bool isSlippery() const;
+ };
+
+ TouchState mTouchState;
+ TouchState mTempTouchState;
+
+ // Focused application.
+ sp<InputApplicationHandle> mFocusedApplicationHandle;
+
+ // Dispatcher state at time of last ANR.
+ String8 mLastANRState;
+
+ // Dispatch inbound events.
+ bool dispatchConfigurationChangedLocked(
+ nsecs_t currentTime, ConfigurationChangedEntry* entry);
+ bool dispatchDeviceResetLocked(
+ nsecs_t currentTime, DeviceResetEntry* entry);
+ bool dispatchKeyLocked(
+ nsecs_t currentTime, KeyEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime);
+ bool dispatchMotionLocked(
+ nsecs_t currentTime, MotionEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime);
+ void dispatchEventLocked(nsecs_t currentTime, EventEntry* entry,
+ const Vector<InputTarget>& inputTargets);
+
+ void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry);
+ void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry);
+
+ // Keeping track of ANR timeouts.
+ enum InputTargetWaitCause {
+ INPUT_TARGET_WAIT_CAUSE_NONE,
+ INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY,
+ INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,
+ };
+
+ InputTargetWaitCause mInputTargetWaitCause;
+ nsecs_t mInputTargetWaitStartTime;
+ nsecs_t mInputTargetWaitTimeoutTime;
+ bool mInputTargetWaitTimeoutExpired;
+ sp<InputApplicationHandle> mInputTargetWaitApplicationHandle;
+
+ // Contains the last window which received a hover event.
+ sp<InputWindowHandle> mLastHoverWindowHandle;
+
+ // Finding targets for input events.
+ int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry,
+ const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t* nextWakeupTime, const char* reason);
+ void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+ const sp<InputChannel>& inputChannel);
+ nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime);
+ void resetANRTimeoutsLocked();
+
+ int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry,
+ Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime);
+ int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry,
+ Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
+ bool* outConflictingPointerActions);
+
+ void addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets);
+ void addMonitoringTargetsLocked(Vector<InputTarget>& inputTargets);
+
+ void pokeUserActivityLocked(const EventEntry* eventEntry);
+ bool checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
+ const InjectionState* injectionState);
+ bool isWindowObscuredAtPointLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t x, int32_t y) const;
+ bool isWindowReadyForMoreInputLocked(nsecs_t currentTime,
+ const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry);
+ String8 getApplicationWindowLabelLocked(const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle);
+
+ // Manage the dispatch cycle for a single connection.
+ // These methods are deliberately not Interruptible because doing all of the work
+ // with the mutex held makes it easier to ensure that connection invariants are maintained.
+ // If needed, the methods post commands to run later once the critical bits are done.
+ void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget);
+ void enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget);
+ void enqueueDispatchEntryLocked(const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget, int32_t dispatchMode);
+ void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+ void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ uint32_t seq, bool handled);
+ void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ bool notify);
+ void drainDispatchQueueLocked(Queue<DispatchEntry>* queue);
+ void releaseDispatchEntryLocked(DispatchEntry* dispatchEntry);
+ static int handleReceiveCallback(int fd, int events, void* data);
+
+ void synthesizeCancelationEventsForAllConnectionsLocked(
+ const CancelationOptions& options);
+ void synthesizeCancelationEventsForInputChannelLocked(const sp<InputChannel>& channel,
+ const CancelationOptions& options);
+ void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection,
+ const CancelationOptions& options);
+
+ // Splitting motion events across windows.
+ MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds);
+
+ // Reset and drop everything the dispatcher is doing.
+ void resetAndDropEverythingLocked(const char* reason);
+
+ // Dump state.
+ void dumpDispatchStateLocked(String8& dump);
+ void logDispatchStateLocked();
+
+ // Registration.
+ void removeMonitorChannelLocked(const sp<InputChannel>& inputChannel);
+ status_t unregisterInputChannelLocked(const sp<InputChannel>& inputChannel, bool notify);
+
+ // Add or remove a connection to the mActiveConnections vector.
+ void activateConnectionLocked(Connection* connection);
+ void deactivateConnectionLocked(Connection* connection);
+
+ // Interesting events that we might like to log or tell the framework about.
+ void onDispatchCycleFinishedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled);
+ void onDispatchCycleBrokenLocked(
+ nsecs_t currentTime, const sp<Connection>& connection);
+ void onANRLocked(
+ nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t eventTime, nsecs_t waitStartTime, const char* reason);
+
+ // Outbound policy interactions.
+ void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry);
+ void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
+ void doNotifyANRLockedInterruptible(CommandEntry* commandEntry);
+ void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry);
+ void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry);
+ bool afterKeyEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, KeyEntry* keyEntry, bool handled);
+ bool afterMotionEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, MotionEntry* motionEntry, bool handled);
+ void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry);
+ void initializeKeyEvent(KeyEvent* event, const KeyEntry* entry);
+
+ // Statistics gathering.
+ void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+ int32_t injectionResult, nsecs_t timeSpentWaitingForApplication);
+ void traceInboundQueueLengthLocked();
+ void traceOutboundQueueLengthLocked(const sp<Connection>& connection);
+ void traceWaitQueueLengthLocked(const sp<Connection>& connection);
+};
+
+/* Enqueues and dispatches input events, endlessly. */
+class InputDispatcherThread : public Thread {
+public:
+ explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher);
+ ~InputDispatcherThread();
+
+private:
+ virtual bool threadLoop();
+
+ sp<InputDispatcherInterface> mDispatcher;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_DISPATCHER_H
diff --git a/widget/gonk/libui/InputListener.cpp b/widget/gonk/libui/InputListener.cpp
new file mode 100644
index 0000000000..3b673f0adb
--- /dev/null
+++ b/widget/gonk/libui/InputListener.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "InputListener"
+
+//#define LOG_NDEBUG 0
+
+#include "InputListener.h"
+
+#include "cutils_log.h"
+
+namespace android {
+
+// --- NotifyConfigurationChangedArgs ---
+
+NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(nsecs_t eventTime) :
+ eventTime(eventTime) {
+}
+
+NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(
+ const NotifyConfigurationChangedArgs& other) :
+ eventTime(other.eventTime) {
+}
+
+void NotifyConfigurationChangedArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyConfigurationChanged(this);
+}
+
+
+// --- NotifyKeyArgs ---
+
+NotifyKeyArgs::NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, nsecs_t downTime) :
+ eventTime(eventTime), deviceId(deviceId), source(source), policyFlags(policyFlags),
+ action(action), flags(flags), keyCode(keyCode), scanCode(scanCode),
+ metaState(metaState), downTime(downTime) {
+}
+
+NotifyKeyArgs::NotifyKeyArgs(const NotifyKeyArgs& other) :
+ eventTime(other.eventTime), deviceId(other.deviceId), source(other.source),
+ policyFlags(other.policyFlags),
+ action(other.action), flags(other.flags),
+ keyCode(other.keyCode), scanCode(other.scanCode),
+ metaState(other.metaState), downTime(other.downTime) {
+}
+
+void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyKey(this);
+}
+
+
+// --- NotifyMotionArgs ---
+
+NotifyMotionArgs::NotifyMotionArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime) :
+ eventTime(eventTime), deviceId(deviceId), source(source), policyFlags(policyFlags),
+ action(action), flags(flags), metaState(metaState), buttonState(buttonState),
+ edgeFlags(edgeFlags), displayId(displayId), pointerCount(pointerCount),
+ xPrecision(xPrecision), yPrecision(yPrecision), downTime(downTime) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ this->pointerProperties[i].copyFrom(pointerProperties[i]);
+ this->pointerCoords[i].copyFrom(pointerCoords[i]);
+ }
+}
+
+NotifyMotionArgs::NotifyMotionArgs(const NotifyMotionArgs& other) :
+ eventTime(other.eventTime), deviceId(other.deviceId), source(other.source),
+ policyFlags(other.policyFlags),
+ action(other.action), flags(other.flags),
+ metaState(other.metaState), buttonState(other.buttonState),
+ edgeFlags(other.edgeFlags), displayId(other.displayId),
+ pointerCount(other.pointerCount),
+ xPrecision(other.xPrecision), yPrecision(other.yPrecision), downTime(other.downTime) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].copyFrom(other.pointerProperties[i]);
+ pointerCoords[i].copyFrom(other.pointerCoords[i]);
+ }
+}
+
+void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyMotion(this);
+}
+
+
+// --- NotifySwitchArgs ---
+
+NotifySwitchArgs::NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags,
+ uint32_t switchValues, uint32_t switchMask) :
+ eventTime(eventTime), policyFlags(policyFlags),
+ switchValues(switchValues), switchMask(switchMask) {
+}
+
+NotifySwitchArgs::NotifySwitchArgs(const NotifySwitchArgs& other) :
+ eventTime(other.eventTime), policyFlags(other.policyFlags),
+ switchValues(other.switchValues), switchMask(other.switchMask) {
+}
+
+void NotifySwitchArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifySwitch(this);
+}
+
+
+// --- NotifyDeviceResetArgs ---
+
+NotifyDeviceResetArgs::NotifyDeviceResetArgs(nsecs_t eventTime, int32_t deviceId) :
+ eventTime(eventTime), deviceId(deviceId) {
+}
+
+NotifyDeviceResetArgs::NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) :
+ eventTime(other.eventTime), deviceId(other.deviceId) {
+}
+
+void NotifyDeviceResetArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyDeviceReset(this);
+}
+
+
+// --- QueuedInputListener ---
+
+QueuedInputListener::QueuedInputListener(const sp<InputListenerInterface>& innerListener) :
+ mInnerListener(innerListener) {
+}
+
+QueuedInputListener::~QueuedInputListener() {
+ size_t count = mArgsQueue.size();
+ for (size_t i = 0; i < count; i++) {
+ delete mArgsQueue[i];
+ }
+}
+
+void QueuedInputListener::notifyConfigurationChanged(
+ const NotifyConfigurationChangedArgs* args) {
+ mArgsQueue.push(new NotifyConfigurationChangedArgs(*args));
+}
+
+void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
+ mArgsQueue.push(new NotifyKeyArgs(*args));
+}
+
+void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
+ mArgsQueue.push(new NotifyMotionArgs(*args));
+}
+
+void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) {
+ mArgsQueue.push(new NotifySwitchArgs(*args));
+}
+
+void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+ mArgsQueue.push(new NotifyDeviceResetArgs(*args));
+}
+
+void QueuedInputListener::flush() {
+ size_t count = mArgsQueue.size();
+ for (size_t i = 0; i < count; i++) {
+ NotifyArgs* args = mArgsQueue[i];
+ args->notify(mInnerListener);
+ delete args;
+ }
+ mArgsQueue.clear();
+}
+
+
+} // namespace android
diff --git a/widget/gonk/libui/InputListener.h b/widget/gonk/libui/InputListener.h
new file mode 100644
index 0000000000..de799322f3
--- /dev/null
+++ b/widget/gonk/libui/InputListener.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _UI_INPUT_LISTENER_H
+#define _UI_INPUT_LISTENER_H
+
+#include "Input.h"
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+class InputListenerInterface;
+
+
+/* Superclass of all input event argument objects */
+struct NotifyArgs {
+ virtual ~NotifyArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const = 0;
+};
+
+
+/* Describes a configuration change event. */
+struct NotifyConfigurationChangedArgs : public NotifyArgs {
+ nsecs_t eventTime;
+
+ inline NotifyConfigurationChangedArgs() { }
+
+ NotifyConfigurationChangedArgs(nsecs_t eventTime);
+
+ NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other);
+
+ virtual ~NotifyConfigurationChangedArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a key event. */
+struct NotifyKeyArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ uint32_t policyFlags;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ nsecs_t downTime;
+
+ inline NotifyKeyArgs() { }
+
+ NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, nsecs_t downTime);
+
+ NotifyKeyArgs(const NotifyKeyArgs& other);
+
+ virtual ~NotifyKeyArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a motion event. */
+struct NotifyMotionArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ uint32_t policyFlags;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t buttonState;
+ int32_t edgeFlags;
+ int32_t displayId;
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+
+ inline NotifyMotionArgs() { }
+
+ NotifyMotionArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime);
+
+ NotifyMotionArgs(const NotifyMotionArgs& other);
+
+ virtual ~NotifyMotionArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a switch event. */
+struct NotifySwitchArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ uint32_t policyFlags;
+ uint32_t switchValues;
+ uint32_t switchMask;
+
+ inline NotifySwitchArgs() { }
+
+ NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags,
+ uint32_t switchValues, uint32_t switchMask);
+
+ NotifySwitchArgs(const NotifySwitchArgs& other);
+
+ virtual ~NotifySwitchArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a device reset event, such as when a device is added,
+ * reconfigured, or removed. */
+struct NotifyDeviceResetArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+
+ inline NotifyDeviceResetArgs() { }
+
+ NotifyDeviceResetArgs(nsecs_t eventTime, int32_t deviceId);
+
+ NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other);
+
+ virtual ~NotifyDeviceResetArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/*
+ * The interface used by the InputReader to notify the InputListener about input events.
+ */
+class InputListenerInterface : public virtual RefBase {
+protected:
+ InputListenerInterface() { }
+ virtual ~InputListenerInterface() { }
+
+public:
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0;
+ virtual void notifyKey(const NotifyKeyArgs* args) = 0;
+ virtual void notifyMotion(const NotifyMotionArgs* args) = 0;
+ virtual void notifySwitch(const NotifySwitchArgs* args) = 0;
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0;
+};
+
+
+/*
+ * An implementation of the listener interface that queues up and defers dispatch
+ * of decoded events until flushed.
+ */
+class QueuedInputListener : public InputListenerInterface {
+protected:
+ virtual ~QueuedInputListener();
+
+public:
+ QueuedInputListener(const sp<InputListenerInterface>& innerListener);
+
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
+ virtual void notifyKey(const NotifyKeyArgs* args);
+ virtual void notifyMotion(const NotifyMotionArgs* args);
+ virtual void notifySwitch(const NotifySwitchArgs* args);
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
+
+ void flush();
+
+private:
+ sp<InputListenerInterface> mInnerListener;
+ Vector<NotifyArgs*> mArgsQueue;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_LISTENER_H
diff --git a/widget/gonk/libui/InputManager.cpp b/widget/gonk/libui/InputManager.cpp
new file mode 100644
index 0000000000..91af056bfe
--- /dev/null
+++ b/widget/gonk/libui/InputManager.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "InputManager"
+
+//#define LOG_NDEBUG 0
+
+#include "InputManager.h"
+
+#include "cutils_log.h"
+
+namespace android {
+
+InputManager::InputManager(
+ const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& readerPolicy,
+ const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
+ mDispatcher = new InputDispatcher(dispatcherPolicy);
+ mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
+ initialize();
+}
+
+InputManager::InputManager(
+ const sp<InputReaderInterface>& reader,
+ const sp<InputDispatcherInterface>& dispatcher) :
+ mReader(reader),
+ mDispatcher(dispatcher) {
+ initialize();
+}
+
+InputManager::~InputManager() {
+ stop();
+}
+
+void InputManager::initialize() {
+ mReaderThread = new InputReaderThread(mReader);
+ mDispatcherThread = new InputDispatcherThread(mDispatcher);
+}
+
+status_t InputManager::start() {
+ status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ ALOGE("Could not start InputDispatcher thread due to error %d.", result);
+ return result;
+ }
+
+ result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ ALOGE("Could not start InputReader thread due to error %d.", result);
+
+ mDispatcherThread->requestExit();
+ return result;
+ }
+
+ return OK;
+}
+
+status_t InputManager::stop() {
+ status_t result = mReaderThread->requestExitAndWait();
+ if (result) {
+ ALOGW("Could not stop InputReader thread due to error %d.", result);
+ }
+
+ result = mDispatcherThread->requestExitAndWait();
+ if (result) {
+ ALOGW("Could not stop InputDispatcher thread due to error %d.", result);
+ }
+
+ return OK;
+}
+
+sp<InputReaderInterface> InputManager::getReader() {
+ return mReader;
+}
+
+sp<InputDispatcherInterface> InputManager::getDispatcher() {
+ return mDispatcher;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputManager.h b/widget/gonk/libui/InputManager.h
new file mode 100644
index 0000000000..15a5176ecd
--- /dev/null
+++ b/widget/gonk/libui/InputManager.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _UI_INPUT_MANAGER_H
+#define _UI_INPUT_MANAGER_H
+
+/**
+ * Native input manager.
+ */
+
+#include "EventHub.h"
+#include "InputReader.h"
+#include "InputDispatcher.h"
+
+#include "Input.h"
+#include "InputTransport.h"
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * The input manager is the core of the system event processing.
+ *
+ * The input manager uses two threads.
+ *
+ * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events,
+ * applies policy, and posts messages to a queue managed by the DispatcherThread.
+ * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the
+ * queue and asynchronously dispatches them to applications.
+ *
+ * By design, the InputReaderThread class and InputDispatcherThread class do not share any
+ * internal state. Moreover, all communication is done one way from the InputReaderThread
+ * into the InputDispatcherThread and never the reverse. Both classes may interact with the
+ * InputDispatchPolicy, however.
+ *
+ * The InputManager class never makes any calls into Java itself. Instead, the
+ * InputDispatchPolicy is responsible for performing all external interactions with the
+ * system, including calling DVM services.
+ */
+class InputManagerInterface : public virtual RefBase {
+protected:
+ InputManagerInterface() { }
+ virtual ~InputManagerInterface() { }
+
+public:
+ /* Starts the input manager threads. */
+ virtual status_t start() = 0;
+
+ /* Stops the input manager threads and waits for them to exit. */
+ virtual status_t stop() = 0;
+
+ /* Gets the input reader. */
+ virtual sp<InputReaderInterface> getReader() = 0;
+
+ /* Gets the input dispatcher. */
+ virtual sp<InputDispatcherInterface> getDispatcher() = 0;
+};
+
+class InputManager : public InputManagerInterface {
+protected:
+ virtual ~InputManager();
+
+public:
+ InputManager(
+ const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& readerPolicy,
+ const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);
+
+ // (used for testing purposes)
+ InputManager(
+ const sp<InputReaderInterface>& reader,
+ const sp<InputDispatcherInterface>& dispatcher);
+
+ virtual status_t start();
+ virtual status_t stop();
+
+ virtual sp<InputReaderInterface> getReader();
+ virtual sp<InputDispatcherInterface> getDispatcher();
+
+private:
+ sp<InputReaderInterface> mReader;
+ sp<InputReaderThread> mReaderThread;
+
+ sp<InputDispatcherInterface> mDispatcher;
+ sp<InputDispatcherThread> mDispatcherThread;
+
+ void initialize();
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_MANAGER_H
diff --git a/widget/gonk/libui/InputReader.cpp b/widget/gonk/libui/InputReader.cpp
new file mode 100644
index 0000000000..3699569aa7
--- /dev/null
+++ b/widget/gonk/libui/InputReader.cpp
@@ -0,0 +1,6510 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "InputReader"
+
+//#define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+// Log debug messages for each raw event received from the EventHub.
+#define DEBUG_RAW_EVENTS 0
+
+// Log debug messages about touch screen filtering hacks.
+#define DEBUG_HACKS 0
+
+// Log debug messages about virtual key processing.
+#define DEBUG_VIRTUAL_KEYS 0
+
+// Log debug messages about pointers.
+#define DEBUG_POINTERS 0
+
+// Log debug messages about pointer assignment calculations.
+#define DEBUG_POINTER_ASSIGNMENT 0
+
+// Log debug messages about gesture detection.
+#define DEBUG_GESTURES 0
+
+// Log debug messages about the vibrator.
+#define DEBUG_VIBRATOR 0
+
+#include "InputReader.h"
+
+#include "Keyboard.h"
+#include "VirtualKeyMap.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+#define INDENT4 " "
+#define INDENT5 " "
+
+namespace android {
+
+// --- Constants ---
+
+// Maximum number of slots supported when using the slot-based Multitouch Protocol B.
+static const size_t MAX_SLOTS = 32;
+
+// --- Static Functions ---
+
+template<typename T>
+inline static T abs(const T& value) {
+ return value < 0 ? - value : value;
+}
+
+template<typename T>
+inline static T min(const T& a, const T& b) {
+ return a < b ? a : b;
+}
+
+template<typename T>
+inline static void swap(T& a, T& b) {
+ T temp = a;
+ a = b;
+ b = temp;
+}
+
+inline static float avg(float x, float y) {
+ return (x + y) / 2;
+}
+
+inline static float distance(float x1, float y1, float x2, float y2) {
+ return hypotf(x1 - x2, y1 - y2);
+}
+
+inline static int32_t signExtendNybble(int32_t value) {
+ return value >= 8 ? value - 16 : value;
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static int32_t rotateValueUsingRotationMap(int32_t value, int32_t orientation,
+ const int32_t map[][4], size_t mapSize) {
+ if (orientation != DISPLAY_ORIENTATION_0) {
+ for (size_t i = 0; i < mapSize; i++) {
+ if (value == map[i][0]) {
+ return map[i][orientation];
+ }
+ }
+ }
+ return value;
+}
+
+static const int32_t keyCodeRotationMap[][4] = {
+ // key codes enumerated counter-clockwise with the original (unrotated) key first
+ // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation
+ { AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT },
+ { AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN },
+ { AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT },
+ { AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP },
+};
+static const size_t keyCodeRotationMapSize =
+ sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
+
+static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) {
+ return rotateValueUsingRotationMap(keyCode, orientation,
+ keyCodeRotationMap, keyCodeRotationMapSize);
+}
+
+static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) {
+ float temp;
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = *deltaX;
+ *deltaX = *deltaY;
+ *deltaY = -temp;
+ break;
+
+ case DISPLAY_ORIENTATION_180:
+ *deltaX = -*deltaX;
+ *deltaY = -*deltaY;
+ break;
+
+ case DISPLAY_ORIENTATION_270:
+ temp = *deltaX;
+ *deltaX = -*deltaY;
+ *deltaY = temp;
+ break;
+ }
+}
+
+static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) {
+ return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0;
+}
+
+// Returns true if the pointer should be reported as being down given the specified
+// button states. This determines whether the event is reported as a touch event.
+static bool isPointerDown(int32_t buttonState) {
+ return buttonState &
+ (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY
+ | AMOTION_EVENT_BUTTON_TERTIARY);
+}
+
+static float calculateCommonVector(float a, float b) {
+ if (a > 0 && b > 0) {
+ return a < b ? a : b;
+ } else if (a < 0 && b < 0) {
+ return a > b ? a : b;
+ } else {
+ return 0;
+ }
+}
+
+static void synthesizeButtonKey(InputReaderContext* context, int32_t action,
+ nsecs_t when, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState,
+ int32_t buttonState, int32_t keyCode) {
+ if (
+ (action == AKEY_EVENT_ACTION_DOWN
+ && !(lastButtonState & buttonState)
+ && (currentButtonState & buttonState))
+ || (action == AKEY_EVENT_ACTION_UP
+ && (lastButtonState & buttonState)
+ && !(currentButtonState & buttonState))) {
+ NotifyKeyArgs args(when, deviceId, source, policyFlags,
+ action, 0, keyCode, 0, context->getGlobalMetaState(), when);
+ context->getListener()->notifyKey(&args);
+ }
+}
+
+static void synthesizeButtonKeys(InputReaderContext* context, int32_t action,
+ nsecs_t when, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState) {
+ synthesizeButtonKey(context, action, when, deviceId, source, policyFlags,
+ lastButtonState, currentButtonState,
+ AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK);
+ synthesizeButtonKey(context, action, when, deviceId, source, policyFlags,
+ lastButtonState, currentButtonState,
+ AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD);
+}
+
+
+// --- InputReaderConfiguration ---
+
+bool InputReaderConfiguration::getDisplayInfo(bool external, DisplayViewport* outViewport) const {
+ const DisplayViewport& viewport = external ? mExternalDisplay : mInternalDisplay;
+ if (viewport.displayId >= 0) {
+ *outViewport = viewport;
+ return true;
+ }
+ return false;
+}
+
+void InputReaderConfiguration::setDisplayInfo(bool external, const DisplayViewport& viewport) {
+ DisplayViewport& v = external ? mExternalDisplay : mInternalDisplay;
+ v = viewport;
+}
+
+
+// --- InputReader ---
+
+InputReader::InputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputListenerInterface>& listener) :
+ mContext(this), mEventHub(eventHub), mPolicy(policy),
+ mGlobalMetaState(0), mGeneration(1),
+ mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
+ mConfigurationChangesToRefresh(0) {
+ mQueuedListener = new QueuedInputListener(listener);
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ refreshConfigurationLocked(0);
+ updateGlobalMetaStateLocked();
+ } // release lock
+}
+
+InputReader::~InputReader() {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ delete mDevices.valueAt(i);
+ }
+}
+
+void InputReader::loopOnce() {
+ int32_t oldGeneration;
+ int32_t timeoutMillis;
+ bool inputDevicesChanged = false;
+ Vector<InputDeviceInfo> inputDevices;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ oldGeneration = mGeneration;
+ timeoutMillis = -1;
+
+ uint32_t changes = mConfigurationChangesToRefresh;
+ if (changes) {
+ mConfigurationChangesToRefresh = 0;
+ timeoutMillis = 0;
+ refreshConfigurationLocked(changes);
+ } else if (mNextTimeout != LLONG_MAX) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
+ }
+ } // release lock
+
+ size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+ mReaderIsAliveCondition.broadcast();
+
+ if (count) {
+ processEventsLocked(mEventBuffer, count);
+ }
+
+ if (mNextTimeout != LLONG_MAX) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (now >= mNextTimeout) {
+#if DEBUG_RAW_EVENTS
+ ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
+#endif
+ mNextTimeout = LLONG_MAX;
+ timeoutExpiredLocked(now);
+ }
+ }
+
+ if (oldGeneration != mGeneration) {
+ inputDevicesChanged = true;
+ getInputDevicesLocked(inputDevices);
+ }
+ } // release lock
+
+ // Send out a message that the describes the changed input devices.
+ if (inputDevicesChanged) {
+ mPolicy->notifyInputDevicesChanged(inputDevices);
+ }
+
+ // Flush queued events out to the listener.
+ // This must happen outside of the lock because the listener could potentially call
+ // back into the InputReader's methods, such as getScanCodeState, or become blocked
+ // on another thread similarly waiting to acquire the InputReader lock thereby
+ // resulting in a deadlock. This situation is actually quite plausible because the
+ // listener is actually the input dispatcher, which calls into the window manager,
+ // which occasionally calls into the input reader.
+ mQueuedListener->flush();
+}
+
+void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
+ for (const RawEvent* rawEvent = rawEvents; count;) {
+ int32_t type = rawEvent->type;
+ size_t batchSize = 1;
+ if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
+ int32_t deviceId = rawEvent->deviceId;
+ while (batchSize < count) {
+ if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
+ || rawEvent[batchSize].deviceId != deviceId) {
+ break;
+ }
+ batchSize += 1;
+ }
+#if DEBUG_RAW_EVENTS
+ ALOGD("BatchSize: %d Count: %d", batchSize, count);
+#endif
+ processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
+ } else {
+ switch (rawEvent->type) {
+ case EventHubInterface::DEVICE_ADDED:
+ addDeviceLocked(rawEvent->when, rawEvent->deviceId);
+ break;
+ case EventHubInterface::DEVICE_REMOVED:
+ removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
+ break;
+ case EventHubInterface::FINISHED_DEVICE_SCAN:
+ handleConfigurationChangedLocked(rawEvent->when);
+ break;
+ default:
+ ALOG_ASSERT(false); // can't happen
+ break;
+ }
+ }
+ count -= batchSize;
+ rawEvent += batchSize;
+ }
+}
+
+void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+ return;
+ }
+
+ InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
+ uint32_t classes = mEventHub->getDeviceClasses(deviceId);
+
+ InputDevice* device = createDeviceLocked(deviceId, identifier, classes);
+ device->configure(when, &mConfig, 0);
+ device->reset(when);
+
+ if (device->isIgnored()) {
+ ALOGI("Device added: id=%d, name='%s' (ignored non-input device)", deviceId,
+ identifier.name.string());
+ } else {
+ ALOGI("Device added: id=%d, name='%s', sources=0x%08x", deviceId,
+ identifier.name.string(), device->getSources());
+ }
+
+ mDevices.add(deviceId, device);
+ bumpGenerationLocked();
+}
+
+void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) {
+ InputDevice* device = NULL;
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex < 0) {
+ ALOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
+ return;
+ }
+
+ device = mDevices.valueAt(deviceIndex);
+ mDevices.removeItemsAt(deviceIndex, 1);
+ bumpGenerationLocked();
+
+ if (device->isIgnored()) {
+ ALOGI("Device removed: id=%d, name='%s' (ignored non-input device)",
+ device->getId(), device->getName().string());
+ } else {
+ ALOGI("Device removed: id=%d, name='%s', sources=0x%08x",
+ device->getId(), device->getName().string(), device->getSources());
+ }
+
+ device->reset(when);
+ delete device;
+}
+
+InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
+ const InputDeviceIdentifier& identifier, uint32_t classes) {
+ InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
+ identifier, classes);
+
+ // External devices.
+ if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
+ device->setExternal(true);
+ }
+
+ // Switch-like devices.
+ if (classes & INPUT_DEVICE_CLASS_SWITCH) {
+ device->addMapper(new SwitchInputMapper(device));
+ }
+
+ // Vibrator-like devices.
+ if (classes & INPUT_DEVICE_CLASS_VIBRATOR) {
+ device->addMapper(new VibratorInputMapper(device));
+ }
+
+ // Keyboard-like devices.
+ uint32_t keyboardSource = 0;
+ int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
+ if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+ keyboardSource |= AINPUT_SOURCE_KEYBOARD;
+ }
+ if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
+ keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
+ }
+ if (classes & INPUT_DEVICE_CLASS_DPAD) {
+ keyboardSource |= AINPUT_SOURCE_DPAD;
+ }
+ if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
+ keyboardSource |= AINPUT_SOURCE_GAMEPAD;
+ }
+
+ if (keyboardSource != 0) {
+ device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
+ }
+
+ // Cursor-like devices.
+ if (classes & INPUT_DEVICE_CLASS_CURSOR) {
+ device->addMapper(new CursorInputMapper(device));
+ }
+
+ // Touchscreens and touchpad devices.
+ if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
+ device->addMapper(new MultiTouchInputMapper(device));
+ } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
+ device->addMapper(new SingleTouchInputMapper(device));
+ }
+
+ // Joystick-like devices.
+ if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
+ device->addMapper(new JoystickInputMapper(device));
+ }
+
+ return device;
+}
+
+void InputReader::processEventsForDeviceLocked(int32_t deviceId,
+ const RawEvent* rawEvents, size_t count) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex < 0) {
+ ALOGW("Discarding event for unknown deviceId %d.", deviceId);
+ return;
+ }
+
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (device->isIgnored()) {
+ //ALOGD("Discarding event for ignored deviceId %d.", deviceId);
+ return;
+ }
+
+ device->process(rawEvents, count);
+}
+
+void InputReader::timeoutExpiredLocked(nsecs_t when) {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (!device->isIgnored()) {
+ device->timeoutExpired(when);
+ }
+ }
+}
+
+void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
+ // Reset global meta state because it depends on the list of all configured devices.
+ updateGlobalMetaStateLocked();
+
+ // Enqueue configuration changed.
+ NotifyConfigurationChangedArgs args(when);
+ mQueuedListener->notifyConfigurationChanged(&args);
+}
+
+void InputReader::refreshConfigurationLocked(uint32_t changes) {
+ mPolicy->getReaderConfiguration(&mConfig);
+ mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
+
+ if (changes) {
+ ALOGI("Reconfiguring input devices. changes=0x%08x", changes);
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
+ mEventHub->requestReopenDevices();
+ } else {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ device->configure(now, &mConfig, changes);
+ }
+ }
+ }
+}
+
+void InputReader::updateGlobalMetaStateLocked() {
+ mGlobalMetaState = 0;
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ mGlobalMetaState |= device->getMetaState();
+ }
+}
+
+int32_t InputReader::getGlobalMetaStateLocked() {
+ return mGlobalMetaState;
+}
+
+void InputReader::disableVirtualKeysUntilLocked(nsecs_t time) {
+ mDisableVirtualKeysTimeout = time;
+}
+
+bool InputReader::shouldDropVirtualKeyLocked(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) {
+ if (now < mDisableVirtualKeysTimeout) {
+ ALOGI("Dropping virtual key from device %s because virtual keys are "
+ "temporarily disabled for the next %0.3fms. keyCode=%d, scanCode=%d",
+ device->getName().string(),
+ (mDisableVirtualKeysTimeout - now) * 0.000001,
+ keyCode, scanCode);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void InputReader::fadePointerLocked() {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ device->fadePointer();
+ }
+}
+
+void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) {
+ if (when < mNextTimeout) {
+ mNextTimeout = when;
+ mEventHub->wake();
+ }
+}
+
+int32_t InputReader::bumpGenerationLocked() {
+ return ++mGeneration;
+}
+
+void InputReader::getInputDevices(Vector<InputDeviceInfo>& outInputDevices) {
+ AutoMutex _l(mLock);
+ getInputDevicesLocked(outInputDevices);
+}
+
+void InputReader::getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices) {
+ outInputDevices.clear();
+
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (!device->isIgnored()) {
+ outInputDevices.push();
+ device->getDeviceInfo(&outInputDevices.editTop());
+ }
+ }
+}
+
+int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode) {
+ AutoMutex _l(mLock);
+
+ return getStateLocked(deviceId, sourceMask, keyCode, &InputDevice::getKeyCodeState);
+}
+
+int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode) {
+ AutoMutex _l(mLock);
+
+ return getStateLocked(deviceId, sourceMask, scanCode, &InputDevice::getScanCodeState);
+}
+
+int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
+ AutoMutex _l(mLock);
+
+ return getStateLocked(deviceId, sourceMask, switchCode, &InputDevice::getSwitchState);
+}
+
+int32_t InputReader::getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
+ GetStateFunc getStateFunc) {
+ int32_t result = AKEY_STATE_UNKNOWN;
+ if (deviceId >= 0) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result = (device->*getStateFunc)(sourceMask, code);
+ }
+ }
+ } else {
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ // If any device reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that
+ // value. Otherwise, return AKEY_STATE_UP as long as one device reports it.
+ int32_t currentResult = (device->*getStateFunc)(sourceMask, code);
+ if (currentResult >= AKEY_STATE_DOWN) {
+ return currentResult;
+ } else if (currentResult == AKEY_STATE_UP) {
+ result = currentResult;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+ AutoMutex _l(mLock);
+
+ memset(outFlags, 0, numCodes);
+ return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+}
+
+bool InputReader::markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+ bool result = false;
+ if (deviceId >= 0) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result = device->markSupportedKeyCodes(sourceMask,
+ numCodes, keyCodes, outFlags);
+ }
+ }
+ } else {
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result |= device->markSupportedKeyCodes(sourceMask,
+ numCodes, keyCodes, outFlags);
+ }
+ }
+ }
+ return result;
+}
+
+void InputReader::requestRefreshConfiguration(uint32_t changes) {
+ AutoMutex _l(mLock);
+
+ if (changes) {
+ bool needWake = !mConfigurationChangesToRefresh;
+ mConfigurationChangesToRefresh |= changes;
+
+ if (needWake) {
+ mEventHub->wake();
+ }
+ }
+}
+
+void InputReader::vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->cancelVibrate(token);
+ }
+}
+
+void InputReader::dump(String8& dump) {
+ AutoMutex _l(mLock);
+
+ mEventHub->dump(dump);
+ dump.append("\n");
+
+ dump.append("Input Reader State:\n");
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ mDevices.valueAt(i)->dump(dump);
+ }
+
+ dump.append(INDENT "Configuration:\n");
+ dump.append(INDENT2 "ExcludedDeviceNames: [");
+ for (size_t i = 0; i < mConfig.excludedDeviceNames.size(); i++) {
+ if (i != 0) {
+ dump.append(", ");
+ }
+ dump.append(mConfig.excludedDeviceNames.itemAt(i).string());
+ }
+ dump.append("]\n");
+ dump.appendFormat(INDENT2 "VirtualKeyQuietTime: %0.1fms\n",
+ mConfig.virtualKeyQuietTime * 0.000001f);
+
+ dump.appendFormat(INDENT2 "PointerVelocityControlParameters: "
+ "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n",
+ mConfig.pointerVelocityControlParameters.scale,
+ mConfig.pointerVelocityControlParameters.lowThreshold,
+ mConfig.pointerVelocityControlParameters.highThreshold,
+ mConfig.pointerVelocityControlParameters.acceleration);
+
+ dump.appendFormat(INDENT2 "WheelVelocityControlParameters: "
+ "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n",
+ mConfig.wheelVelocityControlParameters.scale,
+ mConfig.wheelVelocityControlParameters.lowThreshold,
+ mConfig.wheelVelocityControlParameters.highThreshold,
+ mConfig.wheelVelocityControlParameters.acceleration);
+
+ dump.appendFormat(INDENT2 "PointerGesture:\n");
+ dump.appendFormat(INDENT3 "Enabled: %s\n",
+ toString(mConfig.pointerGesturesEnabled));
+ dump.appendFormat(INDENT3 "QuietInterval: %0.1fms\n",
+ mConfig.pointerGestureQuietInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "DragMinSwitchSpeed: %0.1fpx/s\n",
+ mConfig.pointerGestureDragMinSwitchSpeed);
+ dump.appendFormat(INDENT3 "TapInterval: %0.1fms\n",
+ mConfig.pointerGestureTapInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "TapDragInterval: %0.1fms\n",
+ mConfig.pointerGestureTapDragInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "TapSlop: %0.1fpx\n",
+ mConfig.pointerGestureTapSlop);
+ dump.appendFormat(INDENT3 "MultitouchSettleInterval: %0.1fms\n",
+ mConfig.pointerGestureMultitouchSettleInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "MultitouchMinDistance: %0.1fpx\n",
+ mConfig.pointerGestureMultitouchMinDistance);
+ dump.appendFormat(INDENT3 "SwipeTransitionAngleCosine: %0.1f\n",
+ mConfig.pointerGestureSwipeTransitionAngleCosine);
+ dump.appendFormat(INDENT3 "SwipeMaxWidthRatio: %0.1f\n",
+ mConfig.pointerGestureSwipeMaxWidthRatio);
+ dump.appendFormat(INDENT3 "MovementSpeedRatio: %0.1f\n",
+ mConfig.pointerGestureMovementSpeedRatio);
+ dump.appendFormat(INDENT3 "ZoomSpeedRatio: %0.1f\n",
+ mConfig.pointerGestureZoomSpeedRatio);
+}
+
+void InputReader::monitor() {
+ // Acquire and release the lock to ensure that the reader has not deadlocked.
+ mLock.lock();
+ mEventHub->wake();
+ mReaderIsAliveCondition.wait(mLock);
+ mLock.unlock();
+
+ // Check the EventHub
+ mEventHub->monitor();
+}
+
+
+// --- InputReader::ContextImpl ---
+
+InputReader::ContextImpl::ContextImpl(InputReader* reader) :
+ mReader(reader) {
+}
+
+void InputReader::ContextImpl::updateGlobalMetaState() {
+ // lock is already held by the input loop
+ mReader->updateGlobalMetaStateLocked();
+}
+
+int32_t InputReader::ContextImpl::getGlobalMetaState() {
+ // lock is already held by the input loop
+ return mReader->getGlobalMetaStateLocked();
+}
+
+void InputReader::ContextImpl::disableVirtualKeysUntil(nsecs_t time) {
+ // lock is already held by the input loop
+ mReader->disableVirtualKeysUntilLocked(time);
+}
+
+bool InputReader::ContextImpl::shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) {
+ // lock is already held by the input loop
+ return mReader->shouldDropVirtualKeyLocked(now, device, keyCode, scanCode);
+}
+
+void InputReader::ContextImpl::fadePointer() {
+ // lock is already held by the input loop
+ mReader->fadePointerLocked();
+}
+
+void InputReader::ContextImpl::requestTimeoutAtTime(nsecs_t when) {
+ // lock is already held by the input loop
+ mReader->requestTimeoutAtTimeLocked(when);
+}
+
+int32_t InputReader::ContextImpl::bumpGeneration() {
+ // lock is already held by the input loop
+ return mReader->bumpGenerationLocked();
+}
+
+InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() {
+ return mReader->mPolicy.get();
+}
+
+InputListenerInterface* InputReader::ContextImpl::getListener() {
+ return mReader->mQueuedListener.get();
+}
+
+EventHubInterface* InputReader::ContextImpl::getEventHub() {
+ return mReader->mEventHub.get();
+}
+
+
+// --- InputReaderThread ---
+
+InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
+ Thread(/*canCallJava*/ true), mReader(reader) {
+}
+
+InputReaderThread::~InputReaderThread() {
+}
+
+bool InputReaderThread::threadLoop() {
+ mReader->loopOnce();
+ return true;
+}
+
+
+// --- InputDevice ---
+
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
+ const InputDeviceIdentifier& identifier, uint32_t classes) :
+ mContext(context), mId(id), mGeneration(generation),
+ mIdentifier(identifier), mClasses(classes),
+ mSources(0), mIsExternal(false), mDropUntilNextSync(false) {
+}
+
+InputDevice::~InputDevice() {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ delete mMappers[i];
+ }
+ mMappers.clear();
+}
+
+void InputDevice::dump(String8& dump) {
+ InputDeviceInfo deviceInfo;
+ getDeviceInfo(& deviceInfo);
+
+ dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(),
+ deviceInfo.getDisplayName().string());
+ dump.appendFormat(INDENT2 "Generation: %d\n", mGeneration);
+ dump.appendFormat(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
+ dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+ dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+
+ const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
+ if (!ranges.isEmpty()) {
+ dump.append(INDENT2 "Motion Ranges:\n");
+ for (size_t i = 0; i < ranges.size(); i++) {
+ const InputDeviceInfo::MotionRange& range = ranges.itemAt(i);
+ const char* label = getAxisLabel(range.axis);
+ char name[32];
+ if (label) {
+ strncpy(name, label, sizeof(name));
+ name[sizeof(name) - 1] = '\0';
+ } else {
+ snprintf(name, sizeof(name), "%d", range.axis);
+ }
+ dump.appendFormat(INDENT3 "%s: source=0x%08x, "
+ "min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f, resolution=%0.3f\n",
+ name, range.source, range.min, range.max, range.flat, range.fuzz,
+ range.resolution);
+ }
+ }
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->dump(dump);
+ }
+}
+
+void InputDevice::addMapper(InputMapper* mapper) {
+ mMappers.add(mapper);
+}
+
+void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) {
+ mSources = 0;
+
+ if (!isIgnored()) {
+ if (!changes) { // first time only
+ mContext->getEventHub()->getConfiguration(mId, &mConfiguration);
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
+ if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) {
+ sp<KeyCharacterMap> keyboardLayout =
+ mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier.descriptor);
+ if (mContext->getEventHub()->setKeyboardLayoutOverlay(mId, keyboardLayout)) {
+ bumpGeneration();
+ }
+ }
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) {
+ if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) {
+ String8 alias = mContext->getPolicy()->getDeviceAlias(mIdentifier);
+ if (mAlias != alias) {
+ mAlias = alias;
+ bumpGeneration();
+ }
+ }
+ }
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->configure(when, config, changes);
+ mSources |= mapper->getSources();
+ }
+ }
+}
+
+void InputDevice::reset(nsecs_t when) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->reset(when);
+ }
+
+ mContext->updateGlobalMetaState();
+
+ notifyReset(when);
+}
+
+void InputDevice::process(const RawEvent* rawEvents, size_t count) {
+ // Process all of the events in order for each mapper.
+ // We cannot simply ask each mapper to process them in bulk because mappers may
+ // have side-effects that must be interleaved. For example, joystick movement events and
+ // gamepad button presses are handled by different mappers but they should be dispatched
+ // in the order received.
+ size_t numMappers = mMappers.size();
+ for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
+#if DEBUG_RAW_EVENTS
+ ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%lld",
+ rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value,
+ rawEvent->when);
+#endif
+
+ if (mDropUntilNextSync) {
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ mDropUntilNextSync = false;
+#if DEBUG_RAW_EVENTS
+ ALOGD("Recovered from input event buffer overrun.");
+#endif
+ } else {
+#if DEBUG_RAW_EVENTS
+ ALOGD("Dropped input event while waiting for next input sync.");
+#endif
+ }
+ } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
+ ALOGI("Detected input event buffer overrun for device %s.", getName().string());
+ mDropUntilNextSync = true;
+ reset(rawEvent->when);
+ } else {
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->process(rawEvent);
+ }
+ }
+ }
+}
+
+void InputDevice::timeoutExpired(nsecs_t when) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->timeoutExpired(when);
+ }
+}
+
+void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
+ outDeviceInfo->initialize(mId, mGeneration, mIdentifier, mAlias, mIsExternal);
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->populateDeviceInfo(outDeviceInfo);
+ }
+}
+
+int32_t InputDevice::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return getState(sourceMask, keyCode, & InputMapper::getKeyCodeState);
+}
+
+int32_t InputDevice::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return getState(sourceMask, scanCode, & InputMapper::getScanCodeState);
+}
+
+int32_t InputDevice::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return getState(sourceMask, switchCode, & InputMapper::getSwitchState);
+}
+
+int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) {
+ int32_t result = AKEY_STATE_UNKNOWN;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+ // If any mapper reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that
+ // value. Otherwise, return AKEY_STATE_UP as long as one mapper reports it.
+ int32_t currentResult = (mapper->*getStateFunc)(sourceMask, code);
+ if (currentResult >= AKEY_STATE_DOWN) {
+ return currentResult;
+ } else if (currentResult == AKEY_STATE_UP) {
+ result = currentResult;
+ }
+ }
+ }
+ return result;
+}
+
+bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ bool result = false;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+ result |= mapper->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
+ }
+ }
+ return result;
+}
+
+void InputDevice::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputDevice::cancelVibrate(int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->cancelVibrate(token);
+ }
+}
+
+int32_t InputDevice::getMetaState() {
+ int32_t result = 0;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ result |= mapper->getMetaState();
+ }
+ return result;
+}
+
+void InputDevice::fadePointer() {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->fadePointer();
+ }
+}
+
+void InputDevice::bumpGeneration() {
+ mGeneration = mContext->bumpGeneration();
+}
+
+void InputDevice::notifyReset(nsecs_t when) {
+ NotifyDeviceResetArgs args(when, mId);
+ mContext->getListener()->notifyDeviceReset(&args);
+}
+
+
+// --- CursorButtonAccumulator ---
+
+CursorButtonAccumulator::CursorButtonAccumulator() {
+ clearButtons();
+}
+
+void CursorButtonAccumulator::reset(InputDevice* device) {
+ mBtnLeft = device->isKeyPressed(BTN_LEFT);
+ mBtnRight = device->isKeyPressed(BTN_RIGHT);
+ mBtnMiddle = device->isKeyPressed(BTN_MIDDLE);
+ mBtnBack = device->isKeyPressed(BTN_BACK);
+ mBtnSide = device->isKeyPressed(BTN_SIDE);
+ mBtnForward = device->isKeyPressed(BTN_FORWARD);
+ mBtnExtra = device->isKeyPressed(BTN_EXTRA);
+ mBtnTask = device->isKeyPressed(BTN_TASK);
+}
+
+void CursorButtonAccumulator::clearButtons() {
+ mBtnLeft = 0;
+ mBtnRight = 0;
+ mBtnMiddle = 0;
+ mBtnBack = 0;
+ mBtnSide = 0;
+ mBtnForward = 0;
+ mBtnExtra = 0;
+ mBtnTask = 0;
+}
+
+void CursorButtonAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_KEY) {
+ switch (rawEvent->code) {
+ case BTN_LEFT:
+ mBtnLeft = rawEvent->value;
+ break;
+ case BTN_RIGHT:
+ mBtnRight = rawEvent->value;
+ break;
+ case BTN_MIDDLE:
+ mBtnMiddle = rawEvent->value;
+ break;
+ case BTN_BACK:
+ mBtnBack = rawEvent->value;
+ break;
+ case BTN_SIDE:
+ mBtnSide = rawEvent->value;
+ break;
+ case BTN_FORWARD:
+ mBtnForward = rawEvent->value;
+ break;
+ case BTN_EXTRA:
+ mBtnExtra = rawEvent->value;
+ break;
+ case BTN_TASK:
+ mBtnTask = rawEvent->value;
+ break;
+ }
+ }
+}
+
+uint32_t CursorButtonAccumulator::getButtonState() const {
+ uint32_t result = 0;
+ if (mBtnLeft) {
+ result |= AMOTION_EVENT_BUTTON_PRIMARY;
+ }
+ if (mBtnRight) {
+ result |= AMOTION_EVENT_BUTTON_SECONDARY;
+ }
+ if (mBtnMiddle) {
+ result |= AMOTION_EVENT_BUTTON_TERTIARY;
+ }
+ if (mBtnBack || mBtnSide) {
+ result |= AMOTION_EVENT_BUTTON_BACK;
+ }
+ if (mBtnForward || mBtnExtra) {
+ result |= AMOTION_EVENT_BUTTON_FORWARD;
+ }
+ return result;
+}
+
+
+// --- CursorMotionAccumulator ---
+
+CursorMotionAccumulator::CursorMotionAccumulator() {
+ clearRelativeAxes();
+}
+
+void CursorMotionAccumulator::reset(InputDevice* device) {
+ clearRelativeAxes();
+}
+
+void CursorMotionAccumulator::clearRelativeAxes() {
+ mRelX = 0;
+ mRelY = 0;
+}
+
+void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_REL) {
+ switch (rawEvent->code) {
+ case REL_X:
+ mRelX = rawEvent->value;
+ break;
+ case REL_Y:
+ mRelY = rawEvent->value;
+ break;
+ }
+ }
+}
+
+void CursorMotionAccumulator::finishSync() {
+ clearRelativeAxes();
+}
+
+
+// --- CursorScrollAccumulator ---
+
+CursorScrollAccumulator::CursorScrollAccumulator() :
+ mHaveRelWheel(false), mHaveRelHWheel(false) {
+ clearRelativeAxes();
+}
+
+void CursorScrollAccumulator::configure(InputDevice* device) {
+ mHaveRelWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_WHEEL);
+ mHaveRelHWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_HWHEEL);
+}
+
+void CursorScrollAccumulator::reset(InputDevice* device) {
+ clearRelativeAxes();
+}
+
+void CursorScrollAccumulator::clearRelativeAxes() {
+ mRelWheel = 0;
+ mRelHWheel = 0;
+}
+
+void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_REL) {
+ switch (rawEvent->code) {
+ case REL_WHEEL:
+ mRelWheel = rawEvent->value;
+ break;
+ case REL_HWHEEL:
+ mRelHWheel = rawEvent->value;
+ break;
+ }
+ }
+}
+
+void CursorScrollAccumulator::finishSync() {
+ clearRelativeAxes();
+}
+
+
+// --- TouchButtonAccumulator ---
+
+TouchButtonAccumulator::TouchButtonAccumulator() :
+ mHaveBtnTouch(false), mHaveStylus(false) {
+ clearButtons();
+}
+
+void TouchButtonAccumulator::configure(InputDevice* device) {
+ mHaveBtnTouch = device->hasKey(BTN_TOUCH);
+ mHaveStylus = device->hasKey(BTN_TOOL_PEN)
+ || device->hasKey(BTN_TOOL_RUBBER)
+ || device->hasKey(BTN_TOOL_BRUSH)
+ || device->hasKey(BTN_TOOL_PENCIL)
+ || device->hasKey(BTN_TOOL_AIRBRUSH);
+}
+
+void TouchButtonAccumulator::reset(InputDevice* device) {
+ mBtnTouch = device->isKeyPressed(BTN_TOUCH);
+ mBtnStylus = device->isKeyPressed(BTN_STYLUS);
+ mBtnStylus2 = device->isKeyPressed(BTN_STYLUS);
+ mBtnToolFinger = device->isKeyPressed(BTN_TOOL_FINGER);
+ mBtnToolPen = device->isKeyPressed(BTN_TOOL_PEN);
+ mBtnToolRubber = device->isKeyPressed(BTN_TOOL_RUBBER);
+ mBtnToolBrush = device->isKeyPressed(BTN_TOOL_BRUSH);
+ mBtnToolPencil = device->isKeyPressed(BTN_TOOL_PENCIL);
+ mBtnToolAirbrush = device->isKeyPressed(BTN_TOOL_AIRBRUSH);
+ mBtnToolMouse = device->isKeyPressed(BTN_TOOL_MOUSE);
+ mBtnToolLens = device->isKeyPressed(BTN_TOOL_LENS);
+ mBtnToolDoubleTap = device->isKeyPressed(BTN_TOOL_DOUBLETAP);
+ mBtnToolTripleTap = device->isKeyPressed(BTN_TOOL_TRIPLETAP);
+ mBtnToolQuadTap = device->isKeyPressed(BTN_TOOL_QUADTAP);
+}
+
+void TouchButtonAccumulator::clearButtons() {
+ mBtnTouch = 0;
+ mBtnStylus = 0;
+ mBtnStylus2 = 0;
+ mBtnToolFinger = 0;
+ mBtnToolPen = 0;
+ mBtnToolRubber = 0;
+ mBtnToolBrush = 0;
+ mBtnToolPencil = 0;
+ mBtnToolAirbrush = 0;
+ mBtnToolMouse = 0;
+ mBtnToolLens = 0;
+ mBtnToolDoubleTap = 0;
+ mBtnToolTripleTap = 0;
+ mBtnToolQuadTap = 0;
+}
+
+void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_KEY) {
+ switch (rawEvent->code) {
+ case BTN_TOUCH:
+ mBtnTouch = rawEvent->value;
+ break;
+ case BTN_STYLUS:
+ mBtnStylus = rawEvent->value;
+ break;
+ case BTN_STYLUS2:
+ mBtnStylus2 = rawEvent->value;
+ break;
+ case BTN_TOOL_FINGER:
+ mBtnToolFinger = rawEvent->value;
+ break;
+ case BTN_TOOL_PEN:
+ mBtnToolPen = rawEvent->value;
+ break;
+ case BTN_TOOL_RUBBER:
+ mBtnToolRubber = rawEvent->value;
+ break;
+ case BTN_TOOL_BRUSH:
+ mBtnToolBrush = rawEvent->value;
+ break;
+ case BTN_TOOL_PENCIL:
+ mBtnToolPencil = rawEvent->value;
+ break;
+ case BTN_TOOL_AIRBRUSH:
+ mBtnToolAirbrush = rawEvent->value;
+ break;
+ case BTN_TOOL_MOUSE:
+ mBtnToolMouse = rawEvent->value;
+ break;
+ case BTN_TOOL_LENS:
+ mBtnToolLens = rawEvent->value;
+ break;
+ case BTN_TOOL_DOUBLETAP:
+ mBtnToolDoubleTap = rawEvent->value;
+ break;
+ case BTN_TOOL_TRIPLETAP:
+ mBtnToolTripleTap = rawEvent->value;
+ break;
+ case BTN_TOOL_QUADTAP:
+ mBtnToolQuadTap = rawEvent->value;
+ break;
+ }
+ }
+}
+
+uint32_t TouchButtonAccumulator::getButtonState() const {
+ uint32_t result = 0;
+ if (mBtnStylus) {
+ result |= AMOTION_EVENT_BUTTON_SECONDARY;
+ }
+ if (mBtnStylus2) {
+ result |= AMOTION_EVENT_BUTTON_TERTIARY;
+ }
+ return result;
+}
+
+int32_t TouchButtonAccumulator::getToolType() const {
+ if (mBtnToolMouse || mBtnToolLens) {
+ return AMOTION_EVENT_TOOL_TYPE_MOUSE;
+ }
+ if (mBtnToolRubber) {
+ return AMOTION_EVENT_TOOL_TYPE_ERASER;
+ }
+ if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) {
+ return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+ }
+ if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap) {
+ return AMOTION_EVENT_TOOL_TYPE_FINGER;
+ }
+ return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+bool TouchButtonAccumulator::isToolActive() const {
+ return mBtnTouch || mBtnToolFinger || mBtnToolPen || mBtnToolRubber
+ || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush
+ || mBtnToolMouse || mBtnToolLens
+ || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap;
+}
+
+bool TouchButtonAccumulator::isHovering() const {
+ return mHaveBtnTouch && !mBtnTouch;
+}
+
+bool TouchButtonAccumulator::hasStylus() const {
+ return mHaveStylus;
+}
+
+
+// --- RawPointerAxes ---
+
+RawPointerAxes::RawPointerAxes() {
+ clear();
+}
+
+void RawPointerAxes::clear() {
+ x.clear();
+ y.clear();
+ pressure.clear();
+ touchMajor.clear();
+ touchMinor.clear();
+ toolMajor.clear();
+ toolMinor.clear();
+ orientation.clear();
+ distance.clear();
+ tiltX.clear();
+ tiltY.clear();
+ trackingId.clear();
+ slot.clear();
+}
+
+
+// --- RawPointerData ---
+
+RawPointerData::RawPointerData() {
+ clear();
+}
+
+void RawPointerData::clear() {
+ pointerCount = 0;
+ clearIdBits();
+}
+
+void RawPointerData::copyFrom(const RawPointerData& other) {
+ pointerCount = other.pointerCount;
+ hoveringIdBits = other.hoveringIdBits;
+ touchingIdBits = other.touchingIdBits;
+
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointers[i] = other.pointers[i];
+
+ int id = pointers[i].id;
+ idToIndex[id] = other.idToIndex[id];
+ }
+}
+
+void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const {
+ float x = 0, y = 0;
+ uint32_t count = touchingIdBits.count();
+ if (count) {
+ for (BitSet32 idBits(touchingIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const Pointer& pointer = pointerForId(id);
+ x += pointer.x;
+ y += pointer.y;
+ }
+ x /= count;
+ y /= count;
+ }
+ *outX = x;
+ *outY = y;
+}
+
+
+// --- CookedPointerData ---
+
+CookedPointerData::CookedPointerData() {
+ clear();
+}
+
+void CookedPointerData::clear() {
+ pointerCount = 0;
+ hoveringIdBits.clear();
+ touchingIdBits.clear();
+}
+
+void CookedPointerData::copyFrom(const CookedPointerData& other) {
+ pointerCount = other.pointerCount;
+ hoveringIdBits = other.hoveringIdBits;
+ touchingIdBits = other.touchingIdBits;
+
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].copyFrom(other.pointerProperties[i]);
+ pointerCoords[i].copyFrom(other.pointerCoords[i]);
+
+ int id = pointerProperties[i].id;
+ idToIndex[id] = other.idToIndex[id];
+ }
+}
+
+
+// --- SingleTouchMotionAccumulator ---
+
+SingleTouchMotionAccumulator::SingleTouchMotionAccumulator() {
+ clearAbsoluteAxes();
+}
+
+void SingleTouchMotionAccumulator::reset(InputDevice* device) {
+ mAbsX = device->getAbsoluteAxisValue(ABS_X);
+ mAbsY = device->getAbsoluteAxisValue(ABS_Y);
+ mAbsPressure = device->getAbsoluteAxisValue(ABS_PRESSURE);
+ mAbsToolWidth = device->getAbsoluteAxisValue(ABS_TOOL_WIDTH);
+ mAbsDistance = device->getAbsoluteAxisValue(ABS_DISTANCE);
+ mAbsTiltX = device->getAbsoluteAxisValue(ABS_TILT_X);
+ mAbsTiltY = device->getAbsoluteAxisValue(ABS_TILT_Y);
+}
+
+void SingleTouchMotionAccumulator::clearAbsoluteAxes() {
+ mAbsX = 0;
+ mAbsY = 0;
+ mAbsPressure = 0;
+ mAbsToolWidth = 0;
+ mAbsDistance = 0;
+ mAbsTiltX = 0;
+ mAbsTiltY = 0;
+}
+
+void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_ABS) {
+ switch (rawEvent->code) {
+ case ABS_X:
+ mAbsX = rawEvent->value;
+ break;
+ case ABS_Y:
+ mAbsY = rawEvent->value;
+ break;
+ case ABS_PRESSURE:
+ mAbsPressure = rawEvent->value;
+ break;
+ case ABS_TOOL_WIDTH:
+ mAbsToolWidth = rawEvent->value;
+ break;
+ case ABS_DISTANCE:
+ mAbsDistance = rawEvent->value;
+ break;
+ case ABS_TILT_X:
+ mAbsTiltX = rawEvent->value;
+ break;
+ case ABS_TILT_Y:
+ mAbsTiltY = rawEvent->value;
+ break;
+ }
+ }
+}
+
+
+// --- MultiTouchMotionAccumulator ---
+
+MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() :
+ mCurrentSlot(-1), mSlots(NULL), mSlotCount(0), mUsingSlotsProtocol(false),
+ mHaveStylus(false) {
+}
+
+MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() {
+ delete[] mSlots;
+}
+
+void MultiTouchMotionAccumulator::configure(InputDevice* device,
+ size_t slotCount, bool usingSlotsProtocol) {
+ mSlotCount = slotCount;
+ mUsingSlotsProtocol = usingSlotsProtocol;
+ mHaveStylus = device->hasAbsoluteAxis(ABS_MT_TOOL_TYPE);
+
+ delete[] mSlots;
+ mSlots = new Slot[slotCount];
+}
+
+void MultiTouchMotionAccumulator::reset(InputDevice* device) {
+ // Unfortunately there is no way to read the initial contents of the slots.
+ // So when we reset the accumulator, we must assume they are all zeroes.
+ if (mUsingSlotsProtocol) {
+ // Query the driver for the current slot index and use it as the initial slot
+ // before we start reading events from the device. It is possible that the
+ // current slot index will not be the same as it was when the first event was
+ // written into the evdev buffer, which means the input mapper could start
+ // out of sync with the initial state of the events in the evdev buffer.
+ // In the extremely unlikely case that this happens, the data from
+ // two slots will be confused until the next ABS_MT_SLOT event is received.
+ // This can cause the touch point to "jump", but at least there will be
+ // no stuck touches.
+ int32_t initialSlot;
+ status_t status = device->getEventHub()->getAbsoluteAxisValue(device->getId(),
+ ABS_MT_SLOT, &initialSlot);
+ if (status) {
+ ALOGD("Could not retrieve current multitouch slot index. status=%d", status);
+ initialSlot = -1;
+ }
+ clearSlots(initialSlot);
+ } else {
+ clearSlots(-1);
+ }
+}
+
+void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) {
+ if (mSlots) {
+ for (size_t i = 0; i < mSlotCount; i++) {
+ mSlots[i].clear();
+ }
+ }
+ mCurrentSlot = initialSlot;
+}
+
+void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_ABS) {
+#if DEBUG_POINTERS
+ bool newSlot = false;
+#endif
+ if (mUsingSlotsProtocol) {
+ if (rawEvent->code == ABS_MT_SLOT) {
+ mCurrentSlot = rawEvent->value;
+#if DEBUG_POINTERS
+ newSlot = true;
+#endif
+ }
+ } else if (mCurrentSlot < 0) {
+ mCurrentSlot = 0;
+ }
+
+ if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) {
+#if DEBUG_POINTERS
+ if (newSlot) {
+ ALOGW("MultiTouch device emitted invalid slot index %d but it "
+ "should be between 0 and %d; ignoring this slot.",
+ mCurrentSlot, mSlotCount - 1);
+ }
+#endif
+ } else {
+ Slot* slot = &mSlots[mCurrentSlot];
+
+ switch (rawEvent->code) {
+ case ABS_MT_POSITION_X:
+ slot->mInUse = true;
+ slot->mAbsMTPositionX = rawEvent->value;
+ break;
+ case ABS_MT_POSITION_Y:
+ slot->mInUse = true;
+ slot->mAbsMTPositionY = rawEvent->value;
+ break;
+ case ABS_MT_TOUCH_MAJOR:
+ slot->mInUse = true;
+ slot->mAbsMTTouchMajor = rawEvent->value;
+ break;
+ case ABS_MT_TOUCH_MINOR:
+ slot->mInUse = true;
+ slot->mAbsMTTouchMinor = rawEvent->value;
+ slot->mHaveAbsMTTouchMinor = true;
+ break;
+ case ABS_MT_WIDTH_MAJOR:
+ slot->mInUse = true;
+ slot->mAbsMTWidthMajor = rawEvent->value;
+ break;
+ case ABS_MT_WIDTH_MINOR:
+ slot->mInUse = true;
+ slot->mAbsMTWidthMinor = rawEvent->value;
+ slot->mHaveAbsMTWidthMinor = true;
+ break;
+ case ABS_MT_ORIENTATION:
+ slot->mInUse = true;
+ slot->mAbsMTOrientation = rawEvent->value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ if (mUsingSlotsProtocol && rawEvent->value < 0) {
+ // The slot is no longer in use but it retains its previous contents,
+ // which may be reused for subsequent touches.
+ slot->mInUse = false;
+ } else {
+ slot->mInUse = true;
+ slot->mAbsMTTrackingId = rawEvent->value;
+ }
+ break;
+ case ABS_MT_PRESSURE:
+ slot->mInUse = true;
+ slot->mAbsMTPressure = rawEvent->value;
+ break;
+ case ABS_MT_DISTANCE:
+ slot->mInUse = true;
+ slot->mAbsMTDistance = rawEvent->value;
+ break;
+ case ABS_MT_TOOL_TYPE:
+ slot->mInUse = true;
+ slot->mAbsMTToolType = rawEvent->value;
+ slot->mHaveAbsMTToolType = true;
+ break;
+ }
+ }
+ } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
+ // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+ mCurrentSlot += 1;
+ }
+}
+
+void MultiTouchMotionAccumulator::finishSync() {
+ if (!mUsingSlotsProtocol) {
+ clearSlots(-1);
+ }
+}
+
+bool MultiTouchMotionAccumulator::hasStylus() const {
+ return mHaveStylus;
+}
+
+
+// --- MultiTouchMotionAccumulator::Slot ---
+
+MultiTouchMotionAccumulator::Slot::Slot() {
+ clear();
+}
+
+void MultiTouchMotionAccumulator::Slot::clear() {
+ mInUse = false;
+ mHaveAbsMTTouchMinor = false;
+ mHaveAbsMTWidthMinor = false;
+ mHaveAbsMTToolType = false;
+ mAbsMTPositionX = 0;
+ mAbsMTPositionY = 0;
+ mAbsMTTouchMajor = 0;
+ mAbsMTTouchMinor = 0;
+ mAbsMTWidthMajor = 0;
+ mAbsMTWidthMinor = 0;
+ mAbsMTOrientation = 0;
+ mAbsMTTrackingId = -1;
+ mAbsMTPressure = 0;
+ mAbsMTDistance = 0;
+ mAbsMTToolType = 0;
+}
+
+int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
+ if (mHaveAbsMTToolType) {
+ switch (mAbsMTToolType) {
+ case MT_TOOL_FINGER:
+ return AMOTION_EVENT_TOOL_TYPE_FINGER;
+ case MT_TOOL_PEN:
+ return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+ }
+ }
+ return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+
+// --- InputMapper ---
+
+InputMapper::InputMapper(InputDevice* device) :
+ mDevice(device), mContext(device->getContext()) {
+}
+
+InputMapper::~InputMapper() {
+}
+
+void InputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ info->addSource(getSources());
+}
+
+void InputMapper::dump(String8& dump) {
+}
+
+void InputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+}
+
+void InputMapper::reset(nsecs_t when) {
+}
+
+void InputMapper::timeoutExpired(nsecs_t when) {
+}
+
+int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ return false;
+}
+
+void InputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+}
+
+void InputMapper::cancelVibrate(int32_t token) {
+}
+
+int32_t InputMapper::getMetaState() {
+ return 0;
+}
+
+void InputMapper::fadePointer() {
+}
+
+status_t InputMapper::getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo) {
+ return getEventHub()->getAbsoluteAxisInfo(getDeviceId(), axis, axisInfo);
+}
+
+void InputMapper::bumpGeneration() {
+ mDevice->bumpGeneration();
+}
+
+void InputMapper::dumpRawAbsoluteAxisInfo(String8& dump,
+ const RawAbsoluteAxisInfo& axis, const char* name) {
+ if (axis.valid) {
+ dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n",
+ name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution);
+ } else {
+ dump.appendFormat(INDENT4 "%s: unknown range\n", name);
+ }
+}
+
+
+// --- SwitchInputMapper ---
+
+SwitchInputMapper::SwitchInputMapper(InputDevice* device) :
+ InputMapper(device), mUpdatedSwitchValues(0), mUpdatedSwitchMask(0) {
+}
+
+SwitchInputMapper::~SwitchInputMapper() {
+}
+
+uint32_t SwitchInputMapper::getSources() {
+ return AINPUT_SOURCE_SWITCH;
+}
+
+void SwitchInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_SW:
+ processSwitch(rawEvent->code, rawEvent->value);
+ break;
+
+ case EV_SYN:
+ if (rawEvent->code == SYN_REPORT) {
+ sync(rawEvent->when);
+ }
+ }
+}
+
+void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) {
+ if (switchCode >= 0 && switchCode < 32) {
+ if (switchValue) {
+ mUpdatedSwitchValues |= 1 << switchCode;
+ }
+ mUpdatedSwitchMask |= 1 << switchCode;
+ }
+}
+
+void SwitchInputMapper::sync(nsecs_t when) {
+ if (mUpdatedSwitchMask) {
+ NotifySwitchArgs args(when, 0, mUpdatedSwitchValues, mUpdatedSwitchMask);
+ getListener()->notifySwitch(&args);
+
+ mUpdatedSwitchValues = 0;
+ mUpdatedSwitchMask = 0;
+ }
+}
+
+int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return getEventHub()->getSwitchState(getDeviceId(), switchCode);
+}
+
+
+// --- VibratorInputMapper ---
+
+VibratorInputMapper::VibratorInputMapper(InputDevice* device) :
+ InputMapper(device), mVibrating(false) {
+}
+
+VibratorInputMapper::~VibratorInputMapper() {
+}
+
+uint32_t VibratorInputMapper::getSources() {
+ return 0;
+}
+
+void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ info->setVibrator(true);
+}
+
+void VibratorInputMapper::process(const RawEvent* rawEvent) {
+ // TODO: Handle FF_STATUS, although it does not seem to be widely supported.
+}
+
+void VibratorInputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+#if DEBUG_VIBRATOR
+ String8 patternStr;
+ for (size_t i = 0; i < patternSize; i++) {
+ if (i != 0) {
+ patternStr.append(", ");
+ }
+ patternStr.appendFormat("%lld", pattern[i]);
+ }
+ ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%ld, token=%d",
+ getDeviceId(), patternStr.string(), repeat, token);
+#endif
+
+ mVibrating = true;
+ memcpy(mPattern, pattern, patternSize * sizeof(nsecs_t));
+ mPatternSize = patternSize;
+ mRepeat = repeat;
+ mToken = token;
+ mIndex = -1;
+
+ nextStep();
+}
+
+void VibratorInputMapper::cancelVibrate(int32_t token) {
+#if DEBUG_VIBRATOR
+ ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
+#endif
+
+ if (mVibrating && mToken == token) {
+ stopVibrating();
+ }
+}
+
+void VibratorInputMapper::timeoutExpired(nsecs_t when) {
+ if (mVibrating) {
+ if (when >= mNextStepTime) {
+ nextStep();
+ } else {
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+ }
+ }
+}
+
+void VibratorInputMapper::nextStep() {
+ mIndex += 1;
+ if (size_t(mIndex) >= mPatternSize) {
+ if (mRepeat < 0) {
+ // We are done.
+ stopVibrating();
+ return;
+ }
+ mIndex = mRepeat;
+ }
+
+ bool vibratorOn = mIndex & 1;
+ nsecs_t duration = mPattern[mIndex];
+ if (vibratorOn) {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending vibrate deviceId=%d, duration=%lld",
+ getDeviceId(), duration);
+#endif
+ getEventHub()->vibrate(getDeviceId(), duration);
+ } else {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+ }
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ mNextStepTime = now + duration;
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: scheduled timeout in %0.3fms", duration * 0.000001f);
+#endif
+}
+
+void VibratorInputMapper::stopVibrating() {
+ mVibrating = false;
+#if DEBUG_VIBRATOR
+ ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+}
+
+void VibratorInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Vibrator Input Mapper:\n");
+ dump.appendFormat(INDENT3 "Vibrating: %s\n", toString(mVibrating));
+}
+
+
+// --- KeyboardInputMapper ---
+
+KeyboardInputMapper::KeyboardInputMapper(InputDevice* device,
+ uint32_t source, int32_t keyboardType) :
+ InputMapper(device), mSource(source),
+ mKeyboardType(keyboardType) {
+}
+
+KeyboardInputMapper::~KeyboardInputMapper() {
+}
+
+uint32_t KeyboardInputMapper::getSources() {
+ return mSource;
+}
+
+void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ info->setKeyboardType(mKeyboardType);
+ info->setKeyCharacterMap(getEventHub()->getKeyCharacterMap(getDeviceId()));
+}
+
+void KeyboardInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Keyboard Input Mapper:\n");
+ dumpParameters(dump);
+ dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType);
+ dump.appendFormat(INDENT3 "Orientation: %d\n", mOrientation);
+ dump.appendFormat(INDENT3 "KeyDowns: %d keys currently down\n", mKeyDowns.size());
+ dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mMetaState);
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", mDownTime);
+}
+
+
+void KeyboardInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ // Configure basic parameters.
+ configureParameters();
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+ DisplayViewport v;
+ if (config->getDisplayInfo(false /*external*/, &v)) {
+ mOrientation = v.orientation;
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ }
+}
+
+void KeyboardInputMapper::configureParameters() {
+ mParameters.orientationAware = false;
+ getDevice()->getConfiguration().tryGetProperty(String8("keyboard.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.hasAssociatedDisplay = false;
+ if (mParameters.orientationAware) {
+ mParameters.hasAssociatedDisplay = true;
+ }
+}
+
+void KeyboardInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+ dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n",
+ toString(mParameters.hasAssociatedDisplay));
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void KeyboardInputMapper::reset(nsecs_t when) {
+ mMetaState = AMETA_NONE;
+ mDownTime = 0;
+ mKeyDowns.clear();
+ mCurrentHidUsage = 0;
+
+ resetLedState();
+
+ InputMapper::reset(when);
+}
+
+void KeyboardInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_KEY: {
+ int32_t scanCode = rawEvent->code;
+ int32_t usageCode = mCurrentHidUsage;
+ mCurrentHidUsage = 0;
+
+ if (isKeyboardOrGamepadKey(scanCode)) {
+ int32_t keyCode;
+ uint32_t flags;
+ if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
+ keyCode = AKEYCODE_UNKNOWN;
+ flags = 0;
+ }
+ processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
+ }
+ break;
+ }
+ case EV_MSC: {
+ if (rawEvent->code == MSC_SCAN) {
+ mCurrentHidUsage = rawEvent->value;
+ }
+ break;
+ }
+ case EV_SYN: {
+ if (rawEvent->code == SYN_REPORT) {
+ mCurrentHidUsage = 0;
+ }
+ }
+ }
+}
+
+bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
+ return scanCode < BTN_MOUSE
+ || scanCode >= KEY_OK
+ || (scanCode >= BTN_MISC && scanCode < BTN_MOUSE)
+ || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
+}
+
+void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
+ int32_t scanCode, uint32_t policyFlags) {
+
+ if (down) {
+ // Rotate key codes according to orientation if needed.
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+ keyCode = rotateKeyCode(keyCode, mOrientation);
+ }
+
+ // Add key down.
+ ssize_t keyDownIndex = findKeyDown(scanCode);
+ if (keyDownIndex >= 0) {
+ // key repeat, be sure to use same keycode as before in case of rotation
+ keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
+ } else {
+ // key down
+ if ((policyFlags & POLICY_FLAG_VIRTUAL)
+ && mContext->shouldDropVirtualKey(when,
+ getDevice(), keyCode, scanCode)) {
+ return;
+ }
+
+ mKeyDowns.push();
+ KeyDown& keyDown = mKeyDowns.editTop();
+ keyDown.keyCode = keyCode;
+ keyDown.scanCode = scanCode;
+ }
+
+ mDownTime = when;
+ } else {
+ // Remove key down.
+ ssize_t keyDownIndex = findKeyDown(scanCode);
+ if (keyDownIndex >= 0) {
+ // key up, be sure to use same keycode as before in case of rotation
+ keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
+ mKeyDowns.removeAt(size_t(keyDownIndex));
+ } else {
+ // key was not actually down
+ ALOGI("Dropping key up from device %s because the key was not down. "
+ "keyCode=%d, scanCode=%d",
+ getDeviceName().string(), keyCode, scanCode);
+ return;
+ }
+ }
+
+ bool metaStateChanged = false;
+ int32_t oldMetaState = mMetaState;
+ int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState);
+ if (oldMetaState != newMetaState) {
+ mMetaState = newMetaState;
+ metaStateChanged = true;
+ updateLedState(false);
+ }
+
+ nsecs_t downTime = mDownTime;
+
+ // Key down on external an keyboard should wake the device.
+ // We don't do this for internal keyboards to prevent them from waking up in your pocket.
+ // For internal keyboards, the key layout file should specify the policy flags for
+ // each wake key individually.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ if (down && getDevice()->isExternal()
+ && !(policyFlags & (POLICY_FLAG_WAKE | POLICY_FLAG_WAKE_DROPPED))) {
+ policyFlags |= POLICY_FLAG_WAKE_DROPPED;
+ }
+
+ if (metaStateChanged) {
+ getContext()->updateGlobalMetaState();
+ }
+
+ if (down && !isMetaKey(keyCode)) {
+ getContext()->fadePointer();
+ }
+
+ NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
+ down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
+ getListener()->notifyKey(&args);
+}
+
+ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) {
+ size_t n = mKeyDowns.size();
+ for (size_t i = 0; i < n; i++) {
+ if (mKeyDowns[i].scanCode == scanCode) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+int32_t KeyboardInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return getEventHub()->getKeyCodeState(getDeviceId(), keyCode);
+}
+
+int32_t KeyboardInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+}
+
+bool KeyboardInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ return getEventHub()->markSupportedKeyCodes(getDeviceId(), numCodes, keyCodes, outFlags);
+}
+
+int32_t KeyboardInputMapper::getMetaState() {
+ return mMetaState;
+}
+
+void KeyboardInputMapper::resetLedState() {
+ initializeLedState(mCapsLockLedState, LED_CAPSL);
+ initializeLedState(mNumLockLedState, LED_NUML);
+ initializeLedState(mScrollLockLedState, LED_SCROLLL);
+
+ updateLedState(true);
+}
+
+void KeyboardInputMapper::initializeLedState(LedState& ledState, int32_t led) {
+ ledState.avail = getEventHub()->hasLed(getDeviceId(), led);
+ ledState.on = false;
+}
+
+void KeyboardInputMapper::updateLedState(bool reset) {
+ updateLedStateForModifier(mCapsLockLedState, LED_CAPSL,
+ AMETA_CAPS_LOCK_ON, reset);
+ updateLedStateForModifier(mNumLockLedState, LED_NUML,
+ AMETA_NUM_LOCK_ON, reset);
+ updateLedStateForModifier(mScrollLockLedState, LED_SCROLLL,
+ AMETA_SCROLL_LOCK_ON, reset);
+}
+
+void KeyboardInputMapper::updateLedStateForModifier(LedState& ledState,
+ int32_t led, int32_t modifier, bool reset) {
+ if (ledState.avail) {
+ bool desiredState = (mMetaState & modifier) != 0;
+ if (reset || ledState.on != desiredState) {
+ getEventHub()->setLedState(getDeviceId(), led, desiredState);
+ ledState.on = desiredState;
+ }
+ }
+}
+
+
+// --- CursorInputMapper ---
+
+CursorInputMapper::CursorInputMapper(InputDevice* device) :
+ InputMapper(device) {
+}
+
+CursorInputMapper::~CursorInputMapper() {
+}
+
+uint32_t CursorInputMapper::getSources() {
+ return mSource;
+}
+
+void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ if (mParameters.mode == Parameters::MODE_POINTER) {
+ float minX, minY, maxX, maxY;
+ if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f);
+ }
+ } else {
+ info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f);
+ }
+ info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+
+ if (mCursorScrollAccumulator.haveRelativeVWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+ }
+ if (mCursorScrollAccumulator.haveRelativeHWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+ }
+}
+
+void CursorInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Cursor Input Mapper:\n");
+ dumpParameters(dump);
+ dump.appendFormat(INDENT3 "XScale: %0.3f\n", mXScale);
+ dump.appendFormat(INDENT3 "YScale: %0.3f\n", mYScale);
+ dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
+ dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
+ dump.appendFormat(INDENT3 "HaveVWheel: %s\n",
+ toString(mCursorScrollAccumulator.haveRelativeVWheel()));
+ dump.appendFormat(INDENT3 "HaveHWheel: %s\n",
+ toString(mCursorScrollAccumulator.haveRelativeHWheel()));
+ dump.appendFormat(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
+ dump.appendFormat(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
+ dump.appendFormat(INDENT3 "Orientation: %d\n", mOrientation);
+ dump.appendFormat(INDENT3 "ButtonState: 0x%08x\n", mButtonState);
+ dump.appendFormat(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState)));
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", mDownTime);
+}
+
+void CursorInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ mCursorScrollAccumulator.configure(getDevice());
+
+ // Configure basic parameters.
+ configureParameters();
+
+ // Configure device mode.
+ switch (mParameters.mode) {
+ case Parameters::MODE_POINTER:
+ mSource = AINPUT_SOURCE_MOUSE;
+ mXPrecision = 1.0f;
+ mYPrecision = 1.0f;
+ mXScale = 1.0f;
+ mYScale = 1.0f;
+ mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+ break;
+ case Parameters::MODE_NAVIGATION:
+ mSource = AINPUT_SOURCE_TRACKBALL;
+ mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+ mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+ mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+ mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+ break;
+ }
+
+ mVWheelScale = 1.0f;
+ mHWheelScale = 1.0f;
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
+ mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters);
+ mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters);
+ mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters);
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+ DisplayViewport v;
+ if (config->getDisplayInfo(false /*external*/, &v)) {
+ mOrientation = v.orientation;
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ bumpGeneration();
+ }
+}
+
+void CursorInputMapper::configureParameters() {
+ mParameters.mode = Parameters::MODE_POINTER;
+ String8 cursorModeString;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("cursor.mode"), cursorModeString)) {
+ if (cursorModeString == "navigation") {
+ mParameters.mode = Parameters::MODE_NAVIGATION;
+ } else if (cursorModeString != "pointer" && cursorModeString != "default") {
+ ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string());
+ }
+ }
+
+ mParameters.orientationAware = false;
+ getDevice()->getConfiguration().tryGetProperty(String8("cursor.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.hasAssociatedDisplay = false;
+ if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) {
+ mParameters.hasAssociatedDisplay = true;
+ }
+}
+
+void CursorInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+ dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n",
+ toString(mParameters.hasAssociatedDisplay));
+
+ switch (mParameters.mode) {
+ case Parameters::MODE_POINTER:
+ dump.append(INDENT4 "Mode: pointer\n");
+ break;
+ case Parameters::MODE_NAVIGATION:
+ dump.append(INDENT4 "Mode: navigation\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void CursorInputMapper::reset(nsecs_t when) {
+ mButtonState = 0;
+ mDownTime = 0;
+
+ mPointerVelocityControl.reset();
+ mWheelXVelocityControl.reset();
+ mWheelYVelocityControl.reset();
+
+ mCursorButtonAccumulator.reset(getDevice());
+ mCursorMotionAccumulator.reset(getDevice());
+ mCursorScrollAccumulator.reset(getDevice());
+
+ InputMapper::reset(when);
+}
+
+void CursorInputMapper::process(const RawEvent* rawEvent) {
+ mCursorButtonAccumulator.process(rawEvent);
+ mCursorMotionAccumulator.process(rawEvent);
+ mCursorScrollAccumulator.process(rawEvent);
+
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ sync(rawEvent->when);
+ }
+}
+
+void CursorInputMapper::sync(nsecs_t when) {
+ int32_t lastButtonState = mButtonState;
+ int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
+ mButtonState = currentButtonState;
+
+ bool wasDown = isPointerDown(lastButtonState);
+ bool down = isPointerDown(currentButtonState);
+ bool downChanged;
+ if (!wasDown && down) {
+ mDownTime = when;
+ downChanged = true;
+ } else if (wasDown && !down) {
+ downChanged = true;
+ } else {
+ downChanged = false;
+ }
+ nsecs_t downTime = mDownTime;
+ bool buttonsChanged = currentButtonState != lastButtonState;
+ bool buttonsPressed = currentButtonState & ~lastButtonState;
+
+ float deltaX = mCursorMotionAccumulator.getRelativeX() * mXScale;
+ float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale;
+ bool moved = deltaX != 0 || deltaY != 0;
+
+ // Rotate delta according to orientation if needed.
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay
+ && (deltaX != 0.0f || deltaY != 0.0f)) {
+ rotateDelta(mOrientation, &deltaX, &deltaY);
+ }
+
+ // Move the pointer.
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+
+ float vscroll = mCursorScrollAccumulator.getRelativeVWheel();
+ float hscroll = mCursorScrollAccumulator.getRelativeHWheel();
+ bool scrolled = vscroll != 0 || hscroll != 0;
+
+ mWheelYVelocityControl.move(when, NULL, &vscroll);
+ mWheelXVelocityControl.move(when, &hscroll, NULL);
+
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ int32_t displayId;
+ if (mPointerController != NULL) {
+ if (moved || scrolled || buttonsChanged) {
+ mPointerController->setPresentation(
+ PointerControllerInterface::PRESENTATION_POINTER);
+
+ if (moved) {
+ mPointerController->move(deltaX, deltaY);
+ }
+
+ if (buttonsChanged) {
+ mPointerController->setButtonState(currentButtonState);
+ }
+
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ }
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ displayId = ADISPLAY_ID_DEFAULT;
+ } else {
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY);
+ displayId = ADISPLAY_ID_NONE;
+ }
+
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
+
+ // Moving an external trackball or mouse should wake the device.
+ // We don't do this for internal cursor devices to prevent them from waking up
+ // the device in your pocket.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ uint32_t policyFlags = 0;
+ if ((buttonsPressed || moved || scrolled) && getDevice()->isExternal()) {
+ policyFlags |= POLICY_FLAG_WAKE_DROPPED;
+ }
+
+ // Synthesize key down from buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
+ policyFlags, lastButtonState, currentButtonState);
+
+ // Send motion event.
+ if (downChanged || moved || scrolled || buttonsChanged) {
+ int32_t metaState = mContext->getGlobalMetaState();
+ int32_t motionEventAction;
+ if (downChanged) {
+ motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+ } else if (down || mPointerController == NULL) {
+ motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+ } else {
+ motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE;
+ }
+
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ motionEventAction, 0, metaState, currentButtonState, 0,
+ displayId, 1, &pointerProperties, &pointerCoords,
+ mXPrecision, mYPrecision, downTime);
+ getListener()->notifyMotion(&args);
+
+ // Send hover move after UP to tell the application that the mouse is hovering now.
+ if (motionEventAction == AMOTION_EVENT_ACTION_UP
+ && mPointerController != NULL) {
+ NotifyMotionArgs hoverArgs(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, currentButtonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ displayId, 1, &pointerProperties, &pointerCoords,
+ mXPrecision, mYPrecision, downTime);
+ getListener()->notifyMotion(&hoverArgs);
+ }
+
+ // Send scroll events.
+ if (scrolled) {
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
+
+ NotifyMotionArgs scrollArgs(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_SCROLL, 0, metaState, currentButtonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ displayId, 1, &pointerProperties, &pointerCoords,
+ mXPrecision, mYPrecision, downTime);
+ getListener()->notifyMotion(&scrollArgs);
+ }
+ }
+
+ // Synthesize key up from buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+ policyFlags, lastButtonState, currentButtonState);
+
+ mCursorMotionAccumulator.finishSync();
+ mCursorScrollAccumulator.finishSync();
+}
+
+int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) {
+ return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+ } else {
+ return AKEY_STATE_UNKNOWN;
+ }
+}
+
+void CursorInputMapper::fadePointer() {
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+}
+
+
+// --- TouchInputMapper ---
+
+TouchInputMapper::TouchInputMapper(InputDevice* device) :
+ InputMapper(device),
+ mSource(0), mDeviceMode(DEVICE_MODE_DISABLED),
+ mSurfaceWidth(-1), mSurfaceHeight(-1), mSurfaceLeft(0), mSurfaceTop(0),
+ mSurfaceOrientation(DISPLAY_ORIENTATION_0) {
+}
+
+TouchInputMapper::~TouchInputMapper() {
+}
+
+uint32_t TouchInputMapper::getSources() {
+ return mSource;
+}
+
+void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ if (mDeviceMode != DEVICE_MODE_DISABLED) {
+ info->addMotionRange(mOrientedRanges.x);
+ info->addMotionRange(mOrientedRanges.y);
+ info->addMotionRange(mOrientedRanges.pressure);
+
+ if (mOrientedRanges.haveSize) {
+ info->addMotionRange(mOrientedRanges.size);
+ }
+
+ if (mOrientedRanges.haveTouchSize) {
+ info->addMotionRange(mOrientedRanges.touchMajor);
+ info->addMotionRange(mOrientedRanges.touchMinor);
+ }
+
+ if (mOrientedRanges.haveToolSize) {
+ info->addMotionRange(mOrientedRanges.toolMajor);
+ info->addMotionRange(mOrientedRanges.toolMinor);
+ }
+
+ if (mOrientedRanges.haveOrientation) {
+ info->addMotionRange(mOrientedRanges.orientation);
+ }
+
+ if (mOrientedRanges.haveDistance) {
+ info->addMotionRange(mOrientedRanges.distance);
+ }
+
+ if (mOrientedRanges.haveTilt) {
+ info->addMotionRange(mOrientedRanges.tilt);
+ }
+
+ if (mCursorScrollAccumulator.haveRelativeVWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f);
+ }
+ if (mCursorScrollAccumulator.haveRelativeHWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f);
+ }
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+ const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
+ const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat,
+ x.fuzz, x.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat,
+ y.fuzz, y.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat,
+ x.fuzz, x.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat,
+ y.fuzz, y.resolution);
+ }
+ }
+}
+
+void TouchInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Touch Input Mapper:\n");
+ dumpParameters(dump);
+ dumpVirtualKeys(dump);
+ dumpRawPointerAxes(dump);
+ dumpCalibration(dump);
+ dumpSurface(dump);
+
+ dump.appendFormat(INDENT3 "Translation and Scaling Factors:\n");
+ dump.appendFormat(INDENT4 "XTranslate: %0.3f\n", mXTranslate);
+ dump.appendFormat(INDENT4 "YTranslate: %0.3f\n", mYTranslate);
+ dump.appendFormat(INDENT4 "XScale: %0.3f\n", mXScale);
+ dump.appendFormat(INDENT4 "YScale: %0.3f\n", mYScale);
+ dump.appendFormat(INDENT4 "XPrecision: %0.3f\n", mXPrecision);
+ dump.appendFormat(INDENT4 "YPrecision: %0.3f\n", mYPrecision);
+ dump.appendFormat(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale);
+ dump.appendFormat(INDENT4 "PressureScale: %0.3f\n", mPressureScale);
+ dump.appendFormat(INDENT4 "SizeScale: %0.3f\n", mSizeScale);
+ dump.appendFormat(INDENT4 "OrientationScale: %0.3f\n", mOrientationScale);
+ dump.appendFormat(INDENT4 "DistanceScale: %0.3f\n", mDistanceScale);
+ dump.appendFormat(INDENT4 "HaveTilt: %s\n", toString(mHaveTilt));
+ dump.appendFormat(INDENT4 "TiltXCenter: %0.3f\n", mTiltXCenter);
+ dump.appendFormat(INDENT4 "TiltXScale: %0.3f\n", mTiltXScale);
+ dump.appendFormat(INDENT4 "TiltYCenter: %0.3f\n", mTiltYCenter);
+ dump.appendFormat(INDENT4 "TiltYScale: %0.3f\n", mTiltYScale);
+
+ dump.appendFormat(INDENT3 "Last Button State: 0x%08x\n", mLastButtonState);
+
+ dump.appendFormat(INDENT3 "Last Raw Touch: pointerCount=%d\n",
+ mLastRawPointerData.pointerCount);
+ for (uint32_t i = 0; i < mLastRawPointerData.pointerCount; i++) {
+ const RawPointerData::Pointer& pointer = mLastRawPointerData.pointers[i];
+ dump.appendFormat(INDENT4 "[%d]: id=%d, x=%d, y=%d, pressure=%d, "
+ "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, "
+ "orientation=%d, tiltX=%d, tiltY=%d, distance=%d, "
+ "toolType=%d, isHovering=%s\n", i,
+ pointer.id, pointer.x, pointer.y, pointer.pressure,
+ pointer.touchMajor, pointer.touchMinor,
+ pointer.toolMajor, pointer.toolMinor,
+ pointer.orientation, pointer.tiltX, pointer.tiltY, pointer.distance,
+ pointer.toolType, toString(pointer.isHovering));
+ }
+
+ dump.appendFormat(INDENT3 "Last Cooked Touch: pointerCount=%d\n",
+ mLastCookedPointerData.pointerCount);
+ for (uint32_t i = 0; i < mLastCookedPointerData.pointerCount; i++) {
+ const PointerProperties& pointerProperties = mLastCookedPointerData.pointerProperties[i];
+ const PointerCoords& pointerCoords = mLastCookedPointerData.pointerCoords[i];
+ dump.appendFormat(INDENT4 "[%d]: id=%d, x=%0.3f, y=%0.3f, pressure=%0.3f, "
+ "touchMajor=%0.3f, touchMinor=%0.3f, toolMajor=%0.3f, toolMinor=%0.3f, "
+ "orientation=%0.3f, tilt=%0.3f, distance=%0.3f, "
+ "toolType=%d, isHovering=%s\n", i,
+ pointerProperties.id,
+ pointerCoords.getX(),
+ pointerCoords.getY(),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TILT),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE),
+ pointerProperties.toolType,
+ toString(mLastCookedPointerData.isHovering(i)));
+ }
+
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ dump.appendFormat(INDENT3 "Pointer Gesture Detector:\n");
+ dump.appendFormat(INDENT4 "XMovementScale: %0.3f\n",
+ mPointerXMovementScale);
+ dump.appendFormat(INDENT4 "YMovementScale: %0.3f\n",
+ mPointerYMovementScale);
+ dump.appendFormat(INDENT4 "XZoomScale: %0.3f\n",
+ mPointerXZoomScale);
+ dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n",
+ mPointerYZoomScale);
+ dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n",
+ mPointerGestureMaxSwipeWidth);
+ }
+}
+
+void TouchInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ mConfig = *config;
+
+ if (!changes) { // first time only
+ // Configure basic parameters.
+ configureParameters();
+
+ // Configure common accumulators.
+ mCursorScrollAccumulator.configure(getDevice());
+ mTouchButtonAccumulator.configure(getDevice());
+
+ // Configure absolute axis information.
+ configureRawPointerAxes();
+
+ // Prepare input device calibration.
+ parseCalibration();
+ resolveCalibration();
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
+ // Update pointer speed.
+ mPointerVelocityControl.setParameters(mConfig.pointerVelocityControlParameters);
+ mWheelXVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
+ mWheelYVelocityControl.setParameters(mConfig.wheelVelocityControlParameters);
+ }
+
+ bool resetNeeded = false;
+ if (!changes || (changes & (InputReaderConfiguration::CHANGE_DISPLAY_INFO
+ | InputReaderConfiguration::CHANGE_POINTER_GESTURE_ENABLEMENT
+ | InputReaderConfiguration::CHANGE_SHOW_TOUCHES))) {
+ // Configure device sources, surface dimensions, orientation and
+ // scaling factors.
+ configureSurface(when, &resetNeeded);
+ }
+
+ if (changes && resetNeeded) {
+ // Send reset, unless this is the first time the device has been configured,
+ // in which case the reader will call reset itself after all mappers are ready.
+ getDevice()->notifyReset(when);
+ }
+}
+
+void TouchInputMapper::configureParameters() {
+ // Use the pointer presentation mode for devices that do not support distinct
+ // multitouch. The spot-based presentation relies on being able to accurately
+ // locate two or more fingers on the touch pad.
+ mParameters.gestureMode = getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_SEMI_MT)
+ ? Parameters::GESTURE_MODE_POINTER : Parameters::GESTURE_MODE_SPOTS;
+
+ String8 gestureModeString;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("touch.gestureMode"),
+ gestureModeString)) {
+ if (gestureModeString == "pointer") {
+ mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER;
+ } else if (gestureModeString == "spots") {
+ mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS;
+ } else if (gestureModeString != "default") {
+ ALOGW("Invalid value for touch.gestureMode: '%s'", gestureModeString.string());
+ }
+ }
+
+ if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_DIRECT)) {
+ // The device is a touch screen.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ } else if (getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_POINTER)) {
+ // The device is a pointing device like a track pad.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+ } else if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X)
+ || getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
+ // The device is a cursor device with a touch pad attached.
+ // By default don't use the touch pad to move the pointer.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+ } else {
+ // The device is a touch pad of unknown purpose.
+ mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+ }
+
+ String8 deviceTypeString;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"),
+ deviceTypeString)) {
+ if (deviceTypeString == "touchScreen") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ } else if (deviceTypeString == "touchPad") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+ } else if (deviceTypeString == "touchNavigation") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_NAVIGATION;
+ } else if (deviceTypeString == "pointer") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+ } else if (deviceTypeString != "default") {
+ ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
+ }
+ }
+
+ mParameters.orientationAware = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.hasAssociatedDisplay = false;
+ mParameters.associatedDisplayIsExternal = false;
+ if (mParameters.orientationAware
+ || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
+ || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
+ mParameters.hasAssociatedDisplay = true;
+ mParameters.associatedDisplayIsExternal =
+ mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
+ && getDevice()->isExternal();
+ }
+}
+
+void TouchInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+
+ switch (mParameters.gestureMode) {
+ case Parameters::GESTURE_MODE_POINTER:
+ dump.append(INDENT4 "GestureMode: pointer\n");
+ break;
+ case Parameters::GESTURE_MODE_SPOTS:
+ dump.append(INDENT4 "GestureMode: spots\n");
+ break;
+ default:
+ assert(false);
+ }
+
+ switch (mParameters.deviceType) {
+ case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
+ dump.append(INDENT4 "DeviceType: touchScreen\n");
+ break;
+ case Parameters::DEVICE_TYPE_TOUCH_PAD:
+ dump.append(INDENT4 "DeviceType: touchPad\n");
+ break;
+ case Parameters::DEVICE_TYPE_TOUCH_NAVIGATION:
+ dump.append(INDENT4 "DeviceType: touchNavigation\n");
+ break;
+ case Parameters::DEVICE_TYPE_POINTER:
+ dump.append(INDENT4 "DeviceType: pointer\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ dump.appendFormat(INDENT4 "AssociatedDisplay: hasAssociatedDisplay=%s, isExternal=%s\n",
+ toString(mParameters.hasAssociatedDisplay),
+ toString(mParameters.associatedDisplayIsExternal));
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void TouchInputMapper::configureRawPointerAxes() {
+ mRawPointerAxes.clear();
+}
+
+void TouchInputMapper::dumpRawPointerAxes(String8& dump) {
+ dump.append(INDENT3 "Raw Touch Axes:\n");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.x, "X");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.y, "Y");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.pressure, "Pressure");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMajor, "TouchMajor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMinor, "TouchMinor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMajor, "ToolMajor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMinor, "ToolMinor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.orientation, "Orientation");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.distance, "Distance");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltX, "TiltX");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltY, "TiltY");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.trackingId, "TrackingId");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.slot, "Slot");
+}
+
+void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
+ int32_t oldDeviceMode = mDeviceMode;
+
+ // Determine device mode.
+ if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER
+ && mConfig.pointerGesturesEnabled) {
+ mSource = AINPUT_SOURCE_MOUSE;
+ mDeviceMode = DEVICE_MODE_POINTER;
+ if (hasStylus()) {
+ mSource |= AINPUT_SOURCE_STYLUS;
+ }
+ } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
+ && mParameters.hasAssociatedDisplay) {
+ mSource = AINPUT_SOURCE_TOUCHSCREEN;
+ mDeviceMode = DEVICE_MODE_DIRECT;
+ if (hasStylus()) {
+ mSource |= AINPUT_SOURCE_STYLUS;
+ }
+ } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_NAVIGATION) {
+ mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
+ mDeviceMode = DEVICE_MODE_NAVIGATION;
+ } else {
+ mSource = AINPUT_SOURCE_TOUCHPAD;
+ mDeviceMode = DEVICE_MODE_UNSCALED;
+ }
+
+ // Ensure we have valid X and Y axes.
+ if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
+ ALOGW(INDENT "Touch device '%s' did not report support for X or Y axis! "
+ "The device will be inoperable.", getDeviceName().string());
+ mDeviceMode = DEVICE_MODE_DISABLED;
+ return;
+ }
+
+ // Raw width and height in the natural orientation.
+ int32_t rawWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1;
+ int32_t rawHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1;
+
+ // Get associated display dimensions.
+ bool viewportChanged = false;
+ DisplayViewport newViewport;
+ if (mParameters.hasAssociatedDisplay) {
+ if (!mConfig.getDisplayInfo(mParameters.associatedDisplayIsExternal, &newViewport)) {
+ ALOGI(INDENT "Touch device '%s' could not query the properties of its associated "
+ "display. The device will be inoperable until the display size "
+ "becomes available.",
+ getDeviceName().string());
+ mDeviceMode = DEVICE_MODE_DISABLED;
+ return;
+ }
+ } else {
+ newViewport.setNonDisplayViewport(rawWidth, rawHeight);
+ }
+ if (mViewport != newViewport) {
+ mViewport = newViewport;
+ viewportChanged = true;
+
+ if (mDeviceMode == DEVICE_MODE_DIRECT || mDeviceMode == DEVICE_MODE_POINTER) {
+ // Convert rotated viewport to natural surface coordinates.
+ int32_t naturalLogicalWidth, naturalLogicalHeight;
+ int32_t naturalPhysicalWidth, naturalPhysicalHeight;
+ int32_t naturalPhysicalLeft, naturalPhysicalTop;
+ int32_t naturalDeviceWidth, naturalDeviceHeight;
+ switch (mViewport.orientation) {
+ case DISPLAY_ORIENTATION_90:
+ naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom;
+ naturalPhysicalTop = mViewport.physicalLeft;
+ naturalDeviceWidth = mViewport.deviceHeight;
+ naturalDeviceHeight = mViewport.deviceWidth;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight;
+ naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom;
+ naturalDeviceWidth = mViewport.deviceWidth;
+ naturalDeviceHeight = mViewport.deviceHeight;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalLeft = mViewport.physicalTop;
+ naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight;
+ naturalDeviceWidth = mViewport.deviceHeight;
+ naturalDeviceHeight = mViewport.deviceWidth;
+ break;
+ case DISPLAY_ORIENTATION_0:
+ default:
+ naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalLeft = mViewport.physicalLeft;
+ naturalPhysicalTop = mViewport.physicalTop;
+ naturalDeviceWidth = mViewport.deviceWidth;
+ naturalDeviceHeight = mViewport.deviceHeight;
+ break;
+ }
+
+ mSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
+ mSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
+ mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
+ mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
+
+ mSurfaceOrientation = mParameters.orientationAware ?
+ mViewport.orientation : DISPLAY_ORIENTATION_0;
+ } else {
+ mSurfaceWidth = rawWidth;
+ mSurfaceHeight = rawHeight;
+ mSurfaceLeft = 0;
+ mSurfaceTop = 0;
+ mSurfaceOrientation = DISPLAY_ORIENTATION_0;
+ }
+ }
+
+ // If moving between pointer modes, need to reset some state.
+ bool deviceModeChanged = mDeviceMode != oldDeviceMode;
+ if (deviceModeChanged) {
+ mOrientedRanges.clear();
+ }
+
+ // Create pointer controller if needed.
+ if (mDeviceMode == DEVICE_MODE_POINTER ||
+ (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches)) {
+ if (mPointerController == NULL) {
+ mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+ }
+ } else {
+ mPointerController.clear();
+ }
+
+ if (viewportChanged || deviceModeChanged) {
+ ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
+ "display id %d",
+ getDeviceId(), getDeviceName().string(), mSurfaceWidth, mSurfaceHeight,
+ mSurfaceOrientation, mDeviceMode, mViewport.displayId);
+
+ // Configure X and Y factors.
+ mXScale = float(mSurfaceWidth) / rawWidth;
+ mYScale = float(mSurfaceHeight) / rawHeight;
+ mXTranslate = -mSurfaceLeft;
+ mYTranslate = -mSurfaceTop;
+ mXPrecision = 1.0f / mXScale;
+ mYPrecision = 1.0f / mYScale;
+
+ mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
+ mOrientedRanges.x.source = mSource;
+ mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y;
+ mOrientedRanges.y.source = mSource;
+
+ configureVirtualKeys();
+
+ // Scale factor for terms that are not oriented in a particular axis.
+ // If the pixels are square then xScale == yScale otherwise we fake it
+ // by choosing an average.
+ mGeometricScale = avg(mXScale, mYScale);
+
+ // Size of diagonal axis.
+ float diagonalSize = hypotf(mSurfaceWidth, mSurfaceHeight);
+
+ // Size factors.
+ if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) {
+ if (mRawPointerAxes.touchMajor.valid
+ && mRawPointerAxes.touchMajor.maxValue != 0) {
+ mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue;
+ } else if (mRawPointerAxes.toolMajor.valid
+ && mRawPointerAxes.toolMajor.maxValue != 0) {
+ mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue;
+ } else {
+ mSizeScale = 0.0f;
+ }
+
+ mOrientedRanges.haveTouchSize = true;
+ mOrientedRanges.haveToolSize = true;
+ mOrientedRanges.haveSize = true;
+
+ mOrientedRanges.touchMajor.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR;
+ mOrientedRanges.touchMajor.source = mSource;
+ mOrientedRanges.touchMajor.min = 0;
+ mOrientedRanges.touchMajor.max = diagonalSize;
+ mOrientedRanges.touchMajor.flat = 0;
+ mOrientedRanges.touchMajor.fuzz = 0;
+ mOrientedRanges.touchMajor.resolution = 0;
+
+ mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
+ mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
+
+ mOrientedRanges.toolMajor.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR;
+ mOrientedRanges.toolMajor.source = mSource;
+ mOrientedRanges.toolMajor.min = 0;
+ mOrientedRanges.toolMajor.max = diagonalSize;
+ mOrientedRanges.toolMajor.flat = 0;
+ mOrientedRanges.toolMajor.fuzz = 0;
+ mOrientedRanges.toolMajor.resolution = 0;
+
+ mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
+ mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
+
+ mOrientedRanges.size.axis = AMOTION_EVENT_AXIS_SIZE;
+ mOrientedRanges.size.source = mSource;
+ mOrientedRanges.size.min = 0;
+ mOrientedRanges.size.max = 1.0;
+ mOrientedRanges.size.flat = 0;
+ mOrientedRanges.size.fuzz = 0;
+ mOrientedRanges.size.resolution = 0;
+ } else {
+ mSizeScale = 0.0f;
+ }
+
+ // Pressure factors.
+ mPressureScale = 0;
+ if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL
+ || mCalibration.pressureCalibration
+ == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) {
+ if (mCalibration.havePressureScale) {
+ mPressureScale = mCalibration.pressureScale;
+ } else if (mRawPointerAxes.pressure.valid
+ && mRawPointerAxes.pressure.maxValue != 0) {
+ mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue;
+ }
+ }
+
+ mOrientedRanges.pressure.axis = AMOTION_EVENT_AXIS_PRESSURE;
+ mOrientedRanges.pressure.source = mSource;
+ mOrientedRanges.pressure.min = 0;
+ mOrientedRanges.pressure.max = 1.0;
+ mOrientedRanges.pressure.flat = 0;
+ mOrientedRanges.pressure.fuzz = 0;
+ mOrientedRanges.pressure.resolution = 0;
+
+ // Tilt
+ mTiltXCenter = 0;
+ mTiltXScale = 0;
+ mTiltYCenter = 0;
+ mTiltYScale = 0;
+ mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid;
+ if (mHaveTilt) {
+ mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue,
+ mRawPointerAxes.tiltX.maxValue);
+ mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue,
+ mRawPointerAxes.tiltY.maxValue);
+ mTiltXScale = M_PI / 180;
+ mTiltYScale = M_PI / 180;
+
+ mOrientedRanges.haveTilt = true;
+
+ mOrientedRanges.tilt.axis = AMOTION_EVENT_AXIS_TILT;
+ mOrientedRanges.tilt.source = mSource;
+ mOrientedRanges.tilt.min = 0;
+ mOrientedRanges.tilt.max = M_PI_2;
+ mOrientedRanges.tilt.flat = 0;
+ mOrientedRanges.tilt.fuzz = 0;
+ mOrientedRanges.tilt.resolution = 0;
+ }
+
+ // Orientation
+ mOrientationScale = 0;
+ if (mHaveTilt) {
+ mOrientedRanges.haveOrientation = true;
+
+ mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+ mOrientedRanges.orientation.source = mSource;
+ mOrientedRanges.orientation.min = -M_PI;
+ mOrientedRanges.orientation.max = M_PI;
+ mOrientedRanges.orientation.flat = 0;
+ mOrientedRanges.orientation.fuzz = 0;
+ mOrientedRanges.orientation.resolution = 0;
+ } else if (mCalibration.orientationCalibration !=
+ Calibration::ORIENTATION_CALIBRATION_NONE) {
+ if (mCalibration.orientationCalibration
+ == Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) {
+ if (mRawPointerAxes.orientation.valid) {
+ if (mRawPointerAxes.orientation.maxValue > 0) {
+ mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue;
+ } else if (mRawPointerAxes.orientation.minValue < 0) {
+ mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue;
+ } else {
+ mOrientationScale = 0;
+ }
+ }
+ }
+
+ mOrientedRanges.haveOrientation = true;
+
+ mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+ mOrientedRanges.orientation.source = mSource;
+ mOrientedRanges.orientation.min = -M_PI_2;
+ mOrientedRanges.orientation.max = M_PI_2;
+ mOrientedRanges.orientation.flat = 0;
+ mOrientedRanges.orientation.fuzz = 0;
+ mOrientedRanges.orientation.resolution = 0;
+ }
+
+ // Distance
+ mDistanceScale = 0;
+ if (mCalibration.distanceCalibration != Calibration::DISTANCE_CALIBRATION_NONE) {
+ if (mCalibration.distanceCalibration
+ == Calibration::DISTANCE_CALIBRATION_SCALED) {
+ if (mCalibration.haveDistanceScale) {
+ mDistanceScale = mCalibration.distanceScale;
+ } else {
+ mDistanceScale = 1.0f;
+ }
+ }
+
+ mOrientedRanges.haveDistance = true;
+
+ mOrientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE;
+ mOrientedRanges.distance.source = mSource;
+ mOrientedRanges.distance.min =
+ mRawPointerAxes.distance.minValue * mDistanceScale;
+ mOrientedRanges.distance.max =
+ mRawPointerAxes.distance.maxValue * mDistanceScale;
+ mOrientedRanges.distance.flat = 0;
+ mOrientedRanges.distance.fuzz =
+ mRawPointerAxes.distance.fuzz * mDistanceScale;
+ mOrientedRanges.distance.resolution = 0;
+ }
+
+ // Compute oriented precision, scales and ranges.
+ // Note that the maximum value reported is an inclusive maximum value so it is one
+ // unit less than the total width or height of surface.
+ switch (mSurfaceOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ case DISPLAY_ORIENTATION_270:
+ mOrientedXPrecision = mYPrecision;
+ mOrientedYPrecision = mXPrecision;
+
+ mOrientedRanges.x.min = mYTranslate;
+ mOrientedRanges.x.max = mSurfaceHeight + mYTranslate - 1;
+ mOrientedRanges.x.flat = 0;
+ mOrientedRanges.x.fuzz = 0;
+ mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
+
+ mOrientedRanges.y.min = mXTranslate;
+ mOrientedRanges.y.max = mSurfaceWidth + mXTranslate - 1;
+ mOrientedRanges.y.flat = 0;
+ mOrientedRanges.y.fuzz = 0;
+ mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
+ break;
+
+ default:
+ mOrientedXPrecision = mXPrecision;
+ mOrientedYPrecision = mYPrecision;
+
+ mOrientedRanges.x.min = mXTranslate;
+ mOrientedRanges.x.max = mSurfaceWidth + mXTranslate - 1;
+ mOrientedRanges.x.flat = 0;
+ mOrientedRanges.x.fuzz = 0;
+ mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
+
+ mOrientedRanges.y.min = mYTranslate;
+ mOrientedRanges.y.max = mSurfaceHeight + mYTranslate - 1;
+ mOrientedRanges.y.flat = 0;
+ mOrientedRanges.y.fuzz = 0;
+ mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
+ break;
+ }
+
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ // Compute pointer gesture detection parameters.
+ float rawDiagonal = hypotf(rawWidth, rawHeight);
+ float displayDiagonal = hypotf(mSurfaceWidth, mSurfaceHeight);
+
+ // Scale movements such that one whole swipe of the touch pad covers a
+ // given area relative to the diagonal size of the display when no acceleration
+ // is applied.
+ // Assume that the touch pad has a square aspect ratio such that movements in
+ // X and Y of the same number of raw units cover the same physical distance.
+ mPointerXMovementScale = mConfig.pointerGestureMovementSpeedRatio
+ * displayDiagonal / rawDiagonal;
+ mPointerYMovementScale = mPointerXMovementScale;
+
+ // Scale zooms to cover a smaller range of the display than movements do.
+ // This value determines the area around the pointer that is affected by freeform
+ // pointer gestures.
+ mPointerXZoomScale = mConfig.pointerGestureZoomSpeedRatio
+ * displayDiagonal / rawDiagonal;
+ mPointerYZoomScale = mPointerXZoomScale;
+
+ // Max width between pointers to detect a swipe gesture is more than some fraction
+ // of the diagonal axis of the touch pad. Touches that are wider than this are
+ // translated into freeform gestures.
+ mPointerGestureMaxSwipeWidth =
+ mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal;
+
+ // Abort current pointer usages because the state has changed.
+ abortPointerUsage(when, 0 /*policyFlags*/);
+ }
+
+ // Inform the dispatcher about the changes.
+ *outResetNeeded = true;
+ bumpGeneration();
+ }
+}
+
+void TouchInputMapper::dumpSurface(String8& dump) {
+ dump.appendFormat(INDENT3 "Viewport: displayId=%d, orientation=%d, "
+ "logicalFrame=[%d, %d, %d, %d], "
+ "physicalFrame=[%d, %d, %d, %d], "
+ "deviceSize=[%d, %d]\n",
+ mViewport.displayId, mViewport.orientation,
+ mViewport.logicalLeft, mViewport.logicalTop,
+ mViewport.logicalRight, mViewport.logicalBottom,
+ mViewport.physicalLeft, mViewport.physicalTop,
+ mViewport.physicalRight, mViewport.physicalBottom,
+ mViewport.deviceWidth, mViewport.deviceHeight);
+
+ dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mSurfaceWidth);
+ dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mSurfaceHeight);
+ dump.appendFormat(INDENT3 "SurfaceLeft: %d\n", mSurfaceLeft);
+ dump.appendFormat(INDENT3 "SurfaceTop: %d\n", mSurfaceTop);
+ dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mSurfaceOrientation);
+}
+
+void TouchInputMapper::configureVirtualKeys() {
+ Vector<VirtualKeyDefinition> virtualKeyDefinitions;
+ getEventHub()->getVirtualKeyDefinitions(getDeviceId(), virtualKeyDefinitions);
+
+ mVirtualKeys.clear();
+
+ if (virtualKeyDefinitions.size() == 0) {
+ return;
+ }
+
+ mVirtualKeys.setCapacity(virtualKeyDefinitions.size());
+
+ int32_t touchScreenLeft = mRawPointerAxes.x.minValue;
+ int32_t touchScreenTop = mRawPointerAxes.y.minValue;
+ int32_t touchScreenWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1;
+ int32_t touchScreenHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1;
+
+ for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
+ const VirtualKeyDefinition& virtualKeyDefinition =
+ virtualKeyDefinitions[i];
+
+ mVirtualKeys.add();
+ VirtualKey& virtualKey = mVirtualKeys.editTop();
+
+ virtualKey.scanCode = virtualKeyDefinition.scanCode;
+ int32_t keyCode;
+ uint32_t flags;
+ if (getEventHub()->mapKey(getDeviceId(), virtualKey.scanCode, 0, &keyCode, &flags)) {
+ ALOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring",
+ virtualKey.scanCode);
+ mVirtualKeys.pop(); // drop the key
+ continue;
+ }
+
+ virtualKey.keyCode = keyCode;
+ virtualKey.flags = flags;
+
+ // convert the key definition's display coordinates into touch coordinates for a hit box
+ int32_t halfWidth = virtualKeyDefinition.width / 2;
+ int32_t halfHeight = virtualKeyDefinition.height / 2;
+
+ virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth)
+ * touchScreenWidth / mSurfaceWidth + touchScreenLeft;
+ virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth)
+ * touchScreenWidth / mSurfaceWidth + touchScreenLeft;
+ virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight)
+ * touchScreenHeight / mSurfaceHeight + touchScreenTop;
+ virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
+ * touchScreenHeight / mSurfaceHeight + touchScreenTop;
+ }
+}
+
+void TouchInputMapper::dumpVirtualKeys(String8& dump) {
+ if (!mVirtualKeys.isEmpty()) {
+ dump.append(INDENT3 "Virtual Keys:\n");
+
+ for (size_t i = 0; i < mVirtualKeys.size(); i++) {
+ const VirtualKey& virtualKey = mVirtualKeys.itemAt(i);
+ dump.appendFormat(INDENT4 "%d: scanCode=%d, keyCode=%d, "
+ "hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n",
+ i, virtualKey.scanCode, virtualKey.keyCode,
+ virtualKey.hitLeft, virtualKey.hitRight,
+ virtualKey.hitTop, virtualKey.hitBottom);
+ }
+ }
+}
+
+void TouchInputMapper::parseCalibration() {
+ const PropertyMap& in = getDevice()->getConfiguration();
+ Calibration& out = mCalibration;
+
+ // Size
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_DEFAULT;
+ String8 sizeCalibrationString;
+ if (in.tryGetProperty(String8("touch.size.calibration"), sizeCalibrationString)) {
+ if (sizeCalibrationString == "none") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+ } else if (sizeCalibrationString == "geometric") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
+ } else if (sizeCalibrationString == "diameter") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_DIAMETER;
+ } else if (sizeCalibrationString == "box") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_BOX;
+ } else if (sizeCalibrationString == "area") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_AREA;
+ } else if (sizeCalibrationString != "default") {
+ ALOGW("Invalid value for touch.size.calibration: '%s'",
+ sizeCalibrationString.string());
+ }
+ }
+
+ out.haveSizeScale = in.tryGetProperty(String8("touch.size.scale"),
+ out.sizeScale);
+ out.haveSizeBias = in.tryGetProperty(String8("touch.size.bias"),
+ out.sizeBias);
+ out.haveSizeIsSummed = in.tryGetProperty(String8("touch.size.isSummed"),
+ out.sizeIsSummed);
+
+ // Pressure
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_DEFAULT;
+ String8 pressureCalibrationString;
+ if (in.tryGetProperty(String8("touch.pressure.calibration"), pressureCalibrationString)) {
+ if (pressureCalibrationString == "none") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+ } else if (pressureCalibrationString == "physical") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+ } else if (pressureCalibrationString == "amplitude") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
+ } else if (pressureCalibrationString != "default") {
+ ALOGW("Invalid value for touch.pressure.calibration: '%s'",
+ pressureCalibrationString.string());
+ }
+ }
+
+ out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"),
+ out.pressureScale);
+
+ // Orientation
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_DEFAULT;
+ String8 orientationCalibrationString;
+ if (in.tryGetProperty(String8("touch.orientation.calibration"), orientationCalibrationString)) {
+ if (orientationCalibrationString == "none") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+ } else if (orientationCalibrationString == "interpolated") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+ } else if (orientationCalibrationString == "vector") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_VECTOR;
+ } else if (orientationCalibrationString != "default") {
+ ALOGW("Invalid value for touch.orientation.calibration: '%s'",
+ orientationCalibrationString.string());
+ }
+ }
+
+ // Distance
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_DEFAULT;
+ String8 distanceCalibrationString;
+ if (in.tryGetProperty(String8("touch.distance.calibration"), distanceCalibrationString)) {
+ if (distanceCalibrationString == "none") {
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+ } else if (distanceCalibrationString == "scaled") {
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+ } else if (distanceCalibrationString != "default") {
+ ALOGW("Invalid value for touch.distance.calibration: '%s'",
+ distanceCalibrationString.string());
+ }
+ }
+
+ out.haveDistanceScale = in.tryGetProperty(String8("touch.distance.scale"),
+ out.distanceScale);
+
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_DEFAULT;
+ String8 coverageCalibrationString;
+ if (in.tryGetProperty(String8("touch.coverage.calibration"), coverageCalibrationString)) {
+ if (coverageCalibrationString == "none") {
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+ } else if (coverageCalibrationString == "box") {
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_BOX;
+ } else if (coverageCalibrationString != "default") {
+ ALOGW("Invalid value for touch.coverage.calibration: '%s'",
+ coverageCalibrationString.string());
+ }
+ }
+}
+
+void TouchInputMapper::resolveCalibration() {
+ // Size
+ if (mRawPointerAxes.touchMajor.valid || mRawPointerAxes.toolMajor.valid) {
+ if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DEFAULT) {
+ mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
+ }
+ } else {
+ mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+ }
+
+ // Pressure
+ if (mRawPointerAxes.pressure.valid) {
+ if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_DEFAULT) {
+ mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+ }
+ } else {
+ mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+ }
+
+ // Orientation
+ if (mRawPointerAxes.orientation.valid) {
+ if (mCalibration.orientationCalibration == Calibration::ORIENTATION_CALIBRATION_DEFAULT) {
+ mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+ }
+ } else {
+ mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+ }
+
+ // Distance
+ if (mRawPointerAxes.distance.valid) {
+ if (mCalibration.distanceCalibration == Calibration::DISTANCE_CALIBRATION_DEFAULT) {
+ mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+ }
+ } else {
+ mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+ }
+
+ // Coverage
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_DEFAULT) {
+ mCalibration.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+ }
+}
+
+void TouchInputMapper::dumpCalibration(String8& dump) {
+ dump.append(INDENT3 "Calibration:\n");
+
+ // Size
+ switch (mCalibration.sizeCalibration) {
+ case Calibration::SIZE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.size.calibration: none\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_GEOMETRIC:
+ dump.append(INDENT4 "touch.size.calibration: geometric\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_DIAMETER:
+ dump.append(INDENT4 "touch.size.calibration: diameter\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_BOX:
+ dump.append(INDENT4 "touch.size.calibration: box\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_AREA:
+ dump.append(INDENT4 "touch.size.calibration: area\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ if (mCalibration.haveSizeScale) {
+ dump.appendFormat(INDENT4 "touch.size.scale: %0.3f\n",
+ mCalibration.sizeScale);
+ }
+
+ if (mCalibration.haveSizeBias) {
+ dump.appendFormat(INDENT4 "touch.size.bias: %0.3f\n",
+ mCalibration.sizeBias);
+ }
+
+ if (mCalibration.haveSizeIsSummed) {
+ dump.appendFormat(INDENT4 "touch.size.isSummed: %s\n",
+ toString(mCalibration.sizeIsSummed));
+ }
+
+ // Pressure
+ switch (mCalibration.pressureCalibration) {
+ case Calibration::PRESSURE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.pressure.calibration: none\n");
+ break;
+ case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+ dump.append(INDENT4 "touch.pressure.calibration: physical\n");
+ break;
+ case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+ dump.append(INDENT4 "touch.pressure.calibration: amplitude\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ if (mCalibration.havePressureScale) {
+ dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n",
+ mCalibration.pressureScale);
+ }
+
+ // Orientation
+ switch (mCalibration.orientationCalibration) {
+ case Calibration::ORIENTATION_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.orientation.calibration: none\n");
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+ dump.append(INDENT4 "touch.orientation.calibration: interpolated\n");
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_VECTOR:
+ dump.append(INDENT4 "touch.orientation.calibration: vector\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ // Distance
+ switch (mCalibration.distanceCalibration) {
+ case Calibration::DISTANCE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.distance.calibration: none\n");
+ break;
+ case Calibration::DISTANCE_CALIBRATION_SCALED:
+ dump.append(INDENT4 "touch.distance.calibration: scaled\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ if (mCalibration.haveDistanceScale) {
+ dump.appendFormat(INDENT4 "touch.distance.scale: %0.3f\n",
+ mCalibration.distanceScale);
+ }
+
+ switch (mCalibration.coverageCalibration) {
+ case Calibration::COVERAGE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.coverage.calibration: none\n");
+ break;
+ case Calibration::COVERAGE_CALIBRATION_BOX:
+ dump.append(INDENT4 "touch.coverage.calibration: box\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+}
+
+void TouchInputMapper::reset(nsecs_t when) {
+ mCursorButtonAccumulator.reset(getDevice());
+ mCursorScrollAccumulator.reset(getDevice());
+ mTouchButtonAccumulator.reset(getDevice());
+
+ mPointerVelocityControl.reset();
+ mWheelXVelocityControl.reset();
+ mWheelYVelocityControl.reset();
+
+ mCurrentRawPointerData.clear();
+ mLastRawPointerData.clear();
+ mCurrentCookedPointerData.clear();
+ mLastCookedPointerData.clear();
+ mCurrentButtonState = 0;
+ mLastButtonState = 0;
+ mCurrentRawVScroll = 0;
+ mCurrentRawHScroll = 0;
+ mCurrentFingerIdBits.clear();
+ mLastFingerIdBits.clear();
+ mCurrentStylusIdBits.clear();
+ mLastStylusIdBits.clear();
+ mCurrentMouseIdBits.clear();
+ mLastMouseIdBits.clear();
+ mPointerUsage = POINTER_USAGE_NONE;
+ mSentHoverEnter = false;
+ mDownTime = 0;
+
+ mCurrentVirtualKey.down = false;
+
+ mPointerGesture.reset();
+ mPointerSimple.reset();
+
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ mPointerController->clearSpots();
+ }
+
+ InputMapper::reset(when);
+}
+
+void TouchInputMapper::process(const RawEvent* rawEvent) {
+ mCursorButtonAccumulator.process(rawEvent);
+ mCursorScrollAccumulator.process(rawEvent);
+ mTouchButtonAccumulator.process(rawEvent);
+
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ sync(rawEvent->when);
+ }
+}
+
+void TouchInputMapper::sync(nsecs_t when) {
+ // Sync button state.
+ mCurrentButtonState = mTouchButtonAccumulator.getButtonState()
+ | mCursorButtonAccumulator.getButtonState();
+
+ // Sync scroll state.
+ mCurrentRawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
+ mCurrentRawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
+ mCursorScrollAccumulator.finishSync();
+
+ // Sync touch state.
+ bool havePointerIds = true;
+ mCurrentRawPointerData.clear();
+ syncTouch(when, &havePointerIds);
+
+#if DEBUG_RAW_EVENTS
+ if (!havePointerIds) {
+ ALOGD("syncTouch: pointerCount %d -> %d, no pointer ids",
+ mLastRawPointerData.pointerCount,
+ mCurrentRawPointerData.pointerCount);
+ } else {
+ ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
+ "hovering ids 0x%08x -> 0x%08x",
+ mLastRawPointerData.pointerCount,
+ mCurrentRawPointerData.pointerCount,
+ mLastRawPointerData.touchingIdBits.value,
+ mCurrentRawPointerData.touchingIdBits.value,
+ mLastRawPointerData.hoveringIdBits.value,
+ mCurrentRawPointerData.hoveringIdBits.value);
+ }
+#endif
+
+ // Reset state that we will compute below.
+ mCurrentFingerIdBits.clear();
+ mCurrentStylusIdBits.clear();
+ mCurrentMouseIdBits.clear();
+ mCurrentCookedPointerData.clear();
+
+ if (mDeviceMode == DEVICE_MODE_DISABLED) {
+ // Drop all input if the device is disabled.
+ mCurrentRawPointerData.clear();
+ mCurrentButtonState = 0;
+ } else {
+ // Preprocess pointer data.
+ if (!havePointerIds) {
+ assignPointerIds();
+ }
+
+ // Handle policy on initial down or hover events.
+ uint32_t policyFlags = 0;
+ bool initialDown = mLastRawPointerData.pointerCount == 0
+ && mCurrentRawPointerData.pointerCount != 0;
+ bool buttonsPressed = mCurrentButtonState & ~mLastButtonState;
+ if (initialDown || buttonsPressed) {
+ // If this is a touch screen, hide the pointer on an initial down.
+ if (mDeviceMode == DEVICE_MODE_DIRECT) {
+ getContext()->fadePointer();
+ }
+
+ // Initial downs on external touch devices should wake the device.
+ // We don't do this for internal touch screens to prevent them from waking
+ // up in your pocket.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ if (getDevice()->isExternal()) {
+ policyFlags |= POLICY_FLAG_WAKE_DROPPED;
+ }
+ }
+
+ // Synthesize key down from raw buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
+ policyFlags, mLastButtonState, mCurrentButtonState);
+
+ // Consume raw off-screen touches before cooking pointer data.
+ // If touches are consumed, subsequent code will not receive any pointer data.
+ if (consumeRawTouches(when, policyFlags)) {
+ mCurrentRawPointerData.clear();
+ }
+
+ // Cook pointer data. This call populates the mCurrentCookedPointerData structure
+ // with cooked pointer data that has the same ids and indices as the raw data.
+ // The following code can use either the raw or cooked data, as needed.
+ cookPointerData();
+
+ // Dispatch the touches either directly or by translation through a pointer on screen.
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ for (BitSet32 idBits(mCurrentRawPointerData.touchingIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS
+ || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+ mCurrentStylusIdBits.markBit(id);
+ } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
+ || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ mCurrentFingerIdBits.markBit(id);
+ } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) {
+ mCurrentMouseIdBits.markBit(id);
+ }
+ }
+ for (BitSet32 idBits(mCurrentRawPointerData.hoveringIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS
+ || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+ mCurrentStylusIdBits.markBit(id);
+ }
+ }
+
+ // Stylus takes precedence over all tools, then mouse, then finger.
+ PointerUsage pointerUsage = mPointerUsage;
+ if (!mCurrentStylusIdBits.isEmpty()) {
+ mCurrentMouseIdBits.clear();
+ mCurrentFingerIdBits.clear();
+ pointerUsage = POINTER_USAGE_STYLUS;
+ } else if (!mCurrentMouseIdBits.isEmpty()) {
+ mCurrentFingerIdBits.clear();
+ pointerUsage = POINTER_USAGE_MOUSE;
+ } else if (!mCurrentFingerIdBits.isEmpty() || isPointerDown(mCurrentButtonState)) {
+ pointerUsage = POINTER_USAGE_GESTURES;
+ }
+
+ dispatchPointerUsage(when, policyFlags, pointerUsage);
+ } else {
+ if (mDeviceMode == DEVICE_MODE_DIRECT
+ && mConfig.showTouches && mPointerController != NULL) {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+
+ mPointerController->setButtonState(mCurrentButtonState);
+ mPointerController->setSpots(mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mCurrentCookedPointerData.touchingIdBits);
+ }
+
+ dispatchHoverExit(when, policyFlags);
+ dispatchTouches(when, policyFlags);
+ dispatchHoverEnterAndMove(when, policyFlags);
+ }
+
+ // Synthesize key up from raw buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+ policyFlags, mLastButtonState, mCurrentButtonState);
+ }
+
+ // Copy current touch to last touch in preparation for the next cycle.
+ mLastRawPointerData.copyFrom(mCurrentRawPointerData);
+ mLastCookedPointerData.copyFrom(mCurrentCookedPointerData);
+ mLastButtonState = mCurrentButtonState;
+ mLastFingerIdBits = mCurrentFingerIdBits;
+ mLastStylusIdBits = mCurrentStylusIdBits;
+ mLastMouseIdBits = mCurrentMouseIdBits;
+
+ // Clear some transient state.
+ mCurrentRawVScroll = 0;
+ mCurrentRawHScroll = 0;
+}
+
+void TouchInputMapper::timeoutExpired(nsecs_t when) {
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ if (mPointerUsage == POINTER_USAGE_GESTURES) {
+ dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/);
+ }
+ }
+}
+
+bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags) {
+ // Check for release of a virtual key.
+ if (mCurrentVirtualKey.down) {
+ if (mCurrentRawPointerData.touchingIdBits.isEmpty()) {
+ // Pointer went up while virtual key was down.
+ mCurrentVirtualKey.down = false;
+ if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
+ mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+ dispatchVirtualKey(when, policyFlags,
+ AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+ }
+ return true;
+ }
+
+ if (mCurrentRawPointerData.touchingIdBits.count() == 1) {
+ uint32_t id = mCurrentRawPointerData.touchingIdBits.firstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
+ if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) {
+ // Pointer is still within the space of the virtual key.
+ return true;
+ }
+ }
+
+ // Pointer left virtual key area or another pointer also went down.
+ // Send key cancellation but do not consume the touch yet.
+ // This is useful when the user swipes through from the virtual key area
+ // into the main display surface.
+ mCurrentVirtualKey.down = false;
+ if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
+ mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+ dispatchVirtualKey(when, policyFlags,
+ AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+ | AKEY_EVENT_FLAG_CANCELED);
+ }
+ }
+
+ if (mLastRawPointerData.touchingIdBits.isEmpty()
+ && !mCurrentRawPointerData.touchingIdBits.isEmpty()) {
+ // Pointer just went down. Check for virtual key press or off-screen touches.
+ uint32_t id = mCurrentRawPointerData.touchingIdBits.firstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ if (!isPointInsideSurface(pointer.x, pointer.y)) {
+ // If exactly one pointer went down, check for virtual key hit.
+ // Otherwise we will drop the entire stroke.
+ if (mCurrentRawPointerData.touchingIdBits.count() == 1) {
+ const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
+ if (virtualKey) {
+ mCurrentVirtualKey.down = true;
+ mCurrentVirtualKey.downTime = when;
+ mCurrentVirtualKey.keyCode = virtualKey->keyCode;
+ mCurrentVirtualKey.scanCode = virtualKey->scanCode;
+ mCurrentVirtualKey.ignored = mContext->shouldDropVirtualKey(
+ when, getDevice(), virtualKey->keyCode, virtualKey->scanCode);
+
+ if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
+ mCurrentVirtualKey.keyCode,
+ mCurrentVirtualKey.scanCode);
+#endif
+ dispatchVirtualKey(when, policyFlags,
+ AKEY_EVENT_ACTION_DOWN,
+ AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ // Disable all virtual key touches that happen within a short time interval of the
+ // most recent touch within the screen area. The idea is to filter out stray
+ // virtual key presses when interacting with the touch screen.
+ //
+ // Problems we're trying to solve:
+ //
+ // 1. While scrolling a list or dragging the window shade, the user swipes down into a
+ // virtual key area that is implemented by a separate touch panel and accidentally
+ // triggers a virtual key.
+ //
+ // 2. While typing in the on screen keyboard, the user taps slightly outside the screen
+ // area and accidentally triggers a virtual key. This often happens when virtual keys
+ // are layed out below the screen near to where the on screen keyboard's space bar
+ // is displayed.
+ if (mConfig.virtualKeyQuietTime > 0 && !mCurrentRawPointerData.touchingIdBits.isEmpty()) {
+ mContext->disableVirtualKeysUntil(when + mConfig.virtualKeyQuietTime);
+ }
+ return false;
+}
+
+void TouchInputMapper::dispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
+ int32_t keyEventAction, int32_t keyEventFlags) {
+ int32_t keyCode = mCurrentVirtualKey.keyCode;
+ int32_t scanCode = mCurrentVirtualKey.scanCode;
+ nsecs_t downTime = mCurrentVirtualKey.downTime;
+ int32_t metaState = mContext->getGlobalMetaState();
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+
+ NotifyKeyArgs args(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
+ keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
+ getListener()->notifyKey(&args);
+}
+
+void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
+ BitSet32 currentIdBits = mCurrentCookedPointerData.touchingIdBits;
+ BitSet32 lastIdBits = mLastCookedPointerData.touchingIdBits;
+ int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mCurrentButtonState;
+
+ if (currentIdBits == lastIdBits) {
+ if (!currentIdBits.isEmpty()) {
+ // No pointer id changes so this is a move event.
+ // The listener takes care of batching moves so we don't have to deal with that here.
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ currentIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+ } else {
+ // There may be pointers going up and pointers going down and pointers moving
+ // all at the same time.
+ BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value);
+ BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value);
+ BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
+ BitSet32 dispatchedIdBits(lastIdBits.value);
+
+ // Update last coordinates of pointers that have moved so that we observe the new
+ // pointer positions at the same time as other pointers that have just gone up.
+ bool moveNeeded = updateMovedPointers(
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mLastCookedPointerData.pointerProperties,
+ mLastCookedPointerData.pointerCoords,
+ mLastCookedPointerData.idToIndex,
+ moveIdBits);
+ if (buttonState != mLastButtonState) {
+ moveNeeded = true;
+ }
+
+ // Dispatch pointer up events.
+ while (!upIdBits.isEmpty()) {
+ uint32_t upId = upIdBits.clearFirstMarkedBit();
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_UP, 0, metaState, buttonState, 0,
+ mLastCookedPointerData.pointerProperties,
+ mLastCookedPointerData.pointerCoords,
+ mLastCookedPointerData.idToIndex,
+ dispatchedIdBits, upId,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ dispatchedIdBits.clearBit(upId);
+ }
+
+ // Dispatch move events if any of the remaining pointers moved from their old locations.
+ // Although applications receive new locations as part of individual pointer up
+ // events, they do not generally handle them except when presented in a move event.
+ if (moveNeeded) {
+ ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ dispatchedIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+
+ // Dispatch pointer down events using the new pointer locations.
+ while (!downIdBits.isEmpty()) {
+ uint32_t downId = downIdBits.clearFirstMarkedBit();
+ dispatchedIdBits.markBit(downId);
+
+ if (dispatchedIdBits.count() == 1) {
+ // First pointer is going down. Set down time.
+ mDownTime = when;
+ }
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, buttonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ dispatchedIdBits, downId,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+ }
+}
+
+void TouchInputMapper::dispatchHoverExit(nsecs_t when, uint32_t policyFlags) {
+ if (mSentHoverEnter &&
+ (mCurrentCookedPointerData.hoveringIdBits.isEmpty()
+ || !mCurrentCookedPointerData.touchingIdBits.isEmpty())) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_EXIT, 0, metaState, mLastButtonState, 0,
+ mLastCookedPointerData.pointerProperties,
+ mLastCookedPointerData.pointerCoords,
+ mLastCookedPointerData.idToIndex,
+ mLastCookedPointerData.hoveringIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ mSentHoverEnter = false;
+ }
+}
+
+void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags) {
+ if (mCurrentCookedPointerData.touchingIdBits.isEmpty()
+ && !mCurrentCookedPointerData.hoveringIdBits.isEmpty()) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ if (!mSentHoverEnter) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_ENTER, 0, metaState, mCurrentButtonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mCurrentCookedPointerData.hoveringIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ mSentHoverEnter = true;
+ }
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, mCurrentButtonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mCurrentCookedPointerData.hoveringIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+}
+
+void TouchInputMapper::cookPointerData() {
+ uint32_t currentPointerCount = mCurrentRawPointerData.pointerCount;
+
+ mCurrentCookedPointerData.clear();
+ mCurrentCookedPointerData.pointerCount = currentPointerCount;
+ mCurrentCookedPointerData.hoveringIdBits = mCurrentRawPointerData.hoveringIdBits;
+ mCurrentCookedPointerData.touchingIdBits = mCurrentRawPointerData.touchingIdBits;
+
+ // Walk through the the active pointers and map device coordinates onto
+ // surface coordinates and adjust for display orientation.
+ for (uint32_t i = 0; i < currentPointerCount; i++) {
+ const RawPointerData::Pointer& in = mCurrentRawPointerData.pointers[i];
+
+ // Size
+ float touchMajor, touchMinor, toolMajor, toolMinor, size;
+ switch (mCalibration.sizeCalibration) {
+ case Calibration::SIZE_CALIBRATION_GEOMETRIC:
+ case Calibration::SIZE_CALIBRATION_DIAMETER:
+ case Calibration::SIZE_CALIBRATION_BOX:
+ case Calibration::SIZE_CALIBRATION_AREA:
+ if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.toolMajor.valid) {
+ touchMajor = in.touchMajor;
+ touchMinor = mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
+ toolMajor = in.toolMajor;
+ toolMinor = mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
+ size = mRawPointerAxes.touchMinor.valid
+ ? avg(in.touchMajor, in.touchMinor) : in.touchMajor;
+ } else if (mRawPointerAxes.touchMajor.valid) {
+ toolMajor = touchMajor = in.touchMajor;
+ toolMinor = touchMinor = mRawPointerAxes.touchMinor.valid
+ ? in.touchMinor : in.touchMajor;
+ size = mRawPointerAxes.touchMinor.valid
+ ? avg(in.touchMajor, in.touchMinor) : in.touchMajor;
+ } else if (mRawPointerAxes.toolMajor.valid) {
+ touchMajor = toolMajor = in.toolMajor;
+ touchMinor = toolMinor = mRawPointerAxes.toolMinor.valid
+ ? in.toolMinor : in.toolMajor;
+ size = mRawPointerAxes.toolMinor.valid
+ ? avg(in.toolMajor, in.toolMinor) : in.toolMajor;
+ } else {
+ ALOG_ASSERT(false, "No touch or tool axes. "
+ "Size calibration should have been resolved to NONE.");
+ touchMajor = 0;
+ touchMinor = 0;
+ toolMajor = 0;
+ toolMinor = 0;
+ size = 0;
+ }
+
+ if (mCalibration.haveSizeIsSummed && mCalibration.sizeIsSummed) {
+ uint32_t touchingCount = mCurrentRawPointerData.touchingIdBits.count();
+ if (touchingCount > 1) {
+ touchMajor /= touchingCount;
+ touchMinor /= touchingCount;
+ toolMajor /= touchingCount;
+ toolMinor /= touchingCount;
+ size /= touchingCount;
+ }
+ }
+
+ if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_GEOMETRIC) {
+ touchMajor *= mGeometricScale;
+ touchMinor *= mGeometricScale;
+ toolMajor *= mGeometricScale;
+ toolMinor *= mGeometricScale;
+ } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_AREA) {
+ touchMajor = touchMajor > 0 ? sqrtf(touchMajor) : 0;
+ touchMinor = touchMajor;
+ toolMajor = toolMajor > 0 ? sqrtf(toolMajor) : 0;
+ toolMinor = toolMajor;
+ } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DIAMETER) {
+ touchMinor = touchMajor;
+ toolMinor = toolMajor;
+ }
+
+ mCalibration.applySizeScaleAndBias(&touchMajor);
+ mCalibration.applySizeScaleAndBias(&touchMinor);
+ mCalibration.applySizeScaleAndBias(&toolMajor);
+ mCalibration.applySizeScaleAndBias(&toolMinor);
+ size *= mSizeScale;
+ break;
+ default:
+ touchMajor = 0;
+ touchMinor = 0;
+ toolMajor = 0;
+ toolMinor = 0;
+ size = 0;
+ break;
+ }
+
+ // Pressure
+ float pressure;
+ switch (mCalibration.pressureCalibration) {
+ case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+ case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+ pressure = in.pressure * mPressureScale;
+ break;
+ default:
+ pressure = in.isHovering ? 0 : 1;
+ break;
+ }
+
+ // Tilt and Orientation
+ float tilt;
+ float orientation;
+ if (mHaveTilt) {
+ float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale;
+ float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale;
+ orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle));
+ tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
+ } else {
+ tilt = 0;
+
+ switch (mCalibration.orientationCalibration) {
+ case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+ orientation = in.orientation * mOrientationScale;
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_VECTOR: {
+ int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
+ int32_t c2 = signExtendNybble(in.orientation & 0x0f);
+ if (c1 != 0 || c2 != 0) {
+ orientation = atan2f(c1, c2) * 0.5f;
+ float confidence = hypotf(c1, c2);
+ float scale = 1.0f + confidence / 16.0f;
+ touchMajor *= scale;
+ touchMinor /= scale;
+ toolMajor *= scale;
+ toolMinor /= scale;
+ } else {
+ orientation = 0;
+ }
+ break;
+ }
+ default:
+ orientation = 0;
+ }
+ }
+
+ // Distance
+ float distance;
+ switch (mCalibration.distanceCalibration) {
+ case Calibration::DISTANCE_CALIBRATION_SCALED:
+ distance = in.distance * mDistanceScale;
+ break;
+ default:
+ distance = 0;
+ }
+
+ // Coverage
+ int32_t rawLeft, rawTop, rawRight, rawBottom;
+ switch (mCalibration.coverageCalibration) {
+ case Calibration::COVERAGE_CALIBRATION_BOX:
+ rawLeft = (in.toolMinor & 0xffff0000) >> 16;
+ rawRight = in.toolMinor & 0x0000ffff;
+ rawBottom = in.toolMajor & 0x0000ffff;
+ rawTop = (in.toolMajor & 0xffff0000) >> 16;
+ break;
+ default:
+ rawLeft = rawTop = rawRight = rawBottom = 0;
+ break;
+ }
+
+ // X, Y, and the bounding box for coverage information
+ // Adjust coords for surface orientation.
+ float x, y, left, top, right, bottom;
+ switch (mSurfaceOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ x = float(in.y - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ y = float(mRawPointerAxes.x.maxValue - in.x) * mXScale + mXTranslate;
+ left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ right = float(rawBottom- mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
+ top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
+ orientation -= M_PI_2;
+ if (orientation < - M_PI_2) {
+ orientation += M_PI;
+ }
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = float(mRawPointerAxes.x.maxValue - in.x) * mXScale + mXTranslate;
+ y = float(mRawPointerAxes.y.maxValue - in.y) * mYScale + mYTranslate;
+ left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
+ right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
+ bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
+ top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ x = float(mRawPointerAxes.y.maxValue - in.y) * mYScale + mYTranslate;
+ y = float(in.x - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
+ right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
+ bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ orientation += M_PI_2;
+ if (orientation > M_PI_2) {
+ orientation -= M_PI;
+ }
+ break;
+ default:
+ x = float(in.x - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ y = float(in.y - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ break;
+ }
+
+ // Write output coords.
+ PointerCoords& out = mCurrentCookedPointerData.pointerCoords[i];
+ out.clear();
+ out.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ out.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+ out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt);
+ out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom);
+ } else {
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
+ }
+
+ // Write output properties.
+ PointerProperties& properties = mCurrentCookedPointerData.pointerProperties[i];
+ uint32_t id = in.id;
+ properties.clear();
+ properties.id = id;
+ properties.toolType = in.toolType;
+
+ // Write id index.
+ mCurrentCookedPointerData.idToIndex[id] = i;
+ }
+}
+
+void TouchInputMapper::dispatchPointerUsage(nsecs_t when, uint32_t policyFlags,
+ PointerUsage pointerUsage) {
+ if (pointerUsage != mPointerUsage) {
+ abortPointerUsage(when, policyFlags);
+ mPointerUsage = pointerUsage;
+ }
+
+ switch (mPointerUsage) {
+ case POINTER_USAGE_GESTURES:
+ dispatchPointerGestures(when, policyFlags, false /*isTimeout*/);
+ break;
+ case POINTER_USAGE_STYLUS:
+ dispatchPointerStylus(when, policyFlags);
+ break;
+ case POINTER_USAGE_MOUSE:
+ dispatchPointerMouse(when, policyFlags);
+ break;
+ default:
+ break;
+ }
+}
+
+void TouchInputMapper::abortPointerUsage(nsecs_t when, uint32_t policyFlags) {
+ switch (mPointerUsage) {
+ case POINTER_USAGE_GESTURES:
+ abortPointerGestures(when, policyFlags);
+ break;
+ case POINTER_USAGE_STYLUS:
+ abortPointerStylus(when, policyFlags);
+ break;
+ case POINTER_USAGE_MOUSE:
+ abortPointerMouse(when, policyFlags);
+ break;
+ default:
+ break;
+ }
+
+ mPointerUsage = POINTER_USAGE_NONE;
+}
+
+void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags,
+ bool isTimeout) {
+ // Update current gesture coordinates.
+ bool cancelPreviousGesture, finishPreviousGesture;
+ bool sendEvents = preparePointerGestures(when,
+ &cancelPreviousGesture, &finishPreviousGesture, isTimeout);
+ if (!sendEvents) {
+ return;
+ }
+ if (finishPreviousGesture) {
+ cancelPreviousGesture = false;
+ }
+
+ // Update the pointer presentation and spots.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);
+ if (finishPreviousGesture || cancelPreviousGesture) {
+ mPointerController->clearSpots();
+ }
+ mPointerController->setSpots(mPointerGesture.currentGestureCoords,
+ mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.currentGestureIdBits);
+ } else {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+ }
+
+ // Show or hide the pointer if needed.
+ switch (mPointerGesture.currentGestureMode) {
+ case PointerGesture::NEUTRAL:
+ case PointerGesture::QUIET:
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+ && (mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)) {
+ // Remind the user of where the pointer is after finishing a gesture with spots.
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+ break;
+ case PointerGesture::TAP:
+ case PointerGesture::TAP_DRAG:
+ case PointerGesture::BUTTON_CLICK_OR_DRAG:
+ case PointerGesture::HOVER:
+ case PointerGesture::PRESS:
+ // Unfade the pointer when the current gesture manipulates the
+ // area directly under the pointer.
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ break;
+ case PointerGesture::SWIPE:
+ case PointerGesture::FREEFORM:
+ // Fade the pointer when the current gesture manipulates a different
+ // area and there are spots to guide the user experience.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ } else {
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ }
+ break;
+ }
+
+ // Send events!
+ int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mCurrentButtonState;
+
+ // Update last coordinates of pointers that have moved so that we observe the new
+ // pointer positions at the same time as other pointers that have just gone up.
+ bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP
+ || mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG
+ || mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG
+ || mPointerGesture.currentGestureMode == PointerGesture::PRESS
+ || mPointerGesture.currentGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.currentGestureMode == PointerGesture::FREEFORM;
+ bool moveNeeded = false;
+ if (down && !cancelPreviousGesture && !finishPreviousGesture
+ && !mPointerGesture.lastGestureIdBits.isEmpty()
+ && !mPointerGesture.currentGestureIdBits.isEmpty()) {
+ BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value
+ & mPointerGesture.lastGestureIdBits.value);
+ moveNeeded = updateMovedPointers(mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ movedGestureIdBits);
+ if (buttonState != mLastButtonState) {
+ moveNeeded = true;
+ }
+ }
+
+ // Send motion events for all pointers that went up or were canceled.
+ BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
+ if (!dispatchedGestureIdBits.isEmpty()) {
+ if (cancelPreviousGesture) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_CANCEL, 0, metaState, buttonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ dispatchedGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+
+ dispatchedGestureIdBits.clear();
+ } else {
+ BitSet32 upGestureIdBits;
+ if (finishPreviousGesture) {
+ upGestureIdBits = dispatchedGestureIdBits;
+ } else {
+ upGestureIdBits.value = dispatchedGestureIdBits.value
+ & ~mPointerGesture.currentGestureIdBits.value;
+ }
+ while (!upGestureIdBits.isEmpty()) {
+ uint32_t id = upGestureIdBits.clearFirstMarkedBit();
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_UP, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ dispatchedGestureIdBits, id,
+ 0, 0, mPointerGesture.downTime);
+
+ dispatchedGestureIdBits.clearBit(id);
+ }
+ }
+ }
+
+ // Send motion events for all pointers that moved.
+ if (moveNeeded) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ dispatchedGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+ }
+
+ // Send motion events for all pointers that went down.
+ if (down) {
+ BitSet32 downGestureIdBits(mPointerGesture.currentGestureIdBits.value
+ & ~dispatchedGestureIdBits.value);
+ while (!downGestureIdBits.isEmpty()) {
+ uint32_t id = downGestureIdBits.clearFirstMarkedBit();
+ dispatchedGestureIdBits.markBit(id);
+
+ if (dispatchedGestureIdBits.count() == 1) {
+ mPointerGesture.downTime = when;
+ }
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, buttonState, 0,
+ mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ dispatchedGestureIdBits, id,
+ 0, 0, mPointerGesture.downTime);
+ }
+ }
+
+ // Send motion events for hover.
+ if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.currentGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+ } else if (dispatchedGestureIdBits.isEmpty()
+ && !mPointerGesture.lastGestureIdBits.isEmpty()) {
+ // Synthesize a hover move event after all pointers go up to indicate that
+ // the pointer is hovering again even if the user is not currently touching
+ // the touch pad. This ensures that a view will receive a fresh hover enter
+ // event after a tap.
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mViewport.displayId, 1, &pointerProperties, &pointerCoords,
+ 0, 0, mPointerGesture.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Update state.
+ mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode;
+ if (!down) {
+ mPointerGesture.lastGestureIdBits.clear();
+ } else {
+ mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits;
+ for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
+ mPointerGesture.lastGestureProperties[index].copyFrom(
+ mPointerGesture.currentGestureProperties[index]);
+ mPointerGesture.lastGestureCoords[index].copyFrom(
+ mPointerGesture.currentGestureCoords[index]);
+ mPointerGesture.lastGestureIdToIndex[id] = index;
+ }
+ }
+}
+
+void TouchInputMapper::abortPointerGestures(nsecs_t when, uint32_t policyFlags) {
+ // Cancel previously dispatches pointers.
+ if (!mPointerGesture.lastGestureIdBits.isEmpty()) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mCurrentButtonState;
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_CANCEL, 0, metaState, buttonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ mPointerGesture.lastGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+ }
+
+ // Reset the current pointer gesture.
+ mPointerGesture.reset();
+ mPointerVelocityControl.reset();
+
+ // Remove any current spots.
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ mPointerController->clearSpots();
+ }
+}
+
+bool TouchInputMapper::preparePointerGestures(nsecs_t when,
+ bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout) {
+ *outCancelPreviousGesture = false;
+ *outFinishPreviousGesture = false;
+
+ // Handle TAP timeout.
+ if (isTimeout) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Processing timeout");
+#endif
+
+ if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+ if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
+ // The tap/drag timeout has not yet expired.
+ getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime
+ + mConfig.pointerGestureTapDragInterval);
+ } else {
+ // The tap is finished.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: TAP finished");
+#endif
+ *outFinishPreviousGesture = true;
+
+ mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+ mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerVelocityControl.reset();
+ return true;
+ }
+ }
+
+ // We did not handle this timeout.
+ return false;
+ }
+
+ const uint32_t currentFingerCount = mCurrentFingerIdBits.count();
+ const uint32_t lastFingerCount = mLastFingerIdBits.count();
+
+ // Update the velocity tracker.
+ {
+ VelocityTracker::Position positions[MAX_POINTERS];
+ uint32_t count = 0;
+ for (BitSet32 idBits(mCurrentFingerIdBits); !idBits.isEmpty(); count++) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ positions[count].x = pointer.x * mPointerXMovementScale;
+ positions[count].y = pointer.y * mPointerYMovementScale;
+ }
+ mPointerGesture.velocityTracker.addMovement(when,
+ mCurrentFingerIdBits, positions);
+ }
+
+ // Pick a new active touch id if needed.
+ // Choose an arbitrary pointer that just went down, if there is one.
+ // Otherwise choose an arbitrary remaining pointer.
+ // This guarantees we always have an active touch id when there is at least one pointer.
+ // We keep the same active touch id for as long as possible.
+ int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
+ int32_t activeTouchId = lastActiveTouchId;
+ if (activeTouchId < 0) {
+ if (!mCurrentFingerIdBits.isEmpty()) {
+ activeTouchId = mPointerGesture.activeTouchId =
+ mCurrentFingerIdBits.firstMarkedBit();
+ mPointerGesture.firstTouchTime = when;
+ }
+ } else if (!mCurrentFingerIdBits.hasBit(activeTouchId)) {
+ if (!mCurrentFingerIdBits.isEmpty()) {
+ activeTouchId = mPointerGesture.activeTouchId =
+ mCurrentFingerIdBits.firstMarkedBit();
+ } else {
+ activeTouchId = mPointerGesture.activeTouchId = -1;
+ }
+ }
+
+ // Determine whether we are in quiet time.
+ bool isQuietTime = false;
+ if (activeTouchId < 0) {
+ mPointerGesture.resetQuietTime();
+ } else {
+ isQuietTime = when < mPointerGesture.quietTime + mConfig.pointerGestureQuietInterval;
+ if (!isQuietTime) {
+ if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS
+ || mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
+ && currentFingerCount < 2) {
+ // Enter quiet time when exiting swipe or freeform state.
+ // This is to prevent accidentally entering the hover state and flinging the
+ // pointer when finishing a swipe and there is still one pointer left onscreen.
+ isQuietTime = true;
+ } else if (mPointerGesture.lastGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG
+ && currentFingerCount >= 2
+ && !isPointerDown(mCurrentButtonState)) {
+ // Enter quiet time when releasing the button and there are still two or more
+ // fingers down. This may indicate that one finger was used to press the button
+ // but it has not gone up yet.
+ isQuietTime = true;
+ }
+ if (isQuietTime) {
+ mPointerGesture.quietTime = when;
+ }
+ }
+ }
+
+ // Switch states based on button and pointer state.
+ if (isQuietTime) {
+ // Case 1: Quiet time. (QUIET)
+#if DEBUG_GESTURES
+ ALOGD("Gestures: QUIET for next %0.3fms", (mPointerGesture.quietTime
+ + mConfig.pointerGestureQuietInterval - when) * 0.000001f);
+#endif
+ if (mPointerGesture.lastGestureMode != PointerGesture::QUIET) {
+ *outFinishPreviousGesture = true;
+ }
+
+ mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::QUIET;
+ mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerVelocityControl.reset();
+ } else if (isPointerDown(mCurrentButtonState)) {
+ // Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG)
+ // The pointer follows the active touch point.
+ // Emit DOWN, MOVE, UP events at the pointer location.
+ //
+ // Only the active touch matters; other fingers are ignored. This policy helps
+ // to handle the case where the user places a second finger on the touch pad
+ // to apply the necessary force to depress an integrated button below the surface.
+ // We don't want the second finger to be delivered to applications.
+ //
+ // For this to work well, we need to make sure to track the pointer that is really
+ // active. If the user first puts one finger down to click then adds another
+ // finger to drag then the active pointer should switch to the finger that is
+ // being dragged.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, "
+ "currentFingerCount=%d", activeTouchId, currentFingerCount);
+#endif
+ // Reset state when just starting.
+ if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) {
+ *outFinishPreviousGesture = true;
+ mPointerGesture.activeGestureId = 0;
+ }
+
+ // Switch pointers if needed.
+ // Find the fastest pointer and follow it.
+ if (activeTouchId >= 0 && currentFingerCount > 1) {
+ int32_t bestId = -1;
+ float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
+ for (BitSet32 idBits(mCurrentFingerIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ float vx, vy;
+ if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) {
+ float speed = hypotf(vx, vy);
+ if (speed > bestSpeed) {
+ bestId = id;
+ bestSpeed = speed;
+ }
+ }
+ }
+ if (bestId >= 0 && bestId != activeTouchId) {
+ mPointerGesture.activeTouchId = activeTouchId = bestId;
+#if DEBUG_GESTURES
+ ALOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, "
+ "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed);
+#endif
+ }
+ }
+
+ if (activeTouchId >= 0 && mLastFingerIdBits.hasBit(activeTouchId)) {
+ const RawPointerData::Pointer& currentPointer =
+ mCurrentRawPointerData.pointerForId(activeTouchId);
+ const RawPointerData::Pointer& lastPointer =
+ mLastRawPointerData.pointerForId(activeTouchId);
+ float deltaX = (currentPointer.x - lastPointer.x) * mPointerXMovementScale;
+ float deltaY = (currentPointer.y - lastPointer.y) * mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ // Move the pointer using a relative motion.
+ // When using spots, the click will occur at the position of the anchor
+ // spot and all other spots will move there.
+ mPointerController->move(deltaX, deltaY);
+ } else {
+ mPointerVelocityControl.reset();
+ }
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+
+ mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG;
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ } else if (currentFingerCount == 0) {
+ // Case 3. No fingers down and button is not pressed. (NEUTRAL)
+ if (mPointerGesture.lastGestureMode != PointerGesture::NEUTRAL) {
+ *outFinishPreviousGesture = true;
+ }
+
+ // Watch for taps coming out of HOVER or TAP_DRAG mode.
+ // Checking for taps after TAP_DRAG allows us to detect double-taps.
+ bool tapped = false;
+ if ((mPointerGesture.lastGestureMode == PointerGesture::HOVER
+ || mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG)
+ && lastFingerCount == 1) {
+ if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) {
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop
+ && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: TAP");
+#endif
+
+ mPointerGesture.tapUpTime = when;
+ getContext()->requestTimeoutAtTime(when
+ + mConfig.pointerGestureTapDragInterval);
+
+ mPointerGesture.activeGestureId = 0;
+ mPointerGesture.currentGestureMode = PointerGesture::TAP;
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(
+ mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[
+ mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id =
+ mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(
+ AMOTION_EVENT_AXIS_X, mPointerGesture.tapX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(
+ AMOTION_EVENT_AXIS_Y, mPointerGesture.tapY);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(
+ AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ tapped = true;
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f",
+ x - mPointerGesture.tapX,
+ y - mPointerGesture.tapY);
+#endif
+ }
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP, %0.3fms since down",
+ (when - mPointerGesture.tapDownTime) * 0.000001f);
+#endif
+ }
+ }
+
+ mPointerVelocityControl.reset();
+
+ if (!tapped) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: NEUTRAL");
+#endif
+ mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+ mPointerGesture.currentGestureIdBits.clear();
+ }
+ } else if (currentFingerCount == 1) {
+ // Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG)
+ // The pointer follows the active touch point.
+ // When in HOVER, emit HOVER_MOVE events at the pointer location.
+ // When in TAP_DRAG, emit MOVE events at the pointer location.
+ ALOG_ASSERT(activeTouchId >= 0);
+
+ mPointerGesture.currentGestureMode = PointerGesture::HOVER;
+ if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+ if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop
+ && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+ mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
+ x - mPointerGesture.tapX,
+ y - mPointerGesture.tapY);
+#endif
+ }
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up",
+ (when - mPointerGesture.tapUpTime) * 0.000001f);
+#endif
+ }
+ } else if (mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) {
+ mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+ }
+
+ if (mLastFingerIdBits.hasBit(activeTouchId)) {
+ const RawPointerData::Pointer& currentPointer =
+ mCurrentRawPointerData.pointerForId(activeTouchId);
+ const RawPointerData::Pointer& lastPointer =
+ mLastRawPointerData.pointerForId(activeTouchId);
+ float deltaX = (currentPointer.x - lastPointer.x)
+ * mPointerXMovementScale;
+ float deltaY = (currentPointer.y - lastPointer.y)
+ * mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ // Move the pointer using a relative motion.
+ // When using spots, the hover or drag will occur at the position of the anchor spot.
+ mPointerController->move(deltaX, deltaY);
+ } else {
+ mPointerVelocityControl.reset();
+ }
+
+ bool down;
+ if (mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: TAP_DRAG");
+#endif
+ down = true;
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: HOVER");
+#endif
+ if (mPointerGesture.lastGestureMode != PointerGesture::HOVER) {
+ *outFinishPreviousGesture = true;
+ }
+ mPointerGesture.activeGestureId = 0;
+ down = false;
+ }
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+ down ? 1.0f : 0.0f);
+
+ if (lastFingerCount == 0 && currentFingerCount != 0) {
+ mPointerGesture.resetTap();
+ mPointerGesture.tapDownTime = when;
+ mPointerGesture.tapX = x;
+ mPointerGesture.tapY = y;
+ }
+ } else {
+ // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
+ // We need to provide feedback for each finger that goes down so we cannot wait
+ // for the fingers to move before deciding what to do.
+ //
+ // The ambiguous case is deciding what to do when there are two fingers down but they
+ // have not moved enough to determine whether they are part of a drag or part of a
+ // freeform gesture, or just a press or long-press at the pointer location.
+ //
+ // When there are two fingers we start with the PRESS hypothesis and we generate a
+ // down at the pointer location.
+ //
+ // When the two fingers move enough or when additional fingers are added, we make
+ // a decision to transition into SWIPE or FREEFORM mode accordingly.
+ ALOG_ASSERT(activeTouchId >= 0);
+
+ bool settled = when >= mPointerGesture.firstTouchTime
+ + mConfig.pointerGestureMultitouchSettleInterval;
+ if (mPointerGesture.lastGestureMode != PointerGesture::PRESS
+ && mPointerGesture.lastGestureMode != PointerGesture::SWIPE
+ && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
+ *outFinishPreviousGesture = true;
+ } else if (!settled && currentFingerCount > lastFingerCount) {
+ // Additional pointers have gone down but not yet settled.
+ // Reset the gesture.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, "
+ "settle time remaining %0.3fms", (mPointerGesture.firstTouchTime
+ + mConfig.pointerGestureMultitouchSettleInterval - when)
+ * 0.000001f);
+#endif
+ *outCancelPreviousGesture = true;
+ } else {
+ // Continue previous gesture.
+ mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
+ }
+
+ if (*outFinishPreviousGesture || *outCancelPreviousGesture) {
+ mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+ mPointerGesture.activeGestureId = 0;
+ mPointerGesture.referenceIdBits.clear();
+ mPointerVelocityControl.reset();
+
+ // Use the centroid and pointer location as the reference points for the gesture.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Using centroid as reference for MULTITOUCH, "
+ "settle time remaining %0.3fms", (mPointerGesture.firstTouchTime
+ + mConfig.pointerGestureMultitouchSettleInterval - when)
+ * 0.000001f);
+#endif
+ mCurrentRawPointerData.getCentroidOfTouchingPointers(
+ &mPointerGesture.referenceTouchX,
+ &mPointerGesture.referenceTouchY);
+ mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+ &mPointerGesture.referenceGestureY);
+ }
+
+ // Clear the reference deltas for fingers not yet included in the reference calculation.
+ for (BitSet32 idBits(mCurrentFingerIdBits.value
+ & ~mPointerGesture.referenceIdBits.value); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ mPointerGesture.referenceDeltas[id].dx = 0;
+ mPointerGesture.referenceDeltas[id].dy = 0;
+ }
+ mPointerGesture.referenceIdBits = mCurrentFingerIdBits;
+
+ // Add delta for all fingers and calculate a common movement delta.
+ float commonDeltaX = 0, commonDeltaY = 0;
+ BitSet32 commonIdBits(mLastFingerIdBits.value
+ & mCurrentFingerIdBits.value);
+ for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) {
+ bool first = (idBits == commonIdBits);
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& cpd = mCurrentRawPointerData.pointerForId(id);
+ const RawPointerData::Pointer& lpd = mLastRawPointerData.pointerForId(id);
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ delta.dx += cpd.x - lpd.x;
+ delta.dy += cpd.y - lpd.y;
+
+ if (first) {
+ commonDeltaX = delta.dx;
+ commonDeltaY = delta.dy;
+ } else {
+ commonDeltaX = calculateCommonVector(commonDeltaX, delta.dx);
+ commonDeltaY = calculateCommonVector(commonDeltaY, delta.dy);
+ }
+ }
+
+ // Consider transitions from PRESS to SWIPE or MULTITOUCH.
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+ float dist[MAX_POINTER_ID + 1];
+ int32_t distOverThreshold = 0;
+ for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ dist[id] = hypotf(delta.dx * mPointerXZoomScale,
+ delta.dy * mPointerYZoomScale);
+ if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
+ distOverThreshold += 1;
+ }
+ }
+
+ // Only transition when at least two pointers have moved further than
+ // the minimum distance threshold.
+ if (distOverThreshold >= 2) {
+ if (currentFingerCount > 2) {
+ // There are more than two pointers, switch to FREEFORM.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+ currentFingerCount);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ } else {
+ // There are exactly two pointers.
+ BitSet32 idBits(mCurrentFingerIdBits);
+ uint32_t id1 = idBits.clearFirstMarkedBit();
+ uint32_t id2 = idBits.firstMarkedBit();
+ const RawPointerData::Pointer& p1 = mCurrentRawPointerData.pointerForId(id1);
+ const RawPointerData::Pointer& p2 = mCurrentRawPointerData.pointerForId(id2);
+ float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
+ if (mutualDistance > mPointerGestureMaxSwipeWidth) {
+ // There are two pointers but they are too far apart for a SWIPE,
+ // switch to FREEFORM.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+ mutualDistance, mPointerGestureMaxSwipeWidth);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ } else {
+ // There are two pointers. Wait for both pointers to start moving
+ // before deciding whether this is a SWIPE or FREEFORM gesture.
+ float dist1 = dist[id1];
+ float dist2 = dist[id2];
+ if (dist1 >= mConfig.pointerGestureMultitouchMinDistance
+ && dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
+ // Calculate the dot product of the displacement vectors.
+ // When the vectors are oriented in approximately the same direction,
+ // the angle betweeen them is near zero and the cosine of the angle
+ // approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2).
+ PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
+ PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
+ float dx1 = delta1.dx * mPointerXZoomScale;
+ float dy1 = delta1.dy * mPointerYZoomScale;
+ float dx2 = delta2.dx * mPointerXZoomScale;
+ float dy2 = delta2.dy * mPointerYZoomScale;
+ float dot = dx1 * dx2 + dy1 * dy2;
+ float cosine = dot / (dist1 * dist2); // denominator always > 0
+ if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
+ // Pointers are moving in the same direction. Switch to SWIPE.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to SWIPE, "
+ "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+ "cosine %0.3f >= %0.3f",
+ dist1, mConfig.pointerGestureMultitouchMinDistance,
+ dist2, mConfig.pointerGestureMultitouchMinDistance,
+ cosine, mConfig.pointerGestureSwipeTransitionAngleCosine);
+#endif
+ mPointerGesture.currentGestureMode = PointerGesture::SWIPE;
+ } else {
+ // Pointers are moving in different directions. Switch to FREEFORM.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to FREEFORM, "
+ "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+ "cosine %0.3f < %0.3f",
+ dist1, mConfig.pointerGestureMultitouchMinDistance,
+ dist2, mConfig.pointerGestureMultitouchMinDistance,
+ cosine, mConfig.pointerGestureSwipeTransitionAngleCosine);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ }
+ }
+ }
+ }
+ }
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+ // Switch from SWIPE to FREEFORM if additional pointers go down.
+ // Cancel previous gesture.
+ if (currentFingerCount > 2) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+ currentFingerCount);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ }
+ }
+
+ // Move the reference points based on the overall group motion of the fingers
+ // except in PRESS mode while waiting for a transition to occur.
+ if (mPointerGesture.currentGestureMode != PointerGesture::PRESS
+ && (commonDeltaX || commonDeltaY)) {
+ for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ delta.dx = 0;
+ delta.dy = 0;
+ }
+
+ mPointerGesture.referenceTouchX += commonDeltaX;
+ mPointerGesture.referenceTouchY += commonDeltaY;
+
+ commonDeltaX *= mPointerXMovementScale;
+ commonDeltaY *= mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &commonDeltaX, &commonDeltaY);
+ mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
+
+ mPointerGesture.referenceGestureX += commonDeltaX;
+ mPointerGesture.referenceGestureY += commonDeltaY;
+ }
+
+ // Report gestures.
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS
+ || mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+ // PRESS or SWIPE mode.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS or SWIPE activeTouchId=%d,"
+ "activeGestureId=%d, currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+#endif
+ ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ mPointerGesture.referenceGestureX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ mPointerGesture.referenceGestureY);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
+ // FREEFORM mode.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM activeTouchId=%d,"
+ "activeGestureId=%d, currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+#endif
+ ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+
+ BitSet32 mappedTouchIdBits;
+ BitSet32 usedGestureIdBits;
+ if (mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
+ // Initially, assign the active gesture id to the active touch point
+ // if there is one. No other touch id bits are mapped yet.
+ if (!*outCancelPreviousGesture) {
+ mappedTouchIdBits.markBit(activeTouchId);
+ usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
+ mPointerGesture.activeGestureId;
+ } else {
+ mPointerGesture.activeGestureId = -1;
+ }
+ } else {
+ // Otherwise, assume we mapped all touches from the previous frame.
+ // Reuse all mappings that are still applicable.
+ mappedTouchIdBits.value = mLastFingerIdBits.value
+ & mCurrentFingerIdBits.value;
+ usedGestureIdBits = mPointerGesture.lastGestureIdBits;
+
+ // Check whether we need to choose a new active gesture id because the
+ // current went went up.
+ for (BitSet32 upTouchIdBits(mLastFingerIdBits.value
+ & ~mCurrentFingerIdBits.value);
+ !upTouchIdBits.isEmpty(); ) {
+ uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
+ uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
+ if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
+ mPointerGesture.activeGestureId = -1;
+ break;
+ }
+ }
+ }
+
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM follow up "
+ "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, "
+ "activeGestureId=%d",
+ mappedTouchIdBits.value, usedGestureIdBits.value,
+ mPointerGesture.activeGestureId);
+#endif
+
+ BitSet32 idBits(mCurrentFingerIdBits);
+ for (uint32_t i = 0; i < currentFingerCount; i++) {
+ uint32_t touchId = idBits.clearFirstMarkedBit();
+ uint32_t gestureId;
+ if (!mappedTouchIdBits.hasBit(touchId)) {
+ gestureId = usedGestureIdBits.markFirstUnmarkedBit();
+ mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM "
+ "new mapping for touch id %d -> gesture id %d",
+ touchId, gestureId);
+#endif
+ } else {
+ gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM "
+ "existing mapping for touch id %d -> gesture id %d",
+ touchId, gestureId);
+#endif
+ }
+ mPointerGesture.currentGestureIdBits.markBit(gestureId);
+ mPointerGesture.currentGestureIdToIndex[gestureId] = i;
+
+ const RawPointerData::Pointer& pointer =
+ mCurrentRawPointerData.pointerForId(touchId);
+ float deltaX = (pointer.x - mPointerGesture.referenceTouchX)
+ * mPointerXZoomScale;
+ float deltaY = (pointer.y - mPointerGesture.referenceTouchY)
+ * mPointerYZoomScale;
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+
+ mPointerGesture.currentGestureProperties[i].clear();
+ mPointerGesture.currentGestureProperties[i].id = gestureId;
+ mPointerGesture.currentGestureProperties[i].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[i].clear();
+ mPointerGesture.currentGestureCoords[i].setAxisValue(
+ AMOTION_EVENT_AXIS_X, mPointerGesture.referenceGestureX + deltaX);
+ mPointerGesture.currentGestureCoords[i].setAxisValue(
+ AMOTION_EVENT_AXIS_Y, mPointerGesture.referenceGestureY + deltaY);
+ mPointerGesture.currentGestureCoords[i].setAxisValue(
+ AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ }
+
+ if (mPointerGesture.activeGestureId < 0) {
+ mPointerGesture.activeGestureId =
+ mPointerGesture.currentGestureIdBits.firstMarkedBit();
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM new "
+ "activeGestureId=%d", mPointerGesture.activeGestureId);
+#endif
+ }
+ }
+ }
+
+ mPointerController->setButtonState(mCurrentButtonState);
+
+#if DEBUG_GESTURES
+ ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
+ "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
+ "lastGestureMode=%d, lastGestureIdBits=0x%08x",
+ toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
+ mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
+ mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
+ for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
+ const PointerProperties& properties = mPointerGesture.currentGestureProperties[index];
+ const PointerCoords& coords = mPointerGesture.currentGestureCoords[index];
+ ALOGD(" currentGesture[%d]: index=%d, toolType=%d, "
+ "x=%0.3f, y=%0.3f, pressure=%0.3f",
+ id, index, properties.toolType,
+ coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+ for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = mPointerGesture.lastGestureIdToIndex[id];
+ const PointerProperties& properties = mPointerGesture.lastGestureProperties[index];
+ const PointerCoords& coords = mPointerGesture.lastGestureCoords[index];
+ ALOGD(" lastGesture[%d]: index=%d, toolType=%d, "
+ "x=%0.3f, y=%0.3f, pressure=%0.3f",
+ id, index, properties.toolType,
+ coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+#endif
+ return true;
+}
+
+void TouchInputMapper::dispatchPointerStylus(nsecs_t when, uint32_t policyFlags) {
+ mPointerSimple.currentCoords.clear();
+ mPointerSimple.currentProperties.clear();
+
+ bool down, hovering;
+ if (!mCurrentStylusIdBits.isEmpty()) {
+ uint32_t id = mCurrentStylusIdBits.firstMarkedBit();
+ uint32_t index = mCurrentCookedPointerData.idToIndex[id];
+ float x = mCurrentCookedPointerData.pointerCoords[index].getX();
+ float y = mCurrentCookedPointerData.pointerCoords[index].getY();
+ mPointerController->setPosition(x, y);
+
+ hovering = mCurrentCookedPointerData.hoveringIdBits.hasBit(id);
+ down = !hovering;
+
+ mPointerController->getPosition(&x, &y);
+ mPointerSimple.currentCoords.copyFrom(mCurrentCookedPointerData.pointerCoords[index]);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerSimple.currentProperties.id = 0;
+ mPointerSimple.currentProperties.toolType =
+ mCurrentCookedPointerData.pointerProperties[index].toolType;
+ } else {
+ down = false;
+ hovering = false;
+ }
+
+ dispatchPointerSimple(when, policyFlags, down, hovering);
+}
+
+void TouchInputMapper::abortPointerStylus(nsecs_t when, uint32_t policyFlags) {
+ abortPointerSimple(when, policyFlags);
+}
+
+void TouchInputMapper::dispatchPointerMouse(nsecs_t when, uint32_t policyFlags) {
+ mPointerSimple.currentCoords.clear();
+ mPointerSimple.currentProperties.clear();
+
+ bool down, hovering;
+ if (!mCurrentMouseIdBits.isEmpty()) {
+ uint32_t id = mCurrentMouseIdBits.firstMarkedBit();
+ uint32_t currentIndex = mCurrentRawPointerData.idToIndex[id];
+ if (mLastMouseIdBits.hasBit(id)) {
+ uint32_t lastIndex = mCurrentRawPointerData.idToIndex[id];
+ float deltaX = (mCurrentRawPointerData.pointers[currentIndex].x
+ - mLastRawPointerData.pointers[lastIndex].x)
+ * mPointerXMovementScale;
+ float deltaY = (mCurrentRawPointerData.pointers[currentIndex].y
+ - mLastRawPointerData.pointers[lastIndex].y)
+ * mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ mPointerController->move(deltaX, deltaY);
+ } else {
+ mPointerVelocityControl.reset();
+ }
+
+ down = isPointerDown(mCurrentButtonState);
+ hovering = !down;
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ mPointerSimple.currentCoords.copyFrom(
+ mCurrentCookedPointerData.pointerCoords[currentIndex]);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+ hovering ? 0.0f : 1.0f);
+ mPointerSimple.currentProperties.id = 0;
+ mPointerSimple.currentProperties.toolType =
+ mCurrentCookedPointerData.pointerProperties[currentIndex].toolType;
+ } else {
+ mPointerVelocityControl.reset();
+
+ down = false;
+ hovering = false;
+ }
+
+ dispatchPointerSimple(when, policyFlags, down, hovering);
+}
+
+void TouchInputMapper::abortPointerMouse(nsecs_t when, uint32_t policyFlags) {
+ abortPointerSimple(when, policyFlags);
+
+ mPointerVelocityControl.reset();
+}
+
+void TouchInputMapper::dispatchPointerSimple(nsecs_t when, uint32_t policyFlags,
+ bool down, bool hovering) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+
+ if (mPointerController != NULL) {
+ if (down || hovering) {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+ mPointerController->clearSpots();
+ mPointerController->setButtonState(mCurrentButtonState);
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+ }
+
+ if (mPointerSimple.down && !down) {
+ mPointerSimple.down = false;
+
+ // Send up.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_UP, 0, metaState, mLastButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (mPointerSimple.hovering && !hovering) {
+ mPointerSimple.hovering = false;
+
+ // Send hover exit.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_EXIT, 0, metaState, mLastButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (down) {
+ if (!mPointerSimple.down) {
+ mPointerSimple.down = true;
+ mPointerSimple.downTime = when;
+
+ // Send down.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_DOWN, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Send move.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (hovering) {
+ if (!mPointerSimple.hovering) {
+ mPointerSimple.hovering = true;
+
+ // Send hover enter.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_ENTER, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Send hover move.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (mCurrentRawVScroll || mCurrentRawHScroll) {
+ float vscroll = mCurrentRawVScroll;
+ float hscroll = mCurrentRawHScroll;
+ mWheelYVelocityControl.move(when, NULL, &vscroll);
+ mWheelXVelocityControl.move(when, &hscroll, NULL);
+
+ // Send scroll.
+ PointerCoords pointerCoords;
+ pointerCoords.copyFrom(mPointerSimple.currentCoords);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
+
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_SCROLL, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &pointerCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Save state.
+ if (down || hovering) {
+ mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
+ mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+ } else {
+ mPointerSimple.reset();
+ }
+}
+
+void TouchInputMapper::abortPointerSimple(nsecs_t when, uint32_t policyFlags) {
+ mPointerSimple.currentCoords.clear();
+ mPointerSimple.currentProperties.clear();
+
+ dispatchPointerSimple(when, policyFlags, false, false);
+}
+
+void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags,
+ const PointerProperties* properties, const PointerCoords* coords,
+ const uint32_t* idToIndex, BitSet32 idBits,
+ int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) {
+ PointerCoords pointerCoords[MAX_POINTERS];
+ PointerProperties pointerProperties[MAX_POINTERS];
+ uint32_t pointerCount = 0;
+ while (!idBits.isEmpty()) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = idToIndex[id];
+ pointerProperties[pointerCount].copyFrom(properties[index]);
+ pointerCoords[pointerCount].copyFrom(coords[index]);
+
+ if (changedId >= 0 && id == uint32_t(changedId)) {
+ action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ pointerCount += 1;
+ }
+
+ ALOG_ASSERT(pointerCount != 0);
+
+ if (changedId >= 0 && pointerCount == 1) {
+ // Replace initial down and final up action.
+ // We can compare the action without masking off the changed pointer index
+ // because we know the index is 0.
+ if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ action = AMOTION_EVENT_ACTION_DOWN;
+ } else if (action == AMOTION_EVENT_ACTION_POINTER_UP) {
+ action = AMOTION_EVENT_ACTION_UP;
+ } else {
+ // Can't happen.
+ ALOG_ASSERT(false);
+ }
+ }
+
+ NotifyMotionArgs args(when, getDeviceId(), source, policyFlags,
+ action, flags, metaState, buttonState, edgeFlags,
+ mViewport.displayId, pointerCount, pointerProperties, pointerCoords,
+ xPrecision, yPrecision, downTime);
+ getListener()->notifyMotion(&args);
+}
+
+bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties,
+ const PointerCoords* inCoords, const uint32_t* inIdToIndex,
+ PointerProperties* outProperties, PointerCoords* outCoords, const uint32_t* outIdToIndex,
+ BitSet32 idBits) const {
+ bool changed = false;
+ while (!idBits.isEmpty()) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t inIndex = inIdToIndex[id];
+ uint32_t outIndex = outIdToIndex[id];
+
+ const PointerProperties& curInProperties = inProperties[inIndex];
+ const PointerCoords& curInCoords = inCoords[inIndex];
+ PointerProperties& curOutProperties = outProperties[outIndex];
+ PointerCoords& curOutCoords = outCoords[outIndex];
+
+ if (curInProperties != curOutProperties) {
+ curOutProperties.copyFrom(curInProperties);
+ changed = true;
+ }
+
+ if (curInCoords != curOutCoords) {
+ curOutCoords.copyFrom(curInCoords);
+ changed = true;
+ }
+ }
+ return changed;
+}
+
+void TouchInputMapper::fadePointer() {
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+}
+
+bool TouchInputMapper::isPointInsideSurface(int32_t x, int32_t y) {
+ return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue
+ && y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue;
+}
+
+const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(
+ int32_t x, int32_t y) {
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
+ "left=%d, top=%d, right=%d, bottom=%d",
+ x, y,
+ virtualKey.keyCode, virtualKey.scanCode,
+ virtualKey.hitLeft, virtualKey.hitTop,
+ virtualKey.hitRight, virtualKey.hitBottom);
+#endif
+
+ if (virtualKey.isHit(x, y)) {
+ return & virtualKey;
+ }
+ }
+
+ return NULL;
+}
+
+void TouchInputMapper::assignPointerIds() {
+ uint32_t currentPointerCount = mCurrentRawPointerData.pointerCount;
+ uint32_t lastPointerCount = mLastRawPointerData.pointerCount;
+
+ mCurrentRawPointerData.clearIdBits();
+
+ if (currentPointerCount == 0) {
+ // No pointers to assign.
+ return;
+ }
+
+ if (lastPointerCount == 0) {
+ // All pointers are new.
+ for (uint32_t i = 0; i < currentPointerCount; i++) {
+ uint32_t id = i;
+ mCurrentRawPointerData.pointers[i].id = id;
+ mCurrentRawPointerData.idToIndex[id] = i;
+ mCurrentRawPointerData.markIdBit(id, mCurrentRawPointerData.isHovering(i));
+ }
+ return;
+ }
+
+ if (currentPointerCount == 1 && lastPointerCount == 1
+ && mCurrentRawPointerData.pointers[0].toolType
+ == mLastRawPointerData.pointers[0].toolType) {
+ // Only one pointer and no change in count so it must have the same id as before.
+ uint32_t id = mLastRawPointerData.pointers[0].id;
+ mCurrentRawPointerData.pointers[0].id = id;
+ mCurrentRawPointerData.idToIndex[id] = 0;
+ mCurrentRawPointerData.markIdBit(id, mCurrentRawPointerData.isHovering(0));
+ return;
+ }
+
+ // General case.
+ // We build a heap of squared euclidean distances between current and last pointers
+ // associated with the current and last pointer indices. Then, we find the best
+ // match (by distance) for each current pointer.
+ // The pointers must have the same tool type but it is possible for them to
+ // transition from hovering to touching or vice-versa while retaining the same id.
+ PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS];
+
+ uint32_t heapSize = 0;
+ for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
+ currentPointerIndex++) {
+ for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
+ lastPointerIndex++) {
+ const RawPointerData::Pointer& currentPointer =
+ mCurrentRawPointerData.pointers[currentPointerIndex];
+ const RawPointerData::Pointer& lastPointer =
+ mLastRawPointerData.pointers[lastPointerIndex];
+ if (currentPointer.toolType == lastPointer.toolType) {
+ int64_t deltaX = currentPointer.x - lastPointer.x;
+ int64_t deltaY = currentPointer.y - lastPointer.y;
+
+ uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+ // Insert new element into the heap (sift up).
+ heap[heapSize].currentPointerIndex = currentPointerIndex;
+ heap[heapSize].lastPointerIndex = lastPointerIndex;
+ heap[heapSize].distance = distance;
+ heapSize += 1;
+ }
+ }
+ }
+
+ // Heapify
+ for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) {
+ startIndex -= 1;
+ for (uint32_t parentIndex = startIndex; ;) {
+ uint32_t childIndex = parentIndex * 2 + 1;
+ if (childIndex >= heapSize) {
+ break;
+ }
+
+ if (childIndex + 1 < heapSize
+ && heap[childIndex + 1].distance < heap[childIndex].distance) {
+ childIndex += 1;
+ }
+
+ if (heap[parentIndex].distance <= heap[childIndex].distance) {
+ break;
+ }
+
+ swap(heap[parentIndex], heap[childIndex]);
+ parentIndex = childIndex;
+ }
+ }
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - initial distance min-heap: size=%d", heapSize);
+ for (size_t i = 0; i < heapSize; i++) {
+ ALOGD(" heap[%d]: cur=%d, last=%d, distance=%lld",
+ i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+ heap[i].distance);
+ }
+#endif
+
+ // Pull matches out by increasing order of distance.
+ // To avoid reassigning pointers that have already been matched, the loop keeps track
+ // of which last and current pointers have been matched using the matchedXXXBits variables.
+ // It also tracks the used pointer id bits.
+ BitSet32 matchedLastBits(0);
+ BitSet32 matchedCurrentBits(0);
+ BitSet32 usedIdBits(0);
+ bool first = true;
+ for (uint32_t i = min(currentPointerCount, lastPointerCount); heapSize > 0 && i > 0; i--) {
+ while (heapSize > 0) {
+ if (first) {
+ // The first time through the loop, we just consume the root element of
+ // the heap (the one with smallest distance).
+ first = false;
+ } else {
+ // Previous iterations consumed the root element of the heap.
+ // Pop root element off of the heap (sift down).
+ heap[0] = heap[heapSize];
+ for (uint32_t parentIndex = 0; ;) {
+ uint32_t childIndex = parentIndex * 2 + 1;
+ if (childIndex >= heapSize) {
+ break;
+ }
+
+ if (childIndex + 1 < heapSize
+ && heap[childIndex + 1].distance < heap[childIndex].distance) {
+ childIndex += 1;
+ }
+
+ if (heap[parentIndex].distance <= heap[childIndex].distance) {
+ break;
+ }
+
+ swap(heap[parentIndex], heap[childIndex]);
+ parentIndex = childIndex;
+ }
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - reduced distance min-heap: size=%d", heapSize);
+ for (size_t i = 0; i < heapSize; i++) {
+ ALOGD(" heap[%d]: cur=%d, last=%d, distance=%lld",
+ i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+ heap[i].distance);
+ }
+#endif
+ }
+
+ heapSize -= 1;
+
+ uint32_t currentPointerIndex = heap[0].currentPointerIndex;
+ if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched
+
+ uint32_t lastPointerIndex = heap[0].lastPointerIndex;
+ if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched
+
+ matchedCurrentBits.markBit(currentPointerIndex);
+ matchedLastBits.markBit(lastPointerIndex);
+
+ uint32_t id = mLastRawPointerData.pointers[lastPointerIndex].id;
+ mCurrentRawPointerData.pointers[currentPointerIndex].id = id;
+ mCurrentRawPointerData.idToIndex[id] = currentPointerIndex;
+ mCurrentRawPointerData.markIdBit(id,
+ mCurrentRawPointerData.isHovering(currentPointerIndex));
+ usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld",
+ lastPointerIndex, currentPointerIndex, id, heap[0].distance);
+#endif
+ break;
+ }
+ }
+
+ // Assign fresh ids to pointers that were not matched in the process.
+ for (uint32_t i = currentPointerCount - matchedCurrentBits.count(); i != 0; i--) {
+ uint32_t currentPointerIndex = matchedCurrentBits.markFirstUnmarkedBit();
+ uint32_t id = usedIdBits.markFirstUnmarkedBit();
+
+ mCurrentRawPointerData.pointers[currentPointerIndex].id = id;
+ mCurrentRawPointerData.idToIndex[id] = currentPointerIndex;
+ mCurrentRawPointerData.markIdBit(id,
+ mCurrentRawPointerData.isHovering(currentPointerIndex));
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - assigned: cur=%d, id=%d",
+ currentPointerIndex, id);
+#endif
+ }
+}
+
+int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ if (mCurrentVirtualKey.down && mCurrentVirtualKey.keyCode == keyCode) {
+ return AKEY_STATE_VIRTUAL;
+ }
+
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+ if (virtualKey.keyCode == keyCode) {
+ return AKEY_STATE_UP;
+ }
+ }
+
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ if (mCurrentVirtualKey.down && mCurrentVirtualKey.scanCode == scanCode) {
+ return AKEY_STATE_VIRTUAL;
+ }
+
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+ if (virtualKey.scanCode == scanCode) {
+ return AKEY_STATE_UP;
+ }
+ }
+
+ return AKEY_STATE_UNKNOWN;
+}
+
+bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+
+ for (size_t i = 0; i < numCodes; i++) {
+ if (virtualKey.keyCode == keyCodes[i]) {
+ outFlags[i] = 1;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+// --- SingleTouchInputMapper ---
+
+SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device) :
+ TouchInputMapper(device) {
+}
+
+SingleTouchInputMapper::~SingleTouchInputMapper() {
+}
+
+void SingleTouchInputMapper::reset(nsecs_t when) {
+ mSingleTouchMotionAccumulator.reset(getDevice());
+
+ TouchInputMapper::reset(when);
+}
+
+void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+ TouchInputMapper::process(rawEvent);
+
+ mSingleTouchMotionAccumulator.process(rawEvent);
+}
+
+void SingleTouchInputMapper::syncTouch(nsecs_t when, bool* outHavePointerIds) {
+ if (mTouchButtonAccumulator.isToolActive()) {
+ mCurrentRawPointerData.pointerCount = 1;
+ mCurrentRawPointerData.idToIndex[0] = 0;
+
+ bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE
+ && (mTouchButtonAccumulator.isHovering()
+ || (mRawPointerAxes.pressure.valid
+ && mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0));
+ mCurrentRawPointerData.markIdBit(0, isHovering);
+
+ RawPointerData::Pointer& outPointer = mCurrentRawPointerData.pointers[0];
+ outPointer.id = 0;
+ outPointer.x = mSingleTouchMotionAccumulator.getAbsoluteX();
+ outPointer.y = mSingleTouchMotionAccumulator.getAbsoluteY();
+ outPointer.pressure = mSingleTouchMotionAccumulator.getAbsolutePressure();
+ outPointer.touchMajor = 0;
+ outPointer.touchMinor = 0;
+ outPointer.toolMajor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
+ outPointer.toolMinor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
+ outPointer.orientation = 0;
+ outPointer.distance = mSingleTouchMotionAccumulator.getAbsoluteDistance();
+ outPointer.tiltX = mSingleTouchMotionAccumulator.getAbsoluteTiltX();
+ outPointer.tiltY = mSingleTouchMotionAccumulator.getAbsoluteTiltY();
+ outPointer.toolType = mTouchButtonAccumulator.getToolType();
+ if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ }
+ outPointer.isHovering = isHovering;
+ }
+}
+
+void SingleTouchInputMapper::configureRawPointerAxes() {
+ TouchInputMapper::configureRawPointerAxes();
+
+ getAbsoluteAxisInfo(ABS_X, &mRawPointerAxes.x);
+ getAbsoluteAxisInfo(ABS_Y, &mRawPointerAxes.y);
+ getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPointerAxes.pressure);
+ getAbsoluteAxisInfo(ABS_TOOL_WIDTH, &mRawPointerAxes.toolMajor);
+ getAbsoluteAxisInfo(ABS_DISTANCE, &mRawPointerAxes.distance);
+ getAbsoluteAxisInfo(ABS_TILT_X, &mRawPointerAxes.tiltX);
+ getAbsoluteAxisInfo(ABS_TILT_Y, &mRawPointerAxes.tiltY);
+}
+
+bool SingleTouchInputMapper::hasStylus() const {
+ return mTouchButtonAccumulator.hasStylus();
+}
+
+
+// --- MultiTouchInputMapper ---
+
+MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) :
+ TouchInputMapper(device) {
+}
+
+MultiTouchInputMapper::~MultiTouchInputMapper() {
+}
+
+void MultiTouchInputMapper::reset(nsecs_t when) {
+ mMultiTouchMotionAccumulator.reset(getDevice());
+
+ mPointerIdBits.clear();
+
+ TouchInputMapper::reset(when);
+}
+
+void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+ TouchInputMapper::process(rawEvent);
+
+ mMultiTouchMotionAccumulator.process(rawEvent);
+}
+
+void MultiTouchInputMapper::syncTouch(nsecs_t when, bool* outHavePointerIds) {
+ size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
+ size_t outCount = 0;
+ BitSet32 newPointerIdBits;
+
+ for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
+ const MultiTouchMotionAccumulator::Slot* inSlot =
+ mMultiTouchMotionAccumulator.getSlot(inIndex);
+ if (!inSlot->isInUse()) {
+ continue;
+ }
+
+ if (outCount >= MAX_POINTERS) {
+#if DEBUG_POINTERS
+ ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
+ "ignoring the rest.",
+ getDeviceName().string(), MAX_POINTERS);
+#endif
+ break; // too many fingers!
+ }
+
+ RawPointerData::Pointer& outPointer = mCurrentRawPointerData.pointers[outCount];
+ outPointer.x = inSlot->getX();
+ outPointer.y = inSlot->getY();
+ outPointer.pressure = inSlot->getPressure();
+ outPointer.touchMajor = inSlot->getTouchMajor();
+ outPointer.touchMinor = inSlot->getTouchMinor();
+ outPointer.toolMajor = inSlot->getToolMajor();
+ outPointer.toolMinor = inSlot->getToolMinor();
+ outPointer.orientation = inSlot->getOrientation();
+ outPointer.distance = inSlot->getDistance();
+ outPointer.tiltX = 0;
+ outPointer.tiltY = 0;
+
+ outPointer.toolType = inSlot->getToolType();
+ if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ outPointer.toolType = mTouchButtonAccumulator.getToolType();
+ if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ }
+ }
+
+ bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE
+ && (mTouchButtonAccumulator.isHovering()
+ || (mRawPointerAxes.pressure.valid && inSlot->getPressure() <= 0));
+ outPointer.isHovering = isHovering;
+
+ // Assign pointer id using tracking id if available.
+ if (*outHavePointerIds) {
+ int32_t trackingId = inSlot->getTrackingId();
+ int32_t id = -1;
+ if (trackingId >= 0) {
+ for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty(); ) {
+ uint32_t n = idBits.clearFirstMarkedBit();
+ if (mPointerTrackingIdMap[n] == trackingId) {
+ id = n;
+ }
+ }
+
+ if (id < 0 && !mPointerIdBits.isFull()) {
+ id = mPointerIdBits.markFirstUnmarkedBit();
+ mPointerTrackingIdMap[id] = trackingId;
+ }
+ }
+ if (id < 0) {
+ *outHavePointerIds = false;
+ mCurrentRawPointerData.clearIdBits();
+ newPointerIdBits.clear();
+ } else {
+ outPointer.id = id;
+ mCurrentRawPointerData.idToIndex[id] = outCount;
+ mCurrentRawPointerData.markIdBit(id, isHovering);
+ newPointerIdBits.markBit(id);
+ }
+ }
+
+ outCount += 1;
+ }
+
+ mCurrentRawPointerData.pointerCount = outCount;
+ mPointerIdBits = newPointerIdBits;
+
+ mMultiTouchMotionAccumulator.finishSync();
+}
+
+void MultiTouchInputMapper::configureRawPointerAxes() {
+ TouchInputMapper::configureRawPointerAxes();
+
+ getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x);
+ getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y);
+ getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor);
+ getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor);
+ getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor);
+ getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor);
+ getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation);
+ getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure);
+ getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance);
+ getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId);
+ getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot);
+
+ if (mRawPointerAxes.trackingId.valid
+ && mRawPointerAxes.slot.valid
+ && mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) {
+ size_t slotCount = mRawPointerAxes.slot.maxValue + 1;
+ if (slotCount > MAX_SLOTS) {
+ ALOGW("MultiTouch Device %s reported %d slots but the framework "
+ "only supports a maximum of %d slots at this time.",
+ getDeviceName().string(), slotCount, MAX_SLOTS);
+ slotCount = MAX_SLOTS;
+ }
+ mMultiTouchMotionAccumulator.configure(getDevice(),
+ slotCount, true /*usingSlotsProtocol*/);
+ } else {
+ mMultiTouchMotionAccumulator.configure(getDevice(),
+ MAX_POINTERS, false /*usingSlotsProtocol*/);
+ }
+}
+
+bool MultiTouchInputMapper::hasStylus() const {
+ return mMultiTouchMotionAccumulator.hasStylus()
+ || mTouchButtonAccumulator.hasStylus();
+}
+
+
+// --- JoystickInputMapper ---
+
+JoystickInputMapper::JoystickInputMapper(InputDevice* device) :
+ InputMapper(device) {
+}
+
+JoystickInputMapper::~JoystickInputMapper() {
+}
+
+uint32_t JoystickInputMapper::getSources() {
+ return AINPUT_SOURCE_JOYSTICK;
+}
+
+void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ for (size_t i = 0; i < mAxes.size(); i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ addMotionRange(axis.axisInfo.axis, axis, info);
+
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ addMotionRange(axis.axisInfo.highAxis, axis, info);
+
+ }
+ }
+}
+
+void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis,
+ InputDeviceInfo* info) {
+ info->addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK,
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ /* In order to ease the transition for developers from using the old axes
+ * to the newer, more semantically correct axes, we'll continue to register
+ * the old axes as duplicates of their corresponding new ones. */
+ int32_t compatAxis = getCompatAxis(axisId);
+ if (compatAxis >= 0) {
+ info->addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK,
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ }
+}
+
+/* A mapping from axes the joystick actually has to the axes that should be
+ * artificially created for compatibility purposes.
+ * Returns -1 if no compatibility axis is needed. */
+int32_t JoystickInputMapper::getCompatAxis(int32_t axis) {
+ switch(axis) {
+ case AMOTION_EVENT_AXIS_LTRIGGER:
+ return AMOTION_EVENT_AXIS_BRAKE;
+ case AMOTION_EVENT_AXIS_RTRIGGER:
+ return AMOTION_EVENT_AXIS_GAS;
+ }
+ return -1;
+}
+
+void JoystickInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Joystick Input Mapper:\n");
+
+ dump.append(INDENT3 "Axes:\n");
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ const char* label = getAxisLabel(axis.axisInfo.axis);
+ if (label) {
+ dump.appendFormat(INDENT4 "%s", label);
+ } else {
+ dump.appendFormat(INDENT4 "%d", axis.axisInfo.axis);
+ }
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ label = getAxisLabel(axis.axisInfo.highAxis);
+ if (label) {
+ dump.appendFormat(" / %s (split at %d)", label, axis.axisInfo.splitValue);
+ } else {
+ dump.appendFormat(" / %d (split at %d)", axis.axisInfo.highAxis,
+ axis.axisInfo.splitValue);
+ }
+ } else if (axis.axisInfo.mode == AxisInfo::MODE_INVERT) {
+ dump.append(" (invert)");
+ }
+
+ dump.appendFormat(": min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f, resolution=%0.5f\n",
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ dump.appendFormat(INDENT4 " scale=%0.5f, offset=%0.5f, "
+ "highScale=%0.5f, highOffset=%0.5f\n",
+ axis.scale, axis.offset, axis.highScale, axis.highOffset);
+ dump.appendFormat(INDENT4 " rawAxis=%d, rawMin=%d, rawMax=%d, "
+ "rawFlat=%d, rawFuzz=%d, rawResolution=%d\n",
+ mAxes.keyAt(i), axis.rawAxisInfo.minValue, axis.rawAxisInfo.maxValue,
+ axis.rawAxisInfo.flat, axis.rawAxisInfo.fuzz, axis.rawAxisInfo.resolution);
+ }
+}
+
+void JoystickInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ // Collect all axes.
+ for (int32_t abs = 0; abs <= ABS_MAX; abs++) {
+ if (!(getAbsAxisUsage(abs, getDevice()->getClasses())
+ & INPUT_DEVICE_CLASS_JOYSTICK)) {
+ continue; // axis must be claimed by a different device
+ }
+
+ RawAbsoluteAxisInfo rawAxisInfo;
+ getAbsoluteAxisInfo(abs, &rawAxisInfo);
+ if (rawAxisInfo.valid) {
+ // Map axis.
+ AxisInfo axisInfo;
+ bool explicitlyMapped = !getEventHub()->mapAxis(getDeviceId(), abs, &axisInfo);
+ if (!explicitlyMapped) {
+ // Axis is not explicitly mapped, will choose a generic axis later.
+ axisInfo.mode = AxisInfo::MODE_NORMAL;
+ axisInfo.axis = -1;
+ }
+
+ // Apply flat override.
+ int32_t rawFlat = axisInfo.flatOverride < 0
+ ? rawAxisInfo.flat : axisInfo.flatOverride;
+
+ // Calculate scaling factors and limits.
+ Axis axis;
+ if (axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ float scale = 1.0f / (axisInfo.splitValue - rawAxisInfo.minValue);
+ float highScale = 1.0f / (rawAxisInfo.maxValue - axisInfo.splitValue);
+ axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+ scale, 0.0f, highScale, 0.0f,
+ 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
+ } else if (isCenteredAxis(axisInfo.axis)) {
+ float scale = 2.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
+ float offset = avg(rawAxisInfo.minValue, rawAxisInfo.maxValue) * -scale;
+ axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+ scale, offset, scale, offset,
+ -1.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
+ } else {
+ float scale = 1.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
+ axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+ scale, 0.0f, scale, 0.0f,
+ 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
+ }
+
+ // To eliminate noise while the joystick is at rest, filter out small variations
+ // in axis values up front.
+ axis.filter = axis.flat * 0.25f;
+
+ mAxes.add(abs, axis);
+ }
+ }
+
+ // If there are too many axes, start dropping them.
+ // Prefer to keep explicitly mapped axes.
+ if (mAxes.size() > PointerCoords::MAX_AXES) {
+ ALOGI("Joystick '%s' has %d axes but the framework only supports a maximum of %d.",
+ getDeviceName().string(), mAxes.size(), PointerCoords::MAX_AXES);
+ pruneAxes(true);
+ pruneAxes(false);
+ }
+
+ // Assign generic axis ids to remaining axes.
+ int32_t nextGenericAxisId = AMOTION_EVENT_AXIS_GENERIC_1;
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ Axis& axis = mAxes.editValueAt(i);
+ if (axis.axisInfo.axis < 0) {
+ while (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16
+ && haveAxis(nextGenericAxisId)) {
+ nextGenericAxisId += 1;
+ }
+
+ if (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16) {
+ axis.axisInfo.axis = nextGenericAxisId;
+ nextGenericAxisId += 1;
+ } else {
+ ALOGI("Ignoring joystick '%s' axis %d because all of the generic axis ids "
+ "have already been assigned to other axes.",
+ getDeviceName().string(), mAxes.keyAt(i));
+ mAxes.removeItemsAt(i--);
+ numAxes -= 1;
+ }
+ }
+ }
+ }
+}
+
+bool JoystickInputMapper::haveAxis(int32_t axisId) {
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ if (axis.axisInfo.axis == axisId
+ || (axis.axisInfo.mode == AxisInfo::MODE_SPLIT
+ && axis.axisInfo.highAxis == axisId)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void JoystickInputMapper::pruneAxes(bool ignoreExplicitlyMappedAxes) {
+ size_t i = mAxes.size();
+ while (mAxes.size() > PointerCoords::MAX_AXES && i-- > 0) {
+ if (ignoreExplicitlyMappedAxes && mAxes.valueAt(i).explicitlyMapped) {
+ continue;
+ }
+ ALOGI("Discarding joystick '%s' axis %d because there are too many axes.",
+ getDeviceName().string(), mAxes.keyAt(i));
+ mAxes.removeItemsAt(i);
+ }
+}
+
+bool JoystickInputMapper::isCenteredAxis(int32_t axis) {
+ switch (axis) {
+ case AMOTION_EVENT_AXIS_X:
+ case AMOTION_EVENT_AXIS_Y:
+ case AMOTION_EVENT_AXIS_Z:
+ case AMOTION_EVENT_AXIS_RX:
+ case AMOTION_EVENT_AXIS_RY:
+ case AMOTION_EVENT_AXIS_RZ:
+ case AMOTION_EVENT_AXIS_HAT_X:
+ case AMOTION_EVENT_AXIS_HAT_Y:
+ case AMOTION_EVENT_AXIS_ORIENTATION:
+ case AMOTION_EVENT_AXIS_RUDDER:
+ case AMOTION_EVENT_AXIS_WHEEL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void JoystickInputMapper::reset(nsecs_t when) {
+ // Recenter all axes.
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ Axis& axis = mAxes.editValueAt(i);
+ axis.resetValue();
+ }
+
+ InputMapper::reset(when);
+}
+
+void JoystickInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_ABS: {
+ ssize_t index = mAxes.indexOfKey(rawEvent->code);
+ if (index >= 0) {
+ Axis& axis = mAxes.editValueAt(index);
+ float newValue, highNewValue;
+ switch (axis.axisInfo.mode) {
+ case AxisInfo::MODE_INVERT:
+ newValue = (axis.rawAxisInfo.maxValue - rawEvent->value)
+ * axis.scale + axis.offset;
+ highNewValue = 0.0f;
+ break;
+ case AxisInfo::MODE_SPLIT:
+ if (rawEvent->value < axis.axisInfo.splitValue) {
+ newValue = (axis.axisInfo.splitValue - rawEvent->value)
+ * axis.scale + axis.offset;
+ highNewValue = 0.0f;
+ } else if (rawEvent->value > axis.axisInfo.splitValue) {
+ newValue = 0.0f;
+ highNewValue = (rawEvent->value - axis.axisInfo.splitValue)
+ * axis.highScale + axis.highOffset;
+ } else {
+ newValue = 0.0f;
+ highNewValue = 0.0f;
+ }
+ break;
+ default:
+ newValue = rawEvent->value * axis.scale + axis.offset;
+ highNewValue = 0.0f;
+ break;
+ }
+ axis.newValue = newValue;
+ axis.highNewValue = highNewValue;
+ }
+ break;
+ }
+
+ case EV_SYN:
+ switch (rawEvent->code) {
+ case SYN_REPORT:
+ sync(rawEvent->when, false /*force*/);
+ break;
+ }
+ break;
+ }
+}
+
+void JoystickInputMapper::sync(nsecs_t when, bool force) {
+ if (!filterAxes(force)) {
+ return;
+ }
+
+ int32_t metaState = mContext->getGlobalMetaState();
+ int32_t buttonState = 0;
+
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.axis, axis.currentValue);
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.highAxis,
+ axis.highCurrentValue);
+ }
+ }
+
+ // Moving a joystick axis should not wake the device because joysticks can
+ // be fairly noisy even when not in use. On the other hand, pushing a gamepad
+ // button will likely wake the device.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ uint32_t policyFlags = 0;
+
+ NotifyMotionArgs args(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, policyFlags,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ ADISPLAY_ID_NONE, 1, &pointerProperties, &pointerCoords, 0, 0, 0);
+ getListener()->notifyMotion(&args);
+}
+
+void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords,
+ int32_t axis, float value) {
+ pointerCoords->setAxisValue(axis, value);
+ /* In order to ease the transition for developers from using the old axes
+ * to the newer, more semantically correct axes, we'll continue to produce
+ * values for the old axes as mirrors of the value of their corresponding
+ * new axes. */
+ int32_t compatAxis = getCompatAxis(axis);
+ if (compatAxis >= 0) {
+ pointerCoords->setAxisValue(compatAxis, value);
+ }
+}
+
+bool JoystickInputMapper::filterAxes(bool force) {
+ bool atLeastOneSignificantChange = force;
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ Axis& axis = mAxes.editValueAt(i);
+ if (force || hasValueChangedSignificantly(axis.filter,
+ axis.newValue, axis.currentValue, axis.min, axis.max)) {
+ axis.currentValue = axis.newValue;
+ atLeastOneSignificantChange = true;
+ }
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ if (force || hasValueChangedSignificantly(axis.filter,
+ axis.highNewValue, axis.highCurrentValue, axis.min, axis.max)) {
+ axis.highCurrentValue = axis.highNewValue;
+ atLeastOneSignificantChange = true;
+ }
+ }
+ }
+ return atLeastOneSignificantChange;
+}
+
+bool JoystickInputMapper::hasValueChangedSignificantly(
+ float filter, float newValue, float currentValue, float min, float max) {
+ if (newValue != currentValue) {
+ // Filter out small changes in value unless the value is converging on the axis
+ // bounds or center point. This is intended to reduce the amount of information
+ // sent to applications by particularly noisy joysticks (such as PS3).
+ if (fabs(newValue - currentValue) > filter
+ || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, min)
+ || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, max)
+ || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, 0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool JoystickInputMapper::hasMovedNearerToValueWithinFilteredRange(
+ float filter, float newValue, float currentValue, float thresholdValue) {
+ float newDistance = fabs(newValue - thresholdValue);
+ if (newDistance < filter) {
+ float oldDistance = fabs(currentValue - thresholdValue);
+ if (newDistance < oldDistance) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputReader.h b/widget/gonk/libui/InputReader.h
new file mode 100644
index 0000000000..5c790fdb86
--- /dev/null
+++ b/widget/gonk/libui/InputReader.h
@@ -0,0 +1,1811 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _UI_INPUT_READER_H
+#define _UI_INPUT_READER_H
+
+#include "EventHub.h"
+#include "PointerController.h"
+#include "InputListener.h"
+
+#include "Input.h"
+#include "VelocityControl.h"
+#include "VelocityTracker.h"
+#include <utils/KeyedVector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/BitSet.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+// Maximum supported size of a vibration pattern.
+// Must be at least 2.
+#define MAX_VIBRATE_PATTERN_SIZE 100
+
+// Maximum allowable delay value in a vibration pattern before
+// which the delay will be truncated.
+#define MAX_VIBRATE_PATTERN_DELAY_NSECS (1000000 * 1000000000LL)
+
+namespace android {
+
+class InputDevice;
+class InputMapper;
+
+/*
+ * Describes how coordinates are mapped on a physical display.
+ * See com.android.server.display.DisplayViewport.
+ */
+struct DisplayViewport {
+ int32_t displayId; // -1 if invalid
+ int32_t orientation;
+ int32_t logicalLeft;
+ int32_t logicalTop;
+ int32_t logicalRight;
+ int32_t logicalBottom;
+ int32_t physicalLeft;
+ int32_t physicalTop;
+ int32_t physicalRight;
+ int32_t physicalBottom;
+ int32_t deviceWidth;
+ int32_t deviceHeight;
+
+ DisplayViewport() :
+ displayId(ADISPLAY_ID_NONE), orientation(DISPLAY_ORIENTATION_0),
+ logicalLeft(0), logicalTop(0), logicalRight(0), logicalBottom(0),
+ physicalLeft(0), physicalTop(0), physicalRight(0), physicalBottom(0),
+ deviceWidth(0), deviceHeight(0) {
+ }
+
+ bool operator==(const DisplayViewport& other) const {
+ return displayId == other.displayId
+ && orientation == other.orientation
+ && logicalLeft == other.logicalLeft
+ && logicalTop == other.logicalTop
+ && logicalRight == other.logicalRight
+ && logicalBottom == other.logicalBottom
+ && physicalLeft == other.physicalLeft
+ && physicalTop == other.physicalTop
+ && physicalRight == other.physicalRight
+ && physicalBottom == other.physicalBottom
+ && deviceWidth == other.deviceWidth
+ && deviceHeight == other.deviceHeight;
+ }
+
+ bool operator!=(const DisplayViewport& other) const {
+ return !(*this == other);
+ }
+
+ inline bool isValid() const {
+ return displayId >= 0;
+ }
+
+ void setNonDisplayViewport(int32_t width, int32_t height) {
+ displayId = ADISPLAY_ID_NONE;
+ orientation = DISPLAY_ORIENTATION_0;
+ logicalLeft = 0;
+ logicalTop = 0;
+ logicalRight = width;
+ logicalBottom = height;
+ physicalLeft = 0;
+ physicalTop = 0;
+ physicalRight = width;
+ physicalBottom = height;
+ deviceWidth = width;
+ deviceHeight = height;
+ }
+};
+
+/*
+ * Input reader configuration.
+ *
+ * Specifies various options that modify the behavior of the input reader.
+ */
+struct InputReaderConfiguration {
+ // Describes changes that have occurred.
+ enum {
+ // The pointer speed changed.
+ CHANGE_POINTER_SPEED = 1 << 0,
+
+ // The pointer gesture control changed.
+ CHANGE_POINTER_GESTURE_ENABLEMENT = 1 << 1,
+
+ // The display size or orientation changed.
+ CHANGE_DISPLAY_INFO = 1 << 2,
+
+ // The visible touches option changed.
+ CHANGE_SHOW_TOUCHES = 1 << 3,
+
+ // The keyboard layouts must be reloaded.
+ CHANGE_KEYBOARD_LAYOUTS = 1 << 4,
+
+ // The device name alias supplied by the may have changed for some devices.
+ CHANGE_DEVICE_ALIAS = 1 << 5,
+
+ // All devices must be reopened.
+ CHANGE_MUST_REOPEN = 1 << 31,
+ };
+
+ // Gets the amount of time to disable virtual keys after the screen is touched
+ // in order to filter out accidental virtual key presses due to swiping gestures
+ // or taps near the edge of the display. May be 0 to disable the feature.
+ nsecs_t virtualKeyQuietTime;
+
+ // The excluded device names for the platform.
+ // Devices with these names will be ignored.
+ Vector<String8> excludedDeviceNames;
+
+ // Velocity control parameters for mouse pointer movements.
+ VelocityControlParameters pointerVelocityControlParameters;
+
+ // Velocity control parameters for mouse wheel movements.
+ VelocityControlParameters wheelVelocityControlParameters;
+
+ // True if pointer gestures are enabled.
+ bool pointerGesturesEnabled;
+
+ // Quiet time between certain pointer gesture transitions.
+ // Time to allow for all fingers or buttons to settle into a stable state before
+ // starting a new gesture.
+ nsecs_t pointerGestureQuietInterval;
+
+ // The minimum speed that a pointer must travel for us to consider switching the active
+ // touch pointer to it during a drag. This threshold is set to avoid switching due
+ // to noise from a finger resting on the touch pad (perhaps just pressing it down).
+ float pointerGestureDragMinSwitchSpeed; // in pixels per second
+
+ // Tap gesture delay time.
+ // The time between down and up must be less than this to be considered a tap.
+ nsecs_t pointerGestureTapInterval;
+
+ // Tap drag gesture delay time.
+ // The time between the previous tap's up and the next down must be less than
+ // this to be considered a drag. Otherwise, the previous tap is finished and a
+ // new tap begins.
+ //
+ // Note that the previous tap will be held down for this entire duration so this
+ // interval must be shorter than the long press timeout.
+ nsecs_t pointerGestureTapDragInterval;
+
+ // The distance in pixels that the pointer is allowed to move from initial down
+ // to up and still be called a tap.
+ float pointerGestureTapSlop; // in pixels
+
+ // Time after the first touch points go down to settle on an initial centroid.
+ // This is intended to be enough time to handle cases where the user puts down two
+ // fingers at almost but not quite exactly the same time.
+ nsecs_t pointerGestureMultitouchSettleInterval;
+
+ // The transition from PRESS to SWIPE or FREEFORM gesture mode is made when
+ // at least two pointers have moved at least this far from their starting place.
+ float pointerGestureMultitouchMinDistance; // in pixels
+
+ // The transition from PRESS to SWIPE gesture mode can only occur when the
+ // cosine of the angle between the two vectors is greater than or equal to than this value
+ // which indicates that the vectors are oriented in the same direction.
+ // When the vectors are oriented in the exactly same direction, the cosine is 1.0.
+ // (In exactly opposite directions, the cosine is -1.0.)
+ float pointerGestureSwipeTransitionAngleCosine;
+
+ // The transition from PRESS to SWIPE gesture mode can only occur when the
+ // fingers are no more than this far apart relative to the diagonal size of
+ // the touch pad. For example, a ratio of 0.5 means that the fingers must be
+ // no more than half the diagonal size of the touch pad apart.
+ float pointerGestureSwipeMaxWidthRatio;
+
+ // The gesture movement speed factor relative to the size of the display.
+ // Movement speed applies when the fingers are moving in the same direction.
+ // Without acceleration, a full swipe of the touch pad diagonal in movement mode
+ // will cover this portion of the display diagonal.
+ float pointerGestureMovementSpeedRatio;
+
+ // The gesture zoom speed factor relative to the size of the display.
+ // Zoom speed applies when the fingers are mostly moving relative to each other
+ // to execute a scale gesture or similar.
+ // Without acceleration, a full swipe of the touch pad diagonal in zoom mode
+ // will cover this portion of the display diagonal.
+ float pointerGestureZoomSpeedRatio;
+
+ // True to show the location of touches on the touch screen as spots.
+ bool showTouches;
+
+ InputReaderConfiguration() :
+ virtualKeyQuietTime(0),
+ pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, 3.0f),
+ wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f),
+ pointerGesturesEnabled(true),
+ pointerGestureQuietInterval(100 * 1000000LL), // 100 ms
+ pointerGestureDragMinSwitchSpeed(50), // 50 pixels per second
+ pointerGestureTapInterval(150 * 1000000LL), // 150 ms
+ pointerGestureTapDragInterval(150 * 1000000LL), // 150 ms
+ pointerGestureTapSlop(10.0f), // 10 pixels
+ pointerGestureMultitouchSettleInterval(100 * 1000000LL), // 100 ms
+ pointerGestureMultitouchMinDistance(15), // 15 pixels
+ pointerGestureSwipeTransitionAngleCosine(0.2588f), // cosine of 75 degrees
+ pointerGestureSwipeMaxWidthRatio(0.25f),
+ pointerGestureMovementSpeedRatio(0.8f),
+ pointerGestureZoomSpeedRatio(0.3f),
+ showTouches(false) { }
+
+ bool getDisplayInfo(bool external, DisplayViewport* outViewport) const;
+ void setDisplayInfo(bool external, const DisplayViewport& viewport);
+
+private:
+ DisplayViewport mInternalDisplay;
+ DisplayViewport mExternalDisplay;
+};
+
+
+/*
+ * Input reader policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ *
+ * These methods must NOT re-enter the input reader since they may be called while
+ * holding the input reader lock.
+ */
+class InputReaderPolicyInterface : public virtual RefBase {
+protected:
+ InputReaderPolicyInterface() { }
+ virtual ~InputReaderPolicyInterface() { }
+
+public:
+ /* Gets the input reader configuration. */
+ virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) = 0;
+
+ /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
+ virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+
+ /* Notifies the input reader policy that some input devices have changed
+ * and provides information about all current input devices.
+ */
+ virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) = 0;
+
+ /* Gets the keyboard layout for a particular input device. */
+ virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(const String8& inputDeviceDescriptor) = 0;
+
+ /* Gets a user-supplied alias for a particular input device, or an empty string if none. */
+ virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier) = 0;
+};
+
+
+/* Processes raw input events and sends cooked event data to an input listener. */
+class InputReaderInterface : public virtual RefBase {
+protected:
+ InputReaderInterface() { }
+ virtual ~InputReaderInterface() { }
+
+public:
+ /* Dumps the state of the input reader.
+ *
+ * This method may be called on any thread (usually by the input manager). */
+ virtual void dump(String8& dump) = 0;
+
+ /* Called by the heatbeat to ensures that the reader has not deadlocked. */
+ virtual void monitor() = 0;
+
+ /* Runs a single iteration of the processing loop.
+ * Nominally reads and processes one incoming message from the EventHub.
+ *
+ * This method should be called on the input reader thread.
+ */
+ virtual void loopOnce() = 0;
+
+ /* Gets information about all input devices.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices) = 0;
+
+ /* Query current input state. */
+ virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode) = 0;
+ virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode) = 0;
+ virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+ int32_t sw) = 0;
+
+ /* Determine whether physical keys exist for the given framework-domain key codes. */
+ virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0;
+
+ /* Requests that a reconfiguration of all input devices.
+ * The changes flag is a bitfield that indicates what has changed and whether
+ * the input devices must all be reopened. */
+ virtual void requestRefreshConfiguration(uint32_t changes) = 0;
+
+ /* Controls the vibrator of a particular input device. */
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) = 0;
+ virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0;
+};
+
+
+/* Internal interface used by individual input devices to access global input device state
+ * and parameters maintained by the input reader.
+ */
+class InputReaderContext {
+public:
+ InputReaderContext() { }
+ virtual ~InputReaderContext() { }
+
+ virtual void updateGlobalMetaState() = 0;
+ virtual int32_t getGlobalMetaState() = 0;
+
+ virtual void disableVirtualKeysUntil(nsecs_t time) = 0;
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) = 0;
+
+ virtual void fadePointer() = 0;
+
+ virtual void requestTimeoutAtTime(nsecs_t when) = 0;
+ virtual int32_t bumpGeneration() = 0;
+
+ virtual InputReaderPolicyInterface* getPolicy() = 0;
+ virtual InputListenerInterface* getListener() = 0;
+ virtual EventHubInterface* getEventHub() = 0;
+};
+
+
+/* The input reader reads raw event data from the event hub and processes it into input events
+ * that it sends to the input listener. Some functions of the input reader, such as early
+ * event filtering in low power states, are controlled by a separate policy object.
+ *
+ * The InputReader owns a collection of InputMappers. Most of the work it does happens
+ * on the input reader thread but the InputReader can receive queries from other system
+ * components running on arbitrary threads. To keep things manageable, the InputReader
+ * uses a single Mutex to guard its state. The Mutex may be held while calling into the
+ * EventHub or the InputReaderPolicy but it is never held while calling into the
+ * InputListener.
+ */
+class InputReader : public InputReaderInterface {
+public:
+ InputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputListenerInterface>& listener);
+ virtual ~InputReader();
+
+ virtual void dump(String8& dump);
+ virtual void monitor();
+
+ virtual void loopOnce();
+
+ virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices);
+
+ virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode);
+ virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode);
+ virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+ int32_t sw);
+
+ virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual void requestRefreshConfiguration(uint32_t changes);
+
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token);
+ virtual void cancelVibrate(int32_t deviceId, int32_t token);
+
+protected:
+ // These members are protected so they can be instrumented by test cases.
+ virtual InputDevice* createDeviceLocked(int32_t deviceId,
+ const InputDeviceIdentifier& identifier, uint32_t classes);
+
+ class ContextImpl : public InputReaderContext {
+ InputReader* mReader;
+
+ public:
+ ContextImpl(InputReader* reader);
+
+ virtual void updateGlobalMetaState();
+ virtual int32_t getGlobalMetaState();
+ virtual void disableVirtualKeysUntil(nsecs_t time);
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode);
+ virtual void fadePointer();
+ virtual void requestTimeoutAtTime(nsecs_t when);
+ virtual int32_t bumpGeneration();
+ virtual InputReaderPolicyInterface* getPolicy();
+ virtual InputListenerInterface* getListener();
+ virtual EventHubInterface* getEventHub();
+ } mContext;
+
+ friend class ContextImpl;
+
+private:
+ Mutex mLock;
+
+ Condition mReaderIsAliveCondition;
+
+ sp<EventHubInterface> mEventHub;
+ sp<InputReaderPolicyInterface> mPolicy;
+ sp<QueuedInputListener> mQueuedListener;
+
+ InputReaderConfiguration mConfig;
+
+ // The event queue.
+ static const int EVENT_BUFFER_SIZE = 256;
+ RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
+
+ KeyedVector<int32_t, InputDevice*> mDevices;
+
+ // low-level input event decoding and device management
+ void processEventsLocked(const RawEvent* rawEvents, size_t count);
+
+ void addDeviceLocked(nsecs_t when, int32_t deviceId);
+ void removeDeviceLocked(nsecs_t when, int32_t deviceId);
+ void processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count);
+ void timeoutExpiredLocked(nsecs_t when);
+
+ void handleConfigurationChangedLocked(nsecs_t when);
+
+ int32_t mGlobalMetaState;
+ void updateGlobalMetaStateLocked();
+ int32_t getGlobalMetaStateLocked();
+
+ void fadePointerLocked();
+
+ int32_t mGeneration;
+ int32_t bumpGenerationLocked();
+
+ void getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices);
+
+ nsecs_t mDisableVirtualKeysTimeout;
+ void disableVirtualKeysUntilLocked(nsecs_t time);
+ bool shouldDropVirtualKeyLocked(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode);
+
+ nsecs_t mNextTimeout;
+ void requestTimeoutAtTimeLocked(nsecs_t when);
+
+ uint32_t mConfigurationChangesToRefresh;
+ void refreshConfigurationLocked(uint32_t changes);
+
+ // state queries
+ typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+ int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
+ GetStateFunc getStateFunc);
+ bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+};
+
+
+/* Reads raw events from the event hub and processes them, endlessly. */
+class InputReaderThread : public Thread {
+public:
+ InputReaderThread(const sp<InputReaderInterface>& reader);
+ virtual ~InputReaderThread();
+
+private:
+ uint32_t mFoo;
+ sp<InputReaderInterface> mReader;
+
+ virtual bool threadLoop();
+};
+
+
+/* Represents the state of a single input device. */
+class InputDevice {
+public:
+ InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
+ const InputDeviceIdentifier& identifier, uint32_t classes);
+ ~InputDevice();
+
+ inline InputReaderContext* getContext() { return mContext; }
+ inline int32_t getId() { return mId; }
+ inline int32_t getGeneration() { return mGeneration; }
+ inline const String8& getName() { return mIdentifier.name; }
+ inline uint32_t getClasses() { return mClasses; }
+ inline uint32_t getSources() { return mSources; }
+
+ inline bool isExternal() { return mIsExternal; }
+ inline void setExternal(bool external) { mIsExternal = external; }
+
+ inline bool isIgnored() { return mMappers.isEmpty(); }
+
+ void dump(String8& dump);
+ void addMapper(InputMapper* mapper);
+ void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ void reset(nsecs_t when);
+ void process(const RawEvent* rawEvents, size_t count);
+ void timeoutExpired(nsecs_t when);
+
+ void getDeviceInfo(InputDeviceInfo* outDeviceInfo);
+ int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+ bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+ void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, int32_t token);
+ void cancelVibrate(int32_t token);
+
+ int32_t getMetaState();
+
+ void fadePointer();
+
+ void bumpGeneration();
+
+ void notifyReset(nsecs_t when);
+
+ inline const PropertyMap& getConfiguration() { return mConfiguration; }
+ inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+ bool hasKey(int32_t code) {
+ return getEventHub()->hasScanCode(mId, code);
+ }
+
+ bool hasAbsoluteAxis(int32_t code) {
+ RawAbsoluteAxisInfo info;
+ getEventHub()->getAbsoluteAxisInfo(mId, code, &info);
+ return info.valid;
+ }
+
+ bool isKeyPressed(int32_t code) {
+ return getEventHub()->getScanCodeState(mId, code) == AKEY_STATE_DOWN;
+ }
+
+ int32_t getAbsoluteAxisValue(int32_t code) {
+ int32_t value;
+ getEventHub()->getAbsoluteAxisValue(mId, code, &value);
+ return value;
+ }
+
+private:
+ InputReaderContext* mContext;
+ int32_t mId;
+ int32_t mGeneration;
+ InputDeviceIdentifier mIdentifier;
+ String8 mAlias;
+ uint32_t mClasses;
+
+ Vector<InputMapper*> mMappers;
+
+ uint32_t mSources;
+ bool mIsExternal;
+ bool mDropUntilNextSync;
+
+ typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+ int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
+
+ PropertyMap mConfiguration;
+};
+
+
+/* Keeps track of the state of mouse or touch pad buttons. */
+class CursorButtonAccumulator {
+public:
+ CursorButtonAccumulator();
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+
+ uint32_t getButtonState() const;
+
+private:
+ bool mBtnLeft;
+ bool mBtnRight;
+ bool mBtnMiddle;
+ bool mBtnBack;
+ bool mBtnSide;
+ bool mBtnForward;
+ bool mBtnExtra;
+ bool mBtnTask;
+
+ void clearButtons();
+};
+
+
+/* Keeps track of cursor movements. */
+
+class CursorMotionAccumulator {
+public:
+ CursorMotionAccumulator();
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+
+ inline int32_t getRelativeX() const { return mRelX; }
+ inline int32_t getRelativeY() const { return mRelY; }
+
+private:
+ int32_t mRelX;
+ int32_t mRelY;
+
+ void clearRelativeAxes();
+};
+
+
+/* Keeps track of cursor scrolling motions. */
+
+class CursorScrollAccumulator {
+public:
+ CursorScrollAccumulator();
+ void configure(InputDevice* device);
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+
+ inline bool haveRelativeVWheel() const { return mHaveRelWheel; }
+ inline bool haveRelativeHWheel() const { return mHaveRelHWheel; }
+
+ inline int32_t getRelativeX() const { return mRelX; }
+ inline int32_t getRelativeY() const { return mRelY; }
+ inline int32_t getRelativeVWheel() const { return mRelWheel; }
+ inline int32_t getRelativeHWheel() const { return mRelHWheel; }
+
+private:
+ bool mHaveRelWheel;
+ bool mHaveRelHWheel;
+
+ int32_t mRelX;
+ int32_t mRelY;
+ int32_t mRelWheel;
+ int32_t mRelHWheel;
+
+ void clearRelativeAxes();
+};
+
+
+/* Keeps track of the state of touch, stylus and tool buttons. */
+class TouchButtonAccumulator {
+public:
+ TouchButtonAccumulator();
+ void configure(InputDevice* device);
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+
+ uint32_t getButtonState() const;
+ int32_t getToolType() const;
+ bool isToolActive() const;
+ bool isHovering() const;
+ bool hasStylus() const;
+
+private:
+ bool mHaveBtnTouch;
+ bool mHaveStylus;
+
+ bool mBtnTouch;
+ bool mBtnStylus;
+ bool mBtnStylus2;
+ bool mBtnToolFinger;
+ bool mBtnToolPen;
+ bool mBtnToolRubber;
+ bool mBtnToolBrush;
+ bool mBtnToolPencil;
+ bool mBtnToolAirbrush;
+ bool mBtnToolMouse;
+ bool mBtnToolLens;
+ bool mBtnToolDoubleTap;
+ bool mBtnToolTripleTap;
+ bool mBtnToolQuadTap;
+
+ void clearButtons();
+};
+
+
+/* Raw axis information from the driver. */
+struct RawPointerAxes {
+ RawAbsoluteAxisInfo x;
+ RawAbsoluteAxisInfo y;
+ RawAbsoluteAxisInfo pressure;
+ RawAbsoluteAxisInfo touchMajor;
+ RawAbsoluteAxisInfo touchMinor;
+ RawAbsoluteAxisInfo toolMajor;
+ RawAbsoluteAxisInfo toolMinor;
+ RawAbsoluteAxisInfo orientation;
+ RawAbsoluteAxisInfo distance;
+ RawAbsoluteAxisInfo tiltX;
+ RawAbsoluteAxisInfo tiltY;
+ RawAbsoluteAxisInfo trackingId;
+ RawAbsoluteAxisInfo slot;
+
+ RawPointerAxes();
+ void clear();
+};
+
+
+/* Raw data for a collection of pointers including a pointer id mapping table. */
+struct RawPointerData {
+ struct Pointer {
+ uint32_t id;
+ int32_t x;
+ int32_t y;
+ int32_t pressure;
+ int32_t touchMajor;
+ int32_t touchMinor;
+ int32_t toolMajor;
+ int32_t toolMinor;
+ int32_t orientation;
+ int32_t distance;
+ int32_t tiltX;
+ int32_t tiltY;
+ int32_t toolType; // a fully decoded AMOTION_EVENT_TOOL_TYPE constant
+ bool isHovering;
+ };
+
+ uint32_t pointerCount;
+ Pointer pointers[MAX_POINTERS];
+ BitSet32 hoveringIdBits, touchingIdBits;
+ uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+ RawPointerData();
+ void clear();
+ void copyFrom(const RawPointerData& other);
+ void getCentroidOfTouchingPointers(float* outX, float* outY) const;
+
+ inline void markIdBit(uint32_t id, bool isHovering) {
+ if (isHovering) {
+ hoveringIdBits.markBit(id);
+ } else {
+ touchingIdBits.markBit(id);
+ }
+ }
+
+ inline void clearIdBits() {
+ hoveringIdBits.clear();
+ touchingIdBits.clear();
+ }
+
+ inline const Pointer& pointerForId(uint32_t id) const {
+ return pointers[idToIndex[id]];
+ }
+
+ inline bool isHovering(uint32_t pointerIndex) {
+ return pointers[pointerIndex].isHovering;
+ }
+};
+
+
+/* Cooked data for a collection of pointers including a pointer id mapping table. */
+struct CookedPointerData {
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ BitSet32 hoveringIdBits, touchingIdBits;
+ uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+ CookedPointerData();
+ void clear();
+ void copyFrom(const CookedPointerData& other);
+
+ inline const PointerCoords& pointerCoordsForId(uint32_t id) const {
+ return pointerCoords[idToIndex[id]];
+ }
+
+ inline bool isHovering(uint32_t pointerIndex) {
+ return hoveringIdBits.hasBit(pointerProperties[pointerIndex].id);
+ }
+};
+
+
+/* Keeps track of the state of single-touch protocol. */
+class SingleTouchMotionAccumulator {
+public:
+ SingleTouchMotionAccumulator();
+
+ void process(const RawEvent* rawEvent);
+ void reset(InputDevice* device);
+
+ inline int32_t getAbsoluteX() const { return mAbsX; }
+ inline int32_t getAbsoluteY() const { return mAbsY; }
+ inline int32_t getAbsolutePressure() const { return mAbsPressure; }
+ inline int32_t getAbsoluteToolWidth() const { return mAbsToolWidth; }
+ inline int32_t getAbsoluteDistance() const { return mAbsDistance; }
+ inline int32_t getAbsoluteTiltX() const { return mAbsTiltX; }
+ inline int32_t getAbsoluteTiltY() const { return mAbsTiltY; }
+
+private:
+ int32_t mAbsX;
+ int32_t mAbsY;
+ int32_t mAbsPressure;
+ int32_t mAbsToolWidth;
+ int32_t mAbsDistance;
+ int32_t mAbsTiltX;
+ int32_t mAbsTiltY;
+
+ void clearAbsoluteAxes();
+};
+
+
+/* Keeps track of the state of multi-touch protocol. */
+class MultiTouchMotionAccumulator {
+public:
+ class Slot {
+ public:
+ inline bool isInUse() const { return mInUse; }
+ inline int32_t getX() const { return mAbsMTPositionX; }
+ inline int32_t getY() const { return mAbsMTPositionY; }
+ inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; }
+ inline int32_t getTouchMinor() const {
+ return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor; }
+ inline int32_t getToolMajor() const { return mAbsMTWidthMajor; }
+ inline int32_t getToolMinor() const {
+ return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor; }
+ inline int32_t getOrientation() const { return mAbsMTOrientation; }
+ inline int32_t getTrackingId() const { return mAbsMTTrackingId; }
+ inline int32_t getPressure() const { return mAbsMTPressure; }
+ inline int32_t getDistance() const { return mAbsMTDistance; }
+ inline int32_t getToolType() const;
+
+ private:
+ friend class MultiTouchMotionAccumulator;
+
+ bool mInUse;
+ bool mHaveAbsMTTouchMinor;
+ bool mHaveAbsMTWidthMinor;
+ bool mHaveAbsMTToolType;
+
+ int32_t mAbsMTPositionX;
+ int32_t mAbsMTPositionY;
+ int32_t mAbsMTTouchMajor;
+ int32_t mAbsMTTouchMinor;
+ int32_t mAbsMTWidthMajor;
+ int32_t mAbsMTWidthMinor;
+ int32_t mAbsMTOrientation;
+ int32_t mAbsMTTrackingId;
+ int32_t mAbsMTPressure;
+ int32_t mAbsMTDistance;
+ int32_t mAbsMTToolType;
+
+ Slot();
+ void clear();
+ };
+
+ MultiTouchMotionAccumulator();
+ ~MultiTouchMotionAccumulator();
+
+ void configure(InputDevice* device, size_t slotCount, bool usingSlotsProtocol);
+ void reset(InputDevice* device);
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+ bool hasStylus() const;
+
+ inline size_t getSlotCount() const { return mSlotCount; }
+ inline const Slot* getSlot(size_t index) const { return &mSlots[index]; }
+
+private:
+ int32_t mCurrentSlot;
+ Slot* mSlots;
+ size_t mSlotCount;
+ bool mUsingSlotsProtocol;
+ bool mHaveStylus;
+
+ void clearSlots(int32_t initialSlot);
+};
+
+
+/* An input mapper transforms raw input events into cooked event data.
+ * A single input device can have multiple associated input mappers in order to interpret
+ * different classes of events.
+ *
+ * InputMapper lifecycle:
+ * - create
+ * - configure with 0 changes
+ * - reset
+ * - process, process, process (may occasionally reconfigure with non-zero changes or reset)
+ * - reset
+ * - destroy
+ */
+class InputMapper {
+public:
+ InputMapper(InputDevice* device);
+ virtual ~InputMapper();
+
+ inline InputDevice* getDevice() { return mDevice; }
+ inline int32_t getDeviceId() { return mDevice->getId(); }
+ inline const String8 getDeviceName() { return mDevice->getName(); }
+ inline InputReaderContext* getContext() { return mContext; }
+ inline InputReaderPolicyInterface* getPolicy() { return mContext->getPolicy(); }
+ inline InputListenerInterface* getListener() { return mContext->getListener(); }
+ inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+ virtual uint32_t getSources() = 0;
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent) = 0;
+ virtual void timeoutExpired(nsecs_t when);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
+
+ virtual int32_t getMetaState();
+
+ virtual void fadePointer();
+
+protected:
+ InputDevice* mDevice;
+ InputReaderContext* mContext;
+
+ status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
+ void bumpGeneration();
+
+ static void dumpRawAbsoluteAxisInfo(String8& dump,
+ const RawAbsoluteAxisInfo& axis, const char* name);
+};
+
+
+class SwitchInputMapper : public InputMapper {
+public:
+ SwitchInputMapper(InputDevice* device);
+ virtual ~SwitchInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+
+private:
+ uint32_t mUpdatedSwitchValues;
+ uint32_t mUpdatedSwitchMask;
+
+ void processSwitch(int32_t switchCode, int32_t switchValue);
+ void sync(nsecs_t when);
+};
+
+
+class VibratorInputMapper : public InputMapper {
+public:
+ VibratorInputMapper(InputDevice* device);
+ virtual ~VibratorInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
+ virtual void timeoutExpired(nsecs_t when);
+ virtual void dump(String8& dump);
+
+private:
+ bool mVibrating;
+ nsecs_t mPattern[MAX_VIBRATE_PATTERN_SIZE];
+ size_t mPatternSize;
+ ssize_t mRepeat;
+ int32_t mToken;
+ ssize_t mIndex;
+ nsecs_t mNextStepTime;
+
+ void nextStep();
+ void stopVibrating();
+};
+
+
+class KeyboardInputMapper : public InputMapper {
+public:
+ KeyboardInputMapper(InputDevice* device, uint32_t source, int32_t keyboardType);
+ virtual ~KeyboardInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual int32_t getMetaState();
+
+private:
+ struct KeyDown {
+ int32_t keyCode;
+ int32_t scanCode;
+ };
+
+ uint32_t mSource;
+ int32_t mKeyboardType;
+
+ int32_t mOrientation; // orientation for dpad keys
+
+ Vector<KeyDown> mKeyDowns; // keys that are down
+ int32_t mMetaState;
+ nsecs_t mDownTime; // time of most recent key down
+
+ int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none
+
+ struct LedState {
+ bool avail; // led is available
+ bool on; // we think the led is currently on
+ };
+ LedState mCapsLockLedState;
+ LedState mNumLockLedState;
+ LedState mScrollLockLedState;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ bool hasAssociatedDisplay;
+ bool orientationAware;
+ } mParameters;
+
+ void configureParameters();
+ void dumpParameters(String8& dump);
+
+ bool isKeyboardOrGamepadKey(int32_t scanCode);
+
+ void processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode,
+ uint32_t policyFlags);
+
+ ssize_t findKeyDown(int32_t scanCode);
+
+ void resetLedState();
+ void initializeLedState(LedState& ledState, int32_t led);
+ void updateLedState(bool reset);
+ void updateLedStateForModifier(LedState& ledState, int32_t led,
+ int32_t modifier, bool reset);
+};
+
+
+class CursorInputMapper : public InputMapper {
+public:
+ CursorInputMapper(InputDevice* device);
+ virtual ~CursorInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+
+ virtual void fadePointer();
+
+private:
+ // Amount that trackball needs to move in order to generate a key event.
+ static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ enum Mode {
+ MODE_POINTER,
+ MODE_NAVIGATION,
+ };
+
+ Mode mode;
+ bool hasAssociatedDisplay;
+ bool orientationAware;
+ } mParameters;
+
+ CursorButtonAccumulator mCursorButtonAccumulator;
+ CursorMotionAccumulator mCursorMotionAccumulator;
+ CursorScrollAccumulator mCursorScrollAccumulator;
+
+ int32_t mSource;
+ float mXScale;
+ float mYScale;
+ float mXPrecision;
+ float mYPrecision;
+
+ float mVWheelScale;
+ float mHWheelScale;
+
+ // Velocity controls for mouse pointer and wheel movements.
+ // The controls for X and Y wheel movements are separate to keep them decoupled.
+ VelocityControl mPointerVelocityControl;
+ VelocityControl mWheelXVelocityControl;
+ VelocityControl mWheelYVelocityControl;
+
+ int32_t mOrientation;
+
+ sp<PointerControllerInterface> mPointerController;
+
+ int32_t mButtonState;
+ nsecs_t mDownTime;
+
+ void configureParameters();
+ void dumpParameters(String8& dump);
+
+ void sync(nsecs_t when);
+};
+
+
+class TouchInputMapper : public InputMapper {
+public:
+ TouchInputMapper(InputDevice* device);
+ virtual ~TouchInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual void fadePointer();
+ virtual void timeoutExpired(nsecs_t when);
+
+protected:
+ CursorButtonAccumulator mCursorButtonAccumulator;
+ CursorScrollAccumulator mCursorScrollAccumulator;
+ TouchButtonAccumulator mTouchButtonAccumulator;
+
+ struct VirtualKey {
+ int32_t keyCode;
+ int32_t scanCode;
+ uint32_t flags;
+
+ // computed hit box, specified in touch screen coords based on known display size
+ int32_t hitLeft;
+ int32_t hitTop;
+ int32_t hitRight;
+ int32_t hitBottom;
+
+ inline bool isHit(int32_t x, int32_t y) const {
+ return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom;
+ }
+ };
+
+ // Input sources and device mode.
+ uint32_t mSource;
+
+ enum DeviceMode {
+ DEVICE_MODE_DISABLED, // input is disabled
+ DEVICE_MODE_DIRECT, // direct mapping (touchscreen)
+ DEVICE_MODE_UNSCALED, // unscaled mapping (touchpad)
+ DEVICE_MODE_NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
+ DEVICE_MODE_POINTER, // pointer mapping (pointer)
+ };
+ DeviceMode mDeviceMode;
+
+ // The reader's configuration.
+ InputReaderConfiguration mConfig;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ enum DeviceType {
+ DEVICE_TYPE_TOUCH_SCREEN,
+ DEVICE_TYPE_TOUCH_PAD,
+ DEVICE_TYPE_TOUCH_NAVIGATION,
+ DEVICE_TYPE_POINTER,
+ };
+
+ DeviceType deviceType;
+ bool hasAssociatedDisplay;
+ bool associatedDisplayIsExternal;
+ bool orientationAware;
+
+ enum GestureMode {
+ GESTURE_MODE_POINTER,
+ GESTURE_MODE_SPOTS,
+ };
+ GestureMode gestureMode;
+ } mParameters;
+
+ // Immutable calibration parameters in parsed form.
+ struct Calibration {
+ // Size
+ enum SizeCalibration {
+ SIZE_CALIBRATION_DEFAULT,
+ SIZE_CALIBRATION_NONE,
+ SIZE_CALIBRATION_GEOMETRIC,
+ SIZE_CALIBRATION_DIAMETER,
+ SIZE_CALIBRATION_BOX,
+ SIZE_CALIBRATION_AREA,
+ };
+
+ SizeCalibration sizeCalibration;
+
+ bool haveSizeScale;
+ float sizeScale;
+ bool haveSizeBias;
+ float sizeBias;
+ bool haveSizeIsSummed;
+ bool sizeIsSummed;
+
+ // Pressure
+ enum PressureCalibration {
+ PRESSURE_CALIBRATION_DEFAULT,
+ PRESSURE_CALIBRATION_NONE,
+ PRESSURE_CALIBRATION_PHYSICAL,
+ PRESSURE_CALIBRATION_AMPLITUDE,
+ };
+
+ PressureCalibration pressureCalibration;
+ bool havePressureScale;
+ float pressureScale;
+
+ // Orientation
+ enum OrientationCalibration {
+ ORIENTATION_CALIBRATION_DEFAULT,
+ ORIENTATION_CALIBRATION_NONE,
+ ORIENTATION_CALIBRATION_INTERPOLATED,
+ ORIENTATION_CALIBRATION_VECTOR,
+ };
+
+ OrientationCalibration orientationCalibration;
+
+ // Distance
+ enum DistanceCalibration {
+ DISTANCE_CALIBRATION_DEFAULT,
+ DISTANCE_CALIBRATION_NONE,
+ DISTANCE_CALIBRATION_SCALED,
+ };
+
+ DistanceCalibration distanceCalibration;
+ bool haveDistanceScale;
+ float distanceScale;
+
+ enum CoverageCalibration {
+ COVERAGE_CALIBRATION_DEFAULT,
+ COVERAGE_CALIBRATION_NONE,
+ COVERAGE_CALIBRATION_BOX,
+ };
+
+ CoverageCalibration coverageCalibration;
+
+ inline void applySizeScaleAndBias(float* outSize) const {
+ if (haveSizeScale) {
+ *outSize *= sizeScale;
+ }
+ if (haveSizeBias) {
+ *outSize += sizeBias;
+ }
+ }
+ } mCalibration;
+
+ // Raw pointer axis information from the driver.
+ RawPointerAxes mRawPointerAxes;
+
+ // Raw pointer sample data.
+ RawPointerData mCurrentRawPointerData;
+ RawPointerData mLastRawPointerData;
+
+ // Cooked pointer sample data.
+ CookedPointerData mCurrentCookedPointerData;
+ CookedPointerData mLastCookedPointerData;
+
+ // Button state.
+ int32_t mCurrentButtonState;
+ int32_t mLastButtonState;
+
+ // Scroll state.
+ int32_t mCurrentRawVScroll;
+ int32_t mCurrentRawHScroll;
+
+ // Id bits used to differentiate fingers, stylus and mouse tools.
+ BitSet32 mCurrentFingerIdBits; // finger or unknown
+ BitSet32 mLastFingerIdBits;
+ BitSet32 mCurrentStylusIdBits; // stylus or eraser
+ BitSet32 mLastStylusIdBits;
+ BitSet32 mCurrentMouseIdBits; // mouse or lens
+ BitSet32 mLastMouseIdBits;
+
+ // True if we sent a HOVER_ENTER event.
+ bool mSentHoverEnter;
+
+ // The time the primary pointer last went down.
+ nsecs_t mDownTime;
+
+ // The pointer controller, or null if the device is not a pointer.
+ sp<PointerControllerInterface> mPointerController;
+
+ Vector<VirtualKey> mVirtualKeys;
+
+ virtual void configureParameters();
+ virtual void dumpParameters(String8& dump);
+ virtual void configureRawPointerAxes();
+ virtual void dumpRawPointerAxes(String8& dump);
+ virtual void configureSurface(nsecs_t when, bool* outResetNeeded);
+ virtual void dumpSurface(String8& dump);
+ virtual void configureVirtualKeys();
+ virtual void dumpVirtualKeys(String8& dump);
+ virtual void parseCalibration();
+ virtual void resolveCalibration();
+ virtual void dumpCalibration(String8& dump);
+ virtual bool hasStylus() const = 0;
+
+ virtual void syncTouch(nsecs_t when, bool* outHavePointerIds) = 0;
+
+private:
+ // The current viewport.
+ // The components of the viewport are specified in the display's rotated orientation.
+ DisplayViewport mViewport;
+
+ // The surface orientation, width and height set by configureSurface().
+ // The width and height are derived from the viewport but are specified
+ // in the natural orientation.
+ // The surface origin specifies how the surface coordinates should be translated
+ // to align with the logical display coordinate space.
+ // The orientation may be different from the viewport orientation as it specifies
+ // the rotation of the surface coordinates required to produce the viewport's
+ // requested orientation, so it will depend on whether the device is orientation aware.
+ int32_t mSurfaceWidth;
+ int32_t mSurfaceHeight;
+ int32_t mSurfaceLeft;
+ int32_t mSurfaceTop;
+ int32_t mSurfaceOrientation;
+
+ // Translation and scaling factors, orientation-independent.
+ float mXTranslate;
+ float mXScale;
+ float mXPrecision;
+
+ float mYTranslate;
+ float mYScale;
+ float mYPrecision;
+
+ float mGeometricScale;
+
+ float mPressureScale;
+
+ float mSizeScale;
+
+ float mOrientationScale;
+
+ float mDistanceScale;
+
+ bool mHaveTilt;
+ float mTiltXCenter;
+ float mTiltXScale;
+ float mTiltYCenter;
+ float mTiltYScale;
+
+ // Oriented motion ranges for input device info.
+ struct OrientedRanges {
+ InputDeviceInfo::MotionRange x;
+ InputDeviceInfo::MotionRange y;
+ InputDeviceInfo::MotionRange pressure;
+
+ bool haveSize;
+ InputDeviceInfo::MotionRange size;
+
+ bool haveTouchSize;
+ InputDeviceInfo::MotionRange touchMajor;
+ InputDeviceInfo::MotionRange touchMinor;
+
+ bool haveToolSize;
+ InputDeviceInfo::MotionRange toolMajor;
+ InputDeviceInfo::MotionRange toolMinor;
+
+ bool haveOrientation;
+ InputDeviceInfo::MotionRange orientation;
+
+ bool haveDistance;
+ InputDeviceInfo::MotionRange distance;
+
+ bool haveTilt;
+ InputDeviceInfo::MotionRange tilt;
+
+ OrientedRanges() {
+ clear();
+ }
+
+ void clear() {
+ haveSize = false;
+ haveTouchSize = false;
+ haveToolSize = false;
+ haveOrientation = false;
+ haveDistance = false;
+ haveTilt = false;
+ }
+ } mOrientedRanges;
+
+ // Oriented dimensions and precision.
+ float mOrientedXPrecision;
+ float mOrientedYPrecision;
+
+ struct CurrentVirtualKeyState {
+ bool down;
+ bool ignored;
+ nsecs_t downTime;
+ int32_t keyCode;
+ int32_t scanCode;
+ } mCurrentVirtualKey;
+
+ // Scale factor for gesture or mouse based pointer movements.
+ float mPointerXMovementScale;
+ float mPointerYMovementScale;
+
+ // Scale factor for gesture based zooming and other freeform motions.
+ float mPointerXZoomScale;
+ float mPointerYZoomScale;
+
+ // The maximum swipe width.
+ float mPointerGestureMaxSwipeWidth;
+
+ struct PointerDistanceHeapElement {
+ uint32_t currentPointerIndex : 8;
+ uint32_t lastPointerIndex : 8;
+ uint64_t distance : 48; // squared distance
+ };
+
+ enum PointerUsage {
+ POINTER_USAGE_NONE,
+ POINTER_USAGE_GESTURES,
+ POINTER_USAGE_STYLUS,
+ POINTER_USAGE_MOUSE,
+ };
+ PointerUsage mPointerUsage;
+
+ struct PointerGesture {
+ enum Mode {
+ // No fingers, button is not pressed.
+ // Nothing happening.
+ NEUTRAL,
+
+ // No fingers, button is not pressed.
+ // Tap detected.
+ // Emits DOWN and UP events at the pointer location.
+ TAP,
+
+ // Exactly one finger dragging following a tap.
+ // Pointer follows the active finger.
+ // Emits DOWN, MOVE and UP events at the pointer location.
+ //
+ // Detect double-taps when the finger goes up while in TAP_DRAG mode.
+ TAP_DRAG,
+
+ // Button is pressed.
+ // Pointer follows the active finger if there is one. Other fingers are ignored.
+ // Emits DOWN, MOVE and UP events at the pointer location.
+ BUTTON_CLICK_OR_DRAG,
+
+ // Exactly one finger, button is not pressed.
+ // Pointer follows the active finger.
+ // Emits HOVER_MOVE events at the pointer location.
+ //
+ // Detect taps when the finger goes up while in HOVER mode.
+ HOVER,
+
+ // Exactly two fingers but neither have moved enough to clearly indicate
+ // whether a swipe or freeform gesture was intended. We consider the
+ // pointer to be pressed so this enables clicking or long-pressing on buttons.
+ // Pointer does not move.
+ // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate.
+ PRESS,
+
+ // Exactly two fingers moving in the same direction, button is not pressed.
+ // Pointer does not move.
+ // Emits DOWN, MOVE and UP events with a single pointer coordinate that
+ // follows the midpoint between both fingers.
+ SWIPE,
+
+ // Two or more fingers moving in arbitrary directions, button is not pressed.
+ // Pointer does not move.
+ // Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow
+ // each finger individually relative to the initial centroid of the finger.
+ FREEFORM,
+
+ // Waiting for quiet time to end before starting the next gesture.
+ QUIET,
+ };
+
+ // Time the first finger went down.
+ nsecs_t firstTouchTime;
+
+ // The active pointer id from the raw touch data.
+ int32_t activeTouchId; // -1 if none
+
+ // The active pointer id from the gesture last delivered to the application.
+ int32_t activeGestureId; // -1 if none
+
+ // Pointer coords and ids for the current and previous pointer gesture.
+ Mode currentGestureMode;
+ BitSet32 currentGestureIdBits;
+ uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
+ PointerProperties currentGestureProperties[MAX_POINTERS];
+ PointerCoords currentGestureCoords[MAX_POINTERS];
+
+ Mode lastGestureMode;
+ BitSet32 lastGestureIdBits;
+ uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
+ PointerProperties lastGestureProperties[MAX_POINTERS];
+ PointerCoords lastGestureCoords[MAX_POINTERS];
+
+ // Time the pointer gesture last went down.
+ nsecs_t downTime;
+
+ // Time when the pointer went down for a TAP.
+ nsecs_t tapDownTime;
+
+ // Time when the pointer went up for a TAP.
+ nsecs_t tapUpTime;
+
+ // Location of initial tap.
+ float tapX, tapY;
+
+ // Time we started waiting for quiescence.
+ nsecs_t quietTime;
+
+ // Reference points for multitouch gestures.
+ float referenceTouchX; // reference touch X/Y coordinates in surface units
+ float referenceTouchY;
+ float referenceGestureX; // reference gesture X/Y coordinates in pixels
+ float referenceGestureY;
+
+ // Distance that each pointer has traveled which has not yet been
+ // subsumed into the reference gesture position.
+ BitSet32 referenceIdBits;
+ struct Delta {
+ float dx, dy;
+ };
+ Delta referenceDeltas[MAX_POINTER_ID + 1];
+
+ // Describes how touch ids are mapped to gesture ids for freeform gestures.
+ uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
+
+ // A velocity tracker for determining whether to switch active pointers during drags.
+ VelocityTracker velocityTracker;
+
+ void reset() {
+ firstTouchTime = LLONG_MIN;
+ activeTouchId = -1;
+ activeGestureId = -1;
+ currentGestureMode = NEUTRAL;
+ currentGestureIdBits.clear();
+ lastGestureMode = NEUTRAL;
+ lastGestureIdBits.clear();
+ downTime = 0;
+ velocityTracker.clear();
+ resetTap();
+ resetQuietTime();
+ }
+
+ void resetTap() {
+ tapDownTime = LLONG_MIN;
+ tapUpTime = LLONG_MIN;
+ }
+
+ void resetQuietTime() {
+ quietTime = LLONG_MIN;
+ }
+ } mPointerGesture;
+
+ struct PointerSimple {
+ PointerCoords currentCoords;
+ PointerProperties currentProperties;
+ PointerCoords lastCoords;
+ PointerProperties lastProperties;
+
+ // True if the pointer is down.
+ bool down;
+
+ // True if the pointer is hovering.
+ bool hovering;
+
+ // Time the pointer last went down.
+ nsecs_t downTime;
+
+ void reset() {
+ currentCoords.clear();
+ currentProperties.clear();
+ lastCoords.clear();
+ lastProperties.clear();
+ down = false;
+ hovering = false;
+ downTime = 0;
+ }
+ } mPointerSimple;
+
+ // The pointer and scroll velocity controls.
+ VelocityControl mPointerVelocityControl;
+ VelocityControl mWheelXVelocityControl;
+ VelocityControl mWheelYVelocityControl;
+
+ void sync(nsecs_t when);
+
+ bool consumeRawTouches(nsecs_t when, uint32_t policyFlags);
+ void dispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
+ int32_t keyEventAction, int32_t keyEventFlags);
+
+ void dispatchTouches(nsecs_t when, uint32_t policyFlags);
+ void dispatchHoverExit(nsecs_t when, uint32_t policyFlags);
+ void dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags);
+ void cookPointerData();
+
+ void dispatchPointerUsage(nsecs_t when, uint32_t policyFlags, PointerUsage pointerUsage);
+ void abortPointerUsage(nsecs_t when, uint32_t policyFlags);
+
+ void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout);
+ void abortPointerGestures(nsecs_t when, uint32_t policyFlags);
+ bool preparePointerGestures(nsecs_t when,
+ bool* outCancelPreviousGesture, bool* outFinishPreviousGesture,
+ bool isTimeout);
+
+ void dispatchPointerStylus(nsecs_t when, uint32_t policyFlags);
+ void abortPointerStylus(nsecs_t when, uint32_t policyFlags);
+
+ void dispatchPointerMouse(nsecs_t when, uint32_t policyFlags);
+ void abortPointerMouse(nsecs_t when, uint32_t policyFlags);
+
+ void dispatchPointerSimple(nsecs_t when, uint32_t policyFlags,
+ bool down, bool hovering);
+ void abortPointerSimple(nsecs_t when, uint32_t policyFlags);
+
+ // Dispatches a motion event.
+ // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
+ // method will take care of setting the index and transmuting the action to DOWN or UP
+ // it is the first / last pointer to go down / up.
+ void dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags,
+ const PointerProperties* properties, const PointerCoords* coords,
+ const uint32_t* idToIndex, BitSet32 idBits,
+ int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime);
+
+ // Updates pointer coords and properties for pointers with specified ids that have moved.
+ // Returns true if any of them changed.
+ bool updateMovedPointers(const PointerProperties* inProperties,
+ const PointerCoords* inCoords, const uint32_t* inIdToIndex,
+ PointerProperties* outProperties, PointerCoords* outCoords,
+ const uint32_t* outIdToIndex, BitSet32 idBits) const;
+
+ bool isPointInsideSurface(int32_t x, int32_t y);
+ const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y);
+
+ void assignPointerIds();
+};
+
+
+class SingleTouchInputMapper : public TouchInputMapper {
+public:
+ SingleTouchInputMapper(InputDevice* device);
+ virtual ~SingleTouchInputMapper();
+
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+protected:
+ virtual void syncTouch(nsecs_t when, bool* outHavePointerIds);
+ virtual void configureRawPointerAxes();
+ virtual bool hasStylus() const;
+
+private:
+ SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
+};
+
+
+class MultiTouchInputMapper : public TouchInputMapper {
+public:
+ MultiTouchInputMapper(InputDevice* device);
+ virtual ~MultiTouchInputMapper();
+
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+protected:
+ virtual void syncTouch(nsecs_t when, bool* outHavePointerIds);
+ virtual void configureRawPointerAxes();
+ virtual bool hasStylus() const;
+
+private:
+ MultiTouchMotionAccumulator mMultiTouchMotionAccumulator;
+
+ // Specifies the pointer id bits that are in use, and their associated tracking id.
+ BitSet32 mPointerIdBits;
+ int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
+};
+
+
+class JoystickInputMapper : public InputMapper {
+public:
+ JoystickInputMapper(InputDevice* device);
+ virtual ~JoystickInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+private:
+ struct Axis {
+ RawAbsoluteAxisInfo rawAxisInfo;
+ AxisInfo axisInfo;
+
+ bool explicitlyMapped; // true if the axis was explicitly assigned an axis id
+
+ float scale; // scale factor from raw to normalized values
+ float offset; // offset to add after scaling for normalization
+ float highScale; // scale factor from raw to normalized values of high split
+ float highOffset; // offset to add after scaling for normalization of high split
+
+ float min; // normalized inclusive minimum
+ float max; // normalized inclusive maximum
+ float flat; // normalized flat region size
+ float fuzz; // normalized error tolerance
+ float resolution; // normalized resolution in units/mm
+
+ float filter; // filter out small variations of this size
+ float currentValue; // current value
+ float newValue; // most recent value
+ float highCurrentValue; // current value of high split
+ float highNewValue; // most recent value of high split
+
+ void initialize(const RawAbsoluteAxisInfo& rawAxisInfo, const AxisInfo& axisInfo,
+ bool explicitlyMapped, float scale, float offset,
+ float highScale, float highOffset,
+ float min, float max, float flat, float fuzz, float resolution) {
+ this->rawAxisInfo = rawAxisInfo;
+ this->axisInfo = axisInfo;
+ this->explicitlyMapped = explicitlyMapped;
+ this->scale = scale;
+ this->offset = offset;
+ this->highScale = highScale;
+ this->highOffset = highOffset;
+ this->min = min;
+ this->max = max;
+ this->flat = flat;
+ this->fuzz = fuzz;
+ this->resolution = resolution;
+ this->filter = 0;
+ resetValue();
+ }
+
+ void resetValue() {
+ this->currentValue = 0;
+ this->newValue = 0;
+ this->highCurrentValue = 0;
+ this->highNewValue = 0;
+ }
+ };
+
+ // Axes indexed by raw ABS_* axis index.
+ KeyedVector<int32_t, Axis> mAxes;
+
+ void sync(nsecs_t when, bool force);
+
+ bool haveAxis(int32_t axisId);
+ void pruneAxes(bool ignoreExplicitlyMappedAxes);
+ bool filterAxes(bool force);
+
+ static bool hasValueChangedSignificantly(float filter,
+ float newValue, float currentValue, float min, float max);
+ static bool hasMovedNearerToValueWithinFilteredRange(float filter,
+ float newValue, float currentValue, float thresholdValue);
+
+ static bool isCenteredAxis(int32_t axis);
+ static int32_t getCompatAxis(int32_t axis);
+
+ static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info);
+ static void setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis,
+ float value);
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_READER_H
diff --git a/widget/gonk/libui/InputTransport.cpp b/widget/gonk/libui/InputTransport.cpp
new file mode 100644
index 0000000000..3f0fcb0471
--- /dev/null
+++ b/widget/gonk/libui/InputTransport.cpp
@@ -0,0 +1,957 @@
+//
+// Copyright 2010 The Android Open Source Project
+//
+// Provides a shared memory transport for input events.
+//
+#define LOG_TAG "InputTransport"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about channel messages (send message, receive message)
+#define DEBUG_CHANNEL_MESSAGES 0
+
+// Log debug messages whenever InputChannel objects are created/destroyed
+#define DEBUG_CHANNEL_LIFECYCLE 0
+
+// Log debug messages about transport actions
+#define DEBUG_TRANSPORT_ACTIONS 0
+
+// Log debug messages about touch event resampling
+#define DEBUG_RESAMPLING 0
+
+
+#include "cutils_log.h"
+#include <cutils/properties.h>
+#include <errno.h>
+#include <fcntl.h>
+#include "InputTransport.h"
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <math.h>
+
+
+namespace android {
+
+// Socket buffer size. The default is typically about 128KB, which is much larger than
+// we really need. So we make it smaller. It just needs to be big enough to hold
+// a few dozen large multi-finger motion events in the case where an application gets
+// behind processing touches.
+static const size_t SOCKET_BUFFER_SIZE = 32 * 1024;
+
+// Nanoseconds per milliseconds.
+static const nsecs_t NANOS_PER_MS = 1000000;
+
+// Latency added during resampling. A few milliseconds doesn't hurt much but
+// reduces the impact of mispredicted touch positions.
+static const nsecs_t RESAMPLE_LATENCY = 5 * NANOS_PER_MS;
+
+// Minimum time difference between consecutive samples before attempting to resample.
+static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
+
+// Maximum time to predict forward from the last known state, to avoid predicting too
+// far into the future. This time is further bounded by 50% of the last time delta.
+static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
+
+template<typename T>
+inline static T min(const T& a, const T& b) {
+ return a < b ? a : b;
+}
+
+inline static float lerp(float a, float b, float alpha) {
+ return a + alpha * (b - a);
+}
+
+// --- InputMessage ---
+
+bool InputMessage::isValid(size_t actualSize) const {
+ if (size() == actualSize) {
+ switch (header.type) {
+ case TYPE_KEY:
+ return true;
+ case TYPE_MOTION:
+ return body.motion.pointerCount > 0
+ && body.motion.pointerCount <= MAX_POINTERS;
+ case TYPE_FINISHED:
+ return true;
+ }
+ }
+ return false;
+}
+
+size_t InputMessage::size() const {
+ switch (header.type) {
+ case TYPE_KEY:
+ return sizeof(Header) + body.key.size();
+ case TYPE_MOTION:
+ return sizeof(Header) + body.motion.size();
+ case TYPE_FINISHED:
+ return sizeof(Header) + body.finished.size();
+ }
+ return sizeof(Header);
+}
+
+
+// --- InputChannel ---
+
+InputChannel::InputChannel(const String8& name, int fd) :
+ mName(name), mFd(fd) {
+#if DEBUG_CHANNEL_LIFECYCLE
+ ALOGD("Input channel constructed: name='%s', fd=%d",
+ mName.string(), fd);
+#endif
+
+ int result = fcntl(mFd, F_SETFL, O_NONBLOCK);
+ LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make socket "
+ "non-blocking. errno=%d", mName.string(), errno);
+}
+
+InputChannel::~InputChannel() {
+#if DEBUG_CHANNEL_LIFECYCLE
+ ALOGD("Input channel destroyed: name='%s', fd=%d",
+ mName.string(), mFd);
+#endif
+
+ ::close(mFd);
+}
+
+status_t InputChannel::openInputChannelPair(const String8& name,
+ sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
+ int sockets[2];
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
+ status_t result = -errno;
+ ALOGE("channel '%s' ~ Could not create socket pair. errno=%d",
+ name.string(), errno);
+ outServerChannel.clear();
+ outClientChannel.clear();
+ return result;
+ }
+
+ int bufferSize = SOCKET_BUFFER_SIZE;
+ setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
+ setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
+ setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
+ setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
+
+ String8 serverChannelName = name;
+ serverChannelName.append(" (server)");
+ outServerChannel = new InputChannel(serverChannelName, sockets[0]);
+
+ String8 clientChannelName = name;
+ clientChannelName.append(" (client)");
+ outClientChannel = new InputChannel(clientChannelName, sockets[1]);
+ return OK;
+}
+
+status_t InputChannel::sendMessage(const InputMessage* msg) {
+ size_t msgLength = msg->size();
+ ssize_t nWrite;
+ do {
+ nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
+ } while (nWrite == -1 && errno == EINTR);
+
+ if (nWrite < 0) {
+ int error = errno;
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ error sending message of type %d, errno=%d", mName.string(),
+ msg->header.type, error);
+#endif
+ if (error == EAGAIN || error == EWOULDBLOCK) {
+ return WOULD_BLOCK;
+ }
+ if (error == EPIPE || error == ENOTCONN) {
+ return DEAD_OBJECT;
+ }
+ return -error;
+ }
+
+ if (size_t(nWrite) != msgLength) {
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ error sending message type %d, send was incomplete",
+ mName.string(), msg->header.type);
+#endif
+ return DEAD_OBJECT;
+ }
+
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ sent message of type %d", mName.string(), msg->header.type);
+#endif
+ return OK;
+}
+
+status_t InputChannel::receiveMessage(InputMessage* msg) {
+ ssize_t nRead;
+ do {
+ nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
+ } while (nRead == -1 && errno == EINTR);
+
+ if (nRead < 0) {
+ int error = errno;
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ receive message failed, errno=%d", mName.string(), errno);
+#endif
+ if (error == EAGAIN || error == EWOULDBLOCK) {
+ return WOULD_BLOCK;
+ }
+ if (error == EPIPE || error == ENOTCONN) {
+ return DEAD_OBJECT;
+ }
+ return -error;
+ }
+
+ if (nRead == 0) { // check for EOF
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ receive message failed because peer was closed", mName.string());
+#endif
+ return DEAD_OBJECT;
+ }
+
+ if (!msg->isValid(nRead)) {
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ received invalid message", mName.string());
+#endif
+ return BAD_VALUE;
+ }
+
+#if DEBUG_CHANNEL_MESSAGES
+ ALOGD("channel '%s' ~ received message of type %d", mName.string(), msg->header.type);
+#endif
+ return OK;
+}
+
+sp<InputChannel> InputChannel::dup() const {
+ int fd = ::dup(getFd());
+ return fd >= 0 ? new InputChannel(getName(), fd) : NULL;
+}
+
+
+// --- InputPublisher ---
+
+InputPublisher::InputPublisher(const sp<InputChannel>& channel) :
+ mChannel(channel) {
+}
+
+InputPublisher::~InputPublisher() {
+}
+
+status_t InputPublisher::publishKeyEvent(
+ uint32_t seq,
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t keyCode,
+ int32_t scanCode,
+ int32_t metaState,
+ int32_t repeatCount,
+ nsecs_t downTime,
+ nsecs_t eventTime) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, "
+ "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
+ "downTime=%lld, eventTime=%lld",
+ mChannel->getName().string(), seq,
+ deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount,
+ downTime, eventTime);
+#endif
+
+ if (!seq) {
+ ALOGE("Attempted to publish a key event with sequence number 0.");
+ return BAD_VALUE;
+ }
+
+ InputMessage msg;
+ msg.header.type = InputMessage::TYPE_KEY;
+ msg.body.key.seq = seq;
+ msg.body.key.deviceId = deviceId;
+ msg.body.key.source = source;
+ msg.body.key.action = action;
+ msg.body.key.flags = flags;
+ msg.body.key.keyCode = keyCode;
+ msg.body.key.scanCode = scanCode;
+ msg.body.key.metaState = metaState;
+ msg.body.key.repeatCount = repeatCount;
+ msg.body.key.downTime = downTime;
+ msg.body.key.eventTime = eventTime;
+ return mChannel->sendMessage(&msg);
+}
+
+status_t InputPublisher::publishMotionEvent(
+ uint32_t seq,
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t edgeFlags,
+ int32_t metaState,
+ int32_t buttonState,
+ float xOffset,
+ float yOffset,
+ float xPrecision,
+ float yPrecision,
+ nsecs_t downTime,
+ nsecs_t eventTime,
+ size_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' publisher ~ publishMotionEvent: seq=%u, deviceId=%d, source=0x%x, "
+ "action=0x%x, flags=0x%x, edgeFlags=0x%x, metaState=0x%x, buttonState=0x%x, "
+ "xOffset=%f, yOffset=%f, "
+ "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, "
+ "pointerCount=%d",
+ mChannel->getName().string(), seq,
+ deviceId, source, action, flags, edgeFlags, metaState, buttonState,
+ xOffset, yOffset, xPrecision, yPrecision, downTime, eventTime, pointerCount);
+#endif
+
+ if (!seq) {
+ ALOGE("Attempted to publish a motion event with sequence number 0.");
+ return BAD_VALUE;
+ }
+
+ if (pointerCount > MAX_POINTERS || pointerCount < 1) {
+ ALOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.",
+ mChannel->getName().string(), pointerCount);
+ return BAD_VALUE;
+ }
+
+ InputMessage msg;
+ msg.header.type = InputMessage::TYPE_MOTION;
+ msg.body.motion.seq = seq;
+ msg.body.motion.deviceId = deviceId;
+ msg.body.motion.source = source;
+ msg.body.motion.action = action;
+ msg.body.motion.flags = flags;
+ msg.body.motion.edgeFlags = edgeFlags;
+ msg.body.motion.metaState = metaState;
+ msg.body.motion.buttonState = buttonState;
+ msg.body.motion.xOffset = xOffset;
+ msg.body.motion.yOffset = yOffset;
+ msg.body.motion.xPrecision = xPrecision;
+ msg.body.motion.yPrecision = yPrecision;
+ msg.body.motion.downTime = downTime;
+ msg.body.motion.eventTime = eventTime;
+ msg.body.motion.pointerCount = pointerCount;
+ for (size_t i = 0; i < pointerCount; i++) {
+ msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
+ msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
+ }
+ return mChannel->sendMessage(&msg);
+}
+
+status_t InputPublisher::receiveFinishedSignal(uint32_t* outSeq, bool* outHandled) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' publisher ~ receiveFinishedSignal",
+ mChannel->getName().string());
+#endif
+
+ InputMessage msg;
+ status_t result = mChannel->receiveMessage(&msg);
+ if (result) {
+ *outSeq = 0;
+ *outHandled = false;
+ return result;
+ }
+ if (msg.header.type != InputMessage::TYPE_FINISHED) {
+ ALOGE("channel '%s' publisher ~ Received unexpected message of type %d from consumer",
+ mChannel->getName().string(), msg.header.type);
+ return UNKNOWN_ERROR;
+ }
+ *outSeq = msg.body.finished.seq;
+ *outHandled = msg.body.finished.handled;
+ return OK;
+}
+
+// --- InputConsumer ---
+
+InputConsumer::InputConsumer(const sp<InputChannel>& channel) :
+ mResampleTouch(isTouchResamplingEnabled()),
+ mChannel(channel), mMsgDeferred(false) {
+}
+
+InputConsumer::~InputConsumer() {
+}
+
+bool InputConsumer::isTouchResamplingEnabled() {
+ char value[PROPERTY_VALUE_MAX];
+ int length = property_get("debug.inputconsumer.resample", value, NULL);
+ if (length > 0) {
+ if (!strcmp("0", value)) {
+ return false;
+ }
+ if (strcmp("1", value)) {
+ ALOGD("Unrecognized property value for 'debug.inputconsumer.resample'. "
+ "Use '1' or '0'.");
+ }
+ }
+ return true;
+}
+
+status_t InputConsumer::consume(InputEventFactoryInterface* factory,
+ bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%lld",
+ mChannel->getName().string(), consumeBatches ? "true" : "false", frameTime);
+#endif
+
+ *outSeq = 0;
+ *outEvent = NULL;
+
+ // Fetch the next input message.
+ // Loop until an event can be returned or no additional events are received.
+ while (!*outEvent) {
+ if (mMsgDeferred) {
+ // mMsg contains a valid input message from the previous call to consume
+ // that has not yet been processed.
+ mMsgDeferred = false;
+ } else {
+ // Receive a fresh message.
+ status_t result = mChannel->receiveMessage(&mMsg);
+ if (result) {
+ // Consume the next batched event unless batches are being held for later.
+ if (consumeBatches || result != WOULD_BLOCK) {
+ result = consumeBatch(factory, frameTime, outSeq, outEvent);
+ if (*outEvent) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
+ mChannel->getName().string(), *outSeq);
+#endif
+ break;
+ }
+ }
+ return result;
+ }
+ }
+
+ switch (mMsg.header.type) {
+ case InputMessage::TYPE_KEY: {
+ KeyEvent* keyEvent = factory->createKeyEvent();
+ if (!keyEvent) return NO_MEMORY;
+
+ initializeKeyEvent(keyEvent, &mMsg);
+ *outSeq = mMsg.body.key.seq;
+ *outEvent = keyEvent;
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
+ mChannel->getName().string(), *outSeq);
+#endif
+ break;
+ }
+
+ case AINPUT_EVENT_TYPE_MOTION: {
+ ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
+ if (batchIndex >= 0) {
+ Batch& batch = mBatches.editItemAt(batchIndex);
+ if (canAddSample(batch, &mMsg)) {
+ batch.samples.push(mMsg);
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ appended to batch event",
+ mChannel->getName().string());
+#endif
+ break;
+ } else {
+ // We cannot append to the batch in progress, so we need to consume
+ // the previous batch right now and defer the new message until later.
+ mMsgDeferred = true;
+ status_t result = consumeSamples(factory,
+ batch, batch.samples.size(), outSeq, outEvent);
+ mBatches.removeAt(batchIndex);
+ if (result) {
+ return result;
+ }
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consumed batch event and "
+ "deferred current event, seq=%u",
+ mChannel->getName().string(), *outSeq);
+#endif
+ break;
+ }
+ }
+
+ // Start a new batch if needed.
+ if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE
+ || mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+ mBatches.push();
+ Batch& batch = mBatches.editTop();
+ batch.samples.push(mMsg);
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ started batch event",
+ mChannel->getName().string());
+#endif
+ break;
+ }
+
+ MotionEvent* motionEvent = factory->createMotionEvent();
+ if (! motionEvent) return NO_MEMORY;
+
+ updateTouchState(&mMsg);
+ initializeMotionEvent(motionEvent, &mMsg);
+ *outSeq = mMsg.body.motion.seq;
+ *outEvent = motionEvent;
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u",
+ mChannel->getName().string(), *outSeq);
+#endif
+ break;
+ }
+
+ default:
+ ALOGE("channel '%s' consumer ~ Received unexpected message of type %d",
+ mChannel->getName().string(), mMsg.header.type);
+ return UNKNOWN_ERROR;
+ }
+ }
+ return OK;
+}
+
+status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory,
+ nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
+ status_t result;
+ for (size_t i = mBatches.size(); i-- > 0; ) {
+ Batch& batch = mBatches.editItemAt(i);
+ if (frameTime < 0) {
+ result = consumeSamples(factory, batch, batch.samples.size(),
+ outSeq, outEvent);
+ mBatches.removeAt(i);
+ return result;
+ }
+
+ nsecs_t sampleTime = frameTime - RESAMPLE_LATENCY;
+ ssize_t split = findSampleNoLaterThan(batch, sampleTime);
+ if (split < 0) {
+ continue;
+ }
+
+ result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
+ const InputMessage* next;
+ if (batch.samples.isEmpty()) {
+ mBatches.removeAt(i);
+ next = NULL;
+ } else {
+ next = &batch.samples.itemAt(0);
+ }
+ if (!result) {
+ resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
+ }
+ return result;
+ }
+
+ return WOULD_BLOCK;
+}
+
+status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory,
+ Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent) {
+ MotionEvent* motionEvent = factory->createMotionEvent();
+ if (! motionEvent) return NO_MEMORY;
+
+ uint32_t chain = 0;
+ for (size_t i = 0; i < count; i++) {
+ InputMessage& msg = batch.samples.editItemAt(i);
+ updateTouchState(&msg);
+ if (i) {
+ SeqChain seqChain;
+ seqChain.seq = msg.body.motion.seq;
+ seqChain.chain = chain;
+ mSeqChains.push(seqChain);
+ addSample(motionEvent, &msg);
+ } else {
+ initializeMotionEvent(motionEvent, &msg);
+ }
+ chain = msg.body.motion.seq;
+ }
+ batch.samples.removeItemsAt(0, count);
+
+ *outSeq = chain;
+ *outEvent = motionEvent;
+ return OK;
+}
+
+void InputConsumer::updateTouchState(InputMessage* msg) {
+ if (!mResampleTouch ||
+ !(msg->body.motion.source & AINPUT_SOURCE_CLASS_POINTER)) {
+ return;
+ }
+
+ int32_t deviceId = msg->body.motion.deviceId;
+ int32_t source = msg->body.motion.source;
+ nsecs_t eventTime = msg->body.motion.eventTime;
+
+ // Update the touch state history to incorporate the new input message.
+ // If the message is in the past relative to the most recently produced resampled
+ // touch, then use the resampled time and coordinates instead.
+ switch (msg->body.motion.action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_DOWN: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index < 0) {
+ mTouchStates.push();
+ index = mTouchStates.size() - 1;
+ }
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ touchState.initialize(deviceId, source);
+ touchState.addHistory(msg);
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_MOVE: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ touchState.addHistory(msg);
+ if (eventTime < touchState.lastResample.eventTime) {
+ rewriteMessage(touchState, msg);
+ } else {
+ touchState.lastResample.idBits.clear();
+ }
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId());
+ rewriteMessage(touchState, msg);
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_UP: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ rewriteMessage(touchState, msg);
+ touchState.lastResample.idBits.clearBit(msg->body.motion.getActionId());
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_SCROLL: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ const TouchState& touchState = mTouchStates.itemAt(index);
+ rewriteMessage(touchState, msg);
+ }
+ break;
+ }
+
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ ssize_t index = findTouchState(deviceId, source);
+ if (index >= 0) {
+ const TouchState& touchState = mTouchStates.itemAt(index);
+ rewriteMessage(touchState, msg);
+ mTouchStates.removeAt(index);
+ }
+ break;
+ }
+ }
+}
+
+void InputConsumer::rewriteMessage(const TouchState& state, InputMessage* msg) {
+ for (size_t i = 0; i < msg->body.motion.pointerCount; i++) {
+ uint32_t id = msg->body.motion.pointers[i].properties.id;
+ if (state.lastResample.idBits.hasBit(id)) {
+ PointerCoords& msgCoords = msg->body.motion.pointers[i].coords;
+ const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
+#if DEBUG_RESAMPLING
+ ALOGD("[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
+ resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ resampleCoords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ msgCoords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ msgCoords.getAxisValue(AMOTION_EVENT_AXIS_Y));
+#endif
+ msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
+ msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
+ }
+ }
+}
+
+void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
+ const InputMessage* next) {
+ if (!mResampleTouch
+ || !(event->getSource() & AINPUT_SOURCE_CLASS_POINTER)
+ || event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
+ return;
+ }
+
+ ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
+ if (index < 0) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, no touch state for device.");
+#endif
+ return;
+ }
+
+ TouchState& touchState = mTouchStates.editItemAt(index);
+ if (touchState.historySize < 1) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, no history for device.");
+#endif
+ return;
+ }
+
+ // Ensure that the current sample has all of the pointers that need to be reported.
+ const History* current = touchState.getHistory(0);
+ size_t pointerCount = event->getPointerCount();
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t id = event->getPointerId(i);
+ if (!current->idBits.hasBit(id)) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, missing id %d", id);
+#endif
+ return;
+ }
+ }
+
+ // Find the data to use for resampling.
+ const History* other;
+ History future;
+ float alpha;
+ if (next) {
+ // Interpolate between current sample and future sample.
+ // So current->eventTime <= sampleTime <= future.eventTime.
+ future.initializeFrom(next);
+ other = &future;
+ nsecs_t delta = future.eventTime - current->eventTime;
+ if (delta < RESAMPLE_MIN_DELTA) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, delta time is %lld ns.", delta);
+#endif
+ return;
+ }
+ alpha = float(sampleTime - current->eventTime) / delta;
+ } else if (touchState.historySize >= 2) {
+ // Extrapolate future sample using current sample and past sample.
+ // So other->eventTime <= current->eventTime <= sampleTime.
+ other = touchState.getHistory(1);
+ nsecs_t delta = current->eventTime - other->eventTime;
+ if (delta < RESAMPLE_MIN_DELTA) {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, delta time is %lld ns.", delta);
+#endif
+ return;
+ }
+ nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
+ if (sampleTime > maxPredict) {
+#if DEBUG_RESAMPLING
+ ALOGD("Sample time is too far in the future, adjusting prediction "
+ "from %lld to %lld ns.",
+ sampleTime - current->eventTime, maxPredict - current->eventTime);
+#endif
+ sampleTime = maxPredict;
+ }
+ alpha = float(current->eventTime - sampleTime) / delta;
+ } else {
+#if DEBUG_RESAMPLING
+ ALOGD("Not resampled, insufficient data.");
+#endif
+ return;
+ }
+
+ // Resample touch coordinates.
+ touchState.lastResample.eventTime = sampleTime;
+ touchState.lastResample.idBits.clear();
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t id = event->getPointerId(i);
+ touchState.lastResample.idToIndex[id] = i;
+ touchState.lastResample.idBits.markBit(id);
+ PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
+ const PointerCoords& currentCoords = current->getPointerById(id);
+ if (other->idBits.hasBit(id)
+ && shouldResampleTool(event->getToolType(i))) {
+ const PointerCoords& otherCoords = other->getPointerById(id);
+ resampledCoords.copyFrom(currentCoords);
+ resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
+ lerp(currentCoords.getX(), otherCoords.getX(), alpha));
+ resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
+ lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+#if DEBUG_RESAMPLING
+ ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
+ "other (%0.3f, %0.3f), alpha %0.3f",
+ id, resampledCoords.getX(), resampledCoords.getY(),
+ currentCoords.getX(), currentCoords.getY(),
+ otherCoords.getX(), otherCoords.getY(),
+ alpha);
+#endif
+ } else {
+ resampledCoords.copyFrom(currentCoords);
+#if DEBUG_RESAMPLING
+ ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)",
+ id, resampledCoords.getX(), resampledCoords.getY(),
+ currentCoords.getX(), currentCoords.getY());
+#endif
+ }
+ }
+
+ event->addSample(sampleTime, touchState.lastResample.pointers);
+}
+
+bool InputConsumer::shouldResampleTool(int32_t toolType) {
+ return toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
+ || toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
+#if DEBUG_TRANSPORT_ACTIONS
+ ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
+ mChannel->getName().string(), seq, handled ? "true" : "false");
+#endif
+
+ if (!seq) {
+ ALOGE("Attempted to send a finished signal with sequence number 0.");
+ return BAD_VALUE;
+ }
+
+ // Send finished signals for the batch sequence chain first.
+ size_t seqChainCount = mSeqChains.size();
+ if (seqChainCount) {
+ uint32_t currentSeq = seq;
+ uint32_t chainSeqs[seqChainCount];
+ size_t chainIndex = 0;
+ for (size_t i = seqChainCount; i-- > 0; ) {
+ const SeqChain& seqChain = mSeqChains.itemAt(i);
+ if (seqChain.seq == currentSeq) {
+ currentSeq = seqChain.chain;
+ chainSeqs[chainIndex++] = currentSeq;
+ mSeqChains.removeAt(i);
+ }
+ }
+ status_t status = OK;
+ while (!status && chainIndex-- > 0) {
+ status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
+ }
+ if (status) {
+ // An error occurred so at least one signal was not sent, reconstruct the chain.
+ do {
+ SeqChain seqChain;
+ seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
+ seqChain.chain = chainSeqs[chainIndex];
+ mSeqChains.push(seqChain);
+ } while (chainIndex-- > 0);
+ return status;
+ }
+ }
+
+ // Send finished signal for the last message in the batch.
+ return sendUnchainedFinishedSignal(seq, handled);
+}
+
+status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
+ InputMessage msg;
+ msg.header.type = InputMessage::TYPE_FINISHED;
+ msg.body.finished.seq = seq;
+ msg.body.finished.handled = handled;
+ return mChannel->sendMessage(&msg);
+}
+
+bool InputConsumer::hasDeferredEvent() const {
+ return mMsgDeferred;
+}
+
+bool InputConsumer::hasPendingBatch() const {
+ return !mBatches.isEmpty();
+}
+
+ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
+ for (size_t i = 0; i < mBatches.size(); i++) {
+ const Batch& batch = mBatches.itemAt(i);
+ const InputMessage& head = batch.samples.itemAt(0);
+ if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
+ for (size_t i = 0; i < mTouchStates.size(); i++) {
+ const TouchState& touchState = mTouchStates.itemAt(i);
+ if (touchState.deviceId == deviceId && touchState.source == source) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) {
+ event->initialize(
+ msg->body.key.deviceId,
+ msg->body.key.source,
+ msg->body.key.action,
+ msg->body.key.flags,
+ msg->body.key.keyCode,
+ msg->body.key.scanCode,
+ msg->body.key.metaState,
+ msg->body.key.repeatCount,
+ msg->body.key.downTime,
+ msg->body.key.eventTime);
+}
+
+void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
+ size_t pointerCount = msg->body.motion.pointerCount;
+ PointerProperties pointerProperties[pointerCount];
+ PointerCoords pointerCoords[pointerCount];
+ for (size_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].copyFrom(msg->body.motion.pointers[i].properties);
+ pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+ }
+
+ event->initialize(
+ msg->body.motion.deviceId,
+ msg->body.motion.source,
+ msg->body.motion.action,
+ msg->body.motion.flags,
+ msg->body.motion.edgeFlags,
+ msg->body.motion.metaState,
+ msg->body.motion.buttonState,
+ msg->body.motion.xOffset,
+ msg->body.motion.yOffset,
+ msg->body.motion.xPrecision,
+ msg->body.motion.yPrecision,
+ msg->body.motion.downTime,
+ msg->body.motion.eventTime,
+ pointerCount,
+ pointerProperties,
+ pointerCoords);
+}
+
+void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
+ size_t pointerCount = msg->body.motion.pointerCount;
+ PointerCoords pointerCoords[pointerCount];
+ for (size_t i = 0; i < pointerCount; i++) {
+ pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+ }
+
+ event->setMetaState(event->getMetaState() | msg->body.motion.metaState);
+ event->addSample(msg->body.motion.eventTime, pointerCoords);
+}
+
+bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) {
+ const InputMessage& head = batch.samples.itemAt(0);
+ size_t pointerCount = msg->body.motion.pointerCount;
+ if (head.body.motion.pointerCount != pointerCount
+ || head.body.motion.action != msg->body.motion.action) {
+ return false;
+ }
+ for (size_t i = 0; i < pointerCount; i++) {
+ if (head.body.motion.pointers[i].properties
+ != msg->body.motion.pointers[i].properties) {
+ return false;
+ }
+ }
+ return true;
+}
+
+ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
+ size_t numSamples = batch.samples.size();
+ size_t index = 0;
+ while (index < numSamples
+ && batch.samples.itemAt(index).body.motion.eventTime <= time) {
+ index += 1;
+ }
+ return ssize_t(index) - 1;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputTransport.h b/widget/gonk/libui/InputTransport.h
new file mode 100644
index 0000000000..66ef2850a3
--- /dev/null
+++ b/widget/gonk/libui/InputTransport.h
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_INPUT_TRANSPORT_H
+#define _ANDROIDFW_INPUT_TRANSPORT_H
+
+/**
+ * Native input transport.
+ *
+ * The InputChannel provides a mechanism for exchanging InputMessage structures across processes.
+ *
+ * The InputPublisher and InputConsumer each handle one end-point of an input channel.
+ * The InputPublisher is used by the input dispatcher to send events to the application.
+ * The InputConsumer is used by the application to receive events from the input dispatcher.
+ */
+
+#include "Input.h"
+#include <utils/Errors.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/BitSet.h>
+
+namespace android {
+
+/*
+ * Intermediate representation used to send input events and related signals.
+ */
+struct InputMessage {
+ enum {
+ TYPE_KEY = 1,
+ TYPE_MOTION = 2,
+ TYPE_FINISHED = 3,
+ };
+
+ struct Header {
+ uint32_t type;
+ uint32_t padding; // 8 byte alignment for the body that follows
+ } header;
+
+ union Body {
+ struct Key {
+ uint32_t seq;
+ nsecs_t eventTime;
+ int32_t deviceId;
+ int32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ int32_t repeatCount;
+ nsecs_t downTime;
+
+ inline size_t size() const {
+ return sizeof(Key);
+ }
+ } key;
+
+ struct Motion {
+ uint32_t seq;
+ nsecs_t eventTime;
+ int32_t deviceId;
+ int32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t buttonState;
+ int32_t edgeFlags;
+ nsecs_t downTime;
+ float xOffset;
+ float yOffset;
+ float xPrecision;
+ float yPrecision;
+ size_t pointerCount;
+ struct Pointer {
+ PointerProperties properties;
+ PointerCoords coords;
+ } pointers[MAX_POINTERS];
+
+ int32_t getActionId() const {
+ uint32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ return pointers[index].properties.id;
+ }
+
+ inline size_t size() const {
+ return sizeof(Motion) - sizeof(Pointer) * MAX_POINTERS
+ + sizeof(Pointer) * pointerCount;
+ }
+ } motion;
+
+ struct Finished {
+ uint32_t seq;
+ bool handled;
+
+ inline size_t size() const {
+ return sizeof(Finished);
+ }
+ } finished;
+ } body;
+
+ bool isValid(size_t actualSize) const;
+ size_t size() const;
+};
+
+/*
+ * An input channel consists of a local unix domain socket used to send and receive
+ * input messages across processes. Each channel has a descriptive name for debugging purposes.
+ *
+ * Each endpoint has its own InputChannel object that specifies its file descriptor.
+ *
+ * The input channel is closed when all references to it are released.
+ */
+class InputChannel : public RefBase {
+protected:
+ virtual ~InputChannel();
+
+public:
+ InputChannel(const String8& name, int fd);
+
+ /* Creates a pair of input channels.
+ *
+ * Returns OK on success.
+ */
+ static status_t openInputChannelPair(const String8& name,
+ sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel);
+
+ inline String8 getName() const { return mName; }
+ inline int getFd() const { return mFd; }
+
+ /* Sends a message to the other endpoint.
+ *
+ * If the channel is full then the message is guaranteed not to have been sent at all.
+ * Try again after the consumer has sent a finished signal indicating that it has
+ * consumed some of the pending messages from the channel.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if the channel is full.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t sendMessage(const InputMessage* msg);
+
+ /* Receives a message sent by the other endpoint.
+ *
+ * If there is no message present, try again after poll() indicates that the fd
+ * is readable.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if there is no message present.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t receiveMessage(InputMessage* msg);
+
+ /* Returns a new object that has a duplicate of this channel's fd. */
+ sp<InputChannel> dup() const;
+
+private:
+ String8 mName;
+ int mFd;
+};
+
+/*
+ * Publishes input events to an input channel.
+ */
+class InputPublisher {
+public:
+ /* Creates a publisher associated with an input channel. */
+ explicit InputPublisher(const sp<InputChannel>& channel);
+
+ /* Destroys the publisher and releases its input channel. */
+ ~InputPublisher();
+
+ /* Gets the underlying input channel. */
+ inline sp<InputChannel> getChannel() { return mChannel; }
+
+ /* Publishes a key event to the input channel.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if the channel is full.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Returns BAD_VALUE if seq is 0.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t publishKeyEvent(
+ uint32_t seq,
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t keyCode,
+ int32_t scanCode,
+ int32_t metaState,
+ int32_t repeatCount,
+ nsecs_t downTime,
+ nsecs_t eventTime);
+
+ /* Publishes a motion event to the input channel.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if the channel is full.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Returns BAD_VALUE if seq is 0 or if pointerCount is less than 1 or greater than MAX_POINTERS.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t publishMotionEvent(
+ uint32_t seq,
+ int32_t deviceId,
+ int32_t source,
+ int32_t action,
+ int32_t flags,
+ int32_t edgeFlags,
+ int32_t metaState,
+ int32_t buttonState,
+ float xOffset,
+ float yOffset,
+ float xPrecision,
+ float yPrecision,
+ nsecs_t downTime,
+ nsecs_t eventTime,
+ size_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords);
+
+ /* Receives the finished signal from the consumer in reply to the original dispatch signal.
+ * If a signal was received, returns the message sequence number,
+ * and whether the consumer handled the message.
+ *
+ * The returned sequence number is never 0 unless the operation failed.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if there is no signal present.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t receiveFinishedSignal(uint32_t* outSeq, bool* outHandled);
+
+private:
+ sp<InputChannel> mChannel;
+};
+
+/*
+ * Consumes input events from an input channel.
+ */
+class InputConsumer {
+public:
+ /* Creates a consumer associated with an input channel. */
+ explicit InputConsumer(const sp<InputChannel>& channel);
+
+ /* Destroys the consumer and releases its input channel. */
+ ~InputConsumer();
+
+ /* Gets the underlying input channel. */
+ inline sp<InputChannel> getChannel() { return mChannel; }
+
+ /* Consumes an input event from the input channel and copies its contents into
+ * an InputEvent object created using the specified factory.
+ *
+ * Tries to combine a series of move events into larger batches whenever possible.
+ *
+ * If consumeBatches is false, then defers consuming pending batched events if it
+ * is possible for additional samples to be added to them later. Call hasPendingBatch()
+ * to determine whether a pending batch is available to be consumed.
+ *
+ * If consumeBatches is true, then events are still batched but they are consumed
+ * immediately as soon as the input channel is exhausted.
+ *
+ * The frameTime parameter specifies the time when the current display frame started
+ * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
+ *
+ * The returned sequence number is never 0 unless the operation failed.
+ *
+ * Returns OK on success.
+ * Returns WOULD_BLOCK if there is no event present.
+ * Returns DEAD_OBJECT if the channel's peer has been closed.
+ * Returns NO_MEMORY if the event could not be created.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t consume(InputEventFactoryInterface* factory, bool consumeBatches,
+ nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
+
+ /* Sends a finished signal to the publisher to inform it that the message
+ * with the specified sequence number has finished being process and whether
+ * the message was handled by the consumer.
+ *
+ * Returns OK on success.
+ * Returns BAD_VALUE if seq is 0.
+ * Other errors probably indicate that the channel is broken.
+ */
+ status_t sendFinishedSignal(uint32_t seq, bool handled);
+
+ /* Returns true if there is a deferred event waiting.
+ *
+ * Should be called after calling consume() to determine whether the consumer
+ * has a deferred event to be processed. Deferred events are somewhat special in
+ * that they have already been removed from the input channel. If the input channel
+ * becomes empty, the client may need to do extra work to ensure that it processes
+ * the deferred event despite the fact that the input channel's file descriptor
+ * is not readable.
+ *
+ * One option is simply to call consume() in a loop until it returns WOULD_BLOCK.
+ * This guarantees that all deferred events will be processed.
+ *
+ * Alternately, the caller can call hasDeferredEvent() to determine whether there is
+ * a deferred event waiting and then ensure that its event loop wakes up at least
+ * one more time to consume the deferred event.
+ */
+ bool hasDeferredEvent() const;
+
+ /* Returns true if there is a pending batch.
+ *
+ * Should be called after calling consume() with consumeBatches == false to determine
+ * whether consume() should be called again later on with consumeBatches == true.
+ */
+ bool hasPendingBatch() const;
+
+private:
+ // True if touch resampling is enabled.
+ const bool mResampleTouch;
+
+ // The input channel.
+ sp<InputChannel> mChannel;
+
+ // The current input message.
+ InputMessage mMsg;
+
+ // True if mMsg contains a valid input message that was deferred from the previous
+ // call to consume and that still needs to be handled.
+ bool mMsgDeferred;
+
+ // Batched motion events per device and source.
+ struct Batch {
+ Vector<InputMessage> samples;
+ };
+ Vector<Batch> mBatches;
+
+ // Touch state per device and source, only for sources of class pointer.
+ struct History {
+ nsecs_t eventTime;
+ BitSet32 idBits;
+ int32_t idToIndex[MAX_POINTER_ID + 1];
+ PointerCoords pointers[MAX_POINTERS];
+
+ void initializeFrom(const InputMessage* msg) {
+ eventTime = msg->body.motion.eventTime;
+ idBits.clear();
+ for (size_t i = 0; i < msg->body.motion.pointerCount; i++) {
+ uint32_t id = msg->body.motion.pointers[i].properties.id;
+ idBits.markBit(id);
+ idToIndex[id] = i;
+ pointers[i].copyFrom(msg->body.motion.pointers[i].coords);
+ }
+ }
+
+ const PointerCoords& getPointerById(uint32_t id) const {
+ return pointers[idToIndex[id]];
+ }
+ };
+ struct TouchState {
+ int32_t deviceId;
+ int32_t source;
+ size_t historyCurrent;
+ size_t historySize;
+ History history[2];
+ History lastResample;
+
+ void initialize(int32_t deviceId, int32_t source) {
+ this->deviceId = deviceId;
+ this->source = source;
+ historyCurrent = 0;
+ historySize = 0;
+ lastResample.eventTime = 0;
+ lastResample.idBits.clear();
+ }
+
+ void addHistory(const InputMessage* msg) {
+ historyCurrent ^= 1;
+ if (historySize < 2) {
+ historySize += 1;
+ }
+ history[historyCurrent].initializeFrom(msg);
+ }
+
+ const History* getHistory(size_t index) const {
+ return &history[(historyCurrent + index) & 1];
+ }
+ };
+ Vector<TouchState> mTouchStates;
+
+ // Chain of batched sequence numbers. When multiple input messages are combined into
+ // a batch, we append a record here that associates the last sequence number in the
+ // batch with the previous one. When the finished signal is sent, we traverse the
+ // chain to individually finish all input messages that were part of the batch.
+ struct SeqChain {
+ uint32_t seq; // sequence number of batched input message
+ uint32_t chain; // sequence number of previous batched input message
+ };
+ Vector<SeqChain> mSeqChains;
+
+ status_t consumeBatch(InputEventFactoryInterface* factory,
+ nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
+ status_t consumeSamples(InputEventFactoryInterface* factory,
+ Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent);
+
+ void updateTouchState(InputMessage* msg);
+ void rewriteMessage(const TouchState& state, InputMessage* msg);
+ void resampleTouchState(nsecs_t frameTime, MotionEvent* event,
+ const InputMessage *next);
+
+ ssize_t findBatch(int32_t deviceId, int32_t source) const;
+ ssize_t findTouchState(int32_t deviceId, int32_t source) const;
+
+ status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
+
+ static void initializeKeyEvent(KeyEvent* event, const InputMessage* msg);
+ static void initializeMotionEvent(MotionEvent* event, const InputMessage* msg);
+ static void addSample(MotionEvent* event, const InputMessage* msg);
+ static bool canAddSample(const Batch& batch, const InputMessage* msg);
+ static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
+ static bool shouldResampleTool(int32_t toolType);
+
+ static bool isTouchResamplingEnabled();
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_INPUT_TRANSPORT_H
diff --git a/widget/gonk/libui/InputWindow.cpp b/widget/gonk/libui/InputWindow.cpp
new file mode 100644
index 0000000000..3aea445a0b
--- /dev/null
+++ b/widget/gonk/libui/InputWindow.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "InputWindow"
+
+#include "InputWindow.h"
+
+#include "cutils_log.h"
+
+namespace android {
+
+// --- InputWindowInfo ---
+
+bool InputWindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
+ return touchableRegion.contains(x, y);
+}
+
+bool InputWindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
+ return x >= frameLeft && x <= frameRight
+ && y >= frameTop && y <= frameBottom;
+}
+
+bool InputWindowInfo::isTrustedOverlay() const {
+ return layoutParamsType == TYPE_INPUT_METHOD
+ || layoutParamsType == TYPE_INPUT_METHOD_DIALOG
+ || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY;
+}
+
+bool InputWindowInfo::supportsSplitTouch() const {
+ return layoutParamsFlags & FLAG_SPLIT_TOUCH;
+}
+
+
+// --- InputWindowHandle ---
+
+InputWindowHandle::InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle) :
+ inputApplicationHandle(inputApplicationHandle), mInfo(NULL) {
+}
+
+InputWindowHandle::~InputWindowHandle() {
+ delete mInfo;
+}
+
+void InputWindowHandle::releaseInfo() {
+ if (mInfo) {
+ delete mInfo;
+ mInfo = NULL;
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/InputWindow.h b/widget/gonk/libui/InputWindow.h
new file mode 100644
index 0000000000..cce5fd4fed
--- /dev/null
+++ b/widget/gonk/libui/InputWindow.h
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _UI_INPUT_WINDOW_H
+#define _UI_INPUT_WINDOW_H
+
+#include "Input.h"
+#include "InputTransport.h"
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+#include <utils/String8.h>
+
+#include <SkRegion.h>
+
+#include "InputApplication.h"
+
+namespace android {
+
+/*
+ * Describes the properties of a window that can receive input.
+ */
+struct InputWindowInfo {
+ // Window flags from WindowManager.LayoutParams
+ enum {
+ FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 0x00000001,
+ FLAG_DIM_BEHIND = 0x00000002,
+ FLAG_BLUR_BEHIND = 0x00000004,
+ FLAG_NOT_FOCUSABLE = 0x00000008,
+ FLAG_NOT_TOUCHABLE = 0x00000010,
+ FLAG_NOT_TOUCH_MODAL = 0x00000020,
+ FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040,
+ FLAG_KEEP_SCREEN_ON = 0x00000080,
+ FLAG_LAYOUT_IN_SCREEN = 0x00000100,
+ FLAG_LAYOUT_NO_LIMITS = 0x00000200,
+ FLAG_FULLSCREEN = 0x00000400,
+ FLAG_FORCE_NOT_FULLSCREEN = 0x00000800,
+ FLAG_DITHER = 0x00001000,
+ FLAG_SECURE = 0x00002000,
+ FLAG_SCALED = 0x00004000,
+ FLAG_IGNORE_CHEEK_PRESSES = 0x00008000,
+ FLAG_LAYOUT_INSET_DECOR = 0x00010000,
+ FLAG_ALT_FOCUSABLE_IM = 0x00020000,
+ FLAG_WATCH_OUTSIDE_TOUCH = 0x00040000,
+ FLAG_SHOW_WHEN_LOCKED = 0x00080000,
+ FLAG_SHOW_WALLPAPER = 0x00100000,
+ FLAG_TURN_SCREEN_ON = 0x00200000,
+ FLAG_DISMISS_KEYGUARD = 0x00400000,
+ FLAG_SPLIT_TOUCH = 0x00800000,
+ FLAG_HARDWARE_ACCELERATED = 0x01000000,
+ FLAG_HARDWARE_ACCELERATED_SYSTEM = 0x02000000,
+ FLAG_SLIPPERY = 0x04000000,
+ FLAG_NEEDS_MENU_KEY = 0x08000000,
+ FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000,
+ FLAG_COMPATIBLE_WINDOW = 0x20000000,
+ FLAG_SYSTEM_ERROR = 0x40000000,
+ };
+
+ // Window types from WindowManager.LayoutParams
+ enum {
+ FIRST_APPLICATION_WINDOW = 1,
+ TYPE_BASE_APPLICATION = 1,
+ TYPE_APPLICATION = 2,
+ TYPE_APPLICATION_STARTING = 3,
+ LAST_APPLICATION_WINDOW = 99,
+ FIRST_SUB_WINDOW = 1000,
+ TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW,
+ TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1,
+ TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2,
+ TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3,
+ TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW+4,
+ LAST_SUB_WINDOW = 1999,
+ FIRST_SYSTEM_WINDOW = 2000,
+ TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW,
+ TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1,
+ TYPE_PHONE = FIRST_SYSTEM_WINDOW+2,
+ TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3,
+ TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4,
+ TYPE_TOAST = FIRST_SYSTEM_WINDOW+5,
+ TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6,
+ TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7,
+ TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8,
+ TYPE_KEYGUARD_DIALOG = FIRST_SYSTEM_WINDOW+9,
+ TYPE_SYSTEM_ERROR = FIRST_SYSTEM_WINDOW+10,
+ TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11,
+ TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12,
+ TYPE_WALLPAPER = FIRST_SYSTEM_WINDOW+13,
+ TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14,
+ TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15,
+ TYPE_DRAG = FIRST_SYSTEM_WINDOW+16,
+ TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17,
+ TYPE_POINTER = FIRST_SYSTEM_WINDOW+18,
+ TYPE_NAVIGATION_BAR = FIRST_SYSTEM_WINDOW+19,
+ TYPE_VOLUME_OVERLAY = FIRST_SYSTEM_WINDOW+20,
+ TYPE_BOOT_PROGRESS = FIRST_SYSTEM_WINDOW+21,
+ LAST_SYSTEM_WINDOW = 2999,
+ };
+
+ enum {
+ INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES = 0x00000001,
+ INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002,
+ INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004,
+ };
+
+ sp<InputChannel> inputChannel;
+ String8 name;
+ int32_t layoutParamsFlags;
+ int32_t layoutParamsType;
+ nsecs_t dispatchingTimeout;
+ int32_t frameLeft;
+ int32_t frameTop;
+ int32_t frameRight;
+ int32_t frameBottom;
+ float scaleFactor;
+ SkRegion touchableRegion;
+ bool visible;
+ bool canReceiveKeys;
+ bool hasFocus;
+ bool hasWallpaper;
+ bool paused;
+ int32_t layer;
+ int32_t ownerPid;
+ int32_t ownerUid;
+ int32_t inputFeatures;
+ int32_t displayId;
+
+ bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
+ bool frameContainsPoint(int32_t x, int32_t y) const;
+
+ /* Returns true if the window is of a trusted type that is allowed to silently
+ * overlay other windows for the purpose of implementing the secure views feature.
+ * Trusted overlays, such as IME windows, can partly obscure other windows without causing
+ * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
+ */
+ bool isTrustedOverlay() const;
+
+ bool supportsSplitTouch() const;
+};
+
+
+/*
+ * Handle for a window that can receive input.
+ *
+ * Used by the native input dispatcher to indirectly refer to the window manager objects
+ * that describe a window.
+ */
+class InputWindowHandle : public RefBase {
+public:
+ const sp<InputApplicationHandle> inputApplicationHandle;
+
+ inline const InputWindowInfo* getInfo() const {
+ return mInfo;
+ }
+
+ inline sp<InputChannel> getInputChannel() const {
+ return mInfo ? mInfo->inputChannel : NULL;
+ }
+
+ inline String8 getName() const {
+ return mInfo ? mInfo->name : String8("<invalid>");
+ }
+
+ inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const {
+ return mInfo ? mInfo->dispatchingTimeout : defaultValue;
+ }
+
+ /**
+ * Requests that the state of this object be updated to reflect
+ * the most current available information about the application.
+ *
+ * This method should only be called from within the input dispatcher's
+ * critical section.
+ *
+ * Returns true on success, or false if the handle is no longer valid.
+ */
+ virtual bool updateInfo() = 0;
+
+ /**
+ * Releases the storage used by the associated information when it is
+ * no longer needed.
+ */
+ void releaseInfo();
+
+protected:
+ InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle);
+ virtual ~InputWindowHandle();
+
+ InputWindowInfo* mInfo;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_WINDOW_H
diff --git a/widget/gonk/libui/KeyCharacterMap.cpp b/widget/gonk/libui/KeyCharacterMap.cpp
new file mode 100644
index 0000000000..cec0666ce7
--- /dev/null
+++ b/widget/gonk/libui/KeyCharacterMap.cpp
@@ -0,0 +1,1153 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "KeyCharacterMap"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include "android_keycodes.h"
+#include "Keyboard.h"
+#include "KeyCharacterMap.h"
+
+#if HAVE_ANDROID_OS
+#include <binder/Parcel.h>
+#endif
+
+#include <utils/Errors.h>
+#include "Tokenizer.h"
+#include <utils/Timers.h>
+
+// Enables debug output for the parser.
+#define DEBUG_PARSER 0
+
+// Enables debug output for parser performance.
+#define DEBUG_PARSER_PERFORMANCE 0
+
+// Enables debug output for mapping.
+#define DEBUG_MAPPING 0
+
+
+namespace android {
+
+static const char* WHITESPACE = " \t\r";
+static const char* WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r,:";
+
+struct Modifier {
+ const char* label;
+ int32_t metaState;
+};
+static const Modifier modifiers[] = {
+ { "shift", AMETA_SHIFT_ON },
+ { "lshift", AMETA_SHIFT_LEFT_ON },
+ { "rshift", AMETA_SHIFT_RIGHT_ON },
+ { "alt", AMETA_ALT_ON },
+ { "lalt", AMETA_ALT_LEFT_ON },
+ { "ralt", AMETA_ALT_RIGHT_ON },
+ { "ctrl", AMETA_CTRL_ON },
+ { "lctrl", AMETA_CTRL_LEFT_ON },
+ { "rctrl", AMETA_CTRL_RIGHT_ON },
+ { "meta", AMETA_META_ON },
+ { "lmeta", AMETA_META_LEFT_ON },
+ { "rmeta", AMETA_META_RIGHT_ON },
+ { "sym", AMETA_SYM_ON },
+ { "fn", AMETA_FUNCTION_ON },
+ { "capslock", AMETA_CAPS_LOCK_ON },
+ { "numlock", AMETA_NUM_LOCK_ON },
+ { "scrolllock", AMETA_SCROLL_LOCK_ON },
+};
+
+#if DEBUG_MAPPING
+static String8 toString(const char16_t* chars, size_t numChars) {
+ String8 result;
+ for (size_t i = 0; i < numChars; i++) {
+ result.appendFormat(i == 0 ? "%d" : ", %d", chars[i]);
+ }
+ return result;
+}
+#endif
+
+
+// --- KeyCharacterMap ---
+
+sp<KeyCharacterMap> KeyCharacterMap::sEmpty = new KeyCharacterMap();
+
+KeyCharacterMap::KeyCharacterMap() :
+ mType(KEYBOARD_TYPE_UNKNOWN) {
+}
+
+KeyCharacterMap::KeyCharacterMap(const KeyCharacterMap& other) :
+ RefBase(), mType(other.mType), mKeysByScanCode(other.mKeysByScanCode),
+ mKeysByUsageCode(other.mKeysByUsageCode) {
+ for (size_t i = 0; i < other.mKeys.size(); i++) {
+ mKeys.add(other.mKeys.keyAt(i), new Key(*other.mKeys.valueAt(i)));
+ }
+}
+
+KeyCharacterMap::~KeyCharacterMap() {
+ for (size_t i = 0; i < mKeys.size(); i++) {
+ Key* key = mKeys.editValueAt(i);
+ delete key;
+ }
+}
+
+status_t KeyCharacterMap::load(const String8& filename,
+ Format format, sp<KeyCharacterMap>* outMap) {
+ outMap->clear();
+
+ Tokenizer* tokenizer;
+ status_t status = Tokenizer::open(filename, &tokenizer);
+ if (status) {
+ ALOGE("Error %d opening key character map file %s.", status, filename.string());
+ } else {
+ status = load(tokenizer, format, outMap);
+ delete tokenizer;
+ }
+ return status;
+}
+
+status_t KeyCharacterMap::loadContents(const String8& filename, const char* contents,
+ Format format, sp<KeyCharacterMap>* outMap) {
+ outMap->clear();
+
+ Tokenizer* tokenizer;
+ status_t status = Tokenizer::fromContents(filename, contents, &tokenizer);
+ if (status) {
+ ALOGE("Error %d opening key character map.", status);
+ } else {
+ status = load(tokenizer, format, outMap);
+ delete tokenizer;
+ }
+ return status;
+}
+
+status_t KeyCharacterMap::load(Tokenizer* tokenizer,
+ Format format, sp<KeyCharacterMap>* outMap) {
+ status_t status = OK;
+ sp<KeyCharacterMap> map = new KeyCharacterMap();
+ if (!map.get()) {
+ ALOGE("Error allocating key character map.");
+ status = NO_MEMORY;
+ } else {
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+#endif
+ Parser parser(map.get(), tokenizer, format);
+ status = parser.parse();
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
+ ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",
+ tokenizer->getFilename().string(), tokenizer->getLineNumber(),
+ elapsedTime / 1000000.0);
+#endif
+ if (!status) {
+ *outMap = map;
+ }
+ }
+ return status;
+}
+
+sp<KeyCharacterMap> KeyCharacterMap::combine(const sp<KeyCharacterMap>& base,
+ const sp<KeyCharacterMap>& overlay) {
+ if (overlay == NULL) {
+ return base;
+ }
+ if (base == NULL) {
+ return overlay;
+ }
+
+ sp<KeyCharacterMap> map = new KeyCharacterMap(*base.get());
+ for (size_t i = 0; i < overlay->mKeys.size(); i++) {
+ int32_t keyCode = overlay->mKeys.keyAt(i);
+ Key* key = overlay->mKeys.valueAt(i);
+ ssize_t oldIndex = map->mKeys.indexOfKey(keyCode);
+ if (oldIndex >= 0) {
+ delete map->mKeys.valueAt(oldIndex);
+ map->mKeys.editValueAt(oldIndex) = new Key(*key);
+ } else {
+ map->mKeys.add(keyCode, new Key(*key));
+ }
+ }
+
+ for (size_t i = 0; i < overlay->mKeysByScanCode.size(); i++) {
+ map->mKeysByScanCode.replaceValueFor(overlay->mKeysByScanCode.keyAt(i),
+ overlay->mKeysByScanCode.valueAt(i));
+ }
+
+ for (size_t i = 0; i < overlay->mKeysByUsageCode.size(); i++) {
+ map->mKeysByUsageCode.replaceValueFor(overlay->mKeysByUsageCode.keyAt(i),
+ overlay->mKeysByUsageCode.valueAt(i));
+ }
+ return map;
+}
+
+sp<KeyCharacterMap> KeyCharacterMap::empty() {
+ return sEmpty;
+}
+
+int32_t KeyCharacterMap::getKeyboardType() const {
+ return mType;
+}
+
+char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const {
+ char16_t result = 0;
+ const Key* key;
+ if (getKey(keyCode, &key)) {
+ result = key->label;
+ }
+#if DEBUG_MAPPING
+ ALOGD("getDisplayLabel: keyCode=%d ~ Result %d.", keyCode, result);
+#endif
+ return result;
+}
+
+char16_t KeyCharacterMap::getNumber(int32_t keyCode) const {
+ char16_t result = 0;
+ const Key* key;
+ if (getKey(keyCode, &key)) {
+ result = key->number;
+ }
+#if DEBUG_MAPPING
+ ALOGD("getNumber: keyCode=%d ~ Result %d.", keyCode, result);
+#endif
+ return result;
+}
+
+char16_t KeyCharacterMap::getCharacter(int32_t keyCode, int32_t metaState) const {
+ char16_t result = 0;
+ const Key* key;
+ const Behavior* behavior;
+ if (getKeyBehavior(keyCode, metaState, &key, &behavior)) {
+ result = behavior->character;
+ }
+#if DEBUG_MAPPING
+ ALOGD("getCharacter: keyCode=%d, metaState=0x%08x ~ Result %d.", keyCode, metaState, result);
+#endif
+ return result;
+}
+
+bool KeyCharacterMap::getFallbackAction(int32_t keyCode, int32_t metaState,
+ FallbackAction* outFallbackAction) const {
+ outFallbackAction->keyCode = 0;
+ outFallbackAction->metaState = 0;
+
+ bool result = false;
+ const Key* key;
+ const Behavior* behavior;
+ if (getKeyBehavior(keyCode, metaState, &key, &behavior)) {
+ if (behavior->fallbackKeyCode) {
+ outFallbackAction->keyCode = behavior->fallbackKeyCode;
+ outFallbackAction->metaState = metaState & ~behavior->metaState;
+ result = true;
+ }
+ }
+#if DEBUG_MAPPING
+ ALOGD("getFallbackKeyCode: keyCode=%d, metaState=0x%08x ~ Result %s, "
+ "fallback keyCode=%d, fallback metaState=0x%08x.",
+ keyCode, metaState, result ? "true" : "false",
+ outFallbackAction->keyCode, outFallbackAction->metaState);
+#endif
+ return result;
+}
+
+char16_t KeyCharacterMap::getMatch(int32_t keyCode, const char16_t* chars, size_t numChars,
+ int32_t metaState) const {
+ char16_t result = 0;
+ const Key* key;
+ if (getKey(keyCode, &key)) {
+ // Try to find the most general behavior that maps to this character.
+ // For example, the base key behavior will usually be last in the list.
+ // However, if we find a perfect meta state match for one behavior then use that one.
+ for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) {
+ if (behavior->character) {
+ for (size_t i = 0; i < numChars; i++) {
+ if (behavior->character == chars[i]) {
+ result = behavior->character;
+ if ((behavior->metaState & metaState) == behavior->metaState) {
+ goto ExactMatch;
+ }
+ break;
+ }
+ }
+ }
+ }
+ ExactMatch: ;
+ }
+#if DEBUG_MAPPING
+ ALOGD("getMatch: keyCode=%d, chars=[%s], metaState=0x%08x ~ Result %d.",
+ keyCode, toString(chars, numChars).string(), metaState, result);
+#endif
+ return result;
+}
+
+bool KeyCharacterMap::getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
+ Vector<KeyEvent>& outEvents) const {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ for (size_t i = 0; i < numChars; i++) {
+ int32_t keyCode, metaState;
+ char16_t ch = chars[i];
+ if (!findKey(ch, &keyCode, &metaState)) {
+#if DEBUG_MAPPING
+ ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Failed to find mapping for character %d.",
+ deviceId, toString(chars, numChars).string(), ch);
+#endif
+ return false;
+ }
+
+ int32_t currentMetaState = 0;
+ addMetaKeys(outEvents, deviceId, metaState, true, now, &currentMetaState);
+ addKey(outEvents, deviceId, keyCode, currentMetaState, true, now);
+ addKey(outEvents, deviceId, keyCode, currentMetaState, false, now);
+ addMetaKeys(outEvents, deviceId, metaState, false, now, &currentMetaState);
+ }
+#if DEBUG_MAPPING
+ ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Generated %d events.",
+ deviceId, toString(chars, numChars).string(), int32_t(outEvents.size()));
+ for (size_t i = 0; i < outEvents.size(); i++) {
+ ALOGD(" Key: keyCode=%d, metaState=0x%08x, %s.",
+ outEvents[i].getKeyCode(), outEvents[i].getMetaState(),
+ outEvents[i].getAction() == AKEY_EVENT_ACTION_DOWN ? "down" : "up");
+ }
+#endif
+ return true;
+}
+
+status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const {
+ if (usageCode) {
+ ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
+ if (index >= 0) {
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.",
+ scanCode, usageCode, *outKeyCode);
+#endif
+ *outKeyCode = mKeysByUsageCode.valueAt(index);
+ return OK;
+ }
+ }
+ if (scanCode) {
+ ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
+ if (index >= 0) {
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.",
+ scanCode, usageCode, *outKeyCode);
+#endif
+ *outKeyCode = mKeysByScanCode.valueAt(index);
+ return OK;
+ }
+ }
+
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode);
+#endif
+ *outKeyCode = AKEYCODE_UNKNOWN;
+ return NAME_NOT_FOUND;
+}
+
+bool KeyCharacterMap::getKey(int32_t keyCode, const Key** outKey) const {
+ ssize_t index = mKeys.indexOfKey(keyCode);
+ if (index >= 0) {
+ *outKey = mKeys.valueAt(index);
+ return true;
+ }
+ return false;
+}
+
+bool KeyCharacterMap::getKeyBehavior(int32_t keyCode, int32_t metaState,
+ const Key** outKey, const Behavior** outBehavior) const {
+ const Key* key;
+ if (getKey(keyCode, &key)) {
+ const Behavior* behavior = key->firstBehavior;
+ while (behavior) {
+ if (matchesMetaState(metaState, behavior->metaState)) {
+ *outKey = key;
+ *outBehavior = behavior;
+ return true;
+ }
+ behavior = behavior->next;
+ }
+ }
+ return false;
+}
+
+bool KeyCharacterMap::matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState) {
+ // Behavior must have at least the set of meta states specified.
+ // And if the key event has CTRL, ALT or META then the behavior must exactly
+ // match those, taking into account that a behavior can specify that it handles
+ // one, both or either of a left/right modifier pair.
+ if ((eventMetaState & behaviorMetaState) == behaviorMetaState) {
+ const int32_t EXACT_META_STATES =
+ AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON
+ | AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON
+ | AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON;
+ int32_t unmatchedMetaState = eventMetaState & ~behaviorMetaState & EXACT_META_STATES;
+ if (behaviorMetaState & AMETA_CTRL_ON) {
+ unmatchedMetaState &= ~(AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON);
+ } else if (behaviorMetaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
+ unmatchedMetaState &= ~AMETA_CTRL_ON;
+ }
+ if (behaviorMetaState & AMETA_ALT_ON) {
+ unmatchedMetaState &= ~(AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON);
+ } else if (behaviorMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
+ unmatchedMetaState &= ~AMETA_ALT_ON;
+ }
+ if (behaviorMetaState & AMETA_META_ON) {
+ unmatchedMetaState &= ~(AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
+ } else if (behaviorMetaState & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
+ unmatchedMetaState &= ~AMETA_META_ON;
+ }
+ return !unmatchedMetaState;
+ }
+ return false;
+}
+
+bool KeyCharacterMap::findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const {
+ if (!ch) {
+ return false;
+ }
+
+ for (size_t i = 0; i < mKeys.size(); i++) {
+ const Key* key = mKeys.valueAt(i);
+
+ // Try to find the most general behavior that maps to this character.
+ // For example, the base key behavior will usually be last in the list.
+ const Behavior* found = NULL;
+ for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) {
+ if (behavior->character == ch) {
+ found = behavior;
+ }
+ }
+ if (found) {
+ *outKeyCode = mKeys.keyAt(i);
+ *outMetaState = found->metaState;
+ return true;
+ }
+ }
+ return false;
+}
+
+void KeyCharacterMap::addKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time) {
+ outEvents.push();
+ KeyEvent& event = outEvents.editTop();
+ event.initialize(deviceId, AINPUT_SOURCE_KEYBOARD,
+ down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
+ 0, keyCode, 0, metaState, 0, time, time);
+}
+
+void KeyCharacterMap::addMetaKeys(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t* currentMetaState) {
+ // Add and remove meta keys symmetrically.
+ if (down) {
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState);
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState);
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState);
+
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON,
+ AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON,
+ AMETA_SHIFT_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON,
+ AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON,
+ AMETA_ALT_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON,
+ AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON,
+ AMETA_CTRL_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_META_LEFT, AMETA_META_LEFT_ON,
+ AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON,
+ AMETA_META_ON, currentMetaState);
+
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState);
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, true, time,
+ AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState);
+ } else {
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_FUNCTION, AMETA_FUNCTION_ON, currentMetaState);
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_SYM, AMETA_SYM_ON, currentMetaState);
+
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_META_LEFT, AMETA_META_LEFT_ON,
+ AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON,
+ AMETA_META_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON,
+ AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON,
+ AMETA_CTRL_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON,
+ AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON,
+ AMETA_ALT_ON, currentMetaState);
+ addDoubleEphemeralMetaKey(outEvents, deviceId, metaState, false, time,
+ AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON,
+ AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON,
+ AMETA_SHIFT_ON, currentMetaState);
+
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON, currentMetaState);
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON, currentMetaState);
+ addLockedMetaKey(outEvents, deviceId, metaState, time,
+ AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON, currentMetaState);
+ }
+}
+
+bool KeyCharacterMap::addSingleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t keyCode, int32_t keyMetaState,
+ int32_t* currentMetaState) {
+ if ((metaState & keyMetaState) == keyMetaState) {
+ *currentMetaState = updateMetaState(keyCode, down, *currentMetaState);
+ addKey(outEvents, deviceId, keyCode, *currentMetaState, down, time);
+ return true;
+ }
+ return false;
+}
+
+void KeyCharacterMap::addDoubleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t leftKeyCode, int32_t leftKeyMetaState,
+ int32_t rightKeyCode, int32_t rightKeyMetaState,
+ int32_t eitherKeyMetaState,
+ int32_t* currentMetaState) {
+ bool specific = false;
+ specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time,
+ leftKeyCode, leftKeyMetaState, currentMetaState);
+ specific |= addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time,
+ rightKeyCode, rightKeyMetaState, currentMetaState);
+
+ if (!specific) {
+ addSingleEphemeralMetaKey(outEvents, deviceId, metaState, down, time,
+ leftKeyCode, eitherKeyMetaState, currentMetaState);
+ }
+}
+
+void KeyCharacterMap::addLockedMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, nsecs_t time,
+ int32_t keyCode, int32_t keyMetaState,
+ int32_t* currentMetaState) {
+ if ((metaState & keyMetaState) == keyMetaState) {
+ *currentMetaState = updateMetaState(keyCode, true, *currentMetaState);
+ addKey(outEvents, deviceId, keyCode, *currentMetaState, true, time);
+ *currentMetaState = updateMetaState(keyCode, false, *currentMetaState);
+ addKey(outEvents, deviceId, keyCode, *currentMetaState, false, time);
+ }
+}
+
+#if HAVE_ANDROID_OS
+sp<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
+ sp<KeyCharacterMap> map = new KeyCharacterMap();
+ map->mType = parcel->readInt32();
+ size_t numKeys = parcel->readInt32();
+ if (parcel->errorCheck()) {
+ return NULL;
+ }
+
+ for (size_t i = 0; i < numKeys; i++) {
+ int32_t keyCode = parcel->readInt32();
+ char16_t label = parcel->readInt32();
+ char16_t number = parcel->readInt32();
+ if (parcel->errorCheck()) {
+ return NULL;
+ }
+
+ Key* key = new Key();
+ key->label = label;
+ key->number = number;
+ map->mKeys.add(keyCode, key);
+
+ Behavior* lastBehavior = NULL;
+ while (parcel->readInt32()) {
+ int32_t metaState = parcel->readInt32();
+ char16_t character = parcel->readInt32();
+ int32_t fallbackKeyCode = parcel->readInt32();
+ if (parcel->errorCheck()) {
+ return NULL;
+ }
+
+ Behavior* behavior = new Behavior();
+ behavior->metaState = metaState;
+ behavior->character = character;
+ behavior->fallbackKeyCode = fallbackKeyCode;
+ if (lastBehavior) {
+ lastBehavior->next = behavior;
+ } else {
+ key->firstBehavior = behavior;
+ }
+ lastBehavior = behavior;
+ }
+
+ if (parcel->errorCheck()) {
+ return NULL;
+ }
+ }
+ return map;
+}
+
+void KeyCharacterMap::writeToParcel(Parcel* parcel) const {
+ parcel->writeInt32(mType);
+
+ size_t numKeys = mKeys.size();
+ parcel->writeInt32(numKeys);
+ for (size_t i = 0; i < numKeys; i++) {
+ int32_t keyCode = mKeys.keyAt(i);
+ const Key* key = mKeys.valueAt(i);
+ parcel->writeInt32(keyCode);
+ parcel->writeInt32(key->label);
+ parcel->writeInt32(key->number);
+ for (const Behavior* behavior = key->firstBehavior; behavior != NULL;
+ behavior = behavior->next) {
+ parcel->writeInt32(1);
+ parcel->writeInt32(behavior->metaState);
+ parcel->writeInt32(behavior->character);
+ parcel->writeInt32(behavior->fallbackKeyCode);
+ }
+ parcel->writeInt32(0);
+ }
+}
+#endif
+
+
+// --- KeyCharacterMap::Key ---
+
+KeyCharacterMap::Key::Key() :
+ label(0), number(0), firstBehavior(NULL) {
+}
+
+KeyCharacterMap::Key::Key(const Key& other) :
+ label(other.label), number(other.number),
+ firstBehavior(other.firstBehavior ? new Behavior(*other.firstBehavior) : NULL) {
+}
+
+KeyCharacterMap::Key::~Key() {
+ Behavior* behavior = firstBehavior;
+ while (behavior) {
+ Behavior* next = behavior->next;
+ delete behavior;
+ behavior = next;
+ }
+}
+
+
+// --- KeyCharacterMap::Behavior ---
+
+KeyCharacterMap::Behavior::Behavior() :
+ next(NULL), metaState(0), character(0), fallbackKeyCode(0) {
+}
+
+KeyCharacterMap::Behavior::Behavior(const Behavior& other) :
+ next(other.next ? new Behavior(*other.next) : NULL),
+ metaState(other.metaState), character(other.character),
+ fallbackKeyCode(other.fallbackKeyCode) {
+}
+
+
+// --- KeyCharacterMap::Parser ---
+
+KeyCharacterMap::Parser::Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format) :
+ mMap(map), mTokenizer(tokenizer), mFormat(format), mState(STATE_TOP) {
+}
+
+KeyCharacterMap::Parser::~Parser() {
+}
+
+status_t KeyCharacterMap::Parser::parse() {
+ while (!mTokenizer->isEof()) {
+#if DEBUG_PARSER
+ ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+#endif
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ switch (mState) {
+ case STATE_TOP: {
+ String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
+ if (keywordToken == "type") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseType();
+ if (status) return status;
+ } else if (keywordToken == "map") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseMap();
+ if (status) return status;
+ } else if (keywordToken == "key") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseKey();
+ if (status) return status;
+ } else {
+ ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
+ keywordToken.string());
+ return BAD_VALUE;
+ }
+ break;
+ }
+
+ case STATE_KEY: {
+ status_t status = parseKeyProperty();
+ if (status) return status;
+ break;
+ }
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
+ mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+ return BAD_VALUE;
+ }
+ }
+
+ mTokenizer->nextLine();
+ }
+
+ if (mState != STATE_TOP) {
+ ALOGE("%s: Unterminated key description at end of file.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ if (mMap->mType == KEYBOARD_TYPE_UNKNOWN) {
+ ALOGE("%s: Keyboard layout missing required keyboard 'type' declaration.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ if (mFormat == FORMAT_BASE) {
+ if (mMap->mType == KEYBOARD_TYPE_OVERLAY) {
+ ALOGE("%s: Base keyboard layout must specify a keyboard 'type' other than 'OVERLAY'.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ } else if (mFormat == FORMAT_OVERLAY) {
+ if (mMap->mType != KEYBOARD_TYPE_OVERLAY) {
+ ALOGE("%s: Overlay keyboard layout missing required keyboard "
+ "'type OVERLAY' declaration.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseType() {
+ if (mMap->mType != KEYBOARD_TYPE_UNKNOWN) {
+ ALOGE("%s: Duplicate keyboard 'type' declaration.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ KeyboardType type;
+ String8 typeToken = mTokenizer->nextToken(WHITESPACE);
+ if (typeToken == "NUMERIC") {
+ type = KEYBOARD_TYPE_NUMERIC;
+ } else if (typeToken == "PREDICTIVE") {
+ type = KEYBOARD_TYPE_PREDICTIVE;
+ } else if (typeToken == "ALPHA") {
+ type = KEYBOARD_TYPE_ALPHA;
+ } else if (typeToken == "FULL") {
+ type = KEYBOARD_TYPE_FULL;
+ } else if (typeToken == "SPECIAL_FUNCTION") {
+ type = KEYBOARD_TYPE_SPECIAL_FUNCTION;
+ } else if (typeToken == "OVERLAY") {
+ type = KEYBOARD_TYPE_OVERLAY;
+ } else {
+ ALOGE("%s: Expected keyboard type label, got '%s'.", mTokenizer->getLocation().string(),
+ typeToken.string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed type: type=%d.", type);
+#endif
+ mMap->mType = type;
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseMap() {
+ String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
+ if (keywordToken == "key") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ return parseMapKey();
+ }
+ ALOGE("%s: Expected keyword after 'map', got '%s'.", mTokenizer->getLocation().string(),
+ keywordToken.string());
+ return BAD_VALUE;
+}
+
+status_t KeyCharacterMap::Parser::parseMapKey() {
+ String8 codeToken = mTokenizer->nextToken(WHITESPACE);
+ bool mapUsage = false;
+ if (codeToken == "usage") {
+ mapUsage = true;
+ mTokenizer->skipDelimiters(WHITESPACE);
+ codeToken = mTokenizer->nextToken(WHITESPACE);
+ }
+
+ char* end;
+ int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+ KeyedVector<int32_t, int32_t>& map =
+ mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
+ if (map.indexOfKey(code) >= 0) {
+ ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
+ int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
+ if (!keyCode) {
+ ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
+ keyCodeToken.string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed map key %s: code=%d, keyCode=%d.",
+ mapUsage ? "usage" : "scan code", code, keyCode);
+#endif
+ map.add(code, keyCode);
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseKey() {
+ String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
+ int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
+ if (!keyCode) {
+ ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
+ keyCodeToken.string());
+ return BAD_VALUE;
+ }
+ if (mMap->mKeys.indexOfKey(keyCode) >= 0) {
+ ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(),
+ keyCodeToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 openBraceToken = mTokenizer->nextToken(WHITESPACE);
+ if (openBraceToken != "{") {
+ ALOGE("%s: Expected '{' after key code label, got '%s'.",
+ mTokenizer->getLocation().string(), openBraceToken.string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed beginning of key: keyCode=%d.", keyCode);
+#endif
+ mKeyCode = keyCode;
+ mMap->mKeys.add(keyCode, new Key());
+ mState = STATE_KEY;
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseKeyProperty() {
+ Key* key = mMap->mKeys.valueFor(mKeyCode);
+ String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
+ if (token == "}") {
+ mState = STATE_TOP;
+ return finishKey(key);
+ }
+
+ Vector<Property> properties;
+
+ // Parse all comma-delimited property names up to the first colon.
+ for (;;) {
+ if (token == "label") {
+ properties.add(Property(PROPERTY_LABEL));
+ } else if (token == "number") {
+ properties.add(Property(PROPERTY_NUMBER));
+ } else {
+ int32_t metaState;
+ status_t status = parseModifier(token, &metaState);
+ if (status) {
+ ALOGE("%s: Expected a property name or modifier, got '%s'.",
+ mTokenizer->getLocation().string(), token.string());
+ return status;
+ }
+ properties.add(Property(PROPERTY_META, metaState));
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (!mTokenizer->isEol()) {
+ char ch = mTokenizer->nextChar();
+ if (ch == ':') {
+ break;
+ } else if (ch == ',') {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
+ continue;
+ }
+ }
+
+ ALOGE("%s: Expected ',' or ':' after property name.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ // Parse behavior after the colon.
+ mTokenizer->skipDelimiters(WHITESPACE);
+
+ Behavior behavior;
+ bool haveCharacter = false;
+ bool haveFallback = false;
+
+ do {
+ char ch = mTokenizer->peekChar();
+ if (ch == '\'') {
+ char16_t character;
+ status_t status = parseCharacterLiteral(&character);
+ if (status || !character) {
+ ALOGE("%s: Invalid character literal for key.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ if (haveCharacter) {
+ ALOGE("%s: Cannot combine multiple character literals or 'none'.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ behavior.character = character;
+ haveCharacter = true;
+ } else {
+ token = mTokenizer->nextToken(WHITESPACE);
+ if (token == "none") {
+ if (haveCharacter) {
+ ALOGE("%s: Cannot combine multiple character literals or 'none'.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ haveCharacter = true;
+ } else if (token == "fallback") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ token = mTokenizer->nextToken(WHITESPACE);
+ int32_t keyCode = getKeyCodeByLabel(token.string());
+ if (!keyCode) {
+ ALOGE("%s: Invalid key code label for fallback behavior, got '%s'.",
+ mTokenizer->getLocation().string(),
+ token.string());
+ return BAD_VALUE;
+ }
+ if (haveFallback) {
+ ALOGE("%s: Cannot combine multiple fallback key codes.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ behavior.fallbackKeyCode = keyCode;
+ haveFallback = true;
+ } else {
+ ALOGE("%s: Expected a key behavior after ':'.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ } while (!mTokenizer->isEol() && mTokenizer->peekChar() != '#');
+
+ // Add the behavior.
+ for (size_t i = 0; i < properties.size(); i++) {
+ const Property& property = properties.itemAt(i);
+ switch (property.property) {
+ case PROPERTY_LABEL:
+ if (key->label) {
+ ALOGE("%s: Duplicate label for key.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ key->label = behavior.character;
+#if DEBUG_PARSER
+ ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label);
+#endif
+ break;
+ case PROPERTY_NUMBER:
+ if (key->number) {
+ ALOGE("%s: Duplicate number for key.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ key->number = behavior.character;
+#if DEBUG_PARSER
+ ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number);
+#endif
+ break;
+ case PROPERTY_META: {
+ for (Behavior* b = key->firstBehavior; b; b = b->next) {
+ if (b->metaState == property.metaState) {
+ ALOGE("%s: Duplicate key behavior for modifier.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+ }
+ Behavior* newBehavior = new Behavior(behavior);
+ newBehavior->metaState = property.metaState;
+ newBehavior->next = key->firstBehavior;
+ key->firstBehavior = newBehavior;
+#if DEBUG_PARSER
+ ALOGD("Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d.", mKeyCode,
+ newBehavior->metaState, newBehavior->character, newBehavior->fallbackKeyCode);
+#endif
+ break;
+ }
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::finishKey(Key* key) {
+ // Fill in default number property.
+ if (!key->number) {
+ char16_t digit = 0;
+ char16_t symbol = 0;
+ for (Behavior* b = key->firstBehavior; b; b = b->next) {
+ char16_t ch = b->character;
+ if (ch) {
+ if (ch >= '0' && ch <= '9') {
+ digit = ch;
+ } else if (ch == '(' || ch == ')' || ch == '#' || ch == '*'
+ || ch == '-' || ch == '+' || ch == ',' || ch == '.'
+ || ch == '\'' || ch == ':' || ch == ';' || ch == '/') {
+ symbol = ch;
+ }
+ }
+ }
+ key->number = digit ? digit : symbol;
+ }
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseModifier(const String8& token, int32_t* outMetaState) {
+ if (token == "base") {
+ *outMetaState = 0;
+ return NO_ERROR;
+ }
+
+ int32_t combinedMeta = 0;
+
+ const char* str = token.string();
+ const char* start = str;
+ for (const char* cur = str; ; cur++) {
+ char ch = *cur;
+ if (ch == '+' || ch == '\0') {
+ size_t len = cur - start;
+ int32_t metaState = 0;
+ for (size_t i = 0; i < sizeof(modifiers) / sizeof(Modifier); i++) {
+ if (strlen(modifiers[i].label) == len
+ && strncmp(modifiers[i].label, start, len) == 0) {
+ metaState = modifiers[i].metaState;
+ break;
+ }
+ }
+ if (!metaState) {
+ return BAD_VALUE;
+ }
+ if (combinedMeta & metaState) {
+ ALOGE("%s: Duplicate modifier combination '%s'.",
+ mTokenizer->getLocation().string(), token.string());
+ return BAD_VALUE;
+ }
+
+ combinedMeta |= metaState;
+ start = cur + 1;
+
+ if (ch == '\0') {
+ break;
+ }
+ }
+ }
+ *outMetaState = combinedMeta;
+ return NO_ERROR;
+}
+
+status_t KeyCharacterMap::Parser::parseCharacterLiteral(char16_t* outCharacter) {
+ char ch = mTokenizer->nextChar();
+ if (ch != '\'') {
+ goto Error;
+ }
+
+ ch = mTokenizer->nextChar();
+ if (ch == '\\') {
+ // Escape sequence.
+ ch = mTokenizer->nextChar();
+ if (ch == 'n') {
+ *outCharacter = '\n';
+ } else if (ch == 't') {
+ *outCharacter = '\t';
+ } else if (ch == '\\') {
+ *outCharacter = '\\';
+ } else if (ch == '\'') {
+ *outCharacter = '\'';
+ } else if (ch == '"') {
+ *outCharacter = '"';
+ } else if (ch == 'u') {
+ *outCharacter = 0;
+ for (int i = 0; i < 4; i++) {
+ ch = mTokenizer->nextChar();
+ int digit;
+ if (ch >= '0' && ch <= '9') {
+ digit = ch - '0';
+ } else if (ch >= 'A' && ch <= 'F') {
+ digit = ch - 'A' + 10;
+ } else if (ch >= 'a' && ch <= 'f') {
+ digit = ch - 'a' + 10;
+ } else {
+ goto Error;
+ }
+ *outCharacter = (*outCharacter << 4) | digit;
+ }
+ } else {
+ goto Error;
+ }
+ } else if (ch >= 32 && ch <= 126 && ch != '\'') {
+ // ASCII literal character.
+ *outCharacter = ch;
+ } else {
+ goto Error;
+ }
+
+ ch = mTokenizer->nextChar();
+ if (ch != '\'') {
+ goto Error;
+ }
+
+ // Ensure that we consumed the entire token.
+ if (mTokenizer->nextToken(WHITESPACE).isEmpty()) {
+ return NO_ERROR;
+ }
+
+Error:
+ ALOGE("%s: Malformed character literal.", mTokenizer->getLocation().string());
+ return BAD_VALUE;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/KeyCharacterMap.h b/widget/gonk/libui/KeyCharacterMap.h
new file mode 100644
index 0000000000..c7a6841059
--- /dev/null
+++ b/widget/gonk/libui/KeyCharacterMap.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_KEY_CHARACTER_MAP_H
+#define _ANDROIDFW_KEY_CHARACTER_MAP_H
+
+#include <stdint.h>
+
+#if HAVE_ANDROID_OS
+#include <binder/IBinder.h>
+#endif
+
+#include "Input.h"
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include "Tokenizer.h"
+#include <utils/String8.h>
+#include <utils/Unicode.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+/**
+ * Describes a mapping from Android key codes to characters.
+ * Also specifies other functions of the keyboard such as the keyboard type
+ * and key modifier semantics.
+ *
+ * This object is immutable after it has been loaded.
+ */
+class KeyCharacterMap : public RefBase {
+public:
+ enum KeyboardType {
+ KEYBOARD_TYPE_UNKNOWN = 0,
+ KEYBOARD_TYPE_NUMERIC = 1,
+ KEYBOARD_TYPE_PREDICTIVE = 2,
+ KEYBOARD_TYPE_ALPHA = 3,
+ KEYBOARD_TYPE_FULL = 4,
+ KEYBOARD_TYPE_SPECIAL_FUNCTION = 5,
+ KEYBOARD_TYPE_OVERLAY = 6,
+ };
+
+ enum Format {
+ // Base keyboard layout, may contain device-specific options, such as "type" declaration.
+ FORMAT_BASE = 0,
+ // Overlay keyboard layout, more restrictive, may be published by applications,
+ // cannot override device-specific options.
+ FORMAT_OVERLAY = 1,
+ // Either base or overlay layout ok.
+ FORMAT_ANY = 2,
+ };
+
+ // Substitute key code and meta state for fallback action.
+ struct FallbackAction {
+ int32_t keyCode;
+ int32_t metaState;
+ };
+
+ /* Loads a key character map from a file. */
+ static status_t load(const String8& filename, Format format, sp<KeyCharacterMap>* outMap);
+
+ /* Loads a key character map from its string contents. */
+ static status_t loadContents(const String8& filename,
+ const char* contents, Format format, sp<KeyCharacterMap>* outMap);
+
+ /* Combines a base key character map and an overlay. */
+ static sp<KeyCharacterMap> combine(const sp<KeyCharacterMap>& base,
+ const sp<KeyCharacterMap>& overlay);
+
+ /* Returns an empty key character map. */
+ static sp<KeyCharacterMap> empty();
+
+ /* Gets the keyboard type. */
+ int32_t getKeyboardType() const;
+
+ /* Gets the primary character for this key as in the label physically printed on it.
+ * Returns 0 if none (eg. for non-printing keys). */
+ char16_t getDisplayLabel(int32_t keyCode) const;
+
+ /* Gets the Unicode character for the number or symbol generated by the key
+ * when the keyboard is used as a dialing pad.
+ * Returns 0 if no number or symbol is generated.
+ */
+ char16_t getNumber(int32_t keyCode) const;
+
+ /* Gets the Unicode character generated by the key and meta key modifiers.
+ * Returns 0 if no character is generated.
+ */
+ char16_t getCharacter(int32_t keyCode, int32_t metaState) const;
+
+ /* Gets the fallback action to use by default if the application does not
+ * handle the specified key.
+ * Returns true if an action was available, false if none.
+ */
+ bool getFallbackAction(int32_t keyCode, int32_t metaState,
+ FallbackAction* outFallbackAction) const;
+
+ /* Gets the first matching Unicode character that can be generated by the key,
+ * preferring the one with the specified meta key modifiers.
+ * Returns 0 if no matching character is generated.
+ */
+ char16_t getMatch(int32_t keyCode, const char16_t* chars,
+ size_t numChars, int32_t metaState) const;
+
+ /* Gets a sequence of key events that could plausibly generate the specified
+ * character sequence. Returns false if some of the characters cannot be generated.
+ */
+ bool getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
+ Vector<KeyEvent>& outEvents) const;
+
+ /* Maps a scan code and usage code to a key code, in case this key map overrides
+ * the mapping in some way. */
+ status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const;
+
+#if HAVE_ANDROID_OS
+ /* Reads a key map from a parcel. */
+ static sp<KeyCharacterMap> readFromParcel(Parcel* parcel);
+
+ /* Writes a key map to a parcel. */
+ void writeToParcel(Parcel* parcel) const;
+#endif
+
+protected:
+ virtual ~KeyCharacterMap();
+
+private:
+ struct Behavior {
+ Behavior();
+ Behavior(const Behavior& other);
+
+ /* The next behavior in the list, or NULL if none. */
+ Behavior* next;
+
+ /* The meta key modifiers for this behavior. */
+ int32_t metaState;
+
+ /* The character to insert. */
+ char16_t character;
+
+ /* The fallback keycode if the key is not handled. */
+ int32_t fallbackKeyCode;
+ };
+
+ struct Key {
+ Key();
+ Key(const Key& other);
+ ~Key();
+
+ /* The single character label printed on the key, or 0 if none. */
+ char16_t label;
+
+ /* The number or symbol character generated by the key, or 0 if none. */
+ char16_t number;
+
+ /* The list of key behaviors sorted from most specific to least specific
+ * meta key binding. */
+ Behavior* firstBehavior;
+ };
+
+ class Parser {
+ enum State {
+ STATE_TOP = 0,
+ STATE_KEY = 1,
+ };
+
+ enum {
+ PROPERTY_LABEL = 1,
+ PROPERTY_NUMBER = 2,
+ PROPERTY_META = 3,
+ };
+
+ struct Property {
+ inline Property(int32_t property = 0, int32_t metaState = 0) :
+ property(property), metaState(metaState) { }
+
+ int32_t property;
+ int32_t metaState;
+ };
+
+ KeyCharacterMap* mMap;
+ Tokenizer* mTokenizer;
+ Format mFormat;
+ State mState;
+ int32_t mKeyCode;
+
+ public:
+ Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format);
+ ~Parser();
+ status_t parse();
+
+ private:
+ status_t parseType();
+ status_t parseMap();
+ status_t parseMapKey();
+ status_t parseKey();
+ status_t parseKeyProperty();
+ status_t finishKey(Key* key);
+ status_t parseModifier(const String8& token, int32_t* outMetaState);
+ status_t parseCharacterLiteral(char16_t* outCharacter);
+ };
+
+ static sp<KeyCharacterMap> sEmpty;
+
+ KeyedVector<int32_t, Key*> mKeys;
+ int mType;
+
+ KeyedVector<int32_t, int32_t> mKeysByScanCode;
+ KeyedVector<int32_t, int32_t> mKeysByUsageCode;
+
+ KeyCharacterMap();
+ KeyCharacterMap(const KeyCharacterMap& other);
+
+ bool getKey(int32_t keyCode, const Key** outKey) const;
+ bool getKeyBehavior(int32_t keyCode, int32_t metaState,
+ const Key** outKey, const Behavior** outBehavior) const;
+ static bool matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState);
+
+ bool findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const;
+
+ static status_t load(Tokenizer* tokenizer, Format format, sp<KeyCharacterMap>* outMap);
+
+ static void addKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time);
+ static void addMetaKeys(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t* currentMetaState);
+ static bool addSingleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t keyCode, int32_t keyMetaState,
+ int32_t* currentMetaState);
+ static void addDoubleEphemeralMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, bool down, nsecs_t time,
+ int32_t leftKeyCode, int32_t leftKeyMetaState,
+ int32_t rightKeyCode, int32_t rightKeyMetaState,
+ int32_t eitherKeyMetaState,
+ int32_t* currentMetaState);
+ static void addLockedMetaKey(Vector<KeyEvent>& outEvents,
+ int32_t deviceId, int32_t metaState, nsecs_t time,
+ int32_t keyCode, int32_t keyMetaState,
+ int32_t* currentMetaState);
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_KEY_CHARACTER_MAP_H
diff --git a/widget/gonk/libui/KeyLayoutMap.cpp b/widget/gonk/libui/KeyLayoutMap.cpp
new file mode 100644
index 0000000000..8af4b84e01
--- /dev/null
+++ b/widget/gonk/libui/KeyLayoutMap.cpp
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "KeyLayoutMap"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include "android_keycodes.h"
+#include "Keyboard.h"
+#include "KeyLayoutMap.h"
+#include <utils/Errors.h>
+#include "Tokenizer.h"
+#include <utils/Timers.h>
+
+// Enables debug output for the parser.
+#define DEBUG_PARSER 0
+
+// Enables debug output for parser performance.
+#define DEBUG_PARSER_PERFORMANCE 0
+
+// Enables debug output for mapping.
+#define DEBUG_MAPPING 0
+
+
+namespace android {
+
+static const char* WHITESPACE = " \t\r";
+
+// --- KeyLayoutMap ---
+
+KeyLayoutMap::KeyLayoutMap() {
+}
+
+KeyLayoutMap::~KeyLayoutMap() {
+}
+
+status_t KeyLayoutMap::load(const String8& filename, sp<KeyLayoutMap>* outMap) {
+ outMap->clear();
+
+ Tokenizer* tokenizer;
+ status_t status = Tokenizer::open(filename, &tokenizer);
+ if (status) {
+ ALOGE("Error %d opening key layout map file %s.", status, filename.string());
+ } else {
+ sp<KeyLayoutMap> map = new KeyLayoutMap();
+ if (!map.get()) {
+ ALOGE("Error allocating key layout map.");
+ status = NO_MEMORY;
+ } else {
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+#endif
+ Parser parser(map.get(), tokenizer);
+ status = parser.parse();
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
+ ALOGD("Parsed key layout map file '%s' %d lines in %0.3fms.",
+ tokenizer->getFilename().string(), tokenizer->getLineNumber(),
+ elapsedTime / 1000000.0);
+#endif
+ if (!status) {
+ *outMap = map;
+ }
+ }
+ delete tokenizer;
+ }
+ return status;
+}
+
+status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t usageCode,
+ int32_t* outKeyCode, uint32_t* outFlags) const {
+ const Key* key = getKey(scanCode, usageCode);
+ if (!key) {
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode);
+#endif
+ *outKeyCode = AKEYCODE_UNKNOWN;
+ *outFlags = 0;
+ return NAME_NOT_FOUND;
+ }
+
+ *outKeyCode = key->keyCode;
+ *outFlags = key->flags;
+
+#if DEBUG_MAPPING
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d, outFlags=0x%08x.",
+ scanCode, usageCode, *outKeyCode, *outFlags);
+#endif
+ return NO_ERROR;
+}
+
+const KeyLayoutMap::Key* KeyLayoutMap::getKey(int32_t scanCode, int32_t usageCode) const {
+ if (usageCode) {
+ ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
+ if (index >= 0) {
+ return &mKeysByUsageCode.valueAt(index);
+ }
+ }
+ if (scanCode) {
+ ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
+ if (index >= 0) {
+ return &mKeysByScanCode.valueAt(index);
+ }
+ }
+ return NULL;
+}
+
+status_t KeyLayoutMap::findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const {
+ const size_t N = mKeysByScanCode.size();
+ for (size_t i=0; i<N; i++) {
+ if (mKeysByScanCode.valueAt(i).keyCode == keyCode) {
+ outScanCodes->add(mKeysByScanCode.keyAt(i));
+ }
+ }
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const {
+ ssize_t index = mAxes.indexOfKey(scanCode);
+ if (index < 0) {
+#if DEBUG_MAPPING
+ ALOGD("mapAxis: scanCode=%d ~ Failed.", scanCode);
+#endif
+ return NAME_NOT_FOUND;
+ }
+
+ *outAxisInfo = mAxes.valueAt(index);
+
+#if DEBUG_MAPPING
+ ALOGD("mapAxis: scanCode=%d ~ Result mode=%d, axis=%d, highAxis=%d, "
+ "splitValue=%d, flatOverride=%d.",
+ scanCode,
+ outAxisInfo->mode, outAxisInfo->axis, outAxisInfo->highAxis,
+ outAxisInfo->splitValue, outAxisInfo->flatOverride);
+#endif
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::findScanCodeForLed(int32_t ledCode, int32_t* outScanCode) const {
+ const size_t N = mLedsByScanCode.size();
+ for (size_t i = 0; i < N; i++) {
+ if (mLedsByScanCode.valueAt(i).ledCode == ledCode) {
+ *outScanCode = mLedsByScanCode.keyAt(i);
+#if DEBUG_MAPPING
+ ALOGD("findScanCodeForLed: ledCode=%d, scanCode=%d.", ledCode, *outScanCode);
+#endif
+ return NO_ERROR;
+ }
+ }
+#if DEBUG_MAPPING
+ ALOGD("findScanCodeForLed: ledCode=%d ~ Not found.", ledCode);
+#endif
+ return NAME_NOT_FOUND;
+}
+
+status_t KeyLayoutMap::findUsageCodeForLed(int32_t ledCode, int32_t* outUsageCode) const {
+ const size_t N = mLedsByUsageCode.size();
+ for (size_t i = 0; i < N; i++) {
+ if (mLedsByUsageCode.valueAt(i).ledCode == ledCode) {
+ *outUsageCode = mLedsByUsageCode.keyAt(i);
+#if DEBUG_MAPPING
+ ALOGD("findUsageForLed: ledCode=%d, usage=%x.", ledCode, *outUsageCode);
+#endif
+ return NO_ERROR;
+ }
+ }
+#if DEBUG_MAPPING
+ ALOGD("findUsageForLed: ledCode=%d ~ Not found.", ledCode);
+#endif
+ return NAME_NOT_FOUND;
+}
+
+
+// --- KeyLayoutMap::Parser ---
+
+KeyLayoutMap::Parser::Parser(KeyLayoutMap* map, Tokenizer* tokenizer) :
+ mMap(map), mTokenizer(tokenizer) {
+}
+
+KeyLayoutMap::Parser::~Parser() {
+}
+
+status_t KeyLayoutMap::Parser::parse() {
+ while (!mTokenizer->isEof()) {
+#if DEBUG_PARSER
+ ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+#endif
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
+ if (keywordToken == "key") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseKey();
+ if (status) return status;
+ } else if (keywordToken == "axis") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseAxis();
+ if (status) return status;
+ } else if (keywordToken == "led") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ status_t status = parseLed();
+ if (status) return status;
+ } else {
+ ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
+ keywordToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
+ mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+ return BAD_VALUE;
+ }
+ }
+
+ mTokenizer->nextLine();
+ }
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::Parser::parseKey() {
+ String8 codeToken = mTokenizer->nextToken(WHITESPACE);
+ bool mapUsage = false;
+ if (codeToken == "usage") {
+ mapUsage = true;
+ mTokenizer->skipDelimiters(WHITESPACE);
+ codeToken = mTokenizer->nextToken(WHITESPACE);
+ }
+
+ char* end;
+ int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+ KeyedVector<int32_t, Key>& map =
+ mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
+ if (map.indexOfKey(code) >= 0) {
+ ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
+ int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
+ if (!keyCode) {
+ ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
+ keyCodeToken.string());
+ return BAD_VALUE;
+ }
+
+ uint32_t flags = 0;
+ for (;;) {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;
+
+ String8 flagToken = mTokenizer->nextToken(WHITESPACE);
+ uint32_t flag = getKeyFlagByLabel(flagToken.string());
+ if (!flag) {
+ ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(),
+ flagToken.string());
+ return BAD_VALUE;
+ }
+ if (flags & flag) {
+ ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(),
+ flagToken.string());
+ return BAD_VALUE;
+ }
+ flags |= flag;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.",
+ mapUsage ? "usage" : "scan code", code, keyCode, flags);
+#endif
+ Key key;
+ key.keyCode = keyCode;
+ key.flags = flags;
+ map.add(code, key);
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::Parser::parseAxis() {
+ String8 scanCodeToken = mTokenizer->nextToken(WHITESPACE);
+ char* end;
+ int32_t scanCode = int32_t(strtol(scanCodeToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected axis scan code number, got '%s'.", mTokenizer->getLocation().string(),
+ scanCodeToken.string());
+ return BAD_VALUE;
+ }
+ if (mMap->mAxes.indexOfKey(scanCode) >= 0) {
+ ALOGE("%s: Duplicate entry for axis scan code '%s'.", mTokenizer->getLocation().string(),
+ scanCodeToken.string());
+ return BAD_VALUE;
+ }
+
+ AxisInfo axisInfo;
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 token = mTokenizer->nextToken(WHITESPACE);
+ if (token == "invert") {
+ axisInfo.mode = AxisInfo::MODE_INVERT;
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 axisToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.axis = getAxisByLabel(axisToken.string());
+ if (axisInfo.axis < 0) {
+ ALOGE("%s: Expected inverted axis label, got '%s'.",
+ mTokenizer->getLocation().string(), axisToken.string());
+ return BAD_VALUE;
+ }
+ } else if (token == "split") {
+ axisInfo.mode = AxisInfo::MODE_SPLIT;
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 splitToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.splitValue = int32_t(strtol(splitToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected split value, got '%s'.",
+ mTokenizer->getLocation().string(), splitToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 lowAxisToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.axis = getAxisByLabel(lowAxisToken.string());
+ if (axisInfo.axis < 0) {
+ ALOGE("%s: Expected low axis label, got '%s'.",
+ mTokenizer->getLocation().string(), lowAxisToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 highAxisToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.highAxis = getAxisByLabel(highAxisToken.string());
+ if (axisInfo.highAxis < 0) {
+ ALOGE("%s: Expected high axis label, got '%s'.",
+ mTokenizer->getLocation().string(), highAxisToken.string());
+ return BAD_VALUE;
+ }
+ } else {
+ axisInfo.axis = getAxisByLabel(token.string());
+ if (axisInfo.axis < 0) {
+ ALOGE("%s: Expected axis label, 'split' or 'invert', got '%s'.",
+ mTokenizer->getLocation().string(), token.string());
+ return BAD_VALUE;
+ }
+ }
+
+ for (;;) {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') {
+ break;
+ }
+ String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
+ if (keywordToken == "flat") {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 flatToken = mTokenizer->nextToken(WHITESPACE);
+ axisInfo.flatOverride = int32_t(strtol(flatToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected flat value, got '%s'.",
+ mTokenizer->getLocation().string(), flatToken.string());
+ return BAD_VALUE;
+ }
+ } else {
+ ALOGE("%s: Expected keyword 'flat', got '%s'.",
+ mTokenizer->getLocation().string(), keywordToken.string());
+ return BAD_VALUE;
+ }
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, "
+ "splitValue=%d, flatOverride=%d.",
+ scanCode,
+ axisInfo.mode, axisInfo.axis, axisInfo.highAxis,
+ axisInfo.splitValue, axisInfo.flatOverride);
+#endif
+ mMap->mAxes.add(scanCode, axisInfo);
+ return NO_ERROR;
+}
+
+status_t KeyLayoutMap::Parser::parseLed() {
+ String8 codeToken = mTokenizer->nextToken(WHITESPACE);
+ bool mapUsage = false;
+ if (codeToken == "usage") {
+ mapUsage = true;
+ mTokenizer->skipDelimiters(WHITESPACE);
+ codeToken = mTokenizer->nextToken(WHITESPACE);
+ }
+ char* end;
+ int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
+ if (*end) {
+ ALOGE("%s: Expected led %s number, got '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ KeyedVector<int32_t, Led>& map = mapUsage ? mMap->mLedsByUsageCode : mMap->mLedsByScanCode;
+ if (map.indexOfKey(code) >= 0) {
+ ALOGE("%s: Duplicate entry for led %s '%s'.", mTokenizer->getLocation().string(),
+ mapUsage ? "usage" : "scan code", codeToken.string());
+ return BAD_VALUE;
+ }
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+ String8 ledCodeToken = mTokenizer->nextToken(WHITESPACE);
+ int32_t ledCode = getLedByLabel(ledCodeToken.string());
+ if (ledCode < 0) {
+ ALOGE("%s: Expected LED code label, got '%s'.", mTokenizer->getLocation().string(),
+ ledCodeToken.string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed led %s: code=%d, ledCode=%d.",
+ mapUsage ? "usage" : "scan code", code, ledCode);
+#endif
+
+ Led led;
+ led.ledCode = ledCode;
+ map.add(code, led);
+ return NO_ERROR;
+}
+};
diff --git a/widget/gonk/libui/KeyLayoutMap.h b/widget/gonk/libui/KeyLayoutMap.h
new file mode 100644
index 0000000000..8a61134472
--- /dev/null
+++ b/widget/gonk/libui/KeyLayoutMap.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_KEY_LAYOUT_MAP_H
+#define _ANDROIDFW_KEY_LAYOUT_MAP_H
+
+#include <stdint.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include "Tokenizer.h"
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct AxisInfo {
+ enum Mode {
+ // Axis value is reported directly.
+ MODE_NORMAL = 0,
+ // Axis value should be inverted before reporting.
+ MODE_INVERT = 1,
+ // Axis value should be split into two axes
+ MODE_SPLIT = 2,
+ };
+
+ // Axis mode.
+ Mode mode;
+
+ // Axis id.
+ // When split, this is the axis used for values smaller than the split position.
+ int32_t axis;
+
+ // When split, this is the axis used for values after higher than the split position.
+ int32_t highAxis;
+
+ // The split value, or 0 if not split.
+ int32_t splitValue;
+
+ // The flat value, or -1 if none.
+ int32_t flatOverride;
+
+ AxisInfo() : mode(MODE_NORMAL), axis(-1), highAxis(-1), splitValue(0), flatOverride(-1) {
+ }
+};
+
+/**
+ * Describes a mapping from keyboard scan codes and joystick axes to Android key codes and axes.
+ *
+ * This object is immutable after it has been loaded.
+ */
+class KeyLayoutMap : public RefBase {
+public:
+ static status_t load(const String8& filename, sp<KeyLayoutMap>* outMap);
+
+ status_t mapKey(int32_t scanCode, int32_t usageCode,
+ int32_t* outKeyCode, uint32_t* outFlags) const;
+ status_t findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const;
+ status_t findScanCodeForLed(int32_t ledCode, int32_t* outScanCode) const;
+ status_t findUsageCodeForLed(int32_t ledCode, int32_t* outUsageCode) const;
+
+ status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const;
+
+protected:
+ virtual ~KeyLayoutMap();
+
+private:
+ struct Key {
+ int32_t keyCode;
+ uint32_t flags;
+ };
+
+ struct Led {
+ int32_t ledCode;
+ };
+
+
+ KeyedVector<int32_t, Key> mKeysByScanCode;
+ KeyedVector<int32_t, Key> mKeysByUsageCode;
+ KeyedVector<int32_t, AxisInfo> mAxes;
+ KeyedVector<int32_t, Led> mLedsByScanCode;
+ KeyedVector<int32_t, Led> mLedsByUsageCode;
+
+ KeyLayoutMap();
+
+ const Key* getKey(int32_t scanCode, int32_t usageCode) const;
+
+ class Parser {
+ KeyLayoutMap* mMap;
+ Tokenizer* mTokenizer;
+
+ public:
+ Parser(KeyLayoutMap* map, Tokenizer* tokenizer);
+ ~Parser();
+ status_t parse();
+
+ private:
+ status_t parseKey();
+ status_t parseAxis();
+ status_t parseLed();
+ };
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_KEY_LAYOUT_MAP_H
diff --git a/widget/gonk/libui/Keyboard.cpp b/widget/gonk/libui/Keyboard.cpp
new file mode 100644
index 0000000000..62bb53b7b1
--- /dev/null
+++ b/widget/gonk/libui/Keyboard.cpp
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "Keyboard"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "Keyboard.h"
+#include "KeycodeLabels.h"
+#include "KeyLayoutMap.h"
+#include "KeyCharacterMap.h"
+#include "InputDevice.h"
+#include <utils/Errors.h>
+#include <cutils/properties.h>
+
+namespace android {
+
+// --- KeyMap ---
+
+KeyMap::KeyMap() {
+}
+
+KeyMap::~KeyMap() {
+}
+
+status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
+ const PropertyMap* deviceConfiguration) {
+ // Use the configured key layout if available.
+ if (deviceConfiguration) {
+ String8 keyLayoutName;
+ if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
+ keyLayoutName)) {
+ status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
+ if (status == NAME_NOT_FOUND) {
+ ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
+ "it was not found.",
+ deviceIdenfifier.name.string(), keyLayoutName.string());
+ }
+ }
+
+ String8 keyCharacterMapName;
+ if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
+ keyCharacterMapName)) {
+ status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
+ if (status == NAME_NOT_FOUND) {
+ ALOGE("Configuration for keyboard device '%s' requested keyboard character "
+ "map '%s' but it was not found.",
+ deviceIdenfifier.name.string(), keyLayoutName.string());
+ }
+ }
+
+ if (isComplete()) {
+ return OK;
+ }
+ }
+
+ // Try searching by device identifier.
+ if (probeKeyMap(deviceIdenfifier, String8::empty())) {
+ return OK;
+ }
+
+ // Fall back on the Generic key map.
+ // TODO Apply some additional heuristics here to figure out what kind of
+ // generic key map to use (US English, etc.) for typical external keyboards.
+ if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {
+ return OK;
+ }
+
+ // Try the Virtual key map as a last resort.
+ if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {
+ return OK;
+ }
+
+ // Give up!
+ ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
+ deviceIdenfifier.name.string());
+ return NAME_NOT_FOUND;
+}
+
+bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& keyMapName) {
+ if (!haveKeyLayout()) {
+ loadKeyLayout(deviceIdentifier, keyMapName);
+ }
+ if (!haveKeyCharacterMap()) {
+ loadKeyCharacterMap(deviceIdentifier, keyMapName);
+ }
+ return isComplete();
+}
+
+status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name) {
+ String8 path(getPath(deviceIdentifier, name,
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
+ if (path.isEmpty()) {
+ return NAME_NOT_FOUND;
+ }
+
+ status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
+ if (status) {
+ return status;
+ }
+
+ keyLayoutFile.setTo(path);
+ return OK;
+}
+
+status_t KeyMap::loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name) {
+ String8 path(getPath(deviceIdentifier, name,
+ INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_CHARACTER_MAP));
+ if (path.isEmpty()) {
+ return NAME_NOT_FOUND;
+ }
+
+ status_t status = KeyCharacterMap::load(path,
+ KeyCharacterMap::FORMAT_BASE, &keyCharacterMap);
+ if (status) {
+ return status;
+ }
+
+ keyCharacterMapFile.setTo(path);
+ return OK;
+}
+
+String8 KeyMap::getPath(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name, InputDeviceConfigurationFileType type) {
+ return name.isEmpty()
+ ? getInputDeviceConfigurationFilePathByDeviceIdentifier(deviceIdentifier, type)
+ : getInputDeviceConfigurationFilePathByName(name, type);
+}
+
+
+// --- Global functions ---
+
+bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
+ const PropertyMap* deviceConfiguration, const KeyMap* keyMap) {
+ if (!keyMap->haveKeyCharacterMap()
+ || keyMap->keyCharacterMap->getKeyboardType()
+ == KeyCharacterMap::KEYBOARD_TYPE_SPECIAL_FUNCTION) {
+ return false;
+ }
+
+ if (deviceConfiguration) {
+ bool builtIn = false;
+ if (deviceConfiguration->tryGetProperty(String8("keyboard.builtIn"), builtIn)
+ && builtIn) {
+ return true;
+ }
+ }
+
+ return strstr(deviceIdentifier.name.string(), "-keypad");
+}
+
+static int lookupValueByLabel(const char* literal, const KeycodeLabel *list) {
+ while (list->literal) {
+ if (strcmp(literal, list->literal) == 0) {
+ return list->value;
+ }
+ list++;
+ }
+ return list->value;
+}
+
+static const char* lookupLabelByValue(int value, const KeycodeLabel *list) {
+ while (list->literal) {
+ if (list->value == value) {
+ return list->literal;
+ }
+ list++;
+ }
+ return NULL;
+}
+
+int32_t getKeyCodeByLabel(const char* label) {
+ return int32_t(lookupValueByLabel(label, KEYCODES));
+}
+
+uint32_t getKeyFlagByLabel(const char* label) {
+ return uint32_t(lookupValueByLabel(label, FLAGS));
+}
+
+int32_t getAxisByLabel(const char* label) {
+ return int32_t(lookupValueByLabel(label, AXES));
+}
+
+const char* getAxisLabel(int32_t axisId) {
+ return lookupLabelByValue(axisId, AXES);
+}
+
+int32_t getLedByLabel(const char* label) {
+ return int32_t(lookupValueByLabel(label, LEDS));
+}
+static int32_t setEphemeralMetaState(int32_t mask, bool down, int32_t oldMetaState) {
+ int32_t newMetaState;
+ if (down) {
+ newMetaState = oldMetaState | mask;
+ } else {
+ newMetaState = oldMetaState &
+ ~(mask | AMETA_ALT_ON | AMETA_SHIFT_ON | AMETA_CTRL_ON | AMETA_META_ON);
+ }
+
+ if (newMetaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
+ newMetaState |= AMETA_ALT_ON;
+ }
+
+ if (newMetaState & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
+ newMetaState |= AMETA_SHIFT_ON;
+ }
+
+ if (newMetaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
+ newMetaState |= AMETA_CTRL_ON;
+ }
+
+ if (newMetaState & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
+ newMetaState |= AMETA_META_ON;
+ }
+ return newMetaState;
+}
+
+static int32_t toggleLockedMetaState(int32_t mask, bool down, int32_t oldMetaState) {
+ if (down) {
+ return oldMetaState;
+ } else {
+ return oldMetaState ^ mask;
+ }
+}
+
+int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
+ switch (keyCode) {
+ case AKEYCODE_ALT_LEFT:
+ return setEphemeralMetaState(AMETA_ALT_LEFT_ON, down, oldMetaState);
+ case AKEYCODE_ALT_RIGHT:
+ return setEphemeralMetaState(AMETA_ALT_RIGHT_ON, down, oldMetaState);
+ case AKEYCODE_SHIFT_LEFT:
+ return setEphemeralMetaState(AMETA_SHIFT_LEFT_ON, down, oldMetaState);
+ case AKEYCODE_SHIFT_RIGHT:
+ return setEphemeralMetaState(AMETA_SHIFT_RIGHT_ON, down, oldMetaState);
+ case AKEYCODE_SYM:
+ return setEphemeralMetaState(AMETA_SYM_ON, down, oldMetaState);
+ case AKEYCODE_FUNCTION:
+ return setEphemeralMetaState(AMETA_FUNCTION_ON, down, oldMetaState);
+ case AKEYCODE_CTRL_LEFT:
+ return setEphemeralMetaState(AMETA_CTRL_LEFT_ON, down, oldMetaState);
+ case AKEYCODE_CTRL_RIGHT:
+ return setEphemeralMetaState(AMETA_CTRL_RIGHT_ON, down, oldMetaState);
+ case AKEYCODE_META_LEFT:
+ return setEphemeralMetaState(AMETA_META_LEFT_ON, down, oldMetaState);
+ case AKEYCODE_META_RIGHT:
+ return setEphemeralMetaState(AMETA_META_RIGHT_ON, down, oldMetaState);
+ case AKEYCODE_CAPS_LOCK:
+ return toggleLockedMetaState(AMETA_CAPS_LOCK_ON, down, oldMetaState);
+ case AKEYCODE_NUM_LOCK:
+ return toggleLockedMetaState(AMETA_NUM_LOCK_ON, down, oldMetaState);
+ case AKEYCODE_SCROLL_LOCK:
+ return toggleLockedMetaState(AMETA_SCROLL_LOCK_ON, down, oldMetaState);
+ default:
+ return oldMetaState;
+ }
+}
+
+bool isMetaKey(int32_t keyCode) {
+ switch (keyCode) {
+ case AKEYCODE_ALT_LEFT:
+ case AKEYCODE_ALT_RIGHT:
+ case AKEYCODE_SHIFT_LEFT:
+ case AKEYCODE_SHIFT_RIGHT:
+ case AKEYCODE_SYM:
+ case AKEYCODE_FUNCTION:
+ case AKEYCODE_CTRL_LEFT:
+ case AKEYCODE_CTRL_RIGHT:
+ case AKEYCODE_META_LEFT:
+ case AKEYCODE_META_RIGHT:
+ case AKEYCODE_CAPS_LOCK:
+ case AKEYCODE_NUM_LOCK:
+ case AKEYCODE_SCROLL_LOCK:
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+} // namespace android
diff --git a/widget/gonk/libui/Keyboard.h b/widget/gonk/libui/Keyboard.h
new file mode 100644
index 0000000000..65921b25bd
--- /dev/null
+++ b/widget/gonk/libui/Keyboard.h
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_KEYBOARD_H
+#define _ANDROIDFW_KEYBOARD_H
+
+#include "Input.h"
+#include "InputDevice.h"
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/PropertyMap.h>
+
+namespace android {
+
+enum {
+ /* Device id of the built in keyboard. */
+ DEVICE_ID_BUILT_IN_KEYBOARD = 0,
+
+ /* Device id of a generic virtual keyboard with a full layout that can be used
+ * to synthesize key events. */
+ DEVICE_ID_VIRTUAL_KEYBOARD = -1,
+};
+
+class KeyLayoutMap;
+class KeyCharacterMap;
+
+/**
+ * Loads the key layout map and key character map for a keyboard device.
+ */
+class KeyMap {
+public:
+ String8 keyLayoutFile;
+ sp<KeyLayoutMap> keyLayoutMap;
+
+ String8 keyCharacterMapFile;
+ sp<KeyCharacterMap> keyCharacterMap;
+
+ KeyMap();
+ ~KeyMap();
+
+ status_t load(const InputDeviceIdentifier& deviceIdenfier,
+ const PropertyMap* deviceConfiguration);
+
+ inline bool haveKeyLayout() const {
+ return !keyLayoutFile.isEmpty();
+ }
+
+ inline bool haveKeyCharacterMap() const {
+ return !keyCharacterMapFile.isEmpty();
+ }
+
+ inline bool isComplete() const {
+ return haveKeyLayout() && haveKeyCharacterMap();
+ }
+
+private:
+ bool probeKeyMap(const InputDeviceIdentifier& deviceIdentifier, const String8& name);
+ status_t loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier, const String8& name);
+ status_t loadKeyCharacterMap(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name);
+ String8 getPath(const InputDeviceIdentifier& deviceIdentifier,
+ const String8& name, InputDeviceConfigurationFileType type);
+};
+
+/**
+ * Returns true if the keyboard is eligible for use as a built-in keyboard.
+ */
+extern bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
+ const PropertyMap* deviceConfiguration, const KeyMap* keyMap);
+
+/**
+ * Gets a key code by its short form label, eg. "HOME".
+ * Returns 0 if unknown.
+ */
+extern int32_t getKeyCodeByLabel(const char* label);
+
+/**
+ * Gets a key flag by its short form label, eg. "WAKE".
+ * Returns 0 if unknown.
+ */
+extern uint32_t getKeyFlagByLabel(const char* label);
+
+/**
+ * Gets a axis by its short form label, eg. "X".
+ * Returns -1 if unknown.
+ */
+extern int32_t getAxisByLabel(const char* label);
+
+/**
+ * Gets a axis label by its id.
+ * Returns NULL if unknown.
+ */
+extern const char* getAxisLabel(int32_t axisId);
+
+extern int32_t getLedByLabel(const char* label);
+
+/**
+ * Updates a meta state field when a key is pressed or released.
+ */
+extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState);
+
+/**
+ * Returns true if a key is a meta key like ALT or CAPS_LOCK.
+ */
+extern bool isMetaKey(int32_t keyCode);
+
+} // namespace android
+
+#endif // _ANDROIDFW_KEYBOARD_H
diff --git a/widget/gonk/libui/KeycodeLabels.h b/widget/gonk/libui/KeycodeLabels.h
new file mode 100644
index 0000000000..9f994597b0
--- /dev/null
+++ b/widget/gonk/libui/KeycodeLabels.h
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_KEYCODE_LABELS_H
+#define _ANDROIDFW_KEYCODE_LABELS_H
+
+#include "android_keycodes.h"
+
+struct KeycodeLabel {
+ const char *literal;
+ int value;
+};
+
+static const KeycodeLabel KEYCODES[] = {
+ { "SOFT_LEFT", 1 },
+ { "SOFT_RIGHT", 2 },
+ { "HOME", 3 },
+ { "BACK", 4 },
+ { "CALL", 5 },
+ { "ENDCALL", 6 },
+ { "0", 7 },
+ { "1", 8 },
+ { "2", 9 },
+ { "3", 10 },
+ { "4", 11 },
+ { "5", 12 },
+ { "6", 13 },
+ { "7", 14 },
+ { "8", 15 },
+ { "9", 16 },
+ { "STAR", 17 },
+ { "POUND", 18 },
+ { "DPAD_UP", 19 },
+ { "DPAD_DOWN", 20 },
+ { "DPAD_LEFT", 21 },
+ { "DPAD_RIGHT", 22 },
+ { "DPAD_CENTER", 23 },
+ { "VOLUME_UP", 24 },
+ { "VOLUME_DOWN", 25 },
+ { "POWER", 26 },
+ { "CAMERA", 27 },
+ { "CLEAR", 28 },
+ { "A", 29 },
+ { "B", 30 },
+ { "C", 31 },
+ { "D", 32 },
+ { "E", 33 },
+ { "F", 34 },
+ { "G", 35 },
+ { "H", 36 },
+ { "I", 37 },
+ { "J", 38 },
+ { "K", 39 },
+ { "L", 40 },
+ { "M", 41 },
+ { "N", 42 },
+ { "O", 43 },
+ { "P", 44 },
+ { "Q", 45 },
+ { "R", 46 },
+ { "S", 47 },
+ { "T", 48 },
+ { "U", 49 },
+ { "V", 50 },
+ { "W", 51 },
+ { "X", 52 },
+ { "Y", 53 },
+ { "Z", 54 },
+ { "COMMA", 55 },
+ { "PERIOD", 56 },
+ { "ALT_LEFT", 57 },
+ { "ALT_RIGHT", 58 },
+ { "SHIFT_LEFT", 59 },
+ { "SHIFT_RIGHT", 60 },
+ { "TAB", 61 },
+ { "SPACE", 62 },
+ { "SYM", 63 },
+ { "EXPLORER", 64 },
+ { "ENVELOPE", 65 },
+ { "ENTER", 66 },
+ { "DEL", 67 },
+ { "GRAVE", 68 },
+ { "MINUS", 69 },
+ { "EQUALS", 70 },
+ { "LEFT_BRACKET", 71 },
+ { "RIGHT_BRACKET", 72 },
+ { "BACKSLASH", 73 },
+ { "SEMICOLON", 74 },
+ { "APOSTROPHE", 75 },
+ { "SLASH", 76 },
+ { "AT", 77 },
+ { "NUM", 78 },
+ { "HEADSETHOOK", 79 },
+ { "FOCUS", 80 },
+ { "PLUS", 81 },
+ { "MENU", 82 },
+ { "NOTIFICATION", 83 },
+ { "SEARCH", 84 },
+ { "MEDIA_PLAY_PAUSE", 85 },
+ { "MEDIA_STOP", 86 },
+ { "MEDIA_NEXT", 87 },
+ { "MEDIA_PREVIOUS", 88 },
+ { "MEDIA_REWIND", 89 },
+ { "MEDIA_FAST_FORWARD", 90 },
+ { "MUTE", 91 },
+ { "PAGE_UP", 92 },
+ { "PAGE_DOWN", 93 },
+ { "PICTSYMBOLS", 94 },
+ { "SWITCH_CHARSET", 95 },
+ { "BUTTON_A", 96 },
+ { "BUTTON_B", 97 },
+ { "BUTTON_C", 98 },
+ { "BUTTON_X", 99 },
+ { "BUTTON_Y", 100 },
+ { "BUTTON_Z", 101 },
+ { "BUTTON_L1", 102 },
+ { "BUTTON_R1", 103 },
+ { "BUTTON_L2", 104 },
+ { "BUTTON_R2", 105 },
+ { "BUTTON_THUMBL", 106 },
+ { "BUTTON_THUMBR", 107 },
+ { "BUTTON_START", 108 },
+ { "BUTTON_SELECT", 109 },
+ { "BUTTON_MODE", 110 },
+ { "ESCAPE", 111 },
+ { "FORWARD_DEL", 112 },
+ { "CTRL_LEFT", 113 },
+ { "CTRL_RIGHT", 114 },
+ { "CAPS_LOCK", 115 },
+ { "SCROLL_LOCK", 116 },
+ { "META_LEFT", 117 },
+ { "META_RIGHT", 118 },
+ { "FUNCTION", 119 },
+ { "SYSRQ", 120 },
+ { "BREAK", 121 },
+ { "MOVE_HOME", 122 },
+ { "MOVE_END", 123 },
+ { "INSERT", 124 },
+ { "FORWARD", 125 },
+ { "MEDIA_PLAY", 126 },
+ { "MEDIA_PAUSE", 127 },
+ { "MEDIA_CLOSE", 128 },
+ { "MEDIA_EJECT", 129 },
+ { "MEDIA_RECORD", 130 },
+ { "F1", 131 },
+ { "F2", 132 },
+ { "F3", 133 },
+ { "F4", 134 },
+ { "F5", 135 },
+ { "F6", 136 },
+ { "F7", 137 },
+ { "F8", 138 },
+ { "F9", 139 },
+ { "F10", 140 },
+ { "F11", 141 },
+ { "F12", 142 },
+ { "NUM_LOCK", 143 },
+ { "NUMPAD_0", 144 },
+ { "NUMPAD_1", 145 },
+ { "NUMPAD_2", 146 },
+ { "NUMPAD_3", 147 },
+ { "NUMPAD_4", 148 },
+ { "NUMPAD_5", 149 },
+ { "NUMPAD_6", 150 },
+ { "NUMPAD_7", 151 },
+ { "NUMPAD_8", 152 },
+ { "NUMPAD_9", 153 },
+ { "NUMPAD_DIVIDE", 154 },
+ { "NUMPAD_MULTIPLY", 155 },
+ { "NUMPAD_SUBTRACT", 156 },
+ { "NUMPAD_ADD", 157 },
+ { "NUMPAD_DOT", 158 },
+ { "NUMPAD_COMMA", 159 },
+ { "NUMPAD_ENTER", 160 },
+ { "NUMPAD_EQUALS", 161 },
+ { "NUMPAD_LEFT_PAREN", 162 },
+ { "NUMPAD_RIGHT_PAREN", 163 },
+ { "VOLUME_MUTE", 164 },
+ { "INFO", 165 },
+ { "CHANNEL_UP", 166 },
+ { "CHANNEL_DOWN", 167 },
+ { "ZOOM_IN", 168 },
+ { "ZOOM_OUT", 169 },
+ { "TV", 170 },
+ { "WINDOW", 171 },
+ { "GUIDE", 172 },
+ { "DVR", 173 },
+ { "BOOKMARK", 174 },
+ { "CAPTIONS", 175 },
+ { "SETTINGS", 176 },
+ { "TV_POWER", 177 },
+ { "TV_INPUT", 178 },
+ { "STB_POWER", 179 },
+ { "STB_INPUT", 180 },
+ { "AVR_POWER", 181 },
+ { "AVR_INPUT", 182 },
+ { "PROG_RED", 183 },
+ { "PROG_GREEN", 184 },
+ { "PROG_YELLOW", 185 },
+ { "PROG_BLUE", 186 },
+ { "APP_SWITCH", 187 },
+ { "BUTTON_1", 188 },
+ { "BUTTON_2", 189 },
+ { "BUTTON_3", 190 },
+ { "BUTTON_4", 191 },
+ { "BUTTON_5", 192 },
+ { "BUTTON_6", 193 },
+ { "BUTTON_7", 194 },
+ { "BUTTON_8", 195 },
+ { "BUTTON_9", 196 },
+ { "BUTTON_10", 197 },
+ { "BUTTON_11", 198 },
+ { "BUTTON_12", 199 },
+ { "BUTTON_13", 200 },
+ { "BUTTON_14", 201 },
+ { "BUTTON_15", 202 },
+ { "BUTTON_16", 203 },
+ { "LANGUAGE_SWITCH", 204 },
+ { "MANNER_MODE", 205 },
+ { "3D_MODE", 206 },
+ { "CONTACTS", 207 },
+ { "CALENDAR", 208 },
+ { "MUSIC", 209 },
+ { "CALCULATOR", 210 },
+ { "ZENKAKU_HANKAKU", 211 },
+ { "EISU", 212 },
+ { "MUHENKAN", 213 },
+ { "HENKAN", 214 },
+ { "KATAKANA_HIRAGANA", 215 },
+ { "YEN", 216 },
+ { "RO", 217 },
+ { "KANA", 218 },
+ { "ASSIST", 219 },
+ { "BRIGHTNESS_DOWN", 220 },
+ { "BRIGHTNESS_UP", 221 },
+ { "MEDIA_AUDIO_TRACK", 222 },
+ { "SLEEP", 223 },
+ { "WAKEUP", 224 },
+ { "PAIRING", 225 },
+ { "MEDIA_TOP_MENU", 226 },
+ { "11", 227 },
+ { "12", 228 },
+ { "LAST_CHANNEL", 229 },
+ { "TV_DATA_SERVICE", 230 },
+ { "VOICE_ASSIST", 231 },
+ { "TV_RADIO_SERVICE", 232 },
+ { "TV_TELETEXT", 233 },
+ { "TV_NUMBER_ENTRY", 234 },
+ { "TV_TERRESTRIAL_ANALOG", 235 },
+ { "TV_TERRESTRIAL_DIGITAL", 236 },
+ { "TV_SATELLITE", 237 },
+ { "TV_SATELLITE_BS", 238 },
+ { "TV_SATELLITE_CS", 239 },
+ { "TV_SATELLITE_SERVICE", 240 },
+ { "TV_NETWORK", 241 },
+ { "TV_ANTENNA_CABLE", 242 },
+ { "TV_INPUT_HDMI_1", 243 },
+ { "TV_INPUT_HDMI_2", 244 },
+ { "TV_INPUT_HDMI_3", 245 },
+ { "TV_INPUT_HDMI_4", 246 },
+ { "TV_INPUT_COMPOSITE_1", 247 },
+ { "TV_INPUT_COMPOSITE_2", 248 },
+ { "TV_INPUT_COMPONENT_1", 249 },
+ { "TV_INPUT_COMPONENT_2", 250 },
+ { "TV_INPUT_VGA_1", 251 },
+ { "TV_AUDIO_DESCRIPTION", 252 },
+ { "TV_AUDIO_DESCRIPTION_MIX_UP", 253 },
+ { "TV_AUDIO_DESCRIPTION_MIX_DOWN", 254 },
+ { "TV_ZOOM_MODE", 255 },
+ { "TV_CONTENTS_MENU", 256 },
+ { "TV_MEDIA_CONTEXT_MENU", 257 },
+ { "TV_TIMER_PROGRAMMING", 258 },
+ { "HELP", 259 },
+
+ // NOTE: If you add a new keycode here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
+
+ { NULL, 0 }
+};
+
+// NOTE: If you edit these flags, also edit policy flags in Input.h.
+static const KeycodeLabel FLAGS[] = {
+ { "WAKE", 0x00000001 },
+ { "WAKE_DROPPED", 0x00000002 },
+ { "SHIFT", 0x00000004 },
+ { "CAPS_LOCK", 0x00000008 },
+ { "ALT", 0x00000010 },
+ { "ALT_GR", 0x00000020 },
+ { "MENU", 0x00000040 },
+ { "LAUNCHER", 0x00000080 },
+ { "VIRTUAL", 0x00000100 },
+ { "FUNCTION", 0x00000200 },
+ { NULL, 0 }
+};
+
+static const KeycodeLabel AXES[] = {
+ { "X", 0 },
+ { "Y", 1 },
+ { "PRESSURE", 2 },
+ { "SIZE", 3 },
+ { "TOUCH_MAJOR", 4 },
+ { "TOUCH_MINOR", 5 },
+ { "TOOL_MAJOR", 6 },
+ { "TOOL_MINOR", 7 },
+ { "ORIENTATION", 8 },
+ { "VSCROLL", 9 },
+ { "HSCROLL", 10 },
+ { "Z", 11 },
+ { "RX", 12 },
+ { "RY", 13 },
+ { "RZ", 14 },
+ { "HAT_X", 15 },
+ { "HAT_Y", 16 },
+ { "LTRIGGER", 17 },
+ { "RTRIGGER", 18 },
+ { "THROTTLE", 19 },
+ { "RUDDER", 20 },
+ { "WHEEL", 21 },
+ { "GAS", 22 },
+ { "BRAKE", 23 },
+ { "DISTANCE", 24 },
+ { "TILT", 25 },
+ { "GENERIC_1", 32 },
+ { "GENERIC_2", 33 },
+ { "GENERIC_3", 34 },
+ { "GENERIC_4", 35 },
+ { "GENERIC_5", 36 },
+ { "GENERIC_6", 37 },
+ { "GENERIC_7", 38 },
+ { "GENERIC_8", 39 },
+ { "GENERIC_9", 40 },
+ { "GENERIC_10", 41 },
+ { "GENERIC_11", 42 },
+ { "GENERIC_12", 43 },
+ { "GENERIC_13", 44 },
+ { "GENERIC_14", 45 },
+ { "GENERIC_15", 46 },
+ { "GENERIC_16", 47 },
+
+ // NOTE: If you add a new axis here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
+
+ { NULL, -1 }
+};
+
+static const KeycodeLabel LEDS[] = {
+ { "NUM_LOCK", 1},
+ { "CAPS_LOCK", 2},
+ { "SCROLL_LOCK", 3},
+ { "COMPOSE", 4},
+ { "KANA", 5},
+ { "SLEEP", 6},
+ { "SUSPEND", 7},
+ { "MUTE", 8},
+ { "MISC", 9},
+ { "MAIL", 10},
+ { "CHARGING", 11},
+ { "CONTROLLER_1", 12},
+ { "CONTROLLER_2", 13},
+ { "CONTROLLER_3", 14},
+ { "CONTROLLER_4", 15},
+
+ // NOTE: If you add new LEDs here, you must also add them to Input.h
+ { NULL, 0 }
+};
+
+#endif // _ANDROIDFW_KEYCODE_LABELS_H
diff --git a/widget/gonk/libui/PointerController.cpp b/widget/gonk/libui/PointerController.cpp
new file mode 100644
index 0000000000..ff80a0a9fe
--- /dev/null
+++ b/widget/gonk/libui/PointerController.cpp
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "PointerController"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about pointer updates
+#define DEBUG_POINTER_UPDATES 0
+
+#include "PointerController.h"
+
+#include "cutils_log.h"
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+
+namespace android {
+
+// --- PointerController ---
+
+// Time to wait before starting the fade when the pointer is inactive.
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+
+// Time to wait between animation frames.
+static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
+
+// Time to spend fading out the spot completely.
+static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
+
+// Time to spend fading out the pointer completely.
+static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
+
+
+// --- PointerController ---
+
+PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController) :
+ mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
+ mHandler = new WeakMessageHandler(this);
+
+ AutoMutex _l(mLock);
+
+ mLocked.animationPending = false;
+
+ mLocked.displayWidth = -1;
+ mLocked.displayHeight = -1;
+ mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
+
+ mLocked.presentation = PRESENTATION_POINTER;
+ mLocked.presentationChanged = false;
+
+ mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL;
+
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ mLocked.pointerAlpha = 0.0f; // pointer is initially faded
+ mLocked.pointerSprite = mSpriteController->createSprite();
+ mLocked.pointerIconChanged = false;
+
+ mLocked.buttonState = 0;
+
+ loadResources();
+}
+
+PointerController::~PointerController() {
+ mLooper->removeMessages(mHandler);
+
+ AutoMutex _l(mLock);
+
+ mLocked.pointerSprite.clear();
+
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ delete mLocked.spots.itemAt(i);
+ }
+ mLocked.spots.clear();
+ mLocked.recycledSprites.clear();
+}
+
+bool PointerController::getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const {
+ AutoMutex _l(mLock);
+
+ return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
+}
+
+bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const {
+ if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
+ return false;
+ }
+
+ *outMinX = 0;
+ *outMinY = 0;
+ switch (mLocked.displayOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ case DISPLAY_ORIENTATION_270:
+ *outMaxX = mLocked.displayHeight - 1;
+ *outMaxY = mLocked.displayWidth - 1;
+ break;
+ default:
+ *outMaxX = mLocked.displayWidth - 1;
+ *outMaxY = mLocked.displayHeight - 1;
+ break;
+ }
+ return true;
+}
+
+void PointerController::move(float deltaX, float deltaY) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
+#endif
+ if (deltaX == 0.0f && deltaY == 0.0f) {
+ return;
+ }
+
+ AutoMutex _l(mLock);
+
+ setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
+}
+
+void PointerController::setButtonState(int32_t buttonState) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("Set button state 0x%08x", buttonState);
+#endif
+ AutoMutex _l(mLock);
+
+ if (mLocked.buttonState != buttonState) {
+ mLocked.buttonState = buttonState;
+ }
+}
+
+int32_t PointerController::getButtonState() const {
+ AutoMutex _l(mLock);
+
+ return mLocked.buttonState;
+}
+
+void PointerController::setPosition(float x, float y) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
+#endif
+ AutoMutex _l(mLock);
+
+ setPositionLocked(x, y);
+}
+
+void PointerController::setPositionLocked(float x, float y) {
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ if (x <= minX) {
+ mLocked.pointerX = minX;
+ } else if (x >= maxX) {
+ mLocked.pointerX = maxX;
+ } else {
+ mLocked.pointerX = x;
+ }
+ if (y <= minY) {
+ mLocked.pointerY = minY;
+ } else if (y >= maxY) {
+ mLocked.pointerY = maxY;
+ } else {
+ mLocked.pointerY = y;
+ }
+ updatePointerLocked();
+ }
+}
+
+void PointerController::getPosition(float* outX, float* outY) const {
+ AutoMutex _l(mLock);
+
+ *outX = mLocked.pointerX;
+ *outY = mLocked.pointerY;
+}
+
+void PointerController::fade(Transition transition) {
+ AutoMutex _l(mLock);
+
+ // Remove the inactivity timeout, since we are fading now.
+ removeInactivityTimeoutLocked();
+
+ // Start fading.
+ if (transition == TRANSITION_IMMEDIATE) {
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerAlpha = 0.0f;
+ updatePointerLocked();
+ } else {
+ mLocked.pointerFadeDirection = -1;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::unfade(Transition transition) {
+ AutoMutex _l(mLock);
+
+ // Always reset the inactivity timer.
+ resetInactivityTimeoutLocked();
+
+ // Start unfading.
+ if (transition == TRANSITION_IMMEDIATE) {
+ mLocked.pointerFadeDirection = 0;
+ mLocked.pointerAlpha = 1.0f;
+ updatePointerLocked();
+ } else {
+ mLocked.pointerFadeDirection = 1;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::setPresentation(Presentation presentation) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.presentation != presentation) {
+ mLocked.presentation = presentation;
+ mLocked.presentationChanged = true;
+
+ if (presentation != PRESENTATION_SPOT) {
+ fadeOutAndReleaseAllSpotsLocked();
+ }
+
+ updatePointerLocked();
+ }
+}
+
+void PointerController::setSpots(const PointerCoords* spotCoords,
+ const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("setSpots: idBits=%08x", spotIdBits.value);
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id,
+ c.getAxisValue(AMOTION_EVENT_AXIS_X),
+ c.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+#endif
+
+ AutoMutex _l(mLock);
+
+ mSpriteController->openTransaction();
+
+ // Add or move spots for fingers that are down.
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
+ ? mResources.spotTouch : mResources.spotHover;
+ float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+ float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+ Spot* spot = getSpotLocked(id);
+ if (!spot) {
+ spot = createAndAddSpotLocked(id);
+ }
+
+ spot->updateSprite(&icon, x, y);
+ }
+
+ // Remove spots for fingers that went up.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id != Spot::INVALID_ID
+ && !spotIdBits.hasBit(spot->id)) {
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+ }
+
+ mSpriteController->closeTransaction();
+}
+
+void PointerController::clearSpots() {
+#if DEBUG_POINTER_UPDATES
+ ALOGD("clearSpots");
+#endif
+
+ AutoMutex _l(mLock);
+
+ fadeOutAndReleaseAllSpotsLocked();
+}
+
+void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.inactivityTimeout != inactivityTimeout) {
+ mLocked.inactivityTimeout = inactivityTimeout;
+ resetInactivityTimeoutLocked();
+ }
+}
+
+void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
+ AutoMutex _l(mLock);
+
+ // Adjust to use the display's unrotated coordinate frame.
+ if (orientation == DISPLAY_ORIENTATION_90
+ || orientation == DISPLAY_ORIENTATION_270) {
+ int32_t temp = height;
+ height = width;
+ width = temp;
+ }
+
+ if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+ mLocked.displayWidth = width;
+ mLocked.displayHeight = height;
+
+ float minX, minY, maxX, maxY;
+ if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
+ mLocked.pointerX = (minX + maxX) * 0.5f;
+ mLocked.pointerY = (minY + maxY) * 0.5f;
+ } else {
+ mLocked.pointerX = 0;
+ mLocked.pointerY = 0;
+ }
+
+ fadeOutAndReleaseAllSpotsLocked();
+ }
+
+ if (mLocked.displayOrientation != orientation) {
+ // Apply offsets to convert from the pixel top-left corner position to the pixel center.
+ // This creates an invariant frame of reference that we can easily rotate when
+ // taking into account that the pointer may be located at fractional pixel offsets.
+ float x = mLocked.pointerX + 0.5f;
+ float y = mLocked.pointerY + 0.5f;
+ float temp;
+
+ // Undo the previous rotation.
+ switch (mLocked.displayOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = x;
+ x = mLocked.displayWidth - y;
+ y = temp;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = mLocked.displayWidth - x;
+ y = mLocked.displayHeight - y;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ temp = x;
+ x = y;
+ y = mLocked.displayHeight - temp;
+ break;
+ }
+
+ // Perform the new rotation.
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = x;
+ x = y;
+ y = mLocked.displayWidth - temp;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = mLocked.displayWidth - x;
+ y = mLocked.displayHeight - y;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ temp = x;
+ x = mLocked.displayHeight - y;
+ y = temp;
+ break;
+ }
+
+ // Apply offsets to convert from the pixel center to the pixel top-left corner position
+ // and save the results.
+ mLocked.pointerX = x - 0.5f;
+ mLocked.pointerY = y - 0.5f;
+ mLocked.displayOrientation = orientation;
+ }
+
+ updatePointerLocked();
+}
+
+void PointerController::setPointerIcon(const SpriteIcon& icon) {
+ AutoMutex _l(mLock);
+
+ mLocked.pointerIcon = icon.copy();
+ mLocked.pointerIconChanged = true;
+
+ updatePointerLocked();
+}
+
+void PointerController::handleMessage(const Message& message) {
+ switch (message.what) {
+ case MSG_ANIMATE:
+ doAnimate();
+ break;
+ case MSG_INACTIVITY_TIMEOUT:
+ doInactivityTimeout();
+ break;
+ }
+}
+
+void PointerController::doAnimate() {
+ AutoMutex _l(mLock);
+
+ bool keepAnimating = false;
+ mLocked.animationPending = false;
+ nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
+
+ // Animate pointer fade.
+ if (mLocked.pointerFadeDirection < 0) {
+ mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha <= 0.0f) {
+ mLocked.pointerAlpha = 0.0f;
+ mLocked.pointerFadeDirection = 0;
+ } else {
+ keepAnimating = true;
+ }
+ updatePointerLocked();
+ } else if (mLocked.pointerFadeDirection > 0) {
+ mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha >= 1.0f) {
+ mLocked.pointerAlpha = 1.0f;
+ mLocked.pointerFadeDirection = 0;
+ } else {
+ keepAnimating = true;
+ }
+ updatePointerLocked();
+ }
+
+ // Animate spots that are fading out and being removed.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
+ if (spot->alpha <= 0) {
+ mLocked.spots.removeAt(i--);
+ releaseSpotLocked(spot);
+ } else {
+ spot->sprite->setAlpha(spot->alpha);
+ keepAnimating = true;
+ }
+ }
+ }
+
+ if (keepAnimating) {
+ startAnimationLocked();
+ }
+}
+
+void PointerController::doInactivityTimeout() {
+ fade(TRANSITION_GRADUAL);
+}
+
+void PointerController::startAnimationLocked() {
+ if (!mLocked.animationPending) {
+ mLocked.animationPending = true;
+ mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+ }
+}
+
+void PointerController::resetInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+
+ nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT
+ ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
+ mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::removeInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::updatePointerLocked() {
+ mSpriteController->openTransaction();
+
+ mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
+ mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+
+ if (mLocked.pointerAlpha > 0) {
+ mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
+ mLocked.pointerSprite->setVisible(true);
+ } else {
+ mLocked.pointerSprite->setVisible(false);
+ }
+
+ if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
+ mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER
+ ? mLocked.pointerIcon : mResources.spotAnchor);
+ mLocked.pointerIconChanged = false;
+ mLocked.presentationChanged = false;
+ }
+
+ mSpriteController->closeTransaction();
+}
+
+PointerController::Spot* PointerController::getSpotLocked(uint32_t id) {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == id) {
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) {
+ // Remove spots until we have fewer than MAX_SPOTS remaining.
+ while (mLocked.spots.size() >= MAX_SPOTS) {
+ Spot* spot = removeFirstFadingSpotLocked();
+ if (!spot) {
+ spot = mLocked.spots.itemAt(0);
+ mLocked.spots.removeAt(0);
+ }
+ releaseSpotLocked(spot);
+ }
+
+ // Obtain a sprite from the recycled pool.
+ sp<Sprite> sprite;
+ if (! mLocked.recycledSprites.isEmpty()) {
+ sprite = mLocked.recycledSprites.top();
+ mLocked.recycledSprites.pop();
+ } else {
+ sprite = mSpriteController->createSprite();
+ }
+
+ // Return the new spot.
+ Spot* spot = new Spot(id, sprite);
+ mLocked.spots.push(spot);
+ return spot;
+}
+
+PointerController::Spot* PointerController::removeFirstFadingSpotLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ mLocked.spots.removeAt(i);
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+void PointerController::releaseSpotLocked(Spot* spot) {
+ spot->sprite->clearIcon();
+
+ if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
+ mLocked.recycledSprites.push(spot->sprite);
+ }
+
+ delete spot;
+}
+
+void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
+ if (spot->id != Spot::INVALID_ID) {
+ spot->id = Spot::INVALID_ID;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::fadeOutAndReleaseAllSpotsLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+}
+
+void PointerController::loadResources() {
+ mPolicy->loadPointerResources(&mResources);
+}
+
+
+// --- PointerController::Spot ---
+
+void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) {
+ sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
+ sprite->setAlpha(alpha);
+ sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
+ sprite->setPosition(x, y);
+
+ this->x = x;
+ this->y = y;
+
+ if (icon != lastIcon) {
+ lastIcon = icon;
+ if (icon) {
+ sprite->setIcon(*icon);
+ sprite->setVisible(true);
+ } else {
+ sprite->setVisible(false);
+ }
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/PointerController.h b/widget/gonk/libui/PointerController.h
new file mode 100644
index 0000000000..eb48d9a1f6
--- /dev/null
+++ b/widget/gonk/libui/PointerController.h
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _UI_POINTER_CONTROLLER_H
+#define _UI_POINTER_CONTROLLER_H
+
+#include "SpriteController.h"
+
+#include <ui/DisplayInfo.h>
+#include "Input.h"
+#include <utils/BitSet.h>
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+#include <utils/String8.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/**
+ * Interface for tracking a mouse / touch pad pointer and touch pad spots.
+ *
+ * The spots are sprites on screen that visually represent the positions of
+ * fingers
+ *
+ * The pointer controller is responsible for providing synchronization and for tracking
+ * display orientation changes if needed.
+ */
+class PointerControllerInterface : public virtual RefBase {
+protected:
+ PointerControllerInterface() { }
+ virtual ~PointerControllerInterface() { }
+
+public:
+ /* Gets the bounds of the region that the pointer can traverse.
+ * Returns true if the bounds are available. */
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const = 0;
+
+ /* Move the pointer. */
+ virtual void move(float deltaX, float deltaY) = 0;
+
+ /* Sets a mask that indicates which buttons are pressed. */
+ virtual void setButtonState(int32_t buttonState) = 0;
+
+ /* Gets a mask that indicates which buttons are pressed. */
+ virtual int32_t getButtonState() const = 0;
+
+ /* Sets the absolute location of the pointer. */
+ virtual void setPosition(float x, float y) = 0;
+
+ /* Gets the absolute location of the pointer. */
+ virtual void getPosition(float* outX, float* outY) const = 0;
+
+ enum Transition {
+ // Fade/unfade immediately.
+ TRANSITION_IMMEDIATE,
+ // Fade/unfade gradually.
+ TRANSITION_GRADUAL,
+ };
+
+ /* Fades the pointer out now. */
+ virtual void fade(Transition transition) = 0;
+
+ /* Makes the pointer visible if it has faded out.
+ * The pointer never unfades itself automatically. This method must be called
+ * by the client whenever the pointer is moved or a button is pressed and it
+ * wants to ensure that the pointer becomes visible again. */
+ virtual void unfade(Transition transition) = 0;
+
+ enum Presentation {
+ // Show the mouse pointer.
+ PRESENTATION_POINTER,
+ // Show spots and a spot anchor in place of the mouse pointer.
+ PRESENTATION_SPOT,
+ };
+
+ /* Sets the mode of the pointer controller. */
+ virtual void setPresentation(Presentation presentation) = 0;
+
+ /* Sets the spots for the current gesture.
+ * The spots are not subject to the inactivity timeout like the pointer
+ * itself it since they are expected to remain visible for so long as
+ * the fingers are on the touch pad.
+ *
+ * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant.
+ * For spotCoords, pressure != 0 indicates that the spot's location is being
+ * pressed (not hovering).
+ */
+ virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits) = 0;
+
+ /* Removes all spots. */
+ virtual void clearSpots() = 0;
+};
+
+
+/*
+ * Pointer resources.
+ */
+struct PointerResources {
+ SpriteIcon spotHover;
+ SpriteIcon spotTouch;
+ SpriteIcon spotAnchor;
+};
+
+
+/*
+ * Pointer controller policy interface.
+ *
+ * The pointer controller policy is used by the pointer controller to interact with
+ * the Window Manager and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class PointerControllerPolicyInterface : public virtual RefBase {
+protected:
+ PointerControllerPolicyInterface() { }
+ virtual ~PointerControllerPolicyInterface() { }
+
+public:
+ virtual void loadPointerResources(PointerResources* outResources) = 0;
+};
+
+
+/*
+ * Tracks pointer movements and draws the pointer sprite to a surface.
+ *
+ * Handles pointer acceleration and animation.
+ */
+class PointerController : public PointerControllerInterface, public MessageHandler {
+protected:
+ virtual ~PointerController();
+
+public:
+ enum InactivityTimeout {
+ INACTIVITY_TIMEOUT_NORMAL = 0,
+ INACTIVITY_TIMEOUT_SHORT = 1,
+ };
+
+ PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController);
+
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const;
+ virtual void move(float deltaX, float deltaY);
+ virtual void setButtonState(int32_t buttonState);
+ virtual int32_t getButtonState() const;
+ virtual void setPosition(float x, float y);
+ virtual void getPosition(float* outX, float* outY) const;
+ virtual void fade(Transition transition);
+ virtual void unfade(Transition transition);
+
+ virtual void setPresentation(Presentation presentation);
+ virtual void setSpots(const PointerCoords* spotCoords,
+ const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
+ virtual void clearSpots();
+
+ void setDisplayViewport(int32_t width, int32_t height, int32_t orientation);
+ void setPointerIcon(const SpriteIcon& icon);
+ void setInactivityTimeout(InactivityTimeout inactivityTimeout);
+
+private:
+ static const size_t MAX_RECYCLED_SPRITES = 12;
+ static const size_t MAX_SPOTS = 12;
+
+ enum {
+ MSG_ANIMATE,
+ MSG_INACTIVITY_TIMEOUT,
+ };
+
+ struct Spot {
+ static const uint32_t INVALID_ID = 0xffffffff;
+
+ uint32_t id;
+ sp<Sprite> sprite;
+ float alpha;
+ float scale;
+ float x, y;
+
+ inline Spot(uint32_t id, const sp<Sprite>& sprite)
+ : id(id), sprite(sprite), alpha(1.0f), scale(1.0f),
+ x(0.0f), y(0.0f), lastIcon(NULL) { }
+
+ void updateSprite(const SpriteIcon* icon, float x, float y);
+
+ private:
+ const SpriteIcon* lastIcon;
+ };
+
+ mutable Mutex mLock;
+
+ sp<PointerControllerPolicyInterface> mPolicy;
+ sp<Looper> mLooper;
+ sp<SpriteController> mSpriteController;
+ sp<WeakMessageHandler> mHandler;
+
+ PointerResources mResources;
+
+ struct Locked {
+ bool animationPending;
+ nsecs_t animationTime;
+
+ int32_t displayWidth;
+ int32_t displayHeight;
+ int32_t displayOrientation;
+
+ InactivityTimeout inactivityTimeout;
+
+ Presentation presentation;
+ bool presentationChanged;
+
+ int32_t pointerFadeDirection;
+ float pointerX;
+ float pointerY;
+ float pointerAlpha;
+ sp<Sprite> pointerSprite;
+ SpriteIcon pointerIcon;
+ bool pointerIconChanged;
+
+ int32_t buttonState;
+
+ Vector<Spot*> spots;
+ Vector<sp<Sprite> > recycledSprites;
+ } mLocked;
+
+ bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
+ void setPositionLocked(float x, float y);
+
+ void handleMessage(const Message& message);
+ void doAnimate();
+ void doInactivityTimeout();
+
+ void startAnimationLocked();
+
+ void resetInactivityTimeoutLocked();
+ void removeInactivityTimeoutLocked();
+ void updatePointerLocked();
+
+ Spot* getSpotLocked(uint32_t id);
+ Spot* createAndAddSpotLocked(uint32_t id);
+ Spot* removeFirstFadingSpotLocked();
+ void releaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseAllSpotsLocked();
+
+ void loadResources();
+};
+
+} // namespace android
+
+#endif // _UI_POINTER_CONTROLLER_H
diff --git a/widget/gonk/libui/PowerManager.h b/widget/gonk/libui/PowerManager.h
new file mode 100644
index 0000000000..ba98db07ca
--- /dev/null
+++ b/widget/gonk/libui/PowerManager.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_POWER_MANAGER_H
+#define _ANDROIDFW_POWER_MANAGER_H
+
+
+namespace android {
+
+enum {
+ USER_ACTIVITY_EVENT_OTHER = 0,
+ USER_ACTIVITY_EVENT_BUTTON = 1,
+ USER_ACTIVITY_EVENT_TOUCH = 2,
+
+ USER_ACTIVITY_EVENT_LAST = USER_ACTIVITY_EVENT_TOUCH, // Last valid event code.
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_POWER_MANAGER_H
diff --git a/widget/gonk/libui/SpriteController.cpp b/widget/gonk/libui/SpriteController.cpp
new file mode 100644
index 0000000000..8677476b1b
--- /dev/null
+++ b/widget/gonk/libui/SpriteController.cpp
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "Sprites"
+
+//#define LOG_NDEBUG 0
+
+#include "SpriteController.h"
+
+#include "cutils_log.h"
+#include <utils/String8.h>
+#ifdef HAVE_ANDROID_OS
+#include <gui/Surface.h>
+#endif
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+#include <android/native_window.h>
+
+namespace android {
+
+// --- SpriteController ---
+
+SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
+ mLooper(looper), mOverlayLayer(overlayLayer) {
+#ifdef HAVE_ANDROID_OS
+ mHandler = new WeakMessageHandler(this);
+#endif
+
+ mLocked.transactionNestingCount = 0;
+ mLocked.deferredSpriteUpdate = false;
+}
+
+SpriteController::~SpriteController() {
+#ifdef HAVE_ANDROID_OS
+ mLooper->removeMessages(mHandler);
+
+ if (mSurfaceComposerClient != NULL) {
+ mSurfaceComposerClient->dispose();
+ mSurfaceComposerClient.clear();
+ }
+#endif
+}
+
+sp<Sprite> SpriteController::createSprite() {
+ return new SpriteImpl(this);
+}
+
+void SpriteController::openTransaction() {
+ AutoMutex _l(mLock);
+
+ mLocked.transactionNestingCount += 1;
+}
+
+void SpriteController::closeTransaction() {
+ AutoMutex _l(mLock);
+
+ LOG_ALWAYS_FATAL_IF(mLocked.transactionNestingCount == 0,
+ "Sprite closeTransaction() called but there is no open sprite transaction");
+
+ mLocked.transactionNestingCount -= 1;
+ if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) {
+ mLocked.deferredSpriteUpdate = false;
+#ifdef HAVE_ANDROID_OS
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+#endif
+ }
+}
+
+void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
+ bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
+ mLocked.invalidatedSprites.push(sprite);
+ if (wasEmpty) {
+ if (mLocked.transactionNestingCount != 0) {
+ mLocked.deferredSpriteUpdate = true;
+ } else {
+#ifdef HAVE_ANDROID_OS
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+#endif
+ }
+ }
+}
+
+#ifdef HAVE_ANDROID_OS
+void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
+ bool wasEmpty = mLocked.disposedSurfaces.isEmpty();
+ mLocked.disposedSurfaces.push(surfaceControl);
+ if (wasEmpty) {
+ mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
+ }
+}
+
+void SpriteController::handleMessage(const Message& message) {
+ switch (message.what) {
+ case MSG_UPDATE_SPRITES:
+ doUpdateSprites();
+ break;
+ case MSG_DISPOSE_SURFACES:
+ doDisposeSurfaces();
+ break;
+ }
+}
+#endif
+
+void SpriteController::doUpdateSprites() {
+ // Collect information about sprite updates.
+ // Each sprite update record includes a reference to its associated sprite so we can
+ // be certain the sprites will not be deleted while this function runs. Sprites
+ // may invalidate themselves again during this time but we will handle those changes
+ // in the next iteration.
+ Vector<SpriteUpdate> updates;
+ size_t numSprites;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ numSprites = mLocked.invalidatedSprites.size();
+ for (size_t i = 0; i < numSprites; i++) {
+ const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
+
+ updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
+ sprite->resetDirtyLocked();
+ }
+ mLocked.invalidatedSprites.clear();
+ } // release lock
+
+ // Create missing surfaces.
+ bool surfaceChanged = false;
+#ifdef HAVE_ANDROID_OS
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+ if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
+ update.state.surfaceWidth = update.state.icon.bitmap.width();
+ update.state.surfaceHeight = update.state.icon.bitmap.height();
+ update.state.surfaceDrawn = false;
+ update.state.surfaceVisible = false;
+ update.state.surfaceControl = obtainSurface(
+ update.state.surfaceWidth, update.state.surfaceHeight);
+ if (update.state.surfaceControl != NULL) {
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+#endif
+
+ // Resize sprites if needed, inside a global transaction.
+#ifdef HAVE_ANDROID_OS
+ bool haveGlobalTransaction = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+ if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
+ int32_t desiredWidth = update.state.icon.bitmap.width();
+ int32_t desiredHeight = update.state.icon.bitmap.height();
+ if (update.state.surfaceWidth < desiredWidth
+ || update.state.surfaceHeight < desiredHeight) {
+ if (!haveGlobalTransaction) {
+ SurfaceComposerClient::openGlobalTransaction();
+ haveGlobalTransaction = true;
+ }
+
+ status_t status = update.state.surfaceControl->setSize(desiredWidth, desiredHeight);
+ if (status) {
+ ALOGE("Error %d resizing sprite surface from %dx%d to %dx%d",
+ status, update.state.surfaceWidth, update.state.surfaceHeight,
+ desiredWidth, desiredHeight);
+ } else {
+ update.state.surfaceWidth = desiredWidth;
+ update.state.surfaceHeight = desiredHeight;
+ update.state.surfaceDrawn = false;
+ update.surfaceChanged = surfaceChanged = true;
+
+ if (update.state.surfaceVisible) {
+ status = update.state.surfaceControl->hide();
+ if (status) {
+ ALOGE("Error %d hiding sprite surface after resize.", status);
+ } else {
+ update.state.surfaceVisible = false;
+ }
+ }
+ }
+ }
+ }
+ }
+#endif
+#ifdef HAVE_ANDROID_OS
+ if (haveGlobalTransaction) {
+ SurfaceComposerClient::closeGlobalTransaction();
+ }
+#endif
+
+ // Redraw sprites if needed.
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) {
+ update.state.surfaceDrawn = false;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+
+#ifdef HAVE_ANDROID_OS
+ if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn
+ && update.state.wantSurfaceVisible()) {
+ sp<Surface> surface = update.state.surfaceControl->getSurface();
+ ANativeWindow_Buffer outBuffer;
+ status_t status = surface->lock(&outBuffer, NULL);
+ if (status) {
+ ALOGE("Error %d locking sprite surface before drawing.", status);
+ } else {
+ SkBitmap surfaceBitmap;
+ ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
+ surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ outBuffer.width, outBuffer.height, bpr);
+ surfaceBitmap.setPixels(outBuffer.bits);
+
+ SkCanvas surfaceCanvas(surfaceBitmap);
+
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
+
+ if (outBuffer.width > uint32_t(update.state.icon.bitmap.width())) {
+ paint.setColor(0); // transparent fill color
+ surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
+ outBuffer.width, update.state.icon.bitmap.height(), paint);
+ }
+ if (outBuffer.height > uint32_t(update.state.icon.bitmap.height())) {
+ paint.setColor(0); // transparent fill color
+ surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
+ outBuffer.width, outBuffer.height, paint);
+ }
+
+ status = surface->unlockAndPost();
+ if (status) {
+ ALOGE("Error %d unlocking and posting sprite surface after drawing.", status);
+ } else {
+ update.state.surfaceDrawn = true;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+#endif
+ }
+
+#ifdef HAVE_ANDROID_OS
+ // Set sprite surface properties and make them visible.
+ bool haveTransaction = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+ bool wantSurfaceVisibleAndDrawn = update.state.wantSurfaceVisible()
+ && update.state.surfaceDrawn;
+ bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible;
+ bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible;
+ if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
+ || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
+ | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
+ | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
+ status_t status;
+ if (!haveTransaction) {
+ SurfaceComposerClient::openGlobalTransaction();
+ haveTransaction = true;
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & DIRTY_ALPHA))) {
+ status = update.state.surfaceControl->setAlpha(update.state.alpha);
+ if (status) {
+ ALOGE("Error %d setting sprite surface alpha.", status);
+ }
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & (DIRTY_POSITION
+ | DIRTY_HOTSPOT)))) {
+ status = update.state.surfaceControl->setPosition(
+ update.state.positionX - update.state.icon.hotSpotX,
+ update.state.positionY - update.state.icon.hotSpotY);
+ if (status) {
+ ALOGE("Error %d setting sprite surface position.", status);
+ }
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible
+ || (update.state.dirty & DIRTY_TRANSFORMATION_MATRIX))) {
+ status = update.state.surfaceControl->setMatrix(
+ update.state.transformationMatrix.dsdx,
+ update.state.transformationMatrix.dtdx,
+ update.state.transformationMatrix.dsdy,
+ update.state.transformationMatrix.dtdy);
+ if (status) {
+ ALOGE("Error %d setting sprite surface transformation matrix.", status);
+ }
+ }
+
+ int32_t surfaceLayer = mOverlayLayer + update.state.layer;
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) {
+ status = update.state.surfaceControl->setLayer(surfaceLayer);
+ if (status) {
+ ALOGE("Error %d setting sprite surface layer.", status);
+ }
+ }
+
+ if (becomingVisible) {
+ status = update.state.surfaceControl->show();
+ if (status) {
+ ALOGE("Error %d showing sprite surface.", status);
+ } else {
+ update.state.surfaceVisible = true;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ } else if (becomingHidden) {
+ status = update.state.surfaceControl->hide();
+ if (status) {
+ ALOGE("Error %d hiding sprite surface.", status);
+ } else {
+ update.state.surfaceVisible = false;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+ }
+#endif
+
+#ifdef HAVE_ANDROID_OS
+ if (haveTransaction) {
+ SurfaceComposerClient::closeGlobalTransaction();
+ }
+#endif
+
+#ifdef HAVE_ANDROID_OS
+ // If any surfaces were changed, write back the new surface properties to the sprites.
+ if (surfaceChanged) { // acquire lock
+ AutoMutex _l(mLock);
+
+ for (size_t i = 0; i < numSprites; i++) {
+ const SpriteUpdate& update = updates.itemAt(i);
+
+ if (update.surfaceChanged) {
+ update.sprite->setSurfaceLocked(update.state.surfaceControl,
+ update.state.surfaceWidth, update.state.surfaceHeight,
+ update.state.surfaceDrawn, update.state.surfaceVisible);
+ }
+ }
+ } // release lock
+#endif
+
+ // Clear the sprite update vector outside the lock. It is very important that
+ // we do not clear sprite references inside the lock since we could be releasing
+ // the last remaining reference to the sprite here which would result in the
+ // sprite being deleted and the lock being reacquired by the sprite destructor
+ // while already held.
+ updates.clear();
+}
+
+void SpriteController::doDisposeSurfaces() {
+#ifdef HAVE_ANDROID_OS
+ // Collect disposed surfaces.
+ Vector<sp<SurfaceControl> > disposedSurfaces;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ disposedSurfaces = mLocked.disposedSurfaces;
+ mLocked.disposedSurfaces.clear();
+ } // release lock
+
+ // Release the last reference to each surface outside of the lock.
+ // We don't want the surfaces to be deleted while we are holding our lock.
+ disposedSurfaces.clear();
+#endif
+}
+
+void SpriteController::ensureSurfaceComposerClient() {
+#ifdef HAVE_ANDROID_OS
+ if (mSurfaceComposerClient == NULL) {
+ mSurfaceComposerClient = new SurfaceComposerClient();
+ }
+#endif
+}
+
+#ifdef HAVE_ANDROID_OS
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) {
+ ensureSurfaceComposerClient();
+
+ sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
+ String8("Sprite"), width, height, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eHidden);
+ if (surfaceControl == NULL || !surfaceControl->isValid()) {
+ ALOGE("Error creating sprite surface.");
+ return NULL;
+ }
+ return surfaceControl;
+}
+#endif
+
+
+// --- SpriteController::SpriteImpl ---
+
+SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
+ mController(controller) {
+}
+
+SpriteController::SpriteImpl::~SpriteImpl() {
+ AutoMutex _m(mController->mLock);
+
+#ifdef HAVE_ANDROID_OS
+ // Let the controller take care of deleting the last reference to sprite
+ // surfaces so that we do not block the caller on an IPC here.
+ if (mLocked.state.surfaceControl != NULL) {
+ mController->disposeSurfaceLocked(mLocked.state.surfaceControl);
+ mLocked.state.surfaceControl.clear();
+ }
+#endif
+}
+
+void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
+ AutoMutex _l(mController->mLock);
+
+#ifdef HAVE_ANDROID_OS
+ uint32_t dirty;
+ if (icon.isValid()) {
+ icon.bitmap.copyTo(&mLocked.state.icon.bitmap, SkBitmap::kARGB_8888_Config);
+
+ if (!mLocked.state.icon.isValid()
+ || mLocked.state.icon.hotSpotX != icon.hotSpotX
+ || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+ mLocked.state.icon.hotSpotX = icon.hotSpotX;
+ mLocked.state.icon.hotSpotY = icon.hotSpotY;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ } else {
+ dirty = DIRTY_BITMAP;
+ }
+ } else if (mLocked.state.icon.isValid()) {
+ mLocked.state.icon.bitmap.reset();
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ } else {
+ return; // setting to invalid icon and already invalid so nothing to do
+ }
+
+ invalidateLocked(dirty);
+#endif
+}
+
+void SpriteController::SpriteImpl::setVisible(bool visible) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.visible != visible) {
+ mLocked.state.visible = visible;
+ invalidateLocked(DIRTY_VISIBILITY);
+ }
+}
+
+void SpriteController::SpriteImpl::setPosition(float x, float y) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.positionX != x || mLocked.state.positionY != y) {
+ mLocked.state.positionX = x;
+ mLocked.state.positionY = y;
+ invalidateLocked(DIRTY_POSITION);
+ }
+}
+
+void SpriteController::SpriteImpl::setLayer(int32_t layer) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.layer != layer) {
+ mLocked.state.layer = layer;
+ invalidateLocked(DIRTY_LAYER);
+ }
+}
+
+void SpriteController::SpriteImpl::setAlpha(float alpha) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.alpha != alpha) {
+ mLocked.state.alpha = alpha;
+ invalidateLocked(DIRTY_ALPHA);
+ }
+}
+
+void SpriteController::SpriteImpl::setTransformationMatrix(
+ const SpriteTransformationMatrix& matrix) {
+ AutoMutex _l(mController->mLock);
+
+ if (mLocked.state.transformationMatrix != matrix) {
+ mLocked.state.transformationMatrix = matrix;
+ invalidateLocked(DIRTY_TRANSFORMATION_MATRIX);
+ }
+}
+
+void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
+ bool wasDirty = mLocked.state.dirty;
+ mLocked.state.dirty |= dirty;
+
+ if (!wasDirty) {
+ mController->invalidateSpriteLocked(this);
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/SpriteController.h b/widget/gonk/libui/SpriteController.h
new file mode 100644
index 0000000000..4926095ec8
--- /dev/null
+++ b/widget/gonk/libui/SpriteController.h
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _UI_SPRITES_H
+#define _UI_SPRITES_H
+
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+
+#ifdef HAVE_ANDROID_OS
+#include <gui/SurfaceComposerClient.h>
+#endif
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/*
+ * Transformation matrix for a sprite.
+ */
+struct SpriteTransformationMatrix {
+ inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { }
+ inline SpriteTransformationMatrix(float dsdx, float dtdx, float dsdy, float dtdy) :
+ dsdx(dsdx), dtdx(dtdx), dsdy(dsdy), dtdy(dtdy) { }
+
+ float dsdx;
+ float dtdx;
+ float dsdy;
+ float dtdy;
+
+ inline bool operator== (const SpriteTransformationMatrix& other) {
+ return dsdx == other.dsdx
+ && dtdx == other.dtdx
+ && dsdy == other.dsdy
+ && dtdy == other.dtdy;
+ }
+
+ inline bool operator!= (const SpriteTransformationMatrix& other) {
+ return !(*this == other);
+ }
+};
+
+/*
+ * Icon that a sprite displays, including its hotspot.
+ */
+struct SpriteIcon {
+ inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { }
+#ifdef HAVE_ANDROID_OS
+ inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) :
+ bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
+
+ SkBitmap bitmap;
+#endif
+ float hotSpotX;
+ float hotSpotY;
+
+ inline SpriteIcon copy() const {
+#ifdef HAVE_ANDROID_OS
+ SkBitmap bitmapCopy;
+ bitmap.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config);
+ return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY);
+#else
+ return SpriteIcon();
+#endif
+ }
+
+ inline void reset() {
+#ifdef HAVE_ANDROID_OS
+ bitmap.reset();
+ hotSpotX = 0;
+ hotSpotY = 0;
+#endif
+ }
+
+ inline bool isValid() const {
+#ifdef HAVE_ANDROID_OS
+ return !bitmap.isNull() && !bitmap.empty();
+#else
+ return false;
+#endif
+ }
+};
+
+/*
+ * A sprite is a simple graphical object that is displayed on-screen above other layers.
+ * The basic sprite class is an interface.
+ * The implementation is provided by the sprite controller.
+ */
+class Sprite : public RefBase {
+protected:
+ Sprite() { }
+ virtual ~Sprite() { }
+
+public:
+ enum {
+ // The base layer for pointer sprites.
+ BASE_LAYER_POINTER = 0, // reserve space for 1 pointer
+
+ // The base layer for spot sprites.
+ BASE_LAYER_SPOT = 1, // reserve space for MAX_POINTER_ID spots
+ };
+
+ /* Sets the bitmap that is drawn by the sprite.
+ * The sprite retains a copy of the bitmap for subsequent rendering. */
+ virtual void setIcon(const SpriteIcon& icon) = 0;
+
+ inline void clearIcon() {
+ setIcon(SpriteIcon());
+ }
+
+ /* Sets whether the sprite is visible. */
+ virtual void setVisible(bool visible) = 0;
+
+ /* Sets the sprite position on screen, relative to the sprite's hot spot. */
+ virtual void setPosition(float x, float y) = 0;
+
+ /* Sets the layer of the sprite, relative to the system sprite overlay layer.
+ * Layer 0 is the overlay layer, > 0 appear above this layer. */
+ virtual void setLayer(int32_t layer) = 0;
+
+ /* Sets the sprite alpha blend ratio between 0.0 and 1.0. */
+ virtual void setAlpha(float alpha) = 0;
+
+ /* Sets the sprite transformation matrix. */
+ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
+};
+
+/*
+ * Displays sprites on the screen.
+ *
+ * This interface is used by PointerController and SpotController to draw pointers or
+ * spot representations of fingers. It is not intended for general purpose use
+ * by other components.
+ *
+ * All sprite position updates and rendering is performed asynchronously.
+ *
+ * Clients are responsible for animating sprites by periodically updating their properties.
+ */
+class SpriteController : public MessageHandler {
+protected:
+ virtual ~SpriteController();
+
+public:
+ SpriteController(const sp<Looper>& looper, int32_t overlayLayer);
+
+ /* Creates a new sprite, initially invisible. */
+ sp<Sprite> createSprite();
+
+ /* Opens or closes a transaction to perform a batch of sprite updates as part of
+ * a single operation such as setPosition and setAlpha. It is not necessary to
+ * open a transaction when updating a single property.
+ * Calls to openTransaction() nest and must be matched by an equal number
+ * of calls to closeTransaction(). */
+ void openTransaction();
+ void closeTransaction();
+
+private:
+ enum {
+ MSG_UPDATE_SPRITES,
+ MSG_DISPOSE_SURFACES,
+ };
+
+ enum {
+ DIRTY_BITMAP = 1 << 0,
+ DIRTY_ALPHA = 1 << 1,
+ DIRTY_POSITION = 1 << 2,
+ DIRTY_TRANSFORMATION_MATRIX = 1 << 3,
+ DIRTY_LAYER = 1 << 4,
+ DIRTY_VISIBILITY = 1 << 5,
+ DIRTY_HOTSPOT = 1 << 6,
+ };
+
+ /* Describes the state of a sprite.
+ * This structure is designed so that it can be copied during updates so that
+ * surfaces can be resized and redrawn without blocking the client by holding a lock
+ * on the sprites for a long time.
+ * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
+ struct SpriteState {
+ inline SpriteState() :
+ dirty(0), visible(false),
+ positionX(0), positionY(0), layer(0), alpha(1.0f),
+ surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
+ }
+
+ uint32_t dirty;
+
+ SpriteIcon icon;
+ bool visible;
+ float positionX;
+ float positionY;
+ int32_t layer;
+ float alpha;
+ SpriteTransformationMatrix transformationMatrix;
+
+#ifdef HAVE_ANDROID_OS
+ sp<SurfaceControl> surfaceControl;
+#endif
+ int32_t surfaceWidth;
+ int32_t surfaceHeight;
+ bool surfaceDrawn;
+ bool surfaceVisible;
+
+ inline bool wantSurfaceVisible() const {
+ return visible && alpha > 0.0f && icon.isValid();
+ }
+ };
+
+ /* Client interface for a sprite.
+ * Requests acquire a lock on the controller, update local state and request the
+ * controller to invalidate the sprite.
+ * The real heavy lifting of creating, resizing and redrawing surfaces happens
+ * asynchronously with no locks held except in short critical section to copy
+ * the sprite state before the work and update the sprite surface control afterwards.
+ */
+ class SpriteImpl : public Sprite {
+ protected:
+ virtual ~SpriteImpl();
+
+ public:
+ SpriteImpl(const sp<SpriteController> controller);
+
+ virtual void setIcon(const SpriteIcon& icon);
+ virtual void setVisible(bool visible);
+ virtual void setPosition(float x, float y);
+ virtual void setLayer(int32_t layer);
+ virtual void setAlpha(float alpha);
+ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
+
+ inline const SpriteState& getStateLocked() const {
+ return mLocked.state;
+ }
+
+ inline void resetDirtyLocked() {
+ mLocked.state.dirty = 0;
+ }
+
+#ifdef HAVE_ANDROID_OS
+ inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
+ int32_t width, int32_t height, bool drawn, bool visible) {
+ mLocked.state.surfaceControl = surfaceControl;
+ mLocked.state.surfaceWidth = width;
+ mLocked.state.surfaceHeight = height;
+ mLocked.state.surfaceDrawn = drawn;
+ mLocked.state.surfaceVisible = visible;
+ }
+#endif
+
+ private:
+ sp<SpriteController> mController;
+
+ struct Locked {
+ SpriteState state;
+ } mLocked; // guarded by mController->mLock
+
+ void invalidateLocked(uint32_t dirty);
+ };
+
+ /* Stores temporary information collected during the sprite update cycle. */
+ struct SpriteUpdate {
+ inline SpriteUpdate() : surfaceChanged(false) { }
+ inline SpriteUpdate(const sp<SpriteImpl> sprite, const SpriteState& state) :
+ sprite(sprite), state(state), surfaceChanged(false) {
+ }
+
+ sp<SpriteImpl> sprite;
+ SpriteState state;
+ bool surfaceChanged;
+ };
+
+ mutable Mutex mLock;
+
+ sp<Looper> mLooper;
+ const int32_t mOverlayLayer;
+#ifdef HAVE_ANDROID_OS
+ sp<WeakMessageHandler> mHandler;
+
+ sp<SurfaceComposerClient> mSurfaceComposerClient;
+#endif
+
+ struct Locked {
+ Vector<sp<SpriteImpl> > invalidatedSprites;
+#ifdef HAVE_ANDROID_OS
+ Vector<sp<SurfaceControl> > disposedSurfaces;
+#endif
+ uint32_t transactionNestingCount;
+ bool deferredSpriteUpdate;
+ } mLocked; // guarded by mLock
+
+ void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
+#ifdef HAVE_ANDROID_OS
+ void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
+
+ void handleMessage(const Message& message);
+#endif
+ void doUpdateSprites();
+ void doDisposeSurfaces();
+
+ void ensureSurfaceComposerClient();
+#ifdef HAVE_ANDROID_OS
+ sp<SurfaceControl> obtainSurface(int32_t width, int32_t height);
+#endif
+};
+
+} // namespace android
+
+#endif // _UI_SPRITES_H
diff --git a/widget/gonk/libui/Tokenizer.cpp b/widget/gonk/libui/Tokenizer.cpp
new file mode 100644
index 0000000000..2f585cb4e1
--- /dev/null
+++ b/widget/gonk/libui/Tokenizer.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "Tokenizer"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "Tokenizer.h"
+
+// Enables debug output for the tokenizer.
+#define DEBUG_TOKENIZER 0
+
+
+namespace android {
+
+static inline bool isDelimiter(char ch, const char* delimiters) {
+ return strchr(delimiters, ch) != NULL;
+}
+
+Tokenizer::Tokenizer(const String8& filename, FileMap* fileMap, char* buffer,
+ bool ownBuffer, size_t length) :
+ mFilename(filename), mFileMap(fileMap),
+ mBuffer(buffer), mOwnBuffer(ownBuffer), mLength(length),
+ mCurrent(buffer), mLineNumber(1) {
+}
+
+Tokenizer::~Tokenizer() {
+ if (mFileMap) {
+ mFileMap->release();
+ }
+ if (mOwnBuffer) {
+ delete[] mBuffer;
+ }
+}
+
+status_t Tokenizer::open(const String8& filename, Tokenizer** outTokenizer) {
+ *outTokenizer = NULL;
+
+ int result = NO_ERROR;
+ int fd = ::open(filename.string(), O_RDONLY);
+ if (fd < 0) {
+ result = -errno;
+ ALOGE("Error opening file '%s', %s.", filename.string(), strerror(errno));
+ } else {
+ struct stat stat;
+ if (fstat(fd, &stat)) {
+ result = -errno;
+ ALOGE("Error getting size of file '%s', %s.", filename.string(), strerror(errno));
+ } else {
+ size_t length = size_t(stat.st_size);
+
+ FileMap* fileMap = new FileMap();
+ bool ownBuffer = false;
+ char* buffer;
+ if (fileMap->create(NULL, fd, 0, length, true)) {
+ fileMap->advise(FileMap::SEQUENTIAL);
+ buffer = static_cast<char*>(fileMap->getDataPtr());
+ } else {
+ fileMap->release();
+ fileMap = NULL;
+
+ // Fall back to reading into a buffer since we can't mmap files in sysfs.
+ // The length we obtained from stat is wrong too (it will always be 4096)
+ // so we must trust that read will read the entire file.
+ buffer = new char[length];
+ ownBuffer = true;
+ ssize_t nrd = read(fd, buffer, length);
+ if (nrd < 0) {
+ result = -errno;
+ ALOGE("Error reading file '%s', %s.", filename.string(), strerror(errno));
+ delete[] buffer;
+ buffer = NULL;
+ } else {
+ length = size_t(nrd);
+ }
+ }
+
+ if (!result) {
+ *outTokenizer = new Tokenizer(filename, fileMap, buffer, ownBuffer, length);
+ }
+ }
+ close(fd);
+ }
+ return result;
+}
+
+status_t Tokenizer::fromContents(const String8& filename,
+ const char* contents, Tokenizer** outTokenizer) {
+ *outTokenizer = new Tokenizer(filename, NULL,
+ const_cast<char*>(contents), false, strlen(contents));
+ return OK;
+}
+
+String8 Tokenizer::getLocation() const {
+ String8 result;
+ result.appendFormat("%s:%d", mFilename.string(), mLineNumber);
+ return result;
+}
+
+String8 Tokenizer::peekRemainderOfLine() const {
+ const char* end = getEnd();
+ const char* eol = mCurrent;
+ while (eol != end) {
+ char ch = *eol;
+ if (ch == '\n') {
+ break;
+ }
+ eol += 1;
+ }
+ return String8(mCurrent, eol - mCurrent);
+}
+
+String8 Tokenizer::nextToken(const char* delimiters) {
+#if DEBUG_TOKENIZER
+ ALOGD("nextToken");
+#endif
+ const char* end = getEnd();
+ const char* tokenStart = mCurrent;
+ while (mCurrent != end) {
+ char ch = *mCurrent;
+ if (ch == '\n' || isDelimiter(ch, delimiters)) {
+ break;
+ }
+ mCurrent += 1;
+ }
+ return String8(tokenStart, mCurrent - tokenStart);
+}
+
+void Tokenizer::nextLine() {
+#if DEBUG_TOKENIZER
+ ALOGD("nextLine");
+#endif
+ const char* end = getEnd();
+ while (mCurrent != end) {
+ char ch = *(mCurrent++);
+ if (ch == '\n') {
+ mLineNumber += 1;
+ break;
+ }
+ }
+}
+
+void Tokenizer::skipDelimiters(const char* delimiters) {
+#if DEBUG_TOKENIZER
+ ALOGD("skipDelimiters");
+#endif
+ const char* end = getEnd();
+ while (mCurrent != end) {
+ char ch = *mCurrent;
+ if (ch == '\n' || !isDelimiter(ch, delimiters)) {
+ break;
+ }
+ mCurrent += 1;
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/Tokenizer.h b/widget/gonk/libui/Tokenizer.h
new file mode 100644
index 0000000000..bb25f374cb
--- /dev/null
+++ b/widget/gonk/libui/Tokenizer.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _UTILS_TOKENIZER_H
+#define _UTILS_TOKENIZER_H
+
+#include <assert.h>
+#include <utils/Errors.h>
+#include <utils/FileMap.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/**
+ * A simple tokenizer for loading and parsing ASCII text files line by line.
+ */
+class Tokenizer {
+ Tokenizer(const String8& filename, FileMap* fileMap, char* buffer,
+ bool ownBuffer, size_t length);
+
+public:
+ ~Tokenizer();
+
+ /**
+ * Opens a file and maps it into memory.
+ *
+ * Returns NO_ERROR and a tokenizer for the file, if successful.
+ * Otherwise returns an error and sets outTokenizer to NULL.
+ */
+ static status_t open(const String8& filename, Tokenizer** outTokenizer);
+
+ /**
+ * Prepares to tokenize the contents of a string.
+ *
+ * Returns NO_ERROR and a tokenizer for the string, if successful.
+ * Otherwise returns an error and sets outTokenizer to NULL.
+ */
+ static status_t fromContents(const String8& filename,
+ const char* contents, Tokenizer** outTokenizer);
+
+ /**
+ * Returns true if at the end of the file.
+ */
+ inline bool isEof() const { return mCurrent == getEnd(); }
+
+ /**
+ * Returns true if at the end of the line or end of the file.
+ */
+ inline bool isEol() const { return isEof() || *mCurrent == '\n'; }
+
+ /**
+ * Gets the name of the file.
+ */
+ inline String8 getFilename() const { return mFilename; }
+
+ /**
+ * Gets a 1-based line number index for the current position.
+ */
+ inline int32_t getLineNumber() const { return mLineNumber; }
+
+ /**
+ * Formats a location string consisting of the filename and current line number.
+ * Returns a string like "MyFile.txt:33".
+ */
+ String8 getLocation() const;
+
+ /**
+ * Gets the character at the current position.
+ * Returns null at end of file.
+ */
+ inline char peekChar() const { return isEof() ? '\0' : *mCurrent; }
+
+ /**
+ * Gets the remainder of the current line as a string, excluding the newline character.
+ */
+ String8 peekRemainderOfLine() const;
+
+ /**
+ * Gets the character at the current position and advances past it.
+ * Returns null at end of file.
+ */
+ inline char nextChar() { return isEof() ? '\0' : *(mCurrent++); }
+
+ /**
+ * Gets the next token on this line stopping at the specified delimiters
+ * or the end of the line whichever comes first and advances past it.
+ * Also stops at embedded nulls.
+ * Returns the token or an empty string if the current character is a delimiter
+ * or is at the end of the line.
+ */
+ String8 nextToken(const char* delimiters);
+
+ /**
+ * Advances to the next line.
+ * Does nothing if already at the end of the file.
+ */
+ void nextLine();
+
+ /**
+ * Skips over the specified delimiters in the line.
+ * Also skips embedded nulls.
+ */
+ void skipDelimiters(const char* delimiters);
+
+private:
+ Tokenizer(const Tokenizer& other); // not copyable
+
+ String8 mFilename;
+ FileMap* mFileMap;
+ char* mBuffer;
+ bool mOwnBuffer;
+ size_t mLength;
+
+ const char* mCurrent;
+ int32_t mLineNumber;
+
+ inline const char* getEnd() const { return mBuffer + mLength; }
+
+};
+
+} // namespace android
+
+#endif // _UTILS_TOKENIZER_H
diff --git a/widget/gonk/libui/Trace.h b/widget/gonk/libui/Trace.h
new file mode 100644
index 0000000000..24fbfb6026
--- /dev/null
+++ b/widget/gonk/libui/Trace.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef ANDROID_TRACE_H
+#define ANDROID_TRACE_H
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <cutils/compiler.h>
+#include <utils/threads.h>
+#include "cutils_trace.h"
+
+// See <cutils/trace.h> for more ATRACE_* macros.
+
+// ATRACE_NAME traces the beginning and end of the current scope. To trace
+// the correct start and end times this macro should be declared first in the
+// scope body.
+#define ATRACE_NAME(name) android::ScopedTrace ___tracer(ATRACE_TAG, name)
+// ATRACE_CALL is an ATRACE_NAME that uses the current function name.
+#define ATRACE_CALL() ATRACE_NAME(__FUNCTION__)
+
+namespace android {
+
+class ScopedTrace {
+public:
+inline ScopedTrace(uint64_t tag, const char* name)
+ : mTag(tag) {
+#ifdef HAVE_ANDROID_OS
+ atrace_begin(mTag,name);
+#endif
+}
+
+inline ~ScopedTrace() {
+#ifdef HAVE_ANDROID_OS
+ atrace_end(mTag);
+#endif
+}
+
+private:
+ uint64_t mTag;
+};
+
+}; // namespace android
+
+#endif // ANDROID_TRACE_H
diff --git a/widget/gonk/libui/VelocityControl.cpp b/widget/gonk/libui/VelocityControl.cpp
new file mode 100644
index 0000000000..31365a220a
--- /dev/null
+++ b/widget/gonk/libui/VelocityControl.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "VelocityControl"
+//#define LOG_NDEBUG 0
+
+// Log debug messages about acceleration.
+#define DEBUG_ACCELERATION 0
+
+#include <math.h>
+#include <limits.h>
+
+#include "VelocityControl.h"
+#include <utils/BitSet.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+// --- VelocityControl ---
+
+const nsecs_t VelocityControl::STOP_TIME;
+
+VelocityControl::VelocityControl() {
+ reset();
+}
+
+void VelocityControl::setParameters(const VelocityControlParameters& parameters) {
+ mParameters = parameters;
+ reset();
+}
+
+void VelocityControl::reset() {
+ mLastMovementTime = LLONG_MIN;
+ mRawPosition.x = 0;
+ mRawPosition.y = 0;
+ mVelocityTracker.clear();
+}
+
+void VelocityControl::move(nsecs_t eventTime, float* deltaX, float* deltaY) {
+ if ((deltaX && *deltaX) || (deltaY && *deltaY)) {
+ if (eventTime >= mLastMovementTime + STOP_TIME) {
+#if DEBUG_ACCELERATION
+ ALOGD("VelocityControl: stopped, last movement was %0.3fms ago",
+ (eventTime - mLastMovementTime) * 0.000001f);
+#endif
+ reset();
+ }
+
+ mLastMovementTime = eventTime;
+ if (deltaX) {
+ mRawPosition.x += *deltaX;
+ }
+ if (deltaY) {
+ mRawPosition.y += *deltaY;
+ }
+ mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)), &mRawPosition);
+
+ float vx, vy;
+ float scale = mParameters.scale;
+ if (mVelocityTracker.getVelocity(0, &vx, &vy)) {
+ float speed = hypotf(vx, vy) * scale;
+ if (speed >= mParameters.highThreshold) {
+ // Apply full acceleration above the high speed threshold.
+ scale *= mParameters.acceleration;
+ } else if (speed > mParameters.lowThreshold) {
+ // Linearly interpolate the acceleration to apply between the low and high
+ // speed thresholds.
+ scale *= 1 + (speed - mParameters.lowThreshold)
+ / (mParameters.highThreshold - mParameters.lowThreshold)
+ * (mParameters.acceleration - 1);
+ }
+
+#if DEBUG_ACCELERATION
+ ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): "
+ "vx=%0.3f, vy=%0.3f, speed=%0.3f, accel=%0.3f",
+ mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
+ mParameters.acceleration,
+ vx, vy, speed, scale / mParameters.scale);
+#endif
+ } else {
+#if DEBUG_ACCELERATION
+ ALOGD("VelocityControl(%0.3f, %0.3f, %0.3f, %0.3f): unknown velocity",
+ mParameters.scale, mParameters.lowThreshold, mParameters.highThreshold,
+ mParameters.acceleration);
+#endif
+ }
+
+ if (deltaX) {
+ *deltaX *= scale;
+ }
+ if (deltaY) {
+ *deltaY *= scale;
+ }
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/VelocityControl.h b/widget/gonk/libui/VelocityControl.h
new file mode 100644
index 0000000000..8a2c695d6f
--- /dev/null
+++ b/widget/gonk/libui/VelocityControl.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_VELOCITY_CONTROL_H
+#define _ANDROIDFW_VELOCITY_CONTROL_H
+
+#include "Input.h"
+#include "VelocityTracker.h"
+#include <utils/Timers.h>
+
+namespace android {
+
+/*
+ * Specifies parameters that govern pointer or wheel acceleration.
+ */
+struct VelocityControlParameters {
+ // A scale factor that is multiplied with the raw velocity deltas
+ // prior to applying any other velocity control factors. The scale
+ // factor should be used to adapt the input device resolution
+ // (eg. counts per inch) to the output device resolution (eg. pixels per inch).
+ //
+ // Must be a positive value.
+ // Default is 1.0 (no scaling).
+ float scale;
+
+ // The scaled speed at which acceleration begins to be applied.
+ // This value establishes the upper bound of a low speed regime for
+ // small precise motions that are performed without any acceleration.
+ //
+ // Must be a non-negative value.
+ // Default is 0.0 (no low threshold).
+ float lowThreshold;
+
+ // The scaled speed at which maximum acceleration is applied.
+ // The difference between highThreshold and lowThreshold controls
+ // the range of speeds over which the acceleration factor is interpolated.
+ // The wider the range, the smoother the acceleration.
+ //
+ // Must be a non-negative value greater than or equal to lowThreshold.
+ // Default is 0.0 (no high threshold).
+ float highThreshold;
+
+ // The acceleration factor.
+ // When the speed is above the low speed threshold, the velocity will scaled
+ // by an interpolated value between 1.0 and this amount.
+ //
+ // Must be a positive greater than or equal to 1.0.
+ // Default is 1.0 (no acceleration).
+ float acceleration;
+
+ VelocityControlParameters() :
+ scale(1.0f), lowThreshold(0.0f), highThreshold(0.0f), acceleration(1.0f) {
+ }
+
+ VelocityControlParameters(float scale, float lowThreshold,
+ float highThreshold, float acceleration) :
+ scale(scale), lowThreshold(lowThreshold),
+ highThreshold(highThreshold), acceleration(acceleration) {
+ }
+};
+
+/*
+ * Implements mouse pointer and wheel speed control and acceleration.
+ */
+class VelocityControl {
+public:
+ VelocityControl();
+
+ /* Sets the various parameters. */
+ void setParameters(const VelocityControlParameters& parameters);
+
+ /* Resets the current movement counters to zero.
+ * This has the effect of nullifying any acceleration. */
+ void reset();
+
+ /* Translates a raw movement delta into an appropriately
+ * scaled / accelerated delta based on the current velocity. */
+ void move(nsecs_t eventTime, float* deltaX, float* deltaY);
+
+private:
+ // If no movements are received within this amount of time,
+ // we assume the movement has stopped and reset the movement counters.
+ static const nsecs_t STOP_TIME = 500 * 1000000; // 500 ms
+
+ VelocityControlParameters mParameters;
+
+ nsecs_t mLastMovementTime;
+ VelocityTracker::Position mRawPosition;
+ VelocityTracker mVelocityTracker;
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_VELOCITY_CONTROL_H
diff --git a/widget/gonk/libui/VelocityTracker.cpp b/widget/gonk/libui/VelocityTracker.cpp
new file mode 100644
index 0000000000..11a8bf7fc0
--- /dev/null
+++ b/widget/gonk/libui/VelocityTracker.cpp
@@ -0,0 +1,929 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "VelocityTracker"
+//#define LOG_NDEBUG 0
+#include "cutils_log.h"
+
+// Log debug messages about velocity tracking.
+#define DEBUG_VELOCITY 0
+
+// Log debug messages about the progress of the algorithm itself.
+#define DEBUG_STRATEGY 0
+
+#include <math.h>
+#include <limits.h>
+
+#include "VelocityTracker.h"
+#include <utils/BitSet.h>
+#include <utils/String8.h>
+#include <utils/Timers.h>
+
+#include <cutils/properties.h>
+
+namespace android {
+
+// Nanoseconds per milliseconds.
+static const nsecs_t NANOS_PER_MS = 1000000;
+
+// Threshold for determining that a pointer has stopped moving.
+// Some input devices do not send ACTION_MOVE events in the case where a pointer has
+// stopped. We need to detect this case so that we can accurately predict the
+// velocity after the pointer starts moving again.
+static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS;
+
+
+static float vectorDot(const float* a, const float* b, uint32_t m) {
+ float r = 0;
+ while (m--) {
+ r += *(a++) * *(b++);
+ }
+ return r;
+}
+
+static float vectorNorm(const float* a, uint32_t m) {
+ float r = 0;
+ while (m--) {
+ float t = *(a++);
+ r += t * t;
+ }
+ return sqrtf(r);
+}
+
+#if DEBUG_STRATEGY || DEBUG_VELOCITY
+static String8 vectorToString(const float* a, uint32_t m) {
+ String8 str;
+ str.append("[");
+ while (m--) {
+ str.appendFormat(" %f", *(a++));
+ if (m) {
+ str.append(",");
+ }
+ }
+ str.append(" ]");
+ return str;
+}
+
+static String8 matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
+ String8 str;
+ str.append("[");
+ for (size_t i = 0; i < m; i++) {
+ if (i) {
+ str.append(",");
+ }
+ str.append(" [");
+ for (size_t j = 0; j < n; j++) {
+ if (j) {
+ str.append(",");
+ }
+ str.appendFormat(" %f", a[rowMajor ? i * n + j : j * m + i]);
+ }
+ str.append(" ]");
+ }
+ str.append(" ]");
+ return str;
+}
+#endif
+
+
+// --- VelocityTracker ---
+
+// The default velocity tracker strategy.
+// Although other strategies are available for testing and comparison purposes,
+// this is the strategy that applications will actually use. Be very careful
+// when adjusting the default strategy because it can dramatically affect
+// (often in a bad way) the user experience.
+const char* VelocityTracker::DEFAULT_STRATEGY = "lsq2";
+
+VelocityTracker::VelocityTracker(const char* strategy) :
+ mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) {
+ char value[PROPERTY_VALUE_MAX];
+
+ // Allow the default strategy to be overridden using a system property for debugging.
+ if (!strategy) {
+ int length = property_get("debug.velocitytracker.strategy", value, NULL);
+ if (length > 0) {
+ strategy = value;
+ } else {
+ strategy = DEFAULT_STRATEGY;
+ }
+ }
+
+ // Configure the strategy.
+ if (!configureStrategy(strategy)) {
+ ALOGD("Unrecognized velocity tracker strategy name '%s'.", strategy);
+ if (!configureStrategy(DEFAULT_STRATEGY)) {
+ LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%s'!",
+ strategy);
+ }
+ }
+}
+
+VelocityTracker::~VelocityTracker() {
+ delete mStrategy;
+}
+
+bool VelocityTracker::configureStrategy(const char* strategy) {
+ mStrategy = createStrategy(strategy);
+ return mStrategy != NULL;
+}
+
+VelocityTrackerStrategy* VelocityTracker::createStrategy(const char* strategy) {
+ if (!strcmp("lsq1", strategy)) {
+ // 1st order least squares. Quality: POOR.
+ // Frequently underfits the touch data especially when the finger accelerates
+ // or changes direction. Often underestimates velocity. The direction
+ // is overly influenced by historical touch points.
+ return new LeastSquaresVelocityTrackerStrategy(1);
+ }
+ if (!strcmp("lsq2", strategy)) {
+ // 2nd order least squares. Quality: VERY GOOD.
+ // Pretty much ideal, but can be confused by certain kinds of touch data,
+ // particularly if the panel has a tendency to generate delayed,
+ // duplicate or jittery touch coordinates when the finger is released.
+ return new LeastSquaresVelocityTrackerStrategy(2);
+ }
+ if (!strcmp("lsq3", strategy)) {
+ // 3rd order least squares. Quality: UNUSABLE.
+ // Frequently overfits the touch data yielding wildly divergent estimates
+ // of the velocity when the finger is released.
+ return new LeastSquaresVelocityTrackerStrategy(3);
+ }
+ if (!strcmp("wlsq2-delta", strategy)) {
+ // 2nd order weighted least squares, delta weighting. Quality: EXPERIMENTAL
+ return new LeastSquaresVelocityTrackerStrategy(2,
+ LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA);
+ }
+ if (!strcmp("wlsq2-central", strategy)) {
+ // 2nd order weighted least squares, central weighting. Quality: EXPERIMENTAL
+ return new LeastSquaresVelocityTrackerStrategy(2,
+ LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL);
+ }
+ if (!strcmp("wlsq2-recent", strategy)) {
+ // 2nd order weighted least squares, recent weighting. Quality: EXPERIMENTAL
+ return new LeastSquaresVelocityTrackerStrategy(2,
+ LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT);
+ }
+ if (!strcmp("int1", strategy)) {
+ // 1st order integrating filter. Quality: GOOD.
+ // Not as good as 'lsq2' because it cannot estimate acceleration but it is
+ // more tolerant of errors. Like 'lsq1', this strategy tends to underestimate
+ // the velocity of a fling but this strategy tends to respond to changes in
+ // direction more quickly and accurately.
+ return new IntegratingVelocityTrackerStrategy(1);
+ }
+ if (!strcmp("int2", strategy)) {
+ // 2nd order integrating filter. Quality: EXPERIMENTAL.
+ // For comparison purposes only. Unlike 'int1' this strategy can compensate
+ // for acceleration but it typically overestimates the effect.
+ return new IntegratingVelocityTrackerStrategy(2);
+ }
+ if (!strcmp("legacy", strategy)) {
+ // Legacy velocity tracker algorithm. Quality: POOR.
+ // For comparison purposes only. This algorithm is strongly influenced by
+ // old data points, consistently underestimates velocity and takes a very long
+ // time to adjust to changes in direction.
+ return new LegacyVelocityTrackerStrategy();
+ }
+ return NULL;
+}
+
+void VelocityTracker::clear() {
+ mCurrentPointerIdBits.clear();
+ mActivePointerId = -1;
+
+ mStrategy->clear();
+}
+
+void VelocityTracker::clearPointers(BitSet32 idBits) {
+ BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value);
+ mCurrentPointerIdBits = remainingIdBits;
+
+ if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
+ mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
+ }
+
+ mStrategy->clearPointers(idBits);
+}
+
+void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) {
+ while (idBits.count() > MAX_POINTERS) {
+ idBits.clearLastMarkedBit();
+ }
+
+ if ((mCurrentPointerIdBits.value & idBits.value)
+ && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) {
+#if DEBUG_VELOCITY
+ ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.",
+ (eventTime - mLastEventTime) * 0.000001f);
+#endif
+ // We have not received any movements for too long. Assume that all pointers
+ // have stopped.
+ mStrategy->clear();
+ }
+ mLastEventTime = eventTime;
+
+ mCurrentPointerIdBits = idBits;
+ if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
+ mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit();
+ }
+
+ mStrategy->addMovement(eventTime, idBits, positions);
+
+#if DEBUG_VELOCITY
+ ALOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x, activePointerId=%d",
+ eventTime, idBits.value, mActivePointerId);
+ for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
+ uint32_t id = iterBits.firstMarkedBit();
+ uint32_t index = idBits.getIndexOfBit(id);
+ iterBits.clearBit(id);
+ Estimator estimator;
+ getEstimator(id, &estimator);
+ ALOGD(" %d: position (%0.3f, %0.3f), "
+ "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)",
+ id, positions[index].x, positions[index].y,
+ int(estimator.degree),
+ vectorToString(estimator.xCoeff, estimator.degree + 1).string(),
+ vectorToString(estimator.yCoeff, estimator.degree + 1).string(),
+ estimator.confidence);
+ }
+#endif
+}
+
+void VelocityTracker::addMovement(const MotionEvent* event) {
+ int32_t actionMasked = event->getActionMasked();
+
+ switch (actionMasked) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ // Clear all pointers on down before adding the new movement.
+ clear();
+ break;
+ case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+ // Start a new movement trace for a pointer that just went down.
+ // We do this on down instead of on up because the client may want to query the
+ // final velocity for a pointer that just went up.
+ BitSet32 downIdBits;
+ downIdBits.markBit(event->getPointerId(event->getActionIndex()));
+ clearPointers(downIdBits);
+ break;
+ }
+ case AMOTION_EVENT_ACTION_MOVE:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ break;
+ default:
+ // Ignore all other actions because they do not convey any new information about
+ // pointer movement. We also want to preserve the last known velocity of the pointers.
+ // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
+ // of the pointers that went up. ACTION_POINTER_UP does include the new position of
+ // pointers that remained down but we will also receive an ACTION_MOVE with this
+ // information if any of them actually moved. Since we don't know how many pointers
+ // will be going up at once it makes sense to just wait for the following ACTION_MOVE
+ // before adding the movement.
+ return;
+ }
+
+ size_t pointerCount = event->getPointerCount();
+ if (pointerCount > MAX_POINTERS) {
+ pointerCount = MAX_POINTERS;
+ }
+
+ BitSet32 idBits;
+ for (size_t i = 0; i < pointerCount; i++) {
+ idBits.markBit(event->getPointerId(i));
+ }
+
+ uint32_t pointerIndex[MAX_POINTERS];
+ for (size_t i = 0; i < pointerCount; i++) {
+ pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
+ }
+
+ nsecs_t eventTime;
+ Position positions[pointerCount];
+
+ size_t historySize = event->getHistorySize();
+ for (size_t h = 0; h < historySize; h++) {
+ eventTime = event->getHistoricalEventTime(h);
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t index = pointerIndex[i];
+ positions[index].x = event->getHistoricalX(i, h);
+ positions[index].y = event->getHistoricalY(i, h);
+ }
+ addMovement(eventTime, idBits, positions);
+ }
+
+ eventTime = event->getEventTime();
+ for (size_t i = 0; i < pointerCount; i++) {
+ uint32_t index = pointerIndex[i];
+ positions[index].x = event->getX(i);
+ positions[index].y = event->getY(i);
+ }
+ addMovement(eventTime, idBits, positions);
+}
+
+bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
+ Estimator estimator;
+ if (getEstimator(id, &estimator) && estimator.degree >= 1) {
+ *outVx = estimator.xCoeff[1];
+ *outVy = estimator.yCoeff[1];
+ return true;
+ }
+ *outVx = 0;
+ *outVy = 0;
+ return false;
+}
+
+bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const {
+ return mStrategy->getEstimator(id, outEstimator);
+}
+
+
+// --- LeastSquaresVelocityTrackerStrategy ---
+
+const nsecs_t LeastSquaresVelocityTrackerStrategy::HORIZON;
+const uint32_t LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE;
+
+LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
+ uint32_t degree, Weighting weighting) :
+ mDegree(degree), mWeighting(weighting) {
+ clear();
+}
+
+LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
+}
+
+void LeastSquaresVelocityTrackerStrategy::clear() {
+ mIndex = 0;
+ mMovements[0].idBits.clear();
+}
+
+void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
+ BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
+ mMovements[mIndex].idBits = remainingIdBits;
+}
+
+void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions) {
+ if (++mIndex == HISTORY_SIZE) {
+ mIndex = 0;
+ }
+
+ Movement& movement = mMovements[mIndex];
+ movement.eventTime = eventTime;
+ movement.idBits = idBits;
+ uint32_t count = idBits.count();
+ for (uint32_t i = 0; i < count; i++) {
+ movement.positions[i] = positions[i];
+ }
+}
+
+/**
+ * Solves a linear least squares problem to obtain a N degree polynomial that fits
+ * the specified input data as nearly as possible.
+ *
+ * Returns true if a solution is found, false otherwise.
+ *
+ * The input consists of two vectors of data points X and Y with indices 0..m-1
+ * along with a weight vector W of the same size.
+ *
+ * The output is a vector B with indices 0..n that describes a polynomial
+ * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i]
+ * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized.
+ *
+ * Accordingly, the weight vector W should be initialized by the caller with the
+ * reciprocal square root of the variance of the error in each input data point.
+ * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]).
+ * The weights express the relative importance of each data point. If the weights are
+ * all 1, then the data points are considered to be of equal importance when fitting
+ * the polynomial. It is a good idea to choose weights that diminish the importance
+ * of data points that may have higher than usual error margins.
+ *
+ * Errors among data points are assumed to be independent. W is represented here
+ * as a vector although in the literature it is typically taken to be a diagonal matrix.
+ *
+ * That is to say, the function that generated the input data can be approximated
+ * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
+ *
+ * The coefficient of determination (R^2) is also returned to describe the goodness
+ * of fit of the model for the given data. It is a value between 0 and 1, where 1
+ * indicates perfect correspondence.
+ *
+ * This function first expands the X vector to a m by n matrix A such that
+ * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
+ * multiplies it by w[i]./
+ *
+ * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
+ * and an m by n upper triangular matrix R. Because R is upper triangular (lower
+ * part is all zeroes), we can simplify the decomposition into an m by n matrix
+ * Q1 and a n by n matrix R1 such that A = Q1 R1.
+ *
+ * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y)
+ * to find B.
+ *
+ * For efficiency, we lay out A and Q column-wise in memory because we frequently
+ * operate on the column vectors. Conversely, we lay out R row-wise.
+ *
+ * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
+ * http://en.wikipedia.org/wiki/Gram-Schmidt
+ */
+static bool solveLeastSquares(const float* x, const float* y,
+ const float* w, uint32_t m, uint32_t n, float* outB, float* outDet) {
+#if DEBUG_STRATEGY
+ ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
+ vectorToString(x, m).string(), vectorToString(y, m).string(),
+ vectorToString(w, m).string());
+#endif
+
+ // Expand the X vector to a matrix A, pre-multiplied by the weights.
+ float a[n][m]; // column-major order
+ for (uint32_t h = 0; h < m; h++) {
+ a[0][h] = w[h];
+ for (uint32_t i = 1; i < n; i++) {
+ a[i][h] = a[i - 1][h] * x[h];
+ }
+ }
+#if DEBUG_STRATEGY
+ ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).string());
+#endif
+
+ // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
+ float q[n][m]; // orthonormal basis, column-major order
+ float r[n][n]; // upper triangular matrix, row-major order
+ for (uint32_t j = 0; j < n; j++) {
+ for (uint32_t h = 0; h < m; h++) {
+ q[j][h] = a[j][h];
+ }
+ for (uint32_t i = 0; i < j; i++) {
+ float dot = vectorDot(&q[j][0], &q[i][0], m);
+ for (uint32_t h = 0; h < m; h++) {
+ q[j][h] -= dot * q[i][h];
+ }
+ }
+
+ float norm = vectorNorm(&q[j][0], m);
+ if (norm < 0.000001f) {
+ // vectors are linearly dependent or zero so no solution
+#if DEBUG_STRATEGY
+ ALOGD(" - no solution, norm=%f", norm);
+#endif
+ return false;
+ }
+
+ float invNorm = 1.0f / norm;
+ for (uint32_t h = 0; h < m; h++) {
+ q[j][h] *= invNorm;
+ }
+ for (uint32_t i = 0; i < n; i++) {
+ r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
+ }
+ }
+#if DEBUG_STRATEGY
+ ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).string());
+ ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).string());
+
+ // calculate QR, if we factored A correctly then QR should equal A
+ float qr[n][m];
+ for (uint32_t h = 0; h < m; h++) {
+ for (uint32_t i = 0; i < n; i++) {
+ qr[i][h] = 0;
+ for (uint32_t j = 0; j < n; j++) {
+ qr[i][h] += q[j][h] * r[j][i];
+ }
+ }
+ }
+ ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).string());
+#endif
+
+ // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
+ // We just work from bottom-right to top-left calculating B's coefficients.
+ float wy[m];
+ for (uint32_t h = 0; h < m; h++) {
+ wy[h] = y[h] * w[h];
+ }
+ for (uint32_t i = n; i-- != 0; ) {
+ outB[i] = vectorDot(&q[i][0], wy, m);
+ for (uint32_t j = n - 1; j > i; j--) {
+ outB[i] -= r[i][j] * outB[j];
+ }
+ outB[i] /= r[i][i];
+ }
+#if DEBUG_STRATEGY
+ ALOGD(" - b=%s", vectorToString(outB, n).string());
+#endif
+
+ // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
+ // SSerr is the residual sum of squares (variance of the error),
+ // and SStot is the total sum of squares (variance of the data) where each
+ // has been weighted.
+ float ymean = 0;
+ for (uint32_t h = 0; h < m; h++) {
+ ymean += y[h];
+ }
+ ymean /= m;
+
+ float sserr = 0;
+ float sstot = 0;
+ for (uint32_t h = 0; h < m; h++) {
+ float err = y[h] - outB[0];
+ float term = 1;
+ for (uint32_t i = 1; i < n; i++) {
+ term *= x[h];
+ err -= term * outB[i];
+ }
+ sserr += w[h] * w[h] * err * err;
+ float var = y[h] - ymean;
+ sstot += w[h] * w[h] * var * var;
+ }
+ *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
+#if DEBUG_STRATEGY
+ ALOGD(" - sserr=%f", sserr);
+ ALOGD(" - sstot=%f", sstot);
+ ALOGD(" - det=%f", *outDet);
+#endif
+ return true;
+}
+
+bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
+ VelocityTracker::Estimator* outEstimator) const {
+ outEstimator->clear();
+
+ // Iterate over movement samples in reverse time order and collect samples.
+ float x[HISTORY_SIZE];
+ float y[HISTORY_SIZE];
+ float w[HISTORY_SIZE];
+ float time[HISTORY_SIZE];
+ uint32_t m = 0;
+ uint32_t index = mIndex;
+ const Movement& newestMovement = mMovements[mIndex];
+ do {
+ const Movement& movement = mMovements[index];
+ if (!movement.idBits.hasBit(id)) {
+ break;
+ }
+
+ nsecs_t age = newestMovement.eventTime - movement.eventTime;
+ if (age > HORIZON) {
+ break;
+ }
+
+ const VelocityTracker::Position& position = movement.getPosition(id);
+ x[m] = position.x;
+ y[m] = position.y;
+ w[m] = chooseWeight(index);
+ time[m] = -age * 0.000000001f;
+ index = (index == 0 ? HISTORY_SIZE : index) - 1;
+ } while (++m < HISTORY_SIZE);
+
+ if (m == 0) {
+ return false; // no data
+ }
+
+ // Calculate a least squares polynomial fit.
+ uint32_t degree = mDegree;
+ if (degree > m - 1) {
+ degree = m - 1;
+ }
+ if (degree >= 1) {
+ float xdet, ydet;
+ uint32_t n = degree + 1;
+ if (solveLeastSquares(time, x, w, m, n, outEstimator->xCoeff, &xdet)
+ && solveLeastSquares(time, y, w, m, n, outEstimator->yCoeff, &ydet)) {
+ outEstimator->time = newestMovement.eventTime;
+ outEstimator->degree = degree;
+ outEstimator->confidence = xdet * ydet;
+#if DEBUG_STRATEGY
+ ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f",
+ int(outEstimator->degree),
+ vectorToString(outEstimator->xCoeff, n).string(),
+ vectorToString(outEstimator->yCoeff, n).string(),
+ outEstimator->confidence);
+#endif
+ return true;
+ }
+ }
+
+ // No velocity data available for this pointer, but we do have its current position.
+ outEstimator->xCoeff[0] = x[0];
+ outEstimator->yCoeff[0] = y[0];
+ outEstimator->time = newestMovement.eventTime;
+ outEstimator->degree = 0;
+ outEstimator->confidence = 1;
+ return true;
+}
+
+float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const {
+ switch (mWeighting) {
+ case WEIGHTING_DELTA: {
+ // Weight points based on how much time elapsed between them and the next
+ // point so that points that "cover" a shorter time span are weighed less.
+ // delta 0ms: 0.5
+ // delta 10ms: 1.0
+ if (index == mIndex) {
+ return 1.0f;
+ }
+ uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
+ float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime)
+ * 0.000001f;
+ if (deltaMillis < 0) {
+ return 0.5f;
+ }
+ if (deltaMillis < 10) {
+ return 0.5f + deltaMillis * 0.05;
+ }
+ return 1.0f;
+ }
+
+ case WEIGHTING_CENTRAL: {
+ // Weight points based on their age, weighing very recent and very old points less.
+ // age 0ms: 0.5
+ // age 10ms: 1.0
+ // age 50ms: 1.0
+ // age 60ms: 0.5
+ float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
+ * 0.000001f;
+ if (ageMillis < 0) {
+ return 0.5f;
+ }
+ if (ageMillis < 10) {
+ return 0.5f + ageMillis * 0.05;
+ }
+ if (ageMillis < 50) {
+ return 1.0f;
+ }
+ if (ageMillis < 60) {
+ return 0.5f + (60 - ageMillis) * 0.05;
+ }
+ return 0.5f;
+ }
+
+ case WEIGHTING_RECENT: {
+ // Weight points based on their age, weighing older points less.
+ // age 0ms: 1.0
+ // age 50ms: 1.0
+ // age 100ms: 0.5
+ float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
+ * 0.000001f;
+ if (ageMillis < 50) {
+ return 1.0f;
+ }
+ if (ageMillis < 100) {
+ return 0.5f + (100 - ageMillis) * 0.01f;
+ }
+ return 0.5f;
+ }
+
+ case WEIGHTING_NONE:
+ default:
+ return 1.0f;
+ }
+}
+
+
+// --- IntegratingVelocityTrackerStrategy ---
+
+IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
+ mDegree(degree) {
+}
+
+IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
+}
+
+void IntegratingVelocityTrackerStrategy::clear() {
+ mPointerIdBits.clear();
+}
+
+void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
+ mPointerIdBits.value &= ~idBits.value;
+}
+
+void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions) {
+ uint32_t index = 0;
+ for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) {
+ uint32_t id = iterIdBits.clearFirstMarkedBit();
+ State& state = mPointerState[id];
+ const VelocityTracker::Position& position = positions[index++];
+ if (mPointerIdBits.hasBit(id)) {
+ updateState(state, eventTime, position.x, position.y);
+ } else {
+ initState(state, eventTime, position.x, position.y);
+ }
+ }
+
+ mPointerIdBits = idBits;
+}
+
+bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id,
+ VelocityTracker::Estimator* outEstimator) const {
+ outEstimator->clear();
+
+ if (mPointerIdBits.hasBit(id)) {
+ const State& state = mPointerState[id];
+ populateEstimator(state, outEstimator);
+ return true;
+ }
+
+ return false;
+}
+
+void IntegratingVelocityTrackerStrategy::initState(State& state,
+ nsecs_t eventTime, float xpos, float ypos) const {
+ state.updateTime = eventTime;
+ state.degree = 0;
+
+ state.xpos = xpos;
+ state.xvel = 0;
+ state.xaccel = 0;
+ state.ypos = ypos;
+ state.yvel = 0;
+ state.yaccel = 0;
+}
+
+void IntegratingVelocityTrackerStrategy::updateState(State& state,
+ nsecs_t eventTime, float xpos, float ypos) const {
+ const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS;
+ const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
+
+ if (eventTime <= state.updateTime + MIN_TIME_DELTA) {
+ return;
+ }
+
+ float dt = (eventTime - state.updateTime) * 0.000000001f;
+ state.updateTime = eventTime;
+
+ float xvel = (xpos - state.xpos) / dt;
+ float yvel = (ypos - state.ypos) / dt;
+ if (state.degree == 0) {
+ state.xvel = xvel;
+ state.yvel = yvel;
+ state.degree = 1;
+ } else {
+ float alpha = dt / (FILTER_TIME_CONSTANT + dt);
+ if (mDegree == 1) {
+ state.xvel += (xvel - state.xvel) * alpha;
+ state.yvel += (yvel - state.yvel) * alpha;
+ } else {
+ float xaccel = (xvel - state.xvel) / dt;
+ float yaccel = (yvel - state.yvel) / dt;
+ if (state.degree == 1) {
+ state.xaccel = xaccel;
+ state.yaccel = yaccel;
+ state.degree = 2;
+ } else {
+ state.xaccel += (xaccel - state.xaccel) * alpha;
+ state.yaccel += (yaccel - state.yaccel) * alpha;
+ }
+ state.xvel += (state.xaccel * dt) * alpha;
+ state.yvel += (state.yaccel * dt) * alpha;
+ }
+ }
+ state.xpos = xpos;
+ state.ypos = ypos;
+}
+
+void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state,
+ VelocityTracker::Estimator* outEstimator) const {
+ outEstimator->time = state.updateTime;
+ outEstimator->confidence = 1.0f;
+ outEstimator->degree = state.degree;
+ outEstimator->xCoeff[0] = state.xpos;
+ outEstimator->xCoeff[1] = state.xvel;
+ outEstimator->xCoeff[2] = state.xaccel / 2;
+ outEstimator->yCoeff[0] = state.ypos;
+ outEstimator->yCoeff[1] = state.yvel;
+ outEstimator->yCoeff[2] = state.yaccel / 2;
+}
+
+
+// --- LegacyVelocityTrackerStrategy ---
+
+const nsecs_t LegacyVelocityTrackerStrategy::HORIZON;
+const uint32_t LegacyVelocityTrackerStrategy::HISTORY_SIZE;
+const nsecs_t LegacyVelocityTrackerStrategy::MIN_DURATION;
+
+LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {
+ clear();
+}
+
+LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
+}
+
+void LegacyVelocityTrackerStrategy::clear() {
+ mIndex = 0;
+ mMovements[0].idBits.clear();
+}
+
+void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
+ BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
+ mMovements[mIndex].idBits = remainingIdBits;
+}
+
+void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions) {
+ if (++mIndex == HISTORY_SIZE) {
+ mIndex = 0;
+ }
+
+ Movement& movement = mMovements[mIndex];
+ movement.eventTime = eventTime;
+ movement.idBits = idBits;
+ uint32_t count = idBits.count();
+ for (uint32_t i = 0; i < count; i++) {
+ movement.positions[i] = positions[i];
+ }
+}
+
+bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id,
+ VelocityTracker::Estimator* outEstimator) const {
+ outEstimator->clear();
+
+ const Movement& newestMovement = mMovements[mIndex];
+ if (!newestMovement.idBits.hasBit(id)) {
+ return false; // no data
+ }
+
+ // Find the oldest sample that contains the pointer and that is not older than HORIZON.
+ nsecs_t minTime = newestMovement.eventTime - HORIZON;
+ uint32_t oldestIndex = mIndex;
+ uint32_t numTouches = 1;
+ do {
+ uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
+ const Movement& nextOldestMovement = mMovements[nextOldestIndex];
+ if (!nextOldestMovement.idBits.hasBit(id)
+ || nextOldestMovement.eventTime < minTime) {
+ break;
+ }
+ oldestIndex = nextOldestIndex;
+ } while (++numTouches < HISTORY_SIZE);
+
+ // Calculate an exponentially weighted moving average of the velocity estimate
+ // at different points in time measured relative to the oldest sample.
+ // This is essentially an IIR filter. Newer samples are weighted more heavily
+ // than older samples. Samples at equal time points are weighted more or less
+ // equally.
+ //
+ // One tricky problem is that the sample data may be poorly conditioned.
+ // Sometimes samples arrive very close together in time which can cause us to
+ // overestimate the velocity at that time point. Most samples might be measured
+ // 16ms apart but some consecutive samples could be only 0.5sm apart because
+ // the hardware or driver reports them irregularly or in bursts.
+ float accumVx = 0;
+ float accumVy = 0;
+ uint32_t index = oldestIndex;
+ uint32_t samplesUsed = 0;
+ const Movement& oldestMovement = mMovements[oldestIndex];
+ const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id);
+ nsecs_t lastDuration = 0;
+
+ while (numTouches-- > 1) {
+ if (++index == HISTORY_SIZE) {
+ index = 0;
+ }
+ const Movement& movement = mMovements[index];
+ nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
+
+ // If the duration between samples is small, we may significantly overestimate
+ // the velocity. Consequently, we impose a minimum duration constraint on the
+ // samples that we include in the calculation.
+ if (duration >= MIN_DURATION) {
+ const VelocityTracker::Position& position = movement.getPosition(id);
+ float scale = 1000000000.0f / duration; // one over time delta in seconds
+ float vx = (position.x - oldestPosition.x) * scale;
+ float vy = (position.y - oldestPosition.y) * scale;
+ accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
+ accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
+ lastDuration = duration;
+ samplesUsed += 1;
+ }
+ }
+
+ // Report velocity.
+ const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id);
+ outEstimator->time = newestMovement.eventTime;
+ outEstimator->confidence = 1;
+ outEstimator->xCoeff[0] = newestPosition.x;
+ outEstimator->yCoeff[0] = newestPosition.y;
+ if (samplesUsed) {
+ outEstimator->xCoeff[1] = accumVx;
+ outEstimator->yCoeff[1] = accumVy;
+ outEstimator->degree = 1;
+ } else {
+ outEstimator->degree = 0;
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/VelocityTracker.h b/widget/gonk/libui/VelocityTracker.h
new file mode 100644
index 0000000000..fd077d4385
--- /dev/null
+++ b/widget/gonk/libui/VelocityTracker.h
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_VELOCITY_TRACKER_H
+#define _ANDROIDFW_VELOCITY_TRACKER_H
+
+#include "Input.h"
+#include <utils/Timers.h>
+#include <utils/BitSet.h>
+
+namespace android {
+
+class VelocityTrackerStrategy;
+
+/*
+ * Calculates the velocity of pointer movements over time.
+ */
+class VelocityTracker {
+public:
+ struct Position {
+ float x, y;
+ };
+
+ struct Estimator {
+ static const size_t MAX_DEGREE = 4;
+
+ // Estimator time base.
+ nsecs_t time;
+
+ // Polynomial coefficients describing motion in X and Y.
+ float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1];
+
+ // Polynomial degree (number of coefficients), or zero if no information is
+ // available.
+ uint32_t degree;
+
+ // Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
+ float confidence;
+
+ inline void clear() {
+ time = 0;
+ degree = 0;
+ confidence = 0;
+ for (size_t i = 0; i <= MAX_DEGREE; i++) {
+ xCoeff[i] = 0;
+ yCoeff[i] = 0;
+ }
+ }
+ };
+
+ // Creates a velocity tracker using the specified strategy.
+ // If strategy is NULL, uses the default strategy for the platform.
+ VelocityTracker(const char* strategy = NULL);
+
+ ~VelocityTracker();
+
+ // Resets the velocity tracker state.
+ void clear();
+
+ // Resets the velocity tracker state for specific pointers.
+ // Call this method when some pointers have changed and may be reusing
+ // an id that was assigned to a different pointer earlier.
+ void clearPointers(BitSet32 idBits);
+
+ // Adds movement information for a set of pointers.
+ // The idBits bitfield specifies the pointer ids of the pointers whose positions
+ // are included in the movement.
+ // The positions array contains position information for each pointer in order by
+ // increasing id. Its size should be equal to the number of one bits in idBits.
+ void addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions);
+
+ // Adds movement information for all pointers in a MotionEvent, including historical samples.
+ void addMovement(const MotionEvent* event);
+
+ // Gets the velocity of the specified pointer id in position units per second.
+ // Returns false and sets the velocity components to zero if there is
+ // insufficient movement information for the pointer.
+ bool getVelocity(uint32_t id, float* outVx, float* outVy) const;
+
+ // Gets an estimator for the recent movements of the specified pointer id.
+ // Returns false and clears the estimator if there is no information available
+ // about the pointer.
+ bool getEstimator(uint32_t id, Estimator* outEstimator) const;
+
+ // Gets the active pointer id, or -1 if none.
+ inline int32_t getActivePointerId() const { return mActivePointerId; }
+
+ // Gets a bitset containing all pointer ids from the most recent movement.
+ inline BitSet32 getCurrentPointerIdBits() const { return mCurrentPointerIdBits; }
+
+private:
+ static const char* DEFAULT_STRATEGY;
+
+ nsecs_t mLastEventTime;
+ BitSet32 mCurrentPointerIdBits;
+ int32_t mActivePointerId;
+ VelocityTrackerStrategy* mStrategy;
+
+ bool configureStrategy(const char* strategy);
+
+ static VelocityTrackerStrategy* createStrategy(const char* strategy);
+};
+
+
+/*
+ * Implements a particular velocity tracker algorithm.
+ */
+class VelocityTrackerStrategy {
+protected:
+ VelocityTrackerStrategy() { }
+
+public:
+ virtual ~VelocityTrackerStrategy() { }
+
+ virtual void clear() = 0;
+ virtual void clearPointers(BitSet32 idBits) = 0;
+ virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions) = 0;
+ virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0;
+};
+
+
+/*
+ * Velocity tracker algorithm based on least-squares linear regression.
+ */
+class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
+public:
+ enum Weighting {
+ // No weights applied. All data points are equally reliable.
+ WEIGHTING_NONE,
+
+ // Weight by time delta. Data points clustered together are weighted less.
+ WEIGHTING_DELTA,
+
+ // Weight such that points within a certain horizon are weighed more than those
+ // outside of that horizon.
+ WEIGHTING_CENTRAL,
+
+ // Weight such that points older than a certain amount are weighed less.
+ WEIGHTING_RECENT,
+ };
+
+ // Degree must be no greater than Estimator::MAX_DEGREE.
+ LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = WEIGHTING_NONE);
+ virtual ~LeastSquaresVelocityTrackerStrategy();
+
+ virtual void clear();
+ virtual void clearPointers(BitSet32 idBits);
+ virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions);
+ virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+
+private:
+ // Sample horizon.
+ // We don't use too much history by default since we want to react to quick
+ // changes in direction.
+ static const nsecs_t HORIZON = 100 * 1000000; // 100 ms
+
+ // Number of samples to keep.
+ static const uint32_t HISTORY_SIZE = 20;
+
+ struct Movement {
+ nsecs_t eventTime;
+ BitSet32 idBits;
+ VelocityTracker::Position positions[MAX_POINTERS];
+
+ inline const VelocityTracker::Position& getPosition(uint32_t id) const {
+ return positions[idBits.getIndexOfBit(id)];
+ }
+ };
+
+ float chooseWeight(uint32_t index) const;
+
+ const uint32_t mDegree;
+ const Weighting mWeighting;
+ uint32_t mIndex;
+ Movement mMovements[HISTORY_SIZE];
+};
+
+
+/*
+ * Velocity tracker algorithm that uses an IIR filter.
+ */
+class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy {
+public:
+ // Degree must be 1 or 2.
+ IntegratingVelocityTrackerStrategy(uint32_t degree);
+ ~IntegratingVelocityTrackerStrategy();
+
+ virtual void clear();
+ virtual void clearPointers(BitSet32 idBits);
+ virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions);
+ virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+
+private:
+ // Current state estimate for a particular pointer.
+ struct State {
+ nsecs_t updateTime;
+ uint32_t degree;
+
+ float xpos, xvel, xaccel;
+ float ypos, yvel, yaccel;
+ };
+
+ const uint32_t mDegree;
+ BitSet32 mPointerIdBits;
+ State mPointerState[MAX_POINTER_ID + 1];
+
+ void initState(State& state, nsecs_t eventTime, float xpos, float ypos) const;
+ void updateState(State& state, nsecs_t eventTime, float xpos, float ypos) const;
+ void populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const;
+};
+
+
+/*
+ * Velocity tracker strategy used prior to ICS.
+ */
+class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy {
+public:
+ LegacyVelocityTrackerStrategy();
+ virtual ~LegacyVelocityTrackerStrategy();
+
+ virtual void clear();
+ virtual void clearPointers(BitSet32 idBits);
+ virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
+ const VelocityTracker::Position* positions);
+ virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+
+private:
+ // Oldest sample to consider when calculating the velocity.
+ static const nsecs_t HORIZON = 200 * 1000000; // 100 ms
+
+ // Number of samples to keep.
+ static const uint32_t HISTORY_SIZE = 20;
+
+ // The minimum duration between samples when estimating velocity.
+ static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
+
+ struct Movement {
+ nsecs_t eventTime;
+ BitSet32 idBits;
+ VelocityTracker::Position positions[MAX_POINTERS];
+
+ inline const VelocityTracker::Position& getPosition(uint32_t id) const {
+ return positions[idBits.getIndexOfBit(id)];
+ }
+ };
+
+ uint32_t mIndex;
+ Movement mMovements[HISTORY_SIZE];
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_VELOCITY_TRACKER_H
diff --git a/widget/gonk/libui/VirtualKeyMap.cpp b/widget/gonk/libui/VirtualKeyMap.cpp
new file mode 100644
index 0000000000..444ab37187
--- /dev/null
+++ b/widget/gonk/libui/VirtualKeyMap.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define LOG_TAG "VirtualKeyMap"
+#include "cutils_log.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include "VirtualKeyMap.h"
+#include <utils/Errors.h>
+#include "Tokenizer.h"
+#include <utils/Timers.h>
+
+// Enables debug output for the parser.
+#define DEBUG_PARSER 0
+
+// Enables debug output for parser performance.
+#define DEBUG_PARSER_PERFORMANCE 0
+
+
+namespace android {
+
+static const char* WHITESPACE = " \t\r";
+static const char* WHITESPACE_OR_FIELD_DELIMITER = " \t\r:";
+
+
+// --- VirtualKeyMap ---
+
+VirtualKeyMap::VirtualKeyMap() {
+}
+
+VirtualKeyMap::~VirtualKeyMap() {
+}
+
+status_t VirtualKeyMap::load(const String8& filename, VirtualKeyMap** outMap) {
+ *outMap = NULL;
+
+ Tokenizer* tokenizer;
+ status_t status = Tokenizer::open(filename, &tokenizer);
+ if (status) {
+ ALOGE("Error %d opening virtual key map file %s.", status, filename.string());
+ } else {
+ VirtualKeyMap* map = new VirtualKeyMap();
+ if (!map) {
+ ALOGE("Error allocating virtual key map.");
+ status = NO_MEMORY;
+ } else {
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+#endif
+ Parser parser(map, tokenizer);
+ status = parser.parse();
+#if DEBUG_PARSER_PERFORMANCE
+ nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
+ ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",
+ tokenizer->getFilename().string(), tokenizer->getLineNumber(),
+ elapsedTime / 1000000.0);
+#endif
+ if (status) {
+ delete map;
+ } else {
+ *outMap = map;
+ }
+ }
+ delete tokenizer;
+ }
+ return status;
+}
+
+
+// --- VirtualKeyMap::Parser ---
+
+VirtualKeyMap::Parser::Parser(VirtualKeyMap* map, Tokenizer* tokenizer) :
+ mMap(map), mTokenizer(tokenizer) {
+}
+
+VirtualKeyMap::Parser::~Parser() {
+}
+
+status_t VirtualKeyMap::Parser::parse() {
+ while (!mTokenizer->isEof()) {
+#if DEBUG_PARSER
+ ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+#endif
+
+ mTokenizer->skipDelimiters(WHITESPACE);
+
+ if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
+ // Multiple keys can appear on one line or they can be broken up across multiple lines.
+ do {
+ String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);
+ if (token != "0x01") {
+ ALOGE("%s: Unknown virtual key type, expected 0x01.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+ VirtualKeyDefinition defn;
+ bool success = parseNextIntField(&defn.scanCode)
+ && parseNextIntField(&defn.centerX)
+ && parseNextIntField(&defn.centerY)
+ && parseNextIntField(&defn.width)
+ && parseNextIntField(&defn.height);
+ if (!success) {
+ ALOGE("%s: Expected 5 colon-delimited integers in virtual key definition.",
+ mTokenizer->getLocation().string());
+ return BAD_VALUE;
+ }
+
+#if DEBUG_PARSER
+ ALOGD("Parsed virtual key: scanCode=%d, centerX=%d, centerY=%d, "
+ "width=%d, height=%d",
+ defn.scanCode, defn.centerX, defn.centerY, defn.width, defn.height);
+#endif
+ mMap->mVirtualKeys.push(defn);
+ } while (consumeFieldDelimiterAndSkipWhitespace());
+
+ if (!mTokenizer->isEol()) {
+ ALOGE("%s: Expected end of line, got '%s'.",
+ mTokenizer->getLocation().string(),
+ mTokenizer->peekRemainderOfLine().string());
+ return BAD_VALUE;
+ }
+ }
+
+ mTokenizer->nextLine();
+ }
+
+ return NO_ERROR;
+}
+
+bool VirtualKeyMap::Parser::consumeFieldDelimiterAndSkipWhitespace() {
+ mTokenizer->skipDelimiters(WHITESPACE);
+ if (mTokenizer->peekChar() == ':') {
+ mTokenizer->nextChar();
+ mTokenizer->skipDelimiters(WHITESPACE);
+ return true;
+ }
+ return false;
+}
+
+bool VirtualKeyMap::Parser::parseNextIntField(int32_t* outValue) {
+ if (!consumeFieldDelimiterAndSkipWhitespace()) {
+ return false;
+ }
+
+ String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);
+ char* end;
+ *outValue = strtol(token.string(), &end, 0);
+ if (token.isEmpty() || *end != '\0') {
+ ALOGE("Expected an integer, got '%s'.", token.string());
+ return false;
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/widget/gonk/libui/VirtualKeyMap.h b/widget/gonk/libui/VirtualKeyMap.h
new file mode 100644
index 0000000000..79d61a536c
--- /dev/null
+++ b/widget/gonk/libui/VirtualKeyMap.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROIDFW_VIRTUAL_KEY_MAP_H
+#define _ANDROIDFW_VIRTUAL_KEY_MAP_H
+
+#include <stdint.h>
+
+#include "Input.h"
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include "Tokenizer.h"
+#include <utils/String8.h>
+#include <utils/Unicode.h>
+
+namespace android {
+
+/* Describes a virtual key. */
+struct VirtualKeyDefinition {
+ int32_t scanCode;
+
+ // configured position data, specified in display coords
+ int32_t centerX;
+ int32_t centerY;
+ int32_t width;
+ int32_t height;
+};
+
+
+/**
+ * Describes a collection of virtual keys on a touch screen in terms of
+ * virtual scan codes and hit rectangles.
+ *
+ * This object is immutable after it has been loaded.
+ */
+class VirtualKeyMap {
+public:
+ ~VirtualKeyMap();
+
+ static status_t load(const String8& filename, VirtualKeyMap** outMap);
+
+ inline const Vector<VirtualKeyDefinition>& getVirtualKeys() const {
+ return mVirtualKeys;
+ }
+
+private:
+ class Parser {
+ VirtualKeyMap* mMap;
+ Tokenizer* mTokenizer;
+
+ public:
+ Parser(VirtualKeyMap* map, Tokenizer* tokenizer);
+ ~Parser();
+ status_t parse();
+
+ private:
+ bool consumeFieldDelimiterAndSkipWhitespace();
+ bool parseNextIntField(int32_t* outValue);
+ };
+
+ Vector<VirtualKeyDefinition> mVirtualKeys;
+
+ VirtualKeyMap();
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_KEY_CHARACTER_MAP_H
diff --git a/widget/gonk/libui/android_input.h b/widget/gonk/libui/android_input.h
new file mode 100644
index 0000000000..00e81b28d8
--- /dev/null
+++ b/widget/gonk/libui/android_input.h
@@ -0,0 +1,850 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROID_INPUT_H
+#define _ANDROID_INPUT_H
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ * This file is part of Android's set of stable system headers
+ * exposed by the Android NDK (Native Development Kit).
+ *
+ * Third-party source AND binary code relies on the definitions
+ * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+/*
+ * Structures and functions to receive and process input events in
+ * native code.
+ *
+ * NOTE: These functions MUST be implemented by /system/lib/libui.so
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+#include "android_keycodes.h"
+#include <android/looper.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Key states (may be returned by queries about the current state of a
+ * particular key code, scan code or switch).
+ */
+enum {
+ /* The key state is unknown or the requested key itself is not supported. */
+ AKEY_STATE_UNKNOWN = -1,
+
+ /* The key is up. */
+ AKEY_STATE_UP = 0,
+
+ /* The key is down. */
+ AKEY_STATE_DOWN = 1,
+
+ /* The key is down but is a virtual key press that is being emulated by the system. */
+ AKEY_STATE_VIRTUAL = 2
+};
+
+/*
+ * Meta key / modifer state.
+ */
+enum {
+ /* No meta keys are pressed. */
+ AMETA_NONE = 0,
+
+ /* This mask is used to check whether one of the ALT meta keys is pressed. */
+ AMETA_ALT_ON = 0x02,
+
+ /* This mask is used to check whether the left ALT meta key is pressed. */
+ AMETA_ALT_LEFT_ON = 0x10,
+
+ /* This mask is used to check whether the right ALT meta key is pressed. */
+ AMETA_ALT_RIGHT_ON = 0x20,
+
+ /* This mask is used to check whether one of the SHIFT meta keys is pressed. */
+ AMETA_SHIFT_ON = 0x01,
+
+ /* This mask is used to check whether the left SHIFT meta key is pressed. */
+ AMETA_SHIFT_LEFT_ON = 0x40,
+
+ /* This mask is used to check whether the right SHIFT meta key is pressed. */
+ AMETA_SHIFT_RIGHT_ON = 0x80,
+
+ /* This mask is used to check whether the SYM meta key is pressed. */
+ AMETA_SYM_ON = 0x04,
+
+ /* This mask is used to check whether the FUNCTION meta key is pressed. */
+ AMETA_FUNCTION_ON = 0x08,
+
+ /* This mask is used to check whether one of the CTRL meta keys is pressed. */
+ AMETA_CTRL_ON = 0x1000,
+
+ /* This mask is used to check whether the left CTRL meta key is pressed. */
+ AMETA_CTRL_LEFT_ON = 0x2000,
+
+ /* This mask is used to check whether the right CTRL meta key is pressed. */
+ AMETA_CTRL_RIGHT_ON = 0x4000,
+
+ /* This mask is used to check whether one of the META meta keys is pressed. */
+ AMETA_META_ON = 0x10000,
+
+ /* This mask is used to check whether the left META meta key is pressed. */
+ AMETA_META_LEFT_ON = 0x20000,
+
+ /* This mask is used to check whether the right META meta key is pressed. */
+ AMETA_META_RIGHT_ON = 0x40000,
+
+ /* This mask is used to check whether the CAPS LOCK meta key is on. */
+ AMETA_CAPS_LOCK_ON = 0x100000,
+
+ /* This mask is used to check whether the NUM LOCK meta key is on. */
+ AMETA_NUM_LOCK_ON = 0x200000,
+
+ /* This mask is used to check whether the SCROLL LOCK meta key is on. */
+ AMETA_SCROLL_LOCK_ON = 0x400000,
+};
+
+/*
+ * Input events.
+ *
+ * Input events are opaque structures. Use the provided accessors functions to
+ * read their properties.
+ */
+struct AInputEvent;
+typedef struct AInputEvent AInputEvent;
+
+/*
+ * Input event types.
+ */
+enum {
+ /* Indicates that the input event is a key event. */
+ AINPUT_EVENT_TYPE_KEY = 1,
+
+ /* Indicates that the input event is a motion event. */
+ AINPUT_EVENT_TYPE_MOTION = 2
+};
+
+/*
+ * Key event actions.
+ */
+enum {
+ /* The key has been pressed down. */
+ AKEY_EVENT_ACTION_DOWN = 0,
+
+ /* The key has been released. */
+ AKEY_EVENT_ACTION_UP = 1,
+
+ /* Multiple duplicate key events have occurred in a row, or a complex string is
+ * being delivered. The repeat_count property of the key event contains the number
+ * of times the given key code should be executed.
+ */
+ AKEY_EVENT_ACTION_MULTIPLE = 2
+};
+
+/*
+ * Key event flags.
+ */
+enum {
+ /* This mask is set if the device woke because of this key event. */
+ AKEY_EVENT_FLAG_WOKE_HERE = 0x1,
+
+ /* This mask is set if the key event was generated by a software keyboard. */
+ AKEY_EVENT_FLAG_SOFT_KEYBOARD = 0x2,
+
+ /* This mask is set if we don't want the key event to cause us to leave touch mode. */
+ AKEY_EVENT_FLAG_KEEP_TOUCH_MODE = 0x4,
+
+ /* This mask is set if an event was known to come from a trusted part
+ * of the system. That is, the event is known to come from the user,
+ * and could not have been spoofed by a third party component. */
+ AKEY_EVENT_FLAG_FROM_SYSTEM = 0x8,
+
+ /* This mask is used for compatibility, to identify enter keys that are
+ * coming from an IME whose enter key has been auto-labelled "next" or
+ * "done". This allows TextView to dispatch these as normal enter keys
+ * for old applications, but still do the appropriate action when
+ * receiving them. */
+ AKEY_EVENT_FLAG_EDITOR_ACTION = 0x10,
+
+ /* When associated with up key events, this indicates that the key press
+ * has been canceled. Typically this is used with virtual touch screen
+ * keys, where the user can slide from the virtual key area on to the
+ * display: in that case, the application will receive a canceled up
+ * event and should not perform the action normally associated with the
+ * key. Note that for this to work, the application can not perform an
+ * action for a key until it receives an up or the long press timeout has
+ * expired. */
+ AKEY_EVENT_FLAG_CANCELED = 0x20,
+
+ /* This key event was generated by a virtual (on-screen) hard key area.
+ * Typically this is an area of the touchscreen, outside of the regular
+ * display, dedicated to "hardware" buttons. */
+ AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY = 0x40,
+
+ /* This flag is set for the first key repeat that occurs after the
+ * long press timeout. */
+ AKEY_EVENT_FLAG_LONG_PRESS = 0x80,
+
+ /* Set when a key event has AKEY_EVENT_FLAG_CANCELED set because a long
+ * press action was executed while it was down. */
+ AKEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100,
+
+ /* Set for AKEY_EVENT_ACTION_UP when this event's key code is still being
+ * tracked from its initial down. That is, somebody requested that tracking
+ * started on the key down and a long press has not caused
+ * the tracking to be canceled. */
+ AKEY_EVENT_FLAG_TRACKING = 0x200,
+
+ /* Set when a key event has been synthesized to implement default behavior
+ * for an event that the application did not handle.
+ * Fallback key events are generated by unhandled trackball motions
+ * (to emulate a directional keypad) and by certain unhandled key presses
+ * that are declared in the key map (such as special function numeric keypad
+ * keys when numlock is off). */
+ AKEY_EVENT_FLAG_FALLBACK = 0x400,
+};
+
+/*
+ * Motion event actions.
+ */
+
+/* Bit shift for the action bits holding the pointer index as
+ * defined by AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
+ */
+#define AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8
+
+enum {
+ /* Bit mask of the parts of the action code that are the action itself.
+ */
+ AMOTION_EVENT_ACTION_MASK = 0xff,
+
+ /* Bits in the action code that represent a pointer index, used with
+ * AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP. Shifting
+ * down by AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
+ * index where the data for the pointer going up or down can be found.
+ */
+ AMOTION_EVENT_ACTION_POINTER_INDEX_MASK = 0xff00,
+
+ /* A pressed gesture has started, the motion contains the initial starting location.
+ */
+ AMOTION_EVENT_ACTION_DOWN = 0,
+
+ /* A pressed gesture has finished, the motion contains the final release location
+ * as well as any intermediate points since the last down or move event.
+ */
+ AMOTION_EVENT_ACTION_UP = 1,
+
+ /* A change has happened during a press gesture (between AMOTION_EVENT_ACTION_DOWN and
+ * AMOTION_EVENT_ACTION_UP). The motion contains the most recent point, as well as
+ * any intermediate points since the last down or move event.
+ */
+ AMOTION_EVENT_ACTION_MOVE = 2,
+
+ /* The current gesture has been aborted.
+ * You will not receive any more points in it. You should treat this as
+ * an up event, but not perform any action that you normally would.
+ */
+ AMOTION_EVENT_ACTION_CANCEL = 3,
+
+ /* A movement has happened outside of the normal bounds of the UI element.
+ * This does not provide a full gesture, but only the initial location of the movement/touch.
+ */
+ AMOTION_EVENT_ACTION_OUTSIDE = 4,
+
+ /* A non-primary pointer has gone down.
+ * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+ */
+ AMOTION_EVENT_ACTION_POINTER_DOWN = 5,
+
+ /* A non-primary pointer has gone up.
+ * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+ */
+ AMOTION_EVENT_ACTION_POINTER_UP = 6,
+
+ /* A change happened but the pointer is not down (unlike AMOTION_EVENT_ACTION_MOVE).
+ * The motion contains the most recent point, as well as any intermediate points since
+ * the last hover move event.
+ */
+ AMOTION_EVENT_ACTION_HOVER_MOVE = 7,
+
+ /* The motion event contains relative vertical and/or horizontal scroll offsets.
+ * Use getAxisValue to retrieve the information from AMOTION_EVENT_AXIS_VSCROLL
+ * and AMOTION_EVENT_AXIS_HSCROLL.
+ * The pointer may or may not be down when this event is dispatched.
+ * This action is always delivered to the winder under the pointer, which
+ * may not be the window currently touched.
+ */
+ AMOTION_EVENT_ACTION_SCROLL = 8,
+
+ /* The pointer is not down but has entered the boundaries of a window or view.
+ */
+ AMOTION_EVENT_ACTION_HOVER_ENTER = 9,
+
+ /* The pointer is not down but has exited the boundaries of a window or view.
+ */
+ AMOTION_EVENT_ACTION_HOVER_EXIT = 10,
+};
+
+/*
+ * Motion event flags.
+ */
+enum {
+ /* This flag indicates that the window that received this motion event is partly
+ * or wholly obscured by another visible window above it. This flag is set to true
+ * even if the event did not directly pass through the obscured area.
+ * A security sensitive application can check this flag to identify situations in which
+ * a malicious application may have covered up part of its content for the purpose
+ * of misleading the user or hijacking touches. An appropriate response might be
+ * to drop the suspect touches or to take additional precautions to confirm the user's
+ * actual intent.
+ */
+ AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1,
+};
+
+/*
+ * Motion event edge touch flags.
+ */
+enum {
+ /* No edges intersected */
+ AMOTION_EVENT_EDGE_FLAG_NONE = 0,
+
+ /* Flag indicating the motion event intersected the top edge of the screen. */
+ AMOTION_EVENT_EDGE_FLAG_TOP = 0x01,
+
+ /* Flag indicating the motion event intersected the bottom edge of the screen. */
+ AMOTION_EVENT_EDGE_FLAG_BOTTOM = 0x02,
+
+ /* Flag indicating the motion event intersected the left edge of the screen. */
+ AMOTION_EVENT_EDGE_FLAG_LEFT = 0x04,
+
+ /* Flag indicating the motion event intersected the right edge of the screen. */
+ AMOTION_EVENT_EDGE_FLAG_RIGHT = 0x08
+};
+
+/*
+ * Constants that identify each individual axis of a motion event.
+ * Refer to the documentation on the MotionEvent class for descriptions of each axis.
+ */
+enum {
+ AMOTION_EVENT_AXIS_X = 0,
+ AMOTION_EVENT_AXIS_Y = 1,
+ AMOTION_EVENT_AXIS_PRESSURE = 2,
+ AMOTION_EVENT_AXIS_SIZE = 3,
+ AMOTION_EVENT_AXIS_TOUCH_MAJOR = 4,
+ AMOTION_EVENT_AXIS_TOUCH_MINOR = 5,
+ AMOTION_EVENT_AXIS_TOOL_MAJOR = 6,
+ AMOTION_EVENT_AXIS_TOOL_MINOR = 7,
+ AMOTION_EVENT_AXIS_ORIENTATION = 8,
+ AMOTION_EVENT_AXIS_VSCROLL = 9,
+ AMOTION_EVENT_AXIS_HSCROLL = 10,
+ AMOTION_EVENT_AXIS_Z = 11,
+ AMOTION_EVENT_AXIS_RX = 12,
+ AMOTION_EVENT_AXIS_RY = 13,
+ AMOTION_EVENT_AXIS_RZ = 14,
+ AMOTION_EVENT_AXIS_HAT_X = 15,
+ AMOTION_EVENT_AXIS_HAT_Y = 16,
+ AMOTION_EVENT_AXIS_LTRIGGER = 17,
+ AMOTION_EVENT_AXIS_RTRIGGER = 18,
+ AMOTION_EVENT_AXIS_THROTTLE = 19,
+ AMOTION_EVENT_AXIS_RUDDER = 20,
+ AMOTION_EVENT_AXIS_WHEEL = 21,
+ AMOTION_EVENT_AXIS_GAS = 22,
+ AMOTION_EVENT_AXIS_BRAKE = 23,
+ AMOTION_EVENT_AXIS_DISTANCE = 24,
+ AMOTION_EVENT_AXIS_TILT = 25,
+ AMOTION_EVENT_AXIS_GENERIC_1 = 32,
+ AMOTION_EVENT_AXIS_GENERIC_2 = 33,
+ AMOTION_EVENT_AXIS_GENERIC_3 = 34,
+ AMOTION_EVENT_AXIS_GENERIC_4 = 35,
+ AMOTION_EVENT_AXIS_GENERIC_5 = 36,
+ AMOTION_EVENT_AXIS_GENERIC_6 = 37,
+ AMOTION_EVENT_AXIS_GENERIC_7 = 38,
+ AMOTION_EVENT_AXIS_GENERIC_8 = 39,
+ AMOTION_EVENT_AXIS_GENERIC_9 = 40,
+ AMOTION_EVENT_AXIS_GENERIC_10 = 41,
+ AMOTION_EVENT_AXIS_GENERIC_11 = 42,
+ AMOTION_EVENT_AXIS_GENERIC_12 = 43,
+ AMOTION_EVENT_AXIS_GENERIC_13 = 44,
+ AMOTION_EVENT_AXIS_GENERIC_14 = 45,
+ AMOTION_EVENT_AXIS_GENERIC_15 = 46,
+ AMOTION_EVENT_AXIS_GENERIC_16 = 47,
+
+ // NOTE: If you add a new axis here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
+};
+
+/*
+ * Constants that identify buttons that are associated with motion events.
+ * Refer to the documentation on the MotionEvent class for descriptions of each button.
+ */
+enum {
+ AMOTION_EVENT_BUTTON_PRIMARY = 1 << 0,
+ AMOTION_EVENT_BUTTON_SECONDARY = 1 << 1,
+ AMOTION_EVENT_BUTTON_TERTIARY = 1 << 2,
+ AMOTION_EVENT_BUTTON_BACK = 1 << 3,
+ AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
+};
+
+/*
+ * Constants that identify tool types.
+ * Refer to the documentation on the MotionEvent class for descriptions of each tool type.
+ */
+enum {
+ AMOTION_EVENT_TOOL_TYPE_UNKNOWN = 0,
+ AMOTION_EVENT_TOOL_TYPE_FINGER = 1,
+ AMOTION_EVENT_TOOL_TYPE_STYLUS = 2,
+ AMOTION_EVENT_TOOL_TYPE_MOUSE = 3,
+ AMOTION_EVENT_TOOL_TYPE_ERASER = 4,
+};
+
+/*
+ * Input sources.
+ *
+ * Refer to the documentation on android.view.InputDevice for more details about input sources
+ * and their correct interpretation.
+ */
+enum {
+ AINPUT_SOURCE_CLASS_MASK = 0x000000ff,
+
+ AINPUT_SOURCE_CLASS_NONE = 0x00000000,
+ AINPUT_SOURCE_CLASS_BUTTON = 0x00000001,
+ AINPUT_SOURCE_CLASS_POINTER = 0x00000002,
+ AINPUT_SOURCE_CLASS_NAVIGATION = 0x00000004,
+ AINPUT_SOURCE_CLASS_POSITION = 0x00000008,
+ AINPUT_SOURCE_CLASS_JOYSTICK = 0x00000010,
+};
+
+enum {
+ AINPUT_SOURCE_UNKNOWN = 0x00000000,
+
+ AINPUT_SOURCE_KEYBOARD = 0x00000100 | AINPUT_SOURCE_CLASS_BUTTON,
+ AINPUT_SOURCE_DPAD = 0x00000200 | AINPUT_SOURCE_CLASS_BUTTON,
+ AINPUT_SOURCE_GAMEPAD = 0x00000400 | AINPUT_SOURCE_CLASS_BUTTON,
+ AINPUT_SOURCE_TOUCHSCREEN = 0x00001000 | AINPUT_SOURCE_CLASS_POINTER,
+ AINPUT_SOURCE_MOUSE = 0x00002000 | AINPUT_SOURCE_CLASS_POINTER,
+ AINPUT_SOURCE_STYLUS = 0x00004000 | AINPUT_SOURCE_CLASS_POINTER,
+ AINPUT_SOURCE_TRACKBALL = 0x00010000 | AINPUT_SOURCE_CLASS_NAVIGATION,
+ AINPUT_SOURCE_TOUCHPAD = 0x00100000 | AINPUT_SOURCE_CLASS_POSITION,
+ AINPUT_SOURCE_TOUCH_NAVIGATION = 0x00200000 | AINPUT_SOURCE_CLASS_NONE,
+ AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
+
+ AINPUT_SOURCE_ANY = 0xffffff00,
+};
+
+/*
+ * Keyboard types.
+ *
+ * Refer to the documentation on android.view.InputDevice for more details.
+ */
+enum {
+ AINPUT_KEYBOARD_TYPE_NONE = 0,
+ AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC = 1,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC = 2,
+};
+
+/*
+ * Constants used to retrieve information about the range of motion for a particular
+ * coordinate of a motion event.
+ *
+ * Refer to the documentation on android.view.InputDevice for more details about input sources
+ * and their correct interpretation.
+ *
+ * DEPRECATION NOTICE: These constants are deprecated. Use AMOTION_EVENT_AXIS_* constants instead.
+ */
+enum {
+ AINPUT_MOTION_RANGE_X = AMOTION_EVENT_AXIS_X,
+ AINPUT_MOTION_RANGE_Y = AMOTION_EVENT_AXIS_Y,
+ AINPUT_MOTION_RANGE_PRESSURE = AMOTION_EVENT_AXIS_PRESSURE,
+ AINPUT_MOTION_RANGE_SIZE = AMOTION_EVENT_AXIS_SIZE,
+ AINPUT_MOTION_RANGE_TOUCH_MAJOR = AMOTION_EVENT_AXIS_TOUCH_MAJOR,
+ AINPUT_MOTION_RANGE_TOUCH_MINOR = AMOTION_EVENT_AXIS_TOUCH_MINOR,
+ AINPUT_MOTION_RANGE_TOOL_MAJOR = AMOTION_EVENT_AXIS_TOOL_MAJOR,
+ AINPUT_MOTION_RANGE_TOOL_MINOR = AMOTION_EVENT_AXIS_TOOL_MINOR,
+ AINPUT_MOTION_RANGE_ORIENTATION = AMOTION_EVENT_AXIS_ORIENTATION,
+} __attribute__ ((deprecated));
+
+
+/*
+ * Input event accessors.
+ *
+ * Note that most functions can only be used on input events that are of a given type.
+ * Calling these functions on input events of other types will yield undefined behavior.
+ */
+
+/*** Accessors for all input events. ***/
+
+/* Get the input event type. */
+int32_t AInputEvent_getType(const AInputEvent* event);
+
+/* Get the id for the device that an input event came from.
+ *
+ * Input events can be generated by multiple different input devices.
+ * Use the input device id to obtain information about the input
+ * device that was responsible for generating a particular event.
+ *
+ * An input device id of 0 indicates that the event didn't come from a physical device;
+ * other numbers are arbitrary and you shouldn't depend on the values.
+ * Use the provided input device query API to obtain information about input devices.
+ */
+int32_t AInputEvent_getDeviceId(const AInputEvent* event);
+
+/* Get the input event source. */
+int32_t AInputEvent_getSource(const AInputEvent* event);
+
+/*** Accessors for key events only. ***/
+
+/* Get the key event action. */
+int32_t AKeyEvent_getAction(const AInputEvent* key_event);
+
+/* Get the key event flags. */
+int32_t AKeyEvent_getFlags(const AInputEvent* key_event);
+
+/* Get the key code of the key event.
+ * This is the physical key that was pressed, not the Unicode character. */
+int32_t AKeyEvent_getKeyCode(const AInputEvent* key_event);
+
+/* Get the hardware key id of this key event.
+ * These values are not reliable and vary from device to device. */
+int32_t AKeyEvent_getScanCode(const AInputEvent* key_event);
+
+/* Get the meta key state. */
+int32_t AKeyEvent_getMetaState(const AInputEvent* key_event);
+
+/* Get the repeat count of the event.
+ * For both key up an key down events, this is the number of times the key has
+ * repeated with the first down starting at 0 and counting up from there. For
+ * multiple key events, this is the number of down/up pairs that have occurred. */
+int32_t AKeyEvent_getRepeatCount(const AInputEvent* key_event);
+
+/* Get the time of the most recent key down event, in the
+ * java.lang.System.nanoTime() time base. If this is a down event,
+ * this will be the same as eventTime.
+ * Note that when chording keys, this value is the down time of the most recently
+ * pressed key, which may not be the same physical key of this event. */
+int64_t AKeyEvent_getDownTime(const AInputEvent* key_event);
+
+/* Get the time this event occurred, in the
+ * java.lang.System.nanoTime() time base. */
+int64_t AKeyEvent_getEventTime(const AInputEvent* key_event);
+
+/*** Accessors for motion events only. ***/
+
+/* Get the combined motion event action code and pointer index. */
+int32_t AMotionEvent_getAction(const AInputEvent* motion_event);
+
+/* Get the motion event flags. */
+int32_t AMotionEvent_getFlags(const AInputEvent* motion_event);
+
+/* Get the state of any meta / modifier keys that were in effect when the
+ * event was generated. */
+int32_t AMotionEvent_getMetaState(const AInputEvent* motion_event);
+
+/* Get the button state of all buttons that are pressed. */
+int32_t AMotionEvent_getButtonState(const AInputEvent* motion_event);
+
+/* Get a bitfield indicating which edges, if any, were touched by this motion event.
+ * For touch events, clients can use this to determine if the user's finger was
+ * touching the edge of the display. */
+int32_t AMotionEvent_getEdgeFlags(const AInputEvent* motion_event);
+
+/* Get the time when the user originally pressed down to start a stream of
+ * position events, in the java.lang.System.nanoTime() time base. */
+int64_t AMotionEvent_getDownTime(const AInputEvent* motion_event);
+
+/* Get the time when this specific event was generated,
+ * in the java.lang.System.nanoTime() time base. */
+int64_t AMotionEvent_getEventTime(const AInputEvent* motion_event);
+
+/* Get the X coordinate offset.
+ * For touch events on the screen, this is the delta that was added to the raw
+ * screen coordinates to adjust for the absolute position of the containing windows
+ * and views. */
+float AMotionEvent_getXOffset(const AInputEvent* motion_event);
+
+/* Get the precision of the Y coordinates being reported.
+ * For touch events on the screen, this is the delta that was added to the raw
+ * screen coordinates to adjust for the absolute position of the containing windows
+ * and views. */
+float AMotionEvent_getYOffset(const AInputEvent* motion_event);
+
+/* Get the precision of the X coordinates being reported.
+ * You can multiply this number with an X coordinate sample to find the
+ * actual hardware value of the X coordinate. */
+float AMotionEvent_getXPrecision(const AInputEvent* motion_event);
+
+/* Get the precision of the Y coordinates being reported.
+ * You can multiply this number with a Y coordinate sample to find the
+ * actual hardware value of the Y coordinate. */
+float AMotionEvent_getYPrecision(const AInputEvent* motion_event);
+
+/* Get the number of pointers of data contained in this event.
+ * Always >= 1. */
+size_t AMotionEvent_getPointerCount(const AInputEvent* motion_event);
+
+/* Get the pointer identifier associated with a particular pointer
+ * data index in this event. The identifier tells you the actual pointer
+ * number associated with the data, accounting for individual pointers
+ * going up and down since the start of the current gesture. */
+int32_t AMotionEvent_getPointerId(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the tool type of a pointer for the given pointer index.
+ * The tool type indicates the type of tool used to make contact such as a
+ * finger or stylus, if known. */
+int32_t AMotionEvent_getToolType(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the original raw X coordinate of this event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views. */
+float AMotionEvent_getRawX(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the original raw X coordinate of this event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views. */
+float AMotionEvent_getRawY(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current X coordinate of this event for the given pointer index.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getX(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current Y coordinate of this event for the given pointer index.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getY(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current pressure of this event for the given pointer index.
+ * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
+ * although values higher than 1 may be generated depending on the calibration of
+ * the input device. */
+float AMotionEvent_getPressure(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current scaled value of the approximate size for the given pointer index.
+ * This represents some approximation of the area of the screen being
+ * pressed; the actual value in pixels corresponding to the
+ * touch is normalized with the device specific range of values
+ * and scaled to a value between 0 and 1. The value of size can be used to
+ * determine fat touch events. */
+float AMotionEvent_getSize(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current length of the major axis of an ellipse that describes the touch area
+ * at the point of contact for the given pointer index. */
+float AMotionEvent_getTouchMajor(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current length of the minor axis of an ellipse that describes the touch area
+ * at the point of contact for the given pointer index. */
+float AMotionEvent_getTouchMinor(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current length of the major axis of an ellipse that describes the size
+ * of the approaching tool for the given pointer index.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact. */
+float AMotionEvent_getToolMajor(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current length of the minor axis of an ellipse that describes the size
+ * of the approaching tool for the given pointer index.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact. */
+float AMotionEvent_getToolMinor(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the current orientation of the touch area and tool area in radians clockwise from
+ * vertical for the given pointer index.
+ * An angle of 0 degrees indicates that the major axis of contact is oriented
+ * upwards, is perfectly circular or is of unknown orientation. A positive angle
+ * indicates that the major axis of contact is oriented to the right. A negative angle
+ * indicates that the major axis of contact is oriented to the left.
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+ * (finger pointing fully right). */
+float AMotionEvent_getOrientation(const AInputEvent* motion_event, size_t pointer_index);
+
+/* Get the value of the request axis for the given pointer index. */
+float AMotionEvent_getAxisValue(const AInputEvent* motion_event,
+ int32_t axis, size_t pointer_index);
+
+/* Get the number of historical points in this event. These are movements that
+ * have occurred between this event and the previous event. This only applies
+ * to AMOTION_EVENT_ACTION_MOVE events -- all other actions will have a size of 0.
+ * Historical samples are indexed from oldest to newest. */
+size_t AMotionEvent_getHistorySize(const AInputEvent* motion_event);
+
+/* Get the time that a historical movement occurred between this event and
+ * the previous event, in the java.lang.System.nanoTime() time base. */
+int64_t AMotionEvent_getHistoricalEventTime(AInputEvent* motion_event,
+ size_t history_index);
+
+/* Get the historical raw X coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getHistoricalRawX(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical raw Y coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * For touch events on the screen, this is the original location of the event
+ * on the screen, before it had been adjusted for the containing window
+ * and views.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getHistoricalRawY(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical X coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getHistoricalX(AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical Y coordinate of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * Whole numbers are pixels; the value may have a fraction for input devices
+ * that are sub-pixel precise. */
+float AMotionEvent_getHistoricalY(AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical pressure of this event for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure),
+ * although values higher than 1 may be generated depending on the calibration of
+ * the input device. */
+float AMotionEvent_getHistoricalPressure(AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the current scaled value of the approximate size for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * This represents some approximation of the area of the screen being
+ * pressed; the actual value in pixels corresponding to the
+ * touch is normalized with the device specific range of values
+ * and scaled to a value between 0 and 1. The value of size can be used to
+ * determine fat touch events. */
+float AMotionEvent_getHistoricalSize(AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical length of the major axis of an ellipse that describes the touch area
+ * at the point of contact for the given pointer index that
+ * occurred between this event and the previous motion event. */
+float AMotionEvent_getHistoricalTouchMajor(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical length of the minor axis of an ellipse that describes the touch area
+ * at the point of contact for the given pointer index that
+ * occurred between this event and the previous motion event. */
+float AMotionEvent_getHistoricalTouchMinor(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical length of the major axis of an ellipse that describes the size
+ * of the approaching tool for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact. */
+float AMotionEvent_getHistoricalToolMajor(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical length of the minor axis of an ellipse that describes the size
+ * of the approaching tool for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * The tool area represents the estimated size of the finger or pen that is
+ * touching the device independent of its actual touch area at the point of contact. */
+float AMotionEvent_getHistoricalToolMinor(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical orientation of the touch area and tool area in radians clockwise from
+ * vertical for the given pointer index that
+ * occurred between this event and the previous motion event.
+ * An angle of 0 degrees indicates that the major axis of contact is oriented
+ * upwards, is perfectly circular or is of unknown orientation. A positive angle
+ * indicates that the major axis of contact is oriented to the right. A negative angle
+ * indicates that the major axis of contact is oriented to the left.
+ * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
+ * (finger pointing fully right). */
+float AMotionEvent_getHistoricalOrientation(const AInputEvent* motion_event, size_t pointer_index,
+ size_t history_index);
+
+/* Get the historical value of the request axis for the given pointer index
+ * that occurred between this event and the previous motion event. */
+float AMotionEvent_getHistoricalAxisValue(const AInputEvent* motion_event,
+ int32_t axis, size_t pointer_index, size_t history_index);
+
+
+/*
+ * Input queue
+ *
+ * An input queue is the facility through which you retrieve input
+ * events.
+ */
+struct AInputQueue;
+typedef struct AInputQueue AInputQueue;
+
+/*
+ * Add this input queue to a looper for processing. See
+ * ALooper_addFd() for information on the ident, callback, and data params.
+ */
+void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
+ int ident, ALooper_callbackFunc callback, void* data);
+
+/*
+ * Remove the input queue from the looper it is currently attached to.
+ */
+void AInputQueue_detachLooper(AInputQueue* queue);
+
+/*
+ * Returns true if there are one or more events available in the
+ * input queue. Returns 1 if the queue has events; 0 if
+ * it does not have events; and a negative value if there is an error.
+ */
+int32_t AInputQueue_hasEvents(AInputQueue* queue);
+
+/*
+ * Returns the next available event from the queue. Returns a negative
+ * value if no events are available or an error has occurred.
+ */
+int32_t AInputQueue_getEvent(AInputQueue* queue, AInputEvent** outEvent);
+
+/*
+ * Sends the key for standard pre-dispatching -- that is, possibly deliver
+ * it to the current IME to be consumed before the app. Returns 0 if it
+ * was not pre-dispatched, meaning you can process it right now. If non-zero
+ * is returned, you must abandon the current event processing and allow the
+ * event to appear again in the event queue (if it does not get consumed during
+ * pre-dispatching).
+ */
+int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event);
+
+/*
+ * Report that dispatching has finished with the given event.
+ * This must be called after receiving an event with AInputQueue_get_event().
+ */
+void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _ANDROID_INPUT_H
diff --git a/widget/gonk/libui/android_keycodes.h b/widget/gonk/libui/android_keycodes.h
new file mode 100644
index 0000000000..9e63d1d012
--- /dev/null
+++ b/widget/gonk/libui/android_keycodes.h
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _ANDROID_KEYCODES_H
+#define _ANDROID_KEYCODES_H
+
+/******************************************************************
+ *
+ * IMPORTANT NOTICE:
+ *
+ * This file is part of Android's set of stable system headers
+ * exposed by the Android NDK (Native Development Kit).
+ *
+ * Third-party source AND binary code relies on the definitions
+ * here to be FROZEN ON ALL UPCOMING PLATFORM RELEASES.
+ *
+ * - DO NOT MODIFY ENUMS (EXCEPT IF YOU ADD NEW 32-BIT VALUES)
+ * - DO NOT MODIFY CONSTANTS OR FUNCTIONAL MACROS
+ * - DO NOT CHANGE THE SIGNATURE OF FUNCTIONS IN ANY WAY
+ * - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
+ */
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Key codes.
+ */
+enum {
+ AKEYCODE_UNKNOWN = 0,
+ AKEYCODE_SOFT_LEFT = 1,
+ AKEYCODE_SOFT_RIGHT = 2,
+ AKEYCODE_HOME = 3,
+ AKEYCODE_BACK = 4,
+ AKEYCODE_CALL = 5,
+ AKEYCODE_ENDCALL = 6,
+ AKEYCODE_0 = 7,
+ AKEYCODE_1 = 8,
+ AKEYCODE_2 = 9,
+ AKEYCODE_3 = 10,
+ AKEYCODE_4 = 11,
+ AKEYCODE_5 = 12,
+ AKEYCODE_6 = 13,
+ AKEYCODE_7 = 14,
+ AKEYCODE_8 = 15,
+ AKEYCODE_9 = 16,
+ AKEYCODE_STAR = 17,
+ AKEYCODE_POUND = 18,
+ AKEYCODE_DPAD_UP = 19,
+ AKEYCODE_DPAD_DOWN = 20,
+ AKEYCODE_DPAD_LEFT = 21,
+ AKEYCODE_DPAD_RIGHT = 22,
+ AKEYCODE_DPAD_CENTER = 23,
+ AKEYCODE_VOLUME_UP = 24,
+ AKEYCODE_VOLUME_DOWN = 25,
+ AKEYCODE_POWER = 26,
+ AKEYCODE_CAMERA = 27,
+ AKEYCODE_CLEAR = 28,
+ AKEYCODE_A = 29,
+ AKEYCODE_B = 30,
+ AKEYCODE_C = 31,
+ AKEYCODE_D = 32,
+ AKEYCODE_E = 33,
+ AKEYCODE_F = 34,
+ AKEYCODE_G = 35,
+ AKEYCODE_H = 36,
+ AKEYCODE_I = 37,
+ AKEYCODE_J = 38,
+ AKEYCODE_K = 39,
+ AKEYCODE_L = 40,
+ AKEYCODE_M = 41,
+ AKEYCODE_N = 42,
+ AKEYCODE_O = 43,
+ AKEYCODE_P = 44,
+ AKEYCODE_Q = 45,
+ AKEYCODE_R = 46,
+ AKEYCODE_S = 47,
+ AKEYCODE_T = 48,
+ AKEYCODE_U = 49,
+ AKEYCODE_V = 50,
+ AKEYCODE_W = 51,
+ AKEYCODE_X = 52,
+ AKEYCODE_Y = 53,
+ AKEYCODE_Z = 54,
+ AKEYCODE_COMMA = 55,
+ AKEYCODE_PERIOD = 56,
+ AKEYCODE_ALT_LEFT = 57,
+ AKEYCODE_ALT_RIGHT = 58,
+ AKEYCODE_SHIFT_LEFT = 59,
+ AKEYCODE_SHIFT_RIGHT = 60,
+ AKEYCODE_TAB = 61,
+ AKEYCODE_SPACE = 62,
+ AKEYCODE_SYM = 63,
+ AKEYCODE_EXPLORER = 64,
+ AKEYCODE_ENVELOPE = 65,
+ AKEYCODE_ENTER = 66,
+ AKEYCODE_DEL = 67,
+ AKEYCODE_GRAVE = 68,
+ AKEYCODE_MINUS = 69,
+ AKEYCODE_EQUALS = 70,
+ AKEYCODE_LEFT_BRACKET = 71,
+ AKEYCODE_RIGHT_BRACKET = 72,
+ AKEYCODE_BACKSLASH = 73,
+ AKEYCODE_SEMICOLON = 74,
+ AKEYCODE_APOSTROPHE = 75,
+ AKEYCODE_SLASH = 76,
+ AKEYCODE_AT = 77,
+ AKEYCODE_NUM = 78,
+ AKEYCODE_HEADSETHOOK = 79,
+ AKEYCODE_FOCUS = 80, // *Camera* focus
+ AKEYCODE_PLUS = 81,
+ AKEYCODE_MENU = 82,
+ AKEYCODE_NOTIFICATION = 83,
+ AKEYCODE_SEARCH = 84,
+ AKEYCODE_MEDIA_PLAY_PAUSE= 85,
+ AKEYCODE_MEDIA_STOP = 86,
+ AKEYCODE_MEDIA_NEXT = 87,
+ AKEYCODE_MEDIA_PREVIOUS = 88,
+ AKEYCODE_MEDIA_REWIND = 89,
+ AKEYCODE_MEDIA_FAST_FORWARD = 90,
+ AKEYCODE_MUTE = 91,
+ AKEYCODE_PAGE_UP = 92,
+ AKEYCODE_PAGE_DOWN = 93,
+ AKEYCODE_PICTSYMBOLS = 94,
+ AKEYCODE_SWITCH_CHARSET = 95,
+ AKEYCODE_BUTTON_A = 96,
+ AKEYCODE_BUTTON_B = 97,
+ AKEYCODE_BUTTON_C = 98,
+ AKEYCODE_BUTTON_X = 99,
+ AKEYCODE_BUTTON_Y = 100,
+ AKEYCODE_BUTTON_Z = 101,
+ AKEYCODE_BUTTON_L1 = 102,
+ AKEYCODE_BUTTON_R1 = 103,
+ AKEYCODE_BUTTON_L2 = 104,
+ AKEYCODE_BUTTON_R2 = 105,
+ AKEYCODE_BUTTON_THUMBL = 106,
+ AKEYCODE_BUTTON_THUMBR = 107,
+ AKEYCODE_BUTTON_START = 108,
+ AKEYCODE_BUTTON_SELECT = 109,
+ AKEYCODE_BUTTON_MODE = 110,
+ AKEYCODE_ESCAPE = 111,
+ AKEYCODE_FORWARD_DEL = 112,
+ AKEYCODE_CTRL_LEFT = 113,
+ AKEYCODE_CTRL_RIGHT = 114,
+ AKEYCODE_CAPS_LOCK = 115,
+ AKEYCODE_SCROLL_LOCK = 116,
+ AKEYCODE_META_LEFT = 117,
+ AKEYCODE_META_RIGHT = 118,
+ AKEYCODE_FUNCTION = 119,
+ AKEYCODE_SYSRQ = 120,
+ AKEYCODE_BREAK = 121,
+ AKEYCODE_MOVE_HOME = 122,
+ AKEYCODE_MOVE_END = 123,
+ AKEYCODE_INSERT = 124,
+ AKEYCODE_FORWARD = 125,
+ AKEYCODE_MEDIA_PLAY = 126,
+ AKEYCODE_MEDIA_PAUSE = 127,
+ AKEYCODE_MEDIA_CLOSE = 128,
+ AKEYCODE_MEDIA_EJECT = 129,
+ AKEYCODE_MEDIA_RECORD = 130,
+ AKEYCODE_F1 = 131,
+ AKEYCODE_F2 = 132,
+ AKEYCODE_F3 = 133,
+ AKEYCODE_F4 = 134,
+ AKEYCODE_F5 = 135,
+ AKEYCODE_F6 = 136,
+ AKEYCODE_F7 = 137,
+ AKEYCODE_F8 = 138,
+ AKEYCODE_F9 = 139,
+ AKEYCODE_F10 = 140,
+ AKEYCODE_F11 = 141,
+ AKEYCODE_F12 = 142,
+ AKEYCODE_NUM_LOCK = 143,
+ AKEYCODE_NUMPAD_0 = 144,
+ AKEYCODE_NUMPAD_1 = 145,
+ AKEYCODE_NUMPAD_2 = 146,
+ AKEYCODE_NUMPAD_3 = 147,
+ AKEYCODE_NUMPAD_4 = 148,
+ AKEYCODE_NUMPAD_5 = 149,
+ AKEYCODE_NUMPAD_6 = 150,
+ AKEYCODE_NUMPAD_7 = 151,
+ AKEYCODE_NUMPAD_8 = 152,
+ AKEYCODE_NUMPAD_9 = 153,
+ AKEYCODE_NUMPAD_DIVIDE = 154,
+ AKEYCODE_NUMPAD_MULTIPLY = 155,
+ AKEYCODE_NUMPAD_SUBTRACT = 156,
+ AKEYCODE_NUMPAD_ADD = 157,
+ AKEYCODE_NUMPAD_DOT = 158,
+ AKEYCODE_NUMPAD_COMMA = 159,
+ AKEYCODE_NUMPAD_ENTER = 160,
+ AKEYCODE_NUMPAD_EQUALS = 161,
+ AKEYCODE_NUMPAD_LEFT_PAREN = 162,
+ AKEYCODE_NUMPAD_RIGHT_PAREN = 163,
+ AKEYCODE_VOLUME_MUTE = 164,
+ AKEYCODE_INFO = 165,
+ AKEYCODE_CHANNEL_UP = 166,
+ AKEYCODE_CHANNEL_DOWN = 167,
+ AKEYCODE_ZOOM_IN = 168,
+ AKEYCODE_ZOOM_OUT = 169,
+ AKEYCODE_TV = 170,
+ AKEYCODE_WINDOW = 171,
+ AKEYCODE_GUIDE = 172,
+ AKEYCODE_DVR = 173,
+ AKEYCODE_BOOKMARK = 174,
+ AKEYCODE_CAPTIONS = 175,
+ AKEYCODE_SETTINGS = 176,
+ AKEYCODE_TV_POWER = 177,
+ AKEYCODE_TV_INPUT = 178,
+ AKEYCODE_STB_POWER = 179,
+ AKEYCODE_STB_INPUT = 180,
+ AKEYCODE_AVR_POWER = 181,
+ AKEYCODE_AVR_INPUT = 182,
+ AKEYCODE_PROG_RED = 183,
+ AKEYCODE_PROG_GREEN = 184,
+ AKEYCODE_PROG_YELLOW = 185,
+ AKEYCODE_PROG_BLUE = 186,
+ AKEYCODE_APP_SWITCH = 187,
+ AKEYCODE_BUTTON_1 = 188,
+ AKEYCODE_BUTTON_2 = 189,
+ AKEYCODE_BUTTON_3 = 190,
+ AKEYCODE_BUTTON_4 = 191,
+ AKEYCODE_BUTTON_5 = 192,
+ AKEYCODE_BUTTON_6 = 193,
+ AKEYCODE_BUTTON_7 = 194,
+ AKEYCODE_BUTTON_8 = 195,
+ AKEYCODE_BUTTON_9 = 196,
+ AKEYCODE_BUTTON_10 = 197,
+ AKEYCODE_BUTTON_11 = 198,
+ AKEYCODE_BUTTON_12 = 199,
+ AKEYCODE_BUTTON_13 = 200,
+ AKEYCODE_BUTTON_14 = 201,
+ AKEYCODE_BUTTON_15 = 202,
+ AKEYCODE_BUTTON_16 = 203,
+ AKEYCODE_LANGUAGE_SWITCH = 204,
+ AKEYCODE_MANNER_MODE = 205,
+ AKEYCODE_3D_MODE = 206,
+ AKEYCODE_CONTACTS = 207,
+ AKEYCODE_CALENDAR = 208,
+ AKEYCODE_MUSIC = 209,
+ AKEYCODE_CALCULATOR = 210,
+ AKEYCODE_ZENKAKU_HANKAKU = 211,
+ AKEYCODE_EISU = 212,
+ AKEYCODE_MUHENKAN = 213,
+ AKEYCODE_HENKAN = 214,
+ AKEYCODE_KATAKANA_HIRAGANA = 215,
+ AKEYCODE_YEN = 216,
+ AKEYCODE_RO = 217,
+ AKEYCODE_KANA = 218,
+ AKEYCODE_ASSIST = 219,
+ AKEYCODE_BRIGHTNESS_DOWN = 220,
+ AKEYCODE_BRIGHTNESS_UP = 221,
+ AKEYCODE_MEDIA_AUDIO_TRACK = 222,
+ AKEYCODE_SLEEP = 223,
+ AKEYCODE_WAKEUP = 224,
+ AKEYCODE_PAIRING = 225,
+ AKEYCODE_MEDIA_TOP_MENU = 226,
+ AKEYCODE_11 = 227,
+ AKEYCODE_12 = 228,
+ AKEYCODE_LAST_CHANNEL = 229,
+ AKEYCODE_TV_DATA_SERVICE = 230,
+ AKEYCODE_VOICE_ASSIST = 231,
+ AKEYCODE_TV_RADIO_SERVICE = 232,
+ AKEYCODE_TV_TELETEXT = 233,
+ AKEYCODE_TV_NUMBER_ENTRY = 234,
+ AKEYCODE_TV_TERRESTRIAL_ANALOG = 235,
+ AKEYCODE_TV_TERRESTRIAL_DIGITAL = 236,
+ AKEYCODE_TV_SATELLITE = 237,
+ AKEYCODE_TV_SATELLITE_BS = 238,
+ AKEYCODE_TV_SATELLITE_CS = 239,
+ AKEYCODE_TV_SATELLITE_SERVICE = 240,
+ AKEYCODE_TV_NETWORK = 241,
+ AKEYCODE_TV_ANTENNA_CABLE = 242,
+ AKEYCODE_TV_INPUT_HDMI_1 = 243,
+ AKEYCODE_TV_INPUT_HDMI_2 = 244,
+ AKEYCODE_TV_INPUT_HDMI_3 = 245,
+ AKEYCODE_TV_INPUT_HDMI_4 = 246,
+ AKEYCODE_TV_INPUT_COMPOSITE_1 = 247,
+ AKEYCODE_TV_INPUT_COMPOSITE_2 = 248,
+ AKEYCODE_TV_INPUT_COMPONENT_1 = 249,
+ AKEYCODE_TV_INPUT_COMPONENT_2 = 250,
+ AKEYCODE_TV_INPUT_VGA_1 = 251,
+ AKEYCODE_TV_AUDIO_DESCRIPTION = 252,
+ AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253,
+ AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254,
+ AKEYCODE_TV_ZOOM_MODE = 255,
+ AKEYCODE_TV_CONTENTS_MENU = 256,
+ AKEYCODE_TV_MEDIA_CONTEXT_MENU = 257,
+ AKEYCODE_TV_TIMER_PROGRAMMING = 258,
+ AKEYCODE_HELP = 259,
+
+ // NOTE: If you add a new keycode here you must also add it to several other files.
+ // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _ANDROID_KEYCODES_H
diff --git a/widget/gonk/libui/cutils_log.h b/widget/gonk/libui/cutils_log.h
new file mode 100644
index 0000000000..f4252c8679
--- /dev/null
+++ b/widget/gonk/libui/cutils_log.h
@@ -0,0 +1,569 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * 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.
+ */
+
+//
+// C/C++ logging functions. See the logging documentation for API details.
+//
+// We'd like these to be available from C code (in case we import some from
+// somewhere), so this has a C interface.
+//
+// The output will be correct when the log file is shared between multiple
+// threads and/or multiple processes so long as the operating system
+// supports O_APPEND. These calls have mutex-protected data structures
+// and so are NOT reentrant. Do not use LOG in a signal handler.
+//
+#if !defined(_LIBS_CUTILS_LOG_H) && !defined(_LIBS_LOG_LOG_H)
+#define _LIBS_LOG_LOG_H
+#define _LIBS_CUTILS_LOG_H
+
+#include <stdio.h>
+#include <time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifdef HAVE_PTHREADS
+#include <pthread.h>
+#endif
+#include <stdarg.h>
+
+#if ANDROID_VERSION >= 19
+#include <log/uio.h>
+#include <log/logd.h>
+#else
+#include <cutils/uio.h>
+#include <cutils/logd.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Normally we strip ALOGV (VERBOSE messages) from release builds.
+ * You can modify this (for example with "#define LOG_NDEBUG 0"
+ * at the top of your source file) to change that behavior.
+ */
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * This is the local tag used for the following simplified
+ * logging macros. You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+#ifndef LOG_TAG
+#define LOG_TAG NULL
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose log message using the current LOG_TAG.
+ */
+#ifndef ALOGV
+#if LOG_NDEBUG
+#define ALOGV(...) ((void)0)
+#else
+#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond) (__builtin_expect((cond)!=0, 0))
+
+#ifndef ALOGV_IF
+#if LOG_NDEBUG
+#define ALOGV_IF(cond, ...) ((void)0)
+#else
+#define ALOGV_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug log message using the current LOG_TAG.
+ */
+#ifndef ALOGD
+#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGD_IF
+#define ALOGD_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info log message using the current LOG_TAG.
+ */
+#ifndef ALOGI
+#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGI_IF
+#define ALOGI_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning log message using the current LOG_TAG.
+ */
+#ifndef ALOGW
+#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGW_IF
+#define ALOGW_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error log message using the current LOG_TAG.
+ */
+#ifndef ALOGE
+#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGE_IF
+#define ALOGE_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * verbose priority.
+ */
+#ifndef IF_ALOGV
+#if LOG_NDEBUG
+#define IF_ALOGV() if (false)
+#else
+#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
+#endif
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * debug priority.
+ */
+#ifndef IF_ALOGD
+#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * info priority.
+ */
+#ifndef IF_ALOGI
+#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * warn priority.
+ */
+#ifndef IF_ALOGW
+#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * error priority.
+ */
+#ifndef IF_ALOGE
+#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
+#endif
+
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose system log message using the current LOG_TAG.
+ */
+#ifndef SLOGV
+#if LOG_NDEBUG
+#define SLOGV(...) ((void)0)
+#else
+#define SLOGV(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond) (__builtin_expect((cond)!=0, 0))
+
+#ifndef SLOGV_IF
+#if LOG_NDEBUG
+#define SLOGV_IF(cond, ...) ((void)0)
+#else
+#define SLOGV_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug system log message using the current LOG_TAG.
+ */
+#ifndef SLOGD
+#define SLOGD(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGD_IF
+#define SLOGD_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info system log message using the current LOG_TAG.
+ */
+#ifndef SLOGI
+#define SLOGI(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGI_IF
+#define SLOGI_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning system log message using the current LOG_TAG.
+ */
+#ifndef SLOGW
+#define SLOGW(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGW_IF
+#define SLOGW_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error system log message using the current LOG_TAG.
+ */
+#ifndef SLOGE
+#define SLOGE(...) ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef SLOGE_IF
+#define SLOGE_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGV
+#if LOG_NDEBUG
+#define RLOGV(...) ((void)0)
+#else
+#define RLOGV(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond) (__builtin_expect((cond)!=0, 0))
+
+#ifndef RLOGV_IF
+#if LOG_NDEBUG
+#define RLOGV_IF(cond, ...) ((void)0)
+#else
+#define RLOGV_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGD
+#define RLOGD(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef RLOGD_IF
+#define RLOGD_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGI
+#define RLOGI(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef RLOGI_IF
+#define RLOGI_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGW
+#define RLOGW(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef RLOGW_IF
+#define RLOGW_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error radio log message using the current LOG_TAG.
+ */
+#ifndef RLOGE
+#define RLOGE(...) ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef RLOGE_IF
+#define RLOGE_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+
+// ---------------------------------------------------------------------
+
+/*
+ * Log a fatal error. If the given condition fails, this stops program
+ * execution like a normal assertion, but also generating the given message.
+ * It is NOT stripped from release builds. Note that the condition test
+ * is -inverted- from the normal assert() semantics.
+ */
+#ifndef LOG_ALWAYS_FATAL_IF
+#define LOG_ALWAYS_FATAL_IF(cond, ...) \
+ ( (CONDITION(cond)) \
+ ? ((void)android_printAssert(#cond, LOG_TAG, ## __VA_ARGS__)) \
+ : (void)0 )
+#endif
+
+#ifndef LOG_ALWAYS_FATAL
+#define LOG_ALWAYS_FATAL(...) \
+ ( ((void)android_printAssert(NULL, LOG_TAG, ## __VA_ARGS__)) )
+#endif
+
+/*
+ * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
+ * are stripped out of release builds.
+ */
+#if LOG_NDEBUG
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) ((void)0)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) ((void)0)
+#endif
+
+#else
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ## __VA_ARGS__)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
+#endif
+
+#endif
+
+/*
+ * Assertion that generates a log message when the assertion fails.
+ * Stripped out of release builds. Uses the current LOG_TAG.
+ */
+#ifndef ALOG_ASSERT
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__)
+//#define ALOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Basic log message macro.
+ *
+ * Example:
+ * ALOG(LOG_WARN, NULL, "Failed with error %d", errno);
+ *
+ * The second argument may be NULL or "" to indicate the "global" tag.
+ */
+#ifndef ALOG
+#define ALOG(priority, tag, ...) \
+ LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef LOG_PRI
+#define LOG_PRI(priority, tag, ...) \
+ android_printLog(priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to pass in a varargs ("args" is a va_list).
+ */
+#ifndef LOG_PRI_VA
+#define LOG_PRI_VA(priority, tag, fmt, args) \
+ android_vprintLog(priority, NULL, tag, fmt, args)
+#endif
+
+/*
+ * Conditional given a desired logging priority and tag.
+ */
+#ifndef IF_ALOG
+#define IF_ALOG(priority, tag) \
+ if (android_testLog(ANDROID_##priority, tag))
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Event logging.
+ */
+
+/*
+ * Event log entry types. These must match up with the declarations in
+ * java/android/android/util/EventLog.java.
+ */
+typedef enum {
+ EVENT_TYPE_INT = 0,
+ EVENT_TYPE_LONG = 1,
+ EVENT_TYPE_STRING = 2,
+ EVENT_TYPE_LIST = 3,
+} AndroidEventLogType;
+
+
+#ifndef LOG_EVENT_INT
+#define LOG_EVENT_INT(_tag, _value) { \
+ int intBuf = _value; \
+ (void) android_btWriteLog(_tag, EVENT_TYPE_INT, &intBuf, \
+ sizeof(intBuf)); \
+ }
+#endif
+#ifndef LOG_EVENT_LONG
+#define LOG_EVENT_LONG(_tag, _value) { \
+ long long longBuf = _value; \
+ (void) android_btWriteLog(_tag, EVENT_TYPE_LONG, &longBuf, \
+ sizeof(longBuf)); \
+ }
+#endif
+#ifndef LOG_EVENT_STRING
+#define LOG_EVENT_STRING(_tag, _value) \
+ ((void) 0) /* not implemented -- must combine len with string */
+#endif
+/* TODO: something for LIST */
+
+/*
+ * ===========================================================================
+ *
+ * The stuff in the rest of this file should not be used directly.
+ */
+
+#define android_printLog(prio, tag, fmt...) \
+ __android_log_print(prio, tag, fmt)
+
+#define android_vprintLog(prio, cond, tag, fmt...) \
+ __android_log_vprint(prio, tag, fmt)
+
+/* XXX Macros to work around syntax errors in places where format string
+ * arg is not passed to ALOG_ASSERT, LOG_ALWAYS_FATAL or LOG_ALWAYS_FATAL_IF
+ * (happens only in debug builds).
+ */
+
+/* Returns 2nd arg. Used to substitute default value if caller's vararg list
+ * is empty.
+ */
+#define __android_second(dummy, second, ...) second
+
+/* If passed multiple args, returns ',' followed by all but 1st arg, otherwise
+ * returns nothing.
+ */
+#define __android_rest(first, ...) , ## __VA_ARGS__
+
+#define android_printAssert(cond, tag, fmt...) \
+ __android_log_assert(cond, tag, \
+ __android_second(0, ## fmt, NULL) __android_rest(fmt))
+
+#define android_writeLog(prio, tag, text) \
+ __android_log_write(prio, tag, text)
+
+#define android_bWriteLog(tag, payload, len) \
+ __android_log_bwrite(tag, payload, len)
+#define android_btWriteLog(tag, type, payload, len) \
+ __android_log_btwrite(tag, type, payload, len)
+
+// TODO: remove these prototypes and their users
+#define android_testLog(prio, tag) (1)
+#define android_writevLog(vec,num) do{}while(0)
+#define android_write1Log(str,len) do{}while (0)
+#define android_setMinPriority(tag, prio) do{}while(0)
+//#define android_logToCallback(func) do{}while(0)
+#define android_logToFile(tag, file) (0)
+#define android_logToFd(tag, fd) (0)
+
+typedef enum {
+ LOG_ID_MAIN = 0,
+ LOG_ID_RADIO = 1,
+ LOG_ID_EVENTS = 2,
+ LOG_ID_SYSTEM = 3,
+
+ LOG_ID_MAX
+} log_id_t;
+
+/*
+ * Send a simple string to the log.
+ */
+int __android_log_buf_write(int bufID, int prio, const char *tag, const char *text);
+int __android_log_buf_print(int bufID, int prio, const char *tag, const char *fmt, ...);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _LIBS_CUTILS_LOG_H
diff --git a/widget/gonk/libui/cutils_trace.h b/widget/gonk/libui/cutils_trace.h
new file mode 100644
index 0000000000..29034cab5f
--- /dev/null
+++ b/widget/gonk/libui/cutils_trace.h
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef _LIBS_CUTILS_TRACE_H
+#define _LIBS_CUTILS_TRACE_H
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <cutils/compiler.h>
+
+#ifdef ANDROID_SMP
+#include <cutils/atomic-inline.h>
+#else
+#include <cutils/atomic.h>
+#endif
+
+__BEGIN_DECLS
+
+/**
+ * The ATRACE_TAG macro can be defined before including this header to trace
+ * using one of the tags defined below. It must be defined to one of the
+ * following ATRACE_TAG_* macros. The trace tag is used to filter tracing in
+ * userland to avoid some of the runtime cost of tracing when it is not desired.
+ *
+ * Defining ATRACE_TAG to be ATRACE_TAG_ALWAYS will result in the tracing always
+ * being enabled - this should ONLY be done for debug code, as userland tracing
+ * has a performance cost even when the trace is not being recorded. Defining
+ * ATRACE_TAG to be ATRACE_TAG_NEVER or leaving ATRACE_TAG undefined will result
+ * in the tracing always being disabled.
+ *
+ * ATRACE_TAG_HAL should be bitwise ORed with the relevant tags for tracing
+ * within a hardware module. For example a camera hardware module would set:
+ * #define ATRACE_TAG (ATRACE_TAG_CAMERA | ATRACE_TAG_HAL)
+ *
+ * Keep these in sync with frameworks/base/core/java/android/os/Trace.java.
+ */
+#define ATRACE_TAG_NEVER 0 // This tag is never enabled.
+#define ATRACE_TAG_ALWAYS (1<<0) // This tag is always enabled.
+#define ATRACE_TAG_GRAPHICS (1<<1)
+#define ATRACE_TAG_INPUT (1<<2)
+#define ATRACE_TAG_VIEW (1<<3)
+#define ATRACE_TAG_WEBVIEW (1<<4)
+#define ATRACE_TAG_WINDOW_MANAGER (1<<5)
+#define ATRACE_TAG_ACTIVITY_MANAGER (1<<6)
+#define ATRACE_TAG_SYNC_MANAGER (1<<7)
+#define ATRACE_TAG_AUDIO (1<<8)
+#define ATRACE_TAG_VIDEO (1<<9)
+#define ATRACE_TAG_CAMERA (1<<10)
+#define ATRACE_TAG_HAL (1<<11)
+#define ATRACE_TAG_APP (1<<12)
+#define ATRACE_TAG_RESOURCES (1<<13)
+#define ATRACE_TAG_DALVIK (1<<14)
+#define ATRACE_TAG_LAST ATRACE_TAG_DALVIK
+
+// Reserved for initialization.
+#define ATRACE_TAG_NOT_READY (1LL<<63)
+
+#define ATRACE_TAG_VALID_MASK ((ATRACE_TAG_LAST - 1) | ATRACE_TAG_LAST)
+
+#ifndef ATRACE_TAG
+#define ATRACE_TAG ATRACE_TAG_NEVER
+#elif ATRACE_TAG > ATRACE_TAG_VALID_MASK
+#error ATRACE_TAG must be defined to be one of the tags defined in cutils/trace.h
+#endif
+
+#ifdef HAVE_ANDROID_OS
+/**
+ * Maximum size of a message that can be logged to the trace buffer.
+ * Note this message includes a tag, the pid, and the string given as the name.
+ * Names should be kept short to get the most use of the trace buffer.
+ */
+#define ATRACE_MESSAGE_LENGTH 1024
+
+/**
+ * Opens the trace file for writing and reads the property for initial tags.
+ * The atrace.tags.enableflags property sets the tags to trace.
+ * This function should not be explicitly called, the first call to any normal
+ * trace function will cause it to be run safely.
+ */
+void atrace_setup();
+
+/**
+ * If tracing is ready, set atrace_enabled_tags to the system property
+ * debug.atrace.tags.enableflags. Can be used as a sysprop change callback.
+ */
+void atrace_update_tags();
+
+/**
+ * Set whether the process is debuggable. By default the process is not
+ * considered debuggable. If the process is not debuggable then application-
+ * level tracing is not allowed unless the ro.debuggable system property is
+ * set to '1'.
+ */
+void atrace_set_debuggable(bool debuggable);
+
+/**
+ * Set whether tracing is enabled for the current process. This is used to
+ * prevent tracing within the Zygote process.
+ */
+void atrace_set_tracing_enabled(bool enabled);
+
+/**
+ * Flag indicating whether setup has been completed, initialized to 0.
+ * Nonzero indicates setup has completed.
+ * Note: This does NOT indicate whether or not setup was successful.
+ */
+extern volatile int32_t atrace_is_ready;
+
+/**
+ * Set of ATRACE_TAG flags to trace for, initialized to ATRACE_TAG_NOT_READY.
+ * A value of zero indicates setup has failed.
+ * Any other nonzero value indicates setup has succeeded, and tracing is on.
+ */
+extern uint64_t atrace_enabled_tags;
+
+/**
+ * Handle to the kernel's trace buffer, initialized to -1.
+ * Any other value indicates setup has succeeded, and is a valid fd for tracing.
+ */
+extern int atrace_marker_fd;
+
+/**
+ * atrace_init readies the process for tracing by opening the trace_marker file.
+ * Calling any trace function causes this to be run, so calling it is optional.
+ * This can be explicitly run to avoid setup delay on first trace function.
+ */
+#define ATRACE_INIT() atrace_init()
+static inline void atrace_init()
+{
+ if (CC_UNLIKELY(!android_atomic_acquire_load(&atrace_is_ready))) {
+ atrace_setup();
+ }
+}
+
+/**
+ * Get the mask of all tags currently enabled.
+ * It can be used as a guard condition around more expensive trace calculations.
+ * Every trace function calls this, which ensures atrace_init is run.
+ */
+#define ATRACE_GET_ENABLED_TAGS() atrace_get_enabled_tags()
+static inline uint64_t atrace_get_enabled_tags()
+{
+ atrace_init();
+ return atrace_enabled_tags;
+}
+
+/**
+ * Test if a given tag is currently enabled.
+ * Returns nonzero if the tag is enabled, otherwise zero.
+ * It can be used as a guard condition around more expensive trace calculations.
+ */
+#define ATRACE_ENABLED() atrace_is_tag_enabled(ATRACE_TAG)
+static inline uint64_t atrace_is_tag_enabled(uint64_t tag)
+{
+ return atrace_get_enabled_tags() & tag;
+}
+
+/**
+ * Trace the beginning of a context. name is used to identify the context.
+ * This is often used to time function execution.
+ */
+#define ATRACE_BEGIN(name) atrace_begin(ATRACE_TAG, name)
+static inline void atrace_begin(uint64_t tag, const char* name)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char buf[ATRACE_MESSAGE_LENGTH];
+ size_t len;
+
+ len = snprintf(buf, ATRACE_MESSAGE_LENGTH, "B|%d|%s", getpid(), name);
+ write(atrace_marker_fd, buf, len);
+ }
+}
+
+/**
+ * Trace the end of a context.
+ * This should match up (and occur after) a corresponding ATRACE_BEGIN.
+ */
+#define ATRACE_END() atrace_end(ATRACE_TAG)
+static inline void atrace_end(uint64_t tag)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char c = 'E';
+ write(atrace_marker_fd, &c, 1);
+ }
+}
+
+/**
+ * Trace the beginning of an asynchronous event. Unlike ATRACE_BEGIN/ATRACE_END
+ * contexts, asynchronous events do not need to be nested. The name describes
+ * the event, and the cookie provides a unique identifier for distinguishing
+ * simultaneous events. The name and cookie used to begin an event must be
+ * used to end it.
+ */
+#define ATRACE_ASYNC_BEGIN(name, cookie) \
+ atrace_async_begin(ATRACE_TAG, name, cookie)
+static inline void atrace_async_begin(uint64_t tag, const char* name,
+ int32_t cookie)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char buf[ATRACE_MESSAGE_LENGTH];
+ size_t len;
+
+ len = snprintf(buf, ATRACE_MESSAGE_LENGTH, "S|%d|%s|%d", getpid(),
+ name, cookie);
+ write(atrace_marker_fd, buf, len);
+ }
+}
+
+/**
+ * Trace the end of an asynchronous event.
+ * This should have a corresponding ATRACE_ASYNC_BEGIN.
+ */
+#define ATRACE_ASYNC_END(name, cookie) atrace_async_end(ATRACE_TAG, name, cookie)
+static inline void atrace_async_end(uint64_t tag, const char* name,
+ int32_t cookie)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char buf[ATRACE_MESSAGE_LENGTH];
+ size_t len;
+
+ len = snprintf(buf, ATRACE_MESSAGE_LENGTH, "F|%d|%s|%d", getpid(),
+ name, cookie);
+ write(atrace_marker_fd, buf, len);
+ }
+}
+
+
+/**
+ * Traces an integer counter value. name is used to identify the counter.
+ * This can be used to track how a value changes over time.
+ */
+#define ATRACE_INT(name, value) atrace_int(ATRACE_TAG, name, value)
+static inline void atrace_int(uint64_t tag, const char* name, int32_t value)
+{
+ if (CC_UNLIKELY(atrace_is_tag_enabled(tag))) {
+ char buf[ATRACE_MESSAGE_LENGTH];
+ size_t len;
+
+ len = snprintf(buf, ATRACE_MESSAGE_LENGTH, "C|%d|%s|%d",
+ getpid(), name, value);
+ write(atrace_marker_fd, buf, len);
+ }
+}
+
+#else // not HAVE_ANDROID_OS
+
+#define ATRACE_INIT()
+#define ATRACE_GET_ENABLED_TAGS()
+#define ATRACE_ENABLED()
+#define ATRACE_BEGIN(name)
+#define ATRACE_END()
+#define ATRACE_ASYNC_BEGIN(name, cookie)
+#define ATRACE_ASYNC_END(name, cookie)
+#define ATRACE_INT(name, value)
+
+#endif // not HAVE_ANDROID_OS
+
+__END_DECLS
+
+#endif // _LIBS_CUTILS_TRACE_H
diff --git a/widget/gonk/libui/linux_input.h b/widget/gonk/libui/linux_input.h
new file mode 100644
index 0000000000..2ba14973f5
--- /dev/null
+++ b/widget/gonk/libui/linux_input.h
@@ -0,0 +1,1029 @@
+/****************************************************************************
+ ****************************************************************************
+ ***
+ *** This header was automatically generated from a Linux kernel header
+ *** of the same name, to make information necessary for userspace to
+ *** call into the kernel available to libc. It contains only constants,
+ *** structures, and macros generated from the original header, and thus,
+ *** contains no copyrightable information.
+ ***
+ *** To edit the content of this header, modify the corresponding
+ *** source file (e.g. under external/kernel-headers/original/) then
+ *** run bionic/libc/kernel/tools/update_all.py
+ ***
+ *** Any manual change here will be lost the next time this script will
+ *** be run. You've been warned!
+ ***
+ ****************************************************************************
+ ****************************************************************************/
+#ifndef _INPUT_H
+#define _INPUT_H
+#include <sys/time.h>
+#include <sys/ioctl.h>
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#include <sys/types.h>
+#include <linux/types.h>
+struct input_event {
+ struct timeval time;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 type;
+ __u16 code;
+ __s32 value;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_VERSION 0x010001
+struct input_id {
+ __u16 bustype;
+ __u16 vendor;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 product;
+ __u16 version;
+};
+struct input_absinfo {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __s32 value;
+ __s32 minimum;
+ __s32 maximum;
+ __s32 fuzz;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __s32 flat;
+ __s32 resolution;
+};
+struct input_keymap_entry {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define INPUT_KEYMAP_BY_INDEX (1 << 0)
+ __u8 flags;
+ __u8 len;
+ __u16 index;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u32 keycode;
+ __u8 scancode[32];
+};
+#define EVIOCGVERSION _IOR('E', 0x01, int)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGID _IOR('E', 0x02, struct input_id)
+#define EVIOCGREP _IOR('E', 0x03, unsigned int[2])
+#define EVIOCSREP _IOW('E', 0x03, unsigned int[2])
+#define EVIOCGKEYCODE _IOR('E', 0x04, unsigned int[2])
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGKEYCODE_V2 _IOR('E', 0x04, struct input_keymap_entry)
+#define EVIOCSKEYCODE _IOW('E', 0x04, unsigned int[2])
+#define EVIOCSKEYCODE_V2 _IOW('E', 0x04, struct input_keymap_entry)
+#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGPHYS(len) _IOC(_IOC_READ, 'E', 0x07, len)
+#define EVIOCGUNIQ(len) _IOC(_IOC_READ, 'E', 0x08, len)
+#define EVIOCGPROP(len) _IOC(_IOC_READ, 'E', 0x09, len)
+#define EVIOCGMTSLOTS(len) _IOC(_IOC_READ, 'E', 0x0a, len)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len)
+#define EVIOCGLED(len) _IOC(_IOC_READ, 'E', 0x19, len)
+#define EVIOCGSND(len) _IOC(_IOC_READ, 'E', 0x1a, len)
+#define EVIOCGSW(len) _IOC(_IOC_READ, 'E', 0x1b, len)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len)
+#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo)
+#define EVIOCSABS(abs) _IOW('E', 0xc0 + (abs), struct input_absinfo)
+#define EVIOCSFF _IOC(_IOC_WRITE, 'E', 0x80, sizeof(struct ff_effect))
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCRMFF _IOW('E', 0x81, int)
+#define EVIOCGEFFECTS _IOR('E', 0x84, int)
+#define EVIOCGRAB _IOW('E', 0x90, int)
+#define EVIOCGSUSPENDBLOCK _IOR('E', 0x91, int)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EVIOCSSUSPENDBLOCK _IOW('E', 0x91, int)
+#define EVIOCSCLOCKID _IOW('E', 0xa0, int)
+#define INPUT_PROP_POINTER 0x00
+#define INPUT_PROP_DIRECT 0x01
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define INPUT_PROP_BUTTONPAD 0x02
+#define INPUT_PROP_SEMI_MT 0x03
+#define INPUT_PROP_MAX 0x1f
+#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_SYN 0x00
+#define EV_KEY 0x01
+#define EV_REL 0x02
+#define EV_ABS 0x03
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_MSC 0x04
+#define EV_SW 0x05
+#define EV_LED 0x11
+#define EV_SND 0x12
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_REP 0x14
+#define EV_FF 0x15
+#define EV_PWR 0x16
+#define EV_FF_STATUS 0x17
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define EV_MAX 0x1f
+#define EV_CNT (EV_MAX+1)
+#define SYN_REPORT 0
+#define SYN_CONFIG 1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SYN_MT_REPORT 2
+#define SYN_DROPPED 3
+#define SYN_TIME_SEC 4
+#define SYN_TIME_NSEC 5
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RESERVED 0
+#define KEY_ESC 1
+#define KEY_1 2
+#define KEY_2 3
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_3 4
+#define KEY_4 5
+#define KEY_5 6
+#define KEY_6 7
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_7 8
+#define KEY_8 9
+#define KEY_9 10
+#define KEY_0 11
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MINUS 12
+#define KEY_EQUAL 13
+#define KEY_BACKSPACE 14
+#define KEY_TAB 15
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_Q 16
+#define KEY_W 17
+#define KEY_E 18
+#define KEY_R 19
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_T 20
+#define KEY_Y 21
+#define KEY_U 22
+#define KEY_I 23
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_O 24
+#define KEY_P 25
+#define KEY_LEFTBRACE 26
+#define KEY_RIGHTBRACE 27
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_ENTER 28
+#define KEY_LEFTCTRL 29
+#define KEY_A 30
+#define KEY_S 31
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_D 32
+#define KEY_F 33
+#define KEY_G 34
+#define KEY_H 35
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_J 36
+#define KEY_K 37
+#define KEY_L 38
+#define KEY_SEMICOLON 39
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_APOSTROPHE 40
+#define KEY_GRAVE 41
+#define KEY_LEFTSHIFT 42
+#define KEY_BACKSLASH 43
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_Z 44
+#define KEY_X 45
+#define KEY_C 46
+#define KEY_V 47
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_B 48
+#define KEY_N 49
+#define KEY_M 50
+#define KEY_COMMA 51
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DOT 52
+#define KEY_SLASH 53
+#define KEY_RIGHTSHIFT 54
+#define KEY_KPASTERISK 55
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_LEFTALT 56
+#define KEY_SPACE 57
+#define KEY_CAPSLOCK 58
+#define KEY_F1 59
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F2 60
+#define KEY_F3 61
+#define KEY_F4 62
+#define KEY_F5 63
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F6 64
+#define KEY_F7 65
+#define KEY_F8 66
+#define KEY_F9 67
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F10 68
+#define KEY_NUMLOCK 69
+#define KEY_SCROLLLOCK 70
+#define KEY_KP7 71
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KP8 72
+#define KEY_KP9 73
+#define KEY_KPMINUS 74
+#define KEY_KP4 75
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KP5 76
+#define KEY_KP6 77
+#define KEY_KPPLUS 78
+#define KEY_KP1 79
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KP2 80
+#define KEY_KP3 81
+#define KEY_KP0 82
+#define KEY_KPDOT 83
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_ZENKAKUHANKAKU 85
+#define KEY_102ND 86
+#define KEY_F11 87
+#define KEY_F12 88
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RO 89
+#define KEY_KATAKANA 90
+#define KEY_HIRAGANA 91
+#define KEY_HENKAN 92
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KATAKANAHIRAGANA 93
+#define KEY_MUHENKAN 94
+#define KEY_KPJPCOMMA 95
+#define KEY_KPENTER 96
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RIGHTCTRL 97
+#define KEY_KPSLASH 98
+#define KEY_SYSRQ 99
+#define KEY_RIGHTALT 100
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_LINEFEED 101
+#define KEY_HOME 102
+#define KEY_UP 103
+#define KEY_PAGEUP 104
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_LEFT 105
+#define KEY_RIGHT 106
+#define KEY_END 107
+#define KEY_DOWN 108
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PAGEDOWN 109
+#define KEY_INSERT 110
+#define KEY_DELETE 111
+#define KEY_MACRO 112
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MUTE 113
+#define KEY_VOLUMEDOWN 114
+#define KEY_VOLUMEUP 115
+#define KEY_POWER 116
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KPEQUAL 117
+#define KEY_KPPLUSMINUS 118
+#define KEY_PAUSE 119
+#define KEY_SCALE 120
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KPCOMMA 121
+#define KEY_HANGEUL 122
+#define KEY_HANGUEL KEY_HANGEUL
+#define KEY_HANJA 123
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_YEN 124
+#define KEY_LEFTMETA 125
+#define KEY_RIGHTMETA 126
+#define KEY_COMPOSE 127
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_STOP 128
+#define KEY_AGAIN 129
+#define KEY_PROPS 130
+#define KEY_UNDO 131
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FRONT 132
+#define KEY_COPY 133
+#define KEY_OPEN 134
+#define KEY_PASTE 135
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FIND 136
+#define KEY_CUT 137
+#define KEY_HELP 138
+#define KEY_MENU 139
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CALC 140
+#define KEY_SETUP 141
+#define KEY_SLEEP 142
+#define KEY_WAKEUP 143
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FILE 144
+#define KEY_SENDFILE 145
+#define KEY_DELETEFILE 146
+#define KEY_XFER 147
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PROG1 148
+#define KEY_PROG2 149
+#define KEY_WWW 150
+#define KEY_MSDOS 151
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_COFFEE 152
+#define KEY_SCREENLOCK KEY_COFFEE
+#define KEY_DIRECTION 153
+#define KEY_CYCLEWINDOWS 154
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MAIL 155
+#define KEY_BOOKMARKS 156
+#define KEY_COMPUTER 157
+#define KEY_BACK 158
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FORWARD 159
+#define KEY_CLOSECD 160
+#define KEY_EJECTCD 161
+#define KEY_EJECTCLOSECD 162
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_NEXTSONG 163
+#define KEY_PLAYPAUSE 164
+#define KEY_PREVIOUSSONG 165
+#define KEY_STOPCD 166
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RECORD 167
+#define KEY_REWIND 168
+#define KEY_PHONE 169
+#define KEY_ISO 170
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CONFIG 171
+#define KEY_HOMEPAGE 172
+#define KEY_REFRESH 173
+#define KEY_EXIT 174
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MOVE 175
+#define KEY_EDIT 176
+#define KEY_SCROLLUP 177
+#define KEY_SCROLLDOWN 178
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KPLEFTPAREN 179
+#define KEY_KPRIGHTPAREN 180
+#define KEY_NEW 181
+#define KEY_REDO 182
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F13 183
+#define KEY_F14 184
+#define KEY_F15 185
+#define KEY_F16 186
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F17 187
+#define KEY_F18 188
+#define KEY_F19 189
+#define KEY_F20 190
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_F21 191
+#define KEY_F22 192
+#define KEY_F23 193
+#define KEY_F24 194
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PLAYCD 200
+#define KEY_PAUSECD 201
+#define KEY_PROG3 202
+#define KEY_PROG4 203
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DASHBOARD 204
+#define KEY_SUSPEND 205
+#define KEY_CLOSE 206
+#define KEY_PLAY 207
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FASTFORWARD 208
+#define KEY_BASSBOOST 209
+#define KEY_PRINT 210
+#define KEY_HP 211
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CAMERA 212
+#define KEY_SOUND 213
+#define KEY_QUESTION 214
+#define KEY_EMAIL 215
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CHAT 216
+#define KEY_SEARCH 217
+#define KEY_CONNECT 218
+#define KEY_FINANCE 219
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_SPORT 220
+#define KEY_SHOP 221
+#define KEY_ALTERASE 222
+#define KEY_CANCEL 223
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BRIGHTNESSDOWN 224
+#define KEY_BRIGHTNESSUP 225
+#define KEY_MEDIA 226
+#define KEY_SWITCHVIDEOMODE 227
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KBDILLUMTOGGLE 228
+#define KEY_KBDILLUMDOWN 229
+#define KEY_KBDILLUMUP 230
+#define KEY_SEND 231
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_REPLY 232
+#define KEY_FORWARDMAIL 233
+#define KEY_SAVE 234
+#define KEY_DOCUMENTS 235
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BATTERY 236
+#define KEY_BLUETOOTH 237
+#define KEY_WLAN 238
+#define KEY_UWB 239
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_UNKNOWN 240
+#define KEY_VIDEO_NEXT 241
+#define KEY_VIDEO_PREV 242
+#define KEY_BRIGHTNESS_CYCLE 243
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BRIGHTNESS_ZERO 244
+#define KEY_DISPLAY_OFF 245
+#define KEY_WIMAX 246
+#define KEY_RFKILL 247
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MICMUTE 248
+#define BTN_MISC 0x100
+#define BTN_0 0x100
+#define BTN_1 0x101
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_2 0x102
+#define BTN_3 0x103
+#define BTN_4 0x104
+#define BTN_5 0x105
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_6 0x106
+#define BTN_7 0x107
+#define BTN_8 0x108
+#define BTN_9 0x109
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_MOUSE 0x110
+#define BTN_LEFT 0x110
+#define BTN_RIGHT 0x111
+#define BTN_MIDDLE 0x112
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_SIDE 0x113
+#define BTN_EXTRA 0x114
+#define BTN_FORWARD 0x115
+#define BTN_BACK 0x116
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TASK 0x117
+#define BTN_JOYSTICK 0x120
+#define BTN_TRIGGER 0x120
+#define BTN_THUMB 0x121
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_THUMB2 0x122
+#define BTN_TOP 0x123
+#define BTN_TOP2 0x124
+#define BTN_PINKIE 0x125
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_BASE 0x126
+#define BTN_BASE2 0x127
+#define BTN_BASE3 0x128
+#define BTN_BASE4 0x129
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_BASE5 0x12a
+#define BTN_BASE6 0x12b
+#define BTN_DEAD 0x12f
+#define BTN_GAMEPAD 0x130
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_A 0x130
+#define BTN_B 0x131
+#define BTN_C 0x132
+#define BTN_X 0x133
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_Y 0x134
+#define BTN_Z 0x135
+#define BTN_TL 0x136
+#define BTN_TR 0x137
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TL2 0x138
+#define BTN_TR2 0x139
+#define BTN_SELECT 0x13a
+#define BTN_START 0x13b
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_MODE 0x13c
+#define BTN_THUMBL 0x13d
+#define BTN_THUMBR 0x13e
+#define BTN_DIGI 0x140
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TOOL_PEN 0x140
+#define BTN_TOOL_RUBBER 0x141
+#define BTN_TOOL_BRUSH 0x142
+#define BTN_TOOL_PENCIL 0x143
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TOOL_AIRBRUSH 0x144
+#define BTN_TOOL_FINGER 0x145
+#define BTN_TOOL_MOUSE 0x146
+#define BTN_TOOL_LENS 0x147
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TOOL_QUINTTAP 0x148
+#define BTN_TOUCH 0x14a
+#define BTN_STYLUS 0x14b
+#define BTN_STYLUS2 0x14c
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TOOL_DOUBLETAP 0x14d
+#define BTN_TOOL_TRIPLETAP 0x14e
+#define BTN_TOOL_QUADTAP 0x14f
+#define BTN_WHEEL 0x150
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_GEAR_DOWN 0x150
+#define BTN_GEAR_UP 0x151
+#define KEY_OK 0x160
+#define KEY_SELECT 0x161
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_GOTO 0x162
+#define KEY_CLEAR 0x163
+#define KEY_POWER2 0x164
+#define KEY_OPTION 0x165
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_INFO 0x166
+#define KEY_TIME 0x167
+#define KEY_VENDOR 0x168
+#define KEY_ARCHIVE 0x169
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PROGRAM 0x16a
+#define KEY_CHANNEL 0x16b
+#define KEY_FAVORITES 0x16c
+#define KEY_EPG 0x16d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_PVR 0x16e
+#define KEY_MHP 0x16f
+#define KEY_LANGUAGE 0x170
+#define KEY_TITLE 0x171
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_SUBTITLE 0x172
+#define KEY_ANGLE 0x173
+#define KEY_ZOOM 0x174
+#define KEY_MODE 0x175
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_KEYBOARD 0x176
+#define KEY_SCREEN 0x177
+#define KEY_PC 0x178
+#define KEY_TV 0x179
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_TV2 0x17a
+#define KEY_VCR 0x17b
+#define KEY_VCR2 0x17c
+#define KEY_SAT 0x17d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_SAT2 0x17e
+#define KEY_CD 0x17f
+#define KEY_TAPE 0x180
+#define KEY_RADIO 0x181
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_TUNER 0x182
+#define KEY_PLAYER 0x183
+#define KEY_TEXT 0x184
+#define KEY_DVD 0x185
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_AUX 0x186
+#define KEY_MP3 0x187
+#define KEY_AUDIO 0x188
+#define KEY_VIDEO 0x189
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DIRECTORY 0x18a
+#define KEY_LIST 0x18b
+#define KEY_MEMO 0x18c
+#define KEY_CALENDAR 0x18d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_RED 0x18e
+#define KEY_GREEN 0x18f
+#define KEY_YELLOW 0x190
+#define KEY_BLUE 0x191
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CHANNELUP 0x192
+#define KEY_CHANNELDOWN 0x193
+#define KEY_FIRST 0x194
+#define KEY_LAST 0x195
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_AB 0x196
+#define KEY_NEXT 0x197
+#define KEY_RESTART 0x198
+#define KEY_SLOW 0x199
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_SHUFFLE 0x19a
+#define KEY_BREAK 0x19b
+#define KEY_PREVIOUS 0x19c
+#define KEY_DIGITS 0x19d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_TEEN 0x19e
+#define KEY_TWEN 0x19f
+#define KEY_VIDEOPHONE 0x1a0
+#define KEY_GAMES 0x1a1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_ZOOMIN 0x1a2
+#define KEY_ZOOMOUT 0x1a3
+#define KEY_ZOOMRESET 0x1a4
+#define KEY_WORDPROCESSOR 0x1a5
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_EDITOR 0x1a6
+#define KEY_SPREADSHEET 0x1a7
+#define KEY_GRAPHICSEDITOR 0x1a8
+#define KEY_PRESENTATION 0x1a9
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DATABASE 0x1aa
+#define KEY_NEWS 0x1ab
+#define KEY_VOICEMAIL 0x1ac
+#define KEY_ADDRESSBOOK 0x1ad
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_MESSENGER 0x1ae
+#define KEY_DISPLAYTOGGLE 0x1af
+#define KEY_SPELLCHECK 0x1b0
+#define KEY_LOGOFF 0x1b1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DOLLAR 0x1b2
+#define KEY_EURO 0x1b3
+#define KEY_FRAMEBACK 0x1b4
+#define KEY_FRAMEFORWARD 0x1b5
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CONTEXT_MENU 0x1b6
+#define KEY_MEDIA_REPEAT 0x1b7
+#define KEY_10CHANNELSUP 0x1b8
+#define KEY_10CHANNELSDOWN 0x1b9
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_IMAGES 0x1ba
+#define KEY_DEL_EOL 0x1c0
+#define KEY_DEL_EOS 0x1c1
+#define KEY_INS_LINE 0x1c2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_DEL_LINE 0x1c3
+#define KEY_FN 0x1d0
+#define KEY_FN_ESC 0x1d1
+#define KEY_FN_F1 0x1d2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_F2 0x1d3
+#define KEY_FN_F3 0x1d4
+#define KEY_FN_F4 0x1d5
+#define KEY_FN_F5 0x1d6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_F6 0x1d7
+#define KEY_FN_F7 0x1d8
+#define KEY_FN_F8 0x1d9
+#define KEY_FN_F9 0x1da
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_F10 0x1db
+#define KEY_FN_F11 0x1dc
+#define KEY_FN_F12 0x1dd
+#define KEY_FN_1 0x1de
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_2 0x1df
+#define KEY_FN_D 0x1e0
+#define KEY_FN_E 0x1e1
+#define KEY_FN_F 0x1e2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_FN_S 0x1e3
+#define KEY_FN_B 0x1e4
+#define KEY_BRL_DOT1 0x1f1
+#define KEY_BRL_DOT2 0x1f2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BRL_DOT3 0x1f3
+#define KEY_BRL_DOT4 0x1f4
+#define KEY_BRL_DOT5 0x1f5
+#define KEY_BRL_DOT6 0x1f6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_BRL_DOT7 0x1f7
+#define KEY_BRL_DOT8 0x1f8
+#define KEY_BRL_DOT9 0x1f9
+#define KEY_BRL_DOT10 0x1fa
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_NUMERIC_0 0x200
+#define KEY_NUMERIC_1 0x201
+#define KEY_NUMERIC_2 0x202
+#define KEY_NUMERIC_3 0x203
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_NUMERIC_4 0x204
+#define KEY_NUMERIC_5 0x205
+#define KEY_NUMERIC_6 0x206
+#define KEY_NUMERIC_7 0x207
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_NUMERIC_8 0x208
+#define KEY_NUMERIC_9 0x209
+#define KEY_NUMERIC_STAR 0x20a
+#define KEY_NUMERIC_POUND 0x20b
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CAMERA_SNAPSHOT 0x2fe
+#define KEY_CAMERA_FOCUS 0x210
+#define KEY_WPS_BUTTON 0x211
+#define KEY_TOUCHPAD_TOGGLE 0x212
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_TOUCHPAD_ON 0x213
+#define KEY_TOUCHPAD_OFF 0x214
+#define KEY_CAMERA_ZOOMIN 0x215
+#define KEY_CAMERA_ZOOMOUT 0x216
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define KEY_CAMERA_UP 0x217
+#define KEY_CAMERA_DOWN 0x218
+#define KEY_CAMERA_LEFT 0x219
+#define KEY_CAMERA_RIGHT 0x21a
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY 0x2c0
+#define BTN_TRIGGER_HAPPY1 0x2c0
+#define BTN_TRIGGER_HAPPY2 0x2c1
+#define BTN_TRIGGER_HAPPY3 0x2c2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY4 0x2c3
+#define BTN_TRIGGER_HAPPY5 0x2c4
+#define BTN_TRIGGER_HAPPY6 0x2c5
+#define BTN_TRIGGER_HAPPY7 0x2c6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY8 0x2c7
+#define BTN_TRIGGER_HAPPY9 0x2c8
+#define BTN_TRIGGER_HAPPY10 0x2c9
+#define BTN_TRIGGER_HAPPY11 0x2ca
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY12 0x2cb
+#define BTN_TRIGGER_HAPPY13 0x2cc
+#define BTN_TRIGGER_HAPPY14 0x2cd
+#define BTN_TRIGGER_HAPPY15 0x2ce
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY16 0x2cf
+#define BTN_TRIGGER_HAPPY17 0x2d0
+#define BTN_TRIGGER_HAPPY18 0x2d1
+#define BTN_TRIGGER_HAPPY19 0x2d2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY20 0x2d3
+#define BTN_TRIGGER_HAPPY21 0x2d4
+#define BTN_TRIGGER_HAPPY22 0x2d5
+#define BTN_TRIGGER_HAPPY23 0x2d6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY24 0x2d7
+#define BTN_TRIGGER_HAPPY25 0x2d8
+#define BTN_TRIGGER_HAPPY26 0x2d9
+#define BTN_TRIGGER_HAPPY27 0x2da
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY28 0x2db
+#define BTN_TRIGGER_HAPPY29 0x2dc
+#define BTN_TRIGGER_HAPPY30 0x2dd
+#define BTN_TRIGGER_HAPPY31 0x2de
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY32 0x2df
+#define BTN_TRIGGER_HAPPY33 0x2e0
+#define BTN_TRIGGER_HAPPY34 0x2e1
+#define BTN_TRIGGER_HAPPY35 0x2e2
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY36 0x2e3
+#define BTN_TRIGGER_HAPPY37 0x2e4
+#define BTN_TRIGGER_HAPPY38 0x2e5
+#define BTN_TRIGGER_HAPPY39 0x2e6
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BTN_TRIGGER_HAPPY40 0x2e7
+#define KEY_MIN_INTERESTING KEY_MUTE
+#define KEY_MAX 0x2ff
+#define KEY_CNT (KEY_MAX+1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define REL_X 0x00
+#define REL_Y 0x01
+#define REL_Z 0x02
+#define REL_RX 0x03
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define REL_RY 0x04
+#define REL_RZ 0x05
+#define REL_HWHEEL 0x06
+#define REL_DIAL 0x07
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define REL_WHEEL 0x08
+#define REL_MISC 0x09
+#define REL_MAX 0x0f
+#define REL_CNT (REL_MAX+1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_X 0x00
+#define ABS_Y 0x01
+#define ABS_Z 0x02
+#define ABS_RX 0x03
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_RY 0x04
+#define ABS_RZ 0x05
+#define ABS_THROTTLE 0x06
+#define ABS_RUDDER 0x07
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_WHEEL 0x08
+#define ABS_GAS 0x09
+#define ABS_BRAKE 0x0a
+#define ABS_HAT0X 0x10
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_HAT0Y 0x11
+#define ABS_HAT1X 0x12
+#define ABS_HAT1Y 0x13
+#define ABS_HAT2X 0x14
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_HAT2Y 0x15
+#define ABS_HAT3X 0x16
+#define ABS_HAT3Y 0x17
+#define ABS_PRESSURE 0x18
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_DISTANCE 0x19
+#define ABS_TILT_X 0x1a
+#define ABS_TILT_Y 0x1b
+#define ABS_TOOL_WIDTH 0x1c
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_VOLUME 0x20
+#define ABS_MISC 0x28
+#define ABS_MT_SLOT 0x2f
+#define ABS_MT_TOUCH_MAJOR 0x30
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_MT_TOUCH_MINOR 0x31
+#define ABS_MT_WIDTH_MAJOR 0x32
+#define ABS_MT_WIDTH_MINOR 0x33
+#define ABS_MT_ORIENTATION 0x34
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_MT_POSITION_X 0x35
+#define ABS_MT_POSITION_Y 0x36
+#define ABS_MT_TOOL_TYPE 0x37
+#define ABS_MT_BLOB_ID 0x38
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_MT_TRACKING_ID 0x39
+#define ABS_MT_PRESSURE 0x3a
+#define ABS_MT_DISTANCE 0x3b
+#define ABS_MAX 0x3f
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ABS_CNT (ABS_MAX+1)
+#define SW_LID 0x00
+#define SW_TABLET_MODE 0x01
+#define SW_HEADPHONE_INSERT 0x02
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_RFKILL_ALL 0x03
+#define SW_RADIO SW_RFKILL_ALL
+#define SW_MICROPHONE_INSERT 0x04
+#define SW_DOCK 0x05
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_LINEOUT_INSERT 0x06
+#define SW_JACK_PHYSICAL_INSERT 0x07
+#define SW_VIDEOOUT_INSERT 0x08
+#define SW_CAMERA_LENS_COVER 0x09
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_KEYPAD_SLIDE 0x0a
+#define SW_FRONT_PROXIMITY 0x0b
+#define SW_ROTATE_LOCK 0x0c
+#define SW_LINEIN_INSERT 0x0d
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_HPHL_OVERCURRENT 0x0e
+#define SW_HPHR_OVERCURRENT 0x0f
+#define SW_UNSUPPORT_INSERT 0x10
+#define SW_MAX 0x20
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SW_CNT (SW_MAX+1)
+#define MSC_SERIAL 0x00
+#define MSC_PULSELED 0x01
+#define MSC_GESTURE 0x02
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define MSC_RAW 0x03
+#define MSC_SCAN 0x04
+#define MSC_MAX 0x07
+#define MSC_CNT (MSC_MAX+1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define LED_NUML 0x00
+#define LED_CAPSL 0x01
+#define LED_SCROLLL 0x02
+#define LED_COMPOSE 0x03
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define LED_KANA 0x04
+#define LED_SLEEP 0x05
+#define LED_SUSPEND 0x06
+#define LED_MUTE 0x07
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define LED_MISC 0x08
+#define LED_MAIL 0x09
+#define LED_CHARGING 0x0a
+#define LED_MAX 0x0f
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define LED_CNT (LED_MAX+1)
+#define REP_DELAY 0x00
+#define REP_PERIOD 0x01
+#define REP_MAX 0x01
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define REP_CNT (REP_MAX+1)
+#define SND_CLICK 0x00
+#define SND_BELL 0x01
+#define SND_TONE 0x02
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define SND_MAX 0x07
+#define SND_CNT (SND_MAX+1)
+#define ID_BUS 0
+#define ID_VENDOR 1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define ID_PRODUCT 2
+#define ID_VERSION 3
+#define BUS_PCI 0x01
+#define BUS_ISAPNP 0x02
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_USB 0x03
+#define BUS_HIL 0x04
+#define BUS_BLUETOOTH 0x05
+#define BUS_VIRTUAL 0x06
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_ISA 0x10
+#define BUS_I8042 0x11
+#define BUS_XTKBD 0x12
+#define BUS_RS232 0x13
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_GAMEPORT 0x14
+#define BUS_PARPORT 0x15
+#define BUS_AMIGA 0x16
+#define BUS_ADB 0x17
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_I2C 0x18
+#define BUS_HOST 0x19
+#define BUS_GSC 0x1A
+#define BUS_ATARI 0x1B
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define BUS_SPI 0x1C
+#define MT_TOOL_FINGER 0
+#define MT_TOOL_PEN 1
+#define MT_TOOL_MAX 1
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_STATUS_STOPPED 0x00
+#define FF_STATUS_PLAYING 0x01
+#define FF_STATUS_MAX 0x01
+struct ff_replay {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 length;
+ __u16 delay;
+};
+struct ff_trigger {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 button;
+ __u16 interval;
+};
+struct ff_envelope {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 attack_length;
+ __u16 attack_level;
+ __u16 fade_length;
+ __u16 fade_level;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct ff_constant_effect {
+ __s16 level;
+ struct ff_envelope envelope;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+};
+struct ff_ramp_effect {
+ __s16 start_level;
+ __s16 end_level;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct ff_envelope envelope;
+};
+struct ff_condition_effect {
+ __u16 right_saturation;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 left_saturation;
+ __s16 right_coeff;
+ __s16 left_coeff;
+ __u16 deadband;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __s16 center;
+};
+struct ff_periodic_effect {
+ __u16 waveform;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ __u16 period;
+ __s16 magnitude;
+ __s16 offset;
+ __u16 phase;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct ff_envelope envelope;
+ __u32 custom_len;
+ __s16 __user *custom_data;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct ff_rumble_effect {
+ __u16 strong_magnitude;
+ __u16 weak_magnitude;
+};
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct ff_effect {
+ __u16 type;
+ __s16 id;
+ __u16 direction;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct ff_trigger trigger;
+ struct ff_replay replay;
+ union {
+ struct ff_constant_effect constant;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ struct ff_ramp_effect ramp;
+ struct ff_periodic_effect periodic;
+ struct ff_condition_effect condition[2];
+ struct ff_rumble_effect rumble;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ } u;
+};
+#define FF_RUMBLE 0x50
+#define FF_PERIODIC 0x51
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_CONSTANT 0x52
+#define FF_SPRING 0x53
+#define FF_FRICTION 0x54
+#define FF_DAMPER 0x55
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_INERTIA 0x56
+#define FF_RAMP 0x57
+#define FF_EFFECT_MIN FF_RUMBLE
+#define FF_EFFECT_MAX FF_RAMP
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_SQUARE 0x58
+#define FF_TRIANGLE 0x59
+#define FF_SINE 0x5a
+#define FF_SAW_UP 0x5b
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_SAW_DOWN 0x5c
+#define FF_CUSTOM 0x5d
+#define FF_WAVEFORM_MIN FF_SQUARE
+#define FF_WAVEFORM_MAX FF_CUSTOM
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define FF_GAIN 0x60
+#define FF_AUTOCENTER 0x61
+#define FF_MAX 0x7f
+#define FF_CNT (FF_MAX+1)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#endif
diff --git a/widget/gonk/libui/sha1.c b/widget/gonk/libui/sha1.c
new file mode 100644
index 0000000000..40a4ecaa3b
--- /dev/null
+++ b/widget/gonk/libui/sha1.c
@@ -0,0 +1,289 @@
+/* $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $ */
+/* $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $ */
+
+/*
+ * SHA-1 in C
+ * By Steve Reid <steve@edmweb.com>
+ * 100% Public Domain
+ *
+ * Test Vectors (from FIPS PUB 180-1)
+ * "abc"
+ * A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+ * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ * 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+ * A million repetitions of "a"
+ * 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+ */
+
+#define SHA1HANDSOFF /* Copies data before messing with it. */
+
+#include <sys/cdefs.h>
+
+#if defined(_KERNEL) || defined(_STANDALONE)
+__KERNEL_RCSID(0, "$NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $");
+
+#include <lib/libkern/libkern.h>
+
+#else
+
+#if defined(LIBC_SCCS) && !defined(lint)
+__RCSID("$NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $");
+#endif /* LIBC_SCCS and not lint */
+
+#include <assert.h>
+#include <string.h>
+
+#endif
+
+#include <sys/types.h>
+#include "sha1.h"
+
+#define _DIAGASSERT assert
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#if !HAVE_SHA1_H
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/*
+ * blk0() and blk() perform the initial expand.
+ * I got the idea of expanding during the round function from SSLeay
+ */
+#if BYTE_ORDER == LITTLE_ENDIAN
+# define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+ |(rol(block->l[i],8)&0x00FF00FF))
+#else
+# define blk0(i) block->l[i]
+#endif
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+ ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/*
+ * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
+ */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+
+#if !defined(_KERNEL) && !defined(_STANDALONE)
+#if defined(__weak_alias)
+__weak_alias(SHA1Transform,_SHA1Transform)
+__weak_alias(SHA1Init,_SHA1Init)
+__weak_alias(SHA1Update,_SHA1Update)
+__weak_alias(SHA1Final,_SHA1Final)
+#endif
+#endif
+
+typedef union {
+ uint8_t c[64];
+ uint32_t l[16];
+} CHAR64LONG16;
+
+/* old sparc64 gcc could not compile this */
+#undef SPARC64_GCC_WORKAROUND
+#if defined(__sparc64__) && defined(__GNUC__) && __GNUC__ < 3
+#define SPARC64_GCC_WORKAROUND
+#endif
+
+#ifdef SPARC64_GCC_WORKAROUND
+void do_R01(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *);
+void do_R2(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *);
+void do_R3(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *);
+void do_R4(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *);
+
+#define nR0(v,w,x,y,z,i) R0(*v,*w,*x,*y,*z,i)
+#define nR1(v,w,x,y,z,i) R1(*v,*w,*x,*y,*z,i)
+#define nR2(v,w,x,y,z,i) R2(*v,*w,*x,*y,*z,i)
+#define nR3(v,w,x,y,z,i) R3(*v,*w,*x,*y,*z,i)
+#define nR4(v,w,x,y,z,i) R4(*v,*w,*x,*y,*z,i)
+
+void
+do_R01(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *block)
+{
+ nR0(a,b,c,d,e, 0); nR0(e,a,b,c,d, 1); nR0(d,e,a,b,c, 2); nR0(c,d,e,a,b, 3);
+ nR0(b,c,d,e,a, 4); nR0(a,b,c,d,e, 5); nR0(e,a,b,c,d, 6); nR0(d,e,a,b,c, 7);
+ nR0(c,d,e,a,b, 8); nR0(b,c,d,e,a, 9); nR0(a,b,c,d,e,10); nR0(e,a,b,c,d,11);
+ nR0(d,e,a,b,c,12); nR0(c,d,e,a,b,13); nR0(b,c,d,e,a,14); nR0(a,b,c,d,e,15);
+ nR1(e,a,b,c,d,16); nR1(d,e,a,b,c,17); nR1(c,d,e,a,b,18); nR1(b,c,d,e,a,19);
+}
+
+void
+do_R2(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *block)
+{
+ nR2(a,b,c,d,e,20); nR2(e,a,b,c,d,21); nR2(d,e,a,b,c,22); nR2(c,d,e,a,b,23);
+ nR2(b,c,d,e,a,24); nR2(a,b,c,d,e,25); nR2(e,a,b,c,d,26); nR2(d,e,a,b,c,27);
+ nR2(c,d,e,a,b,28); nR2(b,c,d,e,a,29); nR2(a,b,c,d,e,30); nR2(e,a,b,c,d,31);
+ nR2(d,e,a,b,c,32); nR2(c,d,e,a,b,33); nR2(b,c,d,e,a,34); nR2(a,b,c,d,e,35);
+ nR2(e,a,b,c,d,36); nR2(d,e,a,b,c,37); nR2(c,d,e,a,b,38); nR2(b,c,d,e,a,39);
+}
+
+void
+do_R3(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *block)
+{
+ nR3(a,b,c,d,e,40); nR3(e,a,b,c,d,41); nR3(d,e,a,b,c,42); nR3(c,d,e,a,b,43);
+ nR3(b,c,d,e,a,44); nR3(a,b,c,d,e,45); nR3(e,a,b,c,d,46); nR3(d,e,a,b,c,47);
+ nR3(c,d,e,a,b,48); nR3(b,c,d,e,a,49); nR3(a,b,c,d,e,50); nR3(e,a,b,c,d,51);
+ nR3(d,e,a,b,c,52); nR3(c,d,e,a,b,53); nR3(b,c,d,e,a,54); nR3(a,b,c,d,e,55);
+ nR3(e,a,b,c,d,56); nR3(d,e,a,b,c,57); nR3(c,d,e,a,b,58); nR3(b,c,d,e,a,59);
+}
+
+void
+do_R4(uint32_t *a, uint32_t *b, uint32_t *c, uint32_t *d, uint32_t *e, CHAR64LONG16 *block)
+{
+ nR4(a,b,c,d,e,60); nR4(e,a,b,c,d,61); nR4(d,e,a,b,c,62); nR4(c,d,e,a,b,63);
+ nR4(b,c,d,e,a,64); nR4(a,b,c,d,e,65); nR4(e,a,b,c,d,66); nR4(d,e,a,b,c,67);
+ nR4(c,d,e,a,b,68); nR4(b,c,d,e,a,69); nR4(a,b,c,d,e,70); nR4(e,a,b,c,d,71);
+ nR4(d,e,a,b,c,72); nR4(c,d,e,a,b,73); nR4(b,c,d,e,a,74); nR4(a,b,c,d,e,75);
+ nR4(e,a,b,c,d,76); nR4(d,e,a,b,c,77); nR4(c,d,e,a,b,78); nR4(b,c,d,e,a,79);
+}
+#endif
+
+/*
+ * Hash a single 512-bit block. This is the core of the algorithm.
+ */
+void SHA1Transform(uint32_t state[5], const uint8_t buffer[64])
+{
+ uint32_t a, b, c, d, e;
+ CHAR64LONG16 *block;
+
+#ifdef SHA1HANDSOFF
+ CHAR64LONG16 workspace;
+#endif
+
+ _DIAGASSERT(buffer != 0);
+ _DIAGASSERT(state != 0);
+
+#ifdef SHA1HANDSOFF
+ block = &workspace;
+ (void)memcpy(block, buffer, 64);
+#else
+ block = (CHAR64LONG16 *)(void *)buffer;
+#endif
+
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+
+#ifdef SPARC64_GCC_WORKAROUND
+ do_R01(&a, &b, &c, &d, &e, block);
+ do_R2(&a, &b, &c, &d, &e, block);
+ do_R3(&a, &b, &c, &d, &e, block);
+ do_R4(&a, &b, &c, &d, &e, block);
+#else
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+#endif
+
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+}
+
+
+/*
+ * SHA1Init - Initialize new context
+ */
+void SHA1Init(SHA1_CTX *context)
+{
+
+ _DIAGASSERT(context != 0);
+
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/*
+ * Run your data through this.
+ */
+void SHA1Update(SHA1_CTX *context, const uint8_t *data, unsigned int len)
+{
+ unsigned int i, j;
+
+ _DIAGASSERT(context != 0);
+ _DIAGASSERT(data != 0);
+
+ j = context->count[0];
+ if ((context->count[0] += len << 3) < j)
+ context->count[1] += (len>>29)+1;
+ j = (j >> 3) & 63;
+ if ((j + len) > 63) {
+ (void)memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64)
+ SHA1Transform(context->state, &data[i]);
+ j = 0;
+ } else {
+ i = 0;
+ }
+ (void)memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/*
+ * Add padding and return the message digest.
+ */
+void SHA1Final(uint8_t digest[20], SHA1_CTX *context)
+{
+ unsigned int i;
+ uint8_t finalcount[8];
+
+ _DIAGASSERT(digest != 0);
+ _DIAGASSERT(context != 0);
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (uint8_t)((context->count[(i >= 4 ? 0 : 1)]
+ >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
+ }
+ SHA1Update(context, (const uint8_t *)"\200", 1);
+ while ((context->count[0] & 504) != 448)
+ SHA1Update(context, (const uint8_t *)"\0", 1);
+ SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
+
+ if (digest) {
+ for (i = 0; i < 20; i++)
+ digest[i] = (uint8_t)
+ ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
+ }
+}
+
+#endif /* HAVE_SHA1_H */
diff --git a/widget/gonk/libui/sha1.h b/widget/gonk/libui/sha1.h
new file mode 100644
index 0000000000..f7ada46a50
--- /dev/null
+++ b/widget/gonk/libui/sha1.h
@@ -0,0 +1,31 @@
+/* $NetBSD: sha1.h,v 1.13 2005/12/26 18:41:36 perry Exp $ */
+
+/*
+ * SHA-1 in C
+ * By Steve Reid <steve@edmweb.com>
+ * 100% Public Domain
+ */
+
+#ifndef _SYS_SHA1_H_
+#define _SYS_SHA1_H_
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+#define SHA1_DIGEST_LENGTH 20
+#define SHA1_DIGEST_STRING_LENGTH 41
+
+typedef struct {
+ uint32_t state[5];
+ uint32_t count[2];
+ u_char buffer[64];
+} SHA1_CTX;
+
+__BEGIN_DECLS
+void SHA1Transform(uint32_t[5], const u_char[64]);
+void SHA1Init(SHA1_CTX *);
+void SHA1Update(SHA1_CTX *, const u_char *, u_int);
+void SHA1Final(u_char[SHA1_DIGEST_LENGTH], SHA1_CTX *);
+__END_DECLS
+
+#endif /* _SYS_SHA1_H_ */
diff --git a/widget/gonk/moz.build b/widget/gonk/moz.build
new file mode 100644
index 0000000000..d539dd9a08
--- /dev/null
+++ b/widget/gonk/moz.build
@@ -0,0 +1,96 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# Copyright 2013 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.
+
+EXPORTS += [
+ 'GeckoTouchDispatcher.h',
+ 'GonkPermission.h',
+ 'OrientationObserver.h',
+]
+
+DIRS += ['libdisplay', 'nativewindow']
+
+# libui files
+SOURCES += ['libui/' + src for src in [
+ 'EventHub.cpp',
+ 'Input.cpp',
+ 'InputApplication.cpp',
+ 'InputDevice.cpp',
+ 'InputDispatcher.cpp',
+ 'InputListener.cpp',
+ 'InputReader.cpp',
+ 'InputTransport.cpp',
+ 'InputWindow.cpp',
+ 'Keyboard.cpp',
+ 'KeyCharacterMap.cpp',
+ 'KeyLayoutMap.cpp',
+ 'PointerController.cpp',
+ 'sha1.c',
+ 'SpriteController.cpp',
+ 'Tokenizer.cpp',
+ 'VelocityControl.cpp',
+ 'VelocityTracker.cpp',
+ 'VirtualKeyMap.cpp',
+]]
+
+# HwcHAL files
+if CONFIG['ANDROID_VERSION'] >= '17':
+ SOURCES += [
+ 'hwchal/HwcHAL.cpp',
+ ]
+
+SOURCES += [
+ 'GeckoTouchDispatcher.cpp',
+ 'GfxInfo.cpp',
+ 'GonkClipboardData.cpp',
+ 'GonkMemoryPressureMonitoring.cpp',
+ 'GonkPermission.cpp',
+ 'HwcComposer2D.cpp',
+ 'HwcUtils.cpp',
+ 'nsAppShell.cpp',
+ 'nsClipboard.cpp',
+ 'nsIdleServiceGonk.cpp',
+ 'nsLookAndFeel.cpp',
+ 'nsScreenManagerGonk.cpp',
+ 'nsWidgetFactory.cpp',
+ 'nsWindow.cpp',
+ 'OrientationObserver.cpp',
+ 'ProcessOrientation.cpp',
+ 'WidgetTraceEvent.cpp'
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/system/android',
+ '/gfx/skia/skia/include/config',
+ '/gfx/skia/skia/include/core',
+ '/image',
+ '/widget',
+]
+
+DEFINES['HAVE_OFF64_T'] = True
+DEFINES['SK_BUILD_FOR_ANDROID_NDK'] = True
+DEFINES['HAVE_POSIX_CLOCKS'] = True
+
+LOCAL_INCLUDES += [
+ '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
+ 'frameworks/native/opengl/include',
+ 'hardware/libhardware/include',
+ 'hardware/libhardware_legacy/include',
+ ]
+]
diff --git a/widget/gonk/nativewindow/FakeSurfaceComposer.cpp b/widget/gonk/nativewindow/FakeSurfaceComposer.cpp
new file mode 100644
index 0000000000..7e4a2a9d86
--- /dev/null
+++ b/widget/gonk/nativewindow/FakeSurfaceComposer.cpp
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#define LOG_TAG "FakeSurfaceComposer"
+//#define LOG_NDEBUG 0
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <cutils/atomic.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <private/android_filesystem_config.h>
+
+#include <gui/IDisplayEventConnection.h>
+#include <gui/GraphicBufferAlloc.h>
+#include <gui/Surface.h>
+#include <ui/DisplayInfo.h>
+
+#if ANDROID_VERSION >= 21
+#include <ui/Rect.h>
+#endif
+
+#include "../libdisplay/GonkDisplay.h"
+#include "../nsScreenManagerGonk.h"
+#include "FakeSurfaceComposer.h"
+#include "gfxPrefs.h"
+#include "MainThreadUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+namespace android {
+
+/* static */
+void FakeSurfaceComposer::instantiate() {
+ defaultServiceManager()->addService(
+ String16("SurfaceFlinger"), new FakeSurfaceComposer());
+}
+
+FakeSurfaceComposer::FakeSurfaceComposer()
+ : BnSurfaceComposer()
+{
+}
+
+FakeSurfaceComposer::~FakeSurfaceComposer()
+{
+}
+
+status_t FakeSurfaceComposer::onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags)
+{
+ switch (code) {
+ case CREATE_CONNECTION:
+ case CREATE_DISPLAY:
+ case SET_TRANSACTION_STATE:
+ case CAPTURE_SCREEN:
+ {
+ // codes that require permission check
+ IPCThreadState* ipc = IPCThreadState::self();
+ const int pid = ipc->getCallingPid();
+ const int uid = ipc->getCallingUid();
+ // Accept request only when uid is root.
+ if (uid != AID_ROOT) {
+ ALOGE("Permission Denial: "
+ "can't access SurfaceFlinger pid=%d, uid=%d", pid, uid);
+ return PERMISSION_DENIED;
+ }
+ break;
+ }
+ }
+
+ return BnSurfaceComposer::onTransact(code, data, reply, flags);
+}
+
+sp<ISurfaceComposerClient> FakeSurfaceComposer::createConnection()
+{
+ return nullptr;
+}
+
+sp<IGraphicBufferAlloc> FakeSurfaceComposer::createGraphicBufferAlloc()
+{
+ sp<GraphicBufferAlloc> gba(new GraphicBufferAlloc());
+ return gba;
+}
+
+class DestroyDisplayRunnable : public Runnable {
+public:
+ DestroyDisplayRunnable(FakeSurfaceComposer* aComposer, ssize_t aIndex)
+ : mComposer(aComposer), mIndex(aIndex) { }
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+ Mutex::Autolock _l(mComposer->mStateLock);
+ RefPtr<nsScreenManagerGonk> screenManager =
+ nsScreenManagerGonk::GetInstance();
+ screenManager->RemoveScreen(GonkDisplay::DISPLAY_VIRTUAL);
+ mComposer->mDisplays.removeItemsAt(mIndex);
+ return NS_OK;
+ }
+ sp<FakeSurfaceComposer> mComposer;
+ ssize_t mIndex;
+};
+
+sp<IBinder> FakeSurfaceComposer::createDisplay(const String8& displayName,
+ bool secure)
+{
+#if ANDROID_VERSION >= 19
+ class DisplayToken : public BBinder {
+ sp<FakeSurfaceComposer> composer;
+ virtual ~DisplayToken() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+ // no more references, this display must be terminated
+ Mutex::Autolock _l(composer->mStateLock);
+ ssize_t idx = composer->mDisplays.indexOfKey(this);
+ if (idx >= 0) {
+ nsCOMPtr<nsIRunnable> task(new DestroyDisplayRunnable(composer.get(), idx));
+ NS_DispatchToMainThread(task);
+ }
+ }
+ public:
+ DisplayToken(const sp<FakeSurfaceComposer>& composer)
+ : composer(composer) {
+ }
+ };
+
+ sp<BBinder> token = new DisplayToken(this);
+
+ Mutex::Autolock _l(mStateLock);
+ DisplayDeviceState info(HWC_DISPLAY_VIRTUAL);
+ info.displayName = displayName;
+ info.displayId = GonkDisplay::DISPLAY_VIRTUAL;
+ info.isSecure = secure;
+ mDisplays.add(token, info);
+ return token;
+#else
+ return nullptr;
+#endif
+}
+
+#if ANDROID_VERSION >= 19
+void FakeSurfaceComposer::destroyDisplay(const sp<IBinder>& display)
+{
+ Mutex::Autolock _l(mStateLock);
+
+ ssize_t idx = mDisplays.indexOfKey(display);
+ if (idx < 0) {
+ ALOGW("destroyDisplay: invalid display token");
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> task(new DestroyDisplayRunnable(this, idx));
+ NS_DispatchToMainThread(task);
+}
+#endif
+
+sp<IBinder> FakeSurfaceComposer::getBuiltInDisplay(int32_t id)
+{
+ // support only primary display
+ if (uint32_t(id) != HWC_DISPLAY_PRIMARY) {
+ return NULL;
+ }
+
+ if (!mPrimaryDisplay.get()) {
+ mPrimaryDisplay = new BBinder();
+ }
+ return mPrimaryDisplay;
+}
+
+void FakeSurfaceComposer::setTransactionState(
+ const Vector<ComposerState>& state,
+ const Vector<DisplayState>& displays,
+ uint32_t flags)
+{
+ Mutex::Autolock _l(mStateLock);
+ size_t count = displays.size();
+ for (size_t i=0 ; i<count ; i++) {
+ const DisplayState& s(displays[i]);
+ setDisplayStateLocked(s);
+ }
+}
+
+uint32_t FakeSurfaceComposer::setDisplayStateLocked(const DisplayState& s)
+{
+ ssize_t dpyIdx = mDisplays.indexOfKey(s.token);
+ if (dpyIdx < 0) {
+ return 0;
+ }
+
+ uint32_t flags = 0;
+ DisplayDeviceState& disp(mDisplays.editValueAt(dpyIdx));
+
+ if (!disp.isValid()) {
+ return 0;
+ }
+
+ const uint32_t what = s.what;
+ if (what & DisplayState::eSurfaceChanged) {
+ if (disp.surface->asBinder() != s.surface->asBinder()) {
+ disp.surface = s.surface;
+ flags |= eDisplayTransactionNeeded;
+ }
+ }
+ if (what & DisplayState::eLayerStackChanged) {
+ if (disp.layerStack != s.layerStack) {
+ disp.layerStack = s.layerStack;
+ flags |= eDisplayTransactionNeeded;
+ }
+ }
+ if (what & DisplayState::eDisplayProjectionChanged) {
+ if (disp.orientation != s.orientation) {
+ disp.orientation = s.orientation;
+ flags |= eDisplayTransactionNeeded;
+ }
+ if (disp.frame != s.frame) {
+ disp.frame = s.frame;
+ flags |= eDisplayTransactionNeeded;
+ }
+ if (disp.viewport != s.viewport) {
+ disp.viewport = s.viewport;
+ flags |= eDisplayTransactionNeeded;
+ }
+ }
+#if ANDROID_VERSION >= 21
+ if (what & DisplayState::eDisplaySizeChanged) {
+ if (disp.width != s.width) {
+ disp.width = s.width;
+ flags |= eDisplayTransactionNeeded;
+ }
+ if (disp.height != s.height) {
+ disp.height = s.height;
+ flags |= eDisplayTransactionNeeded;
+ }
+ }
+#endif
+
+ if (what & DisplayState::eSurfaceChanged) {
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([&]() {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+ RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
+ screenManager->AddScreen(GonkDisplay::DISPLAY_VIRTUAL, disp.surface.get());
+ });
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC);
+ }
+
+ return flags;
+}
+
+void FakeSurfaceComposer::bootFinished()
+{
+}
+
+bool FakeSurfaceComposer::authenticateSurfaceTexture(
+ const sp<IGraphicBufferProducer>& bufferProducer) const {
+ return false;
+}
+
+sp<IDisplayEventConnection> FakeSurfaceComposer::createDisplayEventConnection() {
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+// Capture screen into an IGraphiBufferProducer
+// ---------------------------------------------------------------------------
+
+class Barrier {
+public:
+ inline Barrier() : state(CLOSED) { }
+ inline ~Barrier() { }
+
+ // Release any threads waiting at the Barrier.
+ // Provides release semantics: preceding loads and stores will be visible
+ // to other threads before they wake up.
+ void open() {
+ Mutex::Autolock _l(lock);
+ state = OPENED;
+ cv.broadcast();
+ }
+
+ // Reset the Barrier, so wait() will block until open() has been called.
+ void close() {
+ Mutex::Autolock _l(lock);
+ state = CLOSED;
+ }
+
+ // Wait until the Barrier is OPEN.
+ // Provides acquire semantics: no subsequent loads or stores will occur
+ // until wait() returns.
+ void wait() const {
+ Mutex::Autolock _l(lock);
+ while (state == CLOSED) {
+ cv.wait(lock);
+ }
+ }
+private:
+ enum { OPENED, CLOSED };
+ mutable Mutex lock;
+ mutable Condition cv;
+ volatile int state;
+};
+
+/* The code below is here to handle b/8734824
+ *
+ * We create a IGraphicBufferProducer wrapper that forwards all calls
+ * to the calling binder thread, where they are executed. This allows
+ * the calling thread to be reused (on the other side) and not
+ * depend on having "enough" binder threads to handle the requests.
+ *
+ */
+
+class GraphicProducerWrapper : public BBinder, public MessageHandler {
+ sp<IGraphicBufferProducer> impl;
+ sp<Looper> looper;
+ status_t result;
+ bool exitPending;
+ bool exitRequested;
+ mutable Barrier barrier;
+ volatile int32_t memoryBarrier;
+ uint32_t code;
+ Parcel const* data;
+ Parcel* reply;
+
+ enum {
+ MSG_API_CALL,
+ MSG_EXIT
+ };
+
+ /*
+ * this is called by our "fake" BpGraphicBufferProducer. We package the
+ * data and reply Parcel and forward them to the calling thread.
+ */
+ virtual status_t transact(uint32_t code,
+ const Parcel& data, Parcel* reply, uint32_t flags) {
+ this->code = code;
+ this->data = &data;
+ this->reply = reply;
+ android_atomic_acquire_store(0, &memoryBarrier);
+ if (exitPending) {
+ // if we've exited, we run the message synchronously right here
+ handleMessage(Message(MSG_API_CALL));
+ } else {
+ barrier.close();
+ looper->sendMessage(this, Message(MSG_API_CALL));
+ barrier.wait();
+ }
+ return NO_ERROR;
+ }
+
+ /*
+ * here we run on the binder calling thread. All we've got to do is
+ * call the real BpGraphicBufferProducer.
+ */
+ virtual void handleMessage(const Message& message) {
+ android_atomic_release_load(&memoryBarrier);
+ if (message.what == MSG_API_CALL) {
+ impl->asBinder()->transact(code, data[0], reply);
+ barrier.open();
+ } else if (message.what == MSG_EXIT) {
+ exitRequested = true;
+ }
+ }
+
+public:
+ GraphicProducerWrapper(const sp<IGraphicBufferProducer>& impl) :
+ impl(impl), looper(new Looper(true)), result(NO_ERROR),
+ exitPending(false), exitRequested(false) {
+ }
+
+ status_t waitForResponse() {
+ do {
+ looper->pollOnce(-1);
+ } while (!exitRequested);
+ return result;
+ }
+
+ void exit(status_t result) {
+ this->result = result;
+ exitPending = true;
+ looper->sendMessage(this, Message(MSG_EXIT));
+ }
+};
+
+status_t
+FakeSurfaceComposer::captureScreen(const sp<IBinder>& display
+ , const sp<IGraphicBufferProducer>& producer
+#if ANDROID_VERSION >= 21
+ , Rect sourceCrop
+#endif
+ , uint32_t reqWidth
+ , uint32_t reqHeight
+ , uint32_t minLayerZ
+ , uint32_t maxLayerZ
+#if ANDROID_VERSION >= 21
+ , bool useIdentityTransform
+ , Rotation rotation
+#elif ANDROID_VERSION < 19
+ , bool isCpuConsumer
+#endif
+ )
+{
+ if (display == 0 || producer == 0) {
+ return BAD_VALUE;
+ }
+
+ // Limit only to primary display
+ if (display != mPrimaryDisplay) {
+ return BAD_VALUE;
+ }
+
+ // this creates a "fake" BBinder which will serve as a "fake" remote
+ // binder to receive the marshaled calls and forward them to the
+ // real remote (a BpGraphicBufferProducer)
+ sp<GraphicProducerWrapper> wrapper = new GraphicProducerWrapper(producer);
+ // the asInterface() call below creates our "fake" BpGraphicBufferProducer
+ // which does the marshaling work forwards to our "fake remote" above.
+ sp<IGraphicBufferProducer> fakeProducer = IGraphicBufferProducer::asInterface(wrapper);
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([&]() {
+ captureScreenImp(fakeProducer, reqWidth, reqHeight, wrapper.get());
+ });
+ NS_DispatchToMainThread(runnable);
+
+ status_t result = wrapper->waitForResponse();
+
+ return result;
+}
+
+class RunnableCallTask final : public Runnable
+{
+public:
+ explicit RunnableCallTask(nsIRunnable* aRunnable)
+ : mRunnable(aRunnable) {}
+
+ NS_IMETHOD Run() override
+ {
+ return mRunnable->Run();
+ }
+protected:
+ nsCOMPtr<nsIRunnable> mRunnable;
+};
+
+void
+FakeSurfaceComposer::captureScreenImp(const sp<IGraphicBufferProducer>& producer,
+ uint32_t reqWidth,
+ uint32_t reqHeight,
+ const sp<GraphicProducerWrapper>& wrapper)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(wrapper.get());
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+
+ // get screen geometry
+ nsIntRect screenBounds = screen->GetNaturalBounds().ToUnknownRect();
+ const uint32_t hw_w = screenBounds.width;
+ const uint32_t hw_h = screenBounds.height;
+
+ if (reqWidth > hw_w || reqHeight > hw_h) {
+ ALOGE("size mismatch (%d, %d) > (%d, %d)",
+ reqWidth, reqHeight, hw_w, hw_h);
+ static_cast<GraphicProducerWrapper*>(producer->asBinder().get())->exit(BAD_VALUE);
+ return;
+ }
+
+ reqWidth = (!reqWidth) ? hw_w : reqWidth;
+ reqHeight = (!reqHeight) ? hw_h : reqHeight;
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([screen, reqWidth, reqHeight, producer, wrapper]() {
+ // create a surface (because we're a producer, and we need to
+ // dequeue/queue a buffer)
+ sp<Surface> sur = new Surface(producer);
+ ANativeWindow* window = sur.get();
+ // The closure makes screen const and we can't call forget() on it.
+ RefPtr<nsScreenGonk> screenAlias = screen;
+
+ if (native_window_api_connect(window, NATIVE_WINDOW_API_EGL) != NO_ERROR) {
+ static_cast<GraphicProducerWrapper*>(producer->asBinder().get())->exit(BAD_VALUE);
+ NS_ReleaseOnMainThread(screenAlias.forget());
+ return;
+ }
+ uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
+
+ int err = 0;
+ err = native_window_set_buffers_dimensions(window, reqWidth, reqHeight);
+ err |= native_window_set_scaling_mode(window, NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW);
+ err |= native_window_set_buffers_format(window, HAL_PIXEL_FORMAT_RGBA_8888);
+ err |= native_window_set_usage(window, usage);
+
+ status_t result = NO_ERROR;
+ if (err == NO_ERROR) {
+ ANativeWindowBuffer* buffer;
+ result = native_window_dequeue_buffer_and_wait(window, &buffer);
+ if (result == NO_ERROR) {
+ nsresult rv = screen->MakeSnapshot(buffer);
+ if (rv != NS_OK) {
+ result = INVALID_OPERATION;
+ }
+ window->queueBuffer(window, buffer, -1);
+ }
+ } else {
+ result = BAD_VALUE;
+ }
+ native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
+ static_cast<GraphicProducerWrapper*>(producer->asBinder().get())->exit(result);
+ NS_ReleaseOnMainThread(screenAlias.forget());
+ });
+
+ layers::CompositorThreadHolder::Loop()->PostTask(
+ MakeAndAddRef<RunnableCallTask>(runnable));
+}
+
+#if ANDROID_VERSION >= 21
+void
+FakeSurfaceComposer::setPowerMode(const sp<IBinder>& display, int mode)
+{
+}
+
+status_t
+FakeSurfaceComposer::getDisplayConfigs(const sp<IBinder>& display, Vector<DisplayInfo>* configs)
+{
+ if (configs == NULL) {
+ return BAD_VALUE;
+ }
+
+ // Limit DisplayConfigs only to primary display
+ if (!display.get() || display != mPrimaryDisplay) {
+ return NAME_NOT_FOUND;
+ }
+
+ configs->clear();
+ DisplayInfo info = DisplayInfo();
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([&]() {
+ MOZ_ASSERT(NS_IsMainThread());
+ getPrimaryDisplayInfo(&info);
+ });
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC);
+
+ configs->push_back(info);
+ return NO_ERROR;
+}
+
+status_t
+FakeSurfaceComposer::getDisplayStats(const sp<IBinder>& display, DisplayStatInfo* stats)
+{
+ return INVALID_OPERATION;
+}
+
+int
+FakeSurfaceComposer::getActiveConfig(const sp<IBinder>& display)
+{
+ // Only support primary display.
+ if (display.get() && (display == mPrimaryDisplay)) {
+ return 0;
+ }
+ return INVALID_OPERATION;
+}
+
+status_t
+FakeSurfaceComposer::setActiveConfig(const sp<IBinder>& display, int id)
+{
+ return INVALID_OPERATION;
+}
+
+status_t
+FakeSurfaceComposer::clearAnimationFrameStats()
+{
+ return INVALID_OPERATION;
+}
+
+status_t
+FakeSurfaceComposer::getAnimationFrameStats(FrameStats* outStats) const
+{
+ return INVALID_OPERATION;
+}
+#else
+void
+FakeSurfaceComposer::blank(const sp<IBinder>& display)
+{
+}
+
+void
+FakeSurfaceComposer::unblank(const sp<IBinder>& display)
+{
+}
+
+status_t
+FakeSurfaceComposer::getDisplayInfo(const sp<IBinder>& display, DisplayInfo* info)
+{
+ if (info == NULL) {
+ return BAD_VALUE;
+ }
+
+ // Limit DisplayConfigs only to primary display
+ if (!display.get() || display != mPrimaryDisplay) {
+ return NAME_NOT_FOUND;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable =
+ NS_NewRunnableFunction([&]() {
+ MOZ_ASSERT(NS_IsMainThread());
+ getPrimaryDisplayInfo(info);
+ });
+ NS_DispatchToMainThread(runnable, NS_DISPATCH_SYNC);
+
+ return NO_ERROR;
+}
+#endif
+
+#define VSYNC_EVENT_PHASE_OFFSET_NS 0
+#define SF_VSYNC_EVENT_PHASE_OFFSET_NS 0
+
+void
+FakeSurfaceComposer::getPrimaryDisplayInfo(DisplayInfo* info)
+{
+ MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
+
+ // Implementation mimic android SurfaceFlinger::getDisplayConfigs().
+
+ class Density {
+ static int getDensityFromProperty(char const* propName) {
+ char property[PROPERTY_VALUE_MAX];
+ int density = 0;
+ if (property_get(propName, property, NULL) > 0) {
+ density = atoi(property);
+ }
+ return density;
+ }
+ public:
+ static int getEmuDensity() {
+ return getDensityFromProperty("qemu.sf.lcd_density"); }
+ static int getBuildDensity() {
+ return getDensityFromProperty("ro.sf.lcd_density"); }
+ };
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+
+ float xdpi = screen->GetDpi();
+ float ydpi = screen->GetDpi();
+ int fps = 60; // XXX set a value from hwc hal
+ nsIntRect screenBounds = screen->GetNaturalBounds().ToUnknownRect();
+
+ // The density of the device is provided by a build property
+ float density = Density::getBuildDensity() / 160.0f;
+ if (density == 0) {
+ // the build doesn't provide a density -- this is wrong!
+ // use xdpi instead
+ ALOGE("ro.sf.lcd_density must be defined as a build property");
+ density = xdpi / 160.0f;
+ }
+ info->density = density;
+ info->orientation = screen->EffectiveScreenRotation();
+
+ info->w = screenBounds.width;
+ info->h = screenBounds.height;
+ info->xdpi = xdpi;
+ info->ydpi = ydpi;
+ info->fps = fps;
+#if ANDROID_VERSION >= 21
+ info->appVsyncOffset = VSYNC_EVENT_PHASE_OFFSET_NS;
+
+ // This is how far in advance a buffer must be queued for
+ // presentation at a given time. If you want a buffer to appear
+ // on the screen at time N, you must submit the buffer before
+ // (N - presentationDeadline).
+ //
+ // Normally it's one full refresh period (to give SF a chance to
+ // latch the buffer), but this can be reduced by configuring a
+ // DispSync offset. Any additional delays introduced by the hardware
+ // composer or panel must be accounted for here.
+ //
+ // We add an additional 1ms to allow for processing time and
+ // differences between the ideal and actual refresh rate.
+ info->presentationDeadline =
+ (1e9 / fps) - SF_VSYNC_EVENT_PHASE_OFFSET_NS + 1000000;
+#endif
+ // All non-virtual displays are currently considered secure.
+ info->secure = true;
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/FakeSurfaceComposer.h b/widget/gonk/nativewindow/FakeSurfaceComposer.h
new file mode 100644
index 0000000000..97a7174447
--- /dev/null
+++ b/widget/gonk/nativewindow/FakeSurfaceComposer.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_FAKE_SURFACE_COMPOSER_H
+#define NATIVEWINDOW_FAKE_SURFACE_COMPOSER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/Looper.h>
+
+#include <binder/BinderService.h>
+
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/ISurfaceComposerClient.h>
+#include <hardware/hwcomposer.h>
+#include <private/gui/LayerState.h>
+#include <utils/KeyedVector.h>
+
+class nsIWidget;
+
+namespace android {
+
+// ---------------------------------------------------------------------------
+
+class GraphicProducerWrapper;
+class IGraphicBufferAlloc;
+
+enum {
+ eTransactionNeeded = 0x01,
+ eTraversalNeeded = 0x02,
+ eDisplayTransactionNeeded = 0x04,
+ eTransactionMask = 0x07
+};
+
+class FakeSurfaceComposer : public BinderService<FakeSurfaceComposer>,
+ public BnSurfaceComposer
+{
+public:
+ static char const* getServiceName() {
+ return "FakeSurfaceComposer";
+ }
+
+ // Instantiate FakeSurfaceComposer and register to service manager.
+ // If service manager is not present, wait until service manager becomes present.
+ static void instantiate();
+
+#if ANDROID_VERSION >= 19
+ virtual void destroyDisplay(const sp<android::IBinder>& display);
+#endif
+
+#if ANDROID_VERSION >= 21
+ virtual status_t captureScreen(const sp<IBinder>& display,
+ const sp<IGraphicBufferProducer>& producer,
+ Rect sourceCrop, uint32_t reqWidth, uint32_t reqHeight,
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform,
+ Rotation rotation = eRotateNone);
+#elif ANDROID_VERSION >= 19
+ virtual status_t captureScreen(const sp<IBinder>& display,
+ const sp<IGraphicBufferProducer>& producer,
+ uint32_t reqWidth, uint32_t reqHeight,
+ uint32_t minLayerZ, uint32_t maxLayerZ);
+#else
+ virtual status_t captureScreen(const sp<IBinder>& display,
+ const sp<IGraphicBufferProducer>& producer,
+ uint32_t reqWidth, uint32_t reqHeight,
+ uint32_t minLayerZ, uint32_t maxLayerZ, bool isCpuConsumer);
+#endif
+
+private:
+ FakeSurfaceComposer();
+ // We're reference counted, never destroy FakeSurfaceComposer directly
+ virtual ~FakeSurfaceComposer();
+
+ /* ------------------------------------------------------------------------
+ * IBinder interface
+ */
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags);
+
+ /* ------------------------------------------------------------------------
+ * ISurfaceComposer interface
+ */
+ virtual sp<ISurfaceComposerClient> createConnection();
+ virtual sp<IGraphicBufferAlloc> createGraphicBufferAlloc();
+ virtual sp<IBinder> createDisplay(const String8& displayName, bool secure);
+ virtual sp<IBinder> getBuiltInDisplay(int32_t id);
+ virtual void setTransactionState(const Vector<ComposerState>& state,
+ const Vector<DisplayState>& displays, uint32_t flags);
+ virtual void bootFinished();
+ virtual bool authenticateSurfaceTexture(
+ const sp<IGraphicBufferProducer>& bufferProducer) const;
+ virtual sp<IDisplayEventConnection> createDisplayEventConnection();
+#if ANDROID_VERSION >= 21
+ virtual void setPowerMode(const sp<IBinder>& display, int mode);
+ virtual status_t getDisplayConfigs(const sp<IBinder>& display, Vector<DisplayInfo>* configs);
+ virtual status_t getDisplayStats(const sp<IBinder>& display, DisplayStatInfo* stats);
+ virtual int getActiveConfig(const sp<IBinder>& display);
+ virtual status_t setActiveConfig(const sp<IBinder>& display, int id);
+ virtual status_t clearAnimationFrameStats();
+ virtual status_t getAnimationFrameStats(FrameStats* outStats) const;
+#elif ANDROID_VERSION >= 17
+ // called when screen needs to turn off
+ virtual void blank(const sp<IBinder>& display);
+ // called when screen is turning back on
+ virtual void unblank(const sp<IBinder>& display);
+ virtual status_t getDisplayInfo(const sp<IBinder>& display, DisplayInfo* info);
+#endif
+ void getPrimaryDisplayInfo(DisplayInfo* info);
+
+ /* ------------------------------------------------------------------------
+ * Transactions
+ */
+ uint32_t setDisplayStateLocked(const DisplayState& s);
+
+ void captureScreenImp(const sp<IGraphicBufferProducer>& producer,
+ uint32_t reqWidth,
+ uint32_t reqHeight,
+ const sp<GraphicProducerWrapper>& wrapper);
+
+ sp<IBinder> mPrimaryDisplay;
+
+ struct DisplayDeviceState {
+ enum {
+ NO_LAYER_STACK = 0xFFFFFFFF,
+ };
+ DisplayDeviceState()
+ : type(-1), displayId(-1), width(0), height(0) {
+ }
+ DisplayDeviceState(int type)
+ : type(type), displayId(-1), layerStack(NO_LAYER_STACK), orientation(0), width(0), height(0) {
+ viewport.makeInvalid();
+ frame.makeInvalid();
+ }
+ bool isValid() const { return type >= 0; }
+ int type;
+ int displayId;
+ sp<IGraphicBufferProducer> surface;
+ uint32_t layerStack;
+ Rect viewport;
+ Rect frame;
+ uint8_t orientation;
+ uint32_t width, height;
+ String8 displayName;
+ bool isSecure;
+ };
+
+ // access must be protected by mStateLock
+ mutable Mutex mStateLock;
+ DefaultKeyedVector<wp<IBinder>, DisplayDeviceState> mDisplays;
+
+ friend class DestroyDisplayRunnable;
+};
+
+// ---------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_FAKE_SURFACE_COMPOSER_H
diff --git a/widget/gonk/nativewindow/GonkBufferQueue.h b/widget/gonk/nativewindow/GonkBufferQueue.h
new file mode 100644
index 0000000000..defdb0ae2f
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueue.h
@@ -0,0 +1,22 @@
+/* Copyright 2013 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.
+ */
+
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
+# include "GonkBufferQueueLL.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19
+# include "GonkBufferQueueKK.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+# include "GonkBufferQueueJB.h"
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueJB.cpp b/widget/gonk/nativewindow/GonkBufferQueueJB.cpp
new file mode 100644
index 0000000000..81502f81ee
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueJB.cpp
@@ -0,0 +1,1036 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#define LOG_TAG "GonkBufferQueue"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#define LOG_NDEBUG 0
+
+#define GL_GLEXT_PROTOTYPES
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <utils/Log.h>
+
+#include "mozilla/layers/GrallocTextureClient.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+
+#include "GonkBufferQueueJB.h"
+
+// Macros for including the GonkBufferQueue name in log messages
+#define ST_LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define ST_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ST_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ST_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define ST_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+#define ATRACE_BUFFER_INDEX(index)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+static const char* scalingModeName(int scalingMode) {
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP";
+ default: return "Unknown";
+ }
+}
+
+GonkBufferQueue::GonkBufferQueue(bool allowSynchronousMode,
+ const sp<IGraphicBufferAlloc>& allocator) :
+ mDefaultWidth(1),
+ mDefaultHeight(1),
+ mMaxAcquiredBufferCount(1),
+ mDefaultMaxBufferCount(2),
+ mOverrideMaxBufferCount(0),
+ mSynchronousMode(true),
+ mAllowSynchronousMode(allowSynchronousMode),
+ mConnectedApi(NO_CONNECTED_API),
+ mAbandoned(false),
+ mFrameCounter(0),
+ mBufferHasBeenQueued(false),
+ mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
+ mConsumerUsageBits(0),
+ mTransformHint(0)
+{
+ // Choose a name using the PID and a process-unique ID.
+ mConsumerName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ ST_LOGV("GonkBufferQueue");
+}
+
+GonkBufferQueue::~GonkBufferQueue() {
+ ST_LOGV("~GonkBufferQueue");
+}
+
+status_t GonkBufferQueue::setDefaultMaxBufferCountLocked(int count) {
+ if (count < 2 || count > NUM_BUFFER_SLOTS)
+ return BAD_VALUE;
+
+ mDefaultMaxBufferCount = count;
+ mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+bool GonkBufferQueue::isSynchronousMode() const {
+ Mutex::Autolock lock(mMutex);
+ return mSynchronousMode;
+}
+
+void GonkBufferQueue::setConsumerName(const String8& name) {
+ Mutex::Autolock lock(mMutex);
+ mConsumerName = name;
+}
+
+status_t GonkBufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock lock(mMutex);
+ mDefaultBufferFormat = defaultFormat;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setConsumerUsageBits(uint32_t usage) {
+ Mutex::Autolock lock(mMutex);
+ mConsumerUsageBits = usage;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setTransformHint(uint32_t hint) {
+ ST_LOGV("setTransformHint: %02x", hint);
+ Mutex::Autolock lock(mMutex);
+ mTransformHint = hint;
+ return NO_ERROR;
+}
+
+already_AddRefed<TextureClient>
+GonkBufferQueue::getTextureClientFromBuffer(ANativeWindowBuffer* buffer)
+{
+ Mutex::Autolock _l(mMutex);
+ if (buffer == NULL) {
+ ST_LOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return nullptr;
+ }
+
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) {
+ RefPtr<TextureClient> client(mSlots[i].mTextureClient);
+ return client.forget();
+ }
+ }
+ ST_LOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+ return nullptr;
+}
+
+int GonkBufferQueue::getSlotFromTextureClientLocked(
+ TextureClient* client) const
+{
+ if (client == NULL) {
+ ST_LOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return BAD_VALUE;
+ }
+
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mTextureClient == client) {
+ return i;
+ }
+ }
+ ST_LOGE("getSlotFromBufferLocked: unknown TextureClient: %p", client);
+ return BAD_VALUE;
+}
+
+
+status_t GonkBufferQueue::setBufferCount(int bufferCount) {
+ ST_LOGV("setBufferCount: count=%d", bufferCount);
+
+ sp<ConsumerListener> listener;
+ {
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("setBufferCount: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (bufferCount > NUM_BUFFER_SLOTS) {
+ ST_LOGE("setBufferCount: bufferCount too large (max %d)",
+ NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ // Error out if the user has dequeued buffers
+ int maxBufferCount = getMaxBufferCountLocked();
+ for (int i=0 ; i<maxBufferCount; i++) {
+ if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) {
+ ST_LOGE("setBufferCount: client owns some buffers");
+ return -EINVAL;
+ }
+ }
+
+ const int minBufferSlots = getMinMaxBufferCountLocked();
+ if (bufferCount == 0) {
+ mOverrideMaxBufferCount = 0;
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+ }
+
+ if (bufferCount < minBufferSlots) {
+ ST_LOGE("setBufferCount: requested buffer count (%d) is less than "
+ "minimum (%d)", bufferCount, minBufferSlots);
+ return BAD_VALUE;
+ }
+
+ // here we're guaranteed that the client doesn't have dequeued buffers
+ // and will release all of its buffer references.
+ //
+ // XXX: Should this use drainQueueAndFreeBuffersLocked instead?
+ freeAllBuffersLocked();
+ mOverrideMaxBufferCount = bufferCount;
+ mBufferHasBeenQueued = false;
+ mDequeueCondition.broadcast();
+ listener = mConsumerListener;
+ } // scope for lock
+
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return NO_ERROR;
+}
+
+int GonkBufferQueue::query(int what, int* outValue)
+{
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("query: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ int value;
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ value = mDefaultWidth;
+ break;
+ case NATIVE_WINDOW_HEIGHT:
+ value = mDefaultHeight;
+ break;
+ case NATIVE_WINDOW_FORMAT:
+ value = mDefaultBufferFormat;
+ break;
+ case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+ value = getMinUndequeuedBufferCountLocked();
+ break;
+ case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+ value = (mQueue.size() >= 2);
+ break;
+ default:
+ return BAD_VALUE;
+ }
+ outValue[0] = value;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
+ ST_LOGV("requestBuffer: slot=%d", slot);
+ Mutex::Autolock lock(mMutex);
+ if (mAbandoned) {
+ ST_LOGE("requestBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ int maxBufferCount = getMaxBufferCountLocked();
+ if (slot < 0 || maxBufferCount <= slot) {
+ ST_LOGE("requestBuffer: slot index out of range [0, %d]: %d",
+ maxBufferCount, slot);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+ // XXX: I vaguely recall there was some reason this can be valid, but
+ // for the life of me I can't recall under what circumstances that's
+ // the case.
+ ST_LOGE("requestBuffer: slot %d is not owned by the client (state=%d)",
+ slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+ mSlots[slot].mRequestBufferCalled = true;
+ *buf = mSlots[slot].mGraphicBuffer;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::dequeueBuffer(int *outBuf, sp<Fence>* outFence,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
+ ST_LOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage);
+
+ if ((w && !h) || (!w && h)) {
+ ST_LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h);
+ return BAD_VALUE;
+ }
+
+ status_t returnFlags(OK);
+ int buf = INVALID_BUFFER_SLOT;
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (format == 0) {
+ format = mDefaultBufferFormat;
+ }
+ // turn on usage bits the consumer requested
+ usage |= mConsumerUsageBits;
+
+ int found = -1;
+ int dequeuedCount = 0;
+ bool tryAgain = true;
+ while (tryAgain) {
+ if (mAbandoned) {
+ ST_LOGE("dequeueBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = getMaxBufferCountLocked();
+
+ // Free up any buffers that are in slots beyond the max buffer
+ // count.
+ //for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
+ // assert(mSlots[i].mBufferState == BufferSlot::FREE);
+ // if (mSlots[i].mGraphicBuffer != NULL) {
+ // freeBufferLocked(i);
+ // returnFlags |= IGraphicBufferProducer::RELEASE_ALL_BUFFERS;
+ // }
+ //}
+
+ // look for a free buffer to give to the client
+ found = INVALID_BUFFER_SLOT;
+ dequeuedCount = 0;
+ for (int i = 0; i < maxBufferCount; i++) {
+ const int state = mSlots[i].mBufferState;
+ if (state == BufferSlot::DEQUEUED) {
+ dequeuedCount++;
+ }
+
+ if (state == BufferSlot::FREE) {
+ /* We return the oldest of the free buffers to avoid
+ * stalling the producer if possible. This is because
+ * the consumer may still have pending reads of the
+ * buffers in flight.
+ */
+ if ((found < 0) ||
+ mSlots[i].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = i;
+ }
+ }
+ }
+
+ // clients are not allowed to dequeue more than one buffer
+ // if they didn't set a buffer count.
+ if (!mOverrideMaxBufferCount && dequeuedCount) {
+ ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without "
+ "setting the buffer count");
+ return -EINVAL;
+ }
+
+ // See whether a buffer has been queued since the last
+ // setBufferCount so we know whether to perform the min undequeued
+ // buffers check below.
+ if (mBufferHasBeenQueued) {
+ // make sure the client is not trying to dequeue more buffers
+ // than allowed.
+ const int newUndequeuedCount = maxBufferCount - (dequeuedCount+1);
+ const int minUndequeuedCount = getMinUndequeuedBufferCountLocked();
+ if (newUndequeuedCount < minUndequeuedCount) {
+ ST_LOGE("dequeueBuffer: min undequeued buffer count (%d) "
+ "exceeded (dequeued=%d undequeudCount=%d)",
+ minUndequeuedCount, dequeuedCount,
+ newUndequeuedCount);
+ return -EBUSY;
+ }
+ }
+
+ // If no buffer is found, wait for a buffer to be released or for
+ // the max buffer count to change.
+ tryAgain = found == INVALID_BUFFER_SLOT;
+ if (tryAgain) {
+ mDequeueCondition.wait(mMutex);
+ }
+ }
+
+
+ if (found == INVALID_BUFFER_SLOT) {
+ // This should not happen.
+ ST_LOGE("dequeueBuffer: no available buffer slots");
+ return -EBUSY;
+ }
+
+ buf = found;
+ *outBuf = found;
+
+ const bool useDefaultSize = !w && !h;
+ if (useDefaultSize) {
+ // use the default size
+ w = mDefaultWidth;
+ h = mDefaultHeight;
+ }
+
+ mSlots[buf].mBufferState = BufferSlot::DEQUEUED;
+
+ const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer);
+ if ((buffer == NULL) ||
+ (uint32_t(buffer->width) != w) ||
+ (uint32_t(buffer->height) != h) ||
+ (uint32_t(buffer->format) != format) ||
+ ((uint32_t(buffer->usage) & usage) != usage))
+ {
+ mSlots[buf].mAcquireCalled = false;
+ mSlots[buf].mGraphicBuffer = NULL;
+ mSlots[buf].mRequestBufferCalled = false;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ if (mSlots[buf].mTextureClient) {
+ mSlots[buf].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[buf].mTextureClient);
+ mSlots[buf].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ returnFlags |= IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION;
+ }
+
+ *outFence = mSlots[buf].mFence;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ } // end lock scope
+
+ sp<GraphicBuffer> graphicBuffer;
+ if (returnFlags & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+
+ usage |= GraphicBuffer::USAGE_HW_TEXTURE;
+ RefPtr<LayersIPCChannel> allocator = ImageBridgeChild::GetSingleton();
+ GrallocTextureData* texData = GrallocTextureData::Create(IntSize(w,h), format,
+ gfx::BackendType::NONE, usage,
+ allocator);
+ if (!texData) {
+ ST_LOGE("dequeueBuffer: failed to alloc gralloc buffer");
+ return -ENOMEM;
+ }
+ RefPtr<TextureClient> textureClient = new TextureClient(texData, TextureFlags::RECYCLE | TextureFlags::DEALLOCATE_CLIENT, allocator);
+ sp<GraphicBuffer> graphicBuffer = texData->GetGraphicBuffer();
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("dequeueBuffer: SurfaceTexture has been abandoned!");
+ return NO_INIT;
+ }
+
+ mSlots[buf].mGraphicBuffer = graphicBuffer;
+ mSlots[buf].mTextureClient = textureClient;
+ ST_LOGD("dequeueBuffer: returning slot=%d buf=%p ", buf,
+ mSlots[buf].mGraphicBuffer->handle);
+ //mSlots[*outBuf].mGraphicBuffer = graphicBuffer;
+ }
+ }
+
+ ST_LOGV("dequeueBuffer: returning slot=%d buf=%p flags=%#x", *outBuf,
+ mSlots[*outBuf].mGraphicBuffer->handle, returnFlags);
+
+ return returnFlags;
+}
+
+status_t GonkBufferQueue::setSynchronousMode(bool enabled) {
+ ST_LOGV("setSynchronousMode: enabled=%d", enabled);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("setSynchronousMode: BufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mSynchronousMode != enabled) {
+ mSynchronousMode = enabled;
+ mDequeueCondition.broadcast();
+ }
+ return OK;
+}
+
+status_t GonkBufferQueue::queueBuffer(int buf,
+ const QueueBufferInput& input, QueueBufferOutput* output) {
+
+ Rect crop;
+ uint32_t transform;
+ int scalingMode;
+ int64_t timestamp;
+ sp<Fence> fence;
+
+ input.deflate(&timestamp, &crop, &scalingMode, &transform, &fence);
+
+#if ANDROID_VERSION >= 18
+ if (fence == NULL) {
+ ST_LOGE("queueBuffer: fence is NULL");
+ return BAD_VALUE;
+ }
+#endif
+
+ ST_LOGV("queueBuffer: slot=%d time=%#llx crop=[%d,%d,%d,%d] tr=%#x "
+ "scale=%s",
+ buf, timestamp, crop.left, crop.top, crop.right, crop.bottom,
+ transform, scalingModeName(scalingMode));
+
+ sp<ConsumerListener> listener;
+
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+ if (mAbandoned) {
+ ST_LOGE("queueBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ int maxBufferCount = getMaxBufferCountLocked();
+ if (buf < 0 || buf >= maxBufferCount) {
+ ST_LOGE("queueBuffer: slot index out of range [0, %d]: %d",
+ maxBufferCount, buf);
+ return -EINVAL;
+ } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+ ST_LOGE("queueBuffer: slot %d is not owned by the client "
+ "(state=%d)", buf, mSlots[buf].mBufferState);
+ return -EINVAL;
+ } else if (!mSlots[buf].mRequestBufferCalled) {
+ ST_LOGE("queueBuffer: slot %d was enqueued without requesting a "
+ "buffer", buf);
+ return -EINVAL;
+ }
+
+ const sp<GraphicBuffer>& graphicBuffer(mSlots[buf].mGraphicBuffer);
+ Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
+ Rect croppedCrop;
+ crop.intersect(bufferRect, &croppedCrop);
+ if (croppedCrop != crop) {
+ ST_LOGE("queueBuffer: crop rect is not contained within the "
+ "buffer in slot %d", buf);
+ return -EINVAL;
+ }
+
+ if (mSynchronousMode) {
+ // In synchronous mode we queue all buffers in a FIFO.
+ mQueue.push_back(buf);
+ } else {
+ // In asynchronous mode we only keep the most recent buffer.
+ if (mQueue.empty()) {
+ mQueue.push_back(buf);
+ } else {
+ Fifo::iterator front(mQueue.begin());
+ // buffer currently queued is freed
+ mSlots[*front].mBufferState = BufferSlot::FREE;
+ // and we record the new buffer index in the queued list
+ *front = buf;
+ }
+ }
+ // always signals that an additional frame should be consumed
+ // to handle max acquired buffer count reached case.
+ listener = mConsumerListener;
+
+ mSlots[buf].mTimestamp = timestamp;
+ mSlots[buf].mCrop = crop;
+ mSlots[buf].mTransform = transform;
+ mSlots[buf].mFence = fence;
+
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
+ break;
+ default:
+ ST_LOGE("unknown scaling mode: %d (ignoring)", scalingMode);
+ scalingMode = mSlots[buf].mScalingMode;
+ break;
+ }
+
+ mSlots[buf].mBufferState = BufferSlot::QUEUED;
+ mSlots[buf].mScalingMode = scalingMode;
+ mFrameCounter++;
+ mSlots[buf].mFrameNumber = mFrameCounter;
+
+ mBufferHasBeenQueued = true;
+ mDequeueCondition.broadcast();
+
+ output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint,
+ mQueue.size());
+ } // scope for the lock
+
+ // call back without lock held
+ if (listener != 0) {
+ listener->onFrameAvailable();
+ }
+ return NO_ERROR;
+}
+
+#if ANDROID_VERSION == 17
+void GonkBufferQueue::cancelBuffer(int buf, sp<Fence> fence) {
+#else
+void GonkBufferQueue::cancelBuffer(int buf, const sp<Fence>& fence) {
+#endif
+
+ ST_LOGV("cancelBuffer: slot=%d", buf);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGW("cancelBuffer: GonkBufferQueue has been abandoned!");
+ return;
+ }
+
+ int maxBufferCount = getMaxBufferCountLocked();
+ if (buf < 0 || buf >= maxBufferCount) {
+ ST_LOGE("cancelBuffer: slot index out of range [0, %d]: %d",
+ maxBufferCount, buf);
+ return;
+ } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+ ST_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
+ buf, mSlots[buf].mBufferState);
+ return;
+#if ANDROID_VERSION >= 18
+ } else if (fence == NULL) {
+ ST_LOGE("cancelBuffer: fence is NULL");
+ return;
+#endif
+ }
+ mSlots[buf].mBufferState = BufferSlot::FREE;
+ mSlots[buf].mFrameNumber = 0;
+ mSlots[buf].mFence = fence;
+ mDequeueCondition.broadcast();
+}
+
+status_t GonkBufferQueue::connect(int api, QueueBufferOutput* output) {
+ ST_LOGV("connect: api=%d", api);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("connect: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mConsumerListener == NULL) {
+ ST_LOGE("connect: GonkBufferQueue has no consumer!");
+ return NO_INIT;
+ }
+
+ int err = NO_ERROR;
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ if (mConnectedApi != NO_CONNECTED_API) {
+ ST_LOGE("connect: already connected (cur=%d, req=%d)",
+ mConnectedApi, api);
+ err = -EINVAL;
+ } else {
+ mConnectedApi = api;
+ output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint,
+ mQueue.size());
+ }
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ mBufferHasBeenQueued = false;
+
+ return err;
+}
+
+status_t GonkBufferQueue::disconnect(int api) {
+ ST_LOGV("disconnect: api=%d", api);
+
+ int err = NO_ERROR;
+ sp<ConsumerListener> listener;
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ // it is not really an error to disconnect after the surface
+ // has been abandoned, it should just be a no-op.
+ return NO_ERROR;
+ }
+
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ if (mConnectedApi == api) {
+ freeAllBuffersLocked();
+ mConnectedApi = NO_CONNECTED_API;
+ mDequeueCondition.broadcast();
+ listener = mConsumerListener;
+ } else {
+ ST_LOGE("disconnect: connected to another api (cur=%d, req=%d)",
+ mConnectedApi, api);
+ err = -EINVAL;
+ }
+ break;
+ default:
+ ST_LOGE("disconnect: unknown API %d", api);
+ err = -EINVAL;
+ break;
+ }
+ }
+
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return err;
+}
+
+void GonkBufferQueue::dumpToString(String8& result) const
+{
+ char buffer[1024];
+ GonkBufferQueue::dumpToString(result, "", buffer, 1024);
+}
+
+void GonkBufferQueue::dumpToString(String8& result, const char* prefix,
+ char* buffer, size_t SIZE) const
+{
+ Mutex::Autolock _l(mMutex);
+
+ String8 fifo;
+ int fifoSize = 0;
+ Fifo::const_iterator i(mQueue.begin());
+ while (i != mQueue.end()) {
+ snprintf(buffer, SIZE, "%02d ", *i++);
+ fifoSize++;
+ fifo.append(buffer);
+ }
+
+ int maxBufferCount = getMaxBufferCountLocked();
+
+ snprintf(buffer, SIZE,
+ "%s-BufferQueue maxBufferCount=%d, mSynchronousMode=%d, default-size=[%dx%d], "
+ "default-format=%d, transform-hint=%02x, FIFO(%d)={%s}\n",
+ prefix, maxBufferCount, mSynchronousMode, mDefaultWidth,
+ mDefaultHeight, mDefaultBufferFormat, mTransformHint,
+ fifoSize, fifo.string());
+ result.append(buffer);
+
+
+ struct {
+ const char * operator()(int state) const {
+ switch (state) {
+ case BufferSlot::DEQUEUED: return "DEQUEUED";
+ case BufferSlot::QUEUED: return "QUEUED";
+ case BufferSlot::FREE: return "FREE";
+ case BufferSlot::ACQUIRED: return "ACQUIRED";
+ default: return "Unknown";
+ }
+ }
+ } stateName;
+
+ for (int i=0 ; i<maxBufferCount ; i++) {
+ const BufferSlot& slot(mSlots[i]);
+ snprintf(buffer, SIZE,
+ "%s%s[%02d] "
+ "state=%-8s, crop=[%d,%d,%d,%d], "
+ "xform=0x%02x, time=%#llx, scale=%s",
+ prefix, (slot.mBufferState == BufferSlot::ACQUIRED)?">":" ", i,
+ stateName(slot.mBufferState),
+ slot.mCrop.left, slot.mCrop.top, slot.mCrop.right,
+ slot.mCrop.bottom, slot.mTransform, slot.mTimestamp,
+ scalingModeName(slot.mScalingMode)
+ );
+ result.append(buffer);
+
+ const sp<GraphicBuffer>& buf(slot.mGraphicBuffer);
+ if (buf != NULL) {
+ snprintf(buffer, SIZE,
+ ", %p [%4ux%4u:%4u,%3X]",
+ buf->handle, buf->width, buf->height, buf->stride,
+ buf->format);
+ result.append(buffer);
+ }
+ result.append("\n");
+ }
+}
+
+void GonkBufferQueue::freeAllBuffersLocked()
+{
+ ALOGW_IF(!mQueue.isEmpty(),
+ "freeAllBuffersLocked called but mQueue is not empty");
+ mQueue.clear();
+ mBufferHasBeenQueued = false;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ mSlots[i].mGraphicBuffer = 0;
+ if (mSlots[i].mTextureClient) {
+ mSlots[i].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[i].mTextureClient);
+ mSlots[i].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[i].mNeedsCleanupOnRelease = true;
+ }
+ mSlots[i].mBufferState = BufferSlot::FREE;
+ mSlots[i].mFrameNumber = 0;
+ mSlots[i].mAcquireCalled = false;
+ // destroy fence as GonkBufferQueue now takes ownership
+ mSlots[i].mFence = Fence::NO_FENCE;
+ }
+}
+
+status_t GonkBufferQueue::acquireBuffer(BufferItem *buffer) {
+ Mutex::Autolock _l(mMutex);
+
+ // Check that the consumer doesn't currently have the maximum number of
+ // buffers acquired. We allow the max buffer count to be exceeded by one
+ // buffer, so that the consumer can successfully set up the newly acquired
+ // buffer before releasing the old one.
+ int numAcquiredBuffers = 0;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
+ numAcquiredBuffers++;
+ }
+ }
+ if (numAcquiredBuffers >= mMaxAcquiredBufferCount+1) {
+ ST_LOGE("acquireBuffer: max acquired buffer count reached: %d (max=%d)",
+ numAcquiredBuffers, mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+
+ // check if queue is empty
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ if (!mQueue.empty()) {
+ Fifo::iterator front(mQueue.begin());
+ int buf = *front;
+
+ // In android, when the buffer is aquired by BufferConsumer,
+ // BufferQueue releases a reference to the buffer and
+ // it's ownership moves to the BufferConsumer.
+ // In b2g, GonkBufferQueue continues to have a buffer ownership.
+ // It is necessary to free buffer via ImageBridgeChild.
+
+ //if (mSlots[buf].mAcquireCalled) {
+ // buffer->mGraphicBuffer = NULL;
+ //} else {
+ // buffer->mGraphicBuffer = mSlots[buf].mGraphicBuffer;
+ //}
+ buffer->mGraphicBuffer = mSlots[buf].mGraphicBuffer;
+ buffer->mCrop = mSlots[buf].mCrop;
+ buffer->mTransform = mSlots[buf].mTransform;
+ buffer->mScalingMode = mSlots[buf].mScalingMode;
+ buffer->mFrameNumber = mSlots[buf].mFrameNumber;
+ buffer->mTimestamp = mSlots[buf].mTimestamp;
+ buffer->mBuf = buf;
+ buffer->mFence = mSlots[buf].mFence;
+
+ mSlots[buf].mAcquireCalled = true;
+ mSlots[buf].mNeedsCleanupOnRelease = false;
+ mSlots[buf].mBufferState = BufferSlot::ACQUIRED;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+
+ mQueue.erase(front);
+ mDequeueCondition.broadcast();
+ } else {
+ return NO_BUFFER_AVAILABLE;
+ }
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::releaseBuffer(int buf, const sp<Fence>& fence) {
+ Mutex::Autolock _l(mMutex);
+
+#if ANDROID_VERSION == 17
+ if (buf == INVALID_BUFFER_SLOT) {
+#else
+ if (buf == INVALID_BUFFER_SLOT || fence == NULL) {
+#endif
+ return BAD_VALUE;
+ }
+
+ mSlots[buf].mFence = fence;
+
+ // The buffer can now only be released if its in the acquired state
+ if (mSlots[buf].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[buf].mBufferState = BufferSlot::FREE;
+ } else if (mSlots[buf].mNeedsCleanupOnRelease) {
+ ST_LOGV("releasing a stale buf %d its state was %d", buf, mSlots[buf].mBufferState);
+ mSlots[buf].mNeedsCleanupOnRelease = false;
+ return STALE_BUFFER_SLOT;
+ } else {
+ ST_LOGE("attempted to release buf %d but its state was %d", buf, mSlots[buf].mBufferState);
+ return -EINVAL;
+ }
+
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::consumerConnect(const sp<ConsumerListener>& consumerListener) {
+ ST_LOGV("consumerConnect");
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("consumerConnect: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (consumerListener == NULL) {
+ ST_LOGE("consumerConnect: consumerListener may not be NULL");
+ return BAD_VALUE;
+ }
+
+ mConsumerListener = consumerListener;
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::consumerDisconnect() {
+ ST_LOGV("consumerDisconnect");
+ Mutex::Autolock lock(mMutex);
+
+ if (mConsumerListener == NULL) {
+ ST_LOGE("consumerDisconnect: No consumer is connected!");
+ return -EINVAL;
+ }
+
+ mAbandoned = true;
+ mConsumerListener = NULL;
+ mQueue.clear();
+ freeAllBuffersLocked();
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::getReleasedBuffers(uint32_t* slotMask) {
+ ST_LOGV("getReleasedBuffers");
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ST_LOGE("getReleasedBuffers: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ uint32_t mask = 0;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (!mSlots[i].mAcquireCalled) {
+ mask |= 1 << i;
+ }
+ }
+ *slotMask = mask;
+
+ ST_LOGV("getReleasedBuffers: returning mask %#x", mask);
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setDefaultBufferSize(uint32_t w, uint32_t h)
+{
+ ST_LOGV("setDefaultBufferSize: w=%d, h=%d", w, h);
+ if (!w || !h) {
+ ST_LOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)",
+ w, h);
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mMutex);
+ mDefaultWidth = w;
+ mDefaultHeight = h;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setDefaultMaxBufferCount(int bufferCount) {
+ Mutex::Autolock lock(mMutex);
+ return setDefaultMaxBufferCountLocked(bufferCount);
+}
+
+status_t GonkBufferQueue::setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
+ Mutex::Autolock lock(mMutex);
+ if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > MAX_MAX_ACQUIRED_BUFFERS) {
+ ST_LOGE("setMaxAcquiredBufferCount: invalid count specified: %d",
+ maxAcquiredBuffers);
+ return BAD_VALUE;
+ }
+ if (mConnectedApi != NO_CONNECTED_API) {
+ return INVALID_OPERATION;
+ }
+ mMaxAcquiredBufferCount = maxAcquiredBuffers;
+ return NO_ERROR;
+}
+
+int GonkBufferQueue::getMinMaxBufferCountLocked() const {
+ return getMinUndequeuedBufferCountLocked() + 1;
+}
+
+int GonkBufferQueue::getMinUndequeuedBufferCountLocked() const {
+ return mSynchronousMode ? mMaxAcquiredBufferCount :
+ mMaxAcquiredBufferCount + 1;
+}
+
+int GonkBufferQueue::getMaxBufferCountLocked() const {
+ int minMaxBufferCount = getMinMaxBufferCountLocked();
+
+ int maxBufferCount = mDefaultMaxBufferCount;
+ if (maxBufferCount < minMaxBufferCount) {
+ maxBufferCount = minMaxBufferCount;
+ }
+ if (mOverrideMaxBufferCount != 0) {
+ assert(mOverrideMaxBufferCount >= minMaxBufferCount);
+ maxBufferCount = mOverrideMaxBufferCount;
+ }
+
+ // Any buffers that are dequeued by the producer or sitting in the queue
+ // waiting to be consumed need to have their slots preserved. Such
+ // buffers will temporarily keep the max buffer count up until the slots
+ // no longer need to be preserved.
+ for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
+ BufferSlot::BufferState state = mSlots[i].mBufferState;
+ if (state == BufferSlot::QUEUED || state == BufferSlot::DEQUEUED) {
+ maxBufferCount = i + 1;
+ }
+ }
+
+ return maxBufferCount;
+}
+
+GonkBufferQueue::ProxyConsumerListener::ProxyConsumerListener(
+ const wp<GonkBufferQueue::ConsumerListener>& consumerListener):
+ mConsumerListener(consumerListener) {}
+
+GonkBufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {}
+
+void GonkBufferQueue::ProxyConsumerListener::onFrameAvailable() {
+ sp<GonkBufferQueue::ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkBufferQueue::ProxyConsumerListener::onBuffersReleased() {
+ sp<GonkBufferQueue::ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueJB.h b/widget/gonk/nativewindow/GonkBufferQueueJB.h
new file mode 100644
index 0000000000..df0c4599f8
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueJB.h
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUE_JB_H
+#define NATIVEWINDOW_GONKBUFFERQUEUE_JB_H
+
+#include <gui/IGraphicBufferAlloc.h>
+#if ANDROID_VERSION == 17
+#include <gui/ISurfaceTexture.h>
+#else
+#include <gui/IGraphicBufferProducer.h>
+#endif
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "mozilla/layers/LayersSurfaces.h"
+#include "mozilla/layers/TextureClient.h"
+
+#if ANDROID_VERSION == 17
+#define IGraphicBufferProducer ISurfaceTexture
+#endif
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+#if ANDROID_VERSION == 17
+class GonkBufferQueue : public BnSurfaceTexture {
+#else
+class GonkBufferQueue : public BnGraphicBufferProducer {
+#endif
+ typedef mozilla::layers::TextureClient TextureClient;
+
+public:
+ enum { MIN_UNDEQUEUED_BUFFERS = 2 };
+ enum { NUM_BUFFER_SLOTS = 32 };
+ enum { NO_CONNECTED_API = 0 };
+ enum { INVALID_BUFFER_SLOT = -1 };
+ enum { STALE_BUFFER_SLOT = 1, NO_BUFFER_AVAILABLE };
+
+ // When in async mode we reserve two slots in order to guarantee that the
+ // producer and consumer can run asynchronously.
+ enum { MAX_MAX_ACQUIRED_BUFFERS = NUM_BUFFER_SLOTS - 2 };
+
+ // ConsumerListener is the interface through which the GonkBufferQueue notifies
+ // the consumer of events that the consumer may wish to react to. Because
+ // the consumer will generally have a mutex that is locked during calls from
+ // the consumer to the GonkBufferQueue, these calls from the GonkBufferQueue to the
+ // consumer *MUST* be called only when the GonkBufferQueue mutex is NOT locked.
+ struct ConsumerListener : public virtual RefBase {
+ // onFrameAvailable is called from queueBuffer each time an additional
+ // frame becomes available for consumption. This means that frames that
+ // are queued while in asynchronous mode only trigger the callback if no
+ // previous frames are pending. Frames queued while in synchronous mode
+ // always trigger the callback.
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onFrameAvailable() = 0;
+
+ // onBuffersReleased is called to notify the buffer consumer that the
+ // GonkBufferQueue has released its references to one or more GraphicBuffers
+ // contained in its slots. The buffer consumer should then call
+ // GonkBufferQueue::getReleasedBuffers to retrieve the list of buffers
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onBuffersReleased() = 0;
+ };
+
+ // ProxyConsumerListener is a ConsumerListener implementation that keeps a weak
+ // reference to the actual consumer object. It forwards all calls to that
+ // consumer object so long as it exists.
+ //
+ // This class exists to avoid having a circular reference between the
+ // GonkBufferQueue object and the consumer object. The reason this can't be a weak
+ // reference in the GonkBufferQueue class is because we're planning to expose the
+ // consumer side of a GonkBufferQueue as a binder interface, which doesn't support
+ // weak references.
+ class ProxyConsumerListener : public GonkBufferQueue::ConsumerListener {
+ public:
+
+ ProxyConsumerListener(const wp<GonkBufferQueue::ConsumerListener>& consumerListener);
+ virtual ~ProxyConsumerListener();
+ virtual void onFrameAvailable();
+ virtual void onBuffersReleased();
+
+ private:
+
+ // mConsumerListener is a weak reference to the ConsumerListener. This is
+ // the raison d'etre of ProxyConsumerListener.
+ wp<GonkBufferQueue::ConsumerListener> mConsumerListener;
+ };
+
+
+ // GonkBufferQueue manages a pool of gralloc memory slots to be used by
+ // producers and consumers. allowSynchronousMode specifies whether or not
+ // synchronous mode can be enabled by the producer. allocator is used to
+ // allocate all the needed gralloc buffers.
+ GonkBufferQueue(bool allowSynchronousMode = true,
+ const sp<IGraphicBufferAlloc>& allocator = NULL);
+ virtual ~GonkBufferQueue();
+
+ // Query native window attributes. The "what" values are enumerated in
+ // window.h (e.g. NATIVE_WINDOW_FORMAT).
+ virtual int query(int what, int* value);
+
+ // setBufferCount updates the number of available buffer slots. If this
+ // method succeeds, buffer slots will be both unallocated and owned by
+ // the GonkBufferQueue object (i.e. they are not owned by the producer or
+ // consumer).
+ //
+ // This will fail if the producer has dequeued any buffers, or if
+ // bufferCount is invalid. bufferCount must generally be a value
+ // between the minimum undequeued buffer count and NUM_BUFFER_SLOTS
+ // (inclusive). It may also be set to zero (the default) to indicate
+ // that the producer does not wish to set a value. The minimum value
+ // can be obtained by calling query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ // ...).
+ //
+ // This may only be called by the producer. The consumer will be told
+ // to discard buffers through the onBuffersReleased callback.
+ virtual status_t setBufferCount(int bufferCount);
+
+ // requestBuffer returns the GraphicBuffer for slot N.
+ //
+ // In normal operation, this is called the first time slot N is returned
+ // by dequeueBuffer. It must be called again if dequeueBuffer returns
+ // flags indicating that previously-returned buffers are no longer valid.
+ virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
+
+ // dequeueBuffer gets the next buffer slot index for the producer to use.
+ // If a buffer slot is available then that slot index is written to the
+ // location pointed to by the buf argument and a status of OK is returned.
+ // If no slot is available then a status of -EBUSY is returned and buf is
+ // unmodified.
+ //
+ // The fence parameter will be updated to hold the fence associated with
+ // the buffer. The contents of the buffer must not be overwritten until the
+ // fence signals. If the fence is Fence::NO_FENCE, the buffer may be
+ // written immediately.
+ //
+ // The width and height parameters must be no greater than the minimum of
+ // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv).
+ // An error due to invalid dimensions might not be reported until
+ // updateTexImage() is called. If width and height are both zero, the
+ // default values specified by setDefaultBufferSize() are used instead.
+ //
+ // The pixel formats are enumerated in graphics.h, e.g.
+ // HAL_PIXEL_FORMAT_RGBA_8888. If the format is 0, the default format
+ // will be used.
+ //
+ // The usage argument specifies gralloc buffer usage flags. The values
+ // are enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER. These
+ // will be merged with the usage flags specified by setConsumerUsageBits.
+ //
+ // The return value may be a negative error value or a non-negative
+ // collection of flags. If the flags are set, the return values are
+ // valid, but additional actions must be performed.
+ //
+ // If IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION is set, the
+ // producer must discard cached GraphicBuffer references for the slot
+ // returned in buf.
+ // If IGraphicBufferProducer::RELEASE_ALL_BUFFERS is set, the producer
+ // must discard cached GraphicBuffer references for all slots.
+ //
+ // In both cases, the producer will need to call requestBuffer to get a
+ // GraphicBuffer handle for the returned slot.
+#if ANDROID_VERSION == 17
+ virtual status_t dequeueBuffer(int *buf, sp<Fence>& fence,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage) {
+ return dequeueBuffer(buf, &fence, width, height, format, usage);
+ }
+#endif
+
+ virtual status_t dequeueBuffer(int *buf, sp<Fence>* fence,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage);
+
+ // queueBuffer returns a filled buffer to the GonkBufferQueue.
+ //
+ // Additional data is provided in the QueueBufferInput struct. Notably,
+ // a timestamp must be provided for the buffer. The timestamp is in
+ // nanoseconds, and must be monotonically increasing. Its other semantics
+ // (zero point, etc) are producer-specific and should be documented by the
+ // producer.
+ //
+ // The caller may provide a fence that signals when all rendering
+ // operations have completed. Alternatively, NO_FENCE may be used,
+ // indicating that the buffer is ready immediately.
+ //
+ // Some values are returned in the output struct: the current settings
+ // for default width and height, the current transform hint, and the
+ // number of queued buffers.
+ virtual status_t queueBuffer(int buf,
+ const QueueBufferInput& input, QueueBufferOutput* output);
+
+ // cancelBuffer returns a dequeued buffer to the GonkBufferQueue, but doesn't
+ // queue it for use by the consumer.
+ //
+ // The buffer will not be overwritten until the fence signals. The fence
+ // will usually be the one obtained from dequeueBuffer.
+#if ANDROID_VERSION == 17
+ virtual void cancelBuffer(int buf, sp<Fence> fence);
+#else
+ virtual void cancelBuffer(int buf, const sp<Fence>& fence);
+#endif
+
+ // setSynchronousMode sets whether dequeueBuffer is synchronous or
+ // asynchronous. In synchronous mode, dequeueBuffer blocks until
+ // a buffer is available, the currently bound buffer can be dequeued and
+ // queued buffers will be acquired in order. In asynchronous mode,
+ // a queued buffer may be replaced by a subsequently queued buffer.
+ //
+ // The default mode is synchronous.
+ // This should be called only during initialization.
+ virtual status_t setSynchronousMode(bool enabled);
+
+ // connect attempts to connect a producer API to the GonkBufferQueue. This
+ // must be called before any other IGraphicBufferProducer methods are
+ // called except for getAllocator. A consumer must already be connected.
+ //
+ // This method will fail if connect was previously called on the
+ // GonkBufferQueue and no corresponding disconnect call was made (i.e. if
+ // it's still connected to a producer).
+ //
+ // APIs are enumerated in window.h (e.g. NATIVE_WINDOW_API_CPU).
+ virtual status_t connect(int api, QueueBufferOutput* output);
+
+ // disconnect attempts to disconnect a producer API from the GonkBufferQueue.
+ // Calling this method will cause any subsequent calls to other
+ // IGraphicBufferProducer methods to fail except for getAllocator and connect.
+ // Successfully calling connect after this will allow the other methods to
+ // succeed again.
+ //
+ // This method will fail if the the GonkBufferQueue is not currently
+ // connected to the specified producer API.
+ virtual status_t disconnect(int api);
+
+ // dump our state in a String
+ virtual void dumpToString(String8& result) const;
+ virtual void dumpToString(String8& result, const char* prefix, char* buffer, size_t SIZE) const;
+
+ // public facing structure for BufferSlot
+ struct BufferItem {
+
+ BufferItem()
+ :
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mFrameNumber(0),
+ mBuf(INVALID_BUFFER_SLOT) {
+ mCrop.makeInvalid();
+ }
+ // mGraphicBuffer points to the buffer allocated for this slot, or is NULL
+ // if the buffer in this slot has been acquired in the past (see
+ // BufferSlot.mAcquireCalled).
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued.
+ int64_t mTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot.
+ uint64_t mFrameNumber;
+
+ // mBuf is the slot index of this buffer
+ int mBuf;
+
+ // mFence is a fence that will signal when the buffer is idle.
+ sp<Fence> mFence;
+ };
+
+ // The following public functions are the consumer-facing interface
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the GonkBufferQueue. If no buffer is pending then it returns -EINVAL. If a
+ // buffer is successfully acquired, the information about the buffer is
+ // returned in BufferItem. If the buffer returned had previously been
+ // acquired then the BufferItem::mGraphicBuffer field of buffer is set to
+ // NULL and it is assumed that the consumer still holds a reference to the
+ // buffer.
+ status_t acquireBuffer(BufferItem *buffer);
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // GonkBufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ //
+ // Note that the dependencies on EGL will be removed once we switch to using
+ // the Android HW Sync HAL.
+ status_t releaseBuffer(int buf, const sp<Fence>& releaseFence);
+
+ // consumerConnect connects a consumer to the GonkBufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // GonkBufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the GonkBufferQueue by the producer to fail.
+ //
+ // consumer may not be NULL.
+ status_t consumerConnect(const sp<ConsumerListener>& consumer);
+
+ // consumerDisconnect disconnects a consumer from the GonkBufferQueue. All
+ // buffers will be freed and the GonkBufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the GonkBufferQueue by the producer to
+ // fail.
+ status_t consumerDisconnect();
+
+ // getReleasedBuffers sets the value pointed to by slotMask to a bit mask
+ // indicating which buffer slots have been released by the GonkBufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ status_t getReleasedBuffers(uint32_t* slotMask);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ status_t setDefaultMaxBufferCount(int bufferCount);
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the GonkBufferQueue.
+ status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
+
+ // isSynchronousMode returns whether the GonkBufferQueue is currently in
+ // synchronous mode.
+ bool isSynchronousMode() const;
+
+ // setConsumerName sets the name used in logging
+ void setConsumerName(const String8& name);
+
+ // setDefaultBufferFormat allows the GonkBufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ status_t setConsumerUsageBits(uint32_t usage);
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ status_t setTransformHint(uint32_t hint);
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ int getSlotFromTextureClientLocked(TextureClient* client) const;
+
+private:
+ // freeBufferLocked frees the GraphicBuffer and sync resources for the
+ // given slot.
+ //void freeBufferLocked(int index);
+
+ // freeAllBuffersLocked frees the GraphicBuffer and sync resources for
+ // all slots.
+ //void freeAllBuffersLocked();
+ void freeAllBuffersLocked();
+
+ // setDefaultMaxBufferCountLocked sets the maximum number of buffer slots
+ // that will be used if the producer does not override the buffer slot
+ // count. The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ // The initial default is 2.
+ status_t setDefaultMaxBufferCountLocked(int count);
+
+ // getMinBufferCountLocked returns the minimum number of buffers allowed
+ // given the current GonkBufferQueue state.
+ int getMinMaxBufferCountLocked() const;
+
+ // getMinUndequeuedBufferCountLocked returns the minimum number of buffers
+ // that must remain in a state other than DEQUEUED.
+ int getMinUndequeuedBufferCountLocked() const;
+
+ // getMaxBufferCountLocked returns the maximum number of buffers that can
+ // be allocated at once. This value depends upon the following member
+ // variables:
+ //
+ // mSynchronousMode
+ // mMaxAcquiredBufferCount
+ // mDefaultMaxBufferCount
+ // mOverrideMaxBufferCount
+ //
+ // Any time one of these member variables is changed while a producer is
+ // connected, mDequeueCondition must be broadcast.
+ int getMaxBufferCountLocked() const;
+
+ struct BufferSlot {
+
+ BufferSlot()
+ : mBufferState(BufferSlot::FREE),
+ mRequestBufferCalled(false),
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mFrameNumber(0),
+ mAcquireCalled(false),
+ mNeedsCleanupOnRelease(false) {
+ mCrop.makeInvalid();
+ }
+
+ // mGraphicBuffer points to the buffer allocated for this slot or is NULL
+ // if no buffer has been allocated.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mTextureClient is a thin abstraction over remotely allocated GraphicBuffer.
+ RefPtr<TextureClient> mTextureClient;
+
+ // BufferState represents the different states in which a buffer slot
+ // can be. All slots are initially FREE.
+ enum BufferState {
+ // FREE indicates that the buffer is available to be dequeued
+ // by the producer. The buffer may be in use by the consumer for
+ // a finite time, so the buffer must not be modified until the
+ // associated fence is signaled.
+ //
+ // The slot is "owned" by GonkBufferQueue. It transitions to DEQUEUED
+ // when dequeueBuffer is called.
+ FREE = 0,
+
+ // DEQUEUED indicates that the buffer has been dequeued by the
+ // producer, but has not yet been queued or canceled. The
+ // producer may modify the buffer's contents as soon as the
+ // associated ready fence is signaled.
+ //
+ // The slot is "owned" by the producer. It can transition to
+ // QUEUED (via queueBuffer) or back to FREE (via cancelBuffer).
+ DEQUEUED = 1,
+
+ // QUEUED indicates that the buffer has been filled by the
+ // producer and queued for use by the consumer. The buffer
+ // contents may continue to be modified for a finite time, so
+ // the contents must not be accessed until the associated fence
+ // is signaled.
+ //
+ // The slot is "owned" by GonkBufferQueue. It can transition to
+ // ACQUIRED (via acquireBuffer) or to FREE (if another buffer is
+ // queued in asynchronous mode).
+ QUEUED = 2,
+
+ // ACQUIRED indicates that the buffer has been acquired by the
+ // consumer. As with QUEUED, the contents must not be accessed
+ // by the consumer until the fence is signaled.
+ //
+ // The slot is "owned" by the consumer. It transitions to FREE
+ // when releaseBuffer is called.
+ ACQUIRED = 3
+ };
+
+ // mBufferState is the current state of this buffer slot.
+ BufferState mBufferState;
+
+ // mRequestBufferCalled is used for validating that the producer did
+ // call requestBuffer() when told to do so. Technically this is not
+ // needed but useful for debugging and catching producer bugs.
+ bool mRequestBufferCalled;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ // (example: NATIVE_WINDOW_TRANSFORM_ROT_90)
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ // (example: NATIVE_WINDOW_SCALING_MODE_FREEZE)
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued.
+ int64_t mTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot. This
+ // is used to dequeue buffers in LRU order (useful because buffers
+ // may be released before their release fence is signaled).
+ uint64_t mFrameNumber;
+
+ // mEglFence is the EGL sync object that must signal before the buffer
+ // associated with this buffer slot may be dequeued. It is initialized
+ // to EGL_NO_SYNC_KHR when the buffer is created and may be set to a
+ // new sync object in releaseBuffer. (This is deprecated in favor of
+ // mFence, below.)
+ //EGLSyncKHR mEglFence;
+
+ // mFence is a fence which will signal when work initiated by the
+ // previous owner of the buffer is finished. When the buffer is FREE,
+ // the fence indicates when the consumer has finished reading
+ // from the buffer, or when the producer has finished writing if it
+ // called cancelBuffer after queueing some writes. When the buffer is
+ // QUEUED, it indicates when the producer has finished filling the
+ // buffer. When the buffer is DEQUEUED or ACQUIRED, the fence has been
+ // passed to the consumer or producer along with ownership of the
+ // buffer, and mFence is set to NO_FENCE.
+ sp<Fence> mFence;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates whether this buffer needs to be cleaned up by the
+ // consumer. This is set when a buffer in ACQUIRED state is freed.
+ // It causes releaseBuffer to return STALE_BUFFER_SLOT.
+ bool mNeedsCleanupOnRelease;
+ };
+
+ // mSlots is the array of buffer slots that must be mirrored on the
+ // producer side. This allows buffer ownership to be transferred between
+ // the producer and consumer without sending a GraphicBuffer over binder.
+ // The entire array is initialized to NULL at construction time, and
+ // buffers are allocated for a slot when requestBuffer is called with
+ // that slot's index.
+ BufferSlot mSlots[NUM_BUFFER_SLOTS];
+
+ // mDefaultWidth holds the default width of allocated buffers. It is used
+ // in dequeueBuffer() if a width and height of zero is specified.
+ uint32_t mDefaultWidth;
+
+ // mDefaultHeight holds the default height of allocated buffers. It is used
+ // in dequeueBuffer() if a width and height of zero is specified.
+ uint32_t mDefaultHeight;
+
+ // mMaxAcquiredBufferCount is the number of buffers that the consumer may
+ // acquire at one time. It defaults to 1 and can be changed by the
+ // consumer via the setMaxAcquiredBufferCount method, but this may only be
+ // done when no producer is connected to the GonkBufferQueue.
+ //
+ // This value is used to derive the value returned for the
+ // MIN_UNDEQUEUED_BUFFERS query by the producer.
+ int mMaxAcquiredBufferCount;
+
+ // mDefaultMaxBufferCount is the default limit on the number of buffers
+ // that will be allocated at one time. This default limit is set by the
+ // consumer. The limit (as opposed to the default limit) may be
+ // overridden by the producer.
+ int mDefaultMaxBufferCount;
+
+ // mOverrideMaxBufferCount is the limit on the number of buffers that will
+ // be allocated at one time. This value is set by the image producer by
+ // calling setBufferCount. The default is zero, which means the producer
+ // doesn't care about the number of buffers in the pool. In that case
+ // mDefaultMaxBufferCount is used as the limit.
+ int mOverrideMaxBufferCount;
+
+ // mGraphicBufferAlloc is the connection to SurfaceFlinger that is used to
+ // allocate new GraphicBuffer objects.
+ sp<IGraphicBufferAlloc> mGraphicBufferAlloc;
+
+ // mConsumerListener is used to notify the connected consumer of
+ // asynchronous events that it may wish to react to. It is initially set
+ // to NULL and is written by consumerConnect and consumerDisconnect.
+ sp<ConsumerListener> mConsumerListener;
+
+ // mSynchronousMode whether we're in synchronous mode or not
+ bool mSynchronousMode;
+
+ // mAllowSynchronousMode whether we allow synchronous mode or not. Set
+ // when the GonkBufferQueue is created (by the consumer).
+ const bool mAllowSynchronousMode;
+
+ // mConnectedApi indicates the producer API that is currently connected
+ // to this GonkBufferQueue. It defaults to NO_CONNECTED_API (= 0), and gets
+ // updated by the connect and disconnect methods.
+ int mConnectedApi;
+
+ // mDequeueCondition condition used for dequeueBuffer in synchronous mode
+ mutable Condition mDequeueCondition;
+
+ // mQueue is a FIFO of queued buffers used in synchronous mode
+ typedef Vector<int> Fifo;
+ Fifo mQueue;
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume image buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the
+ // consumerDisconnect method. A GonkBufferQueue that has been abandoned will
+ // return the NO_INIT error from all IGraphicBufferProducer methods
+ // capable of returning an error.
+ bool mAbandoned;
+
+ // mConsumerName is a string used to identify the GonkBufferQueue in log
+ // messages. It is set by the setConsumerName method.
+ String8 mConsumerName;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkBufferQueue objects. It must be locked whenever the
+ // member variables are accessed.
+ mutable Mutex mMutex;
+
+ // mFrameCounter is the free running counter, incremented on every
+ // successful queueBuffer call.
+ uint64_t mFrameCounter;
+
+ // mBufferHasBeenQueued is true once a buffer has been queued. It is
+ // reset when something causes all buffers to be freed (e.g. changing the
+ // buffer count).
+ bool mBufferHasBeenQueued;
+
+ // mDefaultBufferFormat can be set so it will override
+ // the buffer format when it isn't specified in dequeueBuffer
+ uint32_t mDefaultBufferFormat;
+
+ // mConsumerUsageBits contains flags the consumer wants for GraphicBuffers
+ uint32_t mConsumerUsageBits;
+
+ // mTransformHint is used to optimize for screen rotations
+ uint32_t mTransformHint;
+
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_BUFFERQUEUE_H
diff --git a/widget/gonk/nativewindow/GonkBufferQueueKK.cpp b/widget/gonk/nativewindow/GonkBufferQueueKK.cpp
new file mode 100644
index 0000000000..0c5cdfeb9f
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueKK.cpp
@@ -0,0 +1,1265 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#define LOG_TAG "GonkBufferQueue"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define GL_GLEXT_PROTOTYPES
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <utils/Log.h>
+#include <utils/Trace.h>
+#include <utils/CallStack.h>
+#include <cutils/compiler.h>
+
+#include "mozilla/layers/GrallocTextureClient.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "GonkBufferQueueKK.h"
+
+#define ATRACE_BUFFER_INDEX(index)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+static const char* scalingModeName(int scalingMode) {
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP";
+ default: return "Unknown";
+ }
+}
+
+GonkBufferQueue::GonkBufferQueue(bool allowSynchronousMode,
+ const sp<IGraphicBufferAlloc>& allocator) :
+ mDefaultWidth(1),
+ mDefaultHeight(1),
+ mMaxAcquiredBufferCount(1),
+ mDefaultMaxBufferCount(2),
+ mOverrideMaxBufferCount(0),
+ mSynchronousMode(true),
+ mConsumerControlledByApp(false),
+ mDequeueBufferCannotBlock(false),
+ mUseAsyncBuffer(true),
+ mConnectedApi(NO_CONNECTED_API),
+ mAbandoned(false),
+ mFrameCounter(0),
+ mBufferHasBeenQueued(false),
+ mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
+ mConsumerUsageBits(0),
+ mTransformHint(0)
+{
+ // Choose a name using the PID and a process-unique ID.
+ mConsumerName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ ALOGV("GonkBufferQueue");
+}
+
+GonkBufferQueue::~GonkBufferQueue() {
+ ALOGV("~GonkBufferQueue");
+}
+
+status_t GonkBufferQueue::setDefaultMaxBufferCountLocked(int count) {
+ if (count < 2 || count > NUM_BUFFER_SLOTS)
+ return BAD_VALUE;
+
+ mDefaultMaxBufferCount = count;
+ mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+void GonkBufferQueue::setConsumerName(const String8& name) {
+ Mutex::Autolock lock(mMutex);
+ mConsumerName = name;
+}
+
+status_t GonkBufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock lock(mMutex);
+ mDefaultBufferFormat = defaultFormat;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setConsumerUsageBits(uint32_t usage) {
+ Mutex::Autolock lock(mMutex);
+ mConsumerUsageBits = usage;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setTransformHint(uint32_t hint) {
+ ALOGV("setTransformHint: %02x", hint);
+ Mutex::Autolock lock(mMutex);
+ mTransformHint = hint;
+ return NO_ERROR;
+}
+
+already_AddRefed<TextureClient>
+GonkBufferQueue::getTextureClientFromBuffer(ANativeWindowBuffer* buffer)
+{
+ Mutex::Autolock _l(mMutex);
+ if (buffer == NULL) {
+ ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return nullptr;
+ }
+
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) {
+ RefPtr<TextureClient> client(mSlots[i].mTextureClient);
+ return client.forget();
+ }
+ }
+ ALOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+ return nullptr;
+}
+
+int GonkBufferQueue::getSlotFromTextureClientLocked(
+ TextureClient* client) const
+{
+ if (client == NULL) {
+ ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return BAD_VALUE;
+ }
+
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mTextureClient == client) {
+ return i;
+ }
+ }
+ ALOGE("getSlotFromBufferLocked: unknown TextureClient: %p", client);
+ return BAD_VALUE;
+}
+
+status_t GonkBufferQueue::setBufferCount(int bufferCount) {
+ ALOGV("setBufferCount: count=%d", bufferCount);
+
+ sp<IConsumerListener> listener;
+ {
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("setBufferCount: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (bufferCount > NUM_BUFFER_SLOTS) {
+ ALOGE("setBufferCount: bufferCount too large (max %d)",
+ NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ // Error out if the user has dequeued buffers
+ for (int i=0 ; i<NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) {
+ ALOGE("setBufferCount: client owns some buffers");
+ return -EINVAL;
+ }
+ }
+
+ if (bufferCount == 0) {
+ mOverrideMaxBufferCount = 0;
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+ }
+
+ // fine to assume async to false before we're setting the buffer count
+ const int minBufferSlots = getMinMaxBufferCountLocked(false);
+ if (bufferCount < minBufferSlots) {
+ ALOGE("setBufferCount: requested buffer count (%d) is less than "
+ "minimum (%d)", bufferCount, minBufferSlots);
+ return BAD_VALUE;
+ }
+
+ // here we're guaranteed that the client doesn't have dequeued buffers
+ // and will release all of its buffer references. We don't clear the
+ // queue, however, so currently queued buffers still get displayed.
+ // XXX: Should this use drainQueueAndFreeBuffersLocked instead?
+ freeAllBuffersLocked();
+ mOverrideMaxBufferCount = bufferCount;
+ mDequeueCondition.broadcast();
+ listener = mConsumerListener;
+ } // scope for lock
+
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return NO_ERROR;
+}
+
+int GonkBufferQueue::query(int what, int* outValue)
+{
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("query: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ int value;
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ value = mDefaultWidth;
+ break;
+ case NATIVE_WINDOW_HEIGHT:
+ value = mDefaultHeight;
+ break;
+ case NATIVE_WINDOW_FORMAT:
+ value = mDefaultBufferFormat;
+ break;
+ case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+ value = getMinUndequeuedBufferCount(false);
+ break;
+ case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+ value = (mQueue.size() >= 2);
+ break;
+ case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
+ value = mConsumerUsageBits;
+ break;
+ default:
+ return BAD_VALUE;
+ }
+ outValue[0] = value;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
+ ATRACE_CALL();
+ ALOGV("requestBuffer: slot=%d", slot);
+ Mutex::Autolock lock(mMutex);
+ if (mAbandoned) {
+ ALOGE("requestBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (slot < 0 || slot >= NUM_BUFFER_SLOTS) {
+ ALOGE("requestBuffer: slot index out of range [0, %d]: %d",
+ NUM_BUFFER_SLOTS, slot);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+ ALOGE("requestBuffer: slot %d is not owned by the client (state=%d)",
+ slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+ mSlots[slot].mRequestBufferCalled = true;
+ *buf = mSlots[slot].mGraphicBuffer;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::dequeueBuffer(int *outBuf, sp<Fence>* outFence, bool async,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
+ ATRACE_CALL();
+ ALOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage);
+
+ if ((w && !h) || (!w && h)) {
+ ALOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h);
+ return BAD_VALUE;
+ }
+
+ status_t returnFlags(OK);
+ int buf = INVALID_BUFFER_SLOT;
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (format == 0) {
+ format = mDefaultBufferFormat;
+ }
+ // turn on usage bits the consumer requested
+ usage |= mConsumerUsageBits;
+
+ int found = -1;
+ bool tryAgain = true;
+ while (tryAgain) {
+ if (mAbandoned) {
+ ALOGE("dequeueBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = getMaxBufferCountLocked(async);
+ if (async && mOverrideMaxBufferCount) {
+ // FIXME: some drivers are manually setting the buffer-count (which they
+ // shouldn't), so we do this extra test here to handle that case.
+ // This is TEMPORARY, until we get this fixed.
+ if (mOverrideMaxBufferCount < maxBufferCount) {
+ ALOGE("dequeueBuffer: async mode is invalid with buffercount override");
+ return BAD_VALUE;
+ }
+ }
+
+ // Free up any buffers that are in slots beyond the max buffer
+ // count.
+ //for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
+ // assert(mSlots[i].mBufferState == BufferSlot::FREE);
+ // if (mSlots[i].mGraphicBuffer != NULL) {
+ // freeBufferLocked(i);
+ // returnFlags |= IGraphicBufferProducer::RELEASE_ALL_BUFFERS;
+ // }
+ //}
+
+ // look for a free buffer to give to the client
+ found = INVALID_BUFFER_SLOT;
+ int dequeuedCount = 0;
+ int acquiredCount = 0;
+ for (int i = 0; i < maxBufferCount; i++) {
+ const int state = mSlots[i].mBufferState;
+ switch (state) {
+ case BufferSlot::DEQUEUED:
+ dequeuedCount++;
+ break;
+ case BufferSlot::ACQUIRED:
+ acquiredCount++;
+ break;
+ case BufferSlot::FREE:
+ /* We return the oldest of the free buffers to avoid
+ * stalling the producer if possible. This is because
+ * the consumer may still have pending reads of the
+ * buffers in flight.
+ */
+ if ((found < 0) ||
+ mSlots[i].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = i;
+ }
+ break;
+ }
+ }
+
+ // clients are not allowed to dequeue more than one buffer
+ // if they didn't set a buffer count.
+ if (!mOverrideMaxBufferCount && dequeuedCount) {
+ ALOGE("dequeueBuffer: can't dequeue multiple buffers without "
+ "setting the buffer count");
+ return -EINVAL;
+ }
+
+ // See whether a buffer has been queued since the last
+ // setBufferCount so we know whether to perform the min undequeued
+ // buffers check below.
+ if (mBufferHasBeenQueued) {
+ // make sure the client is not trying to dequeue more buffers
+ // than allowed.
+ const int newUndequeuedCount = maxBufferCount - (dequeuedCount+1);
+ const int minUndequeuedCount = getMinUndequeuedBufferCount(async);
+ if (newUndequeuedCount < minUndequeuedCount) {
+ ALOGE("dequeueBuffer: min undequeued buffer count (%d) "
+ "exceeded (dequeued=%d undequeudCount=%d)",
+ minUndequeuedCount, dequeuedCount,
+ newUndequeuedCount);
+ return -EBUSY;
+ }
+ }
+
+ // If no buffer is found, wait for a buffer to be released or for
+ // the max buffer count to change.
+ tryAgain = found == INVALID_BUFFER_SLOT;
+ if (tryAgain) {
+ // return an error if we're in "cannot block" mode (producer and consumer
+ // are controlled by the application) -- however, the consumer is allowed
+ // to acquire briefly an extra buffer (which could cause us to have to wait here)
+ // and that's okay because we know the wait will be brief (it happens
+ // if we dequeue a buffer while the consumer has acquired one but not released
+ // the old one yet -- for e.g.: see GLConsumer::updateTexImage()).
+ if (mDequeueBufferCannotBlock && (acquiredCount <= mMaxAcquiredBufferCount)) {
+ ALOGE("dequeueBuffer: would block! returning an error instead.");
+ return WOULD_BLOCK;
+ }
+ mDequeueCondition.wait(mMutex);
+ }
+ }
+
+
+ if (found == INVALID_BUFFER_SLOT) {
+ // This should not happen.
+ ALOGE("dequeueBuffer: no available buffer slots");
+ return -EBUSY;
+ }
+
+ buf = found;
+ *outBuf = found;
+
+ const bool useDefaultSize = !w && !h;
+ if (useDefaultSize) {
+ // use the default size
+ w = mDefaultWidth;
+ h = mDefaultHeight;
+ }
+
+ mSlots[buf].mBufferState = BufferSlot::DEQUEUED;
+
+ const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer);
+ if ((buffer == NULL) ||
+ (uint32_t(buffer->width) != w) ||
+ (uint32_t(buffer->height) != h) ||
+ (uint32_t(buffer->format) != format) ||
+ ((uint32_t(buffer->usage) & usage) != usage))
+ {
+ mSlots[buf].mAcquireCalled = false;
+ mSlots[buf].mGraphicBuffer = NULL;
+ mSlots[buf].mRequestBufferCalled = false;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ if (mSlots[buf].mTextureClient) {
+ mSlots[buf].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[buf].mTextureClient);
+ mSlots[buf].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ returnFlags |= IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION;
+ }
+
+
+ if (CC_UNLIKELY(mSlots[buf].mFence == NULL)) {
+ ALOGE("dequeueBuffer: about to return a NULL fence from mSlot. "
+ "buf=%d, w=%d, h=%d, format=%d",
+ buf, buffer->width, buffer->height, buffer->format);
+ }
+ *outFence = mSlots[buf].mFence;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ } // end lock scope
+
+ if (returnFlags & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
+
+ RefPtr<LayersIPCChannel> allocator = ImageBridgeChild::GetSingleton();
+ usage |= GraphicBuffer::USAGE_HW_TEXTURE;
+ GrallocTextureData* texData = GrallocTextureData::Create(IntSize(w, h), format,
+ gfx::BackendType::NONE, usage,
+ allocator);
+ if (!texData) {
+ return -ENOMEM;
+ }
+
+ RefPtr<TextureClient> textureClient = new TextureClient(texData, TextureFlags::RECYCLE | TextureFlags::DEALLOCATE_CLIENT, allocator);
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("dequeueBuffer: SurfaceTexture has been abandoned!");
+ return NO_INIT;
+ }
+
+ mSlots[buf].mGraphicBuffer = texData->GetGraphicBuffer();
+ mSlots[buf].mTextureClient = textureClient;
+ ALOGD("dequeueBuffer: returning slot=%d buf=%p ", buf,
+ mSlots[buf].mGraphicBuffer->handle);
+
+ }
+
+ }
+
+ ALOGV("dequeueBuffer: returning slot=%d/%llu buf=%p flags=%#x", *outBuf,
+ mSlots[*outBuf].mFrameNumber,
+ mSlots[*outBuf].mGraphicBuffer->handle, returnFlags);
+
+ return returnFlags;
+}
+
+status_t GonkBufferQueue::setSynchronousMode(bool enabled) {
+ ALOGV("setSynchronousMode: enabled=%d", enabled);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("setSynchronousMode: BufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mSynchronousMode != enabled) {
+ mSynchronousMode = enabled;
+ mDequeueCondition.broadcast();
+ }
+ return OK;
+}
+
+status_t GonkBufferQueue::queueBuffer(int buf,
+ const QueueBufferInput& input, QueueBufferOutput* output) {
+ ATRACE_CALL();
+
+ Rect crop;
+ uint32_t transform;
+ int scalingMode;
+ int64_t timestamp;
+ bool isAutoTimestamp;
+ bool async;
+ sp<Fence> fence;
+
+ input.deflate(&timestamp, &isAutoTimestamp, &crop, &scalingMode, &transform,
+ &async, &fence);
+
+ if (fence == NULL) {
+ ALOGE("queueBuffer: fence is NULL");
+ return BAD_VALUE;
+ }
+
+ ALOGV("queueBuffer: slot=%d time=%#llx crop=[%d,%d,%d,%d] tr=%#x "
+ "scale=%s",
+ buf, timestamp, crop.left, crop.top, crop.right, crop.bottom,
+ transform, scalingModeName(scalingMode));
+
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
+ case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
+ break;
+ default:
+ ALOGE("unknown scaling mode: %d", scalingMode);
+ return -EINVAL;
+ }
+
+ sp<IConsumerListener> listener;
+
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("queueBuffer: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = getMaxBufferCountLocked(async);
+ if (async && mOverrideMaxBufferCount) {
+ // FIXME: some drivers are manually setting the buffer-count (which they
+ // shouldn't), so we do this extra test here to handle that case.
+ // This is TEMPORARY, until we get this fixed.
+ if (mOverrideMaxBufferCount < maxBufferCount) {
+ ALOGE("queueBuffer: async mode is invalid with buffercount override");
+ return BAD_VALUE;
+ }
+ }
+ if (buf < 0 || buf >= maxBufferCount) {
+ ALOGE("queueBuffer: slot index out of range [0, %d]: %d",
+ maxBufferCount, buf);
+ return -EINVAL;
+ } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+ ALOGE("queueBuffer: slot %d is not owned by the client "
+ "(state=%d)", buf, mSlots[buf].mBufferState);
+ return -EINVAL;
+ } else if (!mSlots[buf].mRequestBufferCalled) {
+ ALOGE("queueBuffer: slot %d was enqueued without requesting a "
+ "buffer", buf);
+ return -EINVAL;
+ }
+
+ ALOGV("queueBuffer: slot=%d/%llu time=%#llx crop=[%d,%d,%d,%d] "
+ "tr=%#x scale=%s",
+ buf, mFrameCounter + 1, timestamp,
+ crop.left, crop.top, crop.right, crop.bottom,
+ transform, scalingModeName(scalingMode));
+
+ const sp<GraphicBuffer>& graphicBuffer(mSlots[buf].mGraphicBuffer);
+ Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
+ Rect croppedCrop;
+ crop.intersect(bufferRect, &croppedCrop);
+ if (croppedCrop != crop) {
+ ALOGE("queueBuffer: crop rect is not contained within the "
+ "buffer in slot %d", buf);
+ return -EINVAL;
+ }
+
+ mSlots[buf].mFence = fence;
+ mSlots[buf].mBufferState = BufferSlot::QUEUED;
+ mFrameCounter++;
+ mSlots[buf].mFrameNumber = mFrameCounter;
+
+ BufferItem item;
+ item.mAcquireCalled = mSlots[buf].mAcquireCalled;
+ item.mGraphicBuffer = mSlots[buf].mGraphicBuffer;
+ item.mCrop = crop;
+ item.mTransform = transform & ~NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+ item.mTransformToDisplayInverse = bool(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
+ item.mScalingMode = scalingMode;
+ item.mTimestamp = timestamp;
+ item.mIsAutoTimestamp = isAutoTimestamp;
+ item.mFrameNumber = mFrameCounter;
+ item.mBuf = buf;
+ item.mFence = fence;
+ item.mIsDroppable = mDequeueBufferCannotBlock || async;
+
+ if (mQueue.empty()) {
+ // when the queue is empty, we can ignore "mDequeueBufferCannotBlock", and
+ // simply queue this buffer.
+ mQueue.push_back(item);
+ } else {
+ // when the queue is not empty, we need to look at the front buffer
+ // state and see if we need to replace it.
+ Fifo::iterator front(mQueue.begin());
+ if (front->mIsDroppable || !mSynchronousMode) {
+ // buffer slot currently queued is marked free if still tracked
+ if (stillTracking(front)) {
+ mSlots[front->mBuf].mBufferState = BufferSlot::FREE;
+ // reset the frame number of the freed buffer so that it is the first in
+ // line to be dequeued again.
+ mSlots[front->mBuf].mFrameNumber = 0;
+ }
+ // and we record the new buffer in the queued list
+ *front = item;
+ } else {
+ mQueue.push_back(item);
+ }
+ }
+ // always signals that an additional frame should be consumed
+ // to handle max acquired buffer count reached case.
+ listener = mConsumerListener;
+
+ mBufferHasBeenQueued = true;
+ mDequeueCondition.broadcast();
+
+ output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint,
+ mQueue.size());
+
+ } // scope for the lock
+
+ // call back without lock held
+ if (listener != 0) {
+ listener->onFrameAvailable();
+ }
+ return NO_ERROR;
+}
+
+void GonkBufferQueue::cancelBuffer(int buf, const sp<Fence>& fence) {
+ ATRACE_CALL();
+ ALOGV("cancelBuffer: slot=%d", buf);
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGW("cancelBuffer: GonkBufferQueue has been abandoned!");
+ return;
+ }
+
+ if (buf < 0 || buf >= NUM_BUFFER_SLOTS) {
+ ALOGE("cancelBuffer: slot index out of range [0, %d]: %d",
+ NUM_BUFFER_SLOTS, buf);
+ return;
+ } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
+ ALOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
+ buf, mSlots[buf].mBufferState);
+ return;
+ } else if (fence == NULL) {
+ ALOGE("cancelBuffer: fence is NULL");
+ return;
+ }
+ mSlots[buf].mBufferState = BufferSlot::FREE;
+ mSlots[buf].mFrameNumber = 0;
+ mSlots[buf].mFence = fence;
+ mDequeueCondition.broadcast();
+}
+
+
+status_t GonkBufferQueue::connect(const sp<IBinder>& token,
+ int api, bool producerControlledByApp, QueueBufferOutput* output) {
+ ATRACE_CALL();
+ ALOGV("connect: api=%d producerControlledByApp=%s", api,
+ producerControlledByApp ? "true" : "false");
+ Mutex::Autolock lock(mMutex);
+
+retry:
+ if (mAbandoned) {
+ ALOGE("connect: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mConsumerListener == NULL) {
+ ALOGE("connect: GonkBufferQueue has no consumer!");
+ return NO_INIT;
+ }
+
+ if (mConnectedApi != NO_CONNECTED_API) {
+ ALOGE("connect: already connected (cur=%d, req=%d)",
+ mConnectedApi, api);
+ return -EINVAL;
+ }
+
+ // If we disconnect and reconnect quickly, we can be in a state where our slots are
+ // empty but we have many buffers in the queue. This can cause us to run out of
+ // memory if we outrun the consumer. Wait here if it looks like we have too many
+ // buffers queued up.
+ int maxBufferCount = getMaxBufferCountLocked(false); // worst-case, i.e. largest value
+ if (mQueue.size() > (size_t) maxBufferCount) {
+ // TODO: make this bound tighter?
+ ALOGV("queue size is %d, waiting", mQueue.size());
+ mDequeueCondition.wait(mMutex);
+ goto retry;
+ }
+
+ int err = NO_ERROR;
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ mConnectedApi = api;
+ output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint, mQueue.size());
+
+ // set-up a death notification so that we can disconnect
+ // automatically when/if the remote producer dies.
+ if (token != NULL && token->remoteBinder() != NULL) {
+ status_t err = token->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));
+ if (err == NO_ERROR) {
+ mConnectedProducerToken = token;
+ } else {
+ ALOGE("linkToDeath failed: %s (%d)", strerror(-err), err);
+ }
+ }
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ mBufferHasBeenQueued = false;
+ mDequeueBufferCannotBlock = mConsumerControlledByApp && producerControlledByApp;
+
+ return err;
+}
+
+void GonkBufferQueue::binderDied(const wp<IBinder>& who) {
+ // If we're here, it means that a producer we were connected to died.
+ // We're GUARANTEED that we still are connected to it because it has no other way
+ // to get disconnected -- or -- we wouldn't be here because we're removing this
+ // callback upon disconnect. Therefore, it's okay to read mConnectedApi without
+ // synchronization here.
+ int api = mConnectedApi;
+ this->disconnect(api);
+}
+
+status_t GonkBufferQueue::disconnect(int api) {
+ ATRACE_CALL();
+ ALOGV("disconnect: api=%d", api);
+
+ int err = NO_ERROR;
+ sp<IConsumerListener> listener;
+
+ { // Scope for the lock
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ // it is not really an error to disconnect after the surface
+ // has been abandoned, it should just be a no-op.
+ return NO_ERROR;
+ }
+
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ if (mConnectedApi == api) {
+ freeAllBuffersLocked();
+ mConnectedApi = NO_CONNECTED_API;
+ mDequeueCondition.broadcast();
+ listener = mConsumerListener;
+ } else {
+ ALOGE("disconnect: connected to another api (cur=%d, req=%d)",
+ mConnectedApi, api);
+ err = -EINVAL;
+ }
+ break;
+ default:
+ ALOGE("disconnect: unknown API %d", api);
+ err = -EINVAL;
+ break;
+ }
+ }
+
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return err;
+}
+
+void GonkBufferQueue::dumpToString(String8& result, const char* prefix) const {
+ Mutex::Autolock _l(mMutex);
+
+ String8 fifo;
+ int fifoSize = 0;
+ Fifo::const_iterator i(mQueue.begin());
+ while (i != mQueue.end()) {
+ fifo.appendFormat("%02d:%p crop=[%d,%d,%d,%d], "
+ "xform=0x%02x, time=%#llx, scale=%s\n",
+ i->mBuf, i->mGraphicBuffer.get(),
+ i->mCrop.left, i->mCrop.top, i->mCrop.right,
+ i->mCrop.bottom, i->mTransform, i->mTimestamp,
+ scalingModeName(i->mScalingMode)
+ );
+ i++;
+ fifoSize++;
+ }
+
+
+ result.appendFormat(
+ "%s-BufferQueue mMaxAcquiredBufferCount=%d, mDequeueBufferCannotBlock=%d, default-size=[%dx%d], "
+ "default-format=%d, transform-hint=%02x, FIFO(%d)={%s}\n",
+ prefix, mMaxAcquiredBufferCount, mDequeueBufferCannotBlock, mDefaultWidth,
+ mDefaultHeight, mDefaultBufferFormat, mTransformHint,
+ fifoSize, fifo.string());
+
+ struct {
+ const char * operator()(int state) const {
+ switch (state) {
+ case BufferSlot::DEQUEUED: return "DEQUEUED";
+ case BufferSlot::QUEUED: return "QUEUED";
+ case BufferSlot::FREE: return "FREE";
+ case BufferSlot::ACQUIRED: return "ACQUIRED";
+ default: return "Unknown";
+ }
+ }
+ } stateName;
+
+ // just trim the free buffers to not spam the dump
+ int maxBufferCount = 0;
+ for (int i=NUM_BUFFER_SLOTS-1 ; i>=0 ; i--) {
+ const BufferSlot& slot(mSlots[i]);
+ if ((slot.mBufferState != BufferSlot::FREE) || (slot.mGraphicBuffer != NULL)) {
+ maxBufferCount = i+1;
+ break;
+ }
+ }
+
+ for (int i=0 ; i<maxBufferCount ; i++) {
+ const BufferSlot& slot(mSlots[i]);
+ const sp<GraphicBuffer>& buf(slot.mGraphicBuffer);
+ result.appendFormat(
+ "%s%s[%02d:%p] state=%-8s",
+ prefix, (slot.mBufferState == BufferSlot::ACQUIRED)?">":" ", i, buf.get(),
+ stateName(slot.mBufferState)
+ );
+
+ if (buf != NULL) {
+ result.appendFormat(
+ ", %p [%4ux%4u:%4u,%3X]",
+ buf->handle, buf->width, buf->height, buf->stride,
+ buf->format);
+ }
+ result.append("\n");
+ }
+}
+
+void GonkBufferQueue::freeAllBuffersLocked()
+{
+ ALOGW_IF(!mQueue.isEmpty(),
+ "freeAllBuffersLocked called but mQueue is not empty");
+ mQueue.clear();
+ mBufferHasBeenQueued = false;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ mSlots[i].mGraphicBuffer = 0;
+ if (mSlots[i].mTextureClient) {
+ mSlots[i].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[i].mTextureClient);
+ mSlots[i].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[i].mNeedsCleanupOnRelease = true;
+ }
+ mSlots[i].mBufferState = BufferSlot::FREE;
+ mSlots[i].mFrameNumber = 0;
+ mSlots[i].mAcquireCalled = false;
+ // destroy fence as GonkBufferQueue now takes ownership
+ mSlots[i].mFence = Fence::NO_FENCE;
+ }
+}
+
+status_t GonkBufferQueue::acquireBuffer(BufferItem *buffer, nsecs_t expectedPresent) {
+ ATRACE_CALL();
+ Mutex::Autolock _l(mMutex);
+
+ // Check that the consumer doesn't currently have the maximum number of
+ // buffers acquired. We allow the max buffer count to be exceeded by one
+ // buffer, so that the consumer can successfully set up the newly acquired
+ // buffer before releasing the old one.
+ int numAcquiredBuffers = 0;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
+ numAcquiredBuffers++;
+ }
+ }
+ if (numAcquiredBuffers >= mMaxAcquiredBufferCount+1) {
+ ALOGE("acquireBuffer: max acquired buffer count reached: %d (max=%d)",
+ numAcquiredBuffers, mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+
+ // check if queue is empty
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ if (mQueue.empty()) {
+ return NO_BUFFER_AVAILABLE;
+ }
+
+ Fifo::iterator front(mQueue.begin());
+
+ // If expectedPresent is specified, we may not want to return a buffer yet.
+ // If it's specified and there's more than one buffer queued, we may
+ // want to drop a buffer.
+ if (expectedPresent != 0) {
+ const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second
+
+ // The "expectedPresent" argument indicates when the buffer is expected
+ // to be presented on-screen. If the buffer's desired-present time
+ // is earlier (less) than expectedPresent, meaning it'll be displayed
+ // on time or possibly late if we show it ASAP, we acquire and return
+ // it. If we don't want to display it until after the expectedPresent
+ // time, we return PRESENT_LATER without acquiring it.
+ //
+ // To be safe, we don't defer acquisition if expectedPresent is
+ // more than one second in the future beyond the desired present time
+ // (i.e. we'd be holding the buffer for a long time).
+ //
+ // NOTE: code assumes monotonic time values from the system clock are
+ // positive.
+
+ // Start by checking to see if we can drop frames. We skip this check
+ // if the timestamps are being auto-generated by Surface -- if the
+ // app isn't generating timestamps explicitly, they probably don't
+ // want frames to be discarded based on them.
+ while (mQueue.size() > 1 && !mQueue[0].mIsAutoTimestamp) {
+ // If entry[1] is timely, drop entry[0] (and repeat). We apply
+ // an additional criteria here: we only drop the earlier buffer if
+ // our desiredPresent falls within +/- 1 second of the expected
+ // present. Otherwise, bogus desiredPresent times (e.g. 0 or
+ // a small relative timestamp), which normally mean "ignore the
+ // timestamp and acquire immediately", would cause us to drop
+ // frames.
+ //
+ // We may want to add an additional criteria: don't drop the
+ // earlier buffer if entry[1]'s fence hasn't signaled yet.
+ //
+ // (Vector front is [0], back is [size()-1])
+ const BufferItem& bi(mQueue[1]);
+ nsecs_t desiredPresent = bi.mTimestamp;
+ if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
+ desiredPresent > expectedPresent) {
+ // This buffer is set to display in the near future, or
+ // desiredPresent is garbage. Either way we don't want to
+ // drop the previous buffer just to get this on screen sooner.
+ ALOGV("pts nodrop: des=%lld expect=%lld (%lld) now=%lld",
+ desiredPresent, expectedPresent, desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ break;
+ }
+ ALOGV("pts drop: queue1des=%lld expect=%lld size=%d",
+ desiredPresent, expectedPresent, mQueue.size());
+ if (stillTracking(front)) {
+ // front buffer is still in mSlots, so mark the slot as free
+ mSlots[front->mBuf].mBufferState = BufferSlot::FREE;
+ }
+ mQueue.erase(front);
+ front = mQueue.begin();
+ }
+
+ // See if the front buffer is due.
+ nsecs_t desiredPresent = front->mTimestamp;
+ if (desiredPresent > expectedPresent &&
+ desiredPresent < expectedPresent + MAX_REASONABLE_NSEC) {
+ ALOGV("pts defer: des=%lld expect=%lld (%lld) now=%lld",
+ desiredPresent, expectedPresent, desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ return PRESENT_LATER;
+ }
+
+ ALOGV("pts accept: des=%lld expect=%lld (%lld) now=%lld",
+ desiredPresent, expectedPresent, desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ }
+
+ int buf = front->mBuf;
+ buffer->mGraphicBuffer = mSlots[buf].mGraphicBuffer;
+ buffer->mFrameNumber = mSlots[buf].mFrameNumber;
+ buffer->mBuf = buf;
+ buffer->mFence = mSlots[buf].mFence;
+ ATRACE_BUFFER_INDEX(buf);
+
+ ALOGV("acquireBuffer: acquiring { slot=%d/%llu, buffer=%p }",
+ front->mBuf, front->mFrameNumber,
+ front->mGraphicBuffer->handle);
+ // if front buffer still being tracked update slot state
+ if (stillTracking(front)) {
+ mSlots[buf].mAcquireCalled = true;
+ mSlots[buf].mNeedsCleanupOnRelease = false;
+ mSlots[buf].mBufferState = BufferSlot::ACQUIRED;
+ mSlots[buf].mFence = Fence::NO_FENCE;
+ }
+
+ // If the buffer has previously been acquired by the consumer, set
+ // mGraphicBuffer to NULL to avoid unnecessarily remapping this
+ // buffer on the consumer side.
+ //if (buffer->mAcquireCalled) {
+ // buffer->mGraphicBuffer = NULL;
+ //}
+
+ mQueue.erase(front);
+ mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& fence) {
+ ATRACE_CALL();
+
+ if (buf == INVALID_BUFFER_SLOT || fence == NULL) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock _l(mMutex);
+
+ // If the frame number has changed because buffer has been reallocated,
+ // we can ignore this releaseBuffer for the old buffer.
+ //if (frameNumber != mSlots[buf].mFrameNumber) {
+ // return STALE_BUFFER_SLOT;
+ //}
+
+
+ // Internal state consistency checks:
+ // Make sure this buffers hasn't been queued while we were owning it (acquired)
+ Fifo::iterator front(mQueue.begin());
+ Fifo::const_iterator const end(mQueue.end());
+ while (front != end) {
+ if (front->mBuf == buf) {
+ LOG_ALWAYS_FATAL("[%s] received new buffer(#%lld) on slot #%d that has not yet been "
+ "acquired", mConsumerName.string(), frameNumber, buf);
+ break; // never reached
+ }
+ front++;
+ }
+
+ // The buffer can now only be released if its in the acquired state
+ if (mSlots[buf].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[buf].mFence = fence;
+ mSlots[buf].mBufferState = BufferSlot::FREE;
+ } else if (mSlots[buf].mNeedsCleanupOnRelease) {
+ ALOGV("releasing a stale buf %d its state was %d", buf, mSlots[buf].mBufferState);
+ mSlots[buf].mNeedsCleanupOnRelease = false;
+ return STALE_BUFFER_SLOT;
+ } else {
+ ALOGE("attempted to release buf %d but its state was %d", buf, mSlots[buf].mBufferState);
+ return -EINVAL;
+ }
+
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::consumerConnect(const sp<IConsumerListener>& consumerListener,
+ bool controlledByApp) {
+ ALOGV("consumerConnect controlledByApp=%s",
+ controlledByApp ? "true" : "false");
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("consumerConnect: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+ if (consumerListener == NULL) {
+ ALOGE("consumerConnect: consumerListener may not be NULL");
+ return BAD_VALUE;
+ }
+
+ mConsumerListener = consumerListener;
+ mConsumerControlledByApp = controlledByApp;
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::consumerDisconnect() {
+ ALOGV("consumerDisconnect");
+ Mutex::Autolock lock(mMutex);
+
+ if (mConsumerListener == NULL) {
+ ALOGE("consumerDisconnect: No consumer is connected!");
+ return -EINVAL;
+ }
+
+ mAbandoned = true;
+ mConsumerListener = NULL;
+ mQueue.clear();
+ freeAllBuffersLocked();
+ mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::getReleasedBuffers(uint32_t* slotMask) {
+ ALOGV("getReleasedBuffers");
+ Mutex::Autolock lock(mMutex);
+
+ if (mAbandoned) {
+ ALOGE("getReleasedBuffers: GonkBufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ uint32_t mask = 0;
+ for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
+ if (!mSlots[i].mAcquireCalled) {
+ mask |= 1 << i;
+ }
+ }
+
+ // Remove buffers in flight (on the queue) from the mask where acquire has
+ // been called, as the consumer will not receive the buffer address, so
+ // it should not free these slots.
+ Fifo::iterator front(mQueue.begin());
+ while (front != mQueue.end()) {
+ if (front->mAcquireCalled)
+ mask &= ~(1 << front->mBuf);
+ front++;
+ }
+
+ *slotMask = mask;
+
+ ALOGV("getReleasedBuffers: returning mask %#x", mask);
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setDefaultBufferSize(uint32_t w, uint32_t h) {
+ ALOGV("setDefaultBufferSize: w=%d, h=%d", w, h);
+ if (!w || !h) {
+ ALOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)",
+ w, h);
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mMutex);
+ mDefaultWidth = w;
+ mDefaultHeight = h;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setDefaultMaxBufferCount(int bufferCount) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+ return setDefaultMaxBufferCountLocked(bufferCount);
+}
+
+status_t GonkBufferQueue::disableAsyncBuffer() {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+ if (mConsumerListener != NULL) {
+ ALOGE("disableAsyncBuffer: consumer already connected!");
+ return INVALID_OPERATION;
+ }
+ mUseAsyncBuffer = false;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueue::setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+ if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > MAX_MAX_ACQUIRED_BUFFERS) {
+ ALOGE("setMaxAcquiredBufferCount: invalid count specified: %d",
+ maxAcquiredBuffers);
+ return BAD_VALUE;
+ }
+ if (mConnectedApi != NO_CONNECTED_API) {
+ return INVALID_OPERATION;
+ }
+ mMaxAcquiredBufferCount = maxAcquiredBuffers;
+ return NO_ERROR;
+}
+
+int GonkBufferQueue::getMinUndequeuedBufferCount(bool async) const {
+ // if dequeueBuffer is allowed to error out, we don't have to
+ // add an extra buffer.
+ if (!mUseAsyncBuffer)
+ return mMaxAcquiredBufferCount;
+
+ // we're in async mode, or we want to prevent the app to
+ // deadlock itself, we throw-in an extra buffer to guarantee it.
+ if (mDequeueBufferCannotBlock || async || !mSynchronousMode)
+ return mMaxAcquiredBufferCount + 1;
+
+ return mMaxAcquiredBufferCount;
+}
+
+int GonkBufferQueue::getMinMaxBufferCountLocked(bool async) const {
+ return getMinUndequeuedBufferCount(async) + 1;
+}
+
+int GonkBufferQueue::getMaxBufferCountLocked(bool async) const {
+ int minMaxBufferCount = getMinMaxBufferCountLocked(async);
+
+ int maxBufferCount = mDefaultMaxBufferCount;
+ if (maxBufferCount < minMaxBufferCount) {
+ maxBufferCount = minMaxBufferCount;
+ }
+ if (mOverrideMaxBufferCount != 0) {
+ assert(mOverrideMaxBufferCount >= minMaxBufferCount);
+ maxBufferCount = mOverrideMaxBufferCount;
+ }
+
+ // Any buffers that are dequeued by the producer or sitting in the queue
+ // waiting to be consumed need to have their slots preserved. Such
+ // buffers will temporarily keep the max buffer count up until the slots
+ // no longer need to be preserved.
+ for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
+ BufferSlot::BufferState state = mSlots[i].mBufferState;
+ if (state == BufferSlot::QUEUED || state == BufferSlot::DEQUEUED) {
+ maxBufferCount = i + 1;
+ }
+ }
+
+ return maxBufferCount;
+}
+
+bool GonkBufferQueue::stillTracking(const BufferItem *item) const {
+ const BufferSlot &slot = mSlots[item->mBuf];
+
+ ALOGV("stillTracking?: item: { slot=%d/%llu, buffer=%p }, "
+ "slot: { slot=%d/%llu, buffer=%p }",
+ item->mBuf, item->mFrameNumber,
+ (item->mGraphicBuffer.get() ? item->mGraphicBuffer->handle : 0),
+ item->mBuf, slot.mFrameNumber,
+ (slot.mGraphicBuffer.get() ? slot.mGraphicBuffer->handle : 0));
+
+ // Compare item with its original buffer slot. We can check the slot
+ // as the buffer would not be moved to a different slot by the producer.
+ return (slot.mGraphicBuffer != NULL &&
+ item->mGraphicBuffer->handle == slot.mGraphicBuffer->handle);
+}
+
+GonkBufferQueue::ProxyConsumerListener::ProxyConsumerListener(
+ const wp<ConsumerListener>& consumerListener):
+ mConsumerListener(consumerListener) {}
+
+GonkBufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {}
+
+void GonkBufferQueue::ProxyConsumerListener::onFrameAvailable() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkBufferQueue::ProxyConsumerListener::onBuffersReleased() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueKK.h b/widget/gonk/nativewindow/GonkBufferQueueKK.h
new file mode 100644
index 0000000000..01905427dd
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueKK.h
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUE_KK_H
+#define NATIVEWINDOW_GONKBUFFERQUEUE_KK_H
+
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/IGraphicBufferProducer.h>
+#include "IGonkGraphicBufferConsumer.h"
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "mozilla/layers/LayersSurfaces.h"
+#include "mozilla/layers/TextureClient.h"
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class GonkBufferQueue : public BnGraphicBufferProducer,
+ public BnGonkGraphicBufferConsumer,
+ private IBinder::DeathRecipient
+{
+ typedef mozilla::layers::TextureClient TextureClient;
+
+public:
+ enum { MIN_UNDEQUEUED_BUFFERS = 2 };
+ enum { NUM_BUFFER_SLOTS = 32 };
+ enum { NO_CONNECTED_API = 0 };
+ enum { INVALID_BUFFER_SLOT = -1 };
+ enum { STALE_BUFFER_SLOT = 1, NO_BUFFER_AVAILABLE, PRESENT_LATER };
+
+ // When in async mode we reserve two slots in order to guarantee that the
+ // producer and consumer can run asynchronously.
+ enum { MAX_MAX_ACQUIRED_BUFFERS = NUM_BUFFER_SLOTS - 2 };
+
+ // for backward source compatibility
+ typedef ::android::ConsumerListener ConsumerListener;
+
+ // ProxyConsumerListener is a ConsumerListener implementation that keeps a weak
+ // reference to the actual consumer object. It forwards all calls to that
+ // consumer object so long as it exists.
+ //
+ // This class exists to avoid having a circular reference between the
+ // GonkBufferQueue object and the consumer object. The reason this can't be a weak
+ // reference in the GonkBufferQueue class is because we're planning to expose the
+ // consumer side of a GonkBufferQueue as a binder interface, which doesn't support
+ // weak references.
+ class ProxyConsumerListener : public BnConsumerListener {
+ public:
+ ProxyConsumerListener(const wp<ConsumerListener>& consumerListener);
+ virtual ~ProxyConsumerListener();
+ virtual void onFrameAvailable();
+ virtual void onBuffersReleased();
+ private:
+ // mConsumerListener is a weak reference to the IConsumerListener. This is
+ // the raison d'etre of ProxyConsumerListener.
+ wp<ConsumerListener> mConsumerListener;
+ };
+
+
+ // BufferQueue manages a pool of gralloc memory slots to be used by
+ // producers and consumers. allocator is used to allocate all the
+ // needed gralloc buffers.
+ GonkBufferQueue(bool allowSynchronousMode = true,
+ const sp<IGraphicBufferAlloc>& allocator = NULL);
+ virtual ~GonkBufferQueue();
+
+ /*
+ * IBinder::DeathRecipient interface
+ */
+
+ virtual void binderDied(const wp<IBinder>& who);
+
+ /*
+ * IGraphicBufferProducer interface
+ */
+
+ // Query native window attributes. The "what" values are enumerated in
+ // window.h (e.g. NATIVE_WINDOW_FORMAT).
+ virtual int query(int what, int* value);
+
+ // setBufferCount updates the number of available buffer slots. If this
+ // method succeeds, buffer slots will be both unallocated and owned by
+ // the GonkBufferQueue object (i.e. they are not owned by the producer or
+ // consumer).
+ //
+ // This will fail if the producer has dequeued any buffers, or if
+ // bufferCount is invalid. bufferCount must generally be a value
+ // between the minimum undequeued buffer count and NUM_BUFFER_SLOTS
+ // (inclusive). It may also be set to zero (the default) to indicate
+ // that the producer does not wish to set a value. The minimum value
+ // can be obtained by calling query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ // ...).
+ //
+ // This may only be called by the producer. The consumer will be told
+ // to discard buffers through the onBuffersReleased callback.
+ virtual status_t setBufferCount(int bufferCount);
+
+ // requestBuffer returns the GraphicBuffer for slot N.
+ //
+ // In normal operation, this is called the first time slot N is returned
+ // by dequeueBuffer. It must be called again if dequeueBuffer returns
+ // flags indicating that previously-returned buffers are no longer valid.
+ virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
+
+ // dequeueBuffer gets the next buffer slot index for the producer to use.
+ // If a buffer slot is available then that slot index is written to the
+ // location pointed to by the buf argument and a status of OK is returned.
+ // If no slot is available then a status of -EBUSY is returned and buf is
+ // unmodified.
+ //
+ // The fence parameter will be updated to hold the fence associated with
+ // the buffer. The contents of the buffer must not be overwritten until the
+ // fence signals. If the fence is Fence::NO_FENCE, the buffer may be
+ // written immediately.
+ //
+ // The width and height parameters must be no greater than the minimum of
+ // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv).
+ // An error due to invalid dimensions might not be reported until
+ // updateTexImage() is called. If width and height are both zero, the
+ // default values specified by setDefaultBufferSize() are used instead.
+ //
+ // The pixel formats are enumerated in graphics.h, e.g.
+ // HAL_PIXEL_FORMAT_RGBA_8888. If the format is 0, the default format
+ // will be used.
+ //
+ // The usage argument specifies gralloc buffer usage flags. The values
+ // are enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER. These
+ // will be merged with the usage flags specified by setConsumerUsageBits.
+ //
+ // The return value may be a negative error value or a non-negative
+ // collection of flags. If the flags are set, the return values are
+ // valid, but additional actions must be performed.
+ //
+ // If IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION is set, the
+ // producer must discard cached GraphicBuffer references for the slot
+ // returned in buf.
+ // If IGraphicBufferProducer::RELEASE_ALL_BUFFERS is set, the producer
+ // must discard cached GraphicBuffer references for all slots.
+ //
+ // In both cases, the producer will need to call requestBuffer to get a
+ // GraphicBuffer handle for the returned slot.
+ virtual status_t dequeueBuffer(int *buf, sp<Fence>* fence, bool async,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage);
+
+ // queueBuffer returns a filled buffer to the GonkBufferQueue.
+ //
+ // Additional data is provided in the QueueBufferInput struct. Notably,
+ // a timestamp must be provided for the buffer. The timestamp is in
+ // nanoseconds, and must be monotonically increasing. Its other semantics
+ // (zero point, etc) are producer-specific and should be documented by the
+ // producer.
+ //
+ // The caller may provide a fence that signals when all rendering
+ // operations have completed. Alternatively, NO_FENCE may be used,
+ // indicating that the buffer is ready immediately.
+ //
+ // Some values are returned in the output struct: the current settings
+ // for default width and height, the current transform hint, and the
+ // number of queued buffers.
+ virtual status_t queueBuffer(int buf,
+ const QueueBufferInput& input, QueueBufferOutput* output);
+
+ // cancelBuffer returns a dequeued buffer to the GonkBufferQueue, but doesn't
+ // queue it for use by the consumer.
+ //
+ // The buffer will not be overwritten until the fence signals. The fence
+ // will usually be the one obtained from dequeueBuffer.
+ virtual void cancelBuffer(int buf, const sp<Fence>& fence);
+
+ // setSynchronousMode sets whether dequeueBuffer is synchronous or
+ // asynchronous. In synchronous mode, dequeueBuffer blocks until
+ // a buffer is available, the currently bound buffer can be dequeued and
+ // queued buffers will be acquired in order. In asynchronous mode,
+ // a queued buffer may be replaced by a subsequently queued buffer.
+ //
+ // The default mode is synchronous.
+ // This should be called only during initialization.
+ virtual status_t setSynchronousMode(bool enabled);
+
+ // connect attempts to connect a producer API to the GonkBufferQueue. This
+ // must be called before any other IGraphicBufferProducer methods are
+ // called except for getAllocator. A consumer must already be connected.
+ //
+ // This method will fail if connect was previously called on the
+ // GonkBufferQueue and no corresponding disconnect call was made (i.e. if
+ // it's still connected to a producer).
+ //
+ // APIs are enumerated in window.h (e.g. NATIVE_WINDOW_API_CPU).
+ virtual status_t connect(const sp<IBinder>& token,
+ int api, bool producerControlledByApp, QueueBufferOutput* output);
+
+ // disconnect attempts to disconnect a producer API from the GonkBufferQueue.
+ // Calling this method will cause any subsequent calls to other
+ // IGraphicBufferProducer methods to fail except for getAllocator and connect.
+ // Successfully calling connect after this will allow the other methods to
+ // succeed again.
+ //
+ // This method will fail if the the GonkBufferQueue is not currently
+ // connected to the specified producer API.
+ virtual status_t disconnect(int api);
+
+ /*
+ * IGraphicBufferConsumer interface
+ */
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the GonkBufferQueue. If no buffer is pending then it returns -EINVAL. If a
+ // buffer is successfully acquired, the information about the buffer is
+ // returned in BufferItem. If the buffer returned had previously been
+ // acquired then the BufferItem::mGraphicBuffer field of buffer is set to
+ // NULL and it is assumed that the consumer still holds a reference to the
+ // buffer.
+ //
+ // If presentWhen is nonzero, it indicates the time when the buffer will
+ // be displayed on screen. If the buffer's timestamp is farther in the
+ // future, the buffer won't be acquired, and PRESENT_LATER will be
+ // returned. The presentation time is in nanoseconds, and the time base
+ // is CLOCK_MONOTONIC.
+ virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen);
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // GonkBufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use. frameNumber is used to indentify the exact buffer returned.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ //
+ // Note that the dependencies on EGL will be removed once we switch to using
+ // the Android HW Sync HAL.
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
+ const sp<Fence>& releaseFence);
+
+ // consumerConnect connects a consumer to the GonkBufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // GonkBufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the GonkBufferQueue by the producer to fail.
+ // controlledByApp indicates whether the consumer is controlled by
+ // the application.
+ //
+ // consumer may not be NULL.
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp);
+
+ // consumerDisconnect disconnects a consumer from the GonkBufferQueue. All
+ // buffers will be freed and the GonkBufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the GonkBufferQueue by the producer to
+ // fail.
+ virtual status_t consumerDisconnect();
+
+ // getReleasedBuffers sets the value pointed to by slotMask to a bit mask
+ // indicating which buffer slots have been released by the GonkBufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ virtual status_t getReleasedBuffers(uint32_t* slotMask);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ virtual status_t setDefaultMaxBufferCount(int bufferCount);
+
+ // disableAsyncBuffer disables the extra buffer used in async mode
+ // (when both producer and consumer have set their "isControlledByApp"
+ // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
+ //
+ // This can only be called before consumerConnect().
+ virtual status_t disableAsyncBuffer();
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the GonkBufferQueue.
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
+
+ // setConsumerName sets the name used in logging
+ virtual void setConsumerName(const String8& name);
+
+ // setDefaultBufferFormat allows the GonkBufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ virtual status_t setConsumerUsageBits(uint32_t usage);
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ virtual status_t setTransformHint(uint32_t hint);
+
+ // dump our state in a String
+ virtual void dumpToString(String8& result, const char* prefix) const;
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ int getSlotFromTextureClientLocked(TextureClient* client) const;
+
+private:
+ // freeBufferLocked frees the GraphicBuffer and sync resources for the
+ // given slot.
+ //void freeBufferLocked(int index);
+
+ // freeAllBuffersLocked frees the GraphicBuffer and sync resources for
+ // all slots.
+ void freeAllBuffersLocked();
+
+ // setDefaultMaxBufferCountLocked sets the maximum number of buffer slots
+ // that will be used if the producer does not override the buffer slot
+ // count. The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ // The initial default is 2.
+ status_t setDefaultMaxBufferCountLocked(int count);
+
+ // getMinUndequeuedBufferCount returns the minimum number of buffers
+ // that must remain in a state other than DEQUEUED.
+ // The async parameter tells whether we're in asynchronous mode.
+ int getMinUndequeuedBufferCount(bool async) const;
+
+ // getMinBufferCountLocked returns the minimum number of buffers allowed
+ // given the current GonkBufferQueue state.
+ // The async parameter tells whether we're in asynchronous mode.
+ int getMinMaxBufferCountLocked(bool async) const;
+
+ // getMaxBufferCountLocked returns the maximum number of buffers that can
+ // be allocated at once. This value depends upon the following member
+ // variables:
+ //
+ // mDequeueBufferCannotBlock
+ // mMaxAcquiredBufferCount
+ // mDefaultMaxBufferCount
+ // mOverrideMaxBufferCount
+ // async parameter
+ //
+ // Any time one of these member variables is changed while a producer is
+ // connected, mDequeueCondition must be broadcast.
+ int getMaxBufferCountLocked(bool async) const;
+
+ // stillTracking returns true iff the buffer item is still being tracked
+ // in one of the slots.
+ bool stillTracking(const BufferItem *item) const;
+
+ struct BufferSlot {
+
+ BufferSlot()
+ : mBufferState(BufferSlot::FREE),
+ mRequestBufferCalled(false),
+ mFrameNumber(0),
+ mAcquireCalled(false),
+ mNeedsCleanupOnRelease(false) {
+ }
+
+ // mGraphicBuffer points to the buffer allocated for this slot or is NULL
+ // if no buffer has been allocated.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mTextureClient is a thin abstraction over remotely allocated GraphicBuffer.
+ RefPtr<TextureClient> mTextureClient;
+
+ // BufferState represents the different states in which a buffer slot
+ // can be. All slots are initially FREE.
+ enum BufferState {
+ // FREE indicates that the buffer is available to be dequeued
+ // by the producer. The buffer may be in use by the consumer for
+ // a finite time, so the buffer must not be modified until the
+ // associated fence is signaled.
+ //
+ // The slot is "owned" by GonkBufferQueue. It transitions to DEQUEUED
+ // when dequeueBuffer is called.
+ FREE = 0,
+
+ // DEQUEUED indicates that the buffer has been dequeued by the
+ // producer, but has not yet been queued or canceled. The
+ // producer may modify the buffer's contents as soon as the
+ // associated ready fence is signaled.
+ //
+ // The slot is "owned" by the producer. It can transition to
+ // QUEUED (via queueBuffer) or back to FREE (via cancelBuffer).
+ DEQUEUED = 1,
+
+ // QUEUED indicates that the buffer has been filled by the
+ // producer and queued for use by the consumer. The buffer
+ // contents may continue to be modified for a finite time, so
+ // the contents must not be accessed until the associated fence
+ // is signaled.
+ //
+ // The slot is "owned" by GonkBufferQueue. It can transition to
+ // ACQUIRED (via acquireBuffer) or to FREE (if another buffer is
+ // queued in asynchronous mode).
+ QUEUED = 2,
+
+ // ACQUIRED indicates that the buffer has been acquired by the
+ // consumer. As with QUEUED, the contents must not be accessed
+ // by the consumer until the fence is signaled.
+ //
+ // The slot is "owned" by the consumer. It transitions to FREE
+ // when releaseBuffer is called.
+ ACQUIRED = 3
+ };
+
+ // mBufferState is the current state of this buffer slot.
+ BufferState mBufferState;
+
+ // mRequestBufferCalled is used for validating that the producer did
+ // call requestBuffer() when told to do so. Technically this is not
+ // needed but useful for debugging and catching producer bugs.
+ bool mRequestBufferCalled;
+
+ // mFrameNumber is the number of the queued frame for this slot. This
+ // is used to dequeue buffers in LRU order (useful because buffers
+ // may be released before their release fence is signaled).
+ uint64_t mFrameNumber;
+
+ // mFence is a fence which will signal when work initiated by the
+ // previous owner of the buffer is finished. When the buffer is FREE,
+ // the fence indicates when the consumer has finished reading
+ // from the buffer, or when the producer has finished writing if it
+ // called cancelBuffer after queueing some writes. When the buffer is
+ // QUEUED, it indicates when the producer has finished filling the
+ // buffer. When the buffer is DEQUEUED or ACQUIRED, the fence has been
+ // passed to the consumer or producer along with ownership of the
+ // buffer, and mFence is set to NO_FENCE.
+ sp<Fence> mFence;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates whether this buffer needs to be cleaned up by the
+ // consumer. This is set when a buffer in ACQUIRED state is freed.
+ // It causes releaseBuffer to return STALE_BUFFER_SLOT.
+ bool mNeedsCleanupOnRelease;
+ };
+
+ // mSlots is the array of buffer slots that must be mirrored on the
+ // producer side. This allows buffer ownership to be transferred between
+ // the producer and consumer without sending a GraphicBuffer over binder.
+ // The entire array is initialized to NULL at construction time, and
+ // buffers are allocated for a slot when requestBuffer is called with
+ // that slot's index.
+ BufferSlot mSlots[NUM_BUFFER_SLOTS];
+
+ // mDefaultWidth holds the default width of allocated buffers. It is used
+ // in dequeueBuffer() if a width and height of zero is specified.
+ uint32_t mDefaultWidth;
+
+ // mDefaultHeight holds the default height of allocated buffers. It is used
+ // in dequeueBuffer() if a width and height of zero is specified.
+ uint32_t mDefaultHeight;
+
+ // mMaxAcquiredBufferCount is the number of buffers that the consumer may
+ // acquire at one time. It defaults to 1 and can be changed by the
+ // consumer via the setMaxAcquiredBufferCount method, but this may only be
+ // done when no producer is connected to the GonkBufferQueue.
+ //
+ // This value is used to derive the value returned for the
+ // MIN_UNDEQUEUED_BUFFERS query by the producer.
+ int mMaxAcquiredBufferCount;
+
+ // mDefaultMaxBufferCount is the default limit on the number of buffers
+ // that will be allocated at one time. This default limit is set by the
+ // consumer. The limit (as opposed to the default limit) may be
+ // overridden by the producer.
+ int mDefaultMaxBufferCount;
+
+ // mOverrideMaxBufferCount is the limit on the number of buffers that will
+ // be allocated at one time. This value is set by the image producer by
+ // calling setBufferCount. The default is zero, which means the producer
+ // doesn't care about the number of buffers in the pool. In that case
+ // mDefaultMaxBufferCount is used as the limit.
+ int mOverrideMaxBufferCount;
+
+ // mGraphicBufferAlloc is the connection to SurfaceFlinger that is used to
+ // allocate new GraphicBuffer objects.
+ sp<IGraphicBufferAlloc> mGraphicBufferAlloc;
+
+ // mConsumerListener is used to notify the connected consumer of
+ // asynchronous events that it may wish to react to. It is initially set
+ // to NULL and is written by consumerConnect and consumerDisconnect.
+ sp<IConsumerListener> mConsumerListener;
+
+ // mSynchronousMode whether we're in synchronous mode or not
+ bool mSynchronousMode;
+
+ // mConsumerControlledByApp whether the connected consumer is controlled by the
+ // application.
+ bool mConsumerControlledByApp;
+
+ // mDequeueBufferCannotBlock whether dequeueBuffer() isn't allowed to block.
+ // this flag is set during connect() when both consumer and producer are controlled
+ // by the application.
+ bool mDequeueBufferCannotBlock;
+
+ // mUseAsyncBuffer whether an extra buffer is used in async mode to prevent
+ // dequeueBuffer() from ever blocking.
+ bool mUseAsyncBuffer;
+
+ // mConnectedApi indicates the producer API that is currently connected
+ // to this GonkBufferQueue. It defaults to NO_CONNECTED_API (= 0), and gets
+ // updated by the connect and disconnect methods.
+ int mConnectedApi;
+
+ // mDequeueCondition condition used for dequeueBuffer in synchronous mode
+ mutable Condition mDequeueCondition;
+
+ // mQueue is a FIFO of queued buffers used in synchronous mode
+ typedef Vector<BufferItem> Fifo;
+ Fifo mQueue;
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume image buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the
+ // consumerDisconnect method. A GonkBufferQueue that has been abandoned will
+ // return the NO_INIT error from all IGraphicBufferProducer methods
+ // capable of returning an error.
+ bool mAbandoned;
+
+ // mConsumerName is a string used to identify the GonkBufferQueue in log
+ // messages. It is set by the setConsumerName method.
+ String8 mConsumerName;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkBufferQueue objects. It must be locked whenever the
+ // member variables are accessed.
+ mutable Mutex mMutex;
+
+ // mFrameCounter is the free running counter, incremented on every
+ // successful queueBuffer call, and buffer allocation.
+ uint64_t mFrameCounter;
+
+ // mBufferHasBeenQueued is true once a buffer has been queued. It is
+ // reset when something causes all buffers to be freed (e.g. changing the
+ // buffer count).
+ bool mBufferHasBeenQueued;
+
+ // mDefaultBufferFormat can be set so it will override
+ // the buffer format when it isn't specified in dequeueBuffer
+ uint32_t mDefaultBufferFormat;
+
+ // mConsumerUsageBits contains flags the consumer wants for GraphicBuffers
+ uint32_t mConsumerUsageBits;
+
+ // mTransformHint is used to optimize for screen rotations
+ uint32_t mTransformHint;
+
+ // mConnectedProducerToken is used to set a binder death notification on the producer
+ sp<IBinder> mConnectedProducerToken;
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_BUFFERQUEUE_H
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.cpp
new file mode 100644
index 0000000000..7df72bf68e
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.cpp
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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 "GonkBufferItem.h"
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <system/window.h>
+
+namespace android {
+
+GonkBufferItem::GonkBufferItem() :
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mIsAutoTimestamp(false),
+ mFrameNumber(0),
+ mSlot(INVALID_BUFFER_SLOT),
+ mIsDroppable(false),
+ mAcquireCalled(false),
+ mTransformToDisplayInverse(false) {
+ mCrop.makeInvalid();
+}
+
+GonkBufferItem::operator IGonkGraphicBufferConsumer::BufferItem() const {
+ IGonkGraphicBufferConsumer::BufferItem bufferItem;
+ bufferItem.mGraphicBuffer = mGraphicBuffer;
+ bufferItem.mFence = mFence;
+ bufferItem.mCrop = mCrop;
+ bufferItem.mTransform = mTransform;
+ bufferItem.mScalingMode = mScalingMode;
+ bufferItem.mTimestamp = mTimestamp;
+ bufferItem.mIsAutoTimestamp = mIsAutoTimestamp;
+ bufferItem.mFrameNumber = mFrameNumber;
+ bufferItem.mBuf = mSlot;
+ bufferItem.mIsDroppable = mIsDroppable;
+ bufferItem.mAcquireCalled = mAcquireCalled;
+ bufferItem.mTransformToDisplayInverse = mTransformToDisplayInverse;
+ return bufferItem;
+}
+
+size_t GonkBufferItem::getPodSize() const {
+ size_t c = sizeof(mCrop) +
+ sizeof(mTransform) +
+ sizeof(mScalingMode) +
+ sizeof(mTimestamp) +
+ sizeof(mIsAutoTimestamp) +
+ sizeof(mFrameNumber) +
+ sizeof(mSlot) +
+ sizeof(mIsDroppable) +
+ sizeof(mAcquireCalled) +
+ sizeof(mTransformToDisplayInverse);
+ return c;
+}
+
+size_t GonkBufferItem::getFlattenedSize() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ if (mFence != 0) {
+ c += mFence->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ return sizeof(int32_t) + c + getPodSize();
+}
+
+size_t GonkBufferItem::getFdCount() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFdCount();
+ }
+ if (mFence != 0) {
+ c += mFence->getFdCount();
+ }
+ return c;
+}
+
+status_t GonkBufferItem::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const {
+
+ // make sure we have enough space
+ if (count < GonkBufferItem::getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ // content flags are stored first
+ uint32_t& flags = *static_cast<uint32_t*>(buffer);
+
+ // advance the pointer
+ FlattenableUtils::advance(buffer, size, sizeof(uint32_t));
+
+ flags = 0;
+ if (mGraphicBuffer != 0) {
+ status_t err = mGraphicBuffer->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 1;
+ }
+ if (mFence != 0) {
+ status_t err = mFence->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 2;
+ }
+
+ // check we have enough space (in case flattening the fence/graphicbuffer lied to us)
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, mCrop);
+ FlattenableUtils::write(buffer, size, mTransform);
+ FlattenableUtils::write(buffer, size, mScalingMode);
+ FlattenableUtils::write(buffer, size, mTimestamp);
+ FlattenableUtils::write(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::write(buffer, size, mFrameNumber);
+ FlattenableUtils::write(buffer, size, mSlot);
+ FlattenableUtils::write(buffer, size, mIsDroppable);
+ FlattenableUtils::write(buffer, size, mAcquireCalled);
+ FlattenableUtils::write(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferItem::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+
+ if (size < sizeof(uint32_t))
+ return NO_MEMORY;
+
+ uint32_t flags = 0;
+ FlattenableUtils::read(buffer, size, flags);
+
+ if (flags & 1) {
+ mGraphicBuffer = new GraphicBuffer();
+ status_t err = mGraphicBuffer->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ if (flags & 2) {
+ mFence = new Fence();
+ status_t err = mFence->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ // check we have enough space
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, mCrop);
+ FlattenableUtils::read(buffer, size, mTransform);
+ FlattenableUtils::read(buffer, size, mScalingMode);
+ FlattenableUtils::read(buffer, size, mTimestamp);
+ FlattenableUtils::read(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::read(buffer, size, mFrameNumber);
+ FlattenableUtils::read(buffer, size, mSlot);
+ FlattenableUtils::read(buffer, size, mIsDroppable);
+ FlattenableUtils::read(buffer, size, mAcquireCalled);
+ FlattenableUtils::read(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+const char* GonkBufferItem::scalingModeName(uint32_t scalingMode) {
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP";
+ default: return "Unknown";
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.h
new file mode 100644
index 0000000000..b2d6d30681
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferItem.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERITEM_LL_H
+#define NATIVEWINDOW_GONKBUFFERITEM_LL_H
+
+#include "IGonkGraphicBufferConsumerLL.h"
+
+#include <ui/Rect.h>
+
+#include <utils/Flattenable.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class Fence;
+class GraphicBuffer;
+
+class GonkBufferItem : public Flattenable<GonkBufferItem> {
+ friend class Flattenable<GonkBufferItem>;
+ size_t getPodSize() const;
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ public:
+ // The default value of mBuf, used to indicate this doesn't correspond to a slot.
+ enum { INVALID_BUFFER_SLOT = -1 };
+ GonkBufferItem();
+ operator IGonkGraphicBufferConsumer::BufferItem() const;
+
+ static const char* scalingModeName(uint32_t scalingMode);
+
+ // mGraphicBuffer points to the buffer allocated for this slot, or is NULL
+ // if the buffer in this slot has been acquired in the past (see
+ // BufferSlot.mAcquireCalled).
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence that will signal when the buffer is idle.
+ sp<Fence> mFence;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ // refer to NATIVE_WINDOW_TRANSFORM_* in <window.h>
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ // refer to NATIVE_WINDOW_SCALING_* in <window.h>
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued. This value
+ // is guaranteed to be monotonically increasing for each newly
+ // acquired buffer.
+ int64_t mTimestamp;
+
+ // mIsAutoTimestamp indicates whether mTimestamp was generated
+ // automatically when the buffer was queued.
+ bool mIsAutoTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot.
+ uint64_t mFrameNumber;
+
+ // mSlot is the slot index of this buffer (default INVALID_BUFFER_SLOT).
+ int mSlot;
+
+ // mIsDroppable whether this buffer was queued with the
+ // property that it can be replaced by a new buffer for the purpose of
+ // making sure dequeueBuffer() won't block.
+ // i.e.: was the BufferQueue in "mDequeueBufferCannotBlock" when this buffer
+ // was queued.
+ bool mIsDroppable;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates this buffer must be transformed by the inverse transform of the screen
+ // it is displayed onto. This is applied after mTransform.
+ bool mTransformToDisplayInverse;
+};
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.cpp
new file mode 100644
index 0000000000..1d7eb27021
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.cpp
@@ -0,0 +1,559 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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 <inttypes.h>
+
+#define LOG_TAG "GonkBufferQueueConsumer"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include "GonkBufferItem.h"
+#include "GonkBufferQueueConsumer.h"
+#include "GonkBufferQueueCore.h"
+#include <gui/IConsumerListener.h>
+#include <gui/IProducerListener.h>
+
+namespace android {
+
+GonkBufferQueueConsumer::GonkBufferQueueConsumer(const sp<GonkBufferQueueCore>& core) :
+ mCore(core),
+ mSlots(core->mSlots),
+ mConsumerName() {}
+
+GonkBufferQueueConsumer::~GonkBufferQueueConsumer() {}
+
+status_t GonkBufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
+ nsecs_t expectedPresent) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+
+ // Check that the consumer doesn't currently have the maximum number of
+ // buffers acquired. We allow the max buffer count to be exceeded by one
+ // buffer so that the consumer can successfully set up the newly acquired
+ // buffer before releasing the old one.
+ int numAcquiredBuffers = 0;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == GonkBufferSlot::ACQUIRED) {
+ ++numAcquiredBuffers;
+ }
+ }
+ if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
+ ALOGE("acquireBuffer: max acquired buffer count reached: %d (max %d)",
+ numAcquiredBuffers, mCore->mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+
+ // Check if the queue is empty.
+ // In asynchronous mode the list is guaranteed to be one buffer deep,
+ // while in synchronous mode we use the oldest buffer.
+ if (mCore->mQueue.empty()) {
+ return NO_BUFFER_AVAILABLE;
+ }
+
+ GonkBufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
+
+ // If expectedPresent is specified, we may not want to return a buffer yet.
+ // If it's specified and there's more than one buffer queued, we may want
+ // to drop a buffer.
+ if (expectedPresent != 0) {
+ const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second
+
+ // The 'expectedPresent' argument indicates when the buffer is expected
+ // to be presented on-screen. If the buffer's desired present time is
+ // earlier (less) than expectedPresent -- meaning it will be displayed
+ // on time or possibly late if we show it as soon as possible -- we
+ // acquire and return it. If we don't want to display it until after the
+ // expectedPresent time, we return PRESENT_LATER without acquiring it.
+ //
+ // To be safe, we don't defer acquisition if expectedPresent is more
+ // than one second in the future beyond the desired present time
+ // (i.e., we'd be holding the buffer for a long time).
+ //
+ // NOTE: Code assumes monotonic time values from the system clock
+ // are positive.
+
+ // Start by checking to see if we can drop frames. We skip this check if
+ // the timestamps are being auto-generated by Surface. If the app isn't
+ // generating timestamps explicitly, it probably doesn't want frames to
+ // be discarded based on them.
+ while (mCore->mQueue.size() > 1 && !mCore->mQueue[0].mIsAutoTimestamp) {
+ // If entry[1] is timely, drop entry[0] (and repeat). We apply an
+ // additional criterion here: we only drop the earlier buffer if our
+ // desiredPresent falls within +/- 1 second of the expected present.
+ // Otherwise, bogus desiredPresent times (e.g., 0 or a small
+ // relative timestamp), which normally mean "ignore the timestamp
+ // and acquire immediately", would cause us to drop frames.
+ //
+ // We may want to add an additional criterion: don't drop the
+ // earlier buffer if entry[1]'s fence hasn't signaled yet.
+ const BufferItem& bufferItem(mCore->mQueue[1]);
+ nsecs_t desiredPresent = bufferItem.mTimestamp;
+ if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
+ desiredPresent > expectedPresent) {
+ // This buffer is set to display in the near future, or
+ // desiredPresent is garbage. Either way we don't want to drop
+ // the previous buffer just to get this on the screen sooner.
+ ALOGV("acquireBuffer: nodrop desire=%" PRId64 " expect=%"
+ PRId64 " (%" PRId64 ") now=%" PRId64,
+ desiredPresent, expectedPresent,
+ desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ break;
+ }
+
+ ALOGV("acquireBuffer: drop desire=%" PRId64 " expect=%" PRId64
+ " size=%zu",
+ desiredPresent, expectedPresent, mCore->mQueue.size());
+ if (mCore->stillTracking(front)) {
+ // Front buffer is still in mSlots, so mark the slot as free
+ mSlots[front->mSlot].mBufferState = GonkBufferSlot::FREE;
+ }
+ mCore->mQueue.erase(front);
+ front = mCore->mQueue.begin();
+ }
+
+ // See if the front buffer is due
+ nsecs_t desiredPresent = front->mTimestamp;
+ if (desiredPresent > expectedPresent &&
+ desiredPresent < expectedPresent + MAX_REASONABLE_NSEC) {
+ ALOGV("acquireBuffer: defer desire=%" PRId64 " expect=%" PRId64
+ " (%" PRId64 ") now=%" PRId64,
+ desiredPresent, expectedPresent,
+ desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ return PRESENT_LATER;
+ }
+
+ ALOGV("acquireBuffer: accept desire=%" PRId64 " expect=%" PRId64 " "
+ "(%" PRId64 ") now=%" PRId64, desiredPresent, expectedPresent,
+ desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ }
+
+ int slot = front->mSlot;
+ //*outBuffer = *front;
+ outBuffer->mGraphicBuffer = mSlots[slot].mGraphicBuffer;
+ outBuffer->mFrameNumber = mSlots[slot].mFrameNumber;
+ outBuffer->mBuf = slot;
+ outBuffer->mFence = mSlots[slot].mFence;
+
+ ATRACE_BUFFER_INDEX(slot);
+
+ ALOGV("acquireBuffer: acquiring { slot=%d/%" PRIu64 " buffer=%p }",
+ slot, front->mFrameNumber, front->mGraphicBuffer->handle);
+ // If the front buffer is still being tracked, update its slot state
+ if (mCore->stillTracking(front)) {
+ mSlots[slot].mAcquireCalled = true;
+ mSlots[slot].mNeedsCleanupOnRelease = false;
+ mSlots[slot].mBufferState = GonkBufferSlot::ACQUIRED;
+ mSlots[slot].mFence = Fence::NO_FENCE;
+ }
+
+ // If the buffer has previously been acquired by the consumer, set
+ // mGraphicBuffer to NULL to avoid unnecessarily remapping this buffer
+ // on the consumer side
+ //if (outBuffer->mAcquireCalled) {
+ // outBuffer->mGraphicBuffer = NULL;
+ //}
+
+ mCore->mQueue.erase(front);
+
+ // We might have freed a slot while dropping old buffers, or the producer
+ // may be blocked waiting for the number of buffers in the queue to
+ // decrease.
+ mCore->mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::detachBuffer(int slot) {
+ ATRACE_CALL();
+ ATRACE_BUFFER_INDEX(slot);
+ ALOGV("detachBuffer(C): slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("detachBuffer(C): GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("detachBuffer(C): slot index %d out of range [0, %d)",
+ slot, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::ACQUIRED) {
+ ALOGE("detachBuffer(C): slot %d is not owned by the consumer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+
+ mCore->freeBufferLocked(slot);
+ mCore->mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::attachBuffer(int* outSlot,
+ const sp<android::GraphicBuffer>& buffer) {
+ ATRACE_CALL();
+
+ if (outSlot == NULL) {
+ ALOGE("attachBuffer(P): outSlot must not be NULL");
+ return BAD_VALUE;
+ } else if (buffer == NULL) {
+ ALOGE("attachBuffer(P): cannot attach NULL buffer");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ // Make sure we don't have too many acquired buffers and find a free slot
+ // to put the buffer into (the oldest if there are multiple).
+ int numAcquiredBuffers = 0;
+ int found = GonkBufferQueueCore::INVALID_BUFFER_SLOT;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == GonkBufferSlot::ACQUIRED) {
+ ++numAcquiredBuffers;
+ } else if (mSlots[s].mBufferState == GonkBufferSlot::FREE) {
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT ||
+ mSlots[s].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = s;
+ }
+ }
+ }
+
+ if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
+ ALOGE("attachBuffer(P): max acquired buffer count reached: %d "
+ "(max %d)", numAcquiredBuffers,
+ mCore->mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) {
+ ALOGE("attachBuffer(P): could not find free buffer slot");
+ return NO_MEMORY;
+ }
+
+ *outSlot = found;
+ ATRACE_BUFFER_INDEX(*outSlot);
+ ALOGV("attachBuffer(C): returning slot %d", *outSlot);
+
+ mSlots[*outSlot].mGraphicBuffer = buffer;
+ mSlots[*outSlot].mBufferState = GonkBufferSlot::ACQUIRED;
+ mSlots[*outSlot].mAttachedByConsumer = true;
+ mSlots[*outSlot].mNeedsCleanupOnRelease = false;
+ mSlots[*outSlot].mFence = Fence::NO_FENCE;
+ mSlots[*outSlot].mFrameNumber = 0;
+
+ // mAcquireCalled tells GonkBufferQueue that it doesn't need to send a valid
+ // GraphicBuffer pointer on the next acquireBuffer call, which decreases
+ // Binder traffic by not un/flattening the GraphicBuffer. However, it
+ // requires that the consumer maintain a cached copy of the slot <--> buffer
+ // mappings, which is why the consumer doesn't need the valid pointer on
+ // acquire.
+ //
+ // The StreamSplitter is one of the primary users of the attach/detach
+ // logic, and while it is running, all buffers it acquires are immediately
+ // detached, and all buffers it eventually releases are ones that were
+ // attached (as opposed to having been obtained from acquireBuffer), so it
+ // doesn't make sense to maintain the slot/buffer mappings, which would
+ // become invalid for every buffer during detach/attach. By setting this to
+ // false, the valid GraphicBuffer pointer will always be sent with acquire
+ // for attached buffers.
+ mSlots[*outSlot].mAcquireCalled = false;
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
+ const sp<Fence>& releaseFence) {
+ ATRACE_CALL();
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS ||
+ releaseFence == NULL) {
+ return BAD_VALUE;
+ }
+
+ sp<IProducerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ // If the frame number has changed because the buffer has been reallocated,
+ // we can ignore this releaseBuffer for the old buffer
+ //if (frameNumber != mSlots[slot].mFrameNumber) {
+ // return STALE_BUFFER_SLOT;
+ //}
+
+ // Make sure this buffer hasn't been queued while acquired by the consumer
+ GonkBufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
+ while (current != mCore->mQueue.end()) {
+ if (current->mSlot == slot) {
+ ALOGE("releaseBuffer: buffer slot %d pending release is "
+ "currently queued", slot);
+ return BAD_VALUE;
+ }
+ ++current;
+ }
+
+ if (mSlots[slot].mBufferState == GonkBufferSlot::ACQUIRED) {
+ mSlots[slot].mFence = releaseFence;
+ mSlots[slot].mBufferState = GonkBufferSlot::FREE;
+ listener = mCore->mConnectedProducerListener;
+ ALOGV("releaseBuffer: releasing slot %d", slot);
+ } else if (mSlots[slot].mNeedsCleanupOnRelease) {
+ ALOGV("releaseBuffer: releasing a stale buffer slot %d "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ mSlots[slot].mNeedsCleanupOnRelease = false;
+ return STALE_BUFFER_SLOT;
+ } else {
+ ALOGV("releaseBuffer: attempted to release buffer slot %d "
+ "but its state was %d", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+
+ mCore->mDequeueCondition.broadcast();
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onBufferReleased();
+ }
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::connect(
+ const sp<IConsumerListener>& consumerListener, bool controlledByApp) {
+ ATRACE_CALL();
+
+ if (consumerListener == NULL) {
+ ALOGE("connect(C): consumerListener may not be NULL");
+ return BAD_VALUE;
+ }
+
+ ALOGV("connect(C): controlledByApp=%s",
+ controlledByApp ? "true" : "false");
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("connect(C): GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ mCore->mConsumerListener = consumerListener;
+ mCore->mConsumerControlledByApp = controlledByApp;
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::disconnect() {
+ ATRACE_CALL();
+
+ ALOGV("disconnect(C)");
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mConsumerListener == NULL) {
+ ALOGE("disconnect(C): no consumer is connected");
+ return BAD_VALUE;
+ }
+
+ mCore->mIsAbandoned = true;
+ mCore->mConsumerListener = NULL;
+ mCore->mQueue.clear();
+ mCore->freeAllBuffersLocked();
+ mCore->mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::getReleasedBuffers(uint64_t *outSlotMask) {
+ ATRACE_CALL();
+
+ if (outSlotMask == NULL) {
+ ALOGE("getReleasedBuffers: outSlotMask may not be NULL");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("getReleasedBuffers: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ uint64_t mask = 0;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (!mSlots[s].mAcquireCalled) {
+ mask |= (1ULL << s);
+ }
+ }
+
+ // Remove from the mask queued buffers for which acquire has been called,
+ // since the consumer will not receive their buffer addresses and so must
+ // retain their cached information
+ GonkBufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
+ while (current != mCore->mQueue.end()) {
+ if (current->mAcquireCalled) {
+ mask &= ~(1ULL << current->mSlot);
+ }
+ ++current;
+ }
+
+ ALOGV("getReleasedBuffers: returning mask %#" PRIx64, mask);
+ *outSlotMask = mask;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setDefaultBufferSize(uint32_t width,
+ uint32_t height) {
+ ATRACE_CALL();
+
+ if (width == 0 || height == 0) {
+ ALOGV("setDefaultBufferSize: dimensions cannot be 0 (width=%u "
+ "height=%u)", width, height);
+ return BAD_VALUE;
+ }
+
+ ALOGV("setDefaultBufferSize: width=%u height=%u", width, height);
+
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mDefaultWidth = width;
+ mCore->mDefaultHeight = height;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setDefaultMaxBufferCount(int bufferCount) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+ return mCore->setDefaultMaxBufferCountLocked(bufferCount);
+}
+
+status_t GonkBufferQueueConsumer::disableAsyncBuffer() {
+ ATRACE_CALL();
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mConsumerListener != NULL) {
+ ALOGE("disableAsyncBuffer: consumer already connected");
+ return INVALID_OPERATION;
+ }
+
+ ALOGV("disableAsyncBuffer");
+ mCore->mUseAsyncBuffer = false;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setMaxAcquiredBufferCount(
+ int maxAcquiredBuffers) {
+ ATRACE_CALL();
+
+ if (maxAcquiredBuffers < 1 ||
+ maxAcquiredBuffers > GonkBufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) {
+ ALOGE("setMaxAcquiredBufferCount: invalid count %d",
+ maxAcquiredBuffers);
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mConnectedApi != GonkBufferQueueCore::NO_CONNECTED_API) {
+ ALOGE("setMaxAcquiredBufferCount: producer is already connected");
+ return INVALID_OPERATION;
+ }
+
+ ALOGV("setMaxAcquiredBufferCount: %d", maxAcquiredBuffers);
+ mCore->mMaxAcquiredBufferCount = maxAcquiredBuffers;
+ return NO_ERROR;
+}
+
+void GonkBufferQueueConsumer::setConsumerName(const String8& name) {
+ ATRACE_CALL();
+ ALOGV("setConsumerName: '%s'", name.string());
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mConsumerName = name;
+ mConsumerName = name;
+}
+
+status_t GonkBufferQueueConsumer::setDefaultBufferFormat(uint32_t defaultFormat) {
+ ATRACE_CALL();
+ ALOGV("setDefaultBufferFormat: %u", defaultFormat);
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mDefaultBufferFormat = defaultFormat;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setConsumerUsageBits(uint32_t usage) {
+ ATRACE_CALL();
+ ALOGV("setConsumerUsageBits: %#x", usage);
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mConsumerUsageBits = usage;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueConsumer::setTransformHint(uint32_t hint) {
+ ATRACE_CALL();
+ ALOGV("setTransformHint: %#x", hint);
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mTransformHint = hint;
+ return NO_ERROR;
+}
+
+sp<NativeHandle> GonkBufferQueueConsumer::getSidebandStream() const {
+ return mCore->mSidebandStream;
+}
+
+void GonkBufferQueueConsumer::dumpToString(String8& result, const char* prefix) const {
+ mCore->dump(result, prefix);
+}
+
+already_AddRefed<GonkBufferSlot::TextureClient>
+GonkBufferQueueConsumer::getTextureClientFromBuffer(ANativeWindowBuffer* buffer)
+{
+ Mutex::Autolock _l(mCore->mMutex);
+ if (buffer == NULL) {
+ ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return nullptr;
+ }
+
+ for (int i = 0; i < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mGraphicBuffer != NULL && mSlots[i].mGraphicBuffer->handle == buffer->handle) {
+ RefPtr<TextureClient> client(mSlots[i].mTextureClient);
+ return client.forget();
+ }
+ }
+ ALOGE("getSlotFromBufferLocked: unknown buffer: %p", buffer->handle);
+ return nullptr;
+}
+
+int
+GonkBufferQueueConsumer::getSlotFromTextureClientLocked(GonkBufferSlot::TextureClient* client) const
+{
+ if (client == NULL) {
+ ALOGE("getSlotFromBufferLocked: encountered NULL buffer");
+ return BAD_VALUE;
+ }
+
+ for (int i = 0; i < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+ if (mSlots[i].mTextureClient == client) {
+ return i;
+ }
+ }
+ ALOGE("getSlotFromBufferLocked: unknown TextureClient: %p", client);
+ return BAD_VALUE;
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.h
new file mode 100644
index 0000000000..a97cfab428
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueConsumer.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUECONSUMER_LL_H
+#define NATIVEWINDOW_GONKBUFFERQUEUECONSUMER_LL_H
+
+#include "GonkBufferQueueDefs.h"
+#include "IGonkGraphicBufferConsumerLL.h"
+
+namespace android {
+
+class GonkBufferQueueCore;
+
+class GonkBufferQueueConsumer : public BnGonkGraphicBufferConsumer {
+
+public:
+ GonkBufferQueueConsumer(const sp<GonkBufferQueueCore>& core);
+ virtual ~GonkBufferQueueConsumer();
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the GonkBufferQueue. If no buffer is pending then it returns
+ // NO_BUFFER_AVAILABLE. If a buffer is successfully acquired, the
+ // information about the buffer is returned in BufferItem. If the buffer
+ // returned had previously been acquired then the BufferItem::mGraphicBuffer
+ // field of buffer is set to NULL and it is assumed that the consumer still
+ // holds a reference to the buffer.
+ //
+ // If expectedPresent is nonzero, it indicates the time when the buffer
+ // will be displayed on screen. If the buffer's timestamp is farther in the
+ // future, the buffer won't be acquired, and PRESENT_LATER will be
+ // returned. The presentation time is in nanoseconds, and the time base
+ // is CLOCK_MONOTONIC.
+ virtual status_t acquireBuffer(BufferItem* outBuffer,
+ nsecs_t expectedPresent);
+
+ // See IGonkGraphicBufferConsumer::detachBuffer
+ virtual status_t detachBuffer(int slot);
+
+ // See IGonkGraphicBufferConsumer::attachBuffer
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer);
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // GonkBufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use. frameNumber is used to indentify the exact buffer returned.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ virtual status_t releaseBuffer(int slot, uint64_t frameNumber,
+ const sp<Fence>& releaseFence);
+
+ // connect connects a consumer to the GonkBufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // GonkBufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the GonkBufferQueue by the producer to fail.
+ // controlledByApp indicates whether the consumer is controlled by
+ // the application.
+ //
+ // consumerListener may not be NULL.
+ virtual status_t connect(const sp<IConsumerListener>& consumerListener,
+ bool controlledByApp);
+
+ // disconnect disconnects a consumer from the GonkBufferQueue. All
+ // buffers will be freed and the GonkBufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the GonkBufferQueue by the producer to
+ // fail.
+ virtual status_t disconnect();
+
+ // getReleasedBuffers sets the value pointed to by outSlotMask to a bit mask
+ // indicating which buffer slots have been released by the GonkBufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ virtual status_t getReleasedBuffers(uint64_t* outSlotMask);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ virtual status_t setDefaultBufferSize(uint32_t width, uint32_t height);
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ virtual status_t setDefaultMaxBufferCount(int bufferCount);
+
+ // disableAsyncBuffer disables the extra buffer used in async mode
+ // (when both producer and consumer have set their "isControlledByApp"
+ // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
+ //
+ // This can only be called before connect().
+ virtual status_t disableAsyncBuffer();
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the GonkBufferQueue.
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
+
+ // setConsumerName sets the name used in logging
+ virtual void setConsumerName(const String8& name);
+
+ // setDefaultBufferFormat allows the GonkBufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ virtual status_t setConsumerUsageBits(uint32_t usage);
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ virtual status_t setTransformHint(uint32_t hint);
+
+ // Retrieve the sideband buffer stream, if any.
+ virtual sp<NativeHandle> getSidebandStream() const;
+
+ // dump our state in a String
+ virtual void dumpToString(String8& result, const char* prefix) const;
+
+ // Added by mozilla
+ virtual already_AddRefed<GonkBufferSlot::TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ virtual int getSlotFromTextureClientLocked(GonkBufferSlot::TextureClient* client) const;
+
+ // Functions required for backwards compatibility.
+ // These will be modified/renamed in IGonkGraphicBufferConsumer and will be
+ // removed from this class at that time. See b/13306289.
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer,
+ bool controlledByApp) {
+ return connect(consumer, controlledByApp);
+ }
+
+ virtual status_t consumerDisconnect() { return disconnect(); }
+
+ // End functions required for backwards compatibility
+
+private:
+ sp<GonkBufferQueueCore> mCore;
+
+ // This references mCore->mSlots. Lock mCore->mMutex while accessing.
+ GonkBufferQueueDefs::SlotsType& mSlots;
+
+ // This is a cached copy of the name stored in the GonkBufferQueueCore.
+ // It's updated during setConsumerName.
+ String8 mConsumerName;
+
+}; // class GonkBufferQueueConsumer
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.cpp
new file mode 100644
index 0000000000..9e8e337f69
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#define LOG_TAG "GonkBufferQueueCore"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include <inttypes.h>
+
+#include "GonkBufferItem.h"
+#include "GonkBufferQueueCore.h"
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/IProducerListener.h>
+#include <gui/ISurfaceComposer.h>
+#include <private/gui/ComposerService.h>
+
+#include <cutils/compiler.h>
+#include "mozilla/layers/GrallocTextureClient.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+
+template <typename T>
+static inline T max(T a, T b) { return a > b ? a : b; }
+
+namespace android {
+
+static String8 getUniqueName() {
+ static volatile int32_t counter = 0;
+ return String8::format("unnamed-%d-%d", getpid(),
+ android_atomic_inc(&counter));
+}
+
+GonkBufferQueueCore::GonkBufferQueueCore(const sp<IGraphicBufferAlloc>& allocator) :
+ mAllocator(allocator),
+ mMutex(),
+ mIsAbandoned(false),
+ mConsumerControlledByApp(false),
+ mConsumerName(getUniqueName()),
+ mConsumerListener(),
+ mConsumerUsageBits(0),
+ mConnectedApi(NO_CONNECTED_API),
+ mConnectedProducerListener(),
+ mSlots(),
+ mQueue(),
+ mOverrideMaxBufferCount(0),
+ mDequeueCondition(),
+ mUseAsyncBuffer(true),
+ mDequeueBufferCannotBlock(false),
+ mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
+ mDefaultWidth(1),
+ mDefaultHeight(1),
+ mDefaultMaxBufferCount(2),
+ mMaxAcquiredBufferCount(1),
+ mBufferHasBeenQueued(false),
+ mFrameCounter(0),
+ mTransformHint(0),
+ mIsAllocating(false),
+ mIsAllocatingCondition()
+{
+ ALOGV("GonkBufferQueueCore");
+}
+
+GonkBufferQueueCore::~GonkBufferQueueCore() {}
+
+void GonkBufferQueueCore::dump(String8& result, const char* prefix) const {
+ Mutex::Autolock lock(mMutex);
+
+ String8 fifo;
+ Fifo::const_iterator current(mQueue.begin());
+ while (current != mQueue.end()) {
+ fifo.appendFormat("%02d:%p crop=[%d,%d,%d,%d], "
+ "xform=0x%02x, time=%#" PRIx64 ", scale=%s\n",
+ current->mSlot, current->mGraphicBuffer.get(),
+ current->mCrop.left, current->mCrop.top, current->mCrop.right,
+ current->mCrop.bottom, current->mTransform, current->mTimestamp,
+ GonkBufferItem::scalingModeName(current->mScalingMode));
+ ++current;
+ }
+
+ result.appendFormat("%s-GonkBufferQueue mMaxAcquiredBufferCount=%d, "
+ "mDequeueBufferCannotBlock=%d, default-size=[%dx%d], "
+ "default-format=%d, transform-hint=%02x, FIFO(%zu)={%s}\n",
+ prefix, mMaxAcquiredBufferCount, mDequeueBufferCannotBlock,
+ mDefaultWidth, mDefaultHeight, mDefaultBufferFormat, mTransformHint,
+ mQueue.size(), fifo.string());
+
+ // Trim the free buffers so as to not spam the dump
+ int maxBufferCount = 0;
+ for (int s = GonkBufferQueueDefs::NUM_BUFFER_SLOTS - 1; s >= 0; --s) {
+ const GonkBufferSlot& slot(mSlots[s]);
+ if (slot.mBufferState != GonkBufferSlot::FREE ||
+ slot.mGraphicBuffer != NULL) {
+ maxBufferCount = s + 1;
+ break;
+ }
+ }
+
+ for (int s = 0; s < maxBufferCount; ++s) {
+ const GonkBufferSlot& slot(mSlots[s]);
+ const sp<GraphicBuffer>& buffer(slot.mGraphicBuffer);
+ result.appendFormat("%s%s[%02d:%p] state=%-8s", prefix,
+ (slot.mBufferState == GonkBufferSlot::ACQUIRED) ? ">" : " ",
+ s, buffer.get(),
+ GonkBufferSlot::bufferStateName(slot.mBufferState));
+
+ if (buffer != NULL) {
+ result.appendFormat(", %p [%4ux%4u:%4u,%3X]", buffer->handle,
+ buffer->width, buffer->height, buffer->stride,
+ buffer->format);
+ }
+
+ result.append("\n");
+ }
+}
+
+int GonkBufferQueueCore::getMinUndequeuedBufferCountLocked(bool async) const {
+ // If dequeueBuffer is allowed to error out, we don't have to add an
+ // extra buffer.
+ if (!mUseAsyncBuffer) {
+ return mMaxAcquiredBufferCount;
+ }
+
+ if (mDequeueBufferCannotBlock || async) {
+ return mMaxAcquiredBufferCount + 1;
+ }
+
+ return mMaxAcquiredBufferCount;
+}
+
+int GonkBufferQueueCore::getMinMaxBufferCountLocked(bool async) const {
+ return getMinUndequeuedBufferCountLocked(async) + 1;
+}
+
+int GonkBufferQueueCore::getMaxBufferCountLocked(bool async) const {
+ int minMaxBufferCount = getMinMaxBufferCountLocked(async);
+
+ int maxBufferCount = max(mDefaultMaxBufferCount, minMaxBufferCount);
+ if (mOverrideMaxBufferCount != 0) {
+ assert(mOverrideMaxBufferCount >= minMaxBufferCount);
+ maxBufferCount = mOverrideMaxBufferCount;
+ }
+
+ // Any buffers that are dequeued by the producer or sitting in the queue
+ // waiting to be consumed need to have their slots preserved. Such buffers
+ // will temporarily keep the max buffer count up until the slots no longer
+ // need to be preserved.
+ for (int s = maxBufferCount; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ GonkBufferSlot::BufferState state = mSlots[s].mBufferState;
+ if (state == GonkBufferSlot::QUEUED || state == GonkBufferSlot::DEQUEUED) {
+ maxBufferCount = s + 1;
+ }
+ }
+
+ return maxBufferCount;
+}
+
+status_t GonkBufferQueueCore::setDefaultMaxBufferCountLocked(int count) {
+ const int minBufferCount = 2;
+ if (count < minBufferCount || count > GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGV("setDefaultMaxBufferCount: invalid count %d, should be in "
+ "[%d, %d]",
+ count, minBufferCount, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ ALOGV("setDefaultMaxBufferCount: setting count to %d", count);
+ mDefaultMaxBufferCount = count;
+ mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+void GonkBufferQueueCore::freeBufferLocked(int slot) {
+ ALOGV("freeBufferLocked: slot %d", slot);
+
+ if (mSlots[slot].mTextureClient) {
+ mSlots[slot].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[slot].mTextureClient);
+ mSlots[slot].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+ mSlots[slot].mGraphicBuffer.clear();
+ if (mSlots[slot].mBufferState == GonkBufferSlot::ACQUIRED) {
+ mSlots[slot].mNeedsCleanupOnRelease = true;
+ }
+ mSlots[slot].mBufferState = GonkBufferSlot::FREE;
+ mSlots[slot].mFrameNumber = UINT32_MAX;
+ mSlots[slot].mAcquireCalled = false;
+
+ // Destroy fence as GonkBufferQueue now takes ownership
+ mSlots[slot].mFence = Fence::NO_FENCE;
+}
+
+void GonkBufferQueueCore::freeAllBuffersLocked() {
+ ALOGW_IF(!mQueue.isEmpty(),
+ "freeAllBuffersLocked called but mQueue is not empty");
+ mQueue.clear();
+ mBufferHasBeenQueued = false;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ freeBufferLocked(s);
+ }
+}
+
+bool GonkBufferQueueCore::stillTracking(const GonkBufferItem* item) const {
+ const GonkBufferSlot& slot = mSlots[item->mSlot];
+
+ ALOGV("stillTracking: item { slot=%d/%" PRIu64 " buffer=%p } "
+ "slot { slot=%d/%" PRIu64 " buffer=%p }",
+ item->mSlot, item->mFrameNumber,
+ (item->mGraphicBuffer.get() ? item->mGraphicBuffer->handle : 0),
+ item->mSlot, slot.mFrameNumber,
+ (slot.mGraphicBuffer.get() ? slot.mGraphicBuffer->handle : 0));
+
+ // Compare item with its original buffer slot. We can check the slot as
+ // the buffer would not be moved to a different slot by the producer.
+ return (slot.mGraphicBuffer != NULL) &&
+ (item->mGraphicBuffer->handle == slot.mGraphicBuffer->handle);
+}
+
+void GonkBufferQueueCore::waitWhileAllocatingLocked() const {
+ ATRACE_CALL();
+ while (mIsAllocating) {
+ mIsAllocatingCondition.wait(mMutex);
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.h
new file mode 100644
index 0000000000..936e11686c
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueCore.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUECORE_LL_H
+#define NATIVEWINDOW_GONKBUFFERQUEUECORE_LL_H
+
+#include "GonkBufferQueueDefs.h"
+#include "GonkBufferSlot.h"
+
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+#include <utils/NativeHandle.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+#include <utils/Trace.h>
+#include <utils/Vector.h>
+
+#include "mozilla/layers/TextureClient.h"
+
+#define ATRACE_BUFFER_INDEX(index)
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+namespace android {
+
+class GonkBufferItem;
+class IConsumerListener;
+class IGraphicBufferAlloc;
+class IProducerListener;
+
+class GonkBufferQueueCore : public virtual RefBase {
+
+ friend class GonkBufferQueueProducer;
+ friend class GonkBufferQueueConsumer;
+
+public:
+ // Used as a placeholder slot number when the value isn't pointing to an
+ // existing buffer.
+ enum { INVALID_BUFFER_SLOT = -1 }; // TODO: Extract from IGBC::BufferItem
+
+ // We reserve two slots in order to guarantee that the producer and
+ // consumer can run asynchronously.
+ enum { MAX_MAX_ACQUIRED_BUFFERS = GonkBufferQueueDefs::NUM_BUFFER_SLOTS - 2 };
+
+ // The default API number used to indicate that no producer is connected
+ enum { NO_CONNECTED_API = 0 };
+
+ typedef Vector<GonkBufferItem> Fifo;
+ typedef mozilla::layers::TextureClient TextureClient;
+
+ // GonkBufferQueueCore manages a pool of gralloc memory slots to be used by
+ // producers and consumers. allocator is used to allocate all the needed
+ // gralloc buffers.
+ GonkBufferQueueCore(const sp<IGraphicBufferAlloc>& allocator = NULL);
+ virtual ~GonkBufferQueueCore();
+
+private:
+ // Dump our state in a string
+ void dump(String8& result, const char* prefix) const;
+
+ int getSlotFromTextureClientLocked(TextureClient* client) const;
+
+ // getMinUndequeuedBufferCountLocked returns the minimum number of buffers
+ // that must remain in a state other than DEQUEUED. The async parameter
+ // tells whether we're in asynchronous mode.
+ int getMinUndequeuedBufferCountLocked(bool async) const;
+
+ // getMinMaxBufferCountLocked returns the minimum number of buffers allowed
+ // given the current GonkBufferQueue state. The async parameter tells whether
+ // we're in asynchonous mode.
+ int getMinMaxBufferCountLocked(bool async) const;
+
+ // getMaxBufferCountLocked returns the maximum number of buffers that can be
+ // allocated at once. This value depends on the following member variables:
+ //
+ // mDequeueBufferCannotBlock
+ // mMaxAcquiredBufferCount
+ // mDefaultMaxBufferCount
+ // mOverrideMaxBufferCount
+ // async parameter
+ //
+ // Any time one of these member variables is changed while a producer is
+ // connected, mDequeueCondition must be broadcast.
+ int getMaxBufferCountLocked(bool async) const;
+
+ // setDefaultMaxBufferCountLocked sets the maximum number of buffer slots
+ // that will be used if the producer does not override the buffer slot
+ // count. The count must be between 2 and NUM_BUFFER_SLOTS, inclusive. The
+ // initial default is 2.
+ status_t setDefaultMaxBufferCountLocked(int count);
+
+ // freeBufferLocked frees the GraphicBuffer and sync resources for the
+ // given slot.
+ void freeBufferLocked(int slot);
+
+ // freeAllBuffersLocked frees the GraphicBuffer and sync resources for
+ // all slots.
+ void freeAllBuffersLocked();
+
+ // stillTracking returns true iff the buffer item is still being tracked
+ // in one of the slots.
+ bool stillTracking(const GonkBufferItem* item) const;
+
+ // waitWhileAllocatingLocked blocks until mIsAllocating is false.
+ void waitWhileAllocatingLocked() const;
+
+ // mAllocator is the connection to SurfaceFlinger that is used to allocate
+ // new GraphicBuffer objects.
+ sp<IGraphicBufferAlloc> mAllocator;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkBufferQueueCore objects. It must be locked whenever any
+ // member variable is accessed.
+ mutable Mutex mMutex;
+
+ // mIsAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume image buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the
+ // consumerDisconnect method. A GonkBufferQueue that is abandoned will return
+ // the NO_INIT error from all IGraphicBufferProducer methods capable of
+ // returning an error.
+ bool mIsAbandoned;
+
+ // mConsumerControlledByApp indicates whether the connected consumer is
+ // controlled by the application.
+ bool mConsumerControlledByApp;
+
+ // mConsumerName is a string used to identify the GonkBufferQueue in log
+ // messages. It is set by the IGraphicBufferConsumer::setConsumerName
+ // method.
+ String8 mConsumerName;
+
+ // mConsumerListener is used to notify the connected consumer of
+ // asynchronous events that it may wish to react to. It is initially
+ // set to NULL and is written by consumerConnect and consumerDisconnect.
+ sp<IConsumerListener> mConsumerListener;
+
+ // mConsumerUsageBits contains flags that the consumer wants for
+ // GraphicBuffers.
+ uint32_t mConsumerUsageBits;
+
+ // mConnectedApi indicates the producer API that is currently connected
+ // to this GonkBufferQueue. It defaults to NO_CONNECTED_API, and gets updated
+ // by the connect and disconnect methods.
+ int mConnectedApi;
+
+ // mConnectedProducerToken is used to set a binder death notification on
+ // the producer.
+ sp<IProducerListener> mConnectedProducerListener;
+
+ // mSlots is an array of buffer slots that must be mirrored on the producer
+ // side. This allows buffer ownership to be transferred between the producer
+ // and consumer without sending a GraphicBuffer over Binder. The entire
+ // array is initialized to NULL at construction time, and buffers are
+ // allocated for a slot when requestBuffer is called with that slot's index.
+ GonkBufferQueueDefs::SlotsType mSlots;
+
+ // mQueue is a FIFO of queued buffers used in synchronous mode.
+ Fifo mQueue;
+
+ // mOverrideMaxBufferCount is the limit on the number of buffers that will
+ // be allocated at one time. This value is set by the producer by calling
+ // setBufferCount. The default is 0, which means that the producer doesn't
+ // care about the number of buffers in the pool. In that case,
+ // mDefaultMaxBufferCount is used as the limit.
+ int mOverrideMaxBufferCount;
+
+ // mDequeueCondition is a condition variable used for dequeueBuffer in
+ // synchronous mode.
+ mutable Condition mDequeueCondition;
+
+ // mUseAsyncBuffer indicates whether an extra buffer is used in async mode
+ // to prevent dequeueBuffer from blocking.
+ bool mUseAsyncBuffer;
+
+ // mDequeueBufferCannotBlock indicates whether dequeueBuffer is allowed to
+ // block. This flag is set during connect when both the producer and
+ // consumer are controlled by the application.
+ bool mDequeueBufferCannotBlock;
+
+ // mDefaultBufferFormat can be set so it will override the buffer format
+ // when it isn't specified in dequeueBuffer.
+ uint32_t mDefaultBufferFormat;
+
+ // mDefaultWidth holds the default width of allocated buffers. It is used
+ // in dequeueBuffer if a width and height of 0 are specified.
+ int mDefaultWidth;
+
+ // mDefaultHeight holds the default height of allocated buffers. It is used
+ // in dequeueBuffer if a width and height of 0 are specified.
+ int mDefaultHeight;
+
+ // mDefaultMaxBufferCount is the default limit on the number of buffers that
+ // will be allocated at one time. This default limit is set by the consumer.
+ // The limit (as opposed to the default limit) may be overriden by the
+ // producer.
+ int mDefaultMaxBufferCount;
+
+ // mMaxAcquiredBufferCount is the number of buffers that the consumer may
+ // acquire at one time. It defaults to 1, and can be changed by the consumer
+ // via setMaxAcquiredBufferCount, but this may only be done while no
+ // producer is connected to the GonkBufferQueue. This value is used to derive
+ // the value returned for the MIN_UNDEQUEUED_BUFFERS query to the producer.
+ int mMaxAcquiredBufferCount;
+
+ // mBufferHasBeenQueued is true once a buffer has been queued. It is reset
+ // when something causes all buffers to be freed (e.g., changing the buffer
+ // count).
+ bool mBufferHasBeenQueued;
+
+ // mFrameCounter is the free running counter, incremented on every
+ // successful queueBuffer call and buffer allocation.
+ uint64_t mFrameCounter;
+
+ // mTransformHint is used to optimize for screen rotations.
+ uint32_t mTransformHint;
+
+ // mSidebandStream is a handle to the sideband buffer stream, if any
+ sp<NativeHandle> mSidebandStream;
+
+ // mIsAllocating indicates whether a producer is currently trying to allocate buffers (which
+ // releases mMutex while doing the allocation proper). Producers should not modify any of the
+ // FREE slots while this is true. mIsAllocatingCondition is signaled when this value changes to
+ // false.
+ bool mIsAllocating;
+
+ // mIsAllocatingCondition is a condition variable used by producers to wait until mIsAllocating
+ // becomes false.
+ mutable Condition mIsAllocatingCondition;
+}; // class GonkBufferQueueCore
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueDefs.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueDefs.h
new file mode 100644
index 0000000000..60085706f5
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueDefs.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_BUFFERQUEUECOREDEFS_H
+#define NATIVEWINDOW_BUFFERQUEUECOREDEFS_H
+
+#include "GonkBufferSlot.h"
+
+namespace android {
+ class GonkBufferQueueCore;
+
+ namespace GonkBufferQueueDefs {
+ // GonkBufferQueue will keep track of at most this value of buffers.
+ // Attempts at runtime to increase the number of buffers past this
+ // will fail.
+ enum { NUM_BUFFER_SLOTS = 64 };
+
+ typedef GonkBufferSlot SlotsType[NUM_BUFFER_SLOTS];
+ } // namespace GonkBufferQueueDefs
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.cpp
new file mode 100644
index 0000000000..649d06bee8
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#define LOG_TAG "GonkBufferQueue"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#define LOG_NDEBUG 0
+
+#include "GonkBufferQueue.h"
+#include "GonkBufferQueueConsumer.h"
+#include "GonkBufferQueueCore.h"
+#include "GonkBufferQueueProducer.h"
+
+namespace android {
+
+GonkBufferQueue::ProxyConsumerListener::ProxyConsumerListener(
+ const wp<ConsumerListener>& consumerListener):
+ mConsumerListener(consumerListener) {}
+
+GonkBufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {}
+
+#if ANDROID_VERSION == 21
+void GonkBufferQueue::ProxyConsumerListener::onFrameAvailable() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameAvailable();
+ }
+}
+#else
+void GonkBufferQueue::ProxyConsumerListener::onFrameAvailable(const ::android::BufferItem& item) {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameAvailable(item);
+ }
+}
+
+void GonkBufferQueue::ProxyConsumerListener::onFrameReplaced(const ::android::BufferItem& item) {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onFrameReplaced(item);
+ }
+}
+#endif
+
+void GonkBufferQueue::ProxyConsumerListener::onBuffersReleased() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+}
+
+void GonkBufferQueue::ProxyConsumerListener::onSidebandStreamChanged() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onSidebandStreamChanged();
+ }
+}
+
+void GonkBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
+ sp<IGonkGraphicBufferConsumer>* outConsumer,
+ const sp<IGraphicBufferAlloc>& allocator) {
+ LOG_ALWAYS_FATAL_IF(outProducer == NULL,
+ "GonkBufferQueue: outProducer must not be NULL");
+ LOG_ALWAYS_FATAL_IF(outConsumer == NULL,
+ "GonkBufferQueue: outConsumer must not be NULL");
+
+ sp<GonkBufferQueueCore> core(new GonkBufferQueueCore(allocator));
+ LOG_ALWAYS_FATAL_IF(core == NULL,
+ "GonkBufferQueue: failed to create GonkBufferQueueCore");
+
+ sp<IGraphicBufferProducer> producer(new GonkBufferQueueProducer(core));
+ LOG_ALWAYS_FATAL_IF(producer == NULL,
+ "GonkBufferQueue: failed to create GonkBufferQueueProducer");
+
+ sp<IGonkGraphicBufferConsumer> consumer(new GonkBufferQueueConsumer(core));
+ LOG_ALWAYS_FATAL_IF(consumer == NULL,
+ "GonkBufferQueue: failed to create GonkBufferQueueConsumer");
+
+ *outProducer = producer;
+ *outConsumer = consumer;
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.h
new file mode 100644
index 0000000000..b1b4e06b52
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueLL.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUE_LL_H
+#define NATIVEWINDOW_GONKBUFFERQUEUE_LL_H
+
+#include "GonkBufferQueueDefs.h"
+#include "IGonkGraphicBufferConsumerLL.h"
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/IConsumerListener.h>
+
+// These are only required to keep other parts of the framework with incomplete
+// dependencies building successfully
+#include <gui/IGraphicBufferAlloc.h>
+
+namespace android {
+
+class GonkBufferQueue {
+public:
+ // GonkBufferQueue will keep track of at most this value of buffers.
+ // Attempts at runtime to increase the number of buffers past this will fail.
+ enum { NUM_BUFFER_SLOTS = GonkBufferQueueDefs::NUM_BUFFER_SLOTS };
+ // Used as a placeholder slot# when the value isn't pointing to an existing buffer.
+ enum { INVALID_BUFFER_SLOT = IGonkGraphicBufferConsumer::BufferItem::INVALID_BUFFER_SLOT };
+ // Alias to <IGonkGraphicBufferConsumer.h> -- please scope from there in future code!
+ enum {
+ NO_BUFFER_AVAILABLE = IGonkGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
+ PRESENT_LATER = IGonkGraphicBufferConsumer::PRESENT_LATER,
+ };
+
+ // When in async mode we reserve two slots in order to guarantee that the
+ // producer and consumer can run asynchronously.
+ enum { MAX_MAX_ACQUIRED_BUFFERS = NUM_BUFFER_SLOTS - 2 };
+
+ // for backward source compatibility
+ typedef ::android::ConsumerListener ConsumerListener;
+ typedef IGonkGraphicBufferConsumer::BufferItem BufferItem;
+
+ // ProxyConsumerListener is a ConsumerListener implementation that keeps a weak
+ // reference to the actual consumer object. It forwards all calls to that
+ // consumer object so long as it exists.
+ //
+ // This class exists to avoid having a circular reference between the
+ // GonkBufferQueue object and the consumer object. The reason this can't be a weak
+ // reference in the GonkBufferQueue class is because we're planning to expose the
+ // consumer side of a GonkBufferQueue as a binder interface, which doesn't support
+ // weak references.
+ class ProxyConsumerListener : public BnConsumerListener {
+ public:
+ ProxyConsumerListener(const wp<ConsumerListener>& consumerListener);
+ virtual ~ProxyConsumerListener();
+#if ANDROID_VERSION == 21
+ virtual void onFrameAvailable();
+#else
+ virtual void onFrameAvailable(const ::android::BufferItem& item);
+ virtual void onFrameReplaced(const ::android::BufferItem& item);
+#endif
+ virtual void onBuffersReleased();
+ virtual void onSidebandStreamChanged();
+ private:
+ // mConsumerListener is a weak reference to the IConsumerListener. This is
+ // the raison d'etre of ProxyConsumerListener.
+ wp<ConsumerListener> mConsumerListener;
+ };
+
+ // GonkBufferQueue manages a pool of gralloc memory slots to be used by
+ // producers and consumers. allocator is used to allocate all the
+ // needed gralloc buffers.
+ static void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
+ sp<IGonkGraphicBufferConsumer>* outConsumer,
+ const sp<IGraphicBufferAlloc>& allocator = NULL);
+
+private:
+ GonkBufferQueue(); // Create through createBufferQueue
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_GONKBUFFERQUEUE_LL_H
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.cpp
new file mode 100644
index 0000000000..d3436756fb
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.cpp
@@ -0,0 +1,886 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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 <inttypes.h>
+
+#define LOG_TAG "GonkBufferQueueProducer"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include "GonkBufferItem.h"
+#include "GonkBufferQueueCore.h"
+#include "GonkBufferQueueProducer.h"
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/IProducerListener.h>
+
+#include <cutils/compiler.h>
+#include <utils/Log.h>
+#include <utils/Trace.h>
+
+#include "mozilla/layers/GrallocTextureClient.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/TextureClient.h"
+
+namespace android {
+
+GonkBufferQueueProducer::GonkBufferQueueProducer(const sp<GonkBufferQueueCore>& core) :
+ mCore(core),
+ mSlots(core->mSlots),
+ mConsumerName(),
+ mSynchronousMode(true),
+ mStickyTransform(0) {}
+
+GonkBufferQueueProducer::~GonkBufferQueueProducer() {}
+
+status_t GonkBufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
+ ATRACE_CALL();
+ ALOGV("requestBuffer: slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("requestBuffer: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("requestBuffer: slot index %d out of range [0, %d)",
+ slot, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::DEQUEUED) {
+ ALOGE("requestBuffer: slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+
+ mSlots[slot].mRequestBufferCalled = true;
+ *buf = mSlots[slot].mGraphicBuffer;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::setBufferCount(int bufferCount) {
+ ATRACE_CALL();
+ ALOGV("setBufferCount: count = %d", bufferCount);
+
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("setBufferCount: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (bufferCount > GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("setBufferCount: bufferCount %d too large (max %d)",
+ bufferCount, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ // There must be no dequeued buffers when changing the buffer count.
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == GonkBufferSlot::DEQUEUED) {
+ ALOGE("setBufferCount: buffer owned by producer");
+ return BAD_VALUE;
+ }
+ }
+
+ if (bufferCount == 0) {
+ mCore->mOverrideMaxBufferCount = 0;
+ mCore->mDequeueCondition.broadcast();
+ return NO_ERROR;
+ }
+
+ const int minBufferSlots = mCore->getMinMaxBufferCountLocked(false);
+ if (bufferCount < minBufferSlots) {
+ ALOGE("setBufferCount: requested buffer count %d is less than "
+ "minimum %d", bufferCount, minBufferSlots);
+ return BAD_VALUE;
+ }
+
+ // Here we are guaranteed that the producer doesn't have any dequeued
+ // buffers and will release all of its buffer references. We don't
+ // clear the queue, however, so that currently queued buffers still
+ // get displayed.
+ mCore->freeAllBuffersLocked();
+ mCore->mOverrideMaxBufferCount = bufferCount;
+ mCore->mDequeueCondition.broadcast();
+ listener = mCore->mConsumerListener;
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::waitForFreeSlotThenRelock(const char* caller,
+ bool async, int* found, status_t* returnFlags) const {
+ bool tryAgain = true;
+ while (tryAgain) {
+ if (mCore->mIsAbandoned) {
+ ALOGE("%s: GonkBufferQueue has been abandoned", caller);
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = mCore->getMaxBufferCountLocked(async);
+ if (async && mCore->mOverrideMaxBufferCount) {
+ // FIXME: Some drivers are manually setting the buffer count
+ // (which they shouldn't), so we do this extra test here to
+ // handle that case. This is TEMPORARY until we get this fixed.
+ if (mCore->mOverrideMaxBufferCount < maxBufferCount) {
+ ALOGE("%s: async mode is invalid with buffer count override",
+ caller);
+ return BAD_VALUE;
+ }
+ }
+
+ // Free up any buffers that are in slots beyond the max buffer count
+ //for (int s = maxBufferCount; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ // assert(mSlots[s].mBufferState == GonkBufferSlot::FREE);
+ // if (mSlots[s].mGraphicBuffer != NULL) {
+ // mCore->freeBufferLocked(s);
+ // *returnFlags |= RELEASE_ALL_BUFFERS;
+ // }
+ //}
+
+ // Look for a free buffer to give to the client
+ *found = GonkBufferQueueCore::INVALID_BUFFER_SLOT;
+ int dequeuedCount = 0;
+ int acquiredCount = 0;
+ for (int s = 0; s < maxBufferCount; ++s) {
+ switch (mSlots[s].mBufferState) {
+ case GonkBufferSlot::DEQUEUED:
+ ++dequeuedCount;
+ break;
+ case GonkBufferSlot::ACQUIRED:
+ ++acquiredCount;
+ break;
+ case GonkBufferSlot::FREE:
+ // We return the oldest of the free buffers to avoid
+ // stalling the producer if possible, since the consumer
+ // may still have pending reads of in-flight buffers
+ if (*found == GonkBufferQueueCore::INVALID_BUFFER_SLOT ||
+ mSlots[s].mFrameNumber < mSlots[*found].mFrameNumber) {
+ *found = s;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Producers are not allowed to dequeue more than one buffer if they
+ // did not set a buffer count
+ if (!mCore->mOverrideMaxBufferCount && dequeuedCount) {
+ ALOGE("%s: can't dequeue multiple buffers without setting the "
+ "buffer count", caller);
+ return INVALID_OPERATION;
+ }
+
+ // See whether a buffer has been queued since the last
+ // setBufferCount so we know whether to perform the min undequeued
+ // buffers check below
+ if (mCore->mBufferHasBeenQueued) {
+ // Make sure the producer is not trying to dequeue more buffers
+ // than allowed
+ const int newUndequeuedCount =
+ maxBufferCount - (dequeuedCount + 1);
+ const int minUndequeuedCount =
+ mCore->getMinUndequeuedBufferCountLocked(async);
+ if (newUndequeuedCount < minUndequeuedCount) {
+ ALOGE("%s: min undequeued buffer count (%d) exceeded "
+ "(dequeued=%d undequeued=%d)",
+ caller, minUndequeuedCount,
+ dequeuedCount, newUndequeuedCount);
+ return INVALID_OPERATION;
+ }
+ }
+
+ // If we disconnect and reconnect quickly, we can be in a state where
+ // our slots are empty but we have many buffers in the queue. This can
+ // cause us to run out of memory if we outrun the consumer. Wait here if
+ // it looks like we have too many buffers queued up.
+ bool tooManyBuffers = mCore->mQueue.size()
+ > static_cast<size_t>(maxBufferCount);
+ if (tooManyBuffers) {
+ ALOGV("%s: queue size is %zu, waiting", caller,
+ mCore->mQueue.size());
+ }
+
+ // If no buffer is found, or if the queue has too many buffers
+ // outstanding, wait for a buffer to be acquired or released, or for the
+ // max buffer count to change.
+ tryAgain = (*found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) ||
+ tooManyBuffers;
+ if (tryAgain) {
+ // Return an error if we're in non-blocking mode (producer and
+ // consumer are controlled by the application).
+ // However, the consumer is allowed to briefly acquire an extra
+ // buffer (which could cause us to have to wait here), which is
+ // okay, since it is only used to implement an atomic acquire +
+ // release (e.g., in GLConsumer::updateTexImage())
+ if (mCore->mDequeueBufferCannotBlock &&
+ (acquiredCount <= mCore->mMaxAcquiredBufferCount)) {
+ return WOULD_BLOCK;
+ }
+ mCore->mDequeueCondition.wait(mCore->mMutex);
+ }
+ } // while (tryAgain)
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::dequeueBuffer(int *outSlot,
+ sp<android::Fence> *outFence, bool async,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage) {
+ ATRACE_CALL();
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+ mConsumerName = mCore->mConsumerName;
+ } // Autolock scope
+
+ ALOGV("dequeueBuffer: async=%s w=%u h=%u format=%#x, usage=%#x",
+ async ? "true" : "false", width, height, format, usage);
+
+ if ((width && !height) || (!width && height)) {
+ ALOGE("dequeueBuffer: invalid size: w=%u h=%u", width, height);
+ return BAD_VALUE;
+ }
+
+ status_t returnFlags = NO_ERROR;
+ // Reset slot
+ *outSlot = GonkBufferQueueCore::INVALID_BUFFER_SLOT;
+
+ bool attachedByConsumer = false;
+
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ if (format == 0) {
+ format = mCore->mDefaultBufferFormat;
+ }
+
+ // Enable the usage bits the consumer requested
+ usage |= mCore->mConsumerUsageBits;
+
+ int found;
+ status_t status = waitForFreeSlotThenRelock("dequeueBuffer", async,
+ &found, &returnFlags);
+ if (status != NO_ERROR) {
+ return status;
+ }
+
+ // This should not happen
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) {
+ ALOGE("dequeueBuffer: no available buffer slots");
+ return -EBUSY;
+ }
+
+ *outSlot = found;
+
+ attachedByConsumer = mSlots[found].mAttachedByConsumer;
+
+ const bool useDefaultSize = !width && !height;
+ if (useDefaultSize) {
+ width = mCore->mDefaultWidth;
+ height = mCore->mDefaultHeight;
+ }
+
+ mSlots[found].mBufferState = GonkBufferSlot::DEQUEUED;
+
+ const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
+ if ((buffer == NULL) ||
+ (static_cast<uint32_t>(buffer->width) != width) ||
+ (static_cast<uint32_t>(buffer->height) != height) ||
+ (static_cast<uint32_t>(buffer->format) != format) ||
+ ((static_cast<uint32_t>(buffer->usage) & usage) != usage))
+ {
+ mSlots[found].mAcquireCalled = false;
+ mSlots[found].mGraphicBuffer = NULL;
+ mSlots[found].mRequestBufferCalled = false;
+ mSlots[found].mFence = Fence::NO_FENCE;
+
+ if (mSlots[found].mTextureClient) {
+ mSlots[found].mTextureClient->ClearRecycleCallback();
+ // release TextureClient in ImageBridge thread
+ RefPtr<TextureClientReleaseTask> task =
+ MakeAndAddRef<TextureClientReleaseTask>(mSlots[found].mTextureClient);
+ mSlots[found].mTextureClient = NULL;
+ ImageBridgeChild::GetSingleton()->GetMessageLoop()->PostTask(task.forget());
+ }
+
+ returnFlags |= BUFFER_NEEDS_REALLOCATION;
+ }
+
+ if (CC_UNLIKELY(mSlots[found].mFence == NULL)) {
+ ALOGE("dequeueBuffer: about to return a NULL fence - "
+ "slot=%d w=%d h=%d format=%u",
+ found, buffer->width, buffer->height, buffer->format);
+ }
+
+ *outFence = mSlots[found].mFence;
+ mSlots[found].mFence = Fence::NO_FENCE;
+ } // Autolock scope
+
+ if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
+ RefPtr<LayersIPCChannel> allocator = ImageBridgeChild::GetSingleton();
+ usage |= GraphicBuffer::USAGE_HW_TEXTURE;
+ GrallocTextureData* texData = GrallocTextureData::Create(IntSize(width,height), format,
+ gfx::BackendType::NONE,
+ usage, allocator);
+ if (!texData) {
+ ALOGE("dequeueBuffer: failed to alloc gralloc buffer");
+ return -ENOMEM;
+ }
+ RefPtr<TextureClient> textureClient = TextureClient::CreateWithData(
+ texData, TextureFlags::RECYCLE | TextureFlags::DEALLOCATE_CLIENT, allocator);
+
+ sp<GraphicBuffer> graphicBuffer = texData->GetGraphicBuffer();
+
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("dequeueBuffer: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ mSlots[*outSlot].mFrameNumber = UINT32_MAX;
+ mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
+ mSlots[*outSlot].mTextureClient = textureClient;
+ } // Autolock scope
+ }
+
+ if (attachedByConsumer) {
+ returnFlags |= BUFFER_NEEDS_REALLOCATION;
+ }
+
+ ALOGV("dequeueBuffer: returning slot=%d/%" PRIu64 " buf=%p flags=%#x",
+ *outSlot,
+ mSlots[*outSlot].mFrameNumber,
+ mSlots[*outSlot].mGraphicBuffer->handle, returnFlags);
+
+ return returnFlags;
+}
+
+status_t GonkBufferQueueProducer::detachBuffer(int slot) {
+ ATRACE_CALL();
+ ATRACE_BUFFER_INDEX(slot);
+ ALOGV("detachBuffer(P): slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("detachBuffer(P): GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("detachBuffer(P): slot index %d out of range [0, %d)",
+ slot, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::DEQUEUED) {
+ ALOGE("detachBuffer(P): slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ } else if (!mSlots[slot].mRequestBufferCalled) {
+ ALOGE("detachBuffer(P): buffer in slot %d has not been requested",
+ slot);
+ return BAD_VALUE;
+ }
+
+ mCore->freeBufferLocked(slot);
+ mCore->mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence) {
+ ATRACE_CALL();
+
+ if (outBuffer == NULL) {
+ ALOGE("detachNextBuffer: outBuffer must not be NULL");
+ return BAD_VALUE;
+ } else if (outFence == NULL) {
+ ALOGE("detachNextBuffer: outFence must not be NULL");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("detachNextBuffer: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ // Find the oldest valid slot
+ int found = GonkBufferQueueCore::INVALID_BUFFER_SLOT;
+ for (int s = 0; s < GonkBufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == GonkBufferSlot::FREE &&
+ mSlots[s].mGraphicBuffer != NULL) {
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT ||
+ mSlots[s].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = s;
+ }
+ }
+ }
+
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) {
+ return NO_MEMORY;
+ }
+
+ ALOGV("detachNextBuffer detached slot %d", found);
+
+ *outBuffer = mSlots[found].mGraphicBuffer;
+ *outFence = mSlots[found].mFence;
+ mCore->freeBufferLocked(found);
+
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::attachBuffer(int* outSlot,
+ const sp<android::GraphicBuffer>& buffer) {
+ ATRACE_CALL();
+
+ if (outSlot == NULL) {
+ ALOGE("attachBuffer(P): outSlot must not be NULL");
+ return BAD_VALUE;
+ } else if (buffer == NULL) {
+ ALOGE("attachBuffer(P): cannot attach NULL buffer");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ status_t returnFlags = NO_ERROR;
+ int found;
+ // TODO: Should we provide an async flag to attachBuffer? It seems
+ // unlikely that buffers which we are attaching to a GonkBufferQueue will
+ // be asynchronous (droppable), but it may not be impossible.
+ status_t status = waitForFreeSlotThenRelock("attachBuffer(P)", false,
+ &found, &returnFlags);
+ if (status != NO_ERROR) {
+ return status;
+ }
+
+ // This should not happen
+ if (found == GonkBufferQueueCore::INVALID_BUFFER_SLOT) {
+ ALOGE("attachBuffer(P): no available buffer slots");
+ return -EBUSY;
+ }
+
+ *outSlot = found;
+ ATRACE_BUFFER_INDEX(*outSlot);
+ ALOGV("attachBuffer(P): returning slot %d flags=%#x",
+ *outSlot, returnFlags);
+
+ mSlots[*outSlot].mGraphicBuffer = buffer;
+ mSlots[*outSlot].mBufferState = GonkBufferSlot::DEQUEUED;
+ mSlots[*outSlot].mFence = Fence::NO_FENCE;
+ mSlots[*outSlot].mRequestBufferCalled = true;
+
+ return returnFlags;
+}
+
+status_t GonkBufferQueueProducer::setSynchronousMode(bool enabled) {
+ ALOGV("setSynchronousMode: enabled=%d", enabled);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("setSynchronousMode: BufferQueue has been abandoned!");
+ return NO_INIT;
+ }
+
+ if (mSynchronousMode != enabled) {
+ mSynchronousMode = enabled;
+ mCore->mDequeueCondition.broadcast();
+ }
+ return OK;
+}
+
+status_t GonkBufferQueueProducer::queueBuffer(int slot,
+ const QueueBufferInput &input, QueueBufferOutput *output) {
+ ATRACE_CALL();
+
+ int64_t timestamp;
+ bool isAutoTimestamp;
+ Rect crop;
+ int scalingMode;
+ uint32_t transform;
+ uint32_t stickyTransform;
+ bool async;
+ sp<Fence> fence;
+ input.deflate(&timestamp, &isAutoTimestamp, &crop, &scalingMode, &transform,
+ &async, &fence, &stickyTransform);
+
+ if (fence == NULL) {
+ ALOGE("queueBuffer: fence is NULL");
+ // Temporary workaround for b/17946343: soldier-on instead of returning an error. This
+ // prevents the client from dying, at the risk of visible corruption due to hwcomposer
+ // reading the buffer before the producer is done rendering it. Unless the buffer is the
+ // last frame of an animation, the corruption will be transient.
+ fence = Fence::NO_FENCE;
+ // return BAD_VALUE;
+ }
+
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
+ case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
+ break;
+ default:
+ ALOGE("queueBuffer: unknown scaling mode %d", scalingMode);
+ return BAD_VALUE;
+ }
+
+ GonkBufferItem item;
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("queueBuffer: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = mCore->getMaxBufferCountLocked(async);
+ if (async && mCore->mOverrideMaxBufferCount) {
+ // FIXME: Some drivers are manually setting the buffer count
+ // (which they shouldn't), so we do this extra test here to
+ // handle that case. This is TEMPORARY until we get this fixed.
+ if (mCore->mOverrideMaxBufferCount < maxBufferCount) {
+ ALOGE("queueBuffer: async mode is invalid with "
+ "buffer count override");
+ return BAD_VALUE;
+ }
+ }
+
+ if (slot < 0 || slot >= maxBufferCount) {
+ ALOGE("queueBuffer: slot index %d out of range [0, %d)",
+ slot, maxBufferCount);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::DEQUEUED) {
+ ALOGE("queueBuffer: slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ } else if (!mSlots[slot].mRequestBufferCalled) {
+ ALOGE("queueBuffer: slot %d was queued without requesting "
+ "a buffer", slot);
+ return BAD_VALUE;
+ }
+
+ ALOGV("queueBuffer: slot=%d/%" PRIu64 " time=%" PRIu64
+ " crop=[%d,%d,%d,%d] transform=%#x scale=%s",
+ slot, mCore->mFrameCounter + 1, timestamp,
+ crop.left, crop.top, crop.right, crop.bottom,
+ transform, GonkBufferItem::scalingModeName(scalingMode));
+
+ const sp<GraphicBuffer>& graphicBuffer(mSlots[slot].mGraphicBuffer);
+ Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
+ Rect croppedRect;
+ crop.intersect(bufferRect, &croppedRect);
+ if (croppedRect != crop) {
+ ALOGE("queueBuffer: crop rect is not contained within the "
+ "buffer in slot %d", slot);
+ return BAD_VALUE;
+ }
+
+ mSlots[slot].mFence = fence;
+ mSlots[slot].mBufferState = GonkBufferSlot::QUEUED;
+ ++mCore->mFrameCounter;
+ mSlots[slot].mFrameNumber = mCore->mFrameCounter;
+
+ item.mAcquireCalled = mSlots[slot].mAcquireCalled;
+ item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
+ item.mCrop = crop;
+ item.mTransform = transform & ~NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+ item.mTransformToDisplayInverse =
+ bool(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
+ item.mScalingMode = scalingMode;
+ item.mTimestamp = timestamp;
+ item.mIsAutoTimestamp = isAutoTimestamp;
+ item.mFrameNumber = mCore->mFrameCounter;
+ item.mSlot = slot;
+ item.mFence = fence;
+ item.mIsDroppable = mCore->mDequeueBufferCannotBlock || async;
+
+ mStickyTransform = stickyTransform;
+
+ if (mCore->mQueue.empty()) {
+ // When the queue is empty, we can ignore mDequeueBufferCannotBlock
+ // and simply queue this buffer
+ mCore->mQueue.push_back(item);
+ listener = mCore->mConsumerListener;
+ } else {
+ // When the queue is not empty, we need to look at the front buffer
+ // state to see if we need to replace it
+ GonkBufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
+ if (front->mIsDroppable || !mSynchronousMode) {
+ // If the front queued buffer is still being tracked, we first
+ // mark it as freed
+ if (mCore->stillTracking(front)) {
+ mSlots[front->mSlot].mBufferState = GonkBufferSlot::FREE;
+ // Reset the frame number of the freed buffer so that it is
+ // the first in line to be dequeued again
+ mSlots[front->mSlot].mFrameNumber = 0;
+ }
+ // Overwrite the droppable buffer with the incoming one
+ *front = item;
+ listener = mCore->mConsumerListener;
+ } else {
+ mCore->mQueue.push_back(item);
+ listener = mCore->mConsumerListener;
+ }
+ }
+
+ mCore->mBufferHasBeenQueued = true;
+ mCore->mDequeueCondition.broadcast();
+
+ output->inflate(mCore->mDefaultWidth, mCore->mDefaultHeight,
+ mCore->mTransformHint, mCore->mQueue.size());
+
+ item.mGraphicBuffer.clear();
+ item.mSlot = GonkBufferItem::INVALID_BUFFER_SLOT;
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+#if ANDROID_VERSION == 21
+ listener->onFrameAvailable();
+#else
+ listener->onFrameAvailable(reinterpret_cast<::android::BufferItem&>(item));
+#endif
+ }
+
+ return NO_ERROR;
+}
+
+void GonkBufferQueueProducer::cancelBuffer(int slot, const sp<Fence>& fence) {
+ ATRACE_CALL();
+ ALOGV("cancelBuffer: slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("cancelBuffer: GonkBufferQueue has been abandoned");
+ return;
+ }
+
+ if (slot < 0 || slot >= GonkBufferQueueDefs::NUM_BUFFER_SLOTS) {
+ ALOGE("cancelBuffer: slot index %d out of range [0, %d)",
+ slot, GonkBufferQueueDefs::NUM_BUFFER_SLOTS);
+ return;
+ } else if (mSlots[slot].mBufferState != GonkBufferSlot::DEQUEUED) {
+ ALOGE("cancelBuffer: slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return;
+ } else if (fence == NULL) {
+ ALOGE("cancelBuffer: fence is NULL");
+ return;
+ }
+
+ mSlots[slot].mBufferState = GonkBufferSlot::FREE;
+ mSlots[slot].mFrameNumber = 0;
+ mSlots[slot].mFence = fence;
+ mCore->mDequeueCondition.broadcast();
+}
+
+int GonkBufferQueueProducer::query(int what, int *outValue) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (outValue == NULL) {
+ ALOGE("query: outValue was NULL");
+ return BAD_VALUE;
+ }
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("query: GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ int value;
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ value = mCore->mDefaultWidth;
+ break;
+ case NATIVE_WINDOW_HEIGHT:
+ value = mCore->mDefaultHeight;
+ break;
+ case NATIVE_WINDOW_FORMAT:
+ value = mCore->mDefaultBufferFormat;
+ break;
+ case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+ value = mCore->getMinUndequeuedBufferCountLocked(false);
+ break;
+ case NATIVE_WINDOW_STICKY_TRANSFORM:
+ value = static_cast<int>(mStickyTransform);
+ break;
+ case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+ value = (mCore->mQueue.size() > 1);
+ break;
+ case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
+ value = mCore->mConsumerUsageBits;
+ break;
+ default:
+ return BAD_VALUE;
+ }
+
+ ALOGV("query: %d? %d", what, value);
+ *outValue = value;
+ return NO_ERROR;
+}
+
+status_t GonkBufferQueueProducer::connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp, QueueBufferOutput *output) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+ mConsumerName = mCore->mConsumerName;
+ ALOGV("connect(P): api=%d producerControlledByApp=%s", api,
+ producerControlledByApp ? "true" : "false");
+
+ if (mCore->mIsAbandoned) {
+ ALOGE("connect(P): GonkBufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (mCore->mConsumerListener == NULL) {
+ ALOGE("connect(P): GonkBufferQueue has no consumer");
+ return NO_INIT;
+ }
+
+ if (output == NULL) {
+ ALOGE("connect(P): output was NULL");
+ return BAD_VALUE;
+ }
+
+ if (mCore->mConnectedApi != GonkBufferQueueCore::NO_CONNECTED_API) {
+ ALOGE("connect(P): already connected (cur=%d req=%d)", mCore->mConnectedApi,
+ api);
+ return BAD_VALUE;
+ }
+
+ int status = NO_ERROR;
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ mCore->mConnectedApi = api;
+ output->inflate(mCore->mDefaultWidth, mCore->mDefaultHeight,
+ mCore->mTransformHint, mCore->mQueue.size());
+
+ // Set up a death notification so that we can disconnect
+ // automatically if the remote producer dies
+ if (listener != NULL &&
+ listener->asBinder()->remoteBinder() != NULL) {
+ status = listener->asBinder()->linkToDeath(
+ static_cast<IBinder::DeathRecipient*>(this));
+ if (status != NO_ERROR) {
+ ALOGE("connect(P): linkToDeath failed: %s (%d)",
+ strerror(-status), status);
+ }
+ }
+ mCore->mConnectedProducerListener = listener;
+ break;
+ default:
+ ALOGE("connect(P): unknown API %d", api);
+ status = BAD_VALUE;
+ break;
+ }
+
+ mCore->mBufferHasBeenQueued = false;
+ mCore->mDequeueBufferCannotBlock =
+ mCore->mConsumerControlledByApp && producerControlledByApp;
+
+ return status;
+}
+
+status_t GonkBufferQueueProducer::disconnect(int api) {
+ ATRACE_CALL();
+ ALOGV("disconnect(P): api %d", api);
+
+ int status = NO_ERROR;
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->waitWhileAllocatingLocked();
+
+ if (mCore->mIsAbandoned) {
+ // It's not really an error to disconnect after the surface has
+ // been abandoned; it should just be a no-op.
+ return NO_ERROR;
+ }
+
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ if (mCore->mConnectedApi == api) {
+ mCore->freeAllBuffersLocked();
+ mCore->mConnectedApi = GonkBufferQueueCore::NO_CONNECTED_API;
+ mCore->mSidebandStream.clear();
+ mCore->mDequeueCondition.broadcast();
+ listener = mCore->mConsumerListener;
+ } else {
+ ALOGE("disconnect(P): connected to another API "
+ "(cur=%d req=%d)", mCore->mConnectedApi, api);
+ status = BAD_VALUE;
+ }
+ break;
+ default:
+ ALOGE("disconnect(P): unknown API %d", api);
+ status = BAD_VALUE;
+ break;
+ }
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return status;
+}
+
+status_t GonkBufferQueueProducer::setSidebandStream(const sp<NativeHandle>& stream) {
+ return INVALID_OPERATION;
+}
+
+void GonkBufferQueueProducer::allocateBuffers(bool async, uint32_t width,
+ uint32_t height, uint32_t format, uint32_t usage) {
+ ALOGE("allocateBuffers: no op");
+}
+
+void GonkBufferQueueProducer::binderDied(const wp<android::IBinder>& /* who */) {
+ // If we're here, it means that a producer we were connected to died.
+ // We're guaranteed that we are still connected to it because we remove
+ // this callback upon disconnect. It's therefore safe to read mConnectedApi
+ // without synchronization here.
+ int api = mCore->mConnectedApi;
+ disconnect(api);
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.h
new file mode 100644
index 0000000000..a1a22416ad
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferQueueProducer.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERQUEUEPRODUCER_LL_H
+#define NATIVEWINDOW_GONKBUFFERQUEUEPRODUCER_LL_H
+
+#include "GonkBufferQueueDefs.h"
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+
+class GonkBufferQueueProducer : public BnGraphicBufferProducer,
+ private IBinder::DeathRecipient {
+public:
+ friend class GonkBufferQueue; // Needed to access binderDied
+
+ GonkBufferQueueProducer(const sp<GonkBufferQueueCore>& core);
+ virtual ~GonkBufferQueueProducer();
+
+ // requestBuffer returns the GraphicBuffer for slot N.
+ //
+ // In normal operation, this is called the first time slot N is returned
+ // by dequeueBuffer. It must be called again if dequeueBuffer returns
+ // flags indicating that previously-returned buffers are no longer valid.
+ virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
+
+ // setBufferCount updates the number of available buffer slots. If this
+ // method succeeds, buffer slots will be both unallocated and owned by
+ // the GonkBufferQueue object (i.e. they are not owned by the producer or
+ // consumer).
+ //
+ // This will fail if the producer has dequeued any buffers, or if
+ // bufferCount is invalid. bufferCount must generally be a value
+ // between the minimum undequeued buffer count (exclusive) and NUM_BUFFER_SLOTS
+ // (inclusive). It may also be set to zero (the default) to indicate
+ // that the producer does not wish to set a value. The minimum value
+ // can be obtained by calling query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ // ...).
+ //
+ // This may only be called by the producer. The consumer will be told
+ // to discard buffers through the onBuffersReleased callback.
+ virtual status_t setBufferCount(int bufferCount);
+
+ // dequeueBuffer gets the next buffer slot index for the producer to use.
+ // If a buffer slot is available then that slot index is written to the
+ // location pointed to by the buf argument and a status of OK is returned.
+ // If no slot is available then a status of -EBUSY is returned and buf is
+ // unmodified.
+ //
+ // The outFence parameter will be updated to hold the fence associated with
+ // the buffer. The contents of the buffer must not be overwritten until the
+ // fence signals. If the fence is Fence::NO_FENCE, the buffer may be
+ // written immediately.
+ //
+ // The width and height parameters must be no greater than the minimum of
+ // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv).
+ // An error due to invalid dimensions might not be reported until
+ // updateTexImage() is called. If width and height are both zero, the
+ // default values specified by setDefaultBufferSize() are used instead.
+ //
+ // The pixel formats are enumerated in graphics.h, e.g.
+ // HAL_PIXEL_FORMAT_RGBA_8888. If the format is 0, the default format
+ // will be used.
+ //
+ // The usage argument specifies gralloc buffer usage flags. The values
+ // are enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER. These
+ // will be merged with the usage flags specified by setConsumerUsageBits.
+ //
+ // The return value may be a negative error value or a non-negative
+ // collection of flags. If the flags are set, the return values are
+ // valid, but additional actions must be performed.
+ //
+ // If IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION is set, the
+ // producer must discard cached GraphicBuffer references for the slot
+ // returned in buf.
+ // If IGraphicBufferProducer::RELEASE_ALL_BUFFERS is set, the producer
+ // must discard cached GraphicBuffer references for all slots.
+ //
+ // In both cases, the producer will need to call requestBuffer to get a
+ // GraphicBuffer handle for the returned slot.
+ virtual status_t dequeueBuffer(int *outSlot, sp<Fence>* outFence, bool async,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage);
+
+ // See IGraphicBufferProducer::detachBuffer
+ virtual status_t detachBuffer(int slot);
+
+ // See IGraphicBufferProducer::detachNextBuffer
+ virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence);
+
+ // See IGraphicBufferProducer::attachBuffer
+ virtual status_t attachBuffer(int* outSlot, const sp<GraphicBuffer>& buffer);
+
+ // queueBuffer returns a filled buffer to the GonkBufferQueue.
+ //
+ // Additional data is provided in the QueueBufferInput struct. Notably,
+ // a timestamp must be provided for the buffer. The timestamp is in
+ // nanoseconds, and must be monotonically increasing. Its other semantics
+ // (zero point, etc) are producer-specific and should be documented by the
+ // producer.
+ //
+ // The caller may provide a fence that signals when all rendering
+ // operations have completed. Alternatively, NO_FENCE may be used,
+ // indicating that the buffer is ready immediately.
+ //
+ // Some values are returned in the output struct: the current settings
+ // for default width and height, the current transform hint, and the
+ // number of queued buffers.
+ virtual status_t queueBuffer(int slot,
+ const QueueBufferInput& input, QueueBufferOutput* output);
+
+ // cancelBuffer returns a dequeued buffer to the GonkBufferQueue, but doesn't
+ // queue it for use by the consumer.
+ //
+ // The buffer will not be overwritten until the fence signals. The fence
+ // will usually be the one obtained from dequeueBuffer.
+ virtual void cancelBuffer(int slot, const sp<Fence>& fence);
+
+ // Query native window attributes. The "what" values are enumerated in
+ // window.h (e.g. NATIVE_WINDOW_FORMAT).
+ virtual int query(int what, int* outValue);
+
+ // connect attempts to connect a producer API to the GonkBufferQueue. This
+ // must be called before any other IGraphicBufferProducer methods are
+ // called except for getAllocator. A consumer must already be connected.
+ //
+ // This method will fail if connect was previously called on the
+ // GonkBufferQueue and no corresponding disconnect call was made (i.e. if
+ // it's still connected to a producer).
+ //
+ // APIs are enumerated in window.h (e.g. NATIVE_WINDOW_API_CPU).
+ virtual status_t connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp, QueueBufferOutput* output);
+
+ // disconnect attempts to disconnect a producer API from the GonkBufferQueue.
+ // Calling this method will cause any subsequent calls to other
+ // IGraphicBufferProducer methods to fail except for getAllocator and connect.
+ // Successfully calling connect after this will allow the other methods to
+ // succeed again.
+ //
+ // This method will fail if the the GonkBufferQueue is not currently
+ // connected to the specified producer API.
+ virtual status_t disconnect(int api);
+
+ // Attaches a sideband buffer stream to the IGraphicBufferProducer.
+ //
+ // A sideband stream is a device-specific mechanism for passing buffers
+ // from the producer to the consumer without using dequeueBuffer/
+ // queueBuffer. If a sideband stream is present, the consumer can choose
+ // whether to acquire buffers from the sideband stream or from the queued
+ // buffers.
+ //
+ // Passing NULL or a different stream handle will detach the previous
+ // handle if any.
+ virtual status_t setSidebandStream(const sp<NativeHandle>& stream);
+
+ // See IGraphicBufferProducer::allocateBuffers
+ virtual void allocateBuffers(bool async, uint32_t width, uint32_t height,
+ uint32_t format, uint32_t usage);
+
+ // setSynchronousMode sets whether dequeueBuffer is synchronous or
+ // asynchronous. In synchronous mode, dequeueBuffer blocks until
+ // a buffer is available, the currently bound buffer can be dequeued and
+ // queued buffers will be acquired in order. In asynchronous mode,
+ // a queued buffer may be replaced by a subsequently queued buffer.
+ //
+ // The default mode is synchronous.
+ // This should be called only during initialization.
+ virtual status_t setSynchronousMode(bool enabled);
+
+private:
+ // This is required by the IBinder::DeathRecipient interface
+ virtual void binderDied(const wp<IBinder>& who);
+
+ // waitForFreeSlotThenRelock finds the oldest slot in the FREE state. It may
+ // block if there are no available slots and we are not in non-blocking
+ // mode (producer and consumer controlled by the application). If it blocks,
+ // it will release mCore->mMutex while blocked so that other operations on
+ // the GonkBufferQueue may succeed.
+ status_t waitForFreeSlotThenRelock(const char* caller, bool async,
+ int* found, status_t* returnFlags) const;
+
+ sp<GonkBufferQueueCore> mCore;
+
+ // This references mCore->mSlots. Lock mCore->mMutex while accessing.
+ GonkBufferQueueDefs::SlotsType& mSlots;
+
+ // This is a cached copy of the name stored in the GonkBufferQueueCore.
+ // It's updated during connect and dequeueBuffer (which should catch
+ // most updates).
+ String8 mConsumerName;
+
+ // mSynchronousMode whether we're in synchronous mode or not
+ bool mSynchronousMode;
+
+ uint32_t mStickyTransform;
+
+}; // class GonkBufferQueueProducer
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.cpp b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.cpp
new file mode 100644
index 0000000000..9e4a424a93
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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 "GonkBufferSlot.h"
+
+namespace android {
+
+const char* GonkBufferSlot::bufferStateName(BufferState state) {
+ switch (state) {
+ case GonkBufferSlot::DEQUEUED: return "DEQUEUED";
+ case GonkBufferSlot::QUEUED: return "QUEUED";
+ case GonkBufferSlot::FREE: return "FREE";
+ case GonkBufferSlot::ACQUIRED: return "ACQUIRED";
+ default: return "Unknown";
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.h b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.h
new file mode 100644
index 0000000000..759bb7b23c
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkBufferQueueLL/GonkBufferSlot.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKBUFFERSLOT_LL_H
+#define NATIVEWINDOW_GONKBUFFERSLOT_LL_H
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <utils/StrongPointer.h>
+
+#include "mozilla/layers/TextureClient.h"
+
+namespace android {
+
+struct GonkBufferSlot {
+ typedef mozilla::layers::TextureClient TextureClient;
+
+ GonkBufferSlot()
+ : mBufferState(GonkBufferSlot::FREE),
+ mRequestBufferCalled(false),
+ mFrameNumber(0),
+ mAcquireCalled(false),
+ mNeedsCleanupOnRelease(false),
+ mAttachedByConsumer(false) {
+ }
+
+ // mGraphicBuffer points to the buffer allocated for this slot or is NULL
+ // if no buffer has been allocated.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // BufferState represents the different states in which a buffer slot
+ // can be. All slots are initially FREE.
+ enum BufferState {
+ // FREE indicates that the buffer is available to be dequeued
+ // by the producer. The buffer may be in use by the consumer for
+ // a finite time, so the buffer must not be modified until the
+ // associated fence is signaled.
+ //
+ // The slot is "owned" by BufferQueue. It transitions to DEQUEUED
+ // when dequeueBuffer is called.
+ FREE = 0,
+
+ // DEQUEUED indicates that the buffer has been dequeued by the
+ // producer, but has not yet been queued or canceled. The
+ // producer may modify the buffer's contents as soon as the
+ // associated ready fence is signaled.
+ //
+ // The slot is "owned" by the producer. It can transition to
+ // QUEUED (via queueBuffer) or back to FREE (via cancelBuffer).
+ DEQUEUED = 1,
+
+ // QUEUED indicates that the buffer has been filled by the
+ // producer and queued for use by the consumer. The buffer
+ // contents may continue to be modified for a finite time, so
+ // the contents must not be accessed until the associated fence
+ // is signaled.
+ //
+ // The slot is "owned" by BufferQueue. It can transition to
+ // ACQUIRED (via acquireBuffer) or to FREE (if another buffer is
+ // queued in asynchronous mode).
+ QUEUED = 2,
+
+ // ACQUIRED indicates that the buffer has been acquired by the
+ // consumer. As with QUEUED, the contents must not be accessed
+ // by the consumer until the fence is signaled.
+ //
+ // The slot is "owned" by the consumer. It transitions to FREE
+ // when releaseBuffer is called.
+ ACQUIRED = 3
+ };
+
+ static const char* bufferStateName(BufferState state);
+
+ // mBufferState is the current state of this buffer slot.
+ BufferState mBufferState;
+
+ // mRequestBufferCalled is used for validating that the producer did
+ // call requestBuffer() when told to do so. Technically this is not
+ // needed but useful for debugging and catching producer bugs.
+ bool mRequestBufferCalled;
+
+ // mFrameNumber is the number of the queued frame for this slot. This
+ // is used to dequeue buffers in LRU order (useful because buffers
+ // may be released before their release fence is signaled).
+ uint64_t mFrameNumber;
+
+ // mFence is a fence which will signal when work initiated by the
+ // previous owner of the buffer is finished. When the buffer is FREE,
+ // the fence indicates when the consumer has finished reading
+ // from the buffer, or when the producer has finished writing if it
+ // called cancelBuffer after queueing some writes. When the buffer is
+ // QUEUED, it indicates when the producer has finished filling the
+ // buffer. When the buffer is DEQUEUED or ACQUIRED, the fence has been
+ // passed to the consumer or producer along with ownership of the
+ // buffer, and mFence is set to NO_FENCE.
+ sp<Fence> mFence;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates whether this buffer needs to be cleaned up by the
+ // consumer. This is set when a buffer in ACQUIRED state is freed.
+ // It causes releaseBuffer to return STALE_BUFFER_SLOT.
+ bool mNeedsCleanupOnRelease;
+
+ // Indicates whether the buffer was attached on the consumer side.
+ // If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when dequeued
+ // to prevent the producer from using a stale cached buffer.
+ bool mAttachedByConsumer;
+
+ // mTextureClient is a thin abstraction over remotely allocated GraphicBuffer.
+ RefPtr<TextureClient> mTextureClient;
+};
+
+} // namespace android
+
+#endif
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseJB.cpp b/widget/gonk/nativewindow/GonkConsumerBaseJB.cpp
new file mode 100644
index 0000000000..1ee37e4e2f
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseJB.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#define LOG_TAG "GonkConsumerBase"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <hardware/hardware.h>
+
+#include <gui/IGraphicBufferAlloc.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include "GonkConsumerBaseJB.h"
+
+// Macros for including the GonkConsumerBase name in log messages
+#define CB_LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define CB_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define CB_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define CB_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define CB_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+GonkConsumerBase::GonkConsumerBase(const sp<GonkBufferQueue>& bufferQueue) :
+ mAbandoned(false),
+ mBufferQueue(bufferQueue) {
+ // Choose a name using the PID and a process-unique ID.
+ mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ // Note that we can't create an sp<...>(this) in a ctor that will not keep a
+ // reference once the ctor ends, as that would cause the refcount of 'this'
+ // dropping to 0 at the end of the ctor. Since all we need is a wp<...>
+ // that's what we create.
+ wp<GonkBufferQueue::ConsumerListener> listener;
+ sp<GonkBufferQueue::ConsumerListener> proxy;
+ listener = static_cast<GonkBufferQueue::ConsumerListener*>(this);
+ proxy = new GonkBufferQueue::ProxyConsumerListener(listener);
+
+ status_t err = mBufferQueue->consumerConnect(proxy);
+ if (err != NO_ERROR) {
+ CB_LOGE("GonkConsumerBase: error connecting to GonkBufferQueue: %s (%d)",
+ strerror(-err), err);
+ } else {
+ mBufferQueue->setConsumerName(mName);
+ }
+}
+
+GonkConsumerBase::~GonkConsumerBase() {
+ CB_LOGV("~GonkConsumerBase");
+ Mutex::Autolock lock(mMutex);
+
+ // Verify that abandon() has been called before we get here. This should
+ // be done by GonkConsumerBase::onLastStrongRef(), but it's possible for a
+ // derived class to override that method and not call
+ // GonkConsumerBase::onLastStrongRef().
+ LOG_ALWAYS_FATAL_IF(!mAbandoned, "[%s] ~GonkConsumerBase was called, but the "
+ "consumer is not abandoned!", mName.string());
+}
+
+void GonkConsumerBase::onLastStrongRef(const void* id) {
+ abandon();
+}
+
+void GonkConsumerBase::freeBufferLocked(int slotIndex) {
+ CB_LOGV("freeBufferLocked: slotIndex=%d", slotIndex);
+ mSlots[slotIndex].mGraphicBuffer = 0;
+ mSlots[slotIndex].mFence = Fence::NO_FENCE;
+}
+
+// Used for refactoring, should not be in final interface
+sp<GonkBufferQueue> GonkConsumerBase::getBufferQueue() const {
+ Mutex::Autolock lock(mMutex);
+ return mBufferQueue;
+}
+
+void GonkConsumerBase::onFrameAvailable() {
+ CB_LOGV("onFrameAvailable");
+
+ sp<FrameAvailableListener> listener;
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+#if ANDROID_VERSION == 17
+ listener = mFrameAvailableListener;
+#else
+ listener = mFrameAvailableListener.promote();
+#endif
+ }
+
+ if (listener != NULL) {
+ CB_LOGV("actually calling onFrameAvailable");
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkConsumerBase::onBuffersReleased() {
+ Mutex::Autolock lock(mMutex);
+
+ CB_LOGV("onBuffersReleased");
+
+ if (mAbandoned) {
+ // Nothing to do if we're already abandoned.
+ return;
+ }
+
+ uint32_t mask = 0;
+ mBufferQueue->getReleasedBuffers(&mask);
+ for (int i = 0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if (mask & (1 << i)) {
+ freeBufferLocked(i);
+ }
+ }
+}
+
+void GonkConsumerBase::abandon() {
+ CB_LOGV("abandon");
+ Mutex::Autolock lock(mMutex);
+
+ if (!mAbandoned) {
+ abandonLocked();
+ mAbandoned = true;
+ }
+}
+
+void GonkConsumerBase::abandonLocked() {
+ CB_LOGV("abandonLocked");
+ for (int i =0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ freeBufferLocked(i);
+ }
+ // disconnect from the GonkBufferQueue
+ mBufferQueue->consumerDisconnect();
+ mBufferQueue.clear();
+}
+
+void GonkConsumerBase::setFrameAvailableListener(
+#if ANDROID_VERSION == 17
+ const sp<FrameAvailableListener>& listener) {
+#else
+ const wp<FrameAvailableListener>& listener) {
+#endif
+ CB_LOGV("setFrameAvailableListener");
+ Mutex::Autolock lock(mMutex);
+ mFrameAvailableListener = listener;
+}
+
+void GonkConsumerBase::dump(String8& result) const {
+ char buffer[1024];
+ dump(result, "", buffer, 1024);
+}
+
+void GonkConsumerBase::dump(String8& result, const char* prefix,
+ char* buffer, size_t size) const {
+ Mutex::Autolock _l(mMutex);
+ dumpLocked(result, prefix, buffer, size);
+}
+
+void GonkConsumerBase::dumpLocked(String8& result, const char* prefix,
+ char* buffer, size_t SIZE) const {
+ snprintf(buffer, SIZE, "%smAbandoned=%d\n", prefix, int(mAbandoned));
+ result.append(buffer);
+
+ if (!mAbandoned) {
+ mBufferQueue->dumpToString(result, prefix, buffer, SIZE);
+ }
+}
+
+status_t GonkConsumerBase::acquireBufferLocked(GonkBufferQueue::BufferItem *item) {
+ status_t err = mBufferQueue->acquireBuffer(item);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ if (item->mGraphicBuffer != NULL) {
+ mSlots[item->mBuf].mGraphicBuffer = item->mGraphicBuffer;
+ }
+
+ mSlots[item->mBuf].mFence = item->mFence;
+
+ CB_LOGV("acquireBufferLocked: -> slot=%d", item->mBuf);
+
+ return OK;
+}
+
+status_t GonkConsumerBase::addReleaseFence(int slot, const sp<Fence>& fence) {
+ Mutex::Autolock lock(mMutex);
+ return addReleaseFenceLocked(slot, fence);
+}
+
+status_t GonkConsumerBase::addReleaseFenceLocked(int slot, const sp<Fence>& fence) {
+ CB_LOGV("addReleaseFenceLocked: slot=%d", slot);
+
+ if (!mSlots[slot].mFence.get()) {
+ mSlots[slot].mFence = fence;
+ } else {
+ sp<Fence> mergedFence = Fence::merge(
+ String8::format("%.28s:%d", mName.string(), slot),
+ mSlots[slot].mFence, fence);
+ if (!mergedFence.get()) {
+ CB_LOGE("failed to merge release fences");
+ // synchronization is broken, the best we can do is hope fences
+ // signal in order so the new fence will act like a union
+ mSlots[slot].mFence = fence;
+ return BAD_VALUE;
+ }
+ mSlots[slot].mFence = mergedFence;
+ }
+
+ return OK;
+}
+
+status_t GonkConsumerBase::releaseBufferLocked(int slot) {
+ CB_LOGV("releaseBufferLocked: slot=%d", slot);
+ status_t err = mBufferQueue->releaseBuffer(slot, mSlots[slot].mFence);
+ if (err == GonkBufferQueue::STALE_BUFFER_SLOT) {
+ freeBufferLocked(slot);
+ }
+
+ mSlots[slot].mFence = Fence::NO_FENCE;
+
+ return err;
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseJB.h b/widget/gonk/nativewindow/GonkConsumerBaseJB.h
new file mode 100644
index 0000000000..8f523af372
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseJB.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKCONSUMERBASE_JB_H
+#define NATIVEWINDOW_GONKCONSUMERBASE_JB_H
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "GonkBufferQueueJB.h"
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class String8;
+
+// GonkConsumerBase is a base class for GonkBufferQueue consumer end-points. It
+// handles common tasks like management of the connection to the GonkBufferQueue
+// and the buffer pool.
+class GonkConsumerBase : public virtual RefBase,
+ protected GonkBufferQueue::ConsumerListener {
+public:
+ struct FrameAvailableListener : public virtual RefBase {
+ // onFrameAvailable() is called each time an additional frame becomes
+ // available for consumption. This means that frames that are queued
+ // while in asynchronous mode only trigger the callback if no previous
+ // frames are pending. Frames queued while in synchronous mode always
+ // trigger the callback.
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onFrameAvailable() = 0;
+ };
+
+ virtual ~GonkConsumerBase();
+
+ // abandon frees all the buffers and puts the GonkConsumerBase into the
+ // 'abandoned' state. Once put in this state the GonkConsumerBase can never
+ // leave it. When in the 'abandoned' state, all methods of the
+ // IGraphicBufferProducer interface will fail with the NO_INIT error.
+ //
+ // Note that while calling this method causes all the buffers to be freed
+ // from the perspective of the the GonkConsumerBase, if there are additional
+ // references on the buffers (e.g. if a buffer is referenced by a client
+ // or by OpenGL ES as a texture) then those buffer will remain allocated.
+ void abandon();
+
+ // set the name of the GonkConsumerBase that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // getBufferQueue returns the GonkBufferQueue object to which this
+ // GonkConsumerBase is connected.
+ sp<GonkBufferQueue> getBufferQueue() const;
+
+ // dump writes the current state to a string. Child classes should add
+ // their state to the dump by overriding the dumpLocked method, which is
+ // called by these methods after locking the mutex.
+ void dump(String8& result) const;
+ void dump(String8& result, const char* prefix, char* buffer, size_t SIZE) const;
+
+ // setFrameAvailableListener sets the listener object that will be notified
+ // when a new frame becomes available.
+#if ANDROID_VERSION == 17
+ void setFrameAvailableListener(const sp<FrameAvailableListener>& listener);
+#else
+ void setFrameAvailableListener(const wp<FrameAvailableListener>& listener);
+#endif
+
+private:
+ GonkConsumerBase(const GonkConsumerBase&);
+ void operator=(const GonkConsumerBase&);
+
+protected:
+
+ // GonkConsumerBase constructs a new GonkConsumerBase object to consume image
+ // buffers from the given GonkBufferQueue.
+ GonkConsumerBase(const sp<GonkBufferQueue> &bufferQueue);
+
+ // onLastStrongRef gets called by RefBase just before the dtor of the most
+ // derived class. It is used to clean up the buffers so that GonkConsumerBase
+ // can coordinate the clean-up by calling into virtual methods implemented
+ // by the derived classes. This would not be possible from the
+ // ConsuemrBase dtor because by the time that gets called the derived
+ // classes have already been destructed.
+ //
+ // This methods should not need to be overridden by derived classes, but
+ // if they are overridden the GonkConsumerBase implementation must be called
+ // from the derived class.
+ virtual void onLastStrongRef(const void* id);
+
+ // Implementation of the GonkBufferQueue::ConsumerListener interface. These
+ // calls are used to notify the GonkConsumerBase of asynchronous events in the
+ // GonkBufferQueue. These methods should not need to be overridden by derived
+ // classes, but if they are overridden the GonkConsumerBase implementation
+ // must be called from the derived class.
+ virtual void onFrameAvailable();
+ virtual void onBuffersReleased();
+
+ // freeBufferLocked frees up the given buffer slot. If the slot has been
+ // initialized this will release the reference to the GraphicBuffer in that
+ // slot. Otherwise it has no effect.
+ //
+ // Derived classes should override this method to clean up any state they
+ // keep per slot. If it is overridden, the derived class's implementation
+ // must call GonkConsumerBase::freeBufferLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void freeBufferLocked(int slotIndex);
+
+ // abandonLocked puts the GonkBufferQueue into the abandoned state, causing
+ // all future operations on it to fail. This method rather than the public
+ // abandon method should be overridden by child classes to add abandon-
+ // time behavior.
+ //
+ // Derived classes should override this method to clean up any object
+ // state they keep (as opposed to per-slot state). If it is overridden,
+ // the derived class's implementation must call GonkConsumerBase::abandonLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void abandonLocked();
+
+ // dumpLocked dumps the current state of the GonkConsumerBase object to the
+ // result string. Each line is prefixed with the string pointed to by the
+ // prefix argument. The buffer argument points to a buffer that may be
+ // used for intermediate formatting data, and the size of that buffer is
+ // indicated by the size argument.
+ //
+ // Derived classes should override this method to dump their internal
+ // state. If this method is overridden the derived class's implementation
+ // should call GonkConsumerBase::dumpLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void dumpLocked(String8& result, const char* prefix, char* buffer,
+ size_t size) const;
+
+ // acquireBufferLocked fetches the next buffer from the GonkBufferQueue and
+ // updates the buffer slot for the buffer returned.
+ //
+ // Derived classes should override this method to perform any
+ // initialization that must take place the first time a buffer is assigned
+ // to a slot. If it is overridden the derived class's implementation must
+ // call GonkConsumerBase::acquireBufferLocked.
+ virtual status_t acquireBufferLocked(GonkBufferQueue::BufferItem *item);
+
+ // releaseBufferLocked relinquishes control over a buffer, returning that
+ // control to the GonkBufferQueue.
+ //
+ // Derived classes should override this method to perform any cleanup that
+ // must take place when a buffer is released back to the GonkBufferQueue. If
+ // it is overridden the derived class's implementation must call
+ // GonkConsumerBase::releaseBufferLocked.
+ virtual status_t releaseBufferLocked(int buf);
+
+ // addReleaseFence* adds the sync points associated with a fence to the set
+ // of sync points that must be reached before the buffer in the given slot
+ // may be used after the slot has been released. This should be called by
+ // derived classes each time some asynchronous work is kicked off that
+ // references the buffer.
+ status_t addReleaseFence(int slot, const sp<Fence>& fence);
+ status_t addReleaseFenceLocked(int slot, const sp<Fence>& fence);
+
+ // Slot contains the information and object references that
+ // GonkConsumerBase maintains about a GonkBufferQueue buffer slot.
+ struct Slot {
+ // mGraphicBuffer is the Gralloc buffer store in the slot or NULL if
+ // no Gralloc buffer is in the slot.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence which will signal when the buffer associated with
+ // this buffer slot is no longer being used by the consumer and can be
+ // overwritten. The buffer can be dequeued before the fence signals;
+ // the producer is responsible for delaying writes until it signals.
+ sp<Fence> mFence;
+ };
+
+ // mSlots stores the buffers that have been allocated by the GonkBufferQueue
+ // for each buffer slot. It is initialized to null pointers, and gets
+ // filled in with the result of GonkBufferQueue::acquire when the
+ // client dequeues a buffer from a
+ // slot that has not yet been used. The buffer allocated to a slot will also
+ // be replaced if the requested buffer usage or geometry differs from that
+ // of the buffer allocated to a slot.
+ Slot mSlots[GonkBufferQueue::NUM_BUFFER_SLOTS];
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume images buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the abandon
+ // method. A GonkBufferQueue that has been abandoned will return the NO_INIT
+ // error from all IGonkConsumerBase methods capable of returning an error.
+ bool mAbandoned;
+
+ // mName is a string used to identify the GonkConsumerBase in log messages.
+ // It can be set by the setName method.
+ String8 mName;
+
+ // mFrameAvailableListener is the listener object that will be called when a
+ // new frame becomes available. If it is not NULL it will be called from
+ // queueBuffer.
+#if ANDROID_VERSION == 17
+ sp<FrameAvailableListener> mFrameAvailableListener;
+#else
+ wp<FrameAvailableListener> mFrameAvailableListener;
+#endif
+
+ // The GonkConsumerBase has-a GonkBufferQueue and is responsible for creating this object
+ // if none is supplied
+ sp<GonkBufferQueue> mBufferQueue;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkConsumerBase objects. It must be locked whenever the
+ // member variables are accessed or when any of the *Locked methods are
+ // called.
+ //
+ // This mutex is intended to be locked by derived classes.
+ mutable Mutex mMutex;
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_GONKCONSUMERBASE_H
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseKK.cpp b/widget/gonk/nativewindow/GonkConsumerBaseKK.cpp
new file mode 100644
index 0000000000..3fc9fc16c5
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseKK.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#define LOG_TAG "GonkConsumerBase"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <hardware/hardware.h>
+
+#include <gui/IGraphicBufferAlloc.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include "GonkConsumerBaseKK.h"
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+GonkConsumerBase::GonkConsumerBase(const sp<GonkBufferQueue>& bufferQueue, bool controlledByApp) :
+ mAbandoned(false),
+ mConsumer(bufferQueue) {
+ // Choose a name using the PID and a process-unique ID.
+ mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ // Note that we can't create an sp<...>(this) in a ctor that will not keep a
+ // reference once the ctor ends, as that would cause the refcount of 'this'
+ // dropping to 0 at the end of the ctor. Since all we need is a wp<...>
+ // that's what we create.
+ wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
+ sp<IConsumerListener> proxy = new GonkBufferQueue::ProxyConsumerListener(listener);
+
+ status_t err = mConsumer->consumerConnect(proxy, controlledByApp);
+ if (err != NO_ERROR) {
+ ALOGE("GonkConsumerBase: error connecting to GonkBufferQueue: %s (%d)",
+ strerror(-err), err);
+ } else {
+ mConsumer->setConsumerName(mName);
+ }
+}
+
+GonkConsumerBase::~GonkConsumerBase() {
+ ALOGV("~GonkConsumerBase");
+ Mutex::Autolock lock(mMutex);
+
+ // Verify that abandon() has been called before we get here. This should
+ // be done by GonkConsumerBase::onLastStrongRef(), but it's possible for a
+ // derived class to override that method and not call
+ // GonkConsumerBase::onLastStrongRef().
+ LOG_ALWAYS_FATAL_IF(!mAbandoned, "[%s] ~GonkConsumerBase was called, but the "
+ "consumer is not abandoned!", mName.string());
+}
+
+void GonkConsumerBase::onLastStrongRef(const void* id) {
+ abandon();
+}
+
+void GonkConsumerBase::freeBufferLocked(int slotIndex) {
+ ALOGV("freeBufferLocked: slotIndex=%d", slotIndex);
+ mSlots[slotIndex].mGraphicBuffer = 0;
+ mSlots[slotIndex].mFence = Fence::NO_FENCE;
+ mSlots[slotIndex].mFrameNumber = 0;
+}
+
+// Used for refactoring, should not be in final interface
+sp<GonkBufferQueue> GonkConsumerBase::getBufferQueue() const {
+ Mutex::Autolock lock(mMutex);
+ return mConsumer;
+}
+
+void GonkConsumerBase::onFrameAvailable() {
+ ALOGV("onFrameAvailable");
+
+ sp<FrameAvailableListener> listener;
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+ listener = mFrameAvailableListener.promote();
+ }
+
+ if (listener != NULL) {
+ ALOGV("actually calling onFrameAvailable");
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkConsumerBase::onBuffersReleased() {
+ Mutex::Autolock lock(mMutex);
+
+ ALOGV("onBuffersReleased");
+
+ if (mAbandoned) {
+ // Nothing to do if we're already abandoned.
+ return;
+ }
+
+ uint32_t mask = 0;
+ mConsumer->getReleasedBuffers(&mask);
+ for (int i = 0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if (mask & (1 << i)) {
+ freeBufferLocked(i);
+ }
+ }
+}
+
+void GonkConsumerBase::abandon() {
+ ALOGV("abandon");
+ Mutex::Autolock lock(mMutex);
+
+ if (!mAbandoned) {
+ abandonLocked();
+ mAbandoned = true;
+ }
+}
+
+void GonkConsumerBase::abandonLocked() {
+ ALOGV("abandonLocked");
+ for (int i =0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ freeBufferLocked(i);
+ }
+ // disconnect from the BufferQueue
+ mConsumer->consumerDisconnect();
+ mConsumer.clear();
+}
+
+void GonkConsumerBase::setFrameAvailableListener(
+ const wp<FrameAvailableListener>& listener) {
+ ALOGV("setFrameAvailableListener");
+ Mutex::Autolock lock(mMutex);
+ mFrameAvailableListener = listener;
+}
+
+void GonkConsumerBase::dump(String8& result) const {
+ dump(result, "");
+}
+
+void GonkConsumerBase::dump(String8& result, const char* prefix) const {
+ Mutex::Autolock _l(mMutex);
+ dumpLocked(result, prefix);
+}
+
+void GonkConsumerBase::dumpLocked(String8& result, const char* prefix) const {
+ result.appendFormat("%smAbandoned=%d\n", prefix, int(mAbandoned));
+
+ if (!mAbandoned) {
+ mConsumer->dumpToString(result, prefix);
+ }
+}
+
+status_t GonkConsumerBase::acquireBufferLocked(IGonkGraphicBufferConsumer::BufferItem *item,
+ nsecs_t presentWhen) {
+ status_t err = mConsumer->acquireBuffer(item, presentWhen);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ if (item->mGraphicBuffer != NULL) {
+ mSlots[item->mBuf].mGraphicBuffer = item->mGraphicBuffer;
+ }
+
+ mSlots[item->mBuf].mFrameNumber = item->mFrameNumber;
+ mSlots[item->mBuf].mFence = item->mFence;
+
+ ALOGV("acquireBufferLocked: -> slot=%d", item->mBuf);
+
+ return OK;
+}
+
+status_t GonkConsumerBase::addReleaseFence(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
+ Mutex::Autolock lock(mMutex);
+ return addReleaseFenceLocked(slot, graphicBuffer, fence);
+}
+
+status_t GonkConsumerBase::addReleaseFenceLocked(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
+ ALOGV("addReleaseFenceLocked: slot=%d", slot);
+
+ // If consumer no longer tracks this graphicBuffer, we can safely
+ // drop this fence, as it will never be received by the producer.
+ if (!stillTracking(slot, graphicBuffer)) {
+ return OK;
+ }
+
+ if (!mSlots[slot].mFence.get()) {
+ mSlots[slot].mFence = fence;
+ } else {
+ sp<Fence> mergedFence = Fence::merge(
+ String8::format("%.28s:%d", mName.string(), slot),
+ mSlots[slot].mFence, fence);
+ if (!mergedFence.get()) {
+ ALOGE("failed to merge release fences");
+ // synchronization is broken, the best we can do is hope fences
+ // signal in order so the new fence will act like a union
+ mSlots[slot].mFence = fence;
+ return BAD_VALUE;
+ }
+ mSlots[slot].mFence = mergedFence;
+ }
+
+ return OK;
+}
+
+status_t GonkConsumerBase::releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer) {
+ // If consumer no longer tracks this graphicBuffer (we received a new
+ // buffer on the same slot), the buffer producer is definitely no longer
+ // tracking it.
+ if (!stillTracking(slot, graphicBuffer)) {
+ return OK;
+ }
+
+ ALOGV("releaseBufferLocked: slot=%d/%llu",
+ slot, mSlots[slot].mFrameNumber);
+ status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber, mSlots[slot].mFence);
+ if (err == GonkBufferQueue::STALE_BUFFER_SLOT) {
+ freeBufferLocked(slot);
+ }
+
+ mSlots[slot].mFence = Fence::NO_FENCE;
+
+ return err;
+}
+
+bool GonkConsumerBase::stillTracking(int slot,
+ const sp<GraphicBuffer> graphicBuffer) {
+ if (slot < 0 || slot >= GonkBufferQueue::NUM_BUFFER_SLOTS) {
+ return false;
+ }
+ return (mSlots[slot].mGraphicBuffer != NULL &&
+ mSlots[slot].mGraphicBuffer->handle == graphicBuffer->handle);
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseKK.h b/widget/gonk/nativewindow/GonkConsumerBaseKK.h
new file mode 100644
index 0000000000..e198ad8435
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseKK.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKCONSUMERBASE_KK_H
+#define NATIVEWINDOW_GONKCONSUMERBASE_KK_H
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <gui/IConsumerListener.h>
+
+#include "GonkBufferQueueKK.h"
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class String8;
+
+// GonkConsumerBase is a base class for GonkBufferQueue consumer end-points. It
+// handles common tasks like management of the connection to the GonkBufferQueue
+// and the buffer pool.
+class GonkConsumerBase : public virtual RefBase,
+ protected ConsumerListener {
+public:
+ struct FrameAvailableListener : public virtual RefBase {
+ // onFrameAvailable() is called each time an additional frame becomes
+ // available for consumption. This means that frames that are queued
+ // while in asynchronous mode only trigger the callback if no previous
+ // frames are pending. Frames queued while in synchronous mode always
+ // trigger the callback.
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onFrameAvailable() = 0;
+ };
+
+ virtual ~GonkConsumerBase();
+
+ // abandon frees all the buffers and puts the GonkConsumerBase into the
+ // 'abandoned' state. Once put in this state the GonkConsumerBase can never
+ // leave it. When in the 'abandoned' state, all methods of the
+ // IGraphicBufferProducer interface will fail with the NO_INIT error.
+ //
+ // Note that while calling this method causes all the buffers to be freed
+ // from the perspective of the the GonkConsumerBase, if there are additional
+ // references on the buffers (e.g. if a buffer is referenced by a client
+ // or by OpenGL ES as a texture) then those buffer will remain allocated.
+ void abandon();
+
+ // set the name of the GonkConsumerBase that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // getBufferQueue returns the GonkBufferQueue object to which this
+ // GonkConsumerBase is connected.
+ sp<GonkBufferQueue> getBufferQueue() const;
+
+ // dump writes the current state to a string. Child classes should add
+ // their state to the dump by overriding the dumpLocked method, which is
+ // called by these methods after locking the mutex.
+ void dump(String8& result) const;
+ void dump(String8& result, const char* prefix) const;
+
+ // setFrameAvailableListener sets the listener object that will be notified
+ // when a new frame becomes available.
+ void setFrameAvailableListener(const wp<FrameAvailableListener>& listener);
+
+private:
+ GonkConsumerBase(const GonkConsumerBase&);
+ void operator=(const GonkConsumerBase&);
+
+protected:
+
+ // GonkConsumerBase constructs a new GonkConsumerBase object to consume image
+ // buffers from the given GonkBufferQueue.
+ GonkConsumerBase(const sp<GonkBufferQueue>& bufferQueue, bool controlledByApp = false);
+
+ // onLastStrongRef gets called by RefBase just before the dtor of the most
+ // derived class. It is used to clean up the buffers so that GonkConsumerBase
+ // can coordinate the clean-up by calling into virtual methods implemented
+ // by the derived classes. This would not be possible from the
+ // ConsuemrBase dtor because by the time that gets called the derived
+ // classes have already been destructed.
+ //
+ // This methods should not need to be overridden by derived classes, but
+ // if they are overridden the GonkConsumerBase implementation must be called
+ // from the derived class.
+ virtual void onLastStrongRef(const void* id);
+
+ // Implementation of the GonkBufferQueue::ConsumerListener interface. These
+ // calls are used to notify the GonkConsumerBase of asynchronous events in the
+ // GonkBufferQueue. These methods should not need to be overridden by derived
+ // classes, but if they are overridden the GonkConsumerBase implementation
+ // must be called from the derived class.
+ virtual void onFrameAvailable();
+ virtual void onBuffersReleased();
+
+ // freeBufferLocked frees up the given buffer slot. If the slot has been
+ // initialized this will release the reference to the GraphicBuffer in that
+ // slot. Otherwise it has no effect.
+ //
+ // Derived classes should override this method to clean up any state they
+ // keep per slot. If it is overridden, the derived class's implementation
+ // must call GonkConsumerBase::freeBufferLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void freeBufferLocked(int slotIndex);
+
+ // abandonLocked puts the GonkBufferQueue into the abandoned state, causing
+ // all future operations on it to fail. This method rather than the public
+ // abandon method should be overridden by child classes to add abandon-
+ // time behavior.
+ //
+ // Derived classes should override this method to clean up any object
+ // state they keep (as opposed to per-slot state). If it is overridden,
+ // the derived class's implementation must call GonkConsumerBase::abandonLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void abandonLocked();
+
+ // dumpLocked dumps the current state of the GonkConsumerBase object to the
+ // result string. Each line is prefixed with the string pointed to by the
+ // prefix argument. The buffer argument points to a buffer that may be
+ // used for intermediate formatting data, and the size of that buffer is
+ // indicated by the size argument.
+ //
+ // Derived classes should override this method to dump their internal
+ // state. If this method is overridden the derived class's implementation
+ // should call GonkConsumerBase::dumpLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void dumpLocked(String8& result, const char* prefix) const;
+
+ // acquireBufferLocked fetches the next buffer from the GonkBufferQueue and
+ // updates the buffer slot for the buffer returned.
+ //
+ // Derived classes should override this method to perform any
+ // initialization that must take place the first time a buffer is assigned
+ // to a slot. If it is overridden the derived class's implementation must
+ // call GonkConsumerBase::acquireBufferLocked.
+ virtual status_t acquireBufferLocked(IGonkGraphicBufferConsumer::BufferItem *item,
+ nsecs_t presentWhen);
+
+ // releaseBufferLocked relinquishes control over a buffer, returning that
+ // control to the GonkBufferQueue.
+ //
+ // Derived classes should override this method to perform any cleanup that
+ // must take place when a buffer is released back to the GonkBufferQueue. If
+ // it is overridden the derived class's implementation must call
+ // GonkConsumerBase::releaseBufferLocked.
+ virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer);
+
+ // returns true iff the slot still has the graphicBuffer in it.
+ bool stillTracking(int slot, const sp<GraphicBuffer> graphicBuffer);
+
+ // addReleaseFence* adds the sync points associated with a fence to the set
+ // of sync points that must be reached before the buffer in the given slot
+ // may be used after the slot has been released. This should be called by
+ // derived classes each time some asynchronous work is kicked off that
+ // references the buffer.
+ status_t addReleaseFence(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence);
+ status_t addReleaseFenceLocked(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence);
+
+ // Slot contains the information and object references that
+ // GonkConsumerBase maintains about a GonkBufferQueue buffer slot.
+ struct Slot {
+ // mGraphicBuffer is the Gralloc buffer store in the slot or NULL if
+ // no Gralloc buffer is in the slot.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence which will signal when the buffer associated with
+ // this buffer slot is no longer being used by the consumer and can be
+ // overwritten. The buffer can be dequeued before the fence signals;
+ // the producer is responsible for delaying writes until it signals.
+ sp<Fence> mFence;
+
+ // the frame number of the last acquired frame for this slot
+ uint64_t mFrameNumber;
+ };
+
+ // mSlots stores the buffers that have been allocated by the GonkBufferQueue
+ // for each buffer slot. It is initialized to null pointers, and gets
+ // filled in with the result of GonkBufferQueue::acquire when the
+ // client dequeues a buffer from a
+ // slot that has not yet been used. The buffer allocated to a slot will also
+ // be replaced if the requested buffer usage or geometry differs from that
+ // of the buffer allocated to a slot.
+ Slot mSlots[GonkBufferQueue::NUM_BUFFER_SLOTS];
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume images buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the abandon
+ // method. A GonkBufferQueue that has been abandoned will return the NO_INIT
+ // error from all IGonkConsumerBase methods capable of returning an error.
+ bool mAbandoned;
+
+ // mName is a string used to identify the GonkConsumerBase in log messages.
+ // It can be set by the setName method.
+ String8 mName;
+
+ // mFrameAvailableListener is the listener object that will be called when a
+ // new frame becomes available. If it is not NULL it will be called from
+ // queueBuffer.
+ wp<FrameAvailableListener> mFrameAvailableListener;
+
+ // The GonkConsumerBase has-a GonkBufferQueue and is responsible for creating this object
+ // if none is supplied
+ sp<GonkBufferQueue> mConsumer;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkConsumerBase objects. It must be locked whenever the
+ // member variables are accessed or when any of the *Locked methods are
+ // called.
+ //
+ // This mutex is intended to be locked by derived classes.
+ mutable Mutex mMutex;
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_GONKCONSUMERBASE_H
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseLL.cpp b/widget/gonk/nativewindow/GonkConsumerBaseLL.cpp
new file mode 100644
index 0000000000..5b1166b57c
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseLL.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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 <inttypes.h>
+
+#define LOG_TAG "GonkConsumerBase"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <hardware/hardware.h>
+
+#include <gui/IGraphicBufferAlloc.h>
+
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include "GonkConsumerBaseLL.h"
+
+namespace android {
+
+// Get an ID that's unique within this process.
+static int32_t createProcessUniqueId() {
+ static volatile int32_t globalCounter = 0;
+ return android_atomic_inc(&globalCounter);
+}
+
+GonkConsumerBase::GonkConsumerBase(const sp<IGonkGraphicBufferConsumer>& bufferQueue, bool controlledByApp) :
+ mAbandoned(false),
+ mConsumer(bufferQueue) {
+ // Choose a name using the PID and a process-unique ID.
+ mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
+
+ // Note that we can't create an sp<...>(this) in a ctor that will not keep a
+ // reference once the ctor ends, as that would cause the refcount of 'this'
+ // dropping to 0 at the end of the ctor. Since all we need is a wp<...>
+ // that's what we create.
+ wp<ConsumerListener> listener = static_cast<ConsumerListener*>(this);
+ sp<IConsumerListener> proxy = new GonkBufferQueue::ProxyConsumerListener(listener);
+
+ status_t err = mConsumer->consumerConnect(proxy, controlledByApp);
+ if (err != NO_ERROR) {
+ ALOGE("GonkConsumerBase: error connecting to GonkBufferQueue: %s (%d)",
+ strerror(-err), err);
+ } else {
+ mConsumer->setConsumerName(mName);
+ }
+}
+
+GonkConsumerBase::~GonkConsumerBase() {
+ ALOGV("~GonkConsumerBase");
+ Mutex::Autolock lock(mMutex);
+
+ // Verify that abandon() has been called before we get here. This should
+ // be done by GonkConsumerBase::onLastStrongRef(), but it's possible for a
+ // derived class to override that method and not call
+ // GonkConsumerBase::onLastStrongRef().
+ LOG_ALWAYS_FATAL_IF(!mAbandoned, "[%s] ~GonkConsumerBase was called, but the "
+ "consumer is not abandoned!", mName.string());
+}
+
+void GonkConsumerBase::onLastStrongRef(const void* id __attribute__((unused))) {
+ abandon();
+}
+
+void GonkConsumerBase::freeBufferLocked(int slotIndex) {
+ ALOGV("freeBufferLocked: slotIndex=%d", slotIndex);
+ mSlots[slotIndex].mGraphicBuffer = 0;
+ mSlots[slotIndex].mFence = Fence::NO_FENCE;
+ mSlots[slotIndex].mFrameNumber = 0;
+}
+
+#if ANDROID_VERSION == 21
+void GonkConsumerBase::onFrameAvailable() {
+#else
+void GonkConsumerBase::onFrameAvailable(const ::android::BufferItem& item) {
+#endif
+ ALOGV("onFrameAvailable");
+
+ sp<FrameAvailableListener> listener;
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+ listener = mFrameAvailableListener.promote();
+ }
+
+ if (listener != NULL) {
+ ALOGV("actually calling onFrameAvailable");
+ listener->onFrameAvailable();
+ }
+}
+
+void GonkConsumerBase::onBuffersReleased() {
+ Mutex::Autolock lock(mMutex);
+
+ ALOGV("onBuffersReleased");
+
+ if (mAbandoned) {
+ // Nothing to do if we're already abandoned.
+ return;
+ }
+
+ uint64_t mask = 0;
+ mConsumer->getReleasedBuffers(&mask);
+ for (int i = 0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ if (mask & (1ULL << i)) {
+ freeBufferLocked(i);
+ }
+ }
+}
+
+void GonkConsumerBase::onSidebandStreamChanged() {
+}
+
+void GonkConsumerBase::abandon() {
+ ALOGV("abandon");
+ Mutex::Autolock lock(mMutex);
+
+ if (!mAbandoned) {
+ abandonLocked();
+ mAbandoned = true;
+ }
+}
+
+void GonkConsumerBase::abandonLocked() {
+ ALOGV("abandonLocked");
+ for (int i =0; i < GonkBufferQueue::NUM_BUFFER_SLOTS; i++) {
+ freeBufferLocked(i);
+ }
+ // disconnect from the BufferQueue
+ mConsumer->consumerDisconnect();
+ mConsumer.clear();
+}
+
+void GonkConsumerBase::setFrameAvailableListener(
+ const wp<FrameAvailableListener>& listener) {
+ ALOGV("setFrameAvailableListener");
+ Mutex::Autolock lock(mMutex);
+ mFrameAvailableListener = listener;
+}
+
+void GonkConsumerBase::dump(String8& result) const {
+ dump(result, "");
+}
+
+void GonkConsumerBase::dump(String8& result, const char* prefix) const {
+ Mutex::Autolock _l(mMutex);
+ dumpLocked(result, prefix);
+}
+
+void GonkConsumerBase::dumpLocked(String8& result, const char* prefix) const {
+ result.appendFormat("%smAbandoned=%d\n", prefix, int(mAbandoned));
+
+ if (!mAbandoned) {
+ mConsumer->dumpToString(result, prefix);
+ }
+}
+
+status_t GonkConsumerBase::acquireBufferLocked(GonkBufferQueue::BufferItem *item,
+ nsecs_t presentWhen) {
+ status_t err = mConsumer->acquireBuffer(item, presentWhen);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ if (item->mGraphicBuffer != NULL) {
+ mSlots[item->mBuf].mGraphicBuffer = item->mGraphicBuffer;
+ }
+
+ mSlots[item->mBuf].mFrameNumber = item->mFrameNumber;
+ mSlots[item->mBuf].mFence = item->mFence;
+
+ ALOGV("acquireBufferLocked: -> slot=%d/%" PRIu64,
+ item->mBuf, item->mFrameNumber);
+
+ return OK;
+}
+
+status_t GonkConsumerBase::addReleaseFence(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
+ Mutex::Autolock lock(mMutex);
+ return addReleaseFenceLocked(slot, graphicBuffer, fence);
+}
+
+status_t GonkConsumerBase::addReleaseFenceLocked(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence) {
+ ALOGV("addReleaseFenceLocked: slot=%d", slot);
+
+ // If consumer no longer tracks this graphicBuffer, we can safely
+ // drop this fence, as it will never be received by the producer.
+ if (!stillTracking(slot, graphicBuffer)) {
+ return OK;
+ }
+
+ if (!mSlots[slot].mFence.get()) {
+ mSlots[slot].mFence = fence;
+ } else {
+ sp<Fence> mergedFence = Fence::merge(
+ String8::format("%.28s:%d", mName.string(), slot),
+ mSlots[slot].mFence, fence);
+ if (!mergedFence.get()) {
+ ALOGE("failed to merge release fences");
+ // synchronization is broken, the best we can do is hope fences
+ // signal in order so the new fence will act like a union
+ mSlots[slot].mFence = fence;
+ return BAD_VALUE;
+ }
+ mSlots[slot].mFence = mergedFence;
+ }
+
+ return OK;
+}
+
+status_t GonkConsumerBase::releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer) {
+ // If consumer no longer tracks this graphicBuffer (we received a new
+ // buffer on the same slot), the buffer producer is definitely no longer
+ // tracking it.
+ if (!stillTracking(slot, graphicBuffer)) {
+ return OK;
+ }
+
+ ALOGV("releaseBufferLocked: slot=%d/%" PRIu64,
+ slot, mSlots[slot].mFrameNumber);
+ status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber, mSlots[slot].mFence);
+ if (err == IGonkGraphicBufferConsumer::STALE_BUFFER_SLOT) {
+ freeBufferLocked(slot);
+ }
+
+ mSlots[slot].mFence = Fence::NO_FENCE;
+
+ return err;
+}
+
+bool GonkConsumerBase::stillTracking(int slot,
+ const sp<GraphicBuffer> graphicBuffer) {
+ if (slot < 0 || slot >= GonkBufferQueue::NUM_BUFFER_SLOTS) {
+ return false;
+ }
+ return (mSlots[slot].mGraphicBuffer != NULL &&
+ mSlots[slot].mGraphicBuffer->handle == graphicBuffer->handle);
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkConsumerBaseLL.h b/widget/gonk/nativewindow/GonkConsumerBaseLL.h
new file mode 100644
index 0000000000..0b2c2d1664
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkConsumerBaseLL.h
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKCONSUMERBASE_LL_H
+#define NATIVEWINDOW_GONKCONSUMERBASE_LL_H
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <gui/IConsumerListener.h>
+
+#include "GonkBufferQueueLL.h"
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class String8;
+
+// GonkConsumerBase is a base class for GonkBufferQueue consumer end-points. It
+// handles common tasks like management of the connection to the GonkBufferQueue
+// and the buffer pool.
+class GonkConsumerBase : public virtual RefBase,
+ protected ConsumerListener {
+public:
+ struct FrameAvailableListener : public virtual RefBase {
+ // onFrameAvailable() is called each time an additional frame becomes
+ // available for consumption. This means that frames that are queued
+ // while in asynchronous mode only trigger the callback if no previous
+ // frames are pending. Frames queued while in synchronous mode always
+ // trigger the callback.
+ //
+ // This is called without any lock held and can be called concurrently
+ // by multiple threads.
+ virtual void onFrameAvailable() = 0;
+ };
+
+ virtual ~GonkConsumerBase();
+
+ // abandon frees all the buffers and puts the GonkConsumerBase into the
+ // 'abandoned' state. Once put in this state the GonkConsumerBase can never
+ // leave it. When in the 'abandoned' state, all methods of the
+ // IGraphicBufferProducer interface will fail with the NO_INIT error.
+ //
+ // Note that while calling this method causes all the buffers to be freed
+ // from the perspective of the the GonkConsumerBase, if there are additional
+ // references on the buffers (e.g. if a buffer is referenced by a client
+ // or by OpenGL ES as a texture) then those buffer will remain allocated.
+ void abandon();
+
+ // set the name of the GonkConsumerBase that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // dump writes the current state to a string. Child classes should add
+ // their state to the dump by overriding the dumpLocked method, which is
+ // called by these methods after locking the mutex.
+ void dump(String8& result) const;
+ void dump(String8& result, const char* prefix) const;
+
+ // setFrameAvailableListener sets the listener object that will be notified
+ // when a new frame becomes available.
+ void setFrameAvailableListener(const wp<FrameAvailableListener>& listener);
+
+private:
+ GonkConsumerBase(const GonkConsumerBase&);
+ void operator=(const GonkConsumerBase&);
+
+protected:
+ // GonkConsumerBase constructs a new GonkConsumerBase object to consume image
+ // buffers from the given IGonkGraphicBufferConsumer.
+ // The controlledByApp flag indicates that this consumer is under the application's
+ // control.
+ GonkConsumerBase(const sp<IGonkGraphicBufferConsumer>& consumer, bool controlledByApp = false);
+
+ // onLastStrongRef gets called by RefBase just before the dtor of the most
+ // derived class. It is used to clean up the buffers so that GonkConsumerBase
+ // can coordinate the clean-up by calling into virtual methods implemented
+ // by the derived classes. This would not be possible from the
+ // ConsuemrBase dtor because by the time that gets called the derived
+ // classes have already been destructed.
+ //
+ // This methods should not need to be overridden by derived classes, but
+ // if they are overridden the GonkConsumerBase implementation must be called
+ // from the derived class.
+ virtual void onLastStrongRef(const void* id);
+
+ // Implementation of the IConsumerListener interface. These
+ // calls are used to notify the GonkConsumerBase of asynchronous events in the
+ // GonkBufferQueue. The onFrameAvailable and onBuffersReleased methods should
+ // not need to be overridden by derived classes, but if they are overridden
+ // the GonkConsumerBase implementation must be called from the derived class.
+ // The GonkConsumerBase version of onSidebandStreamChanged does nothing and can
+ // be overriden by derived classes if they want the notification.
+#if ANDROID_VERSION == 21
+ virtual void onFrameAvailable();
+#else
+ virtual void onFrameAvailable(const ::android::BufferItem& item);
+ virtual void onFrameReplaced(const ::android::BufferItem& item) {};
+#endif
+ virtual void onBuffersReleased();
+ virtual void onSidebandStreamChanged();
+
+ // freeBufferLocked frees up the given buffer slot. If the slot has been
+ // initialized this will release the reference to the GraphicBuffer in that
+ // slot. Otherwise it has no effect.
+ //
+ // Derived classes should override this method to clean up any state they
+ // keep per slot. If it is overridden, the derived class's implementation
+ // must call GonkConsumerBase::freeBufferLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void freeBufferLocked(int slotIndex);
+
+ // abandonLocked puts the GonkBufferQueue into the abandoned state, causing
+ // all future operations on it to fail. This method rather than the public
+ // abandon method should be overridden by child classes to add abandon-
+ // time behavior.
+ //
+ // Derived classes should override this method to clean up any object
+ // state they keep (as opposed to per-slot state). If it is overridden,
+ // the derived class's implementation must call GonkConsumerBase::abandonLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void abandonLocked();
+
+ // dumpLocked dumps the current state of the GonkConsumerBase object to the
+ // result string. Each line is prefixed with the string pointed to by the
+ // prefix argument. The buffer argument points to a buffer that may be
+ // used for intermediate formatting data, and the size of that buffer is
+ // indicated by the size argument.
+ //
+ // Derived classes should override this method to dump their internal
+ // state. If this method is overridden the derived class's implementation
+ // should call GonkConsumerBase::dumpLocked.
+ //
+ // This method must be called with mMutex locked.
+ virtual void dumpLocked(String8& result, const char* prefix) const;
+
+ // acquireBufferLocked fetches the next buffer from the GonkBufferQueue and
+ // updates the buffer slot for the buffer returned.
+ //
+ // Derived classes should override this method to perform any
+ // initialization that must take place the first time a buffer is assigned
+ // to a slot. If it is overridden the derived class's implementation must
+ // call GonkConsumerBase::acquireBufferLocked.
+ virtual status_t acquireBufferLocked(IGonkGraphicBufferConsumer::BufferItem *item,
+ nsecs_t presentWhen);
+
+ // releaseBufferLocked relinquishes control over a buffer, returning that
+ // control to the GonkBufferQueue.
+ //
+ // Derived classes should override this method to perform any cleanup that
+ // must take place when a buffer is released back to the GonkBufferQueue. If
+ // it is overridden the derived class's implementation must call
+ // GonkConsumerBase::releaseBufferLocked.
+ virtual status_t releaseBufferLocked(int slot, const sp<GraphicBuffer> graphicBuffer);
+
+ // returns true iff the slot still has the graphicBuffer in it.
+ bool stillTracking(int slot, const sp<GraphicBuffer> graphicBuffer);
+
+ // addReleaseFence* adds the sync points associated with a fence to the set
+ // of sync points that must be reached before the buffer in the given slot
+ // may be used after the slot has been released. This should be called by
+ // derived classes each time some asynchronous work is kicked off that
+ // references the buffer.
+ status_t addReleaseFence(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence);
+ status_t addReleaseFenceLocked(int slot,
+ const sp<GraphicBuffer> graphicBuffer, const sp<Fence>& fence);
+
+ // Slot contains the information and object references that
+ // GonkConsumerBase maintains about a GonkBufferQueue buffer slot.
+ struct Slot {
+ // mGraphicBuffer is the Gralloc buffer store in the slot or NULL if
+ // no Gralloc buffer is in the slot.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence which will signal when the buffer associated with
+ // this buffer slot is no longer being used by the consumer and can be
+ // overwritten. The buffer can be dequeued before the fence signals;
+ // the producer is responsible for delaying writes until it signals.
+ sp<Fence> mFence;
+
+ // the frame number of the last acquired frame for this slot
+ uint64_t mFrameNumber;
+ };
+
+ // mSlots stores the buffers that have been allocated by the GonkBufferQueue
+ // for each buffer slot. It is initialized to null pointers, and gets
+ // filled in with the result of GonkBufferQueue::acquire when the
+ // client dequeues a buffer from a
+ // slot that has not yet been used. The buffer allocated to a slot will also
+ // be replaced if the requested buffer usage or geometry differs from that
+ // of the buffer allocated to a slot.
+ Slot mSlots[GonkBufferQueue::NUM_BUFFER_SLOTS];
+
+ // mAbandoned indicates that the GonkBufferQueue will no longer be used to
+ // consume images buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the abandon
+ // method. A GonkBufferQueue that has been abandoned will return the NO_INIT
+ // error from all IConsumerBase methods capable of returning an error.
+ bool mAbandoned;
+
+ // mName is a string used to identify the GonkConsumerBase in log messages.
+ // It can be set by the setName method.
+ String8 mName;
+
+ // mFrameAvailableListener is the listener object that will be called when a
+ // new frame becomes available. If it is not NULL it will be called from
+ // queueBuffer.
+ wp<FrameAvailableListener> mFrameAvailableListener;
+
+ // The GonkConsumerBase has-a GonkBufferQueue and is responsible for creating this object
+ // if none is supplied
+ sp<IGonkGraphicBufferConsumer> mConsumer;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of GonkConsumerBase objects. It must be locked whenever the
+ // member variables are accessed or when any of the *Locked methods are
+ // called.
+ //
+ // This mutex is intended to be locked by derived classes.
+ mutable Mutex mMutex;
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // NATIVEWINDOW_GONKCONSUMERBASE_LL_H
diff --git a/widget/gonk/nativewindow/GonkNativeWindow.h b/widget/gonk/nativewindow/GonkNativeWindow.h
new file mode 100644
index 0000000000..61b6780b83
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindow.h
@@ -0,0 +1,22 @@
+/* Copyright 2013 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.
+ */
+
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
+# include "GonkNativeWindowLL.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19
+# include "GonkNativeWindowKK.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+# include "GonkNativeWindowJB.h"
+#endif
diff --git a/widget/gonk/nativewindow/GonkNativeWindowJB.cpp b/widget/gonk/nativewindow/GonkNativeWindowJB.cpp
new file mode 100644
index 0000000000..e386420097
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowJB.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "GonkNativeWindow"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Log.h>
+
+#include "GonkNativeWindowJB.h"
+#include "GrallocImages.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/RefPtr.h"
+
+#define BI_LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define BI_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define BI_LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define BI_LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define BI_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+namespace android {
+
+GonkNativeWindow::GonkNativeWindow(int bufferCount) :
+ GonkConsumerBase(new GonkBufferQueue(true) ),
+ mNewFrameCallback(nullptr)
+{
+ mBufferQueue->setMaxAcquiredBufferCount(bufferCount);
+}
+
+GonkNativeWindow::~GonkNativeWindow() {
+}
+
+void GonkNativeWindow::setName(const String8& name) {
+ Mutex::Autolock _l(mMutex);
+ mName = name;
+ mBufferQueue->setConsumerName(name);
+}
+#if ANDROID_VERSION >= 18
+status_t GonkNativeWindow::acquireBuffer(BufferItem *item, bool waitForFence) {
+ status_t err;
+
+ if (!item) return BAD_VALUE;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = acquireBufferLocked(item);
+ if (err != OK) {
+ if (err != NO_BUFFER_AVAILABLE) {
+ BI_LOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
+ }
+ return err;
+ }
+
+ if (waitForFence) {
+ err = item->mFence->waitForever("GonkNativeWindow::acquireBuffer");
+ if (err != OK) {
+ BI_LOGE("Failed to wait for fence of acquired buffer: %s (%d)",
+ strerror(-err), err);
+ return err;
+ }
+ }
+
+ item->mGraphicBuffer = mSlots[item->mBuf].mGraphicBuffer;
+
+ return OK;
+}
+
+status_t GonkNativeWindow::releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence) {
+ status_t err;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = addReleaseFenceLocked(item.mBuf, releaseFence);
+
+ err = releaseBufferLocked(item.mBuf);
+ if (err != OK) {
+ BI_LOGE("Failed to release buffer: %s (%d)",
+ strerror(-err), err);
+ }
+ return err;
+}
+#endif
+
+status_t GonkNativeWindow::setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Mutex::Autolock _l(mMutex);
+ return mBufferQueue->setDefaultBufferSize(w, h);
+}
+
+status_t GonkNativeWindow::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock _l(mMutex);
+ return mBufferQueue->setDefaultBufferFormat(defaultFormat);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getCurrentBuffer() {
+ Mutex::Autolock _l(mMutex);
+ GonkBufferQueue::BufferItem item;
+
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ status_t err = acquireBufferLocked(&item);
+ if (err != NO_ERROR) {
+ return NULL;
+ }
+
+ RefPtr<TextureClient> textureClient =
+ mBufferQueue->getTextureClientFromBuffer(item.mGraphicBuffer.get());
+ if (!textureClient) {
+ return NULL;
+ }
+ textureClient->SetRecycleCallback(GonkNativeWindow::RecycleCallback, this);
+ return textureClient.forget();
+}
+
+/* static */ void
+GonkNativeWindow::RecycleCallback(TextureClient* client, void* closure) {
+ GonkNativeWindow* nativeWindow =
+ static_cast<GonkNativeWindow*>(closure);
+
+ MOZ_ASSERT(client && !client->IsDead());
+ client->ClearRecycleCallback();
+ nativeWindow->returnBuffer(client);
+}
+
+void GonkNativeWindow::returnBuffer(TextureClient* client) {
+ BI_LOGD("GonkNativeWindow::returnBuffer");
+ Mutex::Autolock lock(mMutex);
+
+ int index = mBufferQueue->getSlotFromTextureClientLocked(client);
+ if (index < 0) {
+ }
+
+ FenceHandle handle = client->GetAndResetReleaseFenceHandle();
+ RefPtr<FenceHandle::FdObj> fdObj = handle.GetAndResetFdObj();
+ sp<Fence> fence = new Fence(fdObj->GetAndResetFd());
+
+ addReleaseFenceLocked(index, fence);
+
+ releaseBufferLocked(index);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getTextureClientFromBuffer(ANativeWindowBuffer* buffer) {
+ Mutex::Autolock lock(mMutex);
+ return mBufferQueue->getTextureClientFromBuffer(buffer);
+}
+
+void GonkNativeWindow::setNewFrameCallback(
+ GonkNativeWindowNewFrameCallback* callback) {
+ BI_LOGD("setNewFrameCallback");
+ Mutex::Autolock lock(mMutex);
+ mNewFrameCallback = callback;
+}
+
+void GonkNativeWindow::onFrameAvailable() {
+ GonkConsumerBase::onFrameAvailable();
+
+ if (mNewFrameCallback) {
+ mNewFrameCallback->OnNewFrame();
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkNativeWindowJB.h b/widget/gonk/nativewindow/GonkNativeWindowJB.h
new file mode 100644
index 0000000000..e63a7527d8
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowJB.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKNATIVEWINDOW_JB_H
+#define NATIVEWINDOW_GONKNATIVEWINDOW_JB_H
+
+#include <ui/GraphicBuffer.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "GonkConsumerBaseJB.h"
+#include "GrallocImages.h"
+#include "mozilla/layers/LayersSurfaces.h"
+
+namespace mozilla {
+namespace layers {
+ class PGrallocBufferChild;
+}
+}
+
+namespace android {
+
+// The user of GonkNativeWindow who wants to receive notification of
+// new frames should implement this interface.
+class GonkNativeWindowNewFrameCallback {
+public:
+ virtual void OnNewFrame() = 0;
+};
+
+/**
+ * GonkNativeWindow is a GonkBufferQueue consumer endpoint that allows clients
+ * access to the whole BufferItem entry from GonkBufferQueue. Multiple buffers may
+ * be acquired at once, to be used concurrently by the client. This consumer can
+ * operate either in synchronous or asynchronous mode.
+ */
+class GonkNativeWindow: public GonkConsumerBase
+{
+ typedef mozilla::layers::TextureClient TextureClient;
+ public:
+ typedef GonkConsumerBase::FrameAvailableListener FrameAvailableListener;
+
+ typedef GonkBufferQueue::BufferItem BufferItem;
+
+ enum { INVALID_BUFFER_SLOT = GonkBufferQueue::INVALID_BUFFER_SLOT };
+ enum { NO_BUFFER_AVAILABLE = GonkBufferQueue::NO_BUFFER_AVAILABLE };
+
+ // Create a new buffer item consumer. The consumerUsage parameter determines
+ // the consumer usage flags passed to the graphics allocator. The
+ // bufferCount parameter specifies how many buffers can be locked for user
+ // access at the same time.
+ GonkNativeWindow(int bufferCount = GonkBufferQueue::MIN_UNDEQUEUED_BUFFERS);
+
+ virtual ~GonkNativeWindow();
+
+ // set the name of the GonkNativeWindow that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // Gets the next graphics buffer from the producer, filling out the
+ // passed-in BufferItem structure. Returns NO_BUFFER_AVAILABLE if the queue
+ // of buffers is empty, and INVALID_OPERATION if the maximum number of
+ // buffers is already acquired.
+ //
+ // Only a fixed number of buffers can be acquired at a time, determined by
+ // the construction-time bufferCount parameter. If INVALID_OPERATION is
+ // returned by acquireBuffer, then old buffers must be returned to the
+ // queue by calling releaseBuffer before more buffers can be acquired.
+ //
+ // If waitForFence is true, and the acquired BufferItem has a valid fence object,
+ // acquireBuffer will wait on the fence with no timeout before returning.
+#if ANDROID_VERSION >= 18
+ status_t acquireBuffer(BufferItem *item, bool waitForFence = true);
+#endif
+ // Returns an acquired buffer to the queue, allowing it to be reused. Since
+ // only a fixed number of buffers may be acquired at a time, old buffers
+ // must be released by calling releaseBuffer to ensure new buffers can be
+ // acquired by acquireBuffer. Once a BufferItem is released, the caller must
+ // not access any members of the BufferItem, and should immediately remove
+ // all of its references to the BufferItem itself.
+#if ANDROID_VERSION >= 18
+ status_t releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence = Fence::NO_FENCE);
+#endif
+
+ sp<IGraphicBufferProducer> getProducerInterface() const { return getBufferQueue(); }
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // requestBuffers when a with and height of zero is requested.
+ status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultBufferFormat allows the BufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer
+ status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // Get next frame from the queue, caller owns the returned buffer.
+ already_AddRefed<TextureClient> getCurrentBuffer();
+
+ // Return the buffer to the queue and mark it as FREE. After that
+ // the buffer is useable again for the decoder.
+ void returnBuffer(TextureClient* client);
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ void setNewFrameCallback(GonkNativeWindowNewFrameCallback* callback);
+
+ static void RecycleCallback(TextureClient* client, void* closure);
+
+protected:
+ virtual void onFrameAvailable();
+
+private:
+ GonkNativeWindowNewFrameCallback* mNewFrameCallback;
+};
+
+} // namespace android
+
+#endif // NATIVEWINDOW_GONKNATIVEWINDOW_JB_H
diff --git a/widget/gonk/nativewindow/GonkNativeWindowKK.cpp b/widget/gonk/nativewindow/GonkNativeWindowKK.cpp
new file mode 100644
index 0000000000..cf34d65394
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowKK.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "GonkNativeWindow"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Log.h>
+
+#include "GonkNativeWindowKK.h"
+#include "GrallocImages.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+namespace android {
+
+GonkNativeWindow::GonkNativeWindow(int bufferCount) :
+ GonkConsumerBase(new GonkBufferQueue(true), false),
+ mNewFrameCallback(nullptr)
+{
+ mConsumer->setMaxAcquiredBufferCount(bufferCount);
+}
+
+GonkNativeWindow::GonkNativeWindow(const sp<GonkBufferQueue>& bq,
+ uint32_t consumerUsage, int bufferCount, bool controlledByApp) :
+ GonkConsumerBase(bq, controlledByApp)
+{
+ mConsumer->setConsumerUsageBits(consumerUsage);
+ mConsumer->setMaxAcquiredBufferCount(bufferCount);
+}
+
+GonkNativeWindow::~GonkNativeWindow() {
+}
+
+void GonkNativeWindow::setName(const String8& name) {
+ Mutex::Autolock _l(mMutex);
+ mName = name;
+ mConsumer->setConsumerName(name);
+}
+
+status_t GonkNativeWindow::acquireBuffer(BufferItem *item,
+ nsecs_t presentWhen, bool waitForFence) {
+ status_t err;
+
+ if (!item) return BAD_VALUE;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = acquireBufferLocked(item, presentWhen);
+ if (err != OK) {
+ if (err != NO_BUFFER_AVAILABLE) {
+ ALOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
+ }
+ return err;
+ }
+
+ if (waitForFence) {
+ err = item->mFence->waitForever("GonkNativeWindow::acquireBuffer");
+ if (err != OK) {
+ ALOGE("Failed to wait for fence of acquired buffer: %s (%d)",
+ strerror(-err), err);
+ return err;
+ }
+ }
+
+ item->mGraphicBuffer = mSlots[item->mBuf].mGraphicBuffer;
+
+ return OK;
+}
+
+status_t GonkNativeWindow::releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence) {
+ status_t err;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = addReleaseFenceLocked(item.mBuf, item.mGraphicBuffer, releaseFence);
+
+ err = releaseBufferLocked(item.mBuf, item.mGraphicBuffer);
+ if (err != OK) {
+ ALOGE("Failed to release buffer: %s (%d)",
+ strerror(-err), err);
+ }
+ return err;
+}
+
+status_t GonkNativeWindow::setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Mutex::Autolock _l(mMutex);
+ return mConsumer->setDefaultBufferSize(w, h);
+}
+
+status_t GonkNativeWindow::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock _l(mMutex);
+ return mConsumer->setDefaultBufferFormat(defaultFormat);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getCurrentBuffer() {
+ Mutex::Autolock _l(mMutex);
+ BufferItem item;
+
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ status_t err = acquireBufferLocked(&item, 0); //???
+ if (err != NO_ERROR) {
+ return NULL;
+ }
+
+ RefPtr<TextureClient> textureClient =
+ mConsumer->getTextureClientFromBuffer(item.mGraphicBuffer.get());
+ if (!textureClient) {
+ return NULL;
+ }
+ textureClient->SetRecycleCallback(GonkNativeWindow::RecycleCallback, this);
+ return textureClient.forget();
+}
+
+/* static */ void
+GonkNativeWindow::RecycleCallback(TextureClient* client, void* closure) {
+ GonkNativeWindow* nativeWindow =
+ static_cast<GonkNativeWindow*>(closure);
+
+ MOZ_ASSERT(client && !client->IsDead());
+ client->ClearRecycleCallback();
+ nativeWindow->returnBuffer(client);
+}
+
+void GonkNativeWindow::returnBuffer(TextureClient* client) {
+ ALOGD("GonkNativeWindow::returnBuffer");
+ Mutex::Autolock lock(mMutex);
+
+ int index = mConsumer->getSlotFromTextureClientLocked(client);
+ if (index < 0) {
+ }
+
+ FenceHandle handle = client->GetAndResetReleaseFenceHandle();
+ RefPtr<FenceHandle::FdObj> fdObj = handle.GetAndResetFdObj();
+ sp<Fence> fence = new Fence(fdObj->GetAndResetFd());
+
+ addReleaseFenceLocked(index,
+ mSlots[index].mGraphicBuffer,
+ fence);
+
+ releaseBufferLocked(index, mSlots[index].mGraphicBuffer);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getTextureClientFromBuffer(ANativeWindowBuffer* buffer) {
+ Mutex::Autolock lock(mMutex);
+ return mConsumer->getTextureClientFromBuffer(buffer);
+}
+
+void GonkNativeWindow::setNewFrameCallback(
+ GonkNativeWindowNewFrameCallback* callback) {
+ ALOGD("setNewFrameCallback");
+ Mutex::Autolock lock(mMutex);
+ mNewFrameCallback = callback;
+}
+
+void GonkNativeWindow::onFrameAvailable() {
+ GonkConsumerBase::onFrameAvailable();
+
+ if (mNewFrameCallback) {
+ mNewFrameCallback->OnNewFrame();
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkNativeWindowKK.h b/widget/gonk/nativewindow/GonkNativeWindowKK.h
new file mode 100644
index 0000000000..e36788b41a
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowKK.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKNATIVEWINDOW_KK_H
+#define NATIVEWINDOW_GONKNATIVEWINDOW_KK_H
+
+#include <ui/GraphicBuffer.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "GonkConsumerBaseKK.h"
+#include "GrallocImages.h"
+#include "IGonkGraphicBufferConsumer.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/LayersSurfaces.h"
+
+namespace mozilla {
+namespace layers {
+ class PGrallocBufferChild;
+}
+}
+
+namespace android {
+
+// The user of GonkNativeWindow who wants to receive notification of
+// new frames should implement this interface.
+class GonkNativeWindowNewFrameCallback {
+public:
+ virtual void OnNewFrame() = 0;
+};
+
+/**
+ * GonkNativeWindow is a GonkBufferQueue consumer endpoint that allows clients
+ * access to the whole BufferItem entry from GonkBufferQueue. Multiple buffers may
+ * be acquired at once, to be used concurrently by the client. This consumer can
+ * operate either in synchronous or asynchronous mode.
+ */
+class GonkNativeWindow: public GonkConsumerBase
+{
+ typedef mozilla::layers::TextureClient TextureClient;
+ public:
+ typedef GonkConsumerBase::FrameAvailableListener FrameAvailableListener;
+ typedef IGonkGraphicBufferConsumer::BufferItem BufferItem;
+
+ enum { INVALID_BUFFER_SLOT = GonkBufferQueue::INVALID_BUFFER_SLOT };
+ enum { NO_BUFFER_AVAILABLE = GonkBufferQueue::NO_BUFFER_AVAILABLE };
+
+ // Create a new buffer item consumer. The consumerUsage parameter determines
+ // the consumer usage flags passed to the graphics allocator. The
+ // bufferCount parameter specifies how many buffers can be locked for user
+ // access at the same time.
+ // controlledByApp tells whether this consumer is controlled by the
+ // application.
+ GonkNativeWindow(int bufferCount = GonkBufferQueue::MIN_UNDEQUEUED_BUFFERS);
+ GonkNativeWindow(const sp<GonkBufferQueue>& bq, uint32_t consumerUsage,
+ int bufferCount = GonkBufferQueue::MIN_UNDEQUEUED_BUFFERS,
+ bool controlledByApp = false);
+
+ virtual ~GonkNativeWindow();
+
+ // set the name of the GonkNativeWindow that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // Gets the next graphics buffer from the producer, filling out the
+ // passed-in BufferItem structure. Returns NO_BUFFER_AVAILABLE if the queue
+ // of buffers is empty, and INVALID_OPERATION if the maximum number of
+ // buffers is already acquired.
+ //
+ // Only a fixed number of buffers can be acquired at a time, determined by
+ // the construction-time bufferCount parameter. If INVALID_OPERATION is
+ // returned by acquireBuffer, then old buffers must be returned to the
+ // queue by calling releaseBuffer before more buffers can be acquired.
+ //
+ // If waitForFence is true, and the acquired BufferItem has a valid fence object,
+ // acquireBuffer will wait on the fence with no timeout before returning.
+ status_t acquireBuffer(BufferItem *item, nsecs_t presentWhen,
+ bool waitForFence = true);
+
+ // Returns an acquired buffer to the queue, allowing it to be reused. Since
+ // only a fixed number of buffers may be acquired at a time, old buffers
+ // must be released by calling releaseBuffer to ensure new buffers can be
+ // acquired by acquireBuffer. Once a BufferItem is released, the caller must
+ // not access any members of the BufferItem, and should immediately remove
+ // all of its references to the BufferItem itself.
+ status_t releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence = Fence::NO_FENCE);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // requestBuffers when a with and height of zero is requested.
+ status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultBufferFormat allows the BufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer
+ status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // Get next frame from the queue, caller owns the returned buffer.
+ already_AddRefed<TextureClient> getCurrentBuffer();
+
+ // Return the buffer to the queue and mark it as FREE. After that
+ // the buffer is useable again for the decoder.
+ void returnBuffer(TextureClient* client);
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ void setNewFrameCallback(GonkNativeWindowNewFrameCallback* callback);
+
+ static void RecycleCallback(TextureClient* client, void* closure);
+
+protected:
+ virtual void onFrameAvailable();
+
+private:
+ GonkNativeWindowNewFrameCallback* mNewFrameCallback;
+};
+
+} // namespace android
+
+#endif // NATIVEWINDOW_GONKNATIVEWINDOW_JB_H
diff --git a/widget/gonk/nativewindow/GonkNativeWindowLL.cpp b/widget/gonk/nativewindow/GonkNativeWindowLL.cpp
new file mode 100644
index 0000000000..48644a22f0
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowLL.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "GonkNativeWindow"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <utils/Log.h>
+
+#include "GonkNativeWindowLL.h"
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+namespace android {
+
+GonkNativeWindow::GonkNativeWindow(
+ const sp<IGonkGraphicBufferConsumer>& consumer, int bufferCount) :
+ GonkConsumerBase(consumer, false),
+ mNewFrameCallback(nullptr)
+{
+ if (bufferCount != DEFAULT_MAX_BUFFERS) {
+ status_t err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
+ LOG_ALWAYS_FATAL_IF(err != OK,
+ "Failed to set max acquired buffer count to %d", bufferCount);
+ }
+}
+
+GonkNativeWindow::GonkNativeWindow(
+ const sp<IGonkGraphicBufferConsumer>& consumer, uint32_t consumerUsage,
+ int bufferCount, bool controlledByApp) :
+ GonkConsumerBase(consumer, controlledByApp)
+{
+ status_t err = mConsumer->setConsumerUsageBits(consumerUsage);
+ LOG_ALWAYS_FATAL_IF(err != OK,
+ "Failed to set consumer usage bits to %#x", consumerUsage);
+ if (bufferCount != DEFAULT_MAX_BUFFERS) {
+ err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
+ LOG_ALWAYS_FATAL_IF(err != OK,
+ "Failed to set max acquired buffer count to %d", bufferCount);
+ }
+}
+
+GonkNativeWindow::~GonkNativeWindow() {
+}
+
+void GonkNativeWindow::setName(const String8& name) {
+ Mutex::Autolock _l(mMutex);
+ mName = name;
+ mConsumer->setConsumerName(name);
+}
+
+status_t GonkNativeWindow::acquireBuffer(BufferItem *item,
+ nsecs_t presentWhen, bool waitForFence) {
+ status_t err;
+
+ if (!item) return BAD_VALUE;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = acquireBufferLocked(item, presentWhen);
+ if (err != OK) {
+ if (err != NO_BUFFER_AVAILABLE) {
+ ALOGE("Error acquiring buffer: %s (%d)", strerror(err), err);
+ }
+ return err;
+ }
+
+ if (waitForFence) {
+ err = item->mFence->waitForever("GonkNativeWindow::acquireBuffer");
+ if (err != OK) {
+ ALOGE("Failed to wait for fence of acquired buffer: %s (%d)",
+ strerror(-err), err);
+ return err;
+ }
+ }
+
+ item->mGraphicBuffer = mSlots[item->mBuf].mGraphicBuffer;
+
+ return OK;
+}
+
+status_t GonkNativeWindow::releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence) {
+ status_t err;
+
+ Mutex::Autolock _l(mMutex);
+
+ err = addReleaseFenceLocked(item.mBuf, item.mGraphicBuffer, releaseFence);
+
+ err = releaseBufferLocked(item.mBuf, item.mGraphicBuffer);
+ if (err != OK) {
+ ALOGE("Failed to release buffer: %s (%d)",
+ strerror(-err), err);
+ }
+ return err;
+}
+
+status_t GonkNativeWindow::setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Mutex::Autolock _l(mMutex);
+ return mConsumer->setDefaultBufferSize(w, h);
+}
+
+status_t GonkNativeWindow::setDefaultBufferFormat(uint32_t defaultFormat) {
+ Mutex::Autolock _l(mMutex);
+ return mConsumer->setDefaultBufferFormat(defaultFormat);
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getCurrentBuffer() {
+ Mutex::Autolock _l(mMutex);
+ BufferItem item;
+
+ // In asynchronous mode the list is guaranteed to be one buffer
+ // deep, while in synchronous mode we use the oldest buffer.
+ status_t err = acquireBufferLocked(&item, 0); //???
+ if (err != NO_ERROR) {
+ return NULL;
+ }
+
+ RefPtr<TextureClient> textureClient =
+ mConsumer->getTextureClientFromBuffer(item.mGraphicBuffer.get());
+ if (!textureClient) {
+ return NULL;
+ }
+ textureClient->SetRecycleCallback(GonkNativeWindow::RecycleCallback, this);
+ return textureClient.forget();
+}
+
+/* static */ void
+GonkNativeWindow::RecycleCallback(TextureClient* client, void* closure) {
+ GonkNativeWindow* nativeWindow =
+ static_cast<GonkNativeWindow*>(closure);
+
+ MOZ_ASSERT(client && !client->IsDead());
+ client->ClearRecycleCallback();
+ nativeWindow->returnBuffer(client);
+}
+
+void GonkNativeWindow::returnBuffer(TextureClient* client) {
+ ALOGD("GonkNativeWindow::returnBuffer");
+ Mutex::Autolock lock(mMutex);
+
+ int index = mConsumer->getSlotFromTextureClientLocked(client);
+ if (index < 0) {
+ return;
+ }
+
+ FenceHandle handle = client->GetAndResetReleaseFenceHandle();
+ RefPtr<FenceHandle::FdObj> fdObj = handle.GetAndResetFdObj();
+ sp<Fence> fence = new Fence(fdObj->GetAndResetFd());
+
+ status_t err;
+ err = addReleaseFenceLocked(index,
+ mSlots[index].mGraphicBuffer,
+ fence);
+
+ err = releaseBufferLocked(index, mSlots[index].mGraphicBuffer);
+
+ if (err != OK) {
+ ALOGE("Failed to return buffer: %s (%d)", strerror(-err), err);
+ }
+}
+
+already_AddRefed<TextureClient>
+GonkNativeWindow::getTextureClientFromBuffer(ANativeWindowBuffer* buffer) {
+ Mutex::Autolock lock(mMutex);
+ return mConsumer->getTextureClientFromBuffer(buffer);
+}
+
+void GonkNativeWindow::setNewFrameCallback(
+ GonkNativeWindowNewFrameCallback* callback) {
+ ALOGD("setNewFrameCallback");
+ Mutex::Autolock lock(mMutex);
+ mNewFrameCallback = callback;
+}
+
+#if ANDROID_VERSION == 21
+void GonkNativeWindow::onFrameAvailable() {
+ GonkConsumerBase::onFrameAvailable();
+#else
+void GonkNativeWindow::onFrameAvailable(const ::android::BufferItem &item) {
+ GonkConsumerBase::onFrameAvailable(item);
+#endif
+
+ if (mNewFrameCallback) {
+ mNewFrameCallback->OnNewFrame();
+ }
+}
+
+} // namespace android
diff --git a/widget/gonk/nativewindow/GonkNativeWindowLL.h b/widget/gonk/nativewindow/GonkNativeWindowLL.h
new file mode 100644
index 0000000000..64cd6482d3
--- /dev/null
+++ b/widget/gonk/nativewindow/GonkNativeWindowLL.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2014 Mozilla Foundation
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_GONKNATIVEWINDOW_LL_H
+#define NATIVEWINDOW_GONKNATIVEWINDOW_LL_H
+
+#include <ui/GraphicBuffer.h>
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+
+#include "GonkConsumerBaseLL.h"
+#include "IGonkGraphicBufferConsumerLL.h"
+
+namespace android {
+
+// The user of GonkNativeWindow who wants to receive notification of
+// new frames should implement this interface.
+class GonkNativeWindowNewFrameCallback {
+public:
+ virtual void OnNewFrame() = 0;
+};
+
+/**
+ * GonkNativeWindow is a GonkBufferQueue consumer endpoint that allows clients
+ * access to the whole BufferItem entry from GonkBufferQueue. Multiple buffers may
+ * be acquired at once, to be used concurrently by the client. This consumer can
+ * operate either in synchronous or asynchronous mode.
+ */
+class GonkNativeWindow: public GonkConsumerBase
+{
+ typedef mozilla::layers::TextureClient TextureClient;
+ public:
+ typedef GonkConsumerBase::FrameAvailableListener FrameAvailableListener;
+ typedef GonkBufferQueue::BufferItem BufferItem;
+
+ enum { DEFAULT_MAX_BUFFERS = -1 };
+ enum { INVALID_BUFFER_SLOT = GonkBufferQueue::INVALID_BUFFER_SLOT };
+ enum { NO_BUFFER_AVAILABLE = GonkBufferQueue::NO_BUFFER_AVAILABLE };
+
+ // Create a new buffer item consumer. The consumerUsage parameter determines
+ // the consumer usage flags passed to the graphics allocator. The
+ // bufferCount parameter specifies how many buffers can be locked for user
+ // access at the same time.
+ // controlledByApp tells whether this consumer is controlled by the
+ // application.
+ GonkNativeWindow(const sp<IGonkGraphicBufferConsumer>& consumer,
+ int bufferCount = DEFAULT_MAX_BUFFERS);
+ GonkNativeWindow(const sp<IGonkGraphicBufferConsumer>& consumer,
+ uint32_t consumerUsage, int bufferCount = DEFAULT_MAX_BUFFERS,
+ bool controlledByApp = false);
+
+ virtual ~GonkNativeWindow();
+
+ // set the name of the GonkNativeWindow that will be used to identify it in
+ // log messages.
+ void setName(const String8& name);
+
+ // Gets the next graphics buffer from the producer, filling out the
+ // passed-in BufferItem structure. Returns NO_BUFFER_AVAILABLE if the queue
+ // of buffers is empty, and INVALID_OPERATION if the maximum number of
+ // buffers is already acquired.
+ //
+ // Only a fixed number of buffers can be acquired at a time, determined by
+ // the construction-time bufferCount parameter. If INVALID_OPERATION is
+ // returned by acquireBuffer, then old buffers must be returned to the
+ // queue by calling releaseBuffer before more buffers can be acquired.
+ //
+ // If waitForFence is true, and the acquired BufferItem has a valid fence object,
+ // acquireBuffer will wait on the fence with no timeout before returning.
+ status_t acquireBuffer(BufferItem *item, nsecs_t presentWhen,
+ bool waitForFence = true);
+
+ // Returns an acquired buffer to the queue, allowing it to be reused. Since
+ // only a fixed number of buffers may be acquired at a time, old buffers
+ // must be released by calling releaseBuffer to ensure new buffers can be
+ // acquired by acquireBuffer. Once a BufferItem is released, the caller must
+ // not access any members of the BufferItem, and should immediately remove
+ // all of its references to the BufferItem itself.
+ status_t releaseBuffer(const BufferItem &item,
+ const sp<Fence>& releaseFence = Fence::NO_FENCE);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // requestBuffers when a with and height of zero is requested.
+ status_t setDefaultBufferSize(uint32_t w, uint32_t h);
+
+ // setDefaultBufferFormat allows the GonkBufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer
+ status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // Get next frame from the queue, caller owns the returned buffer.
+ already_AddRefed<TextureClient> getCurrentBuffer();
+
+ // Return the buffer to the queue and mark it as FREE. After that
+ // the buffer is useable again for the decoder.
+ void returnBuffer(TextureClient* client);
+
+ already_AddRefed<TextureClient> getTextureClientFromBuffer(ANativeWindowBuffer* buffer);
+
+ void setNewFrameCallback(GonkNativeWindowNewFrameCallback* callback);
+
+ static void RecycleCallback(TextureClient* client, void* closure);
+
+protected:
+#if ANDROID_VERSION == 21
+ virtual void onFrameAvailable();
+#else
+ virtual void onFrameAvailable(const ::android::BufferItem &item);
+#endif
+
+private:
+ GonkNativeWindowNewFrameCallback* mNewFrameCallback;
+};
+
+} // namespace android
+
+#endif // NATIVEWINDOW_GONKNATIVEWINDOW_LL_H
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumer.h b/widget/gonk/nativewindow/IGonkGraphicBufferConsumer.h
new file mode 100644
index 0000000000..14541c9b47
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumer.h
@@ -0,0 +1,20 @@
+/* Copyright 2013 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.
+ */
+
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
+# include "IGonkGraphicBufferConsumerLL.h"
+#elif defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 19
+# include "IGonkGraphicBufferConsumerKK.h"
+#endif
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.cpp b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.cpp
new file mode 100644
index 0000000000..c4c9f65782
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.cpp
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+
+#include <binder/Parcel.h>
+#include <binder/IInterface.h>
+
+#include <gui/IConsumerListener.h>
+#include "IGonkGraphicBufferConsumerKK.h"
+
+#include <ui/GraphicBuffer.h>
+#include <ui/Fence.h>
+
+#include <system/window.h>
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+IGonkGraphicBufferConsumer::BufferItem::BufferItem() :
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mIsAutoTimestamp(false),
+ mFrameNumber(0),
+ mBuf(INVALID_BUFFER_SLOT),
+ mIsDroppable(false),
+ mAcquireCalled(false),
+ mTransformToDisplayInverse(false) {
+ mCrop.makeInvalid();
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getPodSize() const {
+ size_t c = sizeof(mCrop) +
+ sizeof(mTransform) +
+ sizeof(mScalingMode) +
+ sizeof(mTimestamp) +
+ sizeof(mIsAutoTimestamp) +
+ sizeof(mFrameNumber) +
+ sizeof(mBuf) +
+ sizeof(mIsDroppable) +
+ sizeof(mAcquireCalled) +
+ sizeof(mTransformToDisplayInverse);
+ return c;
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getFlattenedSize() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ if (mFence != 0) {
+ c += mFence->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ return sizeof(int32_t) + c + getPodSize();
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getFdCount() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFdCount();
+ }
+ if (mFence != 0) {
+ c += mFence->getFdCount();
+ }
+ return c;
+}
+
+status_t IGonkGraphicBufferConsumer::BufferItem::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const {
+
+ // make sure we have enough space
+ if (count < BufferItem::getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ // content flags are stored first
+ uint32_t& flags = *static_cast<uint32_t*>(buffer);
+
+ // advance the pointer
+ FlattenableUtils::advance(buffer, size, sizeof(uint32_t));
+
+ flags = 0;
+ if (mGraphicBuffer != 0) {
+ status_t err = mGraphicBuffer->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 1;
+ }
+ if (mFence != 0) {
+ status_t err = mFence->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 2;
+ }
+
+ // check we have enough space (in case flattening the fence/graphicbuffer lied to us)
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, mCrop);
+ FlattenableUtils::write(buffer, size, mTransform);
+ FlattenableUtils::write(buffer, size, mScalingMode);
+ FlattenableUtils::write(buffer, size, mTimestamp);
+ FlattenableUtils::write(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::write(buffer, size, mFrameNumber);
+ FlattenableUtils::write(buffer, size, mBuf);
+ FlattenableUtils::write(buffer, size, mIsDroppable);
+ FlattenableUtils::write(buffer, size, mAcquireCalled);
+ FlattenableUtils::write(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+status_t IGonkGraphicBufferConsumer::BufferItem::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+
+ if (size < sizeof(uint32_t))
+ return NO_MEMORY;
+
+ uint32_t flags = 0;
+ FlattenableUtils::read(buffer, size, flags);
+
+ if (flags & 1) {
+ mGraphicBuffer = new GraphicBuffer();
+ status_t err = mGraphicBuffer->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ if (flags & 2) {
+ mFence = new Fence();
+ status_t err = mFence->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ // check we have enough space
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, mCrop);
+ FlattenableUtils::read(buffer, size, mTransform);
+ FlattenableUtils::read(buffer, size, mScalingMode);
+ FlattenableUtils::read(buffer, size, mTimestamp);
+ FlattenableUtils::read(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::read(buffer, size, mFrameNumber);
+ FlattenableUtils::read(buffer, size, mBuf);
+ FlattenableUtils::read(buffer, size, mIsDroppable);
+ FlattenableUtils::read(buffer, size, mAcquireCalled);
+ FlattenableUtils::read(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+// ---------------------------------------------------------------------------
+
+enum {
+ ACQUIRE_BUFFER = IBinder::FIRST_CALL_TRANSACTION,
+ RELEASE_BUFFER,
+ CONSUMER_CONNECT,
+ CONSUMER_DISCONNECT,
+ GET_RELEASED_BUFFERS,
+ SET_DEFAULT_BUFFER_SIZE,
+ SET_DEFAULT_MAX_BUFFER_COUNT,
+ DISABLE_ASYNC_BUFFER,
+ SET_MAX_ACQUIRED_BUFFER_COUNT,
+ SET_CONSUMER_NAME,
+ SET_DEFAULT_BUFFER_FORMAT,
+ SET_CONSUMER_USAGE_BITS,
+ SET_TRANSFORM_HINT,
+ DUMP,
+};
+
+class BpGonkGraphicBufferConsumer : public BpInterface<IGonkGraphicBufferConsumer>
+{
+public:
+ BpGonkGraphicBufferConsumer(const sp<IBinder>& impl)
+ : BpInterface<IGonkGraphicBufferConsumer>(impl)
+ {
+ }
+
+ virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt64(presentWhen);
+ status_t result = remote()->transact(ACQUIRE_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.read(*buffer);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
+ const sp<Fence>& releaseFence) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(buf);
+ data.writeInt64(frameNumber);
+ data.write(*releaseFence);
+ status_t result = remote()->transact(RELEASE_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeStrongBinder(consumer->asBinder());
+ data.writeInt32(controlledByApp);
+ status_t result = remote()->transact(CONSUMER_CONNECT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t consumerDisconnect() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(CONSUMER_DISCONNECT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t getReleasedBuffers(uint32_t* slotMask) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(GET_RELEASED_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ *slotMask = reply.readInt32();
+ return reply.readInt32();
+ }
+
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(w);
+ data.writeInt32(h);
+ status_t result = remote()->transact(SET_DEFAULT_BUFFER_SIZE, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setDefaultMaxBufferCount(int bufferCount) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(bufferCount);
+ status_t result = remote()->transact(SET_DEFAULT_MAX_BUFFER_COUNT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t disableAsyncBuffer() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(DISABLE_ASYNC_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(maxAcquiredBuffers);
+ status_t result = remote()->transact(SET_MAX_ACQUIRED_BUFFER_COUNT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual void setConsumerName(const String8& name) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeString8(name);
+ remote()->transact(SET_CONSUMER_NAME, data, &reply);
+ }
+
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(defaultFormat);
+ status_t result = remote()->transact(SET_DEFAULT_BUFFER_FORMAT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setConsumerUsageBits(uint32_t usage) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(usage);
+ status_t result = remote()->transact(SET_CONSUMER_USAGE_BITS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setTransformHint(uint32_t hint) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(hint);
+ status_t result = remote()->transact(SET_TRANSFORM_HINT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual void dumpToString(String8& result, const char* prefix) const {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeString8(result);
+ data.writeString8(String8(prefix ? prefix : ""));
+ remote()->transact(DUMP, data, &reply);
+ reply.readString8();
+ }
+};
+
+IMPLEMENT_META_INTERFACE(GonkGraphicBufferConsumer, "android.gui.IGonkGraphicBufferConsumer");
+// ----------------------------------------------------------------------
+
+status_t BnGonkGraphicBufferConsumer::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ switch(code) {
+ case ACQUIRE_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ BufferItem item;
+ int64_t presentWhen = data.readInt64();
+ status_t result = acquireBuffer(&item, presentWhen);
+ status_t err = reply->write(item);
+ if (err) return err;
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case RELEASE_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ int buf = data.readInt32();
+ uint64_t frameNumber = data.readInt64();
+ sp<Fence> releaseFence = new Fence();
+ status_t err = data.read(*releaseFence);
+ if (err) return err;
+ status_t result = releaseBuffer(buf, frameNumber, releaseFence);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+
+ case CONSUMER_CONNECT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ sp<IConsumerListener> consumer = IConsumerListener::asInterface( data.readStrongBinder() );
+ bool controlledByApp = data.readInt32();
+ status_t result = consumerConnect(consumer, controlledByApp);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+
+ case CONSUMER_DISCONNECT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ status_t result = consumerDisconnect();
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case GET_RELEASED_BUFFERS: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t slotMask;
+ status_t result = getReleasedBuffers(&slotMask);
+ reply->writeInt32(slotMask);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_BUFFER_SIZE: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t w = data.readInt32();
+ uint32_t h = data.readInt32();
+ status_t result = setDefaultBufferSize(w, h);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_MAX_BUFFER_COUNT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t bufferCount = data.readInt32();
+ status_t result = setDefaultMaxBufferCount(bufferCount);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case DISABLE_ASYNC_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ status_t result = disableAsyncBuffer();
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_MAX_ACQUIRED_BUFFER_COUNT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t maxAcquiredBuffers = data.readInt32();
+ status_t result = setMaxAcquiredBufferCount(maxAcquiredBuffers);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_CONSUMER_NAME: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ setConsumerName( data.readString8() );
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_BUFFER_FORMAT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t defaultFormat = data.readInt32();
+ status_t result = setDefaultBufferFormat(defaultFormat);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_CONSUMER_USAGE_BITS: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t usage = data.readInt32();
+ status_t result = setConsumerUsageBits(usage);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_TRANSFORM_HINT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t hint = data.readInt32();
+ status_t result = setTransformHint(hint);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+
+ case DUMP: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ String8 result = data.readString8();
+ String8 prefix = data.readString8();
+ static_cast<IGonkGraphicBufferConsumer*>(this)->dumpToString(result, prefix);
+ reply->writeString8(result);
+ return NO_ERROR;
+ }
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.h b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.h
new file mode 100644
index 0000000000..ce51e1ef22
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerKK.h
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_IGONKGRAPHICBUFFERCONSUMER_KK_H
+#define NATIVEWINDOW_IGONKGRAPHICBUFFERCONSUMER_KK_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+#include <binder/IInterface.h>
+#include <ui/Rect.h>
+
+#include "mozilla/Types.h"
+#include "mozilla/layers/LayersSurfaces.h"
+
+namespace mozilla {
+
+namespace layers {
+class TextureClient;
+}
+}
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class MOZ_EXPORT IConsumerListener;
+class MOZ_EXPORT GraphicBuffer;
+class MOZ_EXPORT Fence;
+
+class IGonkGraphicBufferConsumer : public IInterface {
+ typedef mozilla::layers::TextureClient TextureClient;
+public:
+
+ // public facing structure for BufferSlot
+ class BufferItem : public Flattenable<BufferItem> {
+ friend class Flattenable<BufferItem>;
+ size_t getPodSize() const;
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ public:
+ enum { INVALID_BUFFER_SLOT = -1 };
+ BufferItem();
+
+ // mGraphicBuffer points to the buffer allocated for this slot, or is NULL
+ // if the buffer in this slot has been acquired in the past (see
+ // BufferSlot.mAcquireCalled).
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence that will signal when the buffer is idle.
+ sp<Fence> mFence;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued.
+ int64_t mTimestamp;
+
+ // mIsAutoTimestamp indicates whether mTimestamp was generated
+ // automatically when the buffer was queued.
+ bool mIsAutoTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot.
+ uint64_t mFrameNumber;
+
+ // mBuf is the slot index of this buffer
+ int mBuf;
+
+ // mIsDroppable whether this buffer was queued with the
+ // property that it can be replaced by a new buffer for the purpose of
+ // making sure dequeueBuffer() won't block.
+ // i.e.: was the BufferQueue in "mDequeueBufferCannotBlock" when this buffer
+ // was queued.
+ bool mIsDroppable;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates this buffer must be transformed by the inverse transform of the screen
+ // it is displayed onto. This is applied after mTransform.
+ bool mTransformToDisplayInverse;
+ };
+
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the BufferQueue. If no buffer is pending then it returns -EINVAL. If a
+ // buffer is successfully acquired, the information about the buffer is
+ // returned in BufferItem. If the buffer returned had previously been
+ // acquired then the BufferItem::mGraphicBuffer field of buffer is set to
+ // NULL and it is assumed that the consumer still holds a reference to the
+ // buffer.
+ //
+ // If presentWhen is nonzero, it indicates the time when the buffer will
+ // be displayed on screen. If the buffer's timestamp is farther in the
+ // future, the buffer won't be acquired, and PRESENT_LATER will be
+ // returned. The presentation time is in nanoseconds, and the time base
+ // is CLOCK_MONOTONIC.
+ virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen) = 0;
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // BufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use. frameNumber is used to indentify the exact buffer returned.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ //
+ // Note that the dependencies on EGL will be removed once we switch to using
+ // the Android HW Sync HAL.
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) = 0;
+
+ // consumerConnect connects a consumer to the BufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // BufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the BufferQueue by the producer to fail.
+ // controlledByApp indicates whether the consumer is controlled by
+ // the application.
+ //
+ // consumer may not be NULL.
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) = 0;
+
+ // consumerDisconnect disconnects a consumer from the BufferQueue. All
+ // buffers will be freed and the BufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the BufferQueue by the producer to
+ // fail.
+ virtual status_t consumerDisconnect() = 0;
+
+ // getReleasedBuffers sets the value pointed to by slotMask to a bit mask
+ // indicating which buffer slots have been released by the BufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ virtual status_t getReleasedBuffers(uint32_t* slotMask) = 0;
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) = 0;
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ virtual status_t setDefaultMaxBufferCount(int bufferCount) = 0;
+
+ // disableAsyncBuffer disables the extra buffer used in async mode
+ // (when both producer and consumer have set their "isControlledByApp"
+ // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
+ //
+ // This can only be called before consumerConnect().
+ virtual status_t disableAsyncBuffer() = 0;
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the BufferQueue.
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) = 0;
+
+ // setConsumerName sets the name used in logging
+ virtual void setConsumerName(const String8& name) = 0;
+
+ // setDefaultBufferFormat allows the BufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat) = 0;
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ virtual status_t setConsumerUsageBits(uint32_t usage) = 0;
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ virtual status_t setTransformHint(uint32_t hint) = 0;
+
+ // dump state into a string
+ virtual void dumpToString(String8& result, const char* prefix) const = 0;
+
+public:
+ DECLARE_META_INTERFACE(GonkGraphicBufferConsumer);
+};
+
+// ----------------------------------------------------------------------------
+
+class BnGonkGraphicBufferConsumer : public BnInterface<IGonkGraphicBufferConsumer>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_IGRAPHICBUFFERCONSUMER_H
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.cpp b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.cpp
new file mode 100644
index 0000000000..729ed67369
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.cpp
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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 <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/NativeHandle.h>
+
+#include <binder/Parcel.h>
+#include <binder/IInterface.h>
+
+#include <gui/IConsumerListener.h>
+#include "IGonkGraphicBufferConsumerLL.h"
+
+#include <ui/GraphicBuffer.h>
+#include <ui/Fence.h>
+
+#include <system/window.h>
+
+#include "mozilla/layers/TextureClient.h"
+
+namespace android {
+// ---------------------------------------------------------------------------
+
+IGonkGraphicBufferConsumer::BufferItem::BufferItem() :
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mIsAutoTimestamp(false),
+ mFrameNumber(0),
+ mBuf(INVALID_BUFFER_SLOT),
+ mIsDroppable(false),
+ mAcquireCalled(false),
+ mTransformToDisplayInverse(false) {
+ mCrop.makeInvalid();
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getPodSize() const {
+ size_t c = sizeof(mCrop) +
+ sizeof(mTransform) +
+ sizeof(mScalingMode) +
+ sizeof(mTimestamp) +
+ sizeof(mIsAutoTimestamp) +
+ sizeof(mFrameNumber) +
+ sizeof(mBuf) +
+ sizeof(mIsDroppable) +
+ sizeof(mAcquireCalled) +
+ sizeof(mTransformToDisplayInverse);
+ return c;
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getFlattenedSize() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFlattenedSize();
+ c = FlattenableUtils::align<4>(c);
+ }
+ if (mFence != 0) {
+ c += mFence->getFlattenedSize();
+ c = FlattenableUtils::align<4>(c);
+ }
+ return sizeof(int32_t) + c + getPodSize();
+}
+
+size_t IGonkGraphicBufferConsumer::BufferItem::getFdCount() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFdCount();
+ }
+ if (mFence != 0) {
+ c += mFence->getFdCount();
+ }
+ return c;
+}
+
+static void writeBoolAsInt(void*& buffer, size_t& size, bool b) {
+ FlattenableUtils::write(buffer, size, static_cast<int32_t>(b));
+}
+
+static bool readBoolFromInt(void const*& buffer, size_t& size) {
+ int32_t i;
+ FlattenableUtils::read(buffer, size, i);
+ return static_cast<bool>(i);
+}
+
+status_t IGonkGraphicBufferConsumer::BufferItem::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const {
+
+ // make sure we have enough space
+ if (size < BufferItem::getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ // content flags are stored first
+ uint32_t& flags = *static_cast<uint32_t*>(buffer);
+
+ // advance the pointer
+ FlattenableUtils::advance(buffer, size, sizeof(uint32_t));
+
+ flags = 0;
+ if (mGraphicBuffer != 0) {
+ status_t err = mGraphicBuffer->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 1;
+ }
+ if (mFence != 0) {
+ status_t err = mFence->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 2;
+ }
+
+ // check we have enough space (in case flattening the fence/graphicbuffer lied to us)
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, mCrop);
+ FlattenableUtils::write(buffer, size, mTransform);
+ FlattenableUtils::write(buffer, size, mScalingMode);
+ FlattenableUtils::write(buffer, size, mTimestamp);
+ writeBoolAsInt(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::write(buffer, size, mFrameNumber);
+ FlattenableUtils::write(buffer, size, mBuf);
+ writeBoolAsInt(buffer, size, mIsDroppable);
+ writeBoolAsInt(buffer, size, mAcquireCalled);
+ writeBoolAsInt(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+status_t IGonkGraphicBufferConsumer::BufferItem::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+
+ if (size < sizeof(uint32_t))
+ return NO_MEMORY;
+
+ uint32_t flags = 0;
+ FlattenableUtils::read(buffer, size, flags);
+
+ if (flags & 1) {
+ mGraphicBuffer = new GraphicBuffer();
+ status_t err = mGraphicBuffer->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ if (flags & 2) {
+ mFence = new Fence();
+ status_t err = mFence->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ // check we have enough space
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, mCrop);
+ FlattenableUtils::read(buffer, size, mTransform);
+ FlattenableUtils::read(buffer, size, mScalingMode);
+ FlattenableUtils::read(buffer, size, mTimestamp);
+ mIsAutoTimestamp = readBoolFromInt(buffer, size);
+ FlattenableUtils::read(buffer, size, mFrameNumber);
+ FlattenableUtils::read(buffer, size, mBuf);
+ mIsDroppable = readBoolFromInt(buffer, size);
+ mAcquireCalled = readBoolFromInt(buffer, size);
+ mTransformToDisplayInverse = readBoolFromInt(buffer, size);
+
+ return NO_ERROR;
+}
+
+// ---------------------------------------------------------------------------
+
+enum {
+ ACQUIRE_BUFFER = IBinder::FIRST_CALL_TRANSACTION,
+ DETACH_BUFFER,
+ ATTACH_BUFFER,
+ RELEASE_BUFFER,
+ CONSUMER_CONNECT,
+ CONSUMER_DISCONNECT,
+ GET_RELEASED_BUFFERS,
+ SET_DEFAULT_BUFFER_SIZE,
+ SET_DEFAULT_MAX_BUFFER_COUNT,
+ DISABLE_ASYNC_BUFFER,
+ SET_MAX_ACQUIRED_BUFFER_COUNT,
+ SET_CONSUMER_NAME,
+ SET_DEFAULT_BUFFER_FORMAT,
+ SET_CONSUMER_USAGE_BITS,
+ SET_TRANSFORM_HINT,
+ GET_SIDEBAND_STREAM,
+ DUMP,
+};
+
+
+class BpGonkGraphicBufferConsumer : public BpInterface<IGonkGraphicBufferConsumer>
+{
+public:
+ BpGonkGraphicBufferConsumer(const sp<IBinder>& impl)
+ : BpInterface<IGonkGraphicBufferConsumer>(impl)
+ {
+ }
+
+ virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt64(presentWhen);
+ status_t result = remote()->transact(ACQUIRE_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.read(*buffer);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t detachBuffer(int slot) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(slot);
+ status_t result = remote()->transact(DETACH_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.readInt32();
+ return result;
+ }
+
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.write(*buffer.get());
+ status_t result = remote()->transact(ATTACH_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ *slot = reply.readInt32();
+ result = reply.readInt32();
+ return result;
+ }
+
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(buf);
+ data.writeInt64(frameNumber);
+ data.write(*releaseFence);
+ status_t result = remote()->transact(RELEASE_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeStrongBinder(consumer->asBinder());
+ data.writeInt32(controlledByApp);
+ status_t result = remote()->transact(CONSUMER_CONNECT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t consumerDisconnect() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(CONSUMER_DISCONNECT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t getReleasedBuffers(uint64_t* slotMask) {
+ Parcel data, reply;
+ if (slotMask == NULL) {
+ ALOGE("getReleasedBuffers: slotMask must not be NULL");
+ return BAD_VALUE;
+ }
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(GET_RELEASED_BUFFERS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ *slotMask = reply.readInt64();
+ return reply.readInt32();
+ }
+
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(w);
+ data.writeInt32(h);
+ status_t result = remote()->transact(SET_DEFAULT_BUFFER_SIZE, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setDefaultMaxBufferCount(int bufferCount) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(bufferCount);
+ status_t result = remote()->transact(SET_DEFAULT_MAX_BUFFER_COUNT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t disableAsyncBuffer() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ status_t result = remote()->transact(DISABLE_ASYNC_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(maxAcquiredBuffers);
+ status_t result = remote()->transact(SET_MAX_ACQUIRED_BUFFER_COUNT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual void setConsumerName(const String8& name) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeString8(name);
+ remote()->transact(SET_CONSUMER_NAME, data, &reply);
+ }
+
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(defaultFormat);
+ status_t result = remote()->transact(SET_DEFAULT_BUFFER_FORMAT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setConsumerUsageBits(uint32_t usage) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(usage);
+ status_t result = remote()->transact(SET_CONSUMER_USAGE_BITS, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual status_t setTransformHint(uint32_t hint) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(hint);
+ status_t result = remote()->transact(SET_TRANSFORM_HINT, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ return reply.readInt32();
+ }
+
+ virtual sp<NativeHandle> getSidebandStream() const {
+ Parcel data, reply;
+ status_t err;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ if ((err = remote()->transact(GET_SIDEBAND_STREAM, data, &reply)) != NO_ERROR) {
+ return NULL;
+ }
+ sp<NativeHandle> stream;
+ if (reply.readInt32()) {
+ stream = NativeHandle::create(reply.readNativeHandle(), true);
+ }
+ return stream;
+ }
+
+ virtual void dumpToString(String8& result, const char* prefix) const {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGonkGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeString8(result);
+ data.writeString8(String8(prefix ? prefix : ""));
+ remote()->transact(DUMP, data, &reply);
+ reply.readString8();
+ }
+
+ // Added by mozilla
+ virtual already_AddRefed<mozilla::layers::TextureClient>
+ getTextureClientFromBuffer(ANativeWindowBuffer* buffer)
+ {
+ return nullptr;
+ }
+
+ virtual int
+ getSlotFromTextureClientLocked(mozilla::layers::TextureClient* client) const
+ {
+ return BAD_VALUE;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(GonkGraphicBufferConsumer, "android.gui.IGonkGraphicBufferConsumer");
+
+// ----------------------------------------------------------------------
+
+status_t BnGonkGraphicBufferConsumer::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ switch(code) {
+ case ACQUIRE_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ BufferItem item;
+ int64_t presentWhen = data.readInt64();
+ status_t result = acquireBuffer(&item, presentWhen);
+ status_t err = reply->write(item);
+ if (err) return err;
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case DETACH_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ int slot = data.readInt32();
+ int result = detachBuffer(slot);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case ATTACH_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ sp<GraphicBuffer> buffer = new GraphicBuffer();
+ data.read(*buffer.get());
+ int slot;
+ int result = attachBuffer(&slot, buffer);
+ reply->writeInt32(slot);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case RELEASE_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ int buf = data.readInt32();
+ uint64_t frameNumber = data.readInt64();
+ sp<Fence> releaseFence = new Fence();
+ status_t err = data.read(*releaseFence);
+ if (err) return err;
+ status_t result = releaseBuffer(buf, frameNumber, releaseFence);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case CONSUMER_CONNECT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ sp<IConsumerListener> consumer = IConsumerListener::asInterface( data.readStrongBinder() );
+ bool controlledByApp = data.readInt32();
+ status_t result = consumerConnect(consumer, controlledByApp);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case CONSUMER_DISCONNECT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ status_t result = consumerDisconnect();
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case GET_RELEASED_BUFFERS: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint64_t slotMask;
+ status_t result = getReleasedBuffers(&slotMask);
+ reply->writeInt64(slotMask);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_BUFFER_SIZE: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t w = data.readInt32();
+ uint32_t h = data.readInt32();
+ status_t result = setDefaultBufferSize(w, h);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_MAX_BUFFER_COUNT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t bufferCount = data.readInt32();
+ status_t result = setDefaultMaxBufferCount(bufferCount);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case DISABLE_ASYNC_BUFFER: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ status_t result = disableAsyncBuffer();
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_MAX_ACQUIRED_BUFFER_COUNT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t maxAcquiredBuffers = data.readInt32();
+ status_t result = setMaxAcquiredBufferCount(maxAcquiredBuffers);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_CONSUMER_NAME: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ setConsumerName( data.readString8() );
+ return NO_ERROR;
+ } break;
+ case SET_DEFAULT_BUFFER_FORMAT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t defaultFormat = data.readInt32();
+ status_t result = setDefaultBufferFormat(defaultFormat);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_CONSUMER_USAGE_BITS: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t usage = data.readInt32();
+ status_t result = setConsumerUsageBits(usage);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case SET_TRANSFORM_HINT: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ uint32_t hint = data.readInt32();
+ status_t result = setTransformHint(hint);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case DUMP: {
+ CHECK_INTERFACE(IGonkGraphicBufferConsumer, data, reply);
+ String8 result = data.readString8();
+ String8 prefix = data.readString8();
+ static_cast<IGonkGraphicBufferConsumer*>(this)->dumpToString(result, prefix);
+ reply->writeString8(result);
+ return NO_ERROR;
+ }
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
diff --git a/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.h b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.h
new file mode 100644
index 0000000000..8a93a08497
--- /dev/null
+++ b/widget/gonk/nativewindow/IGonkGraphicBufferConsumerLL.h
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#ifndef NATIVEWINDOW_IGONKGRAPHICBUFFERCONSUMER_LL_H
+#define NATIVEWINDOW_IGONKGRAPHICBUFFERCONSUMER_LL_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <utils/Errors.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+#include <binder/IInterface.h>
+#include <ui/Rect.h>
+
+#include "mozilla/RefPtr.h"
+
+class ANativeWindowBuffer;
+
+namespace mozilla {
+namespace layers {
+class TextureClient;
+}
+}
+
+namespace android {
+// ----------------------------------------------------------------------------
+
+class Fence;
+class GraphicBuffer;
+class IConsumerListener;
+class NativeHandle;
+
+class IGonkGraphicBufferConsumer : public IInterface {
+public:
+
+ // public facing structure for BufferSlot
+ class BufferItem : public Flattenable<BufferItem> {
+ friend class Flattenable<BufferItem>;
+ size_t getPodSize() const;
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ public:
+ // The default value of mBuf, used to indicate this doesn't correspond to a slot.
+ enum { INVALID_BUFFER_SLOT = -1 };
+ BufferItem();
+
+ // mGraphicBuffer points to the buffer allocated for this slot, or is NULL
+ // if the buffer in this slot has been acquired in the past (see
+ // BufferSlot.mAcquireCalled).
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence that will signal when the buffer is idle.
+ sp<Fence> mFence;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ // refer to NATIVE_WINDOW_TRANSFORM_* in <window.h>
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ // refer to NATIVE_WINDOW_SCALING_* in <window.h>
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued. This value
+ // is guaranteed to be monotonically increasing for each newly
+ // acquired buffer.
+ int64_t mTimestamp;
+
+ // mIsAutoTimestamp indicates whether mTimestamp was generated
+ // automatically when the buffer was queued.
+ bool mIsAutoTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot.
+ uint64_t mFrameNumber;
+
+ // mBuf is the slot index of this buffer (default INVALID_BUFFER_SLOT).
+ int mBuf;
+
+ // mIsDroppable whether this buffer was queued with the
+ // property that it can be replaced by a new buffer for the purpose of
+ // making sure dequeueBuffer() won't block.
+ // i.e.: was the BufferQueue in "mDequeueBufferCannotBlock" when this buffer
+ // was queued.
+ bool mIsDroppable;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates this buffer must be transformed by the inverse transform of the screen
+ // it is displayed onto. This is applied after mTransform.
+ bool mTransformToDisplayInverse;
+ };
+
+ enum {
+ // Returned by releaseBuffer, after which the consumer must
+ // free any references to the just-released buffer that it might have.
+ STALE_BUFFER_SLOT = 1,
+ // Returned by dequeueBuffer if there are no pending buffers available.
+ NO_BUFFER_AVAILABLE,
+ // Returned by dequeueBuffer if it's too early for the buffer to be acquired.
+ PRESENT_LATER,
+ };
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the BufferQueue. If no buffer is pending then it returns
+ // NO_BUFFER_AVAILABLE. If a buffer is successfully acquired, the
+ // information about the buffer is returned in BufferItem.
+ //
+ // If the buffer returned had previously been
+ // acquired then the BufferItem::mGraphicBuffer field of buffer is set to
+ // NULL and it is assumed that the consumer still holds a reference to the
+ // buffer.
+ //
+ // If presentWhen is non-zero, it indicates the time when the buffer will
+ // be displayed on screen. If the buffer's timestamp is farther in the
+ // future, the buffer won't be acquired, and PRESENT_LATER will be
+ // returned. The presentation time is in nanoseconds, and the time base
+ // is CLOCK_MONOTONIC.
+ //
+ // Return of NO_ERROR means the operation completed as normal.
+ //
+ // Return of a positive value means the operation could not be completed
+ // at this time, but the user should try again later:
+ // * NO_BUFFER_AVAILABLE - no buffer is pending (nothing queued by producer)
+ // * PRESENT_LATER - the buffer's timestamp is farther in the future
+ //
+ // Return of a negative value means an error has occurred:
+ // * INVALID_OPERATION - too many buffers have been acquired
+ virtual status_t acquireBuffer(BufferItem* buffer, nsecs_t presentWhen) = 0;
+
+ // detachBuffer attempts to remove all ownership of the buffer in the given
+ // slot from the buffer queue. If this call succeeds, the slot will be
+ // freed, and there will be no way to obtain the buffer from this interface.
+ // The freed slot will remain unallocated until either it is selected to
+ // hold a freshly allocated buffer in dequeueBuffer or a buffer is attached
+ // to the slot. The buffer must have already been acquired.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - the given slot number is invalid, either because it is
+ // out of the range [0, NUM_BUFFER_SLOTS) or because the slot
+ // it refers to is not currently acquired.
+ virtual status_t detachBuffer(int slot) = 0;
+
+ // attachBuffer attempts to transfer ownership of a buffer to the buffer
+ // queue. If this call succeeds, it will be as if this buffer was acquired
+ // from the returned slot number. As such, this call will fail if attaching
+ // this buffer would cause too many buffers to be simultaneously acquired.
+ //
+ // If the buffer is successfully attached, its frameNumber is initialized
+ // to 0. This must be passed into the releaseBuffer call or else the buffer
+ // will be deallocated as stale.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - outSlot or buffer were NULL
+ // * INVALID_OPERATION - cannot attach the buffer because it would cause too
+ // many buffers to be acquired.
+ // * NO_MEMORY - no free slots available
+ virtual status_t attachBuffer(int *outSlot,
+ const sp<GraphicBuffer>& buffer) = 0;
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // BufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use. frameNumber is used to indentify the exact buffer returned.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ //
+ // Note that the dependencies on EGL will be removed once we switch to using
+ // the Android HW Sync HAL.
+ //
+ // Return of NO_ERROR means the operation completed as normal.
+ //
+ // Return of a positive value means the operation could not be completed
+ // at this time, but the user should try again later:
+ // * STALE_BUFFER_SLOT - see above (second paragraph)
+ //
+ // Return of a negative value means an error has occurred:
+ // * BAD_VALUE - one of the following could've happened:
+ // * the buffer slot was invalid
+ // * the fence was NULL
+ // * the buffer slot specified is not in the acquired state
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber, const sp<Fence>& releaseFence) = 0;
+
+ // consumerConnect connects a consumer to the BufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // BufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the BufferQueue by the producer to fail.
+ // controlledByApp indicates whether the consumer is controlled by
+ // the application.
+ //
+ // consumer may not be NULL.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned
+ // * BAD_VALUE - a NULL consumer was provided
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) = 0;
+
+ // consumerDisconnect disconnects a consumer from the BufferQueue. All
+ // buffers will be freed and the BufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the BufferQueue by the producer to
+ // fail.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - no consumer is currently connected
+ virtual status_t consumerDisconnect() = 0;
+
+ // getReleasedBuffers sets the value pointed to by slotMask to a bit set.
+ // Each bit index with a 1 corresponds to a released buffer slot with that
+ // index value. In particular, a released buffer is one that has
+ // been released by the BufferQueue but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ virtual status_t getReleasedBuffers(uint64_t* slotMask) = 0;
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - either w or h was zero
+ virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) = 0;
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - bufferCount was out of range (see above).
+ virtual status_t setDefaultMaxBufferCount(int bufferCount) = 0;
+
+ // disableAsyncBuffer disables the extra buffer used in async mode
+ // (when both producer and consumer have set their "isControlledByApp"
+ // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
+ //
+ // This can only be called before consumerConnect().
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * INVALID_OPERATION - attempting to call this after consumerConnect.
+ virtual status_t disableAsyncBuffer() = 0;
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the BufferQueue.
+ //
+ // maxAcquiredBuffers must be (inclusive) between 1 and MAX_MAX_ACQUIRED_BUFFERS.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - maxAcquiredBuffers was out of range (see above).
+ // * INVALID_OPERATION - attempting to call this after a producer connected.
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) = 0;
+
+ // setConsumerName sets the name used in logging
+ virtual void setConsumerName(const String8& name) = 0;
+
+ // setDefaultBufferFormat allows the BufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ //
+ // Return of a value other than NO_ERROR means an unknown error has occurred.
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat) = 0;
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ //
+ // Return of a value other than NO_ERROR means an unknown error has occurred.
+ virtual status_t setConsumerUsageBits(uint32_t usage) = 0;
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ //
+ // Return of a value other than NO_ERROR means an unknown error has occurred.
+ virtual status_t setTransformHint(uint32_t hint) = 0;
+
+ // Retrieve the sideband buffer stream, if any.
+ virtual sp<NativeHandle> getSidebandStream() const = 0;
+
+ // dump state into a string
+ virtual void dumpToString(String8& result, const char* prefix) const = 0;
+
+ // Added by mozilla
+ virtual already_AddRefed<mozilla::layers::TextureClient>
+ getTextureClientFromBuffer(ANativeWindowBuffer* buffer) = 0;
+
+ virtual int getSlotFromTextureClientLocked(mozilla::layers::TextureClient* client) const = 0;
+
+public:
+ DECLARE_META_INTERFACE(GonkGraphicBufferConsumer);
+};
+
+// ----------------------------------------------------------------------------
+
+class BnGonkGraphicBufferConsumer : public BnInterface<IGonkGraphicBufferConsumer>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+
+#endif // ANDROID_GUI_IGONKGRAPHICBUFFERCONSUMER_H
diff --git a/widget/gonk/nativewindow/moz.build b/widget/gonk/nativewindow/moz.build
new file mode 100644
index 0000000000..fbcee601c8
--- /dev/null
+++ b/widget/gonk/nativewindow/moz.build
@@ -0,0 +1,104 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# Copyright 2013 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.
+
+EXPORTS += [
+ 'GonkBufferQueue.h',
+ 'GonkNativeWindow.h',
+]
+
+if CONFIG['ANDROID_VERSION'] >= '19':
+ EXPORTS += [
+ 'IGonkGraphicBufferConsumer.h',
+ ]
+
+if CONFIG['ANDROID_VERSION'] >= '21':
+ EXPORTS += [
+ 'GonkBufferQueueLL/GonkBufferQueueDefs.h',
+ 'GonkBufferQueueLL/GonkBufferQueueLL.h',
+ 'GonkBufferQueueLL/GonkBufferQueueProducer.h',
+ 'GonkBufferQueueLL/GonkBufferSlot.h',
+ 'GonkConsumerBaseLL.h',
+ 'GonkNativeWindowLL.h',
+ 'IGonkGraphicBufferConsumerLL.h',
+ ]
+elif CONFIG['ANDROID_VERSION'] >= '19':
+ EXPORTS += [
+ 'GonkBufferQueueKK.h',
+ 'GonkConsumerBaseKK.h',
+ 'GonkNativeWindowKK.h',
+ 'IGonkGraphicBufferConsumerKK.h',
+ ]
+elif CONFIG['ANDROID_VERSION'] in ('17', '18'):
+ EXPORTS += [
+ 'GonkBufferQueueJB.h',
+ 'GonkConsumerBaseJB.h',
+ 'GonkNativeWindowJB.h',
+ ]
+
+if CONFIG['MOZ_WEBRTC']:
+ if CONFIG['ANDROID_VERSION'] >= '21':
+ SOURCES += [
+ 'GonkBufferQueueLL/GonkBufferItem.cpp',
+ 'GonkBufferQueueLL/GonkBufferQueueConsumer.cpp',
+ 'GonkBufferQueueLL/GonkBufferQueueCore.cpp',
+ 'GonkBufferQueueLL/GonkBufferQueueLL.cpp',
+ 'GonkBufferQueueLL/GonkBufferQueueProducer.cpp',
+ 'GonkBufferQueueLL/GonkBufferSlot.cpp',
+ 'GonkConsumerBaseLL.cpp',
+ 'GonkNativeWindowLL.cpp',
+ 'IGonkGraphicBufferConsumerLL.cpp',
+ ]
+ elif CONFIG['ANDROID_VERSION'] >= '19':
+ SOURCES += [
+ 'GonkBufferQueueKK.cpp',
+ 'GonkConsumerBaseKK.cpp',
+ 'GonkNativeWindowKK.cpp',
+ 'IGonkGraphicBufferConsumerKK.cpp',
+ ]
+ elif CONFIG['ANDROID_VERSION'] in ('17', '18'):
+ SOURCES += [
+ 'GonkBufferQueueJB.cpp',
+ 'GonkConsumerBaseJB.cpp',
+ 'GonkNativeWindowJB.cpp',
+ ]
+
+if CONFIG['ANDROID_VERSION'] >= '18':
+ SOURCES += [
+ 'FakeSurfaceComposer.cpp',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+if CONFIG['ANDROID_VERSION'] >= '18':
+ LOCAL_INCLUDES += [
+ '%' + '%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
+ 'frameworks/native/opengl/include',
+ ]
+ ]
+
+DEFINES['HAVE_ANDROID_OS'] = True
+
+# Suppress some GCC warnings being treated as errors:
+# - about attributes on forward declarations for types that are already
+# defined, which complains about an important MOZ_EXPORT for android::AString
+if CONFIG['GNU_CC']:
+ CXXFLAGS += ['-Wno-error=attributes', '-Wno-overloaded-virtual']
+
+FINAL_LIBRARY = 'xul'
+
+DISABLE_STL_WRAPPING = True
+
+NO_VISIBILITY_FLAGS = True
diff --git a/widget/gonk/nsAppShell.cpp b/widget/gonk/nsAppShell.cpp
new file mode 100644
index 0000000000..24e791b4b4
--- /dev/null
+++ b/widget/gonk/nsAppShell.cpp
@@ -0,0 +1,1087 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=4 sw=4 sts=4 tw=80 et: */
+/* 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.
+ */
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <hardware_legacy/power.h>
+#include <signal.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/BitSet.h>
+
+#include "base/basictypes.h"
+#include "GonkPermission.h"
+#include "libdisplay/BootAnimation.h"
+#include "nscore.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Hal.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Services.h"
+#include "mozilla/TextEvents.h"
+#if ANDROID_VERSION >= 18
+#include "nativewindow/FakeSurfaceComposer.h"
+#endif
+#include "nsAppShell.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/Touch.h"
+#include "nsGkAtoms.h"
+#include "nsIObserverService.h"
+#include "nsIScreen.h"
+#include "nsScreenManagerGonk.h"
+#include "nsThreadUtils.h"
+#include "nsWindow.h"
+#include "OrientationObserver.h"
+#include "GonkMemoryPressureMonitoring.h"
+
+#include "android/log.h"
+#include "libui/EventHub.h"
+#include "libui/InputReader.h"
+#include "libui/InputDispatcher.h"
+
+#include "mozilla/Preferences.h"
+#include "GeckoProfiler.h"
+
+// Defines kKeyMapping and GetKeyNameIndex()
+#include "GonkKeyMapping.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "GeckoTouchDispatcher.h"
+
+#undef LOG
+#define LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+#ifdef VERBOSE_LOG_ENABLED
+# define VERBOSE_LOG(args...) \
+ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+#else
+# define VERBOSE_LOG(args...) \
+ (void)0
+#endif
+
+using namespace android;
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::services;
+using namespace mozilla::widget;
+
+bool gDrawRequest = false;
+static nsAppShell *gAppShell = nullptr;
+static int epollfd = 0;
+static int signalfds[2] = {0};
+static bool sDevInputAudioJack;
+static int32_t sHeadphoneState;
+static int32_t sMicrophoneState;
+
+// Amount of time in MS before an input is considered expired.
+static const uint64_t kInputExpirationThresholdMs = 1000;
+static const char kKey_WAKE_LOCK_ID[] = "GeckoKeyEvent";
+
+NS_IMPL_ISUPPORTS_INHERITED(nsAppShell, nsBaseAppShell, nsIObserver)
+
+static uint64_t
+nanosecsToMillisecs(nsecs_t nsecs)
+{
+ return nsecs / 1000000;
+}
+
+namespace mozilla {
+
+bool ProcessNextEvent()
+{
+ return gAppShell->ProcessNextNativeEvent(true);
+}
+
+void NotifyEvent()
+{
+ gAppShell->NotifyNativeEvent();
+}
+
+} // namespace mozilla
+
+static void
+pipeHandler(int fd, FdHandler *data)
+{
+ ssize_t len;
+ do {
+ char tmp[32];
+ len = read(fd, tmp, sizeof(tmp));
+ } while (len > 0);
+}
+
+struct Touch {
+ int32_t id;
+ PointerCoords coords;
+};
+
+struct UserInputData {
+ uint64_t timeMs;
+ enum {
+ MOTION_DATA,
+ KEY_DATA
+ } type;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t deviceId;
+ union {
+ struct {
+ int32_t keyCode;
+ int32_t scanCode;
+ } key;
+ struct {
+ int32_t touchCount;
+ ::Touch touches[MAX_POINTERS];
+ } motion;
+ };
+};
+
+static mozilla::Modifiers
+getDOMModifiers(int32_t metaState)
+{
+ mozilla::Modifiers result = 0;
+ if (metaState & (AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
+ result |= MODIFIER_ALT;
+ }
+ if (metaState & (AMETA_SHIFT_ON |
+ AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
+ result |= MODIFIER_SHIFT;
+ }
+ if (metaState & AMETA_FUNCTION_ON) {
+ result |= MODIFIER_FN;
+ }
+ if (metaState & (AMETA_CTRL_ON |
+ AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
+ result |= MODIFIER_CONTROL;
+ }
+ if (metaState & (AMETA_META_ON |
+ AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
+ result |= MODIFIER_META;
+ }
+ if (metaState & AMETA_CAPS_LOCK_ON) {
+ result |= MODIFIER_CAPSLOCK;
+ }
+ if (metaState & AMETA_NUM_LOCK_ON) {
+ result |= MODIFIER_NUMLOCK;
+ }
+ if (metaState & AMETA_SCROLL_LOCK_ON) {
+ result |= MODIFIER_SCROLLLOCK;
+ }
+ return result;
+}
+
+class MOZ_STACK_CLASS KeyEventDispatcher
+{
+public:
+ KeyEventDispatcher(const UserInputData& aData,
+ KeyCharacterMap* aKeyCharMap);
+ void Dispatch();
+
+private:
+ const UserInputData& mData;
+ sp<KeyCharacterMap> mKeyCharMap;
+
+ char16_t mChar;
+ char16_t mUnmodifiedChar;
+
+ uint32_t mDOMKeyCode;
+ uint32_t mDOMKeyLocation;
+ KeyNameIndex mDOMKeyNameIndex;
+ CodeNameIndex mDOMCodeNameIndex;
+ char16_t mDOMPrintableKeyValue;
+
+ bool IsKeyPress() const
+ {
+ return mData.action == AKEY_EVENT_ACTION_DOWN;
+ }
+ bool IsRepeat() const
+ {
+ return IsKeyPress() && (mData.flags & AKEY_EVENT_FLAG_LONG_PRESS);
+ }
+
+ char16_t PrintableKeyValue() const;
+
+ int32_t UnmodifiedMetaState() const
+ {
+ return mData.metaState &
+ ~(AMETA_ALT_ON | AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON |
+ AMETA_CTRL_ON | AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON |
+ AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
+ }
+
+ static bool IsControlChar(char16_t aChar)
+ {
+ return (aChar < ' ' || aChar == 0x7F);
+ }
+
+ void DispatchKeyDownEvent();
+ void DispatchKeyUpEvent();
+ nsEventStatus DispatchKeyEventInternal(EventMessage aEventMessage);
+};
+
+KeyEventDispatcher::KeyEventDispatcher(const UserInputData& aData,
+ KeyCharacterMap* aKeyCharMap)
+ : mData(aData)
+ , mKeyCharMap(aKeyCharMap)
+ , mChar(0)
+ , mUnmodifiedChar(0)
+ , mDOMPrintableKeyValue(0)
+{
+ // XXX Printable key's keyCode value should be computed with actual
+ // input character.
+ mDOMKeyCode = (mData.key.keyCode < (ssize_t)ArrayLength(kKeyMapping)) ?
+ kKeyMapping[mData.key.keyCode] : 0;
+ mDOMKeyNameIndex = GetKeyNameIndex(mData.key.keyCode);
+ mDOMCodeNameIndex = GetCodeNameIndex(mData.key.scanCode);
+ mDOMKeyLocation =
+ WidgetKeyboardEvent::ComputeLocationFromCodeValue(mDOMCodeNameIndex);
+
+ if (!mKeyCharMap.get()) {
+ return;
+ }
+
+ mChar = mKeyCharMap->getCharacter(mData.key.keyCode, mData.metaState);
+ if (IsControlChar(mChar)) {
+ mChar = 0;
+ }
+ int32_t unmodifiedMetaState = UnmodifiedMetaState();
+ if (mData.metaState == unmodifiedMetaState) {
+ mUnmodifiedChar = mChar;
+ } else {
+ mUnmodifiedChar = mKeyCharMap->getCharacter(mData.key.keyCode,
+ unmodifiedMetaState);
+ if (IsControlChar(mUnmodifiedChar)) {
+ mUnmodifiedChar = 0;
+ }
+ }
+
+ mDOMPrintableKeyValue = PrintableKeyValue();
+}
+
+char16_t
+KeyEventDispatcher::PrintableKeyValue() const
+{
+ if (mDOMKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
+ return 0;
+ }
+ return mChar ? mChar : mUnmodifiedChar;
+}
+
+nsEventStatus
+KeyEventDispatcher::DispatchKeyEventInternal(EventMessage aEventMessage)
+{
+ WidgetKeyboardEvent event(true, aEventMessage, nullptr);
+ if (aEventMessage == eKeyPress) {
+ // XXX If the charCode is not a printable character, the charCode
+ // should be computed without Ctrl/Alt/Meta modifiers.
+ event.mCharCode = static_cast<uint32_t>(mChar);
+ }
+ if (!event.mCharCode) {
+ event.mKeyCode = mDOMKeyCode;
+ }
+ event.mIsChar = !!event.mCharCode;
+ event.mIsRepeat = IsRepeat();
+ event.mKeyNameIndex = mDOMKeyNameIndex;
+ if (mDOMPrintableKeyValue) {
+ event.mKeyValue = mDOMPrintableKeyValue;
+ }
+ event.mCodeNameIndex = mDOMCodeNameIndex;
+ event.mModifiers = getDOMModifiers(mData.metaState);
+ event.mLocation = mDOMKeyLocation;
+ event.mTime = mData.timeMs;
+ return nsWindow::DispatchKeyInput(event);
+}
+
+void
+KeyEventDispatcher::Dispatch()
+{
+ // XXX Even if unknown key is pressed, DOM key event should be
+ // dispatched since Gecko for the other platforms are implemented
+ // as so.
+ if (!mDOMKeyCode && mDOMKeyNameIndex == KEY_NAME_INDEX_Unidentified) {
+ VERBOSE_LOG("Got unknown key event code. "
+ "type 0x%04x code 0x%04x value %d",
+ mData.action, mData.key.keyCode, IsKeyPress());
+ return;
+ }
+
+ if (IsKeyPress()) {
+ DispatchKeyDownEvent();
+ } else {
+ DispatchKeyUpEvent();
+ }
+}
+
+void
+KeyEventDispatcher::DispatchKeyDownEvent()
+{
+ nsEventStatus status = DispatchKeyEventInternal(eKeyDown);
+ if (status != nsEventStatus_eConsumeNoDefault) {
+ DispatchKeyEventInternal(eKeyPress);
+ }
+}
+
+void
+KeyEventDispatcher::DispatchKeyUpEvent()
+{
+ DispatchKeyEventInternal(eKeyUp);
+}
+
+class SwitchEventRunnable : public mozilla::Runnable {
+public:
+ SwitchEventRunnable(hal::SwitchEvent& aEvent) : mEvent(aEvent)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ hal::NotifySwitchStateFromInputDevice(mEvent.device(),
+ mEvent.status());
+ return NS_OK;
+ }
+private:
+ hal::SwitchEvent mEvent;
+};
+
+static void
+updateHeadphoneSwitch()
+{
+ hal::SwitchEvent event;
+
+ switch (sHeadphoneState) {
+ case AKEY_STATE_UP:
+ event.status() = hal::SWITCH_STATE_OFF;
+ break;
+ case AKEY_STATE_DOWN:
+ event.status() = sMicrophoneState == AKEY_STATE_DOWN ?
+ hal::SWITCH_STATE_HEADSET : hal::SWITCH_STATE_HEADPHONE;
+ break;
+ default:
+ return;
+ }
+
+ event.device() = hal::SWITCH_HEADPHONES;
+ NS_DispatchToMainThread(new SwitchEventRunnable(event));
+}
+
+class GeckoPointerController : public PointerControllerInterface {
+ float mX;
+ float mY;
+ int32_t mButtonState;
+ InputReaderConfiguration* mConfig;
+public:
+ GeckoPointerController(InputReaderConfiguration* config)
+ : mX(0)
+ , mY(0)
+ , mButtonState(0)
+ , mConfig(config)
+ {}
+
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const;
+ virtual void move(float deltaX, float deltaY);
+ virtual void setButtonState(int32_t buttonState);
+ virtual int32_t getButtonState() const;
+ virtual void setPosition(float x, float y);
+ virtual void getPosition(float* outX, float* outY) const;
+ virtual void fade(Transition transition) {}
+ virtual void unfade(Transition transition) {}
+ virtual void setPresentation(Presentation presentation) {}
+ virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits) {}
+ virtual void clearSpots() {}
+};
+
+bool
+GeckoPointerController::getBounds(float* outMinX,
+ float* outMinY,
+ float* outMaxX,
+ float* outMaxY) const
+{
+ DisplayViewport viewport;
+
+ mConfig->getDisplayInfo(false, &viewport);
+
+ *outMinX = *outMinY = 0;
+ *outMaxX = viewport.logicalRight;
+ *outMaxY = viewport.logicalBottom;
+ return true;
+}
+
+void
+GeckoPointerController::move(float deltaX, float deltaY)
+{
+ float minX, minY, maxX, maxY;
+ getBounds(&minX, &minY, &maxX, &maxY);
+
+ mX = clamped(mX + deltaX, minX, maxX);
+ mY = clamped(mY + deltaY, minY, maxY);
+}
+
+void
+GeckoPointerController::setButtonState(int32_t buttonState)
+{
+ mButtonState = buttonState;
+}
+
+int32_t
+GeckoPointerController::getButtonState() const
+{
+ return mButtonState;
+}
+
+void
+GeckoPointerController::setPosition(float x, float y)
+{
+ mX = x;
+ mY = y;
+}
+
+void
+GeckoPointerController::getPosition(float* outX, float* outY) const
+{
+ *outX = mX;
+ *outY = mY;
+}
+
+class GeckoInputReaderPolicy : public InputReaderPolicyInterface {
+ InputReaderConfiguration mConfig;
+public:
+ GeckoInputReaderPolicy() {}
+
+ virtual void getReaderConfiguration(InputReaderConfiguration* outConfig);
+ virtual sp<PointerControllerInterface> obtainPointerController(int32_t
+deviceId)
+ {
+ return new GeckoPointerController(&mConfig);
+ };
+ virtual void notifyInputDevicesChanged(const android::Vector<InputDeviceInfo>& inputDevices) {};
+ virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(const String8& inputDeviceDescriptor)
+ {
+ return nullptr;
+ };
+ virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier)
+ {
+ return String8::empty();
+ };
+
+ void setDisplayInfo();
+
+protected:
+ virtual ~GeckoInputReaderPolicy() {}
+};
+
+class GeckoInputDispatcher : public InputDispatcherInterface {
+public:
+ GeckoInputDispatcher(sp<EventHub> &aEventHub)
+ : mQueueLock("GeckoInputDispatcher::mQueueMutex")
+ , mEventHub(aEventHub)
+ , mKeyDownCount(0)
+ , mKeyEventsFiltered(false)
+ , mPowerWakelock(false)
+ {
+ mTouchDispatcher = GeckoTouchDispatcher::GetInstance();
+ }
+
+ virtual void dump(String8& dump);
+
+ virtual void monitor() {}
+
+ // Called on the main thread
+ virtual void dispatchOnce();
+
+ // notify* methods are called on the InputReaderThread
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
+ virtual void notifyKey(const NotifyKeyArgs* args);
+ virtual void notifyMotion(const NotifyMotionArgs* args);
+ virtual void notifySwitch(const NotifySwitchArgs* args);
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
+
+ virtual int32_t injectInputEvent(const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags);
+
+ virtual void setInputWindows(const android::Vector<sp<InputWindowHandle> >& inputWindowHandles);
+ virtual void setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle);
+
+ virtual void setInputDispatchMode(bool enabled, bool frozen);
+ virtual void setInputFilterEnabled(bool enabled) {}
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) { return true; }
+
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
+
+
+
+protected:
+ virtual ~GeckoInputDispatcher() { }
+
+private:
+ // mQueueLock should generally be locked while using mEventQueue.
+ // UserInputData is pushed on on the InputReaderThread and
+ // popped and dispatched on the main thread.
+ mozilla::Mutex mQueueLock;
+ std::queue<UserInputData> mEventQueue;
+ sp<EventHub> mEventHub;
+ RefPtr<GeckoTouchDispatcher> mTouchDispatcher;
+
+ int mKeyDownCount;
+ bool mKeyEventsFiltered;
+ bool mPowerWakelock;
+};
+
+// GeckoInputReaderPolicy
+void
+GeckoInputReaderPolicy::setDisplayInfo()
+{
+ static_assert(static_cast<int>(nsIScreen::ROTATION_0_DEG) ==
+ static_cast<int>(DISPLAY_ORIENTATION_0),
+ "Orientation enums not matched!");
+ static_assert(static_cast<int>(nsIScreen::ROTATION_90_DEG) ==
+ static_cast<int>(DISPLAY_ORIENTATION_90),
+ "Orientation enums not matched!");
+ static_assert(static_cast<int>(nsIScreen::ROTATION_180_DEG) ==
+ static_cast<int>(DISPLAY_ORIENTATION_180),
+ "Orientation enums not matched!");
+ static_assert(static_cast<int>(nsIScreen::ROTATION_270_DEG) ==
+ static_cast<int>(DISPLAY_ORIENTATION_270),
+ "Orientation enums not matched!");
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+
+ uint32_t rotation = nsIScreen::ROTATION_0_DEG;
+ DebugOnly<nsresult> rv = screen->GetRotation(&rotation);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ LayoutDeviceIntRect screenBounds = screen->GetNaturalBounds();
+
+ DisplayViewport viewport;
+ viewport.displayId = 0;
+ viewport.orientation = rotation;
+ viewport.physicalRight = viewport.deviceWidth = screenBounds.width;
+ viewport.physicalBottom = viewport.deviceHeight = screenBounds.height;
+ if (viewport.orientation == DISPLAY_ORIENTATION_90 ||
+ viewport.orientation == DISPLAY_ORIENTATION_270) {
+ viewport.logicalRight = screenBounds.height;
+ viewport.logicalBottom = screenBounds.width;
+ } else {
+ viewport.logicalRight = screenBounds.width;
+ viewport.logicalBottom = screenBounds.height;
+ }
+ mConfig.setDisplayInfo(false, viewport);
+}
+
+void GeckoInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig)
+{
+ *outConfig = mConfig;
+}
+
+
+// GeckoInputDispatcher
+void
+GeckoInputDispatcher::dump(String8& dump)
+{
+}
+
+static bool
+isExpired(const UserInputData& data)
+{
+ uint64_t timeNowMs =
+ nanosecsToMillisecs(systemTime(SYSTEM_TIME_MONOTONIC));
+ return (timeNowMs - data.timeMs) > kInputExpirationThresholdMs;
+}
+
+void
+GeckoInputDispatcher::dispatchOnce()
+{
+ UserInputData data;
+ {
+ MutexAutoLock lock(mQueueLock);
+ if (mEventQueue.empty())
+ return;
+ data = mEventQueue.front();
+ mEventQueue.pop();
+ if (!mEventQueue.empty())
+ gAppShell->NotifyNativeEvent();
+ }
+
+ switch (data.type) {
+ case UserInputData::MOTION_DATA: {
+ MOZ_ASSERT_UNREACHABLE("Should not dispatch touch events here anymore");
+ break;
+ }
+ case UserInputData::KEY_DATA: {
+ if (!mKeyDownCount) {
+ // No pending events, the filter state can be updated.
+ mKeyEventsFiltered = isExpired(data);
+ }
+
+ mKeyDownCount += (data.action == AKEY_EVENT_ACTION_DOWN) ? 1 : -1;
+ if (mKeyEventsFiltered) {
+ return;
+ }
+
+ sp<KeyCharacterMap> kcm = mEventHub->getKeyCharacterMap(data.deviceId);
+ KeyEventDispatcher dispatcher(data, kcm.get());
+ dispatcher.Dispatch();
+ break;
+ }
+ }
+ MutexAutoLock lock(mQueueLock);
+ if (mPowerWakelock && mEventQueue.empty()) {
+ release_wake_lock(kKey_WAKE_LOCK_ID);
+ mPowerWakelock = false;
+ }
+}
+
+void
+GeckoInputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs*)
+{
+ gAppShell->CheckPowerKey();
+}
+
+void
+GeckoInputDispatcher::notifyKey(const NotifyKeyArgs* args)
+{
+ UserInputData data;
+ data.timeMs = nanosecsToMillisecs(args->eventTime);
+ data.type = UserInputData::KEY_DATA;
+ data.action = args->action;
+ data.flags = args->flags;
+ data.metaState = args->metaState;
+ data.deviceId = args->deviceId;
+ data.key.keyCode = args->keyCode;
+ data.key.scanCode = args->scanCode;
+ {
+ MutexAutoLock lock(mQueueLock);
+ mEventQueue.push(data);
+ if (!mPowerWakelock) {
+ mPowerWakelock =
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, kKey_WAKE_LOCK_ID);
+ }
+ }
+ gAppShell->NotifyNativeEvent();
+}
+
+static void
+addMultiTouch(MultiTouchInput& aMultiTouch,
+ const NotifyMotionArgs* args, int aIndex)
+{
+ int32_t id = args->pointerProperties[aIndex].id;
+ PointerCoords coords = args->pointerCoords[aIndex];
+ float force = coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
+
+ float orientation = coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+ float rotationAngle = orientation * 180 / M_PI;
+ if (rotationAngle == 90) {
+ rotationAngle = -90;
+ }
+
+ float radiusX, radiusY;
+ if (rotationAngle < 0) {
+ radiusX = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR) / 2;
+ radiusY = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR) / 2;
+ rotationAngle += 90;
+ } else {
+ radiusX = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR) / 2;
+ radiusY = coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR) / 2;
+ }
+
+ ScreenIntPoint point = ScreenIntPoint::Round(coords.getX(),
+ coords.getY());
+
+ SingleTouchData touchData(id, point, ScreenSize(radiusX, radiusY),
+ rotationAngle, force);
+
+ aMultiTouch.mTouches.AppendElement(touchData);
+}
+
+void
+GeckoInputDispatcher::notifyMotion(const NotifyMotionArgs* args)
+{
+ uint32_t time = nanosecsToMillisecs(args->eventTime);
+ int32_t action = args->action & AMOTION_EVENT_ACTION_MASK;
+ int touchCount = args->pointerCount;
+ MOZ_ASSERT(touchCount <= MAX_POINTERS);
+ TimeStamp timestamp = mozilla::TimeStamp::FromSystemTime(args->eventTime);
+ Modifiers modifiers = getDOMModifiers(args->metaState);
+
+ MultiTouchInput::MultiTouchType touchType = MultiTouchInput::MULTITOUCH_CANCEL;
+ switch (action) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ touchType = MultiTouchInput::MULTITOUCH_START;
+ break;
+ case AMOTION_EVENT_ACTION_MOVE:
+ touchType = MultiTouchInput::MULTITOUCH_MOVE;
+ break;
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ touchType = MultiTouchInput::MULTITOUCH_END;
+ break;
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ touchType = MultiTouchInput::MULTITOUCH_CANCEL;
+ break;
+ case AMOTION_EVENT_ACTION_HOVER_EXIT:
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ NS_WARNING("Ignoring hover touch events");
+ return;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Could not assign a touch type");
+ break;
+ }
+
+ MultiTouchInput touchData(touchType, time, timestamp, modifiers);
+
+ // For touch ends, we have to filter out which finger is actually
+ // the touch end since the touch array has all fingers, not just the touch
+ // that we want to end
+ if (touchType == MultiTouchInput::MULTITOUCH_END) {
+ int touchIndex = args->action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK;
+ touchIndex >>= AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ addMultiTouch(touchData, args, touchIndex);
+ } else {
+ for (int32_t i = 0; i < touchCount; ++i) {
+ addMultiTouch(touchData, args, i);
+ }
+ }
+
+ mTouchDispatcher->NotifyTouch(touchData, timestamp);
+}
+
+void GeckoInputDispatcher::notifySwitch(const NotifySwitchArgs* args)
+{
+ if (!sDevInputAudioJack)
+ return;
+
+ bool needSwitchUpdate = false;
+
+ if (args->switchMask & (1 << SW_HEADPHONE_INSERT)) {
+ sHeadphoneState = (args->switchValues & (1 << SW_HEADPHONE_INSERT)) ?
+ AKEY_STATE_DOWN : AKEY_STATE_UP;
+ needSwitchUpdate = true;
+ }
+
+ if (args->switchMask & (1 << SW_MICROPHONE_INSERT)) {
+ sMicrophoneState = (args->switchValues & (1 << SW_MICROPHONE_INSERT)) ?
+ AKEY_STATE_DOWN : AKEY_STATE_UP;
+ needSwitchUpdate = true;
+ }
+
+ if (needSwitchUpdate)
+ updateHeadphoneSwitch();
+}
+
+void GeckoInputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args)
+{
+}
+
+int32_t GeckoInputDispatcher::injectInputEvent(
+ const InputEvent* event,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode,
+ int32_t timeoutMillis, uint32_t policyFlags)
+{
+ return INPUT_EVENT_INJECTION_SUCCEEDED;
+}
+
+void
+GeckoInputDispatcher::setInputWindows(const android::Vector<sp<InputWindowHandle> >& inputWindowHandles)
+{
+}
+
+void
+GeckoInputDispatcher::setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle)
+{
+}
+
+void
+GeckoInputDispatcher::setInputDispatchMode(bool enabled, bool frozen)
+{
+}
+
+status_t
+GeckoInputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor)
+{
+ return OK;
+}
+
+status_t
+GeckoInputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel)
+{
+ return OK;
+}
+
+nsAppShell::nsAppShell()
+ : mNativeCallbackRequest(false)
+ , mEnableDraw(false)
+ , mHandlers()
+ , mPowerKeyChecked(false)
+{
+ gAppShell = this;
+ if (XRE_IsParentProcess()) {
+ Preferences::SetCString("b2g.safe_mode", "unset");
+ }
+}
+
+nsAppShell::~nsAppShell()
+{
+ // mReaderThread and mEventHub will both be null if InitInputDevices
+ // is not called.
+ if (mReaderThread.get()) {
+ // We separate requestExit() and join() here so we can wake the EventHub's
+ // input loop, and stop it from polling for input events
+ mReaderThread->requestExit();
+ mEventHub->wake();
+
+ status_t result = mReaderThread->requestExitAndWait();
+ if (result)
+ LOG("Could not stop reader thread - %d", result);
+ }
+ gAppShell = nullptr;
+}
+
+nsresult
+nsAppShell::Init()
+{
+ nsresult rv = nsBaseAppShell::Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ epollfd = epoll_create(16);
+ NS_ENSURE_TRUE(epollfd >= 0, NS_ERROR_UNEXPECTED);
+
+ int ret = pipe2(signalfds, O_NONBLOCK);
+ NS_ENSURE_FALSE(ret, NS_ERROR_UNEXPECTED);
+
+ rv = AddFdHandler(signalfds[0], pipeHandler, "");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ InitGonkMemoryPressureMonitoring();
+
+ if (XRE_IsParentProcess()) {
+ printf("*****************************************************************\n");
+ printf("***\n");
+ printf("*** This is stdout. Most of the useful output will be in logcat.\n");
+ printf("***\n");
+ printf("*****************************************************************\n");
+ GonkPermissionService::instantiate();
+
+ // Causes the kernel timezone to be set, which in turn causes the
+ // timestamps on SD cards to have the local time rather than UTC time.
+ hal::SetTimezone(hal::GetTimezone());
+ }
+
+ nsCOMPtr<nsIObserverService> obsServ = GetObserverService();
+ if (obsServ) {
+ obsServ->AddObserver(this, "browser-ui-startup-complete", false);
+ obsServ->AddObserver(this, "network-connection-state-changed", false);
+ }
+
+ // Delay initializing input devices until the screen has been
+ // initialized (and we know the resolution).
+ return rv;
+}
+
+void
+nsAppShell::CheckPowerKey()
+{
+ if (mPowerKeyChecked) {
+ return;
+ }
+
+ uint32_t deviceId = 0;
+ int32_t powerState = AKEY_STATE_UNKNOWN;
+
+ // EventHub doesn't report the number of devices.
+ while (powerState != AKEY_STATE_DOWN && deviceId < 32) {
+ powerState = mEventHub->getKeyCodeState(deviceId++, AKEYCODE_POWER);
+ }
+
+ // If Power is pressed while we startup, mark safe mode.
+ // Consumers of the b2g.safe_mode preference need to listen on this
+ // preference change to prevent startup races.
+ nsCOMPtr<nsIRunnable> prefSetter =
+ NS_NewRunnableFunction([powerState] () -> void {
+ Preferences::SetCString("b2g.safe_mode",
+ (powerState == AKEY_STATE_DOWN) ? "yes" : "no");
+ });
+ NS_DispatchToMainThread(prefSetter.forget());
+
+ mPowerKeyChecked = true;
+}
+
+NS_IMETHODIMP
+nsAppShell::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData)
+{
+ if (!strcmp(aTopic, "network-connection-state-changed")) {
+ NS_ConvertUTF16toUTF8 type(aData);
+ if (!type.IsEmpty()) {
+ hal::NotifyNetworkChange(hal::NetworkInformation(atoi(type.get()), 0, 0));
+ }
+ return NS_OK;
+ } else if (!strcmp(aTopic, "browser-ui-startup-complete")) {
+ if (sDevInputAudioJack) {
+ sHeadphoneState = mReader->getSwitchState(-1, AINPUT_SOURCE_SWITCH, SW_HEADPHONE_INSERT);
+ sMicrophoneState = mReader->getSwitchState(-1, AINPUT_SOURCE_SWITCH, SW_MICROPHONE_INSERT);
+ updateHeadphoneSwitch();
+ }
+ mEnableDraw = true;
+
+ // System is almost booting up. Stop the bootAnim now.
+ StopBootAnimation();
+
+ NotifyEvent();
+ return NS_OK;
+ }
+
+ return nsBaseAppShell::Observe(aSubject, aTopic, aData);
+}
+
+NS_IMETHODIMP
+nsAppShell::Exit()
+{
+ OrientationObserver::ShutDown();
+ nsCOMPtr<nsIObserverService> obsServ = GetObserverService();
+ if (obsServ) {
+ obsServ->RemoveObserver(this, "browser-ui-startup-complete");
+ obsServ->RemoveObserver(this, "network-connection-state-changed");
+ }
+ return nsBaseAppShell::Exit();
+}
+
+void
+nsAppShell::InitInputDevices()
+{
+ sDevInputAudioJack = hal::IsHeadphoneEventFromInputDev();
+ sHeadphoneState = AKEY_STATE_UNKNOWN;
+ sMicrophoneState = AKEY_STATE_UNKNOWN;
+
+ mEventHub = new EventHub();
+ mReaderPolicy = new GeckoInputReaderPolicy();
+ mReaderPolicy->setDisplayInfo();
+ mDispatcher = new GeckoInputDispatcher(mEventHub);
+
+ mReader = new InputReader(mEventHub, mReaderPolicy, mDispatcher);
+ mReaderThread = new InputReaderThread(mReader);
+
+ status_t result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ LOG("Failed to initialize InputReader thread, bad things are going to happen...");
+ }
+}
+
+nsresult
+nsAppShell::AddFdHandler(int fd, FdHandlerCallback handlerFunc,
+ const char* deviceName)
+{
+ epoll_event event = {
+ EPOLLIN,
+ { 0 }
+ };
+
+ FdHandler *handler = mHandlers.AppendElement();
+ handler->fd = fd;
+ strncpy(handler->name, deviceName, sizeof(handler->name) - 1);
+ handler->func = handlerFunc;
+ event.data.u32 = mHandlers.Length() - 1;
+ return epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) ?
+ NS_ERROR_UNEXPECTED : NS_OK;
+}
+
+void
+nsAppShell::ScheduleNativeEventCallback()
+{
+ mNativeCallbackRequest = true;
+ NotifyEvent();
+}
+
+bool
+nsAppShell::ProcessNextNativeEvent(bool mayWait)
+{
+ PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent",
+ js::ProfileEntry::Category::EVENTS);
+
+ epoll_event events[16] = {{ 0 }};
+
+ int event_count;
+ {
+ PROFILER_LABEL("nsAppShell", "ProcessNextNativeEvent::Wait",
+ js::ProfileEntry::Category::EVENTS);
+
+ if ((event_count = epoll_wait(epollfd, events, 16, mayWait ? -1 : 0)) <= 0)
+ return true;
+ }
+
+ for (int i = 0; i < event_count; i++)
+ mHandlers[events[i].data.u32].run();
+
+ if (mDispatcher.get())
+ mDispatcher->dispatchOnce();
+
+ // NativeEventCallback always schedules more if it needs it
+ // so we can coalesce these.
+ // See the implementation in nsBaseAppShell.cpp for more info
+ if (mNativeCallbackRequest) {
+ mNativeCallbackRequest = false;
+ NativeEventCallback();
+ }
+
+ if (gDrawRequest && mEnableDraw) {
+ gDrawRequest = false;
+ nsWindow::DoDraw();
+ }
+
+ return true;
+}
+
+void
+nsAppShell::NotifyNativeEvent()
+{
+ write(signalfds[1], "w", 1);
+}
+
+/* static */ void
+nsAppShell::NotifyScreenInitialized()
+{
+ gAppShell->InitInputDevices();
+
+ // Getting the instance of OrientationObserver to initialize it.
+ OrientationObserver::GetInstance();
+}
+
+/* static */ void
+nsAppShell::NotifyScreenRotation()
+{
+ gAppShell->mReaderPolicy->setDisplayInfo();
+ gAppShell->mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+ hal::NotifyScreenConfigurationChange(screen->GetConfiguration());
+}
diff --git a/widget/gonk/nsAppShell.h b/widget/gonk/nsAppShell.h
new file mode 100644
index 0000000000..046a99ea19
--- /dev/null
+++ b/widget/gonk/nsAppShell.h
@@ -0,0 +1,112 @@
+/* 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.
+ */
+
+#ifndef nsAppShell_h
+#define nsAppShell_h
+
+#include <queue>
+
+#include "mozilla/Mutex.h"
+#include "nsBaseAppShell.h"
+#include "nsTArray.h"
+
+#include "utils/RefBase.h"
+
+namespace mozilla {
+bool ProcessNextEvent();
+void NotifyEvent();
+}
+
+extern bool gDrawRequest;
+
+class FdHandler;
+typedef void(*FdHandlerCallback)(int, FdHandler *);
+
+class FdHandler {
+public:
+ FdHandler()
+ {
+ memset(name, 0, sizeof(name));
+ }
+
+ int fd;
+ char name[64];
+ FdHandlerCallback func;
+ void run()
+ {
+ func(fd, this);
+ }
+};
+
+namespace android {
+class EventHub;
+class InputReader;
+class InputReaderThread;
+}
+
+class GeckoInputReaderPolicy;
+class GeckoInputDispatcher;
+
+class nsAppShell : public nsBaseAppShell {
+public:
+ nsAppShell();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+
+ nsresult Init();
+
+ NS_IMETHOD Exit() override;
+
+ virtual bool ProcessNextNativeEvent(bool maywait);
+
+ void NotifyNativeEvent();
+
+ static void NotifyScreenInitialized();
+ static void NotifyScreenRotation();
+
+ void CheckPowerKey();
+
+protected:
+ virtual ~nsAppShell();
+
+ virtual void ScheduleNativeEventCallback();
+
+private:
+ nsresult AddFdHandler(int fd, FdHandlerCallback handlerFunc,
+ const char* deviceName);
+ void InitInputDevices();
+
+ // This is somewhat racy but is perfectly safe given how the callback works
+ bool mNativeCallbackRequest;
+
+ // This gets flipped when we observe a browser-ui-startup-complete.
+ // browser-ui-startup-complete means that we're really ready to draw
+ // and can stop the boot animation
+ bool mEnableDraw;
+ nsTArray<FdHandler> mHandlers;
+
+ android::sp<android::EventHub> mEventHub;
+ android::sp<GeckoInputReaderPolicy> mReaderPolicy;
+ android::sp<GeckoInputDispatcher> mDispatcher;
+ android::sp<android::InputReader> mReader;
+ android::sp<android::InputReaderThread> mReaderThread;
+
+ // Guard against checking power key after the first configuration change.
+ bool mPowerKeyChecked;
+};
+
+#endif /* nsAppShell_h */
+
diff --git a/widget/gonk/nsClipboard.cpp b/widget/gonk/nsClipboard.cpp
new file mode 100644
index 0000000000..a1eabe8e57
--- /dev/null
+++ b/widget/gonk/nsClipboard.cpp
@@ -0,0 +1,366 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsClipboard.h"
+
+#include "gfxDrawable.h"
+#include "gfxUtils.h"
+#include "ImageOps.h"
+#include "imgIContainer.h"
+#include "imgTools.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/Preferences.h"
+#include "nsArrayUtils.h"
+#include "nsClipboardProxy.h"
+#include "nsISupportsPrimitives.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStringStream.h"
+#include "nsXULAppAPI.h"
+
+using namespace mozilla;
+using mozilla::dom::ContentChild;
+
+#define LOG_TAG "Clipboard"
+#define LOGI(args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, ## args)
+
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard)
+
+nsClipboard::nsClipboard()
+ : mClipboard(mozilla::MakeUnique<GonkClipboardData>())
+{
+}
+
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable *aTransferable,
+ nsIClipboardOwner *anOwner,
+ int32_t aWhichClipboard)
+{
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ // Re-direct to the clipboard proxy.
+ RefPtr<nsClipboardProxy> clipboardProxy = new nsClipboardProxy();
+ return clipboardProxy->SetData(aTransferable, anOwner, aWhichClipboard);
+ }
+
+ // Clear out the clipboard in order to set the new data.
+ EmptyClipboard(aWhichClipboard);
+
+ // Use a pref to toggle rich text/non-text support.
+ if (Preferences::GetBool("clipboard.plainTextOnly")) {
+ nsCOMPtr<nsISupports> clip;
+ uint32_t len;
+ nsresult rv = aTransferable->GetTransferData(kUnicodeMime,
+ getter_AddRefs(clip),
+ &len);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ nsCOMPtr<nsISupportsString> wideString = do_QueryInterface(clip);
+ if (!wideString) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ nsAutoString utf16string;
+ wideString->GetData(utf16string);
+ mClipboard->SetText(utf16string);
+ return NS_OK;
+ }
+
+ // Get the types of supported flavors.
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanExport(getter_AddRefs(flavorList));
+ if (!flavorList || NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t flavorCount = 0;
+ flavorList->GetLength(&flavorCount);
+ bool imageAdded = false;
+ for (uint32_t i = 0; i < flavorCount; ++i) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+
+ if (currentFlavor) {
+ // MIME type
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // Clip is the data which will be sent to the clipboard.
+ nsCOMPtr<nsISupports> clip;
+ uint32_t len;
+
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ // text/plain
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(clip), &len);
+ nsCOMPtr<nsISupportsString> wideString = do_QueryInterface(clip);
+ if (!wideString || NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsAutoString utf16string;
+ wideString->GetData(utf16string);
+ mClipboard->SetText(utf16string);
+ } else if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ // text/html
+ rv = aTransferable->GetTransferData(flavorStr, getter_AddRefs(clip), &len);
+ nsCOMPtr<nsISupportsString> wideString = do_QueryInterface(clip);
+ if (!wideString || NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsAutoString utf16string;
+ wideString->GetData(utf16string);
+ mClipboard->SetHTML(utf16string);
+ } else if (!imageAdded && // image is added only once to the clipboard.
+ (flavorStr.EqualsLiteral(kNativeImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime))) {
+ // image/[png|jpeg|jpg] or application/x-moz-nativeimage
+
+ // Look through our transfer data for the image.
+ static const char* const imageMimeTypes[] = {
+ kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime };
+
+ nsCOMPtr<nsISupportsInterfacePointer> imgPtr;
+ for (uint32_t i = 0; !imgPtr && i < ArrayLength(imageMimeTypes); ++i) {
+ aTransferable->GetTransferData(imageMimeTypes[i], getter_AddRefs(clip), &len);
+ imgPtr = do_QueryInterface(clip);
+ }
+ if (!imgPtr) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> imageData;
+ imgPtr->GetData(getter_AddRefs(imageData));
+ nsCOMPtr<imgIContainer> image(do_QueryInterface(imageData));
+ if (!image) {
+ continue;
+ }
+
+ RefPtr<gfx::SourceSurface> surface =
+ image->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ if (!surface) {
+ continue;
+ }
+
+ RefPtr<gfx::DataSourceSurface> dataSurface;
+ if (surface->GetFormat() == gfx::SurfaceFormat::B8G8R8A8) {
+ dataSurface = surface->GetDataSurface();
+ } else {
+ // Convert format to SurfaceFormat::B8G8R8A8.
+ dataSurface = gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(surface, gfx::SurfaceFormat::B8G8R8A8);
+ }
+
+ mClipboard->SetImage(dataSurface);
+ imageAdded = true;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable *aTransferable,
+ int32_t aWhichClipboard)
+{
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (!XRE_IsParentProcess()) {
+ // Re-direct to the clipboard proxy.
+ RefPtr<nsClipboardProxy> clipboardProxy = new nsClipboardProxy();
+ return clipboardProxy->GetData(aTransferable, aWhichClipboard);
+ }
+
+ // Use a pref to toggle rich text/non-text support.
+ if (Preferences::GetBool("clipboard.plainTextOnly")) {
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> dataWrapper =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ rv = dataWrapper->SetData(mClipboard->GetText());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ nsCOMPtr<nsISupports> genericDataWrapper = do_QueryInterface(dataWrapper);
+ uint32_t len = mClipboard->GetText().Length() * sizeof(char16_t);
+ rv = aTransferable->SetTransferData(kUnicodeMime, genericDataWrapper, len);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ return NS_OK;
+ }
+
+ // Get flavor list that includes all acceptable flavors (including
+ // ones obtained through conversion).
+ // Note: We don't need to call nsITransferable::AddDataFlavor here
+ // because ContentParent already did.
+ nsCOMPtr<nsIArray> flavorList;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(getter_AddRefs(flavorList));
+
+ if (!flavorList || NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Walk through flavors and see which flavor matches the one being pasted.
+ uint32_t flavorCount;
+ flavorList->GetLength(&flavorCount);
+
+ for (uint32_t i = 0; i < flavorCount; ++i) {
+ nsCOMPtr<nsISupportsCString> currentFlavor = do_QueryElementAt(flavorList, i);
+
+ if (currentFlavor) {
+ // flavorStr is the mime type.
+ nsXPIDLCString flavorStr;
+ currentFlavor->ToString(getter_Copies(flavorStr));
+
+ // text/plain, text/Unicode
+ if (flavorStr.EqualsLiteral(kUnicodeMime) && mClipboard->HasText()) {
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> dataWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ rv = dataWrapper->SetData(mClipboard->GetText());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper = do_QueryInterface(dataWrapper);
+ uint32_t len = mClipboard->GetText().Length() * sizeof(char16_t);
+ rv = aTransferable->SetTransferData(flavorStr, genericDataWrapper, len);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ break;
+ }
+
+ // text/html
+ if (flavorStr.EqualsLiteral(kHTMLMime) && mClipboard->HasHTML()) {
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> dataWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ rv = dataWrapper->SetData(mClipboard->GetHTML());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> genericDataWrapper = do_QueryInterface(dataWrapper);
+ uint32_t len = mClipboard->GetHTML().Length() * sizeof(char16_t);
+ rv = aTransferable->SetTransferData(flavorStr, genericDataWrapper, len);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ break;
+ }
+
+ // image/[png|jpeg|jpg]
+ if ((flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime)) &&
+ mClipboard->HasImage() ) {
+ // Get image buffer from clipboard.
+ RefPtr<gfx::DataSourceSurface> image = mClipboard->GetImage();
+
+ // Encode according to MIME type.
+ RefPtr<gfxDrawable> drawable = new gfxSurfaceDrawable(image, image->GetSize());
+ nsCOMPtr<imgIContainer> imageContainer(image::ImageOps::CreateFromDrawable(drawable));
+ nsCOMPtr<imgITools> imgTool = do_GetService(NS_IMGTOOLS_CID);
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ nsresult rv = imgTool->EncodeImage(imageContainer,
+ flavorStr,
+ EmptyString(),
+ getter_AddRefs(byteStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ // Set transferable.
+ rv = aTransferable->SetTransferData(flavorStr,
+ byteStream,
+ sizeof(nsIInputStream*));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard)
+{
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ if (XRE_IsParentProcess()) {
+ mClipboard->Clear();
+ } else {
+ ContentChild::GetSingleton()->SendEmptyClipboard(aWhichClipboard);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const char **aFlavorList,
+ uint32_t aLength, int32_t aWhichClipboard,
+ bool *aHasType)
+{
+ *aHasType = false;
+ if (aWhichClipboard != kGlobalClipboard) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ if (XRE_IsParentProcess()) {
+ // Retrieve the union of all aHasType in aFlavorList
+ for (uint32_t i = 0; i < aLength; ++i) {
+ const char *flavor = aFlavorList[i];
+ if (!flavor) {
+ continue;
+ }
+ if (!strcmp(flavor, kUnicodeMime) && mClipboard->HasText()) {
+ *aHasType = true;
+ } else if (!strcmp(flavor, kHTMLMime) && mClipboard->HasHTML()) {
+ *aHasType = true;
+ } else if (!strcmp(flavor, kJPEGImageMime) ||
+ !strcmp(flavor, kJPGImageMime) ||
+ !strcmp(flavor, kPNGImageMime)) {
+ // We will encode the image into any format you want, so we don't
+ // need to check each specific format
+ if (mClipboard->HasImage()) {
+ *aHasType = true;
+ }
+ }
+ }
+ } else {
+ RefPtr<nsClipboardProxy> clipboardProxy = new nsClipboardProxy();
+ return clipboardProxy->HasDataMatchingFlavors(aFlavorList, aLength, aWhichClipboard, aHasType);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool *aIsSupported)
+{
+ *aIsSupported = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool* _retval)
+{
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = false;
+ return NS_OK;
+}
+
diff --git a/widget/gonk/nsClipboard.h b/widget/gonk/nsClipboard.h
new file mode 100644
index 0000000000..cd2e0dbd54
--- /dev/null
+++ b/widget/gonk/nsClipboard.h
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsClipbard_h__
+#define nsClipbard_h__
+
+#include "GonkClipboardData.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIClipboard.h"
+
+class nsClipboard final : public nsIClipboard
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICLIPBOARD
+
+ nsClipboard();
+
+protected:
+ ~nsClipboard() {}
+
+private:
+ mozilla::UniquePtr<mozilla::GonkClipboardData> mClipboard;
+};
+
+#endif
diff --git a/widget/gonk/nsIdleServiceGonk.cpp b/widget/gonk/nsIdleServiceGonk.cpp
new file mode 100644
index 0000000000..dc7588e5ee
--- /dev/null
+++ b/widget/gonk/nsIdleServiceGonk.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4: */
+/* 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 "nsIdleServiceGonk.h"
+#include "nsIServiceManager.h"
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsIdleServiceGonk, nsIdleService)
+
+bool
+nsIdleServiceGonk::PollIdleTime(uint32_t *aIdleTime)
+{
+ return false;
+}
+
+bool
+nsIdleServiceGonk::UsePollMode()
+{
+ return false;
+}
diff --git a/widget/gonk/nsIdleServiceGonk.h b/widget/gonk/nsIdleServiceGonk.h
new file mode 100644
index 0000000000..32520dce6b
--- /dev/null
+++ b/widget/gonk/nsIdleServiceGonk.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4: */
+/* 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.
+ */
+
+#ifndef nsIdleServiceGonk_h__
+#define nsIdleServiceGonk_h__
+
+#include "nsIdleService.h"
+
+class nsIdleServiceGonk : public nsIdleService
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ bool PollIdleTime(uint32_t* aIdleTime);
+
+ static already_AddRefed<nsIdleServiceGonk> GetInstance()
+ {
+ RefPtr<nsIdleServiceGonk> idleService =
+ nsIdleService::GetInstance().downcast<nsIdleServiceGonk>();
+ if (!idleService) {
+ idleService = new nsIdleServiceGonk();
+ }
+
+ return idleService.forget();
+ }
+
+protected:
+ nsIdleServiceGonk() { }
+ virtual ~nsIdleServiceGonk() { }
+ bool UsePollMode();
+};
+
+#endif // nsIdleServiceGonk_h__
diff --git a/widget/gonk/nsLookAndFeel.cpp b/widget/gonk/nsLookAndFeel.cpp
new file mode 100644
index 0000000000..120f257df4
--- /dev/null
+++ b/widget/gonk/nsLookAndFeel.cpp
@@ -0,0 +1,465 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "nsLookAndFeel.h"
+#include "nsStyleConsts.h"
+#include "gfxFont.h"
+#include "gfxFontConstants.h"
+#include "mozilla/gfx/2D.h"
+#include "cutils/properties.h"
+
+static const char16_t UNICODE_BULLET = 0x2022;
+
+nsLookAndFeel::nsLookAndFeel()
+ : nsXPLookAndFeel()
+{
+}
+
+nsLookAndFeel::~nsLookAndFeel()
+{
+}
+
+nsresult
+nsLookAndFeel::NativeGetColor(ColorID aID, nscolor &aColor)
+{
+ nsresult rv = NS_OK;
+
+#define BASE_ACTIVE_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define BASE_NORMAL_COLOR NS_RGB(0xff,0xff,0xff)
+#define BASE_SELECTED_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define BG_ACTIVE_COLOR NS_RGB(0xff,0xff,0xff)
+#define BG_INSENSITIVE_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define BG_NORMAL_COLOR NS_RGB(0xff,0xff,0xff)
+#define BG_PRELIGHT_COLOR NS_RGB(0xee,0xee,0xee)
+#define BG_SELECTED_COLOR NS_RGB(0x99,0x99,0x99)
+#define DARK_NORMAL_COLOR NS_RGB(0x88,0x88,0x88)
+#define FG_INSENSITIVE_COLOR NS_RGB(0x44,0x44,0x44)
+#define FG_NORMAL_COLOR NS_RGB(0x00,0x00,0x00)
+#define FG_PRELIGHT_COLOR NS_RGB(0x77,0x77,0x77)
+#define FG_SELECTED_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define LIGHT_NORMAL_COLOR NS_RGB(0xaa,0xaa,0xaa)
+#define TEXT_ACTIVE_COLOR NS_RGB(0x99,0x99,0x99)
+#define TEXT_NORMAL_COLOR NS_RGB(0x00,0x00,0x00)
+#define TEXT_SELECTED_COLOR NS_RGB(0x00,0x00,0x00)
+
+ switch (aID) {
+ // These colors don't seem to be used for anything anymore in Mozilla
+ // (except here at least TextSelectBackground and TextSelectForeground)
+ // The CSS2 colors below are used.
+ case eColorID_WindowBackground:
+ aColor = BASE_NORMAL_COLOR;
+ break;
+ case eColorID_WindowForeground:
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID_WidgetBackground:
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_WidgetForeground:
+ aColor = FG_NORMAL_COLOR;
+ break;
+ case eColorID_WidgetSelectBackground:
+ aColor = BG_SELECTED_COLOR;
+ break;
+ case eColorID_WidgetSelectForeground:
+ aColor = FG_SELECTED_COLOR;
+ break;
+ case eColorID_Widget3DHighlight:
+ aColor = NS_RGB(0xa0,0xa0,0xa0);
+ break;
+ case eColorID_Widget3DShadow:
+ aColor = NS_RGB(0x40,0x40,0x40);
+ break;
+ case eColorID_TextBackground:
+ // not used?
+ aColor = BASE_NORMAL_COLOR;
+ break;
+ case eColorID_TextForeground:
+ // not used?
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID_TextSelectBackground:
+ aColor = NS_RGBA(0x33,0xb5,0xe5,0x66);
+ break;
+ case eColorID_IMESelectedRawTextBackground:
+ case eColorID_IMESelectedConvertedTextBackground:
+ // still used
+ aColor = BASE_SELECTED_COLOR;
+ break;
+ case eColorID_TextSelectForegroundCustom:
+ aColor = NS_RGB(0x4d,0x4d,0x4d);
+ break;
+ case eColorID_TextSelectForeground:
+ aColor = NS_CHANGE_COLOR_IF_SAME_AS_BG;
+ break;
+ case eColorID_IMESelectedRawTextForeground:
+ case eColorID_IMESelectedConvertedTextForeground:
+ // still used
+ aColor = TEXT_SELECTED_COLOR;
+ break;
+ case eColorID_IMERawInputBackground:
+ case eColorID_IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_IMERawInputForeground:
+ case eColorID_IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMERawInputUnderline:
+ case eColorID_IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID_IMESelectedRawTextUnderline:
+ case eColorID_IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID_SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ break;
+
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case eColorID_activeborder:
+ // active window border
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_activecaption:
+ // active window caption background
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_appworkspace:
+ // MDI background color
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_background:
+ // desktop background
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_captiontext:
+ // text in active window caption, size box, and scrollbar arrow box (!)
+ aColor = FG_NORMAL_COLOR;
+ break;
+ case eColorID_graytext:
+ // disabled text in windows, menus, etc.
+ aColor = FG_INSENSITIVE_COLOR;
+ break;
+ case eColorID_highlight:
+ // background of selected item
+ aColor = BASE_SELECTED_COLOR;
+ break;
+ case eColorID_highlighttext:
+ // text of selected item
+ aColor = TEXT_SELECTED_COLOR;
+ break;
+ case eColorID_inactiveborder:
+ // inactive window border
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_inactivecaption:
+ // inactive window caption
+ aColor = BG_INSENSITIVE_COLOR;
+ break;
+ case eColorID_inactivecaptiontext:
+ // text in inactive window caption
+ aColor = FG_INSENSITIVE_COLOR;
+ break;
+ case eColorID_infobackground:
+ // tooltip background color
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_infotext:
+ // tooltip text color
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID_menu:
+ // menu background
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID_menutext:
+ // menu text
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID_scrollbar:
+ // scrollbar gray area
+ aColor = BG_ACTIVE_COLOR;
+ break;
+
+ case eColorID_threedface:
+ case eColorID_buttonface:
+ // 3-D face color
+ aColor = BG_NORMAL_COLOR;
+ break;
+
+ case eColorID_buttontext:
+ // text on push buttons
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+
+ case eColorID_buttonhighlight:
+ // 3-D highlighted edge color
+ case eColorID_threedhighlight:
+ // 3-D highlighted outer edge color
+ aColor = LIGHT_NORMAL_COLOR;
+ break;
+
+ case eColorID_threedlightshadow:
+ // 3-D highlighted inner edge color
+ aColor = BG_NORMAL_COLOR;
+ break;
+
+ case eColorID_buttonshadow:
+ // 3-D shadow edge color
+ case eColorID_threedshadow:
+ // 3-D shadow inner edge color
+ aColor = DARK_NORMAL_COLOR;
+ break;
+
+ case eColorID_threeddarkshadow:
+ // 3-D shadow outer edge color
+ aColor = NS_RGB(0,0,0);
+ break;
+
+ case eColorID_window:
+ case eColorID_windowframe:
+ aColor = BG_NORMAL_COLOR;
+ break;
+
+ case eColorID_windowtext:
+ aColor = FG_NORMAL_COLOR;
+ break;
+
+ case eColorID__moz_eventreerow:
+ case eColorID__moz_field:
+ aColor = BASE_NORMAL_COLOR;
+ break;
+ case eColorID__moz_fieldtext:
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID__moz_dialog:
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID__moz_dialogtext:
+ aColor = FG_NORMAL_COLOR;
+ break;
+ case eColorID__moz_dragtargetzone:
+ aColor = BG_SELECTED_COLOR;
+ break;
+ case eColorID__moz_buttondefault:
+ // default button border color
+ aColor = NS_RGB(0,0,0);
+ break;
+ case eColorID__moz_buttonhoverface:
+ aColor = BG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_buttonhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_cellhighlight:
+ case eColorID__moz_html_cellhighlight:
+ aColor = BASE_ACTIVE_COLOR;
+ break;
+ case eColorID__moz_cellhighlighttext:
+ case eColorID__moz_html_cellhighlighttext:
+ aColor = TEXT_ACTIVE_COLOR;
+ break;
+ case eColorID__moz_menuhover:
+ aColor = BG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_menuhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ case eColorID__moz_oddtreerow:
+ aColor = NS_TRANSPARENT;
+ break;
+ case eColorID__moz_nativehyperlinktext:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case eColorID__moz_comboboxtext:
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID__moz_combobox:
+ aColor = BG_NORMAL_COLOR;
+ break;
+ case eColorID__moz_menubartext:
+ aColor = TEXT_NORMAL_COLOR;
+ break;
+ case eColorID__moz_menubarhovertext:
+ aColor = FG_PRELIGHT_COLOR;
+ break;
+ default:
+ /* default color is BLACK */
+ aColor = 0;
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return rv;
+}
+
+nsresult
+nsLookAndFeel::GetIntImpl(IntID aID, int32_t &aResult)
+{
+ nsresult rv = nsXPLookAndFeel::GetIntImpl(aID, aResult);
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ rv = NS_OK;
+
+ switch (aID) {
+ case eIntID_CaretBlinkTime:
+ aResult = 500;
+ break;
+
+ case eIntID_CaretWidth:
+ aResult = 1;
+ break;
+
+ case eIntID_ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+
+ case eIntID_SelectTextfieldsOnKeyFocus:
+ // Select textfield content when focused by kbd
+ // used by EventStateManager::sTextfieldSelectModel
+ aResult = 1;
+ break;
+
+ case eIntID_SubmenuDelay:
+ aResult = 200;
+ break;
+
+ case eIntID_TooltipDelay:
+ aResult = 500;
+ break;
+
+ case eIntID_MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+
+ case eIntID_ScrollArrowStyle:
+ aResult = eScrollArrowStyle_Single;
+ break;
+
+ case eIntID_ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ break;
+
+ case eIntID_TouchEnabled:
+ aResult = 1;
+ break;
+
+ case eIntID_WindowsDefaultTheme:
+ case eIntID_WindowsThemeIdentifier:
+ case eIntID_OperatingSystemVersionIdentifier:
+ aResult = 0;
+ rv = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+
+ case eIntID_IMERawInputUnderlineStyle:
+ case eIntID_IMEConvertedTextUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+
+ case eIntID_IMESelectedRawTextUnderlineStyle:
+ case eIntID_IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ break;
+
+ case eIntID_SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+
+ case eIntID_ScrollbarButtonAutoRepeatBehavior:
+ aResult = 0;
+ break;
+
+ case eIntID_PhysicalHomeButton: {
+ char propValue[PROPERTY_VALUE_MAX];
+ property_get("ro.moz.has_home_button", propValue, "1");
+ aResult = atoi(propValue);
+ break;
+ }
+
+ case eIntID_ContextMenuOffsetVertical:
+ case eIntID_ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+
+ default:
+ aResult = 0;
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult
+nsLookAndFeel::GetFloatImpl(FloatID aID, float &aResult)
+{
+ nsresult res = nsXPLookAndFeel::GetFloatImpl(aID, aResult);
+ if (NS_SUCCEEDED(res))
+ return res;
+ res = NS_OK;
+
+ switch (aID) {
+ case eFloatID_IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case eFloatID_SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ default:
+ aResult = -1.0;
+ res = NS_ERROR_FAILURE;
+ }
+ return res;
+}
+
+/*virtual*/
+bool
+nsLookAndFeel::GetFontImpl(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle,
+ float aDevPixPerCSSPixel)
+{
+ aFontName.AssignLiteral("\"Fira Sans\"");
+ aFontStyle.style = NS_FONT_STYLE_NORMAL;
+ aFontStyle.weight = NS_FONT_WEIGHT_NORMAL;
+ aFontStyle.stretch = NS_FONT_STRETCH_NORMAL;
+ aFontStyle.size = 9.0 * 96.0f / 72.0f;
+ aFontStyle.systemFont = true;
+ return true;
+}
+
+/*virtual*/
+bool
+nsLookAndFeel::GetEchoPasswordImpl() {
+ return true;
+}
+
+/*virtual*/
+uint32_t
+nsLookAndFeel::GetPasswordMaskDelayImpl()
+{
+ // Same value on Android framework
+ return 1500;
+}
+
+/* virtual */
+char16_t
+nsLookAndFeel::GetPasswordCharacterImpl()
+{
+ return UNICODE_BULLET;
+}
diff --git a/widget/gonk/nsLookAndFeel.h b/widget/gonk/nsLookAndFeel.h
new file mode 100644
index 0000000000..aa7dce823c
--- /dev/null
+++ b/widget/gonk/nsLookAndFeel.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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.
+ */
+
+#ifndef __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "nsXPLookAndFeel.h"
+
+class nsLookAndFeel : public nsXPLookAndFeel
+{
+public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ virtual bool GetFontImpl(FontID aID, nsString& aName, gfxFontStyle& aStyle,
+ float aDevPixPerCSSPixel);
+ virtual nsresult GetIntImpl(IntID aID, int32_t &aResult);
+ virtual nsresult GetFloatImpl(FloatID aID, float &aResult);
+ virtual bool GetEchoPasswordImpl();
+ virtual uint32_t GetPasswordMaskDelayImpl();
+ virtual char16_t GetPasswordCharacterImpl();
+
+protected:
+ virtual nsresult NativeGetColor(ColorID aID, nscolor &aColor);
+};
+
+#endif
diff --git a/widget/gonk/nsScreenManagerGonk.cpp b/widget/gonk/nsScreenManagerGonk.cpp
new file mode 100644
index 0000000000..e359fd195e
--- /dev/null
+++ b/widget/gonk/nsScreenManagerGonk.cpp
@@ -0,0 +1,1081 @@
+/* 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 "android/log.h"
+#include "GLContext.h"
+#include "gfxPrefs.h"
+#include "gfxUtils.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/Hal.h"
+#include "libdisplay/BootAnimation.h"
+#include "libdisplay/GonkDisplay.h"
+#include "nsScreenManagerGonk.h"
+#include "nsThreadUtils.h"
+#include "HwcComposer2D.h"
+#include "VsyncSource.h"
+#include "nsWindow.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/Services.h"
+#include "mozilla/ProcessPriorityManager.h"
+#include "nsIdleService.h"
+#include "nsIObserverService.h"
+#include "nsAppShell.h"
+#include "nsProxyRelease.h"
+#include "nsTArray.h"
+#include "pixelflinger/format.h"
+#include "nsIDisplayInfo.h"
+#include "base/task.h"
+
+#if ANDROID_VERSION >= 17
+#include "libdisplay/DisplaySurface.h"
+#endif
+
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "nsScreenGonk" , ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "nsScreenGonk", ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "nsScreenGonk", ## args)
+
+using namespace mozilla;
+using namespace mozilla::hal;
+using namespace mozilla::gfx;
+using namespace mozilla::gl;
+using namespace mozilla::layers;
+using namespace mozilla::dom;
+
+namespace {
+
+class ScreenOnOffEvent : public mozilla::Runnable {
+public:
+ ScreenOnOffEvent(bool on)
+ : mIsOn(on)
+ {}
+
+ NS_IMETHOD Run() override {
+ // Notify observers that the screen state has just changed.
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(
+ nullptr, "screen-state-changed",
+ mIsOn ? u"on" : u"off"
+ );
+ }
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+ const nsTArray<nsWindow*>& windows = screen->GetTopWindows();
+
+ for (uint32_t i = 0; i < windows.Length(); i++) {
+ nsWindow *win = windows[i];
+
+ if (nsIWidgetListener* listener = win->GetWidgetListener()) {
+ listener->SizeModeChanged(mIsOn ? nsSizeMode_Fullscreen : nsSizeMode_Minimized);
+ }
+ }
+
+ return NS_OK;
+ }
+
+private:
+ bool mIsOn;
+};
+
+static void
+displayEnabledCallback(bool enabled)
+{
+ RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
+ screenManager->DisplayEnabled(enabled);
+}
+
+} // namespace
+
+static uint32_t
+SurfaceFormatToColorDepth(int32_t aSurfaceFormat)
+{
+ switch (aSurfaceFormat) {
+ case GGL_PIXEL_FORMAT_RGB_565:
+ return 16;
+ case GGL_PIXEL_FORMAT_RGBA_8888:
+ return 32;
+ }
+ return 24; // GGL_PIXEL_FORMAT_RGBX_8888
+}
+
+// nsScreenGonk.cpp
+
+nsScreenGonk::nsScreenGonk(uint32_t aId,
+ GonkDisplay::DisplayType aDisplayType,
+ const GonkDisplay::NativeData& aNativeData,
+ NotifyDisplayChangedEvent aEventVisibility)
+ : mId(aId)
+ , mEventVisibility(aEventVisibility)
+ , mNativeWindow(aNativeData.mNativeWindow)
+ , mDpi(aNativeData.mXdpi)
+ , mScreenRotation(nsIScreen::ROTATION_0_DEG)
+ , mPhysicalScreenRotation(nsIScreen::ROTATION_0_DEG)
+#if ANDROID_VERSION >= 17
+ , mDisplaySurface(aNativeData.mDisplaySurface)
+#endif
+ , mIsMirroring(false)
+ , mDisplayType(aDisplayType)
+ , mEGLDisplay(EGL_NO_DISPLAY)
+ , mEGLSurface(EGL_NO_SURFACE)
+ , mGLContext(nullptr)
+ , mFramebuffer(nullptr)
+ , mMappedBuffer(nullptr)
+{
+ if (mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_WIDTH, &mVirtualBounds.width) ||
+ mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_HEIGHT, &mVirtualBounds.height) ||
+ mNativeWindow->query(mNativeWindow.get(), NATIVE_WINDOW_FORMAT, &mSurfaceFormat)) {
+ NS_RUNTIMEABORT("Failed to get native window size, aborting...");
+ }
+
+ mNaturalBounds = mVirtualBounds;
+
+ if (IsPrimaryScreen()) {
+ char propValue[PROPERTY_VALUE_MAX];
+ property_get("ro.sf.hwrotation", propValue, "0");
+ mPhysicalScreenRotation = atoi(propValue) / 90;
+ }
+
+ mColorDepth = SurfaceFormatToColorDepth(mSurfaceFormat);
+}
+
+static void
+ReleaseGLContextSync(mozilla::gl::GLContext* aGLContext)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ aGLContext->Release();
+}
+
+nsScreenGonk::~nsScreenGonk()
+{
+ // Release GLContext on compositor thread
+ if (mGLContext) {
+ CompositorThreadHolder::Loop()->PostTask(
+ NewRunnableFunction(&ReleaseGLContextSync,
+ mGLContext.forget().take()));
+ mGLContext = nullptr;
+ }
+}
+
+bool
+nsScreenGonk::IsPrimaryScreen()
+{
+ return mDisplayType == GonkDisplay::DISPLAY_PRIMARY;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetId(uint32_t *outId)
+{
+ *outId = mId;
+ return NS_OK;
+}
+
+uint32_t
+nsScreenGonk::GetId()
+{
+ return mId;
+}
+
+NotifyDisplayChangedEvent
+nsScreenGonk::GetEventVisibility()
+{
+ return mEventVisibility;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetRect(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight)
+{
+ *outLeft = mVirtualBounds.x;
+ *outTop = mVirtualBounds.y;
+
+ *outWidth = mVirtualBounds.width;
+ *outHeight = mVirtualBounds.height;
+
+ return NS_OK;
+}
+
+LayoutDeviceIntRect
+nsScreenGonk::GetRect()
+{
+ return mVirtualBounds;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetAvailRect(int32_t *outLeft, int32_t *outTop,
+ int32_t *outWidth, int32_t *outHeight)
+{
+ return GetRect(outLeft, outTop, outWidth, outHeight);
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetPixelDepth(int32_t *aPixelDepth)
+{
+ // XXX: this should actually return 32 when we're using 24-bit
+ // color, because we use RGBX.
+ *aPixelDepth = mColorDepth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetColorDepth(int32_t *aColorDepth)
+{
+ *aColorDepth = mColorDepth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenGonk::GetRotation(uint32_t* aRotation)
+{
+ *aRotation = mScreenRotation;
+ return NS_OK;
+}
+
+float
+nsScreenGonk::GetDpi()
+{
+ return mDpi;
+}
+
+int32_t
+nsScreenGonk::GetSurfaceFormat()
+{
+ return mSurfaceFormat;
+}
+
+ANativeWindow*
+nsScreenGonk::GetNativeWindow()
+{
+ return mNativeWindow.get();
+}
+
+NS_IMETHODIMP
+nsScreenGonk::SetRotation(uint32_t aRotation)
+{
+ if (!(aRotation <= ROTATION_270_DEG)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (mScreenRotation == aRotation) {
+ return NS_OK;
+ }
+
+ mScreenRotation = aRotation;
+ uint32_t rotation = EffectiveScreenRotation();
+ if (rotation == nsIScreen::ROTATION_90_DEG ||
+ rotation == nsIScreen::ROTATION_270_DEG) {
+ mVirtualBounds = LayoutDeviceIntRect(0, 0,
+ mNaturalBounds.height,
+ mNaturalBounds.width);
+ } else {
+ mVirtualBounds = mNaturalBounds;
+ }
+
+ nsAppShell::NotifyScreenRotation();
+
+ for (unsigned int i = 0; i < mTopWindows.Length(); i++) {
+ mTopWindows[i]->Resize(mVirtualBounds.width,
+ mVirtualBounds.height,
+ true);
+ }
+
+ return NS_OK;
+}
+
+LayoutDeviceIntRect
+nsScreenGonk::GetNaturalBounds()
+{
+ return mNaturalBounds;
+}
+
+uint32_t
+nsScreenGonk::EffectiveScreenRotation()
+{
+ return (mScreenRotation + mPhysicalScreenRotation) % (360 / 90);
+}
+
+// NB: This isn't gonk-specific, but gonk is the only widget backend
+// that does this calculation itself, currently.
+static ScreenOrientationInternal
+ComputeOrientation(uint32_t aRotation, const LayoutDeviceIntSize& aScreenSize)
+{
+ bool naturallyPortrait = (aScreenSize.height > aScreenSize.width);
+ switch (aRotation) {
+ case nsIScreen::ROTATION_0_DEG:
+ return (naturallyPortrait ? eScreenOrientation_PortraitPrimary :
+ eScreenOrientation_LandscapePrimary);
+ case nsIScreen::ROTATION_90_DEG:
+ // Arbitrarily choosing 90deg to be primary "unnatural"
+ // rotation.
+ return (naturallyPortrait ? eScreenOrientation_LandscapePrimary :
+ eScreenOrientation_PortraitPrimary);
+ case nsIScreen::ROTATION_180_DEG:
+ return (naturallyPortrait ? eScreenOrientation_PortraitSecondary :
+ eScreenOrientation_LandscapeSecondary);
+ case nsIScreen::ROTATION_270_DEG:
+ return (naturallyPortrait ? eScreenOrientation_LandscapeSecondary :
+ eScreenOrientation_PortraitSecondary);
+ default:
+ MOZ_CRASH("Gonk screen must always have a known rotation");
+ }
+}
+
+static uint16_t
+RotationToAngle(uint32_t aRotation)
+{
+ uint16_t angle = 90 * aRotation;
+ MOZ_ASSERT(angle == 0 || angle == 90 || angle == 180 || angle == 270);
+ return angle;
+}
+
+ScreenConfiguration
+nsScreenGonk::GetConfiguration()
+{
+ ScreenOrientationInternal orientation =
+ ComputeOrientation(mScreenRotation, mNaturalBounds.Size());
+
+ // NB: perpetuating colorDepth == pixelDepth illusion here, for
+ // consistency.
+ return ScreenConfiguration(mVirtualBounds.ToUnknownRect(), orientation,
+ RotationToAngle(mScreenRotation),
+ mColorDepth, mColorDepth);
+}
+
+void
+nsScreenGonk::RegisterWindow(nsWindow* aWindow)
+{
+ mTopWindows.AppendElement(aWindow);
+}
+
+void
+nsScreenGonk::UnregisterWindow(nsWindow* aWindow)
+{
+ mTopWindows.RemoveElement(aWindow);
+}
+
+void
+nsScreenGonk::BringToTop(nsWindow* aWindow)
+{
+ mTopWindows.RemoveElement(aWindow);
+ mTopWindows.InsertElementAt(0, aWindow);
+}
+
+static gralloc_module_t const*
+gralloc_module()
+{
+ hw_module_t const *module;
+ if (hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module)) {
+ return nullptr;
+ }
+ return reinterpret_cast<gralloc_module_t const*>(module);
+}
+
+static SurfaceFormat
+HalFormatToSurfaceFormat(int aHalFormat)
+{
+ switch (aHalFormat) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ // Needs RB swap
+ return SurfaceFormat::B8G8R8A8;
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ // Needs RB swap
+ return SurfaceFormat::B8G8R8X8;
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ return SurfaceFormat::B8G8R8A8;
+ case HAL_PIXEL_FORMAT_RGB_565:
+ return SurfaceFormat::R5G6B5_UINT16;
+ default:
+ MOZ_CRASH("Unhandled HAL pixel format");
+ return SurfaceFormat::UNKNOWN; // not reached
+ }
+}
+
+static bool
+NeedsRBSwap(int aHalFormat)
+{
+ switch (aHalFormat) {
+ case HAL_PIXEL_FORMAT_RGBA_8888:
+ return true;
+ case HAL_PIXEL_FORMAT_RGBX_8888:
+ return true;
+ case HAL_PIXEL_FORMAT_BGRA_8888:
+ return false;
+ case HAL_PIXEL_FORMAT_RGB_565:
+ return false;
+ default:
+ MOZ_CRASH("Unhandled HAL pixel format");
+ return false; // not reached
+ }
+}
+
+already_AddRefed<DrawTarget>
+nsScreenGonk::StartRemoteDrawing()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(!mFramebuffer);
+ MOZ_ASSERT(!mMappedBuffer);
+
+ mFramebuffer = DequeueBuffer();
+ int width = mFramebuffer->width, height = mFramebuffer->height;
+ if (gralloc_module()->lock(gralloc_module(), mFramebuffer->handle,
+ GRALLOC_USAGE_SW_READ_NEVER |
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_FB,
+ 0, 0, width, height,
+ reinterpret_cast<void**>(&mMappedBuffer))) {
+ EndRemoteDrawing();
+ return nullptr;
+ }
+ SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
+ mFramebufferTarget = Factory::CreateDrawTargetForData(
+ BackendType::CAIRO,
+ mMappedBuffer,
+ IntSize(width, height),
+ mFramebuffer->stride * gfx::BytesPerPixel(format),
+ format);
+ if (!mFramebufferTarget) {
+ MOZ_CRASH("nsWindow::StartRemoteDrawing failed in CreateDrawTargetForData");
+ }
+ if (!mBackBuffer ||
+ mBackBuffer->GetSize() != mFramebufferTarget->GetSize() ||
+ mBackBuffer->GetFormat() != mFramebufferTarget->GetFormat()) {
+ mBackBuffer = mFramebufferTarget->CreateSimilarDrawTarget(
+ mFramebufferTarget->GetSize(), mFramebufferTarget->GetFormat());
+ }
+ RefPtr<DrawTarget> buffer(mBackBuffer);
+ return buffer.forget();
+}
+
+void
+nsScreenGonk::EndRemoteDrawing()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+ if (mFramebufferTarget && mFramebuffer) {
+ IntSize size = mFramebufferTarget->GetSize();
+ Rect rect(0, 0, size.width, size.height);
+ RefPtr<SourceSurface> source = mBackBuffer->Snapshot();
+ mFramebufferTarget->DrawSurface(source, rect, rect);
+
+ // Convert from BGR to RGB
+ // XXX this is a temporary solution. It consumes extra cpu cycles,
+ // it should not be used on product device.
+ if (NeedsRBSwap(GetSurfaceFormat())) {
+ LOGE("Very slow composition path, it should not be used on product!!!");
+ SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
+ gfxUtils::ConvertBGRAtoRGBA(
+ mMappedBuffer,
+ mFramebuffer->stride * mFramebuffer->height * gfx::BytesPerPixel(format));
+ }
+ }
+ if (mMappedBuffer) {
+ MOZ_ASSERT(mFramebuffer);
+ gralloc_module()->unlock(gralloc_module(), mFramebuffer->handle);
+ mMappedBuffer = nullptr;
+ }
+ if (mFramebuffer) {
+ QueueBuffer(mFramebuffer);
+ }
+ mFramebuffer = nullptr;
+ mFramebufferTarget = nullptr;
+}
+
+ANativeWindowBuffer*
+nsScreenGonk::DequeueBuffer()
+{
+ ANativeWindowBuffer* buf = nullptr;
+#if ANDROID_VERSION >= 17
+ int fenceFd = -1;
+ mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf, &fenceFd);
+ android::sp<android::Fence> fence(new android::Fence(fenceFd));
+#if ANDROID_VERSION == 17
+ fence->waitForever(1000, "nsScreenGonk_DequeueBuffer");
+ // 1000 is what Android uses. It is a warning timeout in ms.
+ // This timeout was removed in ANDROID_VERSION 18.
+#else
+ fence->waitForever("nsScreenGonk_DequeueBuffer");
+#endif
+#else
+ mNativeWindow->dequeueBuffer(mNativeWindow.get(), &buf);
+#endif
+ return buf;
+}
+
+bool
+nsScreenGonk::QueueBuffer(ANativeWindowBuffer* buf)
+{
+#if ANDROID_VERSION >= 17
+ int ret = mNativeWindow->queueBuffer(mNativeWindow.get(), buf, -1);
+ return ret == 0;
+#else
+ int ret = mNativeWindow->queueBuffer(mNativeWindow.get(), buf);
+ return ret == 0;
+#endif
+}
+
+nsresult
+nsScreenGonk::MakeSnapshot(ANativeWindowBuffer* aBuffer)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(aBuffer);
+
+ layers::CompositorBridgeParent* compositorParent = mCompositorBridgeParent;
+ if (!compositorParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int width = aBuffer->width, height = aBuffer->height;
+ uint8_t* mappedBuffer = nullptr;
+ if (gralloc_module()->lock(gralloc_module(), aBuffer->handle,
+ GRALLOC_USAGE_SW_READ_OFTEN |
+ GRALLOC_USAGE_SW_WRITE_OFTEN,
+ 0, 0, width, height,
+ reinterpret_cast<void**>(&mappedBuffer))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
+ RefPtr<DrawTarget> mTarget =
+ Factory::CreateDrawTargetForData(
+ BackendType::CAIRO,
+ mappedBuffer,
+ IntSize(width, height),
+ aBuffer->stride * gfx::BytesPerPixel(format),
+ format);
+ if (!mTarget) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gfx::IntRect rect = GetRect().ToUnknownRect();
+ compositorParent->ForceComposeToTarget(mTarget, &rect);
+
+ // Convert from BGR to RGB
+ // XXX this is a temporary solution. It consumes extra cpu cycles,
+ if (NeedsRBSwap(GetSurfaceFormat())) {
+ LOGE("Slow path of making Snapshot!!!");
+ SurfaceFormat format = HalFormatToSurfaceFormat(GetSurfaceFormat());
+ gfxUtils::ConvertBGRAtoRGBA(
+ mappedBuffer,
+ aBuffer->stride * aBuffer->height * gfx::BytesPerPixel(format));
+ mappedBuffer = nullptr;
+ }
+ gralloc_module()->unlock(gralloc_module(), aBuffer->handle);
+ return NS_OK;
+}
+
+void
+nsScreenGonk::SetCompositorBridgeParent(layers::CompositorBridgeParent* aCompositorBridgeParent)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mCompositorBridgeParent = aCompositorBridgeParent;
+}
+
+#if ANDROID_VERSION >= 17
+android::DisplaySurface*
+nsScreenGonk::GetDisplaySurface()
+{
+ return mDisplaySurface.get();
+}
+
+int
+nsScreenGonk::GetPrevDispAcquireFd()
+{
+ if (!mDisplaySurface.get()) {
+ return -1;
+ }
+ return mDisplaySurface->GetPrevDispAcquireFd();
+}
+#endif
+
+GonkDisplay::DisplayType
+nsScreenGonk::GetDisplayType()
+{
+ return mDisplayType;
+}
+
+void
+nsScreenGonk::SetEGLInfo(hwc_display_t aDisplay,
+ hwc_surface_t aSurface,
+ gl::GLContext* aGLContext)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ mEGLDisplay = aDisplay;
+ mEGLSurface = aSurface;
+ mGLContext = aGLContext;
+}
+
+hwc_display_t
+nsScreenGonk::GetEGLDisplay()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ return mEGLDisplay;
+}
+
+hwc_surface_t
+nsScreenGonk::GetEGLSurface()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ return mEGLSurface;
+}
+
+already_AddRefed<mozilla::gl::GLContext>
+nsScreenGonk::GetGLContext()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ RefPtr<mozilla::gl::GLContext>glContext = mGLContext;
+ return glContext.forget();
+}
+
+static void
+UpdateMirroringWidgetSync(nsMainThreadPtrHandle<nsScreenGonk>&& aScreen, nsWindow* aWindow)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ already_AddRefed<nsWindow> window(aWindow);
+ aScreen->UpdateMirroringWidget(window);
+}
+
+bool
+nsScreenGonk::EnableMirroring()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsPrimaryScreen());
+
+ RefPtr<nsScreenGonk> primaryScreen = nsScreenManagerGonk::GetPrimaryScreen();
+ NS_ENSURE_TRUE(primaryScreen, false);
+
+ bool ret = primaryScreen->SetMirroringScreen(this);
+ NS_ENSURE_TRUE(ret, false);
+
+ // Create a widget for mirroring
+ nsWidgetInitData initData;
+ initData.mScreenId = mId;
+ RefPtr<nsWindow> window = new nsWindow();
+ nsresult rv = window->Create(nullptr, nullptr, mNaturalBounds, &initData);
+ NS_ENSURE_SUCCESS(rv, false);
+ MOZ_ASSERT(static_cast<nsWindow*>(window)->GetScreen() == this);
+
+ // Update mMirroringWidget on compositor thread
+ nsMainThreadPtrHandle<nsScreenGonk> primary =
+ nsMainThreadPtrHandle<nsScreenGonk>(new nsMainThreadPtrHolder<nsScreenGonk>(primaryScreen, false));
+ CompositorThreadHolder::Loop()->PostTask(
+ NewRunnableFunction(&UpdateMirroringWidgetSync,
+ primary,
+ window.forget().take()));
+
+ mIsMirroring = true;
+ return true;
+}
+
+bool
+nsScreenGonk::DisableMirroring()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!IsPrimaryScreen());
+
+ mIsMirroring = false;
+ RefPtr<nsScreenGonk> primaryScreen = nsScreenManagerGonk::GetPrimaryScreen();
+ NS_ENSURE_TRUE(primaryScreen, false);
+
+ bool ret = primaryScreen->ClearMirroringScreen(this);
+ NS_ENSURE_TRUE(ret, false);
+
+ // Update mMirroringWidget on compositor thread
+ nsMainThreadPtrHandle<nsScreenGonk> primary =
+ nsMainThreadPtrHandle<nsScreenGonk>(new nsMainThreadPtrHolder<nsScreenGonk>(primaryScreen, false));
+ CompositorThreadHolder::Loop()->PostTask(
+ NewRunnableFunction(&UpdateMirroringWidgetSync,
+ primary,
+ nullptr));
+ return true;
+}
+
+bool
+nsScreenGonk::SetMirroringScreen(nsScreenGonk* aScreen)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(IsPrimaryScreen());
+
+ if (mMirroringScreen) {
+ return false;
+ }
+ mMirroringScreen = aScreen;
+ return true;
+}
+
+bool
+nsScreenGonk::ClearMirroringScreen(nsScreenGonk* aScreen)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(IsPrimaryScreen());
+
+ if (mMirroringScreen != aScreen) {
+ return false;
+ }
+ mMirroringScreen = nullptr;
+ return true;
+}
+
+void
+nsScreenGonk::UpdateMirroringWidget(already_AddRefed<nsWindow>& aWindow)
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(IsPrimaryScreen());
+
+ if (mMirroringWidget) {
+ nsCOMPtr<nsIWidget> widget = mMirroringWidget.forget();
+ NS_ReleaseOnMainThread(widget.forget());
+ }
+ mMirroringWidget = aWindow;
+}
+
+nsWindow*
+nsScreenGonk::GetMirroringWidget()
+{
+ MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+ MOZ_ASSERT(IsPrimaryScreen());
+
+ return mMirroringWidget;
+}
+
+NS_IMPL_ISUPPORTS(nsScreenManagerGonk, nsIScreenManager)
+
+nsScreenManagerGonk::nsScreenManagerGonk()
+ : mInitialized(false)
+#if ANDROID_VERSION >= 19
+ , mDisplayEnabled(false)
+#endif
+{
+}
+
+nsScreenManagerGonk::~nsScreenManagerGonk()
+{
+}
+
+static StaticRefPtr<nsScreenManagerGonk> sScreenManagerGonk;
+
+/* static */ already_AddRefed<nsScreenManagerGonk>
+nsScreenManagerGonk::GetInstance()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Avoid creating nsScreenManagerGonk from content process.
+ if (!XRE_IsParentProcess()) {
+ MOZ_CRASH("Non-chrome processes should not get here.");
+ }
+
+ // Avoid creating multiple nsScreenManagerGonk instance inside main process.
+ if (!sScreenManagerGonk) {
+ sScreenManagerGonk = new nsScreenManagerGonk();
+ ClearOnShutdown(&sScreenManagerGonk);
+ }
+
+ RefPtr<nsScreenManagerGonk> screenMgr = sScreenManagerGonk.get();
+ return screenMgr.forget();
+}
+
+/* static */ already_AddRefed< nsScreenGonk>
+nsScreenManagerGonk::GetPrimaryScreen()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<nsScreenManagerGonk> manager = nsScreenManagerGonk::GetInstance();
+ nsCOMPtr<nsIScreen> screen;
+ manager->GetPrimaryScreen(getter_AddRefs(screen));
+ MOZ_ASSERT(screen);
+ return already_AddRefed<nsScreenGonk>(
+ static_cast<nsScreenGonk*>(screen.forget().take()));
+}
+
+void
+nsScreenManagerGonk::Initialize()
+{
+ if (mInitialized) {
+ return;
+ }
+
+ mScreenOnEvent = new ScreenOnOffEvent(true);
+ mScreenOffEvent = new ScreenOnOffEvent(false);
+ GetGonkDisplay()->OnEnabled(displayEnabledCallback);
+
+ AddScreen(GonkDisplay::DISPLAY_PRIMARY);
+
+ nsAppShell::NotifyScreenInitialized();
+ mInitialized = true;
+}
+
+void
+nsScreenManagerGonk::DisplayEnabled(bool aEnabled)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+#if ANDROID_VERSION >= 19
+ /* Bug 1244044
+ * This function could be called before |mCompositorVsyncScheduler| is set.
+ * To avoid this issue, keep the value stored in |mDisplayEnabled|.
+ */
+ mDisplayEnabled = aEnabled;
+ if (mCompositorVsyncScheduler) {
+ mCompositorVsyncScheduler->SetDisplay(mDisplayEnabled);
+ }
+#endif
+
+ VsyncControl(aEnabled);
+ NS_DispatchToMainThread(aEnabled ? mScreenOnEvent : mScreenOffEvent);
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::GetPrimaryScreen(nsIScreen **outScreen)
+{
+ NS_IF_ADDREF(*outScreen = mScreens[0].get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::ScreenForId(uint32_t aId,
+ nsIScreen **outScreen)
+{
+ for (size_t i = 0; i < mScreens.Length(); i++) {
+ if (mScreens[i]->GetId() == aId) {
+ NS_IF_ADDREF(*outScreen = mScreens[i].get());
+ return NS_OK;
+ }
+ }
+
+ *outScreen = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::ScreenForRect(int32_t inLeft,
+ int32_t inTop,
+ int32_t inWidth,
+ int32_t inHeight,
+ nsIScreen **outScreen)
+{
+ // Since all screens have independent coordinate system, we could
+ // only return the primary screen no matter what rect is given.
+ return GetPrimaryScreen(outScreen);
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::ScreenForNativeWidget(void *aWidget, nsIScreen **outScreen)
+{
+ for (size_t i = 0; i < mScreens.Length(); i++) {
+ if (aWidget == mScreens[i]->GetNativeWindow()) {
+ NS_IF_ADDREF(*outScreen = mScreens[i].get());
+ return NS_OK;
+ }
+ }
+
+ *outScreen = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::GetNumberOfScreens(uint32_t *aNumberOfScreens)
+{
+ *aNumberOfScreens = mScreens.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsScreenManagerGonk::GetSystemDefaultScale(float *aDefaultScale)
+{
+ *aDefaultScale = 1.0f;
+ return NS_OK;
+}
+
+void
+nsScreenManagerGonk::VsyncControl(bool aEnabled)
+{
+ if (!NS_IsMainThread()) {
+ NS_DispatchToMainThread(
+ NewRunnableMethod<bool>(this,
+ &nsScreenManagerGonk::VsyncControl,
+ aEnabled));
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ VsyncSource::Display &display = gfxPlatform::GetPlatform()->GetHardwareVsync()->GetGlobalDisplay();
+ if (aEnabled) {
+ display.EnableVsync();
+ } else {
+ display.DisableVsync();
+ }
+}
+
+uint32_t
+nsScreenManagerGonk::GetIdFromType(GonkDisplay::DisplayType aDisplayType)
+{
+ // This is the only place where we make the assumption that
+ // display type is equivalent to screen id.
+
+ // Bug 1138287 will address the conversion from type to id.
+ return aDisplayType;
+}
+
+bool
+nsScreenManagerGonk::IsScreenConnected(uint32_t aId)
+{
+ for (size_t i = 0; i < mScreens.Length(); ++i) {
+ if (mScreens[i]->GetId() == aId) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace {
+
+// A concrete class as a subject for 'display-changed' observer event.
+class DisplayInfo : public nsIDisplayInfo {
+public:
+ NS_DECL_ISUPPORTS
+
+ DisplayInfo(uint32_t aId, bool aConnected)
+ : mId(aId)
+ , mConnected(aConnected)
+ {
+ }
+
+ NS_IMETHODIMP GetId(int32_t *aId)
+ {
+ *aId = mId;
+ return NS_OK;
+ }
+
+ NS_IMETHODIMP GetConnected(bool *aConnected)
+ {
+ *aConnected = mConnected;
+ return NS_OK;
+ }
+
+private:
+ virtual ~DisplayInfo() {}
+
+ uint32_t mId;
+ bool mConnected;
+};
+
+NS_IMPL_ISUPPORTS(DisplayInfo, nsIDisplayInfo, nsISupports)
+
+class NotifyTask : public mozilla::Runnable {
+public:
+ NotifyTask(uint32_t aId, bool aConnected)
+ : mDisplayInfo(new DisplayInfo(aId, aConnected))
+ {
+ }
+
+ NS_IMETHOD Run() override
+ {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->NotifyObservers(mDisplayInfo, "display-changed", nullptr);
+ }
+
+ return NS_OK;
+ }
+private:
+ RefPtr<DisplayInfo> mDisplayInfo;
+};
+
+void
+NotifyDisplayChange(uint32_t aId, bool aConnected)
+{
+ NS_DispatchToMainThread(new NotifyTask(aId, aConnected));
+}
+
+} // end of unnamed namespace.
+
+nsresult
+nsScreenManagerGonk::AddScreen(GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink,
+ NotifyDisplayChangedEvent aEventVisibility)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(aDisplayType < GonkDisplay::DisplayType::NUM_DISPLAY_TYPES,
+ NS_ERROR_FAILURE);
+
+ uint32_t id = GetIdFromType(aDisplayType);
+ NS_ENSURE_TRUE(!IsScreenConnected(id), NS_ERROR_FAILURE);
+
+ GonkDisplay::NativeData nativeData =
+ GetGonkDisplay()->GetNativeData(aDisplayType, aSink);
+ nsScreenGonk* screen = new nsScreenGonk(id,
+ aDisplayType,
+ nativeData,
+ aEventVisibility);
+ mScreens.AppendElement(screen);
+
+ if (aEventVisibility == NotifyDisplayChangedEvent::Observable) {
+ NotifyDisplayChange(id, true);
+ }
+
+ // By default, non primary screen does mirroring.
+ if (aDisplayType != GonkDisplay::DISPLAY_PRIMARY &&
+ gfxPrefs::ScreenMirroringEnabled()) {
+ screen->EnableMirroring();
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsScreenManagerGonk::RemoveScreen(GonkDisplay::DisplayType aDisplayType)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ENSURE_TRUE(aDisplayType < GonkDisplay::DisplayType::NUM_DISPLAY_TYPES,
+ NS_ERROR_FAILURE);
+
+ NotifyDisplayChangedEvent eventVisibility = NotifyDisplayChangedEvent::Observable;
+ uint32_t screenId = GetIdFromType(aDisplayType);
+ NS_ENSURE_TRUE(IsScreenConnected(screenId), NS_ERROR_FAILURE);
+
+ for (size_t i = 0; i < mScreens.Length(); i++) {
+ if (mScreens[i]->GetId() == screenId) {
+ if (mScreens[i]->IsMirroring()) {
+ mScreens[i]->DisableMirroring();
+ }
+ eventVisibility = mScreens[i]->GetEventVisibility();
+ mScreens.RemoveElementAt(i);
+ break;
+ }
+ }
+
+ if (eventVisibility == NotifyDisplayChangedEvent::Observable) {
+ NotifyDisplayChange(screenId, false);
+ }
+ return NS_OK;
+}
+
+#if ANDROID_VERSION >= 19
+void
+nsScreenManagerGonk::SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler *aObserver)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We assume on b2g that there is only 1 CompositorBridgeParent
+ MOZ_ASSERT(mCompositorVsyncScheduler == nullptr);
+ MOZ_ASSERT(aObserver);
+ mCompositorVsyncScheduler = aObserver;
+ mCompositorVsyncScheduler->SetDisplay(mDisplayEnabled);
+}
+#endif
diff --git a/widget/gonk/nsScreenManagerGonk.h b/widget/gonk/nsScreenManagerGonk.h
new file mode 100644
index 0000000000..33ef5edb89
--- /dev/null
+++ b/widget/gonk/nsScreenManagerGonk.h
@@ -0,0 +1,228 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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.
+ */
+
+#ifndef nsScreenManagerGonk_h___
+#define nsScreenManagerGonk_h___
+
+#include "cutils/properties.h"
+#include "hardware/hwcomposer.h"
+
+#include "libdisplay/GonkDisplay.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Mutex.h"
+#include "nsBaseScreen.h"
+#include "nsCOMPtr.h"
+#include "nsIScreenManager.h"
+#include "nsProxyRelease.h"
+
+#include <android/native_window.h>
+
+class nsWindow;
+
+namespace android {
+ class DisplaySurface;
+ class IGraphicBufferProducer;
+};
+
+namespace mozilla {
+ class Runnable;
+namespace gl {
+ class GLContext;
+}
+namespace layers {
+class CompositorVsyncScheduler;
+class CompositorBridgeParent;
+}
+}
+
+enum class NotifyDisplayChangedEvent : int8_t {
+ Observable,
+ Suppressed
+};
+
+class nsScreenGonk : public nsBaseScreen
+{
+ typedef mozilla::hal::ScreenConfiguration ScreenConfiguration;
+ typedef mozilla::GonkDisplay GonkDisplay;
+ typedef mozilla::LayoutDeviceIntRect LayoutDeviceIntRect;
+ typedef mozilla::layers::CompositorBridgeParent CompositorBridgeParent;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+public:
+ nsScreenGonk(uint32_t aId,
+ GonkDisplay::DisplayType aDisplayType,
+ const GonkDisplay::NativeData& aNativeData,
+ NotifyDisplayChangedEvent aEventVisibility);
+
+ ~nsScreenGonk();
+
+ NS_IMETHOD GetId(uint32_t* aId);
+ NS_IMETHOD GetRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetAvailRect(int32_t* aLeft, int32_t* aTop, int32_t* aWidth, int32_t* aHeight);
+ NS_IMETHOD GetPixelDepth(int32_t* aPixelDepth);
+ NS_IMETHOD GetColorDepth(int32_t* aColorDepth);
+ NS_IMETHOD GetRotation(uint32_t* aRotation);
+ NS_IMETHOD SetRotation(uint32_t aRotation);
+
+ uint32_t GetId();
+ NotifyDisplayChangedEvent GetEventVisibility();
+ LayoutDeviceIntRect GetRect();
+ float GetDpi();
+ int32_t GetSurfaceFormat();
+ ANativeWindow* GetNativeWindow();
+ LayoutDeviceIntRect GetNaturalBounds();
+ uint32_t EffectiveScreenRotation();
+ ScreenConfiguration GetConfiguration();
+ bool IsPrimaryScreen();
+
+ already_AddRefed<DrawTarget> StartRemoteDrawing();
+ void EndRemoteDrawing();
+
+ nsresult MakeSnapshot(ANativeWindowBuffer* aBuffer);
+ void SetCompositorBridgeParent(CompositorBridgeParent* aCompositorBridgeParent);
+
+#if ANDROID_VERSION >= 17
+ android::DisplaySurface* GetDisplaySurface();
+ int GetPrevDispAcquireFd();
+#endif
+ GonkDisplay::DisplayType GetDisplayType();
+
+ void RegisterWindow(nsWindow* aWindow);
+ void UnregisterWindow(nsWindow* aWindow);
+ void BringToTop(nsWindow* aWindow);
+
+ const nsTArray<nsWindow*>& GetTopWindows() const
+ {
+ return mTopWindows;
+ }
+
+ // Non-primary screen only
+ bool EnableMirroring();
+ bool DisableMirroring();
+ bool IsMirroring()
+ {
+ return mIsMirroring;
+ }
+
+ // Primary screen only
+ bool SetMirroringScreen(nsScreenGonk* aScreen);
+ bool ClearMirroringScreen(nsScreenGonk* aScreen);
+
+ // Called only on compositor thread
+ void SetEGLInfo(hwc_display_t aDisplay, hwc_surface_t aSurface,
+ mozilla::gl::GLContext* aGLContext);
+ hwc_display_t GetEGLDisplay();
+ hwc_surface_t GetEGLSurface();
+ already_AddRefed<mozilla::gl::GLContext> GetGLContext();
+ void UpdateMirroringWidget(already_AddRefed<nsWindow>& aWindow); // Primary screen only
+ nsWindow* GetMirroringWidget(); // Primary screen only
+
+protected:
+ ANativeWindowBuffer* DequeueBuffer();
+ bool QueueBuffer(ANativeWindowBuffer* buf);
+
+ uint32_t mId;
+ NotifyDisplayChangedEvent mEventVisibility;
+ int32_t mColorDepth;
+ android::sp<ANativeWindow> mNativeWindow;
+ float mDpi;
+ int32_t mSurfaceFormat;
+ LayoutDeviceIntRect mNaturalBounds; // Screen bounds w/o rotation taken into account.
+ LayoutDeviceIntRect mVirtualBounds; // Screen bounds w/ rotation taken into account.
+ uint32_t mScreenRotation;
+ uint32_t mPhysicalScreenRotation;
+ nsTArray<nsWindow*> mTopWindows;
+#if ANDROID_VERSION >= 17
+ android::sp<android::DisplaySurface> mDisplaySurface;
+#endif
+ bool mIsMirroring; // Non-primary screen only
+ RefPtr<nsScreenGonk> mMirroringScreen; // Primary screen only
+ mozilla::Atomic<CompositorBridgeParent*> mCompositorBridgeParent;
+
+ // Accessed and updated only on compositor thread
+ GonkDisplay::DisplayType mDisplayType;
+ hwc_display_t mEGLDisplay;
+ hwc_surface_t mEGLSurface;
+ RefPtr<mozilla::gl::GLContext> mGLContext;
+ RefPtr<nsWindow> mMirroringWidget; // Primary screen only
+
+ // If we're using a BasicCompositor, these fields are temporarily
+ // set during frame composition. They wrap the hardware
+ // framebuffer.
+ RefPtr<DrawTarget> mFramebufferTarget;
+ ANativeWindowBuffer* mFramebuffer;
+ /**
+ * Points to a mapped gralloc buffer between calls to lock and unlock.
+ * Should be null outside of the lock-unlock pair.
+ */
+ uint8_t* mMappedBuffer;
+ // If we're using a BasicCompositor, this is our window back
+ // buffer. The gralloc framebuffer driver expects us to draw the
+ // entire framebuffer on every frame, but gecko expects the
+ // windowing system to be tracking buffer updates for invalidated
+ // regions. We get stuck holding that bag.
+ //
+ // Only accessed on the compositor thread, except during
+ // destruction.
+ RefPtr<DrawTarget> mBackBuffer;
+};
+
+class nsScreenManagerGonk final : public nsIScreenManager
+{
+public:
+ typedef mozilla::GonkDisplay GonkDisplay;
+
+public:
+ nsScreenManagerGonk();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISCREENMANAGER
+
+ static already_AddRefed<nsScreenManagerGonk> GetInstance();
+ static already_AddRefed<nsScreenGonk> GetPrimaryScreen();
+
+ void Initialize();
+ void DisplayEnabled(bool aEnabled);
+
+ nsresult AddScreen(GonkDisplay::DisplayType aDisplayType,
+ android::IGraphicBufferProducer* aSink = nullptr,
+ NotifyDisplayChangedEvent aEventVisibility = NotifyDisplayChangedEvent::Observable);
+
+ nsresult RemoveScreen(GonkDisplay::DisplayType aDisplayType);
+
+#if ANDROID_VERSION >= 19
+ void SetCompositorVsyncScheduler(mozilla::layers::CompositorVsyncScheduler* aObserver);
+#endif
+
+protected:
+ ~nsScreenManagerGonk();
+ void VsyncControl(bool aEnabled);
+ uint32_t GetIdFromType(GonkDisplay::DisplayType aDisplayType);
+ bool IsScreenConnected(uint32_t aId);
+
+ bool mInitialized;
+ nsTArray<RefPtr<nsScreenGonk>> mScreens;
+ RefPtr<mozilla::Runnable> mScreenOnEvent;
+ RefPtr<mozilla::Runnable> mScreenOffEvent;
+
+#if ANDROID_VERSION >= 19
+ bool mDisplayEnabled;
+ RefPtr<mozilla::layers::CompositorVsyncScheduler> mCompositorVsyncScheduler;
+#endif
+};
+
+#endif /* nsScreenManagerGonk_h___ */
diff --git a/widget/gonk/nsWidgetFactory.cpp b/widget/gonk/nsWidgetFactory.cpp
new file mode 100644
index 0000000000..1c75255445
--- /dev/null
+++ b/widget/gonk/nsWidgetFactory.cpp
@@ -0,0 +1,128 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "base/basictypes.h"
+
+#include "mozilla/ModuleUtils.h"
+#include "mozilla/WidgetUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+
+#include "nsWindow.h"
+#include "nsLookAndFeel.h"
+#include "nsAppShellSingleton.h"
+#include "nsScreenManagerGonk.h"
+#include "nsIdleServiceGonk.h"
+#include "nsTransferable.h"
+#include "nsClipboard.h"
+#include "nsClipboardHelper.h"
+
+#include "nsHTMLFormatConverter.h"
+#include "nsXULAppAPI.h"
+
+#include "PuppetWidget.h"
+
+using namespace mozilla::widget;
+
+// taken from android/nsWidgetFactory.cpp. GfxInfo is a legacy kludge, unfortunately
+// for the time being we still have to implement it on all platforms.
+#include "GfxInfo.h"
+namespace mozilla {
+namespace widget {
+// This constructor should really be shared with all platforms.
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init)
+}
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindow)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsScreenManagerGonk, nsScreenManagerGonk::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(PuppetScreenManager)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsHTMLFormatConverter)
+NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIdleServiceGonk, nsIdleServiceGonk::GetInstance)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTransferable)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboard)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsClipboardHelper)
+
+NS_DEFINE_NAMED_CID(NS_APPSHELL_CID);
+NS_DEFINE_NAMED_CID(NS_WINDOW_CID);
+NS_DEFINE_NAMED_CID(NS_CHILD_CID);
+NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID);
+NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID);
+NS_DEFINE_NAMED_CID(NS_IDLE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID);
+NS_DEFINE_NAMED_CID(NS_GFXINFO_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID);
+NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID);
+
+static nsresult
+ScreenManagerConstructor(nsISupports *aOuter, REFNSIID aIID, void **aResult)
+{
+ return (XRE_IsParentProcess()) ?
+ nsScreenManagerGonkConstructor(aOuter, aIID, aResult) :
+ PuppetScreenManagerConstructor(aOuter, aIID, aResult);
+}
+
+static const mozilla::Module::CIDEntry kWidgetCIDs[] = {
+ { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_CHILD_CID, false, nullptr, nsWindowConstructor },
+ { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor },
+ { &kNS_SCREENMANAGER_CID, false, nullptr, ScreenManagerConstructor },
+ { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor },
+ { &kNS_IDLE_SERVICE_CID, false, nullptr, nsIdleServiceGonkConstructor },
+ { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor },
+ { &kNS_GFXINFO_CID, false, nullptr, mozilla::widget::GfxInfoConstructor },
+ { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor },
+ { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor },
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kWidgetContracts[] = {
+ { "@mozilla.org/widgets/window/gonk;1", &kNS_WINDOW_CID },
+ { "@mozilla.org/widgets/child_window/gonk;1", &kNS_CHILD_CID },
+ { "@mozilla.org/widget/appshell/gonk;1", &kNS_APPSHELL_CID },
+ { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID },
+ { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID },
+ { "@mozilla.org/widget/idleservice;1", &kNS_IDLE_SERVICE_CID },
+ { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID },
+ { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID },
+ { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID },
+ { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID },
+ { nullptr }
+};
+
+static void
+nsWidgetGonkModuleDtor()
+{
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ nsLookAndFeel::Shutdown();
+ nsAppShellShutdown();
+}
+
+static const mozilla::Module kWidgetModule = {
+ mozilla::Module::kVersion,
+ kWidgetCIDs,
+ kWidgetContracts,
+ nullptr,
+ nullptr,
+ nsAppShellInit,
+ nsWidgetGonkModuleDtor
+};
+
+NSMODULE_DEFN(nsWidgetGonkModule) = &kWidgetModule;
diff --git a/widget/gonk/nsWindow.cpp b/widget/gonk/nsWindow.cpp
new file mode 100644
index 0000000000..e11b7f2339
--- /dev/null
+++ b/widget/gonk/nsWindow.cpp
@@ -0,0 +1,744 @@
+/* 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 "nsWindow.h"
+
+#include "mozilla/DebugOnly.h"
+
+#include <fcntl.h>
+
+#include "android/log.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Services.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "GLContextProvider.h"
+#include "GLContext.h"
+#include "GLContextEGL.h"
+#include "nsAppShell.h"
+#include "nsScreenManagerGonk.h"
+#include "nsTArray.h"
+#include "nsIWidgetListener.h"
+#include "ClientLayerManager.h"
+#include "BasicLayers.h"
+#include "libdisplay/GonkDisplay.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/APZCTreeManager.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/CompositorSession.h"
+#include "mozilla/TouchEvents.h"
+#include "HwcComposer2D.h"
+
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args)
+#define LOGW(args...) __android_log_print(ANDROID_LOG_WARN, "Gonk", ## args)
+#define LOGE(args...) __android_log_print(ANDROID_LOG_ERROR, "Gonk", ## args)
+
+#define IS_TOPLEVEL() (mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog)
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::hal;
+using namespace mozilla::gfx;
+using namespace mozilla::gl;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+
+static nsWindow *gFocusedWindow = nullptr;
+
+NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
+
+nsWindow::nsWindow()
+{
+ RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
+ screenManager->Initialize();
+
+ // This is a hack to force initialization of the compositor
+ // resources, if we're going to use omtc.
+ //
+ // NB: GetPlatform() will create the gfxPlatform, which wants
+ // to know the color depth, which asks our native window.
+ // This has to happen after other init has finished.
+ gfxPlatform::GetPlatform();
+ if (!ShouldUseOffMainThreadCompositing()) {
+ MOZ_CRASH("How can we render apps, then?");
+ }
+}
+
+nsWindow::~nsWindow()
+{
+ if (mScreen->IsPrimaryScreen()) {
+ mComposer2D->SetCompositorBridgeParent(nullptr);
+ }
+}
+
+void
+nsWindow::DoDraw(void)
+{
+ if (!hal::GetScreenEnabled()) {
+ gDrawRequest = true;
+ return;
+ }
+
+ RefPtr<nsScreenGonk> screen = nsScreenManagerGonk::GetPrimaryScreen();
+ const nsTArray<nsWindow*>& windows = screen->GetTopWindows();
+
+ if (windows.IsEmpty()) {
+ LOG(" no window to draw, bailing");
+ return;
+ }
+
+ RefPtr<nsWindow> targetWindow = (nsWindow *)windows[0];
+ while (targetWindow->GetLastChild()) {
+ targetWindow = (nsWindow *)targetWindow->GetLastChild();
+ }
+
+ nsIWidgetListener* listener = targetWindow->GetWidgetListener();
+ if (listener) {
+ listener->WillPaintWindow(targetWindow);
+ }
+
+ listener = targetWindow->GetWidgetListener();
+ if (listener) {
+ LayerManager* lm = targetWindow->GetLayerManager();
+ if (mozilla::layers::LayersBackend::LAYERS_CLIENT == lm->GetBackendType()) {
+ // No need to do anything, the compositor will handle drawing
+ } else {
+ NS_RUNTIMEABORT("Unexpected layer manager type");
+ }
+
+ listener->DidPaintWindow();
+ }
+}
+
+void
+nsWindow::ConfigureAPZControllerThread()
+{
+ APZThreadUtils::SetControllerThread(CompositorThreadHolder::Loop());
+}
+
+/*static*/ nsEventStatus
+nsWindow::DispatchKeyInput(WidgetKeyboardEvent& aEvent)
+{
+ if (!gFocusedWindow) {
+ return nsEventStatus_eIgnore;
+ }
+
+ gFocusedWindow->UserActivity();
+
+ nsEventStatus status;
+ aEvent.mWidget = gFocusedWindow;
+ gFocusedWindow->DispatchEvent(&aEvent, status);
+ return status;
+}
+
+/*static*/ void
+nsWindow::DispatchTouchInput(MultiTouchInput& aInput)
+{
+ APZThreadUtils::AssertOnControllerThread();
+
+ if (!gFocusedWindow) {
+ return;
+ }
+
+ gFocusedWindow->DispatchTouchInputViaAPZ(aInput);
+}
+
+class DispatchTouchInputOnMainThread : public mozilla::Runnable
+{
+public:
+ DispatchTouchInputOnMainThread(const MultiTouchInput& aInput,
+ const ScrollableLayerGuid& aGuid,
+ const uint64_t& aInputBlockId,
+ nsEventStatus aApzResponse)
+ : mInput(aInput)
+ , mGuid(aGuid)
+ , mInputBlockId(aInputBlockId)
+ , mApzResponse(aApzResponse)
+ {}
+
+ NS_IMETHOD Run() override {
+ if (gFocusedWindow) {
+ gFocusedWindow->DispatchTouchEventForAPZ(mInput, mGuid, mInputBlockId, mApzResponse);
+ }
+ return NS_OK;
+ }
+
+private:
+ MultiTouchInput mInput;
+ ScrollableLayerGuid mGuid;
+ uint64_t mInputBlockId;
+ nsEventStatus mApzResponse;
+};
+
+void
+nsWindow::DispatchTouchInputViaAPZ(MultiTouchInput& aInput)
+{
+ APZThreadUtils::AssertOnControllerThread();
+
+ if (!mAPZC) {
+ // In general mAPZC should not be null, but during initial setup
+ // it might be, so we handle that case by ignoring touch input there.
+ return;
+ }
+
+ // First send it through the APZ code
+ mozilla::layers::ScrollableLayerGuid guid;
+ uint64_t inputBlockId;
+ nsEventStatus result = mAPZC->ReceiveInputEvent(aInput, &guid, &inputBlockId);
+ // If the APZ says to drop it, then we drop it
+ if (result == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+
+ // Can't use NS_NewRunnableMethod because it only takes up to one arg and
+ // we need more. Also we can't pass in |this| to the task because nsWindow
+ // refcounting is not threadsafe. Instead we just use the gFocusedWindow
+ // static ptr inside the task.
+ NS_DispatchToMainThread(new DispatchTouchInputOnMainThread(
+ aInput, guid, inputBlockId, result));
+}
+
+void
+nsWindow::DispatchTouchEventForAPZ(const MultiTouchInput& aInput,
+ const ScrollableLayerGuid& aGuid,
+ const uint64_t aInputBlockId,
+ nsEventStatus aApzResponse)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ UserActivity();
+
+ // Convert it to an event we can send to Gecko
+ WidgetTouchEvent event = aInput.ToWidgetTouchEvent(this);
+
+ // Dispatch the event into the gecko root process for "normal" flow.
+ // The event might get sent to a child process,
+ // but if it doesn't we need to notify the APZ of various things.
+ // All of that happens in ProcessUntransformedAPZEvent
+ ProcessUntransformedAPZEvent(&event, aGuid, aInputBlockId, aApzResponse);
+}
+
+class DispatchTouchInputOnControllerThread : public Runnable
+{
+public:
+ DispatchTouchInputOnControllerThread(const MultiTouchInput& aInput)
+ : mInput(aInput)
+ {}
+
+ NS_IMETHOD Run() override {
+ if (gFocusedWindow) {
+ gFocusedWindow->DispatchTouchInputViaAPZ(mInput);
+ }
+ return NS_OK;
+ }
+
+private:
+ MultiTouchInput mInput;
+};
+
+nsresult
+nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver)
+{
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (aPointerState == TOUCH_HOVER) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!mSynthesizedTouchInput) {
+ mSynthesizedTouchInput = MakeUnique<MultiTouchInput>();
+ }
+
+ // We should probably use a real timestamp here, but this is B2G and
+ // so this probably never even exercised any more.
+ uint32_t time = 0;
+ TimeStamp timestamp = TimeStamp::FromSystemTime(time);
+
+ MultiTouchInput inputToDispatch = UpdateSynthesizedTouchState(
+ mSynthesizedTouchInput.get(), time, timestamp, aPointerId, aPointerState,
+ aPoint, aPointerPressure, aPointerOrientation);
+
+ // Can't use NewRunnableMethod here because that will pass a const-ref
+ // argument to DispatchTouchInputViaAPZ whereas that function takes a
+ // non-const ref. At this callsite we don't care about the mutations that
+ // the function performs so this is fine. Also we can't pass |this| to the
+ // task because nsWindow refcounting is not threadsafe. Instead we just use
+ // the gFocusedWindow static ptr instead the task.
+ APZThreadUtils::RunOnControllerThread(
+ MakeAndAddRef<DispatchTouchInputOnControllerThread>(inputToDispatch));
+
+ return NS_OK;
+}
+
+nsresult
+nsWindow::Create(nsIWidget* aParent,
+ void* aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData)
+{
+ BaseCreate(aParent, aInitData);
+
+ nsCOMPtr<nsIScreen> screen;
+
+ uint32_t screenId = aParent ? ((nsWindow*)aParent)->mScreen->GetId() :
+ aInitData->mScreenId;
+
+ RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
+ screenManager->ScreenForId(screenId, getter_AddRefs(screen));
+
+ mScreen = static_cast<nsScreenGonk*>(screen.get());
+
+ mBounds = aRect;
+
+ mParent = (nsWindow *)aParent;
+ mVisible = false;
+
+ if (!aParent) {
+ mBounds = mScreen->GetRect();
+ }
+
+ mComposer2D = HwcComposer2D::GetInstance();
+
+ if (!IS_TOPLEVEL()) {
+ return NS_OK;
+ }
+
+ mScreen->RegisterWindow(this);
+
+ Resize(0, 0, mBounds.width, mBounds.height, false);
+
+ return NS_OK;
+}
+
+void
+nsWindow::Destroy()
+{
+ mOnDestroyCalled = true;
+ mScreen->UnregisterWindow(this);
+ if (this == gFocusedWindow) {
+ gFocusedWindow = nullptr;
+ }
+ nsBaseWidget::OnDestroy();
+}
+
+NS_IMETHODIMP
+nsWindow::Show(bool aState)
+{
+ if (mWindowType == eWindowType_invisible) {
+ return NS_OK;
+ }
+
+ if (mVisible == aState) {
+ return NS_OK;
+ }
+
+ mVisible = aState;
+ if (!IS_TOPLEVEL()) {
+ return mParent ? mParent->Show(aState) : NS_OK;
+ }
+
+ if (aState) {
+ BringToTop();
+ } else {
+ const nsTArray<nsWindow*>& windows =
+ mScreen->GetTopWindows();
+ for (unsigned int i = 0; i < windows.Length(); i++) {
+ nsWindow *win = windows[i];
+ if (!win->mVisible) {
+ continue;
+ }
+ win->BringToTop();
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool
+nsWindow::IsVisible() const
+{
+ return mVisible;
+}
+
+NS_IMETHODIMP
+nsWindow::Move(double aX,
+ double aY)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aWidth,
+ double aHeight,
+ bool aRepaint)
+{
+ return Resize(0, 0, aWidth, aHeight, aRepaint);
+}
+
+NS_IMETHODIMP
+nsWindow::Resize(double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint)
+{
+ mBounds = LayoutDeviceIntRect(NSToIntRound(aX), NSToIntRound(aY),
+ NSToIntRound(aWidth), NSToIntRound(aHeight));
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+
+ if (aRepaint) {
+ Invalidate(mBounds);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Enable(bool aState)
+{
+ return NS_OK;
+}
+
+bool
+nsWindow::IsEnabled() const
+{
+ return true;
+}
+
+NS_IMETHODIMP
+nsWindow::SetFocus(bool aRaise)
+{
+ if (aRaise) {
+ BringToTop();
+ }
+
+ if (!IS_TOPLEVEL() && mScreen->IsPrimaryScreen()) {
+ // We should only set focused window on non-toplevel primary window.
+ gFocusedWindow = this;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::ConfigureChildren(const nsTArray<nsIWidget::Configuration>&)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindow::Invalidate(const LayoutDeviceIntRect& aRect)
+{
+ nsWindow *top = mParent;
+ while (top && top->mParent) {
+ top = top->mParent;
+ }
+ const nsTArray<nsWindow*>& windows = mScreen->GetTopWindows();
+ if (top != windows[0] && this != windows[0]) {
+ return NS_OK;
+ }
+
+ gDrawRequest = true;
+ mozilla::NotifyEvent();
+ return NS_OK;
+}
+
+LayoutDeviceIntPoint
+nsWindow::WidgetToScreenOffset()
+{
+ LayoutDeviceIntPoint p(0, 0);
+ nsWindow *w = this;
+
+ while (w && w->mParent) {
+ p.x += w->mBounds.x;
+ p.y += w->mBounds.y;
+
+ w = w->mParent;
+ }
+
+ return p;
+}
+
+void*
+nsWindow::GetNativeData(uint32_t aDataType)
+{
+ switch (aDataType) {
+ case NS_NATIVE_WINDOW:
+ // Called before primary display's EGLSurface creation.
+ return mScreen->GetNativeWindow();
+ case NS_NATIVE_OPENGL_CONTEXT:
+ return mScreen->GetGLContext().take();
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ // There is only one IME context on Gonk.
+ return NS_ONLY_ONE_NATIVE_IME_CONTEXT;
+ }
+ }
+
+ return nullptr;
+}
+
+void
+nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal)
+{
+ switch (aDataType) {
+ case NS_NATIVE_OPENGL_CONTEXT:
+ GLContext* context = reinterpret_cast<GLContext*>(aVal);
+ if (!context) {
+ mScreen->SetEGLInfo(EGL_NO_DISPLAY,
+ EGL_NO_SURFACE,
+ nullptr);
+ return;
+ }
+ mScreen->SetEGLInfo(GLContextEGL::Cast(context)->GetEGLDisplay(),
+ GLContextEGL::Cast(context)->GetEGLSurface(),
+ context);
+ return;
+ }
+}
+
+NS_IMETHODIMP
+nsWindow::DispatchEvent(WidgetGUIEvent* aEvent, nsEventStatus& aStatus)
+{
+ if (mWidgetListener) {
+ aStatus = mWidgetListener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction)
+{
+ mInputContext = aContext;
+}
+
+NS_IMETHODIMP_(InputContext)
+nsWindow::GetInputContext()
+{
+ return mInputContext;
+}
+
+nsresult
+nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen*)
+{
+ if (mWindowType != eWindowType_toplevel) {
+ // Ignore fullscreen request for non-toplevel windows.
+ NS_WARNING("MakeFullScreen() on a dialog or child widget?");
+ nsBaseWidget::InfallibleMakeFullScreen(aFullScreen);
+ return NS_OK;
+ }
+
+ if (aFullScreen) {
+ // Fullscreen is "sticky" for toplevel widgets on gonk: we
+ // must paint the entire screen, and should only have one
+ // toplevel widget, so it doesn't make sense to ever "exit"
+ // fullscreen. If we do, we can leave parts of the screen
+ // unpainted.
+ nsIntRect virtualBounds;
+ mScreen->GetRect(&virtualBounds.x, &virtualBounds.y,
+ &virtualBounds.width, &virtualBounds.height);
+ Resize(virtualBounds.x, virtualBounds.y,
+ virtualBounds.width, virtualBounds.height,
+ /*repaint*/true);
+ }
+
+ if (nsIWidgetListener* listener = GetWidgetListener()) {
+ listener->FullscreenChanged(aFullScreen);
+ }
+ return NS_OK;
+}
+
+already_AddRefed<DrawTarget>
+nsWindow::StartRemoteDrawing()
+{
+ RefPtr<DrawTarget> buffer = mScreen->StartRemoteDrawing();
+ return buffer.forget();
+}
+
+void
+nsWindow::EndRemoteDrawing()
+{
+ mScreen->EndRemoteDrawing();
+}
+
+float
+nsWindow::GetDPI()
+{
+ return mScreen->GetDpi();
+}
+
+double
+nsWindow::GetDefaultScaleInternal()
+{
+ float dpi = GetDPI();
+ // The mean pixel density for mdpi devices is 160dpi, 240dpi for hdpi,
+ // and 320dpi for xhdpi, respectively.
+ // We'll take the mid-value between these three numbers as the boundary.
+ if (dpi < 200.0) {
+ return 1.0; // mdpi devices.
+ }
+ if (dpi < 280.0) {
+ return 1.5; // hdpi devices.
+ }
+ // xhdpi devices and beyond.
+ return floor(dpi / 150.0 + 0.5);
+}
+
+LayerManager *
+nsWindow::GetLayerManager(PLayerTransactionChild* aShadowManager,
+ LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence)
+{
+ if (mLayerManager) {
+ // This layer manager might be used for painting outside of DoDraw(), so we need
+ // to set the correct rotation on it.
+ if (ClientLayerManager* manager = mLayerManager->AsClientLayerManager()) {
+ uint32_t rotation = mScreen->EffectiveScreenRotation();
+ manager->SetDefaultTargetConfiguration(mozilla::layers::BufferMode::BUFFER_NONE,
+ ScreenRotation(rotation));
+ }
+ return mLayerManager;
+ }
+
+ const nsTArray<nsWindow*>& windows = mScreen->GetTopWindows();
+ nsWindow *topWindow = windows[0];
+
+ if (!topWindow) {
+ LOGW(" -- no topwindow\n");
+ return nullptr;
+ }
+
+ CreateCompositor();
+ if (RefPtr<CompositorBridgeParent> bridge = GetCompositorBridgeParent()) {
+ mScreen->SetCompositorBridgeParent(bridge);
+ if (mScreen->IsPrimaryScreen()) {
+ mComposer2D->SetCompositorBridgeParent(bridge);
+ }
+ }
+ MOZ_ASSERT(mLayerManager);
+ return mLayerManager;
+}
+
+void
+nsWindow::DestroyCompositor()
+{
+ if (RefPtr<CompositorBridgeParent> bridge = GetCompositorBridgeParent()) {
+ mScreen->SetCompositorBridgeParent(nullptr);
+ if (mScreen->IsPrimaryScreen()) {
+ // Unset CompositorBridgeParent
+ mComposer2D->SetCompositorBridgeParent(nullptr);
+ }
+ }
+ nsBaseWidget::DestroyCompositor();
+}
+
+void
+nsWindow::BringToTop()
+{
+ const nsTArray<nsWindow*>& windows = mScreen->GetTopWindows();
+ if (!windows.IsEmpty()) {
+ if (nsIWidgetListener* listener = windows[0]->GetWidgetListener()) {
+ listener->WindowDeactivated();
+ }
+ }
+
+ mScreen->BringToTop(this);
+
+ if (mWidgetListener) {
+ mWidgetListener->WindowActivated();
+ }
+
+ Invalidate(mBounds);
+}
+
+void
+nsWindow::UserActivity()
+{
+ if (!mIdleService) {
+ mIdleService = do_GetService("@mozilla.org/widget/idleservice;1");
+ }
+
+ if (mIdleService) {
+ mIdleService->ResetIdleTimeOut(0);
+ }
+}
+
+uint32_t
+nsWindow::GetGLFrameBufferFormat()
+{
+ if (mLayerManager &&
+ mLayerManager->GetBackendType() == mozilla::layers::LayersBackend::LAYERS_OPENGL) {
+ // We directly map the hardware fb on Gonk. The hardware fb
+ // has RGB format.
+ return LOCAL_GL_RGB;
+ }
+ return LOCAL_GL_NONE;
+}
+
+LayoutDeviceIntRect
+nsWindow::GetNaturalBounds()
+{
+ return mScreen->GetNaturalBounds();
+}
+
+nsScreenGonk*
+nsWindow::GetScreen()
+{
+ return mScreen;
+}
+
+bool
+nsWindow::NeedsPaint()
+{
+ if (!mLayerManager) {
+ return false;
+ }
+ return nsIWidget::NeedsPaint();
+}
+
+Composer2D*
+nsWindow::GetComposer2D()
+{
+ if (mScreen->GetDisplayType() == GonkDisplay::DISPLAY_VIRTUAL) {
+ return nullptr;
+ }
+
+ return mComposer2D;
+}
+
+CompositorBridgeParent*
+nsWindow::GetCompositorBridgeParent() const
+{
+ return mCompositorSession ? mCompositorSession->GetInProcessBridge() : nullptr;
+}
diff --git a/widget/gonk/nsWindow.h b/widget/gonk/nsWindow.h
new file mode 100644
index 0000000000..6106982f93
--- /dev/null
+++ b/widget/gonk/nsWindow.h
@@ -0,0 +1,154 @@
+/* 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.
+ */
+
+#ifndef nsWindow_h
+#define nsWindow_h
+
+#include "InputData.h"
+#include "mozilla/UniquePtr.h"
+#include "nsBaseWidget.h"
+#include "nsRegion.h"
+#include "nsIIdleServiceInternal.h"
+#include "Units.h"
+
+class ANativeWindowBuffer;
+
+namespace widget {
+struct InputContext;
+struct InputContextAction;
+}
+
+namespace mozilla {
+class HwcComposer2D;
+}
+
+class nsScreenGonk;
+
+class nsWindow : public nsBaseWidget
+{
+public:
+ nsWindow();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ static void DoDraw(void);
+ static nsEventStatus DispatchKeyInput(mozilla::WidgetKeyboardEvent& aEvent);
+ static void DispatchTouchInput(mozilla::MultiTouchInput& aInput);
+
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ virtual MOZ_MUST_USE nsresult Create(nsIWidget* aParent,
+ void* aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) override;
+ virtual void Destroy();
+
+ NS_IMETHOD Show(bool aState);
+ virtual bool IsVisible() const;
+ NS_IMETHOD Move(double aX,
+ double aY);
+ NS_IMETHOD Resize(double aWidth,
+ double aHeight,
+ bool aRepaint);
+ NS_IMETHOD Resize(double aX,
+ double aY,
+ double aWidth,
+ double aHeight,
+ bool aRepaint);
+ NS_IMETHOD Enable(bool aState);
+ virtual bool IsEnabled() const;
+ NS_IMETHOD SetFocus(bool aRaise = false);
+ NS_IMETHOD ConfigureChildren(const nsTArray<nsIWidget::Configuration>&);
+ NS_IMETHOD Invalidate(const LayoutDeviceIntRect& aRect);
+ virtual void* GetNativeData(uint32_t aDataType);
+ virtual void SetNativeData(uint32_t aDataType, uintptr_t aVal);
+ NS_IMETHOD SetTitle(const nsAString& aTitle)
+ {
+ return NS_OK;
+ }
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset();
+ void DispatchTouchInputViaAPZ(mozilla::MultiTouchInput& aInput);
+ void DispatchTouchEventForAPZ(const mozilla::MultiTouchInput& aInput,
+ const ScrollableLayerGuid& aGuid,
+ const uint64_t aInputBlockId,
+ nsEventStatus aApzResponse);
+ NS_IMETHOD DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus);
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+
+ virtual nsresult MakeFullScreen(
+ bool aFullScreen, nsIScreen* aTargetScreen = nullptr) override;
+
+ virtual already_AddRefed<mozilla::gfx::DrawTarget>
+ StartRemoteDrawing() override;
+ virtual void EndRemoteDrawing() override;
+
+ virtual float GetDPI();
+ virtual double GetDefaultScaleInternal();
+ virtual mozilla::layers::LayerManager*
+ GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT);
+ virtual void DestroyCompositor();
+
+ NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction);
+ NS_IMETHOD_(InputContext) GetInputContext();
+
+ virtual uint32_t GetGLFrameBufferFormat() override;
+
+ virtual LayoutDeviceIntRect GetNaturalBounds() override;
+ virtual bool NeedsPaint();
+
+ virtual Composer2D* GetComposer2D() override;
+
+ void ConfigureAPZControllerThread() override;
+
+ nsScreenGonk* GetScreen();
+
+protected:
+ nsWindow* mParent;
+ bool mVisible;
+ InputContext mInputContext;
+ nsCOMPtr<nsIIdleServiceInternal> mIdleService;
+
+ virtual ~nsWindow();
+
+ void BringToTop();
+
+ // Call this function when the users activity is the direct cause of an
+ // event (like a keypress or mouse click).
+ void UserActivity();
+
+ bool UseExternalCompositingSurface() const override {
+ return true;
+ }
+ CompositorBridgeParent* GetCompositorBridgeParent() const;
+
+private:
+ // This is used by SynthesizeNativeTouchPoint to maintain state between
+ // multiple synthesized points
+ mozilla::UniquePtr<mozilla::MultiTouchInput> mSynthesizedTouchInput;
+
+ RefPtr<nsScreenGonk> mScreen;
+
+ RefPtr<mozilla::HwcComposer2D> mComposer2D;
+};
+
+#endif /* nsWindow_h */