diff options
Diffstat (limited to 'widget/gonk')
137 files changed, 53584 insertions, 0 deletions
diff --git a/widget/gonk/GeckoTouchDispatcher.cpp b/widget/gonk/GeckoTouchDispatcher.cpp new file mode 100644 index 000000000..0b18c91a1 --- /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 000000000..3c7acd0e3 --- /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 000000000..7ddd39038 --- /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 000000000..61494713f --- /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 000000000..ced6422a5 --- /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 000000000..8bc1f1c9c --- /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 000000000..d5d4e7a0b --- /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 000000000..0fafb37cf --- /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 000000000..4d5149cd6 --- /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 000000000..8ebc43de8 --- /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 000000000..d34fcd8ac --- /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 000000000..6b4c7a1cc --- /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 000000000..56c1d1ec1 --- /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 000000000..a8f030f3c --- /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 000000000..876ff8e99 --- /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 000000000..9096404cf --- /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 000000000..c841ea878 --- /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 000000000..bbdcface8 --- /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 000000000..d6d4bc3b6 --- /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 000000000..558d9313e --- /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 000000000..1793b75e6 --- /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 000000000..05cb6a45f --- /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 000000000..0ef00a325 --- /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 000000000..c275179fc --- /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 000000000..9fdc20eca --- /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 000000000..398541c49 --- /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 000000000..a289acbb8 --- /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 000000000..c1cc84272 --- /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 000000000..96978a6e9 --- /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 000000000..197b85a47 --- /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 000000000..60bcdffc4 --- /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 000000000..5722b7fe3 --- /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 000000000..b08750c2f --- /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 000000000..746707885 --- /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(×tamp, &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 000000000..9125d8751 --- /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 000000000..917320592 --- /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 000000000..9da29bbeb --- /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 000000000..e4e658b21 --- /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 000000000..2208191e6 --- /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 000000000..3d958bfab --- /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 000000000..ce432356b --- /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 000000000..ba789559c --- /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 000000000..01a437dd4 --- /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 ⦥ + } + } + 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 000000000..0ab5863c9 --- /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 000000000..7adaa1998 --- /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 000000000..5453421f6 --- /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 000000000..3b673f0ad --- /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 000000000..de799322f --- /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 000000000..91af056bf --- /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 000000000..15a5176ec --- /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 000000000..3699569aa --- /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 000000000..5c790fdb8 --- /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 000000000..3f0fcb047 --- /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 000000000..66ef2850a --- /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 000000000..3aea445a0 --- /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 000000000..cce5fd4fe --- /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 000000000..cec0666ce --- /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, ¤tMetaState); + addKey(outEvents, deviceId, keyCode, currentMetaState, true, now); + addKey(outEvents, deviceId, keyCode, currentMetaState, false, now); + addMetaKeys(outEvents, deviceId, metaState, false, now, ¤tMetaState); + } +#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 000000000..c7a684105 --- /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 000000000..8af4b84e0 --- /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 000000000..8a6113447 --- /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 000000000..62bb53b7b --- /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 000000000..65921b25b --- /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 000000000..9f994597b --- /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 000000000..ff80a0a9f --- /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 000000000..eb48d9a1f --- /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 000000000..ba98db07c --- /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 000000000..8677476b1 --- /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 000000000..4926095ec --- /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 000000000..2f585cb4e --- /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 000000000..bb25f374c --- /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 000000000..24fbfb602 --- /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 000000000..31365a220 --- /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 000000000..8a2c695d6 --- /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 000000000..11a8bf7fc --- /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 000000000..fd077d438 --- /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 000000000..444ab3718 --- /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 000000000..79d61a536 --- /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 000000000..00e81b28d --- /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 000000000..9e63d1d01 --- /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 000000000..f4252c867 --- /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 000000000..29034cab5 --- /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 000000000..2ba14973f --- /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 000000000..40a4ecaa3 --- /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 000000000..f7ada46a5 --- /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 000000000..d539dd9a0 --- /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 000000000..7e4a2a9d8 --- /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 000000000..97a717444 --- /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 000000000..defdb0ae2 --- /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 000000000..81502f81e --- /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(×tamp, &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 000000000..df0c4599f --- /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 000000000..0c5cdfeb9 --- /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(×tamp, &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 000000000..01905427d --- /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 000000000..7df72bf68 --- /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 000000000..b2d6d3068 --- /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 000000000..1d7eb2702 --- /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 000000000..a97cfab42 --- /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 000000000..9e8e337f6 --- /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 000000000..936e11686 --- /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 000000000..60085706f --- /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 000000000..649d06bee --- /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 000000000..b1b4e06b5 --- /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 000000000..d3436756f --- /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(×tamp, &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 000000000..a1a22416a --- /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 000000000..9e4a424a9 --- /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 000000000..759bb7b23 --- /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 000000000..1ee37e4e2 --- /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 000000000..8f523af37 --- /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 000000000..3fc9fc16c --- /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 000000000..e198ad843 --- /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 000000000..5b1166b57 --- /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 000000000..0b2c2d166 --- /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 000000000..61b6780b8 --- /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 000000000..e38642009 --- /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 000000000..e63a7527d --- /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 000000000..cf34d6539 --- /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 000000000..e36788b41 --- /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 000000000..48644a22f --- /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 000000000..64cd6482d --- /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 000000000..14541c9b4 --- /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 000000000..c4c9f6578 --- /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 000000000..ce51e1ef2 --- /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 000000000..729ed6736 --- /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 000000000..8a93a0849 --- /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 000000000..fbcee601c --- /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 000000000..24e791b4b --- /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 000000000..046a99ea1 --- /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 000000000..a1eabe8e5 --- /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 000000000..cd2e0dbd5 --- /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 000000000..dc7588e5e --- /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 000000000..32520dce6 --- /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 000000000..120f257df --- /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 000000000..aa7dce823 --- /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 000000000..e359fd195 --- /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 000000000..33ef5edb8 --- /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 000000000..1c7525544 --- /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 000000000..e11b7f233 --- /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 000000000..6106982f9 --- /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 */ |