diff --git a/config/autoconf.mk.in b/config/autoconf.mk.in index 611d8b1bf32..a90c2cdc2dc 100644 --- a/config/autoconf.mk.in +++ b/config/autoconf.mk.in @@ -691,6 +691,7 @@ ANDROID_PLATFORM = @ANDROID_PLATFORM@ ANDROID_SDK = @ANDROID_SDK@ ANDROID_PLATFORM_TOOLS = @ANDROID_PLATFORM_TOOLS@ ANDROID_VERSION = @ANDROID_VERSION@ +ANDROID_SOURCE = @ANDROID_SOURCE@ STLPORT_SOURCES = @STLPORT_SOURCES@ ANDROID_PACKAGE_NAME = @ANDROID_PACKAGE_NAME@ diff --git a/configure.in b/configure.in index d1ee60438eb..170b47aa9d5 100644 --- a/configure.in +++ b/configure.in @@ -301,19 +301,25 @@ MOZ_ARG_WITH_STRING(gonk, location of gonk dir], gonkdir=$withval) +MOZ_ARG_WITH_STRING(gonk-toolchain-prefix, +[ --with-gonk-toolchain-prefix=DIR + prefix to gonk toolchain commands], + gonk_toolchain_prefix=$withval) + if test -n "$gonkdir" ; then kernel_name=`uname -s | tr "[[:upper:]]" "[[:lower:]]"` - gonk_toolchain="$gonkdir"/prebuilt/$kernel_name-x86/toolchain/arm-eabi-4.4.3 + android_source="$gonkdir" + ANDROID_SOURCE="$android_source" dnl set up compilers - AS="$gonk_toolchain"/bin/"$android_tool_prefix"-as - CC="$gonk_toolchain"/bin/"$android_tool_prefix"-gcc - CXX="$gonk_toolchain"/bin/"$android_tool_prefix"-g++ - CPP="$gonk_toolchain"/bin/"$android_tool_prefix"-cpp - LD="$gonk_toolchain"/bin/"$android_tool_prefix"-ld - AR="$gonk_toolchain"/bin/"$android_tool_prefix"-ar - RANLIB="$gonk_toolchain"/bin/"$android_tool_prefix"-ranlib - STRIP="$gonk_toolchain"/bin/"$android_tool_prefix"-strip + AS="$gonk_toolchain_prefix"as + CC="$gonk_toolchain_prefix"gcc + CXX="$gonk_toolchain_prefix"g++ + CPP="$gonk_toolchain_prefix"cpp + LD="$gonk_toolchain_prefix"ld + AR="$gonk_toolchain_prefix"ar + RANLIB="$gonk_toolchain_prefix"ranlib + STRIP="$gonk_toolchain_prefix"strip STLPORT_CPPFLAGS="-I$gonkdir/ndk/sources/cxx-stl/stlport/stlport/" STLPORT_LIBS="-lstlport" diff --git a/dom/power/PowerManager.cpp b/dom/power/PowerManager.cpp index 8d6ec778086..4bc47c279fe 100644 --- a/dom/power/PowerManager.cpp +++ b/dom/power/PowerManager.cpp @@ -90,6 +90,10 @@ PowerManager::Shutdown() nsresult PowerManager::CheckPermission() { + if (nsContentUtils::IsCallerChrome()) { + return NS_OK; + } + nsCOMPtr win = do_QueryReferent(mWindow); NS_ENSURE_STATE(win); nsCOMPtr doc = do_QueryInterface(win->GetExtantDocument()); diff --git a/dom/system/gonk/AudioManager.cpp b/dom/system/gonk/AudioManager.cpp index 78567a245bc..d901614edee 100644 --- a/dom/system/gonk/AudioManager.cpp +++ b/dom/system/gonk/AudioManager.cpp @@ -36,7 +36,7 @@ * ***** END LICENSE BLOCK ***** */ #include "AudioManager.h" -#include +#include "gonk/AudioSystem.h" using namespace mozilla::dom::gonk; using namespace android; @@ -120,15 +120,21 @@ AudioManager::SetPhoneState(PRInt32 aState) NS_IMETHODIMP AudioManager::SetForceForUse(PRInt32 aUsage, PRInt32 aForce) { + /* + * FIXME/bug XXXXXX: why do we need to disable forceUse in ICS? if (AudioSystem::setForceUse((AudioSystem::force_use)aUsage, (AudioSystem::forced_config)aForce)) { return NS_ERROR_FAILURE; } + */ return NS_OK; } NS_IMETHODIMP AudioManager::GetForceForUse(PRInt32 aUsage, PRInt32* aForce) { + /* + * FIXME/bug XXXXXX: why do we need to disable forceUse in ICS? *aForce = AudioSystem::getForceUse((AudioSystem::force_use)aUsage); + */ return NS_OK; } diff --git a/dom/system/gonk/Makefile.in b/dom/system/gonk/Makefile.in index 1c707262114..6589c5dea0f 100644 --- a/dom/system/gonk/Makefile.in +++ b/dom/system/gonk/Makefile.in @@ -23,13 +23,6 @@ CPPSRCS = \ SystemWorkerManager.cpp \ $(NULL) -ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) -CPPSRCS += \ - AudioManager.cpp \ - GonkGPSGeolocationProvider.cpp \ - $(NULL) -endif - XPIDLSRCS = \ nsIAudioManager.idl \ nsIRadioInterfaceLayer.idl \ @@ -44,6 +37,15 @@ LOCAL_INCLUDES = \ -I$(topsrcdir)/content/events/src \ $(NULL) +ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) +CPPSRCS += \ + AudioManager.cpp \ + GonkGPSGeolocationProvider.cpp \ + $(NULL) +# for our local copy of AudioSystem.h +LOCAL_INCLUDES += -I$(topsrcdir)/media/libsydneyaudio/src +endif + EXTRA_COMPONENTS = \ RadioInterfaceLayer.manifest \ RadioInterfaceLayer.js \ diff --git a/js/src/configure.in b/js/src/configure.in index 48d5ac765af..b5583b4e8b3 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -292,19 +292,23 @@ MOZ_ARG_WITH_STRING(gonk, location of gonk dir], gonkdir=$withval) +MOZ_ARG_WITH_STRING(gonk-toolchain-prefix, +[ --with-gonk-toolchain-prefix=DIR + prefix to gonk toolchain commands], + gonk_toolchain_prefix=$withval) + if test -n "$gonkdir" ; then kernel_name=`uname -s | tr "[[:upper:]]" "[[:lower:]]"` - gonk_toolchain="$gonkdir"/prebuilt/$kernel_name-x86/toolchain/arm-eabi-4.4.3 dnl set up compilers - AS="$gonk_toolchain"/bin/"$android_tool_prefix"-as - CC="$gonk_toolchain"/bin/"$android_tool_prefix"-gcc - CXX="$gonk_toolchain"/bin/"$android_tool_prefix"-g++ - CPP="$gonk_toolchain"/bin/"$android_tool_prefix"-cpp - LD="$gonk_toolchain"/bin/"$android_tool_prefix"-ld - AR="$gonk_toolchain"/bin/"$android_tool_prefix"-ar - RANLIB="$gonk_toolchain"/bin/"$android_tool_prefix"-ranlib - STRIP="$gonk_toolchain"/bin/"$android_tool_prefix"-strip + AS="$gonk_toolchain_prefix"as + CC="$gonk_toolchain_prefix"gcc + CXX="$gonk_toolchain_prefix"g++ + CPP="$gonk_toolchain_prefix"cpp + LD="$gonk_toolchain_prefix"ld + AR="$gonk_toolchain_prefix"ar + RANLIB="$gonk_toolchain_prefix"ranlib + STRIP="$gonk_toolchain_prefix"strip STLPORT_CPPFLAGS="-I$gonkdir/external/stlport/stlport" STLPORT_LIBS="-lstlport" diff --git a/media/libsydneyaudio/src/gonk/AudioSystem.h b/media/libsydneyaudio/src/gonk/AudioSystem.h new file mode 100644 index 00000000000..dc6bc6c85eb --- /dev/null +++ b/media/libsydneyaudio/src/gonk/AudioSystem.h @@ -0,0 +1,514 @@ +/* + * 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 ANDROID_AUDIOSYSTEM_H_ +#define ANDROID_AUDIOSYSTEM_H_ + +#include +#include +#include "IAudioFlinger.h" + +namespace android { + +typedef void (*audio_error_callback)(status_t err); +typedef int audio_io_handle_t; + +class IAudioPolicyService; +class String8; + +class AudioSystem +{ +public: + + enum stream_type { + DEFAULT =-1, + VOICE_CALL = 0, + SYSTEM = 1, + RING = 2, + MUSIC = 3, + ALARM = 4, + NOTIFICATION = 5, + BLUETOOTH_SCO = 6, + ENFORCED_AUDIBLE = 7, // Sounds that cannot be muted by user and must be routed to speaker + DTMF = 8, + TTS = 9, + NUM_STREAM_TYPES + }; + + // Audio sub formats (see AudioSystem::audio_format). + enum pcm_sub_format { + PCM_SUB_16_BIT = 0x1, // must be 1 for backward compatibility + PCM_SUB_8_BIT = 0x2, // must be 2 for backward compatibility + }; + + // MP3 sub format field definition : can use 11 LSBs in the same way as MP3 frame header to specify + // bit rate, stereo mode, version... + enum mp3_sub_format { + //TODO + }; + + // AMR NB/WB sub format field definition: specify frame block interleaving, bandwidth efficient or octet aligned, + // encoding mode for recording... + enum amr_sub_format { + //TODO + }; + + // AAC sub format field definition: specify profile or bitrate for recording... + enum aac_sub_format { + //TODO + }; + + // VORBIS sub format field definition: specify quality for recording... + enum vorbis_sub_format { + //TODO + }; + + // Audio format consists in a main format field (upper 8 bits) and a sub format field (lower 24 bits). + // The main format indicates the main codec type. The sub format field indicates options and parameters + // for each format. The sub format is mainly used for record to indicate for instance the requested bitrate + // or profile. It can also be used for certain formats to give informations not present in the encoded + // audio stream (e.g. octet alignement for AMR). + enum audio_format { + INVALID_FORMAT = -1, + FORMAT_DEFAULT = 0, + PCM = 0x00000000, // must be 0 for backward compatibility + MP3 = 0x01000000, + AMR_NB = 0x02000000, + AMR_WB = 0x03000000, + AAC = 0x04000000, + HE_AAC_V1 = 0x05000000, + HE_AAC_V2 = 0x06000000, + VORBIS = 0x07000000, + EVRC = 0x08000000, + QCELP = 0x09000000, + VOIP_PCM_INPUT = 0x0A000000, + MAIN_FORMAT_MASK = 0xFF000000, + SUB_FORMAT_MASK = 0x00FFFFFF, + // Aliases + PCM_16_BIT = (PCM|PCM_SUB_16_BIT), + PCM_8_BIT = (PCM|PCM_SUB_8_BIT) + }; + + + // Channel mask definitions must be kept in sync with JAVA values in /media/java/android/media/AudioFormat.java + enum audio_channels { + // output channels + CHANNEL_OUT_FRONT_LEFT = 0x4, + CHANNEL_OUT_FRONT_RIGHT = 0x8, + CHANNEL_OUT_FRONT_CENTER = 0x10, + CHANNEL_OUT_LOW_FREQUENCY = 0x20, + CHANNEL_OUT_BACK_LEFT = 0x40, + CHANNEL_OUT_BACK_RIGHT = 0x80, + CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x100, + CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x200, + CHANNEL_OUT_BACK_CENTER = 0x400, + CHANNEL_OUT_MONO = CHANNEL_OUT_FRONT_LEFT, + CHANNEL_OUT_STEREO = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT), + CHANNEL_OUT_QUAD = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | + CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT), + CHANNEL_OUT_SURROUND = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | + CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_BACK_CENTER), + CHANNEL_OUT_5POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | + CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT), + CHANNEL_OUT_7POINT1 = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | + CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT | + CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER), + CHANNEL_OUT_ALL = (CHANNEL_OUT_FRONT_LEFT | CHANNEL_OUT_FRONT_RIGHT | + CHANNEL_OUT_FRONT_CENTER | CHANNEL_OUT_LOW_FREQUENCY | CHANNEL_OUT_BACK_LEFT | CHANNEL_OUT_BACK_RIGHT | + CHANNEL_OUT_FRONT_LEFT_OF_CENTER | CHANNEL_OUT_FRONT_RIGHT_OF_CENTER | CHANNEL_OUT_BACK_CENTER), + + // input channels + CHANNEL_IN_LEFT = 0x4, + CHANNEL_IN_RIGHT = 0x8, + CHANNEL_IN_FRONT = 0x10, + CHANNEL_IN_BACK = 0x20, + CHANNEL_IN_LEFT_PROCESSED = 0x40, + CHANNEL_IN_RIGHT_PROCESSED = 0x80, + CHANNEL_IN_FRONT_PROCESSED = 0x100, + CHANNEL_IN_BACK_PROCESSED = 0x200, + CHANNEL_IN_PRESSURE = 0x400, + CHANNEL_IN_X_AXIS = 0x800, + CHANNEL_IN_Y_AXIS = 0x1000, + CHANNEL_IN_Z_AXIS = 0x2000, + CHANNEL_IN_VOICE_UPLINK = 0x4000, + CHANNEL_IN_VOICE_DNLINK = 0x8000, + CHANNEL_IN_MONO = CHANNEL_IN_FRONT, + CHANNEL_IN_STEREO = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT), + CHANNEL_IN_ALL = (CHANNEL_IN_LEFT | CHANNEL_IN_RIGHT | CHANNEL_IN_FRONT | CHANNEL_IN_BACK| + CHANNEL_IN_LEFT_PROCESSED | CHANNEL_IN_RIGHT_PROCESSED | CHANNEL_IN_FRONT_PROCESSED | CHANNEL_IN_BACK_PROCESSED| + CHANNEL_IN_PRESSURE | CHANNEL_IN_X_AXIS | CHANNEL_IN_Y_AXIS | CHANNEL_IN_Z_AXIS | + CHANNEL_IN_VOICE_UPLINK | CHANNEL_IN_VOICE_DNLINK) + }; + + enum audio_mode { + MODE_INVALID = -2, + MODE_CURRENT = -1, + MODE_NORMAL = 0, + MODE_RINGTONE, + MODE_IN_CALL, + MODE_IN_COMMUNICATION, + NUM_MODES // not a valid entry, denotes end-of-list + }; + + enum audio_in_acoustics { + AGC_ENABLE = 0x0001, + AGC_DISABLE = 0, + NS_ENABLE = 0x0002, + NS_DISABLE = 0, + TX_IIR_ENABLE = 0x0004, + TX_DISABLE = 0 + }; + + // special audio session values + enum audio_sessions { + SESSION_OUTPUT_STAGE = -1, // session for effects attached to a particular output stream + // (value must be less than 0) + SESSION_OUTPUT_MIX = 0, // session for effects applied to output mix. These effects can + // be moved by audio policy manager to another output stream + // (value must be 0) + }; + + /* These are static methods to control the system-wide AudioFlinger + * only privileged processes can have access to them + */ + + // mute/unmute microphone + static status_t muteMicrophone(bool state); + static status_t isMicrophoneMuted(bool *state); + + // set/get master volume + static status_t setMasterVolume(float value); + static status_t getMasterVolume(float* volume); + // mute/unmute audio outputs + static status_t setMasterMute(bool mute); + static status_t getMasterMute(bool* mute); + + // set/get stream volume on specified output + static status_t setStreamVolume(int stream, float value, int output); + static status_t getStreamVolume(int stream, float* volume, int output); + + // mute/unmute stream + static status_t setStreamMute(int stream, bool mute); + static status_t getStreamMute(int stream, bool* mute); + + // set audio mode in audio hardware (see AudioSystem::audio_mode) + static status_t setMode(int mode); + + // returns true in *state if tracks are active on the specified stream + static status_t isStreamActive(int stream, bool *state); + + // set/get audio hardware parameters. The function accepts a list of parameters + // key value pairs in the form: key1=value1;key2=value2;... + // Some keys are reserved for standard parameters (See AudioParameter class). + static status_t setParameters(audio_io_handle_t ioHandle, const String8& keyValuePairs); + static String8 getParameters(audio_io_handle_t ioHandle, const String8& keys); + + static void setErrorCallback(audio_error_callback cb); + + // helper function to obtain AudioFlinger service handle + static const sp& get_audio_flinger(); + + static float linearToLog(int volume); + static int logToLinear(float volume); + + static status_t getOutputSamplingRate(int* samplingRate, int stream = DEFAULT); + static status_t getOutputFrameCount(int* frameCount, int stream = DEFAULT); + static status_t getOutputLatency(uint32_t* latency, int stream = DEFAULT); + + static bool routedToA2dpOutput(int streamType); + + static status_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount, + size_t* buffSize); + + static status_t setVoiceVolume(float volume); + + // return the number of audio frames written by AudioFlinger to audio HAL and + // audio dsp to DAC since the output on which the specificed stream is playing + // has exited standby. + // returned status (from utils/Errors.h) can be: + // - NO_ERROR: successful operation, halFrames and dspFrames point to valid data + // - INVALID_OPERATION: Not supported on current hardware platform + // - BAD_VALUE: invalid parameter + // NOTE: this feature is not supported on all hardware platforms and it is + // necessary to check returned status before using the returned values. + static status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int stream = DEFAULT); + + static unsigned int getInputFramesLost(audio_io_handle_t ioHandle); + + static int newAudioSessionId(); + // + // AudioPolicyService interface + // + + enum audio_devices { + // output devices + DEVICE_OUT_EARPIECE = 0x1, + DEVICE_OUT_SPEAKER = 0x2, + DEVICE_OUT_WIRED_HEADSET = 0x4, + DEVICE_OUT_WIRED_HEADPHONE = 0x8, + DEVICE_OUT_BLUETOOTH_SCO = 0x10, + DEVICE_OUT_BLUETOOTH_SCO_HEADSET = 0x20, + DEVICE_OUT_BLUETOOTH_SCO_CARKIT = 0x40, + DEVICE_OUT_BLUETOOTH_A2DP = 0x80, + DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100, + DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200, + DEVICE_OUT_AUX_DIGITAL = 0x400, + DEVICE_OUT_DEFAULT = 0x8000, + DEVICE_OUT_ALL = (DEVICE_OUT_EARPIECE | DEVICE_OUT_SPEAKER | DEVICE_OUT_WIRED_HEADSET | + DEVICE_OUT_WIRED_HEADPHONE | DEVICE_OUT_BLUETOOTH_SCO | DEVICE_OUT_BLUETOOTH_SCO_HEADSET | + DEVICE_OUT_BLUETOOTH_SCO_CARKIT | DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | + DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER | DEVICE_OUT_AUX_DIGITAL | DEVICE_OUT_DEFAULT), + DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP | DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES | + DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER), + + // input devices + DEVICE_IN_COMMUNICATION = 0x10000, + DEVICE_IN_AMBIENT = 0x20000, + DEVICE_IN_BUILTIN_MIC = 0x40000, + DEVICE_IN_BLUETOOTH_SCO_HEADSET = 0x80000, + DEVICE_IN_WIRED_HEADSET = 0x100000, + DEVICE_IN_AUX_DIGITAL = 0x200000, + DEVICE_IN_VOICE_CALL = 0x400000, + DEVICE_IN_BACK_MIC = 0x800000, + DEVICE_IN_DEFAULT = 0x80000000, + + DEVICE_IN_ALL = (DEVICE_IN_COMMUNICATION | DEVICE_IN_AMBIENT | DEVICE_IN_BUILTIN_MIC | + DEVICE_IN_BLUETOOTH_SCO_HEADSET | DEVICE_IN_WIRED_HEADSET | DEVICE_IN_AUX_DIGITAL | + DEVICE_IN_VOICE_CALL | DEVICE_IN_BACK_MIC | DEVICE_IN_DEFAULT) + }; + + // device connection states used for setDeviceConnectionState() + enum device_connection_state { + DEVICE_STATE_UNAVAILABLE, + DEVICE_STATE_AVAILABLE, + NUM_DEVICE_STATES + }; + + // request to open a direct output with getOutput() (by opposition to sharing an output with other AudioTracks) + enum output_flags { + OUTPUT_FLAG_INDIRECT = 0x0, + OUTPUT_FLAG_DIRECT = 0x1 + }; + + // device categories used for setForceUse() + enum forced_config { + FORCE_NONE, + FORCE_SPEAKER, + FORCE_HEADPHONES, + FORCE_BT_SCO, + FORCE_BT_A2DP, + FORCE_WIRED_ACCESSORY, + FORCE_BT_CAR_DOCK, + FORCE_BT_DESK_DOCK, + NUM_FORCE_CONFIG, + FORCE_DEFAULT = FORCE_NONE + }; + + // usages used for setForceUse() + enum force_use { + FOR_COMMUNICATION, + FOR_MEDIA, + FOR_RECORD, + FOR_DOCK, + NUM_FORCE_USE + }; + + // types of io configuration change events received with ioConfigChanged() + enum io_config_event { + OUTPUT_OPENED, + OUTPUT_CLOSED, + OUTPUT_CONFIG_CHANGED, + INPUT_OPENED, + INPUT_CLOSED, + INPUT_CONFIG_CHANGED, + STREAM_CONFIG_CHANGED, + NUM_CONFIG_EVENTS + }; + + // audio output descritor used to cache output configurations in client process to avoid frequent calls + // through IAudioFlinger + class OutputDescriptor { + public: + OutputDescriptor() + : samplingRate(0), format(0), channels(0), frameCount(0), latency(0) {} + + uint32_t samplingRate; + int32_t format; + int32_t channels; + size_t frameCount; + uint32_t latency; + }; + + // + // IAudioPolicyService interface (see AudioPolicyInterface for method descriptions) + // + static status_t setDeviceConnectionState(audio_devices device, device_connection_state state, const char *device_address); + static device_connection_state getDeviceConnectionState(audio_devices device, const char *device_address); + static status_t setPhoneState(int state); + static status_t setRingerMode(uint32_t mode, uint32_t mask); + static status_t setForceUse(force_use usage, forced_config config); + static forced_config getForceUse(force_use usage); + static audio_io_handle_t getOutput(stream_type stream, + uint32_t samplingRate = 0, + uint32_t format = FORMAT_DEFAULT, + uint32_t channels = CHANNEL_OUT_STEREO, + output_flags flags = OUTPUT_FLAG_INDIRECT); + static status_t startOutput(audio_io_handle_t output, + AudioSystem::stream_type stream, + int session = 0); + static status_t stopOutput(audio_io_handle_t output, + AudioSystem::stream_type stream, + int session = 0); + static void releaseOutput(audio_io_handle_t output); + static audio_io_handle_t getInput(int inputSource, + uint32_t samplingRate = 0, + uint32_t format = FORMAT_DEFAULT, + uint32_t channels = CHANNEL_IN_MONO, + audio_in_acoustics acoustics = (audio_in_acoustics)0); + static status_t startInput(audio_io_handle_t input); + static status_t stopInput(audio_io_handle_t input); + static void releaseInput(audio_io_handle_t input); + static status_t initStreamVolume(stream_type stream, + int indexMin, + int indexMax); + static status_t setStreamVolumeIndex(stream_type stream, int index); + static status_t getStreamVolumeIndex(stream_type stream, int *index); + + static uint32_t getStrategyForStream(stream_type stream); + + static audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc); + static status_t registerEffect(effect_descriptor_t *desc, + audio_io_handle_t output, + uint32_t strategy, + int session, + int id); + static status_t unregisterEffect(int id); + + static const sp& get_audio_policy_service(); + + // ---------------------------------------------------------------------------- + + static uint32_t popCount(uint32_t u); + static bool isOutputDevice(audio_devices device); + static bool isInputDevice(audio_devices device); + static bool isA2dpDevice(audio_devices device); + static bool isBluetoothScoDevice(audio_devices device); + static bool isSeperatedStream(stream_type stream); + static bool isLowVisibility(stream_type stream); + static bool isOutputChannel(uint32_t channel); + static bool isInputChannel(uint32_t channel); + static bool isValidFormat(uint32_t format); + static bool isLinearPCM(uint32_t format); + static bool isModeInCall(); + +private: + + class AudioFlingerClient: public IBinder::DeathRecipient, public BnAudioFlingerClient + { + public: + AudioFlingerClient() { + } + + // DeathRecipient + virtual void binderDied(const wp& who); + + // IAudioFlingerClient + + // indicate a change in the configuration of an output or input: keeps the cached + // values for output/input parameters upto date in client process + virtual void ioConfigChanged(int event, int ioHandle, void *param2); + }; + + class AudioPolicyServiceClient: public IBinder::DeathRecipient + { + public: + AudioPolicyServiceClient() { + } + + // DeathRecipient + virtual void binderDied(const wp& who); + }; + + static sp gAudioFlingerClient; + static sp gAudioPolicyServiceClient; + friend class AudioFlingerClient; + friend class AudioPolicyServiceClient; + + static Mutex gLock; + static sp gAudioFlinger; + static audio_error_callback gAudioErrorCallback; + + static size_t gInBuffSize; + // previous parameters for recording buffer size queries + static uint32_t gPrevInSamplingRate; + static int gPrevInFormat; + static int gPrevInChannelCount; + + static sp gAudioPolicyService; + + // mapping between stream types and outputs + static DefaultKeyedVector gStreamOutputMap; + // list of output descritor containing cached parameters (sampling rate, framecount, channel count...) + static DefaultKeyedVector gOutputs; +}; + +class AudioParameter { + +public: + AudioParameter() {} + AudioParameter(const String8& keyValuePairs); + virtual ~AudioParameter(); + + // reserved parameter keys for changing standard parameters with setParameters() function. + // Using these keys is mandatory for AudioFlinger to properly monitor audio output/input + // configuration changes and act accordingly. + // keyRouting: to change audio routing, value is an int in AudioSystem::audio_devices + // keySamplingRate: to change sampling rate routing, value is an int + // keyFormat: to change audio format, value is an int in AudioSystem::audio_format + // keyChannels: to change audio channel configuration, value is an int in AudioSystem::audio_channels + // keyFrameCount: to change audio output frame count, value is an int + // keyInputSource: to change audio input source, value is an int in audio_source + // (defined in media/mediarecorder.h) + static const char *keyRouting; + static const char *keySamplingRate; + static const char *keyFormat; + static const char *keyChannels; + static const char *keyFrameCount; + static const char *keyInputSource; + + String8 toString(); + + status_t add(const String8& key, const String8& value); + status_t addInt(const String8& key, const int value); + status_t addFloat(const String8& key, const float value); + + status_t remove(const String8& key); + + status_t get(const String8& key, String8& value); + status_t getInt(const String8& key, int& value); + status_t getFloat(const String8& key, float& value); + status_t getAt(size_t index, String8& key, String8& value); + + size_t size() { return mParameters.size(); } + +private: + String8 mKeyValuePairs; + KeyedVector mParameters; +}; + +}; // namespace android + +#endif /*ANDROID_AUDIOSYSTEM_H_*/ diff --git a/media/libsydneyaudio/src/gonk/AudioTrack.h b/media/libsydneyaudio/src/gonk/AudioTrack.h new file mode 100644 index 00000000000..ae561988b3c --- /dev/null +++ b/media/libsydneyaudio/src/gonk/AudioTrack.h @@ -0,0 +1,488 @@ +/* + * 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_AUDIOTRACK_H +#define ANDROID_AUDIOTRACK_H + +#include +#include + +#include "IAudioFlinger.h" +#include "IAudioTrack.h" +#include "AudioSystem.h" + +#include +#include +#include +#include +#include + + +namespace android { + +// ---------------------------------------------------------------------------- + +class audio_track_cblk_t; + +// ---------------------------------------------------------------------------- + +class AudioTrack +{ +public: + enum channel_index { + MONO = 0, + LEFT = 0, + RIGHT = 1 + }; + + /* Events used by AudioTrack callback function (audio_track_cblk_t). + */ + enum event_type { + EVENT_MORE_DATA = 0, // Request to write more data to PCM buffer. + EVENT_UNDERRUN = 1, // PCM buffer underrun occured. + EVENT_LOOP_END = 2, // Sample loop end was reached; playback restarted from loop start if loop count was not 0. + EVENT_MARKER = 3, // Playback head is at the specified marker position (See setMarkerPosition()). + EVENT_NEW_POS = 4, // Playback head is at a new position (See setPositionUpdatePeriod()). + EVENT_BUFFER_END = 5 // Playback head is at the end of the buffer. + }; + + /* Create Buffer on the stack and pass it to obtainBuffer() + * and releaseBuffer(). + */ + + class Buffer + { + public: + enum { + MUTE = 0x00000001 + }; + uint32_t flags; + int channelCount; + int format; + size_t frameCount; + size_t size; + union { + void* raw; + short* i16; + int8_t* i8; + }; + }; + + + /* As a convenience, if a callback is supplied, a handler thread + * is automatically created with the appropriate priority. This thread + * invokes the callback when a new buffer becomes availlable or an underrun condition occurs. + * Parameters: + * + * event: type of event notified (see enum AudioTrack::event_type). + * user: Pointer to context for use by the callback receiver. + * info: Pointer to optional parameter according to event type: + * - EVENT_MORE_DATA: pointer to AudioTrack::Buffer struct. The callback must not write + * more bytes than indicated by 'size' field and update 'size' if less bytes are + * written. + * - EVENT_UNDERRUN: unused. + * - EVENT_LOOP_END: pointer to an int indicating the number of loops remaining. + * - EVENT_MARKER: pointer to an uin32_t containing the marker position in frames. + * - EVENT_NEW_POS: pointer to an uin32_t containing the new position in frames. + * - EVENT_BUFFER_END: unused. + */ + + typedef void (*callback_t)(int event, void* user, void *info); + + /* Returns the minimum frame count required for the successful creation of + * an AudioTrack object. + * Returned status (from utils/Errors.h) can be: + * - NO_ERROR: successful operation + * - NO_INIT: audio server or audio hardware not initialized + */ + + static status_t getMinFrameCount(int* frameCount, + int streamType =-1, + uint32_t sampleRate = 0); + + /* Constructs an uninitialized AudioTrack. No connection with + * AudioFlinger takes place. + */ + AudioTrack(); + + /* Creates an audio track and registers it with AudioFlinger. + * Once created, the track needs to be started before it can be used. + * Unspecified values are set to the audio hardware's current + * values. + * + * Parameters: + * + * streamType: Select the type of audio stream this track is attached to + * (e.g. AudioSystem::MUSIC). + * sampleRate: Track sampling rate in Hz. + * format: Audio format (e.g AudioSystem::PCM_16_BIT for signed + * 16 bits per sample). + * channels: Channel mask: see AudioSystem::audio_channels. + * frameCount: Total size of track PCM buffer in frames. This defines the + * latency of the track. + * flags: Reserved for future use. + * cbf: Callback function. If not null, this function is called periodically + * to request new PCM data. + * notificationFrames: The callback function is called each time notificationFrames PCM + * frames have been comsumed from track input buffer. + * user Context for use by the callback receiver. + */ + + AudioTrack( int streamType, + uint32_t sampleRate = 0, + int format = 0, + int channels = 0, + int frameCount = 0, + uint32_t flags = 0, + callback_t cbf = 0, + void* user = 0, + int notificationFrames = 0, + int sessionId = 0); + + /* Creates an audio track and registers it with AudioFlinger. With this constructor, + * The PCM data to be rendered by AudioTrack is passed in a shared memory buffer + * identified by the argument sharedBuffer. This prototype is for static buffer playback. + * PCM data must be present into memory before the AudioTrack is started. + * The Write() and Flush() methods are not supported in this case. + * It is recommented to pass a callback function to be notified of playback end by an + * EVENT_UNDERRUN event. + */ + + AudioTrack( int streamType, + uint32_t sampleRate = 0, + int format = 0, + int channels = 0, + const sp& sharedBuffer = 0, + uint32_t flags = 0, + callback_t cbf = 0, + void* user = 0, + int notificationFrames = 0, + int sessionId = 0); + + /* Terminates the AudioTrack and unregisters it from AudioFlinger. + * Also destroys all resources assotiated with the AudioTrack. + */ + ~AudioTrack(); + + + /* Initialize an uninitialized AudioTrack. + * Returned status (from utils/Errors.h) can be: + * - NO_ERROR: successful intialization + * - INVALID_OPERATION: AudioTrack is already intitialized + * - BAD_VALUE: invalid parameter (channels, format, sampleRate...) + * - NO_INIT: audio server or audio hardware not initialized + * */ + status_t set(int streamType =-1, + uint32_t sampleRate = 0, + int format = 0, + int channels = 0, + int frameCount = 0, + uint32_t flags = 0, + callback_t cbf = 0, + void* user = 0, + int notificationFrames = 0, + const sp& sharedBuffer = 0, + bool threadCanCallJava = false, + int sessionId = 0); + + + /* Result of constructing the AudioTrack. This must be checked + * before using any AudioTrack API (except for set()), using + * an uninitialized AudioTrack produces undefined results. + * See set() method above for possible return codes. + */ + status_t initCheck() const; + + /* Returns this track's latency in milliseconds. + * This includes the latency due to AudioTrack buffer size, AudioMixer (if any) + * and audio hardware driver. + */ + uint32_t latency() const; + + /* getters, see constructor */ + + int streamType() const; + int format() const; + int channelCount() const; + uint32_t frameCount() const; + int frameSize() const; + sp& sharedBuffer(); + + + /* After it's created the track is not active. Call start() to + * make it active. If set, the callback will start being called. + */ + void start(); + + /* Stop a track. If set, the callback will cease being called and + * obtainBuffer returns STOPPED. Note that obtainBuffer() still works + * and will fill up buffers until the pool is exhausted. + */ + void stop(); + bool stopped() const; + + /* flush a stopped track. All pending buffers are discarded. + * This function has no effect if the track is not stoped. + */ + void flush(); + + /* Pause a track. If set, the callback will cease being called and + * obtainBuffer returns STOPPED. Note that obtainBuffer() still works + * and will fill up buffers until the pool is exhausted. + */ + void pause(); + + /* mute or unmutes this track. + * While mutted, the callback, if set, is still called. + */ + void mute(bool); + bool muted() const; + + + /* set volume for this track, mostly used for games' sound effects + * left and right volumes. Levels must be <= 1.0. + */ + status_t setVolume(float left, float right); + void getVolume(float* left, float* right); + + /* set the send level for this track. An auxiliary effect should be attached + * to the track with attachEffect(). Level must be <= 1.0. + */ + status_t setAuxEffectSendLevel(float level); + void getAuxEffectSendLevel(float* level); + + /* set sample rate for this track, mostly used for games' sound effects + */ + status_t setSampleRate(int sampleRate); + uint32_t getSampleRate(); + + /* Enables looping and sets the start and end points of looping. + * + * Parameters: + * + * loopStart: loop start expressed as the number of PCM frames played since AudioTrack start. + * loopEnd: loop end expressed as the number of PCM frames played since AudioTrack start. + * loopCount: number of loops to execute. Calling setLoop() with loopCount == 0 cancels any pending or + * active loop. loopCount = -1 means infinite looping. + * + * For proper operation the following condition must be respected: + * (loopEnd-loopStart) <= framecount() + */ + status_t setLoop(uint32_t loopStart, uint32_t loopEnd, int loopCount); + status_t getLoop(uint32_t *loopStart, uint32_t *loopEnd, int *loopCount); + + + /* Sets marker position. When playback reaches the number of frames specified, a callback with event + * type EVENT_MARKER is called. Calling setMarkerPosition with marker == 0 cancels marker notification + * callback. + * If the AudioTrack has been opened with no callback function associated, the operation will fail. + * + * Parameters: + * + * marker: marker position expressed in frames. + * + * Returned status (from utils/Errors.h) can be: + * - NO_ERROR: successful operation + * - INVALID_OPERATION: the AudioTrack has no callback installed. + */ + status_t setMarkerPosition(uint32_t marker); + status_t getMarkerPosition(uint32_t *marker); + + + /* Sets position update period. Every time the number of frames specified has been played, + * a callback with event type EVENT_NEW_POS is called. + * Calling setPositionUpdatePeriod with updatePeriod == 0 cancels new position notification + * callback. + * If the AudioTrack has been opened with no callback function associated, the operation will fail. + * + * Parameters: + * + * updatePeriod: position update notification period expressed in frames. + * + * Returned status (from utils/Errors.h) can be: + * - NO_ERROR: successful operation + * - INVALID_OPERATION: the AudioTrack has no callback installed. + */ + status_t setPositionUpdatePeriod(uint32_t updatePeriod); + status_t getPositionUpdatePeriod(uint32_t *updatePeriod); + + + /* Sets playback head position within AudioTrack buffer. The new position is specified + * in number of frames. + * This method must be called with the AudioTrack in paused or stopped state. + * Note that the actual position set is modulo the AudioTrack buffer size in frames. + * Therefore using this method makes sense only when playing a "static" audio buffer + * as opposed to streaming. + * The getPosition() method on the other hand returns the total number of frames played since + * playback start. + * + * Parameters: + * + * position: New playback head position within AudioTrack buffer. + * + * Returned status (from utils/Errors.h) can be: + * - NO_ERROR: successful operation + * - INVALID_OPERATION: the AudioTrack is not stopped. + * - BAD_VALUE: The specified position is beyond the number of frames present in AudioTrack buffer + */ + status_t setPosition(uint32_t position); + status_t getPosition(uint32_t *position); + + /* Forces AudioTrack buffer full condition. When playing a static buffer, this method avoids + * rewriting the buffer before restarting playback after a stop. + * This method must be called with the AudioTrack in paused or stopped state. + * + * Returned status (from utils/Errors.h) can be: + * - NO_ERROR: successful operation + * - INVALID_OPERATION: the AudioTrack is not stopped. + */ + status_t reload(); + + /* returns a handle on the audio output used by this AudioTrack. + * + * Parameters: + * none. + * + * Returned value: + * handle on audio hardware output + */ + audio_io_handle_t getOutput(); + + /* returns the unique ID associated to this track. + * + * Parameters: + * none. + * + * Returned value: + * AudioTrack ID. + */ + int getSessionId(); + + + /* Attach track auxiliary output to specified effect. Used effectId = 0 + * to detach track from effect. + * + * Parameters: + * + * effectId: effectId obtained from AudioEffect::id(). + * + * Returned status (from utils/Errors.h) can be: + * - NO_ERROR: successful operation + * - INVALID_OPERATION: the effect is not an auxiliary effect. + * - BAD_VALUE: The specified effect ID is invalid + */ + status_t attachAuxEffect(int effectId); + + /* obtains a buffer of "frameCount" frames. The buffer must be + * filled entirely. If the track is stopped, obtainBuffer() returns + * STOPPED instead of NO_ERROR as long as there are buffers availlable, + * at which point NO_MORE_BUFFERS is returned. + * Buffers will be returned until the pool (buffercount()) + * is exhausted, at which point obtainBuffer() will either block + * or return WOULD_BLOCK depending on the value of the "blocking" + * parameter. + */ + + enum { + NO_MORE_BUFFERS = 0x80000001, + STOPPED = 1 + }; + + status_t obtainBuffer(Buffer* audioBuffer, int32_t waitCount); + void releaseBuffer(Buffer* audioBuffer); + + + /* As a convenience we provide a write() interface to the audio buffer. + * This is implemented on top of lockBuffer/unlockBuffer. For best + * performance + * + */ + ssize_t write(const void* buffer, size_t size); + + /* + * Dumps the state of an audio track. + */ + status_t dump(int fd, const Vector& args) const; + +private: + /* copying audio tracks is not allowed */ + AudioTrack(const AudioTrack& other); + AudioTrack& operator = (const AudioTrack& other); + + /* a small internal class to handle the callback */ + class AudioTrackThread : public Thread + { + public: + AudioTrackThread(AudioTrack& receiver, bool bCanCallJava = false); + private: + friend class AudioTrack; + virtual bool threadLoop(); + virtual status_t readyToRun(); + virtual void onFirstRef(); + AudioTrack& mReceiver; + Mutex mLock; + }; + + bool processAudioBuffer(const sp& thread); + status_t createTrack(int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + const sp& sharedBuffer, + audio_io_handle_t output, + bool enforceFrameCount); + + sp mAudioTrack; + sp mCblkMemory; + sp mAudioTrackThread; + + float mVolume[2]; + float mSendLevel; + uint32_t mFrameCount; + + audio_track_cblk_t* mCblk; + uint8_t mStreamType; + uint8_t mFormat; + uint8_t mChannelCount; + uint8_t mMuted; + uint32_t mChannels; + status_t mStatus; + uint32_t mLatency; + + volatile int32_t mActive; + + callback_t mCbf; + void* mUserData; + uint32_t mNotificationFramesReq; // requested number of frames between each notification callback + uint32_t mNotificationFramesAct; // actual number of frames between each notification callback + sp mSharedBuffer; + int mLoopCount; + uint32_t mRemainingFrames; + uint32_t mMarkerPosition; + bool mMarkerReached; + uint32_t mNewPosition; + uint32_t mUpdatePeriod; + uint32_t mFlags; + int mSessionId; + int mAuxEffectId; +}; + + +}; // namespace android + +#endif // ANDROID_AUDIOTRACK_H diff --git a/media/libsydneyaudio/src/gonk/EffectApi.h b/media/libsydneyaudio/src/gonk/EffectApi.h new file mode 100644 index 00000000000..b97c22ebb62 --- /dev/null +++ b/media/libsydneyaudio/src/gonk/EffectApi.h @@ -0,0 +1,796 @@ +/* + * 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_EFFECTAPI_H_ +#define ANDROID_EFFECTAPI_H_ + +#include +#include +#include + +#if __cplusplus +extern "C" { +#endif + +///////////////////////////////////////////////// +// Effect control interface +///////////////////////////////////////////////// + +// The effect control interface is exposed by each effect engine implementation. It consists of +// a set of functions controlling the configuration, activation and process of the engine. +// The functions are grouped in a structure of type effect_interface_s: +// struct effect_interface_s { +// effect_process_t process; +// effect_command_t command; +// }; + + +// effect_interface_t: Effect control interface handle. +// The effect_interface_t serves two purposes regarding the implementation of the effect engine: +// - 1 it is the address of a pointer to an effect_interface_s structure where the functions +// of the effect control API for a particular effect are located. +// - 2 it is the address of the context of a particular effect instance. +// A typical implementation in the effect library would define a structure as follows: +// struct effect_module_s { +// const struct effect_interface_s *itfe; +// effect_config_t config; +// effect_context_t context; +// } +// The implementation of EffectCreate() function would then allocate a structure of this +// type and return its address as effect_interface_t +typedef struct effect_interface_s **effect_interface_t; + + +// Effect API version 1.0 +#define EFFECT_API_VERSION 0x0100 // Format 0xMMmm MM: Major version, mm: minor version + +// Maximum length of character strings in structures defines by this API. +#define EFFECT_STRING_LEN_MAX 64 + +// +//--- Effect descriptor structure effect_descriptor_t +// + +// Unique effect ID (can be generated from the following site: +// http://www.itu.int/ITU-T/asn1/uuid.html) +// This format is used for both "type" and "uuid" fields of the effect descriptor structure. +// - When used for effect type and the engine is implementing and effect corresponding to a standard +// OpenSL ES interface, this ID must be the one defined in OpenSLES_IID.h for that interface. +// - When used as uuid, it should be a unique UUID for this particular implementation. +typedef struct effect_uuid_s { + uint32_t timeLow; + uint16_t timeMid; + uint16_t timeHiAndVersion; + uint16_t clockSeq; + uint8_t node[6]; +} effect_uuid_t; + +// NULL UUID definition (matches SL_IID_NULL_) +#define EFFECT_UUID_INITIALIZER { 0xec7178ec, 0xe5e1, 0x4432, 0xa3f4, \ + { 0x46, 0x57, 0xe6, 0x79, 0x52, 0x10 } } +static const effect_uuid_t EFFECT_UUID_NULL_ = EFFECT_UUID_INITIALIZER; +const effect_uuid_t * const EFFECT_UUID_NULL = &EFFECT_UUID_NULL_; +const char * const EFFECT_UUID_NULL_STR = "ec7178ec-e5e1-4432-a3f4-4657e6795210"; + +// The effect descriptor contains necessary information to facilitate the enumeration of the effect +// engines present in a library. +typedef struct effect_descriptor_s { + effect_uuid_t type; // UUID of to the OpenSL ES interface implemented by this effect + effect_uuid_t uuid; // UUID for this particular implementation + uint16_t apiVersion; // Version of the effect API implemented: matches EFFECT_API_VERSION + uint32_t flags; // effect engine capabilities/requirements flags (see below) + uint16_t cpuLoad; // CPU load indication (see below) + uint16_t memoryUsage; // Data Memory usage (see below) + char name[EFFECT_STRING_LEN_MAX]; // human readable effect name + char implementor[EFFECT_STRING_LEN_MAX]; // human readable effect implementor name +} effect_descriptor_t; + +// CPU load and memory usage indication: each effect implementation must provide an indication of +// its CPU and memory usage for the audio effect framework to limit the number of effects +// instantiated at a given time on a given platform. +// The CPU load is expressed in 0.1 MIPS units as estimated on an ARM9E core (ARMv5TE) with 0 WS. +// The memory usage is expressed in KB and includes only dynamically allocated memory + +// Definitions for flags field of effect descriptor. +// +---------------------------+-----------+----------------------------------- +// | description | bits | values +// +---------------------------+-----------+----------------------------------- +// | connection mode | 0..1 | 0 insert: after track process +// | | | 1 auxiliary: connect to track auxiliary +// | | | output and use send level +// | | | 2 replace: replaces track process function; +// | | | must implement SRC, volume and mono to stereo. +// | | | 3 reserved +// +---------------------------+-----------+----------------------------------- +// | insertion preference | 2..4 | 0 none +// | | | 1 first of the chain +// | | | 2 last of the chain +// | | | 3 exclusive (only effect in the insert chain) +// | | | 4..7 reserved +// +---------------------------+-----------+----------------------------------- +// | Volume management | 5..6 | 0 none +// | | | 1 implements volume control +// | | | 2 requires volume indication +// | | | 3 reserved +// +---------------------------+-----------+----------------------------------- +// | Device indication | 7..8 | 0 none +// | | | 1 requires device updates +// | | | 2..3 reserved +// +---------------------------+-----------+----------------------------------- +// | Sample input mode | 9..10 | 0 direct: process() function or EFFECT_CMD_CONFIGURE +// | | | command must specify a buffer descriptor +// | | | 1 provider: process() function uses the +// | | | bufferProvider indicated by the +// | | | EFFECT_CMD_CONFIGURE command to request input. +// | | | buffers. +// | | | 2 both: both input modes are supported +// | | | 3 reserved +// +---------------------------+-----------+----------------------------------- +// | Sample output mode | 11..12 | 0 direct: process() function or EFFECT_CMD_CONFIGURE +// | | | command must specify a buffer descriptor +// | | | 1 provider: process() function uses the +// | | | bufferProvider indicated by the +// | | | EFFECT_CMD_CONFIGURE command to request output +// | | | buffers. +// | | | 2 both: both output modes are supported +// | | | 3 reserved +// +---------------------------+-----------+----------------------------------- +// | Hardware acceleration | 13..15 | 0 No hardware acceleration +// | | | 1 non tunneled hw acceleration: the process() function +// | | | reads the samples, send them to HW accelerated +// | | | effect processor, reads back the processed samples +// | | | and returns them to the output buffer. +// | | | 2 tunneled hw acceleration: the process() function is +// | | | transparent. The effect interface is only used to +// | | | control the effect engine. This mode is relevant for +// | | | global effects actually applied by the audio +// | | | hardware on the output stream. +// +---------------------------+-----------+----------------------------------- +// | Audio Mode indication | 16..17 | 0 none +// | | | 1 requires audio mode updates +// | | | 2..3 reserved +// +---------------------------+-----------+----------------------------------- + +// Insert mode +#define EFFECT_FLAG_TYPE_MASK 0x00000003 +#define EFFECT_FLAG_TYPE_INSERT 0x00000000 +#define EFFECT_FLAG_TYPE_AUXILIARY 0x00000001 +#define EFFECT_FLAG_TYPE_REPLACE 0x00000002 + +// Insert preference +#define EFFECT_FLAG_INSERT_MASK 0x0000001C +#define EFFECT_FLAG_INSERT_ANY 0x00000000 +#define EFFECT_FLAG_INSERT_FIRST 0x00000004 +#define EFFECT_FLAG_INSERT_LAST 0x00000008 +#define EFFECT_FLAG_INSERT_EXCLUSIVE 0x0000000C + + +// Volume control +#define EFFECT_FLAG_VOLUME_MASK 0x00000060 +#define EFFECT_FLAG_VOLUME_CTRL 0x00000020 +#define EFFECT_FLAG_VOLUME_IND 0x00000040 +#define EFFECT_FLAG_VOLUME_NONE 0x00000000 + +// Device indication +#define EFFECT_FLAG_DEVICE_MASK 0x00000180 +#define EFFECT_FLAG_DEVICE_IND 0x00000080 +#define EFFECT_FLAG_DEVICE_NONE 0x00000000 + +// Sample input modes +#define EFFECT_FLAG_INPUT_MASK 0x00000600 +#define EFFECT_FLAG_INPUT_DIRECT 0x00000000 +#define EFFECT_FLAG_INPUT_PROVIDER 0x00000200 +#define EFFECT_FLAG_INPUT_BOTH 0x00000400 + +// Sample output modes +#define EFFECT_FLAG_OUTPUT_MASK 0x00001800 +#define EFFECT_FLAG_OUTPUT_DIRECT 0x00000000 +#define EFFECT_FLAG_OUTPUT_PROVIDER 0x00000800 +#define EFFECT_FLAG_OUTPUT_BOTH 0x00001000 + +// Hardware acceleration mode +#define EFFECT_FLAG_HW_ACC_MASK 0x00006000 +#define EFFECT_FLAG_HW_ACC_SIMPLE 0x00002000 +#define EFFECT_FLAG_HW_ACC_TUNNEL 0x00004000 + +// Audio mode indication +#define EFFECT_FLAG_AUDIO_MODE_MASK 0x00018000 +#define EFFECT_FLAG_AUDIO_MODE_IND 0x00008000 +#define EFFECT_FLAG_AUDIO_MODE_NONE 0x00000000 + +// Forward definition of type audio_buffer_t +typedef struct audio_buffer_s audio_buffer_t; + +//////////////////////////////////////////////////////////////////////////////// +// +// Function: process +// +// Description: Effect process function. Takes input samples as specified +// (count and location) in input buffer descriptor and output processed +// samples as specified in output buffer descriptor. If the buffer descriptor +// is not specified the function must use either the buffer or the +// buffer provider function installed by the EFFECT_CMD_CONFIGURE command. +// The effect framework will call the process() function after the EFFECT_CMD_ENABLE +// command is received and until the EFFECT_CMD_DISABLE is received. When the engine +// receives the EFFECT_CMD_DISABLE command it should turn off the effect gracefully +// and when done indicate that it is OK to stop calling the process() function by +// returning the -ENODATA status. +// +// NOTE: the process() function implementation should be "real-time safe" that is +// it should not perform blocking calls: malloc/free, sleep, read/write/open/close, +// pthread_cond_wait/pthread_mutex_lock... +// +// Input: +// effect_interface_t: handle to the effect interface this function +// is called on. +// inBuffer: buffer descriptor indicating where to read samples to process. +// If NULL, use the configuration passed by EFFECT_CMD_CONFIGURE command. +// +// inBuffer: buffer descriptor indicating where to write processed samples. +// If NULL, use the configuration passed by EFFECT_CMD_CONFIGURE command. +// +// Output: +// returned value: 0 successful operation +// -ENODATA the engine has finished the disable phase and the framework +// can stop calling process() +// -EINVAL invalid interface handle or +// invalid input/output buffer description +//////////////////////////////////////////////////////////////////////////////// +typedef int32_t (*effect_process_t)(effect_interface_t self, + audio_buffer_t *inBuffer, + audio_buffer_t *outBuffer); + +//////////////////////////////////////////////////////////////////////////////// +// +// Function: command +// +// Description: Send a command and receive a response to/from effect engine. +// +// Input: +// effect_interface_t: handle to the effect interface this function +// is called on. +// cmdCode: command code: the command can be a standardized command defined in +// effect_command_e (see below) or a proprietary command. +// cmdSize: size of command in bytes +// pCmdData: pointer to command data +// pReplyData: pointer to reply data +// +// Input/Output: +// replySize: maximum size of reply data as input +// actual size of reply data as output +// +// Output: +// returned value: 0 successful operation +// -EINVAL invalid interface handle or +// invalid command/reply size or format according to command code +// The return code should be restricted to indicate problems related to the this +// API specification. Status related to the execution of a particular command should be +// indicated as part of the reply field. +// +// *pReplyData updated with command response +// +//////////////////////////////////////////////////////////////////////////////// +typedef int32_t (*effect_command_t)(effect_interface_t self, + uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *replySize, + void *pReplyData); + + +// Effect control interface definition +struct effect_interface_s { + effect_process_t process; + effect_command_t command; +}; + + +// +//--- Standardized command codes for command() function +// +enum effect_command_e { + EFFECT_CMD_INIT, // initialize effect engine + EFFECT_CMD_CONFIGURE, // configure effect engine (see effect_config_t) + EFFECT_CMD_RESET, // reset effect engine + EFFECT_CMD_ENABLE, // enable effect process + EFFECT_CMD_DISABLE, // disable effect process + EFFECT_CMD_SET_PARAM, // set parameter immediately (see effect_param_t) + EFFECT_CMD_SET_PARAM_DEFERRED, // set parameter deferred + EFFECT_CMD_SET_PARAM_COMMIT, // commit previous set parameter deferred + EFFECT_CMD_GET_PARAM, // get parameter + EFFECT_CMD_SET_DEVICE, // set audio device (see audio_device_e) + EFFECT_CMD_SET_VOLUME, // set volume + EFFECT_CMD_SET_AUDIO_MODE, // set the audio mode (normal, ring, ...) + EFFECT_CMD_FIRST_PROPRIETARY = 0x10000 // first proprietary command code +}; + +//================================================================================================== +// command: EFFECT_CMD_INIT +//-------------------------------------------------------------------------------------------------- +// description: +// Initialize effect engine: All configurations return to default +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_CONFIGURE +//-------------------------------------------------------------------------------------------------- +// description: +// Apply new audio parameters configurations for input and output buffers +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_config_t) +// data: effect_config_t +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_RESET +//-------------------------------------------------------------------------------------------------- +// description: +// Reset the effect engine. Keep configuration but resets state and buffer content +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_ENABLE +//-------------------------------------------------------------------------------------------------- +// description: +// Enable the process. Called by the framework before the first call to process() +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_DISABLE +//-------------------------------------------------------------------------------------------------- +// description: +// Disable the process. Called by the framework after the last call to process() +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_SET_PARAM +//-------------------------------------------------------------------------------------------------- +// description: +// Set a parameter and apply it immediately +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_param_t) + size of param and value +// data: effect_param_t + param + value. See effect_param_t definition below for value offset +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_SET_PARAM_DEFERRED +//-------------------------------------------------------------------------------------------------- +// description: +// Set a parameter but apply it only when receiving EFFECT_CMD_SET_PARAM_COMMIT command +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_param_t) + size of param and value +// data: effect_param_t + param + value. See effect_param_t definition below for value offset +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_SET_PARAM_COMMIT +//-------------------------------------------------------------------------------------------------- +// description: +// Apply all previously received EFFECT_CMD_SET_PARAM_DEFERRED commands +//-------------------------------------------------------------------------------------------------- +// command format: +// size: 0 +// data: N/A +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(int) +// data: status +//================================================================================================== +// command: EFFECT_CMD_GET_PARAM +//-------------------------------------------------------------------------------------------------- +// description: +// Get a parameter value +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(effect_param_t) + size of param +// data: effect_param_t + param +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: sizeof(effect_param_t) + size of param and value +// data: effect_param_t + param + value. See effect_param_t definition below for value offset +//================================================================================================== +// command: EFFECT_CMD_SET_DEVICE +//-------------------------------------------------------------------------------------------------- +// description: +// Set the rendering device the audio output path is connected to. See audio_device_e for device +// values. +// The effect implementation must set EFFECT_FLAG_DEVICE_IND flag in its descriptor to receive this +// command when the device changes +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(uint32_t) +// data: audio_device_e +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_SET_VOLUME +//-------------------------------------------------------------------------------------------------- +// description: +// Set and get volume. Used by audio framework to delegate volume control to effect engine. +// The effect implementation must set EFFECT_FLAG_VOLUME_IND or EFFECT_FLAG_VOLUME_CTRL flag in +// its descriptor to receive this command before every call to process() function +// If EFFECT_FLAG_VOLUME_CTRL flag is set in the effect descriptor, the effect engine must return +// the volume that should be applied before the effect is processed. The overall volume (the volume +// actually applied by the effect engine multiplied by the returned value) should match the value +// indicated in the command. +//-------------------------------------------------------------------------------------------------- +// command format: +// size: n * sizeof(uint32_t) +// data: volume for each channel defined in effect_config_t for output buffer expressed in +// 8.24 fixed point format +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: n * sizeof(uint32_t) / 0 +// data: - if EFFECT_FLAG_VOLUME_CTRL is set in effect descriptor: +// volume for each channel defined in effect_config_t for output buffer expressed in +// 8.24 fixed point format +// - if EFFECT_FLAG_VOLUME_CTRL is not set in effect descriptor: +// N/A +// It is legal to receive a null pointer as pReplyData in which case the effect framework has +// delegated volume control to another effect +//================================================================================================== +// command: EFFECT_CMD_SET_AUDIO_MODE +//-------------------------------------------------------------------------------------------------- +// description: +// Set the audio mode. The effect implementation must set EFFECT_FLAG_AUDIO_MODE_IND flag in its +// descriptor to receive this command when the audio mode changes. +//-------------------------------------------------------------------------------------------------- +// command format: +// size: sizeof(uint32_t) +// data: audio_mode_e +//-------------------------------------------------------------------------------------------------- +// reply format: +// size: 0 +// data: N/A +//================================================================================================== +// command: EFFECT_CMD_FIRST_PROPRIETARY +//-------------------------------------------------------------------------------------------------- +// description: +// All proprietary effect commands must use command codes above this value. The size and format of +// command and response fields is free in this case +//================================================================================================== + + +// Audio buffer descriptor used by process(), bufferProvider() functions and buffer_config_t +// structure. Multi-channel audio is always interleaved. The channel order is from LSB to MSB with +// regard to the channel mask definition in audio_channels_e e.g : +// Stereo: left, right +// 5 point 1: front left, front right, front center, low frequency, back left, back right +// The buffer size is expressed in frame count, a frame being composed of samples for all +// channels at a given time. Frame size for unspecified format (AUDIO_FORMAT_OTHER) is 8 bit by +// definition +struct audio_buffer_s { + size_t frameCount; // number of frames in buffer + union { + void* raw; // raw pointer to start of buffer + int32_t* s32; // pointer to signed 32 bit data at start of buffer + int16_t* s16; // pointer to signed 16 bit data at start of buffer + uint8_t* u8; // pointer to unsigned 8 bit data at start of buffer + }; +}; + +// The buffer_provider_s structure contains functions that can be used +// by the effect engine process() function to query and release input +// or output audio buffer. +// The getBuffer() function is called to retrieve a buffer where data +// should read from or written to by process() function. +// The releaseBuffer() function MUST be called when the buffer retrieved +// with getBuffer() is not needed anymore. +// The process function should use the buffer provider mechanism to retrieve +// input or output buffer if the inBuffer or outBuffer passed as argument is NULL +// and the buffer configuration (buffer_config_t) given by the EFFECT_CMD_CONFIGURE +// command did not specify an audio buffer. + +typedef int32_t (* buffer_function_t)(void *cookie, audio_buffer_t *buffer); + +typedef struct buffer_provider_s { + buffer_function_t getBuffer; // retrieve next buffer + buffer_function_t releaseBuffer; // release used buffer + void *cookie; // for use by client of buffer provider functions +} buffer_provider_t; + + +// The buffer_config_s structure specifies the input or output audio format +// to be used by the effect engine. It is part of the effect_config_t +// structure that defines both input and output buffer configurations and is +// passed by the EFFECT_CMD_CONFIGURE command. +typedef struct buffer_config_s { + audio_buffer_t buffer; // buffer for use by process() function if not passed explicitly + uint32_t samplingRate; // sampling rate + uint32_t channels; // channel mask (see audio_channels_e) + buffer_provider_t bufferProvider; // buffer provider + uint8_t format; // Audio format (see audio_format_e) + uint8_t accessMode; // read/write or accumulate in buffer (effect_buffer_access_e) + uint16_t mask; // indicates which of the above fields is valid +} buffer_config_t; + +// Sample format +enum audio_format_e { + SAMPLE_FORMAT_PCM_S15, // PCM signed 16 bits + SAMPLE_FORMAT_PCM_U8, // PCM unsigned 8 bits + SAMPLE_FORMAT_PCM_S7_24, // PCM signed 7.24 fixed point representation + SAMPLE_FORMAT_OTHER // other format (e.g. compressed) +}; + +// Channel mask +enum audio_channels_e { + CHANNEL_FRONT_LEFT = 0x1, // front left channel + CHANNEL_FRONT_RIGHT = 0x2, // front right channel + CHANNEL_FRONT_CENTER = 0x4, // front center channel + CHANNEL_LOW_FREQUENCY = 0x8, // low frequency channel + CHANNEL_BACK_LEFT = 0x10, // back left channel + CHANNEL_BACK_RIGHT = 0x20, // back right channel + CHANNEL_FRONT_LEFT_OF_CENTER = 0x40, // front left of center channel + CHANNEL_FRONT_RIGHT_OF_CENTER = 0x80, // front right of center channel + CHANNEL_BACK_CENTER = 0x100, // back center channel + CHANNEL_MONO = CHANNEL_FRONT_LEFT, + CHANNEL_STEREO = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT), + CHANNEL_QUAD = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT), + CHANNEL_SURROUND = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER), + CHANNEL_5POINT1 = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT), + CHANNEL_7POINT1 = (CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | + CHANNEL_FRONT_CENTER | CHANNEL_LOW_FREQUENCY | CHANNEL_BACK_LEFT | CHANNEL_BACK_RIGHT | + CHANNEL_FRONT_LEFT_OF_CENTER | CHANNEL_FRONT_RIGHT_OF_CENTER), +}; + +// Render device +enum audio_device_e { + DEVICE_EARPIECE = 0x1, // earpiece + DEVICE_SPEAKER = 0x2, // speaker + DEVICE_WIRED_HEADSET = 0x4, // wired headset, with microphone + DEVICE_WIRED_HEADPHONE = 0x8, // wired headphone, without microphone + DEVICE_BLUETOOTH_SCO = 0x10, // generic bluetooth SCO + DEVICE_BLUETOOTH_SCO_HEADSET = 0x20, // bluetooth SCO headset + DEVICE_BLUETOOTH_SCO_CARKIT = 0x40, // bluetooth SCO car kit + DEVICE_BLUETOOTH_A2DP = 0x80, // generic bluetooth A2DP + DEVICE_BLUETOOTH_A2DP_HEADPHONES = 0x100, // bluetooth A2DP headphones + DEVICE_BLUETOOTH_A2DP_SPEAKER = 0x200, // bluetooth A2DP speakers + DEVICE_AUX_DIGITAL = 0x400, // digital output + DEVICE_EXTERNAL_SPEAKER = 0x800 // external speaker (stereo and High quality) +}; + +// Audio mode +enum audio_mode_e { + AUDIO_MODE_NORMAL, // device idle + AUDIO_MODE_RINGTONE, // device ringing + AUDIO_MODE_IN_CALL // audio call connected (VoIP or telephony) +}; + +// Values for "accessMode" field of buffer_config_t: +// overwrite, read only, accumulate (read/modify/write) +enum effect_buffer_access_e { + EFFECT_BUFFER_ACCESS_WRITE, + EFFECT_BUFFER_ACCESS_READ, + EFFECT_BUFFER_ACCESS_ACCUMULATE + +}; + +// Values for bit field "mask" in buffer_config_t. If a bit is set, the corresponding field +// in buffer_config_t must be taken into account when executing the EFFECT_CMD_CONFIGURE command +#define EFFECT_CONFIG_BUFFER 0x0001 // buffer field must be taken into account +#define EFFECT_CONFIG_SMP_RATE 0x0002 // samplingRate field must be taken into account +#define EFFECT_CONFIG_CHANNELS 0x0004 // channels field must be taken into account +#define EFFECT_CONFIG_FORMAT 0x0008 // format field must be taken into account +#define EFFECT_CONFIG_ACC_MODE 0x0010 // accessMode field must be taken into account +#define EFFECT_CONFIG_PROVIDER 0x0020 // bufferProvider field must be taken into account +#define EFFECT_CONFIG_ALL (EFFECT_CONFIG_BUFFER | EFFECT_CONFIG_SMP_RATE | \ + EFFECT_CONFIG_CHANNELS | EFFECT_CONFIG_FORMAT | \ + EFFECT_CONFIG_ACC_MODE | EFFECT_CONFIG_PROVIDER) + + +// effect_config_s structure describes the format of the pCmdData argument of EFFECT_CMD_CONFIGURE +// command to configure audio parameters and buffers for effect engine input and output. +typedef struct effect_config_s { + buffer_config_t inputCfg; + buffer_config_t outputCfg;; +} effect_config_t; + + +// effect_param_s structure describes the format of the pCmdData argument of EFFECT_CMD_SET_PARAM +// command and pCmdData and pReplyData of EFFECT_CMD_GET_PARAM command. +// psize and vsize represent the actual size of parameter and value. +// +// NOTE: the start of value field inside the data field is always on a 32 bit boundary: +// +// +-----------+ +// | status | sizeof(int) +// +-----------+ +// | psize | sizeof(int) +// +-----------+ +// | vsize | sizeof(int) +// +-----------+ +// | | | | +// ~ parameter ~ > psize | +// | | | > ((psize - 1)/sizeof(int) + 1) * sizeof(int) +// +-----------+ | +// | padding | | +// +-----------+ +// | | | +// ~ value ~ > vsize +// | | | +// +-----------+ + +typedef struct effect_param_s { + int32_t status; // Transaction status (unused for command, used for reply) + uint32_t psize; // Parameter size + uint32_t vsize; // Value size + char data[]; // Start of Parameter + Value data +} effect_param_t; + + +///////////////////////////////////////////////// +// Effect library interface +///////////////////////////////////////////////// + +// An effect library is required to implement and expose the following functions +// to enable effect enumeration and instantiation. The name of these functions must be as +// specified here as the effect framework will get the function address with dlsym(): +// +// - effect_QueryNumberEffects_t EffectQueryNumberEffects; +// - effect_QueryEffect_t EffectQueryEffect; +// - effect_CreateEffect_t EffectCreate; +// - effect_ReleaseEffect_t EffectRelease; + + +//////////////////////////////////////////////////////////////////////////////// +// +// Function: EffectQueryNumberEffects +// +// Description: Returns the number of different effects exposed by the +// library. Each effect must have a unique effect uuid (see +// effect_descriptor_t). This function together with EffectQueryEffect() +// is used to enumerate all effects present in the library. +// +// Input/Output: +// pNumEffects: address where the number of effects should be returned. +// +// Output: +// returned value: 0 successful operation. +// -ENODEV library failed to initialize +// -EINVAL invalid pNumEffects +// *pNumEffects: updated with number of effects in library +// +//////////////////////////////////////////////////////////////////////////////// +typedef int32_t (*effect_QueryNumberEffects_t)(uint32_t *pNumEffects); + +//////////////////////////////////////////////////////////////////////////////// +// +// Function: EffectQueryEffect +// +// Description: Returns the descriptor of the effect engine which index is +// given as first argument. +// See effect_descriptor_t for details on effect descriptors. +// This function together with EffectQueryNumberEffects() is used to enumerate all +// effects present in the library. The enumeration sequence is: +// EffectQueryNumberEffects(&num_effects); +// for (i = 0; i < num_effects; i++) +// EffectQueryEffect(i,...); +// +// Input/Output: +// index: index of the effect +// pDescriptor: address where to return the effect descriptor. +// +// Output: +// returned value: 0 successful operation. +// -ENODEV library failed to initialize +// -EINVAL invalid pDescriptor or index +// -ENOSYS effect list has changed since last execution of +// EffectQueryNumberEffects() +// -ENOENT no more effect available +// *pDescriptor: updated with the effect descriptor. +// +//////////////////////////////////////////////////////////////////////////////// +typedef int32_t (*effect_QueryEffect_t)(uint32_t index, + effect_descriptor_t *pDescriptor); + +//////////////////////////////////////////////////////////////////////////////// +// +// Function: EffectCreate +// +// Description: Creates an effect engine of the specified type and returns an +// effect control interface on this engine. The function will allocate the +// resources for an instance of the requested effect engine and return +// a handle on the effect control interface. +// +// Input: +// uuid: pointer to the effect uuid. +// sessionId: audio session to which this effect instance will be attached. All effects +// created with the same session ID are connected in series and process the same signal +// stream. Knowing that two effects are part of the same effect chain can help the +// library implement some kind of optimizations. +// ioId: identifies the output or input stream this effect is directed to at audio HAL. +// For future use especially with tunneled HW accelerated effects +// +// Input/Output: +// pInterface: address where to return the effect interface. +// +// Output: +// returned value: 0 successful operation. +// -ENODEV library failed to initialize +// -EINVAL invalid pEffectUuid or pInterface +// -ENOENT no effect with this uuid found +// *pInterface: updated with the effect interface handle. +// +//////////////////////////////////////////////////////////////////////////////// +typedef int32_t (*effect_CreateEffect_t)(effect_uuid_t *uuid, + int32_t sessionId, + int32_t ioId, + effect_interface_t *pInterface); + +//////////////////////////////////////////////////////////////////////////////// +// +// Function: EffectRelease +// +// Description: Releases the effect engine whose handle is given as argument. +// All resources allocated to this particular instance of the effect are +// released. +// +// Input: +// interface: handle on the effect interface to be released. +// +// Output: +// returned value: 0 successful operation. +// -ENODEV library failed to initialize +// -EINVAL invalid interface handle +// +//////////////////////////////////////////////////////////////////////////////// +typedef int32_t (*effect_ReleaseEffect_t)(effect_interface_t interface); + + +#if __cplusplus +} // extern "C" +#endif + + +#endif /*ANDROID_EFFECTAPI_H_*/ diff --git a/media/libsydneyaudio/src/gonk/IAudioFlinger.h b/media/libsydneyaudio/src/gonk/IAudioFlinger.h new file mode 100644 index 00000000000..b10d3ab937a --- /dev/null +++ b/media/libsydneyaudio/src/gonk/IAudioFlinger.h @@ -0,0 +1,184 @@ +/* + * 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_IAUDIOFLINGER_H +#define ANDROID_IAUDIOFLINGER_H + +#include +#include +#include + +#include +#include +#include +#include "IAudioTrack.h" +#include "IAudioRecord.h" +#include "IAudioFlingerClient.h" +#include "EffectApi.h" +#include "IEffect.h" +#include "IEffectClient.h" +#include + +namespace android { + +// ---------------------------------------------------------------------------- + +class IAudioFlinger : public IInterface +{ +public: + DECLARE_META_INTERFACE(AudioFlinger); + + /* create an audio track and registers it with AudioFlinger. + * return null if the track cannot be created. + */ + virtual sp createTrack( + pid_t pid, + int streamType, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + const sp& sharedBuffer, + int output, + int *sessionId, + status_t *status) = 0; + + virtual sp openRecord( + pid_t pid, + int input, + uint32_t sampleRate, + int format, + int channelCount, + int frameCount, + uint32_t flags, + int *sessionId, + status_t *status) = 0; + + /* query the audio hardware state. This state never changes, + * and therefore can be cached. + */ + virtual uint32_t sampleRate(int output) const = 0; + virtual int channelCount(int output) const = 0; + virtual int format(int output) const = 0; + virtual size_t frameCount(int output) const = 0; + virtual uint32_t latency(int output) const = 0; + + /* set/get the audio hardware state. This will probably be used by + * the preference panel, mostly. + */ + virtual status_t setMasterVolume(float value) = 0; + virtual status_t setMasterMute(bool muted) = 0; + + virtual float masterVolume() const = 0; + virtual bool masterMute() const = 0; + + /* set/get stream type state. This will probably be used by + * the preference panel, mostly. + */ + virtual status_t setStreamVolume(int stream, float value, int output) = 0; + virtual status_t setStreamMute(int stream, bool muted) = 0; + + virtual float streamVolume(int stream, int output) const = 0; + virtual bool streamMute(int stream) const = 0; + + // set audio mode + virtual status_t setMode(int mode) = 0; + + // mic mute/state + virtual status_t setMicMute(bool state) = 0; + virtual bool getMicMute() const = 0; + + // is any track active on this stream? + virtual bool isStreamActive(int stream) const = 0; + + virtual status_t setParameters(int ioHandle, const String8& keyValuePairs) = 0; + virtual String8 getParameters(int ioHandle, const String8& keys) = 0; + + // register a current process for audio output change notifications + virtual void registerClient(const sp& client) = 0; + + // retrieve the audio recording buffer size + virtual size_t getInputBufferSize(uint32_t sampleRate, int format, int channelCount) = 0; + + virtual int openOutput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t *pLatencyMs, + uint32_t flags) = 0; + virtual int openDuplicateOutput(int output1, int output2) = 0; + virtual status_t closeOutput(int output) = 0; + virtual status_t suspendOutput(int output) = 0; + virtual status_t restoreOutput(int output) = 0; + + virtual int openInput(uint32_t *pDevices, + uint32_t *pSamplingRate, + uint32_t *pFormat, + uint32_t *pChannels, + uint32_t acoustics) = 0; + virtual status_t closeInput(int input) = 0; + + virtual status_t setStreamOutput(uint32_t stream, int output) = 0; + + virtual status_t setVoiceVolume(float volume) = 0; + + virtual status_t getRenderPosition(uint32_t *halFrames, uint32_t *dspFrames, int output) = 0; + + virtual unsigned int getInputFramesLost(int ioHandle) = 0; + + virtual int newAudioSessionId() = 0; + + virtual status_t loadEffectLibrary(const char *libPath, int *handle) = 0; + + virtual status_t unloadEffectLibrary(int handle) = 0; + + virtual status_t queryNumberEffects(uint32_t *numEffects) = 0; + + virtual status_t queryEffect(uint32_t index, effect_descriptor_t *pDescriptor) = 0; + + virtual status_t getEffectDescriptor(effect_uuid_t *pEffectUUID, effect_descriptor_t *pDescriptor) = 0; + + virtual sp createEffect(pid_t pid, + effect_descriptor_t *pDesc, + const sp& client, + int32_t priority, + int output, + int sessionId, + status_t *status, + int *id, + int *enabled) = 0; + + virtual status_t moveEffects(int session, int srcOutput, int dstOutput) = 0; +}; + + +// ---------------------------------------------------------------------------- + +class BnAudioFlinger : public BnInterface +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif // ANDROID_IAUDIOFLINGER_H diff --git a/media/libsydneyaudio/src/gonk/IAudioFlingerClient.h b/media/libsydneyaudio/src/gonk/IAudioFlingerClient.h new file mode 100644 index 00000000000..aa0cdcff1e8 --- /dev/null +++ b/media/libsydneyaudio/src/gonk/IAudioFlingerClient.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009 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_IAUDIOFLINGERCLIENT_H +#define ANDROID_IAUDIOFLINGERCLIENT_H + + +#include +#include +#include + +namespace android { + +// ---------------------------------------------------------------------------- + +class IAudioFlingerClient : public IInterface +{ +public: + DECLARE_META_INTERFACE(AudioFlingerClient); + + // Notifies a change of audio input/output configuration. + virtual void ioConfigChanged(int event, int ioHandle, void *param2) = 0; + +}; + + +// ---------------------------------------------------------------------------- + +class BnAudioFlingerClient : public BnInterface +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif // ANDROID_IAUDIOFLINGERCLIENT_H diff --git a/media/libsydneyaudio/src/gonk/IAudioRecord.h b/media/libsydneyaudio/src/gonk/IAudioRecord.h new file mode 100644 index 00000000000..46735def2f5 --- /dev/null +++ b/media/libsydneyaudio/src/gonk/IAudioRecord.h @@ -0,0 +1,68 @@ +/* + * 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 IAUDIORECORD_H_ +#define IAUDIORECORD_H_ + +#include +#include + +#include +#include +#include +#include + + +namespace android { + +// ---------------------------------------------------------------------------- + +class IAudioRecord : public IInterface +{ +public: + DECLARE_META_INTERFACE(AudioRecord); + + /* After it's created the track is not active. Call start() to + * make it active. If set, the callback will start being called. + */ + virtual status_t start() = 0; + + /* Stop a track. If set, the callback will cease being called and + * obtainBuffer will return an error. Buffers that are already released + * will be processed, unless flush() is called. + */ + virtual void stop() = 0; + + /* get this tracks control block */ + virtual sp getCblk() const = 0; +}; + +// ---------------------------------------------------------------------------- + +class BnAudioRecord : public BnInterface +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif /*IAUDIORECORD_H_*/ diff --git a/media/libsydneyaudio/src/gonk/IAudioTrack.h b/media/libsydneyaudio/src/gonk/IAudioTrack.h new file mode 100644 index 00000000000..47d530be5bd --- /dev/null +++ b/media/libsydneyaudio/src/gonk/IAudioTrack.h @@ -0,0 +1,89 @@ +/* + * 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_IAUDIOTRACK_H +#define ANDROID_IAUDIOTRACK_H + +#include +#include + +#include +#include +#include +#include + + +namespace android { + +// ---------------------------------------------------------------------------- + +class IAudioTrack : public IInterface +{ +public: + DECLARE_META_INTERFACE(AudioTrack); + + /* After it's created the track is not active. Call start() to + * make it active. If set, the callback will start being called. + */ + virtual status_t start() = 0; + + /* Stop a track. If set, the callback will cease being called and + * obtainBuffer will return an error. Buffers that are already released + * will be processed, unless flush() is called. + */ + virtual void stop() = 0; + + /* flush a stopped track. All pending buffers are discarded. + * This function has no effect if the track is not stoped. + */ + virtual void flush() = 0; + + /* mute or unmutes this track. + * While mutted, the callback, if set, is still called. + */ + virtual void mute(bool) = 0; + + /* Pause a track. If set, the callback will cease being called and + * obtainBuffer will return an error. Buffers that are already released + * will be processed, unless flush() is called. + */ + virtual void pause() = 0; + + /* Attach track auxiliary output to specified effect. Use effectId = 0 + * to detach track from effect. + */ + virtual status_t attachAuxEffect(int effectId) = 0; + + /* get this tracks control block */ + virtual sp getCblk() const = 0; +}; + +// ---------------------------------------------------------------------------- + +class BnAudioTrack : public BnInterface +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +// ---------------------------------------------------------------------------- + +}; // namespace android + +#endif // ANDROID_IAUDIOTRACK_H diff --git a/media/libsydneyaudio/src/gonk/IEffect.h b/media/libsydneyaudio/src/gonk/IEffect.h new file mode 100644 index 00000000000..ff04869e06b --- /dev/null +++ b/media/libsydneyaudio/src/gonk/IEffect.h @@ -0,0 +1,60 @@ +/* + * 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_IEFFECT_H +#define ANDROID_IEFFECT_H + +#include +#include +#include +#include + +namespace android { + +class IEffect: public IInterface +{ +public: + DECLARE_META_INTERFACE(Effect); + + virtual status_t enable() = 0; + + virtual status_t disable() = 0; + + virtual status_t command(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t *pReplySize, + void *pReplyData) = 0; + + virtual void disconnect() = 0; + + virtual sp getCblk() const = 0; +}; + +// ---------------------------------------------------------------------------- + +class BnEffect: public BnInterface +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +}; // namespace android + +#endif // ANDROID_IEFFECT_H diff --git a/media/libsydneyaudio/src/gonk/IEffectClient.h b/media/libsydneyaudio/src/gonk/IEffectClient.h new file mode 100644 index 00000000000..2f78c98f172 --- /dev/null +++ b/media/libsydneyaudio/src/gonk/IEffectClient.h @@ -0,0 +1,54 @@ +/* + * 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_IEFFECTCLIENT_H +#define ANDROID_IEFFECTCLIENT_H + +#include +#include +#include +#include + +namespace android { + +class IEffectClient: public IInterface +{ +public: + DECLARE_META_INTERFACE(EffectClient); + + virtual void controlStatusChanged(bool controlGranted) = 0; + virtual void enableStatusChanged(bool enabled) = 0; + virtual void commandExecuted(uint32_t cmdCode, + uint32_t cmdSize, + void *pCmdData, + uint32_t replySize, + void *pReplyData) = 0; +}; + +// ---------------------------------------------------------------------------- + +class BnEffectClient: public BnInterface +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +}; // namespace android + +#endif // ANDROID_IEFFECTCLIENT_H diff --git a/media/libsydneyaudio/src/sydney_audio_gonk.cpp b/media/libsydneyaudio/src/sydney_audio_gonk.cpp index 5f43f3be1f3..a3257e09686 100644 --- a/media/libsydneyaudio/src/sydney_audio_gonk.cpp +++ b/media/libsydneyaudio/src/sydney_audio_gonk.cpp @@ -40,16 +40,14 @@ extern "C" { #include "sydney_audio.h" } +#include "gonk/AudioTrack.h" #include "android/log.h" -#include "media/AudioTrack.h" -#ifndef ALOG #if defined(DEBUG) || defined(FORCE_ALOG) #define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - SYDNEY_AUDIO" , ## args) #else #define ALOG(args...) #endif -#endif /* Gonk implementation based on sydney_audio_android.c */ diff --git a/mozglue/gonk/Makefile.in b/mozglue/gonk/Makefile.in index 3a6c8442241..0ab2518383a 100644 --- a/mozglue/gonk/Makefile.in +++ b/mozglue/gonk/Makefile.in @@ -1,7 +1,6 @@ -/* -*- Mode: C++; tab-width: 4; 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/. */ +# 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/. DEPTH = ../.. topsrcdir = @top_srcdir@ diff --git a/netwerk/protocol/device/GonkCaptureProvider.cpp b/netwerk/protocol/device/GonkCaptureProvider.cpp index 2eafb1afd28..25e505f215f 100644 --- a/netwerk/protocol/device/GonkCaptureProvider.cpp +++ b/netwerk/protocol/device/GonkCaptureProvider.cpp @@ -18,7 +18,7 @@ #define USE_GS2_LIBCAMERA #define CameraHardwareInterface CameraHardwareInterface_SGS2 #define HAL_openCameraHardware HAL_openCameraHardware_SGS2 -#include "camera/CameraHardwareInterface.h" +#include "gonk/CameraHardwareInterface.h" #undef CameraHardwareInterface #undef USE_GS2_LIBCAMERA #undef HAL_openCameraHardware @@ -31,7 +31,7 @@ #define USE_MAGURO_LIBCAMERA #define CameraHardwareInterface CameraHardwareInterface_MAGURO #define HAL_openCameraHardware HAL_openCameraHardware_MAGURO -#include "camera/CameraHardwareInterface.h" +#include "gonk/CameraHardwareInterface.h" #undef CameraHardwareInterface #undef USE_MAGURO_LIBCAMERA #undef HAL_openCameraHardware @@ -42,7 +42,7 @@ #define image_rect_type image_rect_type3 #define image_rect_struct image_rect_struct3 #define CameraHardwareInterface CameraHardwareInterface_DEFAULT -#include "camera/CameraHardwareInterface.h" +#include "gonk/CameraHardwareInterface.h" #undef CameraHardwareInterface using namespace android; diff --git a/netwerk/protocol/device/gonk/Camera.h b/netwerk/protocol/device/gonk/Camera.h new file mode 100644 index 00000000000..795b4c3d554 --- /dev/null +++ b/netwerk/protocol/device/gonk/Camera.h @@ -0,0 +1,256 @@ +/* + * 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 ANDROID_HARDWARE_CAMERA_H +#define ANDROID_HARDWARE_CAMERA_H + +#include +#include "ICameraClient.h" + +namespace android { + +class ISurface; + +/* + * A set of bit masks for specifying how the received preview frames are + * handled before the previewCallback() call. + * + * The least significant 3 bits of an "int" value are used for this purpose: + * + * ..... 0 0 0 + * ^ ^ ^ + * | | |---------> determine whether the callback is enabled or not + * | |-----------> determine whether the callback is one-shot or not + * |-------------> determine whether the frame is copied out or not + * + * WARNING: + * When a frame is sent directly without copying, it is the frame receiver's + * responsiblity to make sure that the frame data won't get corrupted by + * subsequent preview frames filled by the camera. This flag is recommended + * only when copying out data brings significant performance price and the + * handling/processing of the received frame data is always faster than + * the preview frame rate so that data corruption won't occur. + * + * For instance, + * 1. 0x00 disables the callback. In this case, copy out and one shot bits + * are ignored. + * 2. 0x01 enables a callback without copying out the received frames. A + * typical use case is the Camcorder application to avoid making costly + * frame copies. + * 3. 0x05 is enabling a callback with frame copied out repeatedly. A typical + * use case is the Camera application. + * 4. 0x07 is enabling a callback with frame copied out only once. A typical use + * case is the Barcode scanner application. + */ +#define FRAME_CALLBACK_FLAG_ENABLE_MASK 0x01 +#define FRAME_CALLBACK_FLAG_ONE_SHOT_MASK 0x02 +#define FRAME_CALLBACK_FLAG_COPY_OUT_MASK 0x04 + +// Typical use cases +#define FRAME_CALLBACK_FLAG_NOOP 0x00 +#define FRAME_CALLBACK_FLAG_CAMCORDER 0x01 +#define FRAME_CALLBACK_FLAG_CAMERA 0x05 +#define FRAME_CALLBACK_FLAG_BARCODE_SCANNER 0x07 + +// msgType in notifyCallback and dataCallback functions +enum { + CAMERA_MSG_ERROR = 0x001, + CAMERA_MSG_SHUTTER = 0x002, + CAMERA_MSG_FOCUS = 0x004, + CAMERA_MSG_ZOOM = 0x008, + CAMERA_MSG_PREVIEW_FRAME = 0x010, + CAMERA_MSG_VIDEO_FRAME = 0x020, + CAMERA_MSG_POSTVIEW_FRAME = 0x040, + CAMERA_MSG_RAW_IMAGE = 0x080, + CAMERA_MSG_COMPRESSED_IMAGE = 0x100, + CAMERA_MSG_ALL_MSGS = 0x1FF +}; + +// cmdType in sendCommand functions +enum { + CAMERA_CMD_START_SMOOTH_ZOOM = 1, + CAMERA_CMD_STOP_SMOOTH_ZOOM = 2, + // Set the clockwise rotation of preview display (setPreviewDisplay) in + // degrees. This affects the preview frames and the picture displayed after + // snapshot. This method is useful for portrait mode applications. Note that + // preview display of front-facing cameras is flipped horizontally before + // the rotation, that is, the image is reflected along the central vertical + // axis of the camera sensor. So the users can see themselves as looking + // into a mirror. + // + // This does not affect the order of byte array of CAMERA_MSG_PREVIEW_FRAME, + // CAMERA_MSG_VIDEO_FRAME, CAMERA_MSG_POSTVIEW_FRAME, CAMERA_MSG_RAW_IMAGE, + // or CAMERA_MSG_COMPRESSED_IMAGE. This is not allowed to be set during + // preview. + CAMERA_CMD_SET_DISPLAY_ORIENTATION = 3, +}; + +// camera fatal errors +enum { + CAMERA_ERROR_UKNOWN = 1, + CAMERA_ERROR_SERVER_DIED = 100 +}; + +enum { + CAMERA_FACING_BACK = 0, /* The facing of the camera is opposite to that of the screen. */ + CAMERA_FACING_FRONT = 1 /* The facing of the camera is the same as that of the screen. */ +}; + +struct CameraInfo { + + /** + * The direction that the camera faces to. It should be + * CAMERA_FACING_BACK or CAMERA_FACING_FRONT. + */ + int facing; + + /** + * The orientation of the camera image. The value is the angle that the + * camera image needs to be rotated clockwise so it shows correctly on + * the display in its natural orientation. It should be 0, 90, 180, or 270. + * + * For example, suppose a device has a naturally tall screen. The + * back-facing camera sensor is mounted in landscape. You are looking at + * the screen. If the top side of the camera sensor is aligned with the + * right edge of the screen in natural orientation, the value should be + * 90. If the top side of a front-facing camera sensor is aligned with + * the right of the screen, the value should be 270. + */ + int orientation; +}; + +class ICameraService; +class ICamera; +class Surface; +class Mutex; +class String8; + +// ref-counted object for callbacks +class CameraListener: virtual public RefBase +{ +public: + virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2) = 0; + virtual void postData(int32_t msgType, const sp& dataPtr) = 0; + virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp& dataPtr) = 0; +}; + +class Camera : public BnCameraClient, public IBinder::DeathRecipient +{ +public: + // construct a camera client from an existing remote + static sp create(const sp& camera); + static int32_t getNumberOfCameras(); + static status_t getCameraInfo(int cameraId, + struct CameraInfo* cameraInfo); + static sp connect(int cameraId); + ~Camera(); + void init(); + + status_t reconnect(); + void disconnect(); + status_t lock(); + status_t unlock(); + + status_t getStatus() { return mStatus; } + + // pass the buffered ISurface to the camera service + status_t setPreviewDisplay(const sp& surface); + status_t setPreviewDisplay(const sp& surface); + + // start preview mode, must call setPreviewDisplay first + status_t startPreview(); + + // stop preview mode + void stopPreview(); + + // get preview state + bool previewEnabled(); + + // start recording mode, must call setPreviewDisplay first + status_t startRecording(); + + // stop recording mode + void stopRecording(); + + // get recording state + bool recordingEnabled(); + + // release a recording frame + void releaseRecordingFrame(const sp& mem); + + // autoFocus - status returned from callback + status_t autoFocus(); + + // cancel auto focus + status_t cancelAutoFocus(); + + // take a picture - picture returned from callback + status_t takePicture(); + + // set preview/capture parameters - key/value pairs + status_t setParameters(const String8& params); + + // get preview/capture parameters - key/value pairs + String8 getParameters() const; + + // send command to camera driver + status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2); + + void setListener(const sp& listener); + void setPreviewCallbackFlags(int preview_callback_flag); + + // ICameraClient interface + virtual void notifyCallback(int32_t msgType, int32_t ext, int32_t ext2); + virtual void dataCallback(int32_t msgType, const sp& dataPtr); + virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp& dataPtr); + + sp remote(); + +private: + Camera(); + Camera(const Camera&); + Camera& operator=(const Camera); + virtual void binderDied(const wp& who); + + class DeathNotifier: public IBinder::DeathRecipient + { + public: + DeathNotifier() { + } + + virtual void binderDied(const wp& who); + }; + + static sp mDeathNotifier; + + // helper function to obtain camera service handle + static const sp& getCameraService(); + + sp mCamera; + status_t mStatus; + + sp mListener; + + friend class DeathNotifier; + + static Mutex mLock; + static sp mCameraService; + +}; + +}; // namespace android + +#endif diff --git a/netwerk/protocol/device/gonk/CameraHardwareInterface.h b/netwerk/protocol/device/gonk/CameraHardwareInterface.h new file mode 100644 index 00000000000..c60739cec88 --- /dev/null +++ b/netwerk/protocol/device/gonk/CameraHardwareInterface.h @@ -0,0 +1,267 @@ +/* + * 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 ANDROID_HARDWARE_CAMERA_HARDWARE_INTERFACE_H +#define ANDROID_HARDWARE_CAMERA_HARDWARE_INTERFACE_H + +#include +#include +#include +#include "Camera.h" +#include "CameraParameters.h" + +namespace android { + +class Overlay; + +/** + * The size of image for display. + */ +typedef struct image_rect_struct +{ + uint32_t width; /* Image width */ + uint32_t height; /* Image height */ +} image_rect_type; + + +typedef void (*notify_callback)(int32_t msgType, + int32_t ext1, + int32_t ext2, + void* user); + +typedef void (*data_callback)(int32_t msgType, + const sp& dataPtr, + void* user); + +typedef void (*data_callback_timestamp)(nsecs_t timestamp, + int32_t msgType, + const sp& dataPtr, + void* user); + +/** + * CameraHardwareInterface.h defines the interface to the + * camera hardware abstraction layer, used for setting and getting + * parameters, live previewing, and taking pictures. + * + * It is a referenced counted interface with RefBase as its base class. + * CameraService calls openCameraHardware() to retrieve a strong pointer to the + * instance of this interface and may be called multiple times. The + * following steps describe a typical sequence: + * + * -# After CameraService calls openCameraHardware(), getParameters() and + * setParameters() are used to initialize the camera instance. + * CameraService calls getPreviewHeap() to establish access to the + * preview heap so it can be registered with SurfaceFlinger for + * efficient display updating while in preview mode. + * -# startPreview() is called. The camera instance then periodically + * sends the message CAMERA_MSG_PREVIEW_FRAME (if enabled) each time + * a new preview frame is available. If data callback code needs to use + * this memory after returning, it must copy the data. + * + * Prior to taking a picture, CameraService calls autofocus(). When auto + * focusing has completed, the camera instance sends a CAMERA_MSG_FOCUS notification, + * which informs the application whether focusing was successful. The camera instance + * only sends this message once and it is up to the application to call autoFocus() + * again if refocusing is desired. + * + * CameraService calls takePicture() to request the camera instance take a + * picture. At this point, if a shutter, postview, raw, and/or compressed callback + * is desired, the corresponding message must be enabled. As with CAMERA_MSG_PREVIEW_FRAME, + * any memory provided in a data callback must be copied if it's needed after returning. + */ +class CameraHardwareInterface : public virtual RefBase { +public: + virtual ~CameraHardwareInterface() { } + + /** Return the IMemoryHeap for the preview image heap */ + virtual sp getPreviewHeap() const = 0; + + /** Return the IMemoryHeap for the raw image heap */ + virtual sp getRawHeap() const = 0; + + /** Set the notification and data callbacks */ + virtual void setCallbacks(notify_callback notify_cb, + data_callback data_cb, + data_callback_timestamp data_cb_timestamp, + void* user) = 0; + + /** + * The following three functions all take a msgtype, + * which is a bitmask of the messages defined in + * include/ui/Camera.h + */ + + /** + * Enable a message, or set of messages. + */ + virtual void enableMsgType(int32_t msgType) = 0; + + /** + * Disable a message, or a set of messages. + */ + virtual void disableMsgType(int32_t msgType) = 0; + + /** + * Query whether a message, or a set of messages, is enabled. + * Note that this is operates as an AND, if any of the messages + * queried are off, this will return false. + */ + virtual bool msgTypeEnabled(int32_t msgType) = 0; + + /** + * Start preview mode. + */ + virtual status_t startPreview() = 0; + +#ifdef USE_MAGURO_LIBCAMERA + /** + * Query the recording buffer information from HAL. + * This is needed because the opencore expects the buffer + * information before starting the recording. + */ + virtual status_t getBufferInfo(sp& Frame, size_t *alignedSize) = 0; + + /** + * Encode the YUV data. + */ + virtual void encodeData() = 0; +#endif + + /** + * Only used if overlays are used for camera preview. + */ + virtual bool useOverlay() {return false;} + virtual status_t setOverlay(const sp &overlay) {return BAD_VALUE;} + +#ifdef USE_GS2_LIBCAMERA + /** + * XXX Something in the binary blob but I don't know what it is to do. + */ + virtual void something() {} +#endif + + /** + * Stop a previously started preview. + */ + virtual void stopPreview() = 0; + + /** + * Returns true if preview is enabled. + */ + virtual bool previewEnabled() = 0; + + /** + * Start record mode. When a record image is available a CAMERA_MSG_VIDEO_FRAME + * message is sent with the corresponding frame. Every record frame must be released + * by calling releaseRecordingFrame(). + */ + virtual status_t startRecording() = 0; + + /** + * Stop a previously started recording. + */ + virtual void stopRecording() = 0; + + /** + * Returns true if recording is enabled. + */ + virtual bool recordingEnabled() = 0; + + /** + * Release a record frame previously returned by CAMERA_MSG_VIDEO_FRAME. + */ + virtual void releaseRecordingFrame(const sp& mem) = 0; + + /** + * Start auto focus, the notification callback routine is called + * with CAMERA_MSG_FOCUS once when focusing is complete. autoFocus() + * will be called again if another auto focus is needed. + */ + virtual status_t autoFocus() = 0; + + /** + * Cancels auto-focus function. If the auto-focus is still in progress, + * this function will cancel it. Whether the auto-focus is in progress + * or not, this function will return the focus position to the default. + * If the camera does not support auto-focus, this is a no-op. + */ + virtual status_t cancelAutoFocus() = 0; + + /** + * Take a picture. + */ + virtual status_t takePicture() = 0; + + /** + * Cancel a picture that was started with takePicture. Calling this + * method when no picture is being taken is a no-op. + */ + virtual status_t cancelPicture() = 0; + + /** + * Set the camera parameters. This returns BAD_VALUE if any parameter is + * invalid or not supported. */ + virtual status_t setParameters(const CameraParameters& params) = 0; + + /** Return the camera parameters. */ + virtual CameraParameters getParameters() const = 0; + + /** + * Send command to camera driver. + */ + virtual status_t sendCommand(int32_t cmd, int32_t arg1, int32_t arg2) = 0; + + /** + * Release the hardware resources owned by this object. Note that this is + * *not* done in the destructor. + */ + virtual void release() = 0; + + /** + * Dump state of the camera hardware + */ + virtual status_t dump(int fd, const Vector& args) const = 0; + +#ifdef USE_MAGURO_LIBCAMERA + /** + * Take a LiveSnapshot - Picture while recording + */ + virtual status_t takeLiveSnapshot() = 0; +#endif + +}; + +/** + * The functions need to be provided by the camera HAL. + * + * If getNumberOfCameras() returns N, the valid cameraId for getCameraInfo() + * and openCameraHardware() is 0 to N-1. + */ +extern "C" int HAL_getNumberOfCameras(); +extern "C" void HAL_getCameraInfo(int cameraId, struct CameraInfo* cameraInfo); +#ifdef USE_MAGURO_LIBCAMERA +/* HAL should return NULL if it fails to open camera hardware. */ +extern "C" sp HAL_openCameraHardware(int cameraId, int mode); +/* Returns whether the camera is in 3D mode or not */ +extern "C" int HAL_isIn3DMode(); +#else +/* HAL should return NULL if it fails to open camera hardware. */ +extern "C" sp HAL_openCameraHardware(int cameraId); +#endif + +}; // namespace android + +#endif diff --git a/netwerk/protocol/device/gonk/CameraParameters.h b/netwerk/protocol/device/gonk/CameraParameters.h new file mode 100644 index 00000000000..4e770fddefb --- /dev/null +++ b/netwerk/protocol/device/gonk/CameraParameters.h @@ -0,0 +1,405 @@ +/* + * 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 ANDROID_HARDWARE_CAMERA_PARAMETERS_H +#define ANDROID_HARDWARE_CAMERA_PARAMETERS_H + +#include +#include + +namespace android { + +struct Size { + int width; + int height; + + Size() { + width = 0; + height = 0; + } + + Size(int w, int h) { + width = w; + height = h; + } +}; + +class CameraParameters +{ +public: + CameraParameters(); + CameraParameters(const String8 ¶ms) { unflatten(params); } + ~CameraParameters(); + + String8 flatten() const; + void unflatten(const String8 ¶ms); + + void set(const char *key, const char *value); + void set(const char *key, int value); + void setFloat(const char *key, float value); + const char *get(const char *key) const; + int getInt(const char *key) const; + float getFloat(const char *key) const; + + void remove(const char *key); + + void setPreviewSize(int width, int height); + void getPreviewSize(int *width, int *height) const; + void getSupportedPreviewSizes(Vector &sizes) const; + void setPreviewFrameRate(int fps); + int getPreviewFrameRate() const; + void getPreviewFpsRange(int *min_fps, int *max_fps) const; + void setPreviewFormat(const char *format); + const char *getPreviewFormat() const; + void setPictureSize(int width, int height); + void getPictureSize(int *width, int *height) const; + void getSupportedPictureSizes(Vector &sizes) const; + void setPictureFormat(const char *format); + const char *getPictureFormat() const; + + void dump() const; + status_t dump(int fd, const Vector& args) const; + + // Parameter keys to communicate between camera application and driver. + // The access (read/write, read only, or write only) is viewed from the + // perspective of applications, not driver. + + // Preview frame size in pixels (width x height). + // Example value: "480x320". Read/Write. + static const char KEY_PREVIEW_SIZE[]; + // Supported preview frame sizes in pixels. + // Example value: "800x600,480x320". Read only. + static const char KEY_SUPPORTED_PREVIEW_SIZES[]; + // The current minimum and maximum preview fps. This controls the rate of + // preview frames received (CAMERA_MSG_PREVIEW_FRAME). The minimum and + // maximum fps must be one of the elements from + // KEY_SUPPORTED_PREVIEW_FPS_RANGE parameter. + // Example value: "10500,26623" + static const char KEY_PREVIEW_FPS_RANGE[]; + // The supported preview fps (frame-per-second) ranges. Each range contains + // a minimum fps and maximum fps. If minimum fps equals to maximum fps, the + // camera outputs frames in fixed frame rate. If not, the camera outputs + // frames in auto frame rate. The actual frame rate fluctuates between the + // minimum and the maximum. The list has at least one element. The list is + // sorted from small to large (first by maximum fps and then minimum fps). + // Example value: "(10500,26623),(15000,26623),(30000,30000)" + static const char KEY_SUPPORTED_PREVIEW_FPS_RANGE[]; + // The image format for preview frames. See CAMERA_MSG_PREVIEW_FRAME in + // frameworks/base/include/camera/Camera.h. + // Example value: "yuv420sp" or PIXEL_FORMAT_XXX constants. Read/write. + static const char KEY_PREVIEW_FORMAT[]; + // Supported image formats for preview frames. + // Example value: "yuv420sp,yuv422i-yuyv". Read only. + static const char KEY_SUPPORTED_PREVIEW_FORMATS[]; + // Number of preview frames per second. This is the target frame rate. The + // actual frame rate depends on the driver. + // Example value: "15". Read/write. + static const char KEY_PREVIEW_FRAME_RATE[]; + // Supported number of preview frames per second. + // Example value: "24,15,10". Read. + static const char KEY_SUPPORTED_PREVIEW_FRAME_RATES[]; + // The dimensions for captured pictures in pixels (width x height). + // Example value: "1024x768". Read/write. + static const char KEY_PICTURE_SIZE[]; + // Supported dimensions for captured pictures in pixels. + // Example value: "2048x1536,1024x768". Read only. + static const char KEY_SUPPORTED_PICTURE_SIZES[]; + // The image format for captured pictures. See CAMERA_MSG_COMPRESSED_IMAGE + // in frameworks/base/include/camera/Camera.h. + // Example value: "jpeg" or PIXEL_FORMAT_XXX constants. Read/write. + static const char KEY_PICTURE_FORMAT[]; + // Supported image formats for captured pictures. + // Example value: "jpeg,rgb565". Read only. + static const char KEY_SUPPORTED_PICTURE_FORMATS[]; + // The width (in pixels) of EXIF thumbnail in Jpeg picture. + // Example value: "512". Read/write. + static const char KEY_JPEG_THUMBNAIL_WIDTH[]; + // The height (in pixels) of EXIF thumbnail in Jpeg picture. + // Example value: "384". Read/write. + static const char KEY_JPEG_THUMBNAIL_HEIGHT[]; + // Supported EXIF thumbnail sizes (width x height). 0x0 means not thumbnail + // in EXIF. + // Example value: "512x384,320x240,0x0". Read only. + static const char KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES[]; + // The quality of the EXIF thumbnail in Jpeg picture. The range is 1 to 100, + // with 100 being the best. + // Example value: "90". Read/write. + static const char KEY_JPEG_THUMBNAIL_QUALITY[]; + // Jpeg quality of captured picture. The range is 1 to 100, with 100 being + // the best. + // Example value: "90". Read/write. + static const char KEY_JPEG_QUALITY[]; + // The rotation angle in degrees relative to the orientation of the camera. + // This affects the pictures returned from CAMERA_MSG_COMPRESSED_IMAGE. The + // camera driver may set orientation in the EXIF header without rotating the + // picture. Or the driver may rotate the picture and the EXIF thumbnail. If + // the Jpeg picture is rotated, the orientation in the EXIF header will be + // missing or 1 (row #0 is top and column #0 is left side). + // + // Note that the JPEG pictures of front-facing cameras are not mirrored + // as in preview display. + // + // For example, suppose the natural orientation of the device is portrait. + // The device is rotated 270 degrees clockwise, so the device orientation is + // 270. Suppose a back-facing camera sensor is mounted in landscape and the + // top side of the camera sensor is aligned with the right edge of the + // display in natural orientation. So the camera orientation is 90. The + // rotation should be set to 0 (270 + 90). + // + // Example value: "0" or "90" or "180" or "270". Write only. + static const char KEY_ROTATION[]; + // GPS latitude coordinate. GPSLatitude and GPSLatitudeRef will be stored in + // JPEG EXIF header. + // Example value: "25.032146" or "-33.462809". Write only. + static const char KEY_GPS_LATITUDE[]; + // GPS longitude coordinate. GPSLongitude and GPSLongitudeRef will be stored + // in JPEG EXIF header. + // Example value: "121.564448" or "-70.660286". Write only. + static const char KEY_GPS_LONGITUDE[]; + // GPS altitude. GPSAltitude and GPSAltitudeRef will be stored in JPEG EXIF + // header. + // Example value: "21.0" or "-5". Write only. + static const char KEY_GPS_ALTITUDE[]; + // GPS timestamp (UTC in seconds since January 1, 1970). This should be + // stored in JPEG EXIF header. + // Example value: "1251192757". Write only. + static const char KEY_GPS_TIMESTAMP[]; + // GPS Processing Method + // Example value: "GPS" or "NETWORK". Write only. + static const char KEY_GPS_PROCESSING_METHOD[]; + // Current white balance setting. + // Example value: "auto" or WHITE_BALANCE_XXX constants. Read/write. + static const char KEY_WHITE_BALANCE[]; + // Supported white balance settings. + // Example value: "auto,incandescent,daylight". Read only. + static const char KEY_SUPPORTED_WHITE_BALANCE[]; + // Current color effect setting. + // Example value: "none" or EFFECT_XXX constants. Read/write. + static const char KEY_EFFECT[]; + // Supported color effect settings. + // Example value: "none,mono,sepia". Read only. + static const char KEY_SUPPORTED_EFFECTS[]; + // Current antibanding setting. + // Example value: "auto" or ANTIBANDING_XXX constants. Read/write. + static const char KEY_ANTIBANDING[]; + // Supported antibanding settings. + // Example value: "auto,50hz,60hz,off". Read only. + static const char KEY_SUPPORTED_ANTIBANDING[]; + // Current scene mode. + // Example value: "auto" or SCENE_MODE_XXX constants. Read/write. + static const char KEY_SCENE_MODE[]; + // Supported scene mode settings. + // Example value: "auto,night,fireworks". Read only. + static const char KEY_SUPPORTED_SCENE_MODES[]; + // Current flash mode. + // Example value: "auto" or FLASH_MODE_XXX constants. Read/write. + static const char KEY_FLASH_MODE[]; + // Supported flash modes. + // Example value: "auto,on,off". Read only. + static const char KEY_SUPPORTED_FLASH_MODES[]; + // Current focus mode. This will not be empty. Applications should call + // CameraHardwareInterface.autoFocus to start the focus if focus mode is + // FOCUS_MODE_AUTO or FOCUS_MODE_MACRO. + // Example value: "auto" or FOCUS_MODE_XXX constants. Read/write. + static const char KEY_FOCUS_MODE[]; + // Supported focus modes. + // Example value: "auto,macro,fixed". Read only. + static const char KEY_SUPPORTED_FOCUS_MODES[]; + // Focal length in millimeter. + // Example value: "4.31". Read only. + static const char KEY_FOCAL_LENGTH[]; + // Horizontal angle of view in degrees. + // Example value: "54.8". Read only. + static const char KEY_HORIZONTAL_VIEW_ANGLE[]; + // Vertical angle of view in degrees. + // Example value: "42.5". Read only. + static const char KEY_VERTICAL_VIEW_ANGLE[]; + // Exposure compensation index. 0 means exposure is not adjusted. + // Example value: "0" or "5". Read/write. + static const char KEY_EXPOSURE_COMPENSATION[]; + // The maximum exposure compensation index (>=0). + // Example value: "6". Read only. + static const char KEY_MAX_EXPOSURE_COMPENSATION[]; + // The minimum exposure compensation index (<=0). + // Example value: "-6". Read only. + static const char KEY_MIN_EXPOSURE_COMPENSATION[]; + // The exposure compensation step. Exposure compensation index multiply by + // step eqals to EV. Ex: if exposure compensation index is 6 and step is + // 0.3333, EV is -2. + // Example value: "0.333333333" or "0.5". Read only. + static const char KEY_EXPOSURE_COMPENSATION_STEP[]; + // Current zoom value. + // Example value: "0" or "6". Read/write. + static const char KEY_ZOOM[]; + // Maximum zoom value. + // Example value: "6". Read only. + static const char KEY_MAX_ZOOM[]; + // The zoom ratios of all zoom values. The zoom ratio is in 1/100 + // increments. Ex: a zoom of 3.2x is returned as 320. The number of list + // elements is KEY_MAX_ZOOM + 1. The first element is always 100. The last + // element is the zoom ratio of zoom value KEY_MAX_ZOOM. + // Example value: "100,150,200,250,300,350,400". Read only. + static const char KEY_ZOOM_RATIOS[]; + // Whether zoom is supported. Zoom is supported if the value is "true". Zoom + // is not supported if the value is not "true" or the key does not exist. + // Example value: "true". Read only. + static const char KEY_ZOOM_SUPPORTED[]; + // Whether if smooth zoom is supported. Smooth zoom is supported if the + // value is "true". It is not supported if the value is not "true" or the + // key does not exist. + // See CAMERA_CMD_START_SMOOTH_ZOOM, CAMERA_CMD_STOP_SMOOTH_ZOOM, and + // CAMERA_MSG_ZOOM in frameworks/base/include/camera/Camera.h. + // Example value: "true". Read only. + static const char KEY_SMOOTH_ZOOM_SUPPORTED[]; + + // The distances (in meters) from the camera to where an object appears to + // be in focus. The object is sharpest at the optimal focus distance. The + // depth of field is the far focus distance minus near focus distance. + // + // Focus distances may change after starting auto focus, canceling auto + // focus, or starting the preview. Applications can read this anytime to get + // the latest focus distances. If the focus mode is FOCUS_MODE_CONTINUOUS, + // focus distances may change from time to time. + // + // This is intended to estimate the distance between the camera and the + // subject. After autofocus, the subject distance may be within near and far + // focus distance. However, the precision depends on the camera hardware, + // autofocus algorithm, the focus area, and the scene. The error can be + // large and it should be only used as a reference. + // + // Far focus distance > optimal focus distance > near focus distance. If + // the far focus distance is infinity, the value should be "Infinity" (case + // sensitive). The format is three float values separated by commas. The + // first is near focus distance. The second is optimal focus distance. The + // third is far focus distance. + // Example value: "0.95,1.9,Infinity" or "0.049,0.05,0.051". Read only. + static const char KEY_FOCUS_DISTANCES[]; + + // The image format for video frames. See CAMERA_MSG_VIDEO_FRAME in + // frameworks/base/include/camera/Camera.h. + // Example value: "yuv420sp" or PIXEL_FORMAT_XXX constants. Read only. + static const char KEY_VIDEO_FRAME_FORMAT[]; + + // Value for KEY_ZOOM_SUPPORTED or KEY_SMOOTH_ZOOM_SUPPORTED. + static const char TRUE[]; + + // Value for KEY_FOCUS_DISTANCES. + static const char FOCUS_DISTANCE_INFINITY[]; + + // Values for white balance settings. + static const char WHITE_BALANCE_AUTO[]; + static const char WHITE_BALANCE_INCANDESCENT[]; + static const char WHITE_BALANCE_FLUORESCENT[]; + static const char WHITE_BALANCE_WARM_FLUORESCENT[]; + static const char WHITE_BALANCE_DAYLIGHT[]; + static const char WHITE_BALANCE_CLOUDY_DAYLIGHT[]; + static const char WHITE_BALANCE_TWILIGHT[]; + static const char WHITE_BALANCE_SHADE[]; + + // Values for effect settings. + static const char EFFECT_NONE[]; + static const char EFFECT_MONO[]; + static const char EFFECT_NEGATIVE[]; + static const char EFFECT_SOLARIZE[]; + static const char EFFECT_SEPIA[]; + static const char EFFECT_POSTERIZE[]; + static const char EFFECT_WHITEBOARD[]; + static const char EFFECT_BLACKBOARD[]; + static const char EFFECT_AQUA[]; + + // Values for antibanding settings. + static const char ANTIBANDING_AUTO[]; + static const char ANTIBANDING_50HZ[]; + static const char ANTIBANDING_60HZ[]; + static const char ANTIBANDING_OFF[]; + + // Values for flash mode settings. + // Flash will not be fired. + static const char FLASH_MODE_OFF[]; + // Flash will be fired automatically when required. The flash may be fired + // during preview, auto-focus, or snapshot depending on the driver. + static const char FLASH_MODE_AUTO[]; + // Flash will always be fired during snapshot. The flash may also be + // fired during preview or auto-focus depending on the driver. + static const char FLASH_MODE_ON[]; + // Flash will be fired in red-eye reduction mode. + static const char FLASH_MODE_RED_EYE[]; + // Constant emission of light during preview, auto-focus and snapshot. + // This can also be used for video recording. + static const char FLASH_MODE_TORCH[]; + + // Values for scene mode settings. + static const char SCENE_MODE_AUTO[]; + static const char SCENE_MODE_ACTION[]; + static const char SCENE_MODE_PORTRAIT[]; + static const char SCENE_MODE_LANDSCAPE[]; + static const char SCENE_MODE_NIGHT[]; + static const char SCENE_MODE_NIGHT_PORTRAIT[]; + static const char SCENE_MODE_THEATRE[]; + static const char SCENE_MODE_BEACH[]; + static const char SCENE_MODE_SNOW[]; + static const char SCENE_MODE_SUNSET[]; + static const char SCENE_MODE_STEADYPHOTO[]; + static const char SCENE_MODE_FIREWORKS[]; + static const char SCENE_MODE_SPORTS[]; + static const char SCENE_MODE_PARTY[]; + static const char SCENE_MODE_CANDLELIGHT[]; + // Applications are looking for a barcode. Camera driver will be optimized + // for barcode reading. + static const char SCENE_MODE_BARCODE[]; + + // Formats for setPreviewFormat and setPictureFormat. + static const char PIXEL_FORMAT_YUV422SP[]; + static const char PIXEL_FORMAT_YUV420SP[]; // NV21 + static const char PIXEL_FORMAT_YUV422I[]; // YUY2 + static const char PIXEL_FORMAT_RGB565[]; + static const char PIXEL_FORMAT_JPEG[]; + + // Values for focus mode settings. + // Auto-focus mode. Applications should call + // CameraHardwareInterface.autoFocus to start the focus in this mode. + static const char FOCUS_MODE_AUTO[]; + // Focus is set at infinity. Applications should not call + // CameraHardwareInterface.autoFocus in this mode. + static const char FOCUS_MODE_INFINITY[]; + // Macro (close-up) focus mode. Applications should call + // CameraHardwareInterface.autoFocus to start the focus in this mode. + static const char FOCUS_MODE_MACRO[]; + // Focus is fixed. The camera is always in this mode if the focus is not + // adjustable. If the camera has auto-focus, this mode can fix the + // focus, which is usually at hyperfocal distance. Applications should + // not call CameraHardwareInterface.autoFocus in this mode. + static const char FOCUS_MODE_FIXED[]; + // Extended depth of field (EDOF). Focusing is done digitally and + // continuously. Applications should not call + // CameraHardwareInterface.autoFocus in this mode. + static const char FOCUS_MODE_EDOF[]; + // Continuous auto focus mode intended for video recording. The camera + // continuously tries to focus. This is ideal for shooting video. + // Applications still can call CameraHardwareInterface.takePicture in this + // mode but the subject may not be in focus. Auto focus starts when the + // parameter is set. Applications should not call + // CameraHardwareInterface.autoFocus in this mode. To stop continuous focus, + // applications should change the focus mode to other modes. + static const char FOCUS_MODE_CONTINUOUS_VIDEO[]; + +private: + DefaultKeyedVector mMap; +}; + +}; // namespace android + +#endif diff --git a/netwerk/protocol/device/gonk/ICameraClient.h b/netwerk/protocol/device/gonk/ICameraClient.h new file mode 100644 index 00000000000..236d0f65f3a --- /dev/null +++ b/netwerk/protocol/device/gonk/ICameraClient.h @@ -0,0 +1,51 @@ +/* + * 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 ANDROID_HARDWARE_ICAMERA_APP_H +#define ANDROID_HARDWARE_ICAMERA_APP_H + +#include +#include +#include +#include +#include + +namespace android { + +class ICameraClient: public IInterface +{ +public: + DECLARE_META_INTERFACE(CameraClient); + + virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) = 0; + virtual void dataCallback(int32_t msgType, const sp& data) = 0; + virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp& data) = 0; +}; + +// ---------------------------------------------------------------------------- + +class BnCameraClient: public BnInterface +{ +public: + virtual status_t onTransact( uint32_t code, + const Parcel& data, + Parcel* reply, + uint32_t flags = 0); +}; + +}; // namespace android + +#endif diff --git a/toolkit/crashreporter/google-breakpad/src/common/linux/Makefile.in b/toolkit/crashreporter/google-breakpad/src/common/linux/Makefile.in index 424614c2f15..6580d117837 100644 --- a/toolkit/crashreporter/google-breakpad/src/common/linux/Makefile.in +++ b/toolkit/crashreporter/google-breakpad/src/common/linux/Makefile.in @@ -56,6 +56,10 @@ ifneq (Android,$(OS_TARGET)) CPPSRCS += http_upload.cc endif +ifeq (gonk,$(MOZ_WIDGET_TOOLKIT)) +DEFINES += -DELFSIZE=32 +endif + HOST_CPPSRCS = \ dump_symbols.cc \ file_id.cc \ diff --git a/widget/gonk/Makefile.in b/widget/gonk/Makefile.in index 5b0cd944d99..f5209767c17 100644 --- a/widget/gonk/Makefile.in +++ b/widget/gonk/Makefile.in @@ -38,7 +38,10 @@ DEPTH = ../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ -VPATH = @srcdir@ +VPATH = \ + $(srcdir) \ + $(srcdir)/libui \ + $(NULL) include $(DEPTH)/config/autoconf.mk @@ -58,20 +61,35 @@ CPPSRCS = \ nsWindow.cpp \ nsLookAndFeel.cpp \ nsIdleServiceGonk.cpp \ + EventHub.cpp \ + InputApplication.cpp \ + InputDispatcher.cpp \ + InputListener.cpp \ + InputReader.cpp \ + InputTransport.cpp \ + InputWindow.cpp \ + Keyboard.cpp \ + KeyCharacterMap.cpp \ + KeyLayoutMap.cpp \ + PixelFormat.cpp \ + VirtualKeyMap.cpp \ $(NULL) SHARED_LIBRARY_LIBS = ../xpwidgets/libxpwidgets_s.a include $(topsrcdir)/config/rules.mk -DEFINES += -D_IMPL_NS_WIDGET +DEFINES += -D_IMPL_NS_WIDGET -DHAVE_OFF64_T LOCAL_INCLUDES += \ -I$(topsrcdir)/widget/xpwidgets \ -I$(topsrcdir)/widget/shared \ -I$(topsrcdir)/dom/system/android \ -I$(topsrcdir)/content/events/src \ + -I$(topsrcdir)/gfx/skia/include/core \ + -I$(topsrcdir)/gfx/skia/include/config \ -I$(srcdir) \ $(NULL) + include $(topsrcdir)/ipc/chromium/chromium-config.mk diff --git a/widget/gonk/libui/DisplayInfo.h b/widget/gonk/libui/DisplayInfo.h new file mode 100644 index 00000000000..5774ec0ef42 --- /dev/null +++ b/widget/gonk/libui/DisplayInfo.h @@ -0,0 +1,52 @@ +/* + * 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_UI_DISPLAY_INFO_H +#define ANDROID_UI_DISPLAY_INFO_H + +#include +#include + +#include "PixelFormat.h" + +namespace android { + +struct DisplayInfo { + uint32_t w; + uint32_t h; + PixelFormatInfo pixelFormatInfo; + uint8_t orientation; + uint8_t reserved[3]; + float fps; + float density; + float xdpi; + float ydpi; +}; + +/* Display orientations as defined in Surface.java and ISurfaceComposer.h. */ +enum { + DISPLAY_ORIENTATION_0 = 0, + DISPLAY_ORIENTATION_90 = 1, + DISPLAY_ORIENTATION_180 = 2, + DISPLAY_ORIENTATION_270 = 3 +}; + + +}; // namespace android + +#endif // ANDROID_COMPOSER_DISPLAY_INFO_H + diff --git a/widget/gonk/libui/EventHub.cpp b/widget/gonk/libui/EventHub.cpp new file mode 100644 index 00000000000..1212d7656f3 --- /dev/null +++ b/widget/gonk/libui/EventHub.cpp @@ -0,0 +1,1337 @@ +/* + * 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 "EventHub.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "KeyLayoutMap.h" +#include "KeyCharacterMap.h" +#include "VirtualKeyMap.h" + +#include +#include +#include + +#include +#include +#include +#include + +/* 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"; +} + +// --- 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) { + 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(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(-1), 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); + + mNumCpus = sysconf(_SC_NPROCESSORS_ONLN); + + 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); +} + +String8 EventHub::getDeviceName(int32_t deviceId) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == NULL) return String8(); + return device->identifier.name; +} + +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 && 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 && 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->keyMap.haveKeyLayout()) { + Vector 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 && 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 && 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 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, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const +{ + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + + if (device && device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->mapKey(scancode, outKeycode, outFlags); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + + if (mBuiltInKeyboardId != -1) { + device = getDeviceLocked(mBuiltInKeyboardId); + + if (device && device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->mapKey(scancode, outKeycode, outFlags); + if (err == NO_ERROR) { + return NO_ERROR; + } + } + } + + *outKeycode = 0; + *outFlags = 0; + return NAME_NOT_FOUND; +} + +status_t EventHub::mapAxis(int32_t deviceId, int 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; + } + } + + if (mBuiltInKeyboardId != -1) { + device = getDeviceLocked(mBuiltInKeyboardId); + + 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& 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 && 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& outVirtualKeys) const { + outVirtualKeys.clear(); + + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device && device->virtualKeyMap) { + outVirtualKeys.appendVector(device->virtualKeyMap->getVirtualKeys()); + } +} + +String8 EventHub::getKeyCharacterMapFile(int32_t deviceId) const { + AutoMutex _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device) { + return device->keyMap.keyCharacterMapFile; + } + return String8(); +} + +EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const { + if (deviceId == 0) { + 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 ? 0 : 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++) { + const struct input_event& iev = readBuffer[i]; + ALOGV("%s got: t0=%d, t1=%d, 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); + +#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); +#else + event->when = now; +#endif + event->deviceId = deviceId; + event->type = iev.type; + event->scanCode = iev.code; + event->value = iev.value; + event->keyCode = AKEYCODE_UNKNOWN; + event->flags = 0; + if (iev.type == EV_KEY && device->keyMap.haveKeyLayout()) { + status_t err = device->keyMap.keyLayoutMap->mapKey(iev.code, + &event->keyCode, &event->flags); + ALOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n", + iev.code, event->keyCode, event->flags, err); + } + event += 1; + } + capacity -= count; + 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 { + 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); + + // On an SMP system, it is possible for the framework to read input events + // faster than the kernel input device driver can produce a complete packet. + // Because poll() wakes up as soon as the first input event becomes available, + // the framework will often end up reading one event at a time until the + // packet is complete. Instead of one call to read() returning 71 events, + // it could take 71 calls to read() each returning 1 event. + // + // Sleep for a short period of time after waking up from the poll() to give + // the kernel time to finish writing the entire packet of input events. + if (mNumCpus > 1) { + usleep(250); + } + } + } + + // 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); + } +} + +// ---------------------------------------------------------------------------- + +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); + 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); + } + + // 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); + +#if 0 + ALOGI("add device %d: %s\n", deviceId, devicePath); + ALOGI(" bus: %04x\n" + " vendor %04x\n" + " product %04x\n" + " version %04x\n", + identifier.bus, identifier.vendor, identifier.product, identifier.version); + ALOGI(" name: \"%s\"\n", identifier.name.string()); + ALOGI(" location: \"%s\"\n", identifier.location.string()); + ALOGI(" unique id: \"%s\"\n", identifier.uniqueId.string()); + ALOGI(" driver: v%d.%d.%d\n", + driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff); +#endif + + // 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, 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; + } + } + + // 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 == -1 + && 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; + } + } + } + + // 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; + } + + ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, " + "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s", + deviceId, fd, devicePath, device->identifier.name.string(), + device->classes, + device->configurationFile.string(), + device->keyMap.keyLayoutFile.string(), + device->keyMap.keyCharacterMapFile.string(), + toString(mBuiltInKeyboardId == deviceId)); + + mDevices.add(deviceId, device); + + device->next = mOpeningDevices; + mOpeningDevices = device; + return 0; +} + +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 scanCodes; + device->keyMap.keyLayoutMap->findScanCodesForKey(keycode, &scanCodes); + const size_t N = scanCodes.size(); + for (size_t i=0; i= 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 = -1; + } + + 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 "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()); + } + } // 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 00000000000..0b1b110343b --- /dev/null +++ b/widget/gonk/libui/EventHub.h @@ -0,0 +1,377 @@ +/* + * 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 "Keyboard.h" +#include "KeyLayoutMap.h" +#include "KeyCharacterMap.h" +#include "VirtualKeyMap.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Convenience constants. */ + +#define BTN_FIRST 0x100 // first button scancode +#define BTN_LAST 0x15f // last button scancode + +namespace android { + +/* + * A raw event as retrieved from the EventHub. + */ +struct RawEvent { + nsecs_t when; + int32_t deviceId; + int32_t type; + int32_t scanCode; + int32_t keyCode; + int32_t value; + uint32_t flags; +}; + +/* 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 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 String8 getDeviceName(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, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const = 0; + + virtual status_t mapAxis(int32_t deviceId, int 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& 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& outVirtualKeys) const = 0; + + virtual String8 getKeyCharacterMapFile(int32_t deviceId) const = 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 String8 getDeviceName(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, int scancode, + int32_t* outKeycode, uint32_t* outFlags) const; + + virtual status_t mapAxis(int32_t deviceId, int scancode, + AxisInfo* outAxisInfo) const; + + virtual void setExcludedDevices(const Vector& 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& outVirtualKeys) const; + + virtual String8 getKeyCharacterMapFile(int32_t deviceId) const; + + virtual void requestReopenDevices(); + + virtual void wake(); + + virtual void dump(String8& dump); + virtual void monitor(); + +protected: + virtual ~EventHub(); + +private: + struct Device { + Device* next; + + int fd; + 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 propBitmask[(INPUT_PROP_MAX + 1) / 8]; + + String8 configurationFile; + PropertyMap* configuration; + VirtualKeyMap* virtualKeyMap; + KeyMap keyMap; + + Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier); + ~Device(); + + void close(); + }; + + status_t openDeviceLocked(const char *devicePath); + 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 -1 if none. + // EventHub remaps the built-in keyboard to id 0 externally as required by the API. + int32_t mBuiltInKeyboardId; + + int32_t mNextDeviceId; + + KeyedVector mDevices; + + Device *mOpeningDevices; + Device *mClosingDevices; + + bool mNeedToSendFinishedDeviceScan; + bool mNeedToReopenDevices; + bool mNeedToScanDevices; + Vector 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; + + // Set to the number of CPUs. + int32_t mNumCpus; +}; + +}; // namespace android + +#endif // _RUNTIME_EVENT_HUB_H diff --git a/widget/gonk/libui/Input.h b/widget/gonk/libui/Input.h new file mode 100644 index 00000000000..c2cbe1d6e87 --- /dev/null +++ b/widget/gonk/libui/Input.h @@ -0,0 +1,897 @@ +/* + * 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_H +#define _UI_INPUT_H + +/** + * Native input event structures. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ANDROID_OS +class SkMatrix; +#endif + +/* + * Additional private constants not defined in ndk/ui/input.h. + */ +enum { + /* 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 { + /* + * 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, +}; + +/* + * Describes the basic configuration of input devices that are present. + */ +struct InputConfiguration { + enum { + TOUCHSCREEN_UNDEFINED = 0, + TOUCHSCREEN_NOTOUCH = 1, + TOUCHSCREEN_STYLUS = 2, + TOUCHSCREEN_FINGER = 3 + }; + + enum { + KEYBOARD_UNDEFINED = 0, + KEYBOARD_NOKEYS = 1, + KEYBOARD_QWERTY = 2, + KEYBOARD_12KEY = 3 + }; + + enum { + NAVIGATION_UNDEFINED = 0, + NAVIGATION_NONAV = 1, + NAVIGATION_DPAD = 2, + NAVIGATION_TRACKBALL = 3, + NAVIGATION_WHEEL = 4 + }; + + int32_t touchScreen; + int32_t keyboard; + int32_t navigation; +}; + +/* + * 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 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 mPointerProperties; + Vector mSampleEventTimes; + Vector 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; +}; + +/* + * Calculates the velocity of pointer movements over time. + */ +class VelocityTracker { +public: + // Default polynomial degree. (used by getVelocity) + static const uint32_t DEFAULT_DEGREE = 2; + + // Default sample horizon. (used by getVelocity) + // We don't use too much history by default since we want to react to quick + // changes in direction. + static const nsecs_t DEFAULT_HORIZON = 100 * 1000000; // 100 ms + + struct Position { + float x, y; + }; + + struct Estimator { + static const size_t MAX_DEGREE = 2; + + // 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() { + degree = 0; + confidence = 0; + for (size_t i = 0; i <= MAX_DEGREE; i++) { + xCoeff[i] = 0; + yCoeff[i] = 0; + } + } + }; + + 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 a quadratic estimator for the 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, uint32_t degree, nsecs_t horizon, + 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 mMovements[mIndex].idBits; } + +private: + // Number of samples to keep. + static const uint32_t HISTORY_SIZE = 20; + + struct Movement { + nsecs_t eventTime; + BitSet32 idBits; + Position positions[MAX_POINTERS]; + + inline const Position& getPosition(uint32_t id) const { + return positions[idBits.getIndexOfBit(id)]; + } + }; + + uint32_t mIndex; + Movement mMovements[HISTORY_SIZE]; + int32_t mActivePointerId; +}; + + +/* + * 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; +}; + + +/* + * 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; + }; + + void initialize(int32_t id, const String8& name); + + inline int32_t getId() const { return mId; } + inline const String8 getName() const { return mName; } + 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); + void addMotionRange(const MotionRange& range); + + inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; } + inline int32_t getKeyboardType() const { return mKeyboardType; } + + inline void setKeyCharacterMapFile(const String8& value) { mKeyCharacterMapFile = value; } + inline const String8& getKeyCharacterMapFile() const { return mKeyCharacterMapFile; } + + inline const Vector& getMotionRanges() const { + return mMotionRanges; + } + +private: + int32_t mId; + String8 mName; + uint32_t mSources; + int32_t mKeyboardType; + String8 mKeyCharacterMapFile; + + Vector mMotionRanges; +}; + +/* + * Identifies a device. + */ +struct InputDeviceIdentifier { + inline InputDeviceIdentifier() : + bus(0), vendor(0), product(0), version(0) { + } + + String8 name; + String8 location; + String8 uniqueId; + uint16_t bus; + uint16_t vendor; + uint16_t product; + uint16_t version; +}; + +/* 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 // _UI_INPUT_H diff --git a/widget/gonk/libui/InputApplication.cpp b/widget/gonk/libui/InputApplication.cpp new file mode 100644 index 00000000000..a99e637fdf4 --- /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 + +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 00000000000..ba789559c97 --- /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 +#include +#include + +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(""); + } + + 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/InputDispatcher.cpp b/widget/gonk/libui/InputDispatcher.cpp new file mode 100644 index 00000000000..7a48a686af5 --- /dev/null +++ b/widget/gonk/libui/InputDispatcher.cpp @@ -0,0 +1,4770 @@ +/* + * 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 LOG_NDEBUG 0 + +// 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 batching. +#define DEBUG_BATCHING 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 performance statistics. +#define DEBUG_PERFORMANCE_STATISTICS 0 + +// Log debug messages about input event injection. +#define DEBUG_INJECTION 0 + +// Log debug messages about input event throttling. +#define DEBUG_THROTTLING 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 +#include + +#include +#include +#include +#include + +#define INDENT " " +#define INDENT2 " " + +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 + +// Motion samples that are received within this amount of time are simply coalesced +// when batched instead of being appended. This is done because some drivers update +// the location of pointers one at a time instead of all at once. +// For example, when there are 10 fingers down, the input dispatcher may receive 10 +// samples in quick succession with only one finger's location changed in each sample. +// +// This value effectively imposes an upper bound on the touch sampling rate. +// Touch sensors typically have a 50Hz - 200Hz sampling rate, so we expect distinct +// samples to become available 5-20ms apart but individual finger reports can trickle +// in over a period of 2-4ms or so. +// +// Empirical testing shows that a 2ms coalescing interval (500Hz) is not enough, +// a 3ms coalescing interval (333Hz) works well most of the time and doesn't introduce +// significant quantization noise on current hardware. +const nsecs_t MOTION_SAMPLE_COALESCE_INTERVAL = 3 * 1000000LL; // 3ms, 333Hz + + +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 void scalePointerCoords(const PointerCoords* inCoords, size_t count, float scaleFactor, + PointerCoords* outCoords) { + for (size_t i = 0; i < count; i++) { + outCoords[i] = inCoords[i]; + outCoords[i].scale(scaleFactor); + } +} + +static void dumpRegion(String8& dump, const SkRegion& region) { + if (region.isEmpty()) { + dump.append(""); + return; + } + +#ifdef HAVE_ANDROID_OS + 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); + } +#endif +} + + +// --- InputDispatcher --- + +InputDispatcher::InputDispatcher(const sp& policy) : + mPolicy(policy), + mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX), + mNextUnblockedEvent(NULL), + mDispatchEnabled(true), mDispatchFrozen(false), mInputFilterEnabled(false), + mCurrentInputTargetsValid(false), + mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) { + mLooper = new Looper(false); + + mKeyRepeatState.lastKeyEntry = NULL; + + policy->getDispatcherConfiguration(&mConfig); + + mThrottleState.minTimeBetweenEvents = 1000000000LL / mConfig.maxEventsPerSecond; + mThrottleState.lastDeviceId = -1; + +#if DEBUG_THROTTLING + mThrottleState.originalSampleCount = 0; + ALOGD("Throttling - Max events per second = %d", mConfig.maxEventsPerSecond); +#endif +} + +InputDispatcher::~InputDispatcher() { + { // acquire lock + AutoMutex _l(mLock); + + resetKeyRepeatLocked(); + releasePendingEventLocked(); + drainInboundQueueLocked(); + } + + while (mConnectionsByReceiveFd.size() != 0) { + unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel); + } +} + +void InputDispatcher::dispatchOnce() { + nsecs_t nextWakeupTime = LONG_LONG_MAX; + { // acquire lock + AutoMutex _l(mLock); + dispatchOnceInnerLocked(&nextWakeupTime); + + if (runCommandsLockedInterruptible()) { + nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately + } + } // 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) { + if (mActiveConnections.isEmpty()) { + dispatchIdleLocked(); + } + return; + } + } else { + // Inbound queue has at least one entry. + EventEntry* entry = mInboundQueue.head; + + // Throttle the entry if it is a move event and there are no + // other events behind it in the queue. Due to movement batching, additional + // samples may be appended to this event by the time the throttling timeout + // expires. + // TODO Make this smarter and consider throttling per device independently. + if (entry->type == EventEntry::TYPE_MOTION + && !isAppSwitchDue + && mDispatchEnabled + && (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) + && !entry->isInjected()) { + MotionEntry* motionEntry = static_cast(entry); + int32_t deviceId = motionEntry->deviceId; + uint32_t source = motionEntry->source; + if (! isAppSwitchDue + && !motionEntry->next // exactly one event, no successors + && (motionEntry->action == AMOTION_EVENT_ACTION_MOVE + || motionEntry->action == AMOTION_EVENT_ACTION_HOVER_MOVE) + && deviceId == mThrottleState.lastDeviceId + && source == mThrottleState.lastSource) { + nsecs_t nextTime = mThrottleState.lastEventTime + + mThrottleState.minTimeBetweenEvents; + if (currentTime < nextTime) { + // Throttle it! +#if DEBUG_THROTTLING + ALOGD("Throttling - Delaying motion event for " + "device %d, source 0x%08x by up to %0.3fms.", + deviceId, source, (nextTime - currentTime) * 0.000001); +#endif + if (nextTime < *nextWakeupTime) { + *nextWakeupTime = nextTime; + } + if (mThrottleState.originalSampleCount == 0) { + mThrottleState.originalSampleCount = + motionEntry->countSamples(); + } + return; + } + } + +#if DEBUG_THROTTLING + if (mThrottleState.originalSampleCount != 0) { + uint32_t count = motionEntry->countSamples(); + ALOGD("Throttling - Motion event sample count grew by %d from %d to %d.", + count - mThrottleState.originalSampleCount, + mThrottleState.originalSampleCount, count); + mThrottleState.originalSampleCount = 0; + } +#endif + + mThrottleState.lastEventTime = currentTime; + mThrottleState.lastDeviceId = deviceId; + mThrottleState.lastSource = source; + } + + mInboundQueue.dequeue(entry); + mPendingEvent = entry; + } + + // Poke user activity for this event. + if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) { + pokeUserActivityLocked(mPendingEvent); + } + } + + // 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(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(mPendingEvent); + done = dispatchDeviceResetLocked(currentTime, typedEntry); + dropReason = DROP_REASON_NOT_DROPPED; // device resets are never dropped + break; + } + + case EventEntry::TYPE_KEY: { + KeyEntry* typedEntry = static_cast(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(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 + } +} + +void InputDispatcher::dispatchIdleLocked() { +#if DEBUG_FOCUS + ALOGD("Dispatcher idle. There are no pending events or active connections."); +#endif + + // Reset targets when idle, to release input channels and other resources + // they are holding onto. + resetTargetsLocked(); +} + +bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) { + bool needWake = mInboundQueue.isEmpty(); + mInboundQueue.enqueueAtTail(entry); + + 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(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(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 x = int32_t(motionEntry->firstSample.pointerCoords[0]. + getAxisValue(AMOTION_EVENT_AXIS_X)); + int32_t y = int32_t(motionEntry->firstSample.pointerCoords[0]. + getAxisValue(AMOTION_EVENT_AXIS_Y)); + sp touchedWindowHandle = findTouchedWindowAtLocked(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 InputDispatcher::findTouchedWindowAtLocked(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 windowHandle = mWindowHandles.itemAt(i); + const InputWindowInfo* windowInfo = windowHandle->getInfo(); + 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(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; +} + +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::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); + } +} + +void InputDispatcher::releasePendingEventLocked() { + if (mPendingEvent) { + 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; + resetTargetsLocked(); + + 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) { + resetTargetsLocked(); + setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY + ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED); + return true; + } + + // Identify targets. + if (! mCurrentInputTargetsValid) { + int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime, + entry, nextWakeupTime); + if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { + return false; + } + + setInjectionResultLocked(entry, injectionResult); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return true; + } + + addMonitoringTargetsLocked(); + commitTargetsLocked(); + } + + // Dispatch the key. + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + 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; + resetTargetsLocked(); + + logOutboundMotionDetailsLocked("dispatchMotion - ", entry); + } + + // Clean up if dropping the event. + if (*dropReason != DROP_REASON_NOT_DROPPED) { + resetTargetsLocked(); + 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. + bool conflictingPointerActions = false; + if (! mCurrentInputTargetsValid) { + int32_t injectionResult; + const MotionSample* splitBatchAfterSample = NULL; + if (isPointerEvent) { + // Pointer event. (eg. touchscreen) + injectionResult = findTouchedWindowTargetsLocked(currentTime, + entry, nextWakeupTime, &conflictingPointerActions, &splitBatchAfterSample); + } else { + // Non touch event. (eg. trackball) + injectionResult = findFocusedWindowTargetsLocked(currentTime, + entry, nextWakeupTime); + } + if (injectionResult == INPUT_EVENT_INJECTION_PENDING) { + return false; + } + + setInjectionResultLocked(entry, injectionResult); + if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) { + return true; + } + + addMonitoringTargetsLocked(); + commitTargetsLocked(); + + // Unbatch the event if necessary by splitting it into two parts after the + // motion sample indicated by splitBatchAfterSample. + if (splitBatchAfterSample && splitBatchAfterSample->next) { +#if DEBUG_BATCHING + uint32_t originalSampleCount = entry->countSamples(); +#endif + MotionSample* nextSample = splitBatchAfterSample->next; + MotionEntry* nextEntry = new MotionEntry(nextSample->eventTime, + entry->deviceId, entry->source, entry->policyFlags, + entry->action, entry->flags, + entry->metaState, entry->buttonState, entry->edgeFlags, + entry->xPrecision, entry->yPrecision, entry->downTime, + entry->pointerCount, entry->pointerProperties, nextSample->pointerCoords); + if (nextSample != entry->lastSample) { + nextEntry->firstSample.next = nextSample->next; + nextEntry->lastSample = entry->lastSample; + } + delete nextSample; + + entry->lastSample = const_cast(splitBatchAfterSample); + entry->lastSample->next = NULL; + + if (entry->injectionState) { + nextEntry->injectionState = entry->injectionState; + entry->injectionState->refCount += 1; + } + +#if DEBUG_BATCHING + ALOGD("Split batch of %d samples into two parts, first part has %d samples, " + "second part has %d samples.", originalSampleCount, + entry->countSamples(), nextEntry->countSamples()); +#endif + + mInboundQueue.enqueueAtHead(nextEntry); + } + } + + // Dispatch the motion. + if (conflictingPointerActions) { + CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, + "conflicting pointer actions"); + synthesizeCancelationEventsForAllConnectionsLocked(options); + } + dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false); + 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); + + // Print the most recent sample that we have available, this may change due to batching. + size_t sampleCount = 1; + const MotionSample* sample = & entry->firstSample; + for (; sample->next != NULL; sample = sample->next) { + sampleCount += 1; + } + 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, + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), + sample->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)); + } + + // Keep in mind that due to batching, it is possible for the number of samples actually + // dispatched to change before the application finally consumed them. + if (entry->action == AMOTION_EVENT_ACTION_MOVE) { + ALOGD(" ... Total movement samples currently batched %d ...", sampleCount); + } +#endif +} + +void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime, + EventEntry* eventEntry, bool resumeWithAppendedMotionSample) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("dispatchEventToCurrentInputTargets - " + "resumeWithAppendedMotionSample=%s", + toString(resumeWithAppendedMotionSample)); +#endif + + ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true + + pokeUserActivityLocked(eventEntry); + + for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { + const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i); + + ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); + if (connectionIndex >= 0) { + sp connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget, + resumeWithAppendedMotionSample); + } 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 + } + } +} + +void InputDispatcher::resetTargetsLocked() { + mCurrentInputTargetsValid = false; + mCurrentInputTargets.clear(); + resetANRTimeoutsLocked(); +} + +void InputDispatcher::commitTargetsLocked() { + mCurrentInputTargetsValid = true; +} + +int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime, + const EventEntry* entry, + const sp& applicationHandle, + const sp& windowHandle, + nsecs_t* nextWakeupTime) { + 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."); +#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", + getApplicationWindowLabelLocked(applicationHandle, windowHandle).string()); +#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); + + // 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) { + if (newTimeout > 0) { + // Extend the timeout. + mInputTargetWaitTimeoutTime = now() + newTimeout; + } else { + // Give up. + mInputTargetWaitTimeoutExpired = true; + + // Release the touch targets. + mTouchState.reset(); + + // Input state will not be realistic. Mark it out of sync. + if (inputChannel.get()) { + ssize_t connectionIndex = getConnectionIndexLocked(inputChannel); + if (connectionIndex >= 0) { + sp connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + 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, nsecs_t* nextWakeupTime) { + mCurrentInputTargets.clear(); + + int32_t injectionResult; + + // If there is no currently focused window and no focused application + // then drop the event. + if (mFocusedWindowHandle == NULL) { + if (mFocusedApplicationHandle != NULL) { +#if DEBUG_FOCUS + ALOGD("Waiting because there is no focused window but there is a " + "focused application that may eventually add a window: %s.", + getApplicationWindowLabelLocked(mFocusedApplicationHandle, NULL).string()); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplicationHandle, NULL, nextWakeupTime); + 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) { +#if DEBUG_FOCUS + ALOGD("Waiting because focused window is paused."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime); + goto Unresponsive; + } + + // If the currently focused window is still working on previous events then keep waiting. + if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindowHandle)) { +#if DEBUG_FOCUS + ALOGD("Waiting because focused window still processing previous input."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime); + goto Unresponsive; + } + + // Success! Output targets. + injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED; + addWindowTargetLocked(mFocusedWindowHandle, + InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0)); + + // Done. +Failed: +Unresponsive: + nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime); + updateDispatchStatisticsLocked(currentTime, entry, + injectionResult, timeSpentWaitingForApplication); +#if DEBUG_FOCUS + ALOGD("findFocusedWindow finished: injectionResult=%d, " + "timeSpendWaitingForApplication=%0.1fms", + injectionResult, timeSpentWaitingForApplication / 1000000.0); +#endif + return injectionResult; +} + +int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, + const MotionEntry* entry, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions, + const MotionSample** outSplitBatchAfterSample) { + enum InjectionPermission { + INJECTION_PERMISSION_UNKNOWN, + INJECTION_PERMISSION_GRANTED, + INJECTION_PERMISSION_DENIED + }; + + mCurrentInputTargets.clear(); + + nsecs_t startTime = now(); + + // 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 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 newHoverWindowHandle; + + bool isSplit = mTouchState.split; + bool switchedDevice = mTouchState.deviceId >= 0 + && (mTouchState.deviceId != entry->deviceId + || mTouchState.source != entry->source); + 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; + 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. */ + + const MotionSample* sample = &entry->firstSample; + int32_t pointerIndex = getMotionEventActionPointerIndex(action); + int32_t x = int32_t(sample->pointerCoords[pointerIndex]. + getAxisValue(AMOTION_EVENT_AXIS_X)); + int32_t y = int32_t(sample->pointerCoords[pointerIndex]. + getAxisValue(AMOTION_EVENT_AXIS_Y)); + sp newTouchedWindowHandle; + sp 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 windowHandle = mWindowHandles.itemAt(i); + const InputWindowInfo* windowInfo = windowHandle->getInfo(); + 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) { +#if DEBUG_FOCUS + ALOGD("Waiting because system error window is pending."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, NULL, nextWakeupTime); + 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. + // Assign the pointer to the first foreground window we find. + // (May be NULL which is why we put this code block before the next check.) + newTouchedWindowHandle = mTempTouchState.getFirstForegroundWindowHandle(); + } + + // If we did not find a touched window then fail. + if (newTouchedWindowHandle == NULL) { + if (mFocusedApplicationHandle != NULL) { +#if DEBUG_FOCUS + ALOGD("Waiting because there is no touched window but there is a " + "focused application that may eventually add a new window: %s.", + getApplicationWindowLabelLocked(mFocusedApplicationHandle, NULL).string()); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + mFocusedApplicationHandle, NULL, nextWakeupTime); + goto Unresponsive; + } + + ALOGI("Dropping event because there is no touched window or focused application."); + 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; + + // Ensure all subsequent motion samples are also within the touched window. + // Set *outSplitBatchAfterSample to the sample before the first one that is not + // within the touched window. + if (!isTouchModal) { + while (sample->next) { + if (!newHoverWindowHandle->getInfo()->touchableRegionContainsPoint( + sample->next->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), + sample->next->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y))) { + *outSplitBatchAfterSample = sample; + break; + } + sample = sample->next; + } + } + } 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()) { + const MotionSample* sample = &entry->firstSample; + int32_t x = int32_t(sample->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X)); + int32_t y = int32_t(sample->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y)); + + sp oldTouchedWindowHandle = + mTempTouchState.getFirstForegroundWindowHandle(); + sp newTouchedWindowHandle = findTouchedWindowAtLocked(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); + + // Split the batch here so we send exactly one sample. + *outSplitBatchAfterSample = &entry->firstSample; + } + } + } + + if (newHoverWindowHandle != mLastHoverWindowHandle) { + // Split the batch here so we send exactly one sample as part of ENTER or EXIT. + *outSplitBatchAfterSample = &entry->firstSample; + + // 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 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 = 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) { +#if DEBUG_FOCUS + ALOGD("Waiting because touched window is paused."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, touchedWindow.windowHandle, nextWakeupTime); + goto Unresponsive; + } + + // If the touched window is still working on previous events then keep waiting. + if (! isWindowFinishedWithPreviousInputLocked(touchedWindow.windowHandle)) { +#if DEBUG_FOCUS + ALOGD("Waiting because touched window still processing previous input."); +#endif + injectionResult = handleTargetsNotReadyLocked(currentTime, entry, + NULL, touchedWindow.windowHandle, nextWakeupTime); + 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 foregroundWindowHandle = + mTempTouchState.getFirstForegroundWindowHandle(); + if (foregroundWindowHandle->getInfo()->hasWallpaper) { + for (size_t i = 0; i < mWindowHandles.size(); i++) { + sp windowHandle = mWindowHandles.itemAt(i); + if (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); + } + + // 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; + } + } 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& windowHandle, + int32_t targetFlags, BitSet32 pointerIds) { + mCurrentInputTargets.push(); + + const InputWindowInfo* windowInfo = windowHandle->getInfo(); + InputTarget& target = mCurrentInputTargets.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() { + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + mCurrentInputTargets.push(); + + InputTarget& target = mCurrentInputTargets.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& 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& windowHandle, int32_t x, int32_t y) const { + size_t numWindows = mWindowHandles.size(); + for (size_t i = 0; i < numWindows; i++) { + sp otherHandle = mWindowHandles.itemAt(i); + if (otherHandle == windowHandle) { + break; + } + + const InputWindowInfo* otherInfo = otherHandle->getInfo(); + if (otherInfo->visible && ! otherInfo->isTrustedOverlay() + && otherInfo->frameContainsPoint(x, y)) { + return true; + } + } + return false; +} + +bool InputDispatcher::isWindowFinishedWithPreviousInputLocked( + const sp& windowHandle) { + ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel()); + if (connectionIndex >= 0) { + sp connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + return connection->outboundQueue.isEmpty(); + } else { + return true; + } +} + +String8 InputDispatcher::getApplicationWindowLabelLocked( + const sp& applicationHandle, + const sp& 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(""); + } +} + +void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) { + int32_t eventType = POWER_MANAGER_OTHER_EVENT; + switch (eventEntry->type) { + case EventEntry::TYPE_MOTION: { + const MotionEntry* motionEntry = static_cast(eventEntry); + if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) { + return; + } + + if (MotionEvent::isTouchEvent(motionEntry->source, motionEntry->action)) { + eventType = POWER_MANAGER_TOUCH_EVENT; + } + break; + } + case EventEntry::TYPE_KEY: { + const KeyEntry* keyEntry = static_cast(eventEntry); + if (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) { + return; + } + eventType = POWER_MANAGER_BUTTON_EVENT; + break; + } + } + + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doPokeUserActivityLockedInterruptible); + commandEntry->eventTime = eventEntry->eventTime; + commandEntry->userActivityEventType = eventType; +} + +void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime, + const sp& connection, EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, " + "xOffset=%f, yOffset=%f, scaleFactor=%f, " + "pointerIds=0x%x, " + "resumeWithAppendedMotionSample=%s", + connection->getInputChannelName(), inputTarget->flags, + inputTarget->xOffset, inputTarget->yOffset, + inputTarget->scaleFactor, inputTarget->pointerIds.value, + toString(resumeWithAppendedMotionSample)); +#endif + + // Make sure we are never called for streaming when splitting across multiple windows. + bool isSplit = inputTarget->flags & InputTarget::FLAG_SPLIT; + ALOG_ASSERT(! (resumeWithAppendedMotionSample && isSplit)); + + // 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 (isSplit) { + ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION); + + MotionEntry* originalMotionEntry = static_cast(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 + eventEntry = splitMotionEntry; + } + } + + // Resume the dispatch cycle with a freshly appended motion sample. + // First we check that the last dispatch entry in the outbound queue is for the same + // motion event to which we appended the motion sample. If we find such a dispatch + // entry, and if it is currently in progress then we try to stream the new sample. + bool wasEmpty = connection->outboundQueue.isEmpty(); + + if (! wasEmpty && resumeWithAppendedMotionSample) { + DispatchEntry* motionEventDispatchEntry = + connection->findQueuedDispatchEntryForEvent(eventEntry); + if (motionEventDispatchEntry) { + // If the dispatch entry is not in progress, then we must be busy dispatching an + // earlier event. Not a problem, the motion event is on the outbound queue and will + // be dispatched later. + if (! motionEventDispatchEntry->inProgress) { +#if DEBUG_BATCHING + ALOGD("channel '%s' ~ Not streaming because the motion event has " + "not yet been dispatched. " + "(Waiting for earlier events to be consumed.)", + connection->getInputChannelName()); +#endif + return; + } + + // If the dispatch entry is in progress but it already has a tail of pending + // motion samples, then it must mean that the shared memory buffer filled up. + // Not a problem, when this dispatch cycle is finished, we will eventually start + // a new dispatch cycle to process the tail and that tail includes the newly + // appended motion sample. + if (motionEventDispatchEntry->tailMotionSample) { +#if DEBUG_BATCHING + ALOGD("channel '%s' ~ Not streaming because no new samples can " + "be appended to the motion event in this dispatch cycle. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); +#endif + return; + } + + // If the motion event was modified in flight, then we cannot stream the sample. + if ((motionEventDispatchEntry->targetFlags & InputTarget::FLAG_DISPATCH_MASK) + != InputTarget::FLAG_DISPATCH_AS_IS) { +#if DEBUG_BATCHING + ALOGD("channel '%s' ~ Not streaming because the motion event was not " + "being dispatched as-is. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); +#endif + return; + } + + // The dispatch entry is in progress and is still potentially open for streaming. + // Try to stream the new motion sample. This might fail if the consumer has already + // consumed the motion event (or if the channel is broken). + MotionEntry* motionEntry = static_cast(eventEntry); + MotionSample* appendedMotionSample = motionEntry->lastSample; + status_t status; + if (motionEventDispatchEntry->scaleFactor == 1.0f) { + status = connection->inputPublisher.appendMotionSample( + appendedMotionSample->eventTime, appendedMotionSample->pointerCoords); + } else { + PointerCoords scaledCoords[MAX_POINTERS]; + for (size_t i = 0; i < motionEntry->pointerCount; i++) { + scaledCoords[i] = appendedMotionSample->pointerCoords[i]; + scaledCoords[i].scale(motionEventDispatchEntry->scaleFactor); + } + status = connection->inputPublisher.appendMotionSample( + appendedMotionSample->eventTime, scaledCoords); + } + if (status == OK) { +#if DEBUG_BATCHING + ALOGD("channel '%s' ~ Successfully streamed new motion sample.", + connection->getInputChannelName()); +#endif + return; + } + +#if DEBUG_BATCHING + if (status == NO_MEMORY) { + ALOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event because the shared memory buffer is full. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); + } else if (status == status_t(FAILED_TRANSACTION)) { + ALOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event because the event has already been consumed. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName()); + } else { + ALOGD("channel '%s' ~ Could not append motion sample to currently " + "dispatched move event due to an error, status=%d. " + "(Waiting for next dispatch cycle to start.)", + connection->getInputChannelName(), status); + } +#endif + // Failed to stream. Start a new tail of pending motion samples to dispatch + // in the next cycle. + motionEventDispatchEntry->tailMotionSample = appendedMotionSample; + return; + } + } + + // Enqueue dispatch entries for the requested modes. + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_OUTSIDE); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_IS); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT); + enqueueDispatchEntryLocked(connection, eventEntry, inputTarget, + resumeWithAppendedMotionSample, InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER); + + // If the outbound queue was previously empty, start the dispatch cycle going. + if (wasEmpty && !connection->outboundQueue.isEmpty()) { + activateConnectionLocked(connection.get()); + startDispatchCycleLocked(currentTime, connection); + } +} + +void InputDispatcher::enqueueDispatchEntryLocked( + const sp& connection, EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample, 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); + if (dispatchEntry->hasForegroundTarget()) { + incrementPendingForegroundDispatchesLocked(eventEntry); + } + + // Handle the case where we could not stream a new motion sample because the consumer has + // already consumed the motion event (otherwise the corresponding dispatch entry would + // still be in the outbound queue for this connection). We set the head motion sample + // to the list starting with the newly appended motion sample. + if (resumeWithAppendedMotionSample) { +#if DEBUG_BATCHING + ALOGD("channel '%s' ~ Preparing a new dispatch cycle for additional motion samples " + "that cannot be streamed because the motion event has already been consumed.", + connection->getInputChannelName()); +#endif + MotionSample* appendedMotionSample = static_cast(eventEntry)->lastSample; + dispatchEntry->headMotionSample = appendedMotionSample; + } + + // Apply target flags and update the connection's input state. + switch (eventEntry->type) { + case EventEntry::TYPE_KEY: { + KeyEntry* keyEntry = static_cast(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 + return; // skip the inconsistent event + } + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* motionEntry = static_cast(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)) { +#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 + return; // skip the inconsistent event + } + break; + } + } + + // Enqueue the dispatch entry. + connection->outboundQueue.enqueueAtTail(dispatchEntry); +} + +void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime, + const sp& connection) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ startDispatchCycle", + connection->getInputChannelName()); +#endif + + ALOG_ASSERT(connection->status == Connection::STATUS_NORMAL); + ALOG_ASSERT(! connection->outboundQueue.isEmpty()); + + DispatchEntry* dispatchEntry = connection->outboundQueue.head; + ALOG_ASSERT(! dispatchEntry->inProgress); + + // Mark the dispatch entry as in progress. + dispatchEntry->inProgress = true; + + // Publish the event. + status_t status; + EventEntry* eventEntry = dispatchEntry->eventEntry; + switch (eventEntry->type) { + case EventEntry::TYPE_KEY: { + KeyEntry* keyEntry = static_cast(eventEntry); + + // Publish the key event. + status = connection->inputPublisher.publishKeyEvent( + keyEntry->deviceId, keyEntry->source, + dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags, + keyEntry->keyCode, keyEntry->scanCode, + keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime, + keyEntry->eventTime); + + if (status) { + ALOGE("channel '%s' ~ Could not publish key event, " + "status=%d", connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); + return; + } + break; + } + + case EventEntry::TYPE_MOTION: { + MotionEntry* motionEntry = static_cast(eventEntry); + + // If headMotionSample is non-NULL, then it points to the first new sample that we + // were unable to dispatch during the previous cycle so we resume dispatching from + // that point in the list of motion samples. + // Otherwise, we just start from the first sample of the motion event. + MotionSample* firstMotionSample = dispatchEntry->headMotionSample; + if (! firstMotionSample) { + firstMotionSample = & motionEntry->firstSample; + } + + PointerCoords scaledCoords[MAX_POINTERS]; + const PointerCoords* usingCoords = firstMotionSample->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] = firstMotionSample->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 and the first motion sample. + status = connection->inputPublisher.publishMotionEvent( + motionEntry->deviceId, motionEntry->source, + dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags, + motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState, + xOffset, yOffset, + motionEntry->xPrecision, motionEntry->yPrecision, + motionEntry->downTime, firstMotionSample->eventTime, + motionEntry->pointerCount, motionEntry->pointerProperties, + usingCoords); + + if (status) { + ALOGE("channel '%s' ~ Could not publish motion event, " + "status=%d", connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); + return; + } + + if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_MOVE + || dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) { + // Append additional motion samples. + MotionSample* nextMotionSample = firstMotionSample->next; + for (; nextMotionSample != NULL; nextMotionSample = nextMotionSample->next) { + if (usingCoords == scaledCoords) { + if (!(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) { + for (size_t i = 0; i < motionEntry->pointerCount; i++) { + scaledCoords[i] = nextMotionSample->pointerCoords[i]; + scaledCoords[i].scale(scaleFactor); + } + } + } else { + usingCoords = nextMotionSample->pointerCoords; + } + status = connection->inputPublisher.appendMotionSample( + nextMotionSample->eventTime, usingCoords); + if (status == NO_MEMORY) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ Shared memory buffer full. Some motion samples will " + "be sent in the next dispatch cycle.", + connection->getInputChannelName()); +#endif + break; + } + if (status != OK) { + ALOGE("channel '%s' ~ Could not append motion sample " + "for a reason other than out of memory, status=%d", + connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); + return; + } + } + + // Remember the next motion sample that we could not dispatch, in case we ran out + // of space in the shared memory buffer. + dispatchEntry->tailMotionSample = nextMotionSample; + } + break; + } + + default: { + ALOG_ASSERT(false); + } + } + + // Send the dispatch signal. + status = connection->inputPublisher.sendDispatchSignal(); + if (status) { + ALOGE("channel '%s' ~ Could not send dispatch signal, status=%d", + connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); + return; + } + + // Record information about the newly started dispatch cycle. + connection->lastEventTime = eventEntry->eventTime; + connection->lastDispatchTime = currentTime; + + // Notify other system components. + onDispatchCycleStartedLocked(currentTime, connection); +} + +void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime, + const sp& connection, bool handled) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ finishDispatchCycle - %01.1fms since event, " + "%01.1fms since dispatch, handled=%s", + connection->getInputChannelName(), + connection->getEventLatencyMillis(currentTime), + connection->getDispatchLatencyMillis(currentTime), + toString(handled)); +#endif + + if (connection->status == Connection::STATUS_BROKEN + || connection->status == Connection::STATUS_ZOMBIE) { + return; + } + + // Reset the publisher since the event has been consumed. + // We do this now so that the publisher can release some of its internal resources + // while waiting for the next dispatch cycle to begin. + status_t status = connection->inputPublisher.reset(); + if (status) { + ALOGE("channel '%s' ~ Could not reset publisher, status=%d", + connection->getInputChannelName(), status); + abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/); + return; + } + + // Notify other system components and prepare to start the next dispatch cycle. + onDispatchCycleFinishedLocked(currentTime, connection, handled); +} + +void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime, + const sp& connection) { + // Start the next dispatch cycle for this connection. + while (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.head; + if (dispatchEntry->inProgress) { + // Finish or resume current event in progress. + if (dispatchEntry->tailMotionSample) { + // We have a tail of undispatched motion samples. + // Reuse the same DispatchEntry and start a new cycle. + dispatchEntry->inProgress = false; + dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample; + dispatchEntry->tailMotionSample = NULL; + startDispatchCycleLocked(currentTime, connection); + return; + } + // Finished. + connection->outboundQueue.dequeueAtHead(); + if (dispatchEntry->hasForegroundTarget()) { + decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry); + } + delete dispatchEntry; + } else { + // If the head is not in progress, then we must have already dequeued the in + // progress event, which means we actually aborted it. + // So just start the next event for this connection. + startDispatchCycleLocked(currentTime, connection); + return; + } + } + + // Outbound queue is empty, deactivate the connection. + deactivateConnectionLocked(connection.get()); +} + +void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime, + const sp& connection, bool notify) { +#if DEBUG_DISPATCH_CYCLE + ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s", + connection->getInputChannelName(), toString(notify)); +#endif + + // Clear the outbound queue. + drainOutboundQueueLocked(connection.get()); + + // 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::drainOutboundQueueLocked(Connection* connection) { + while (! connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead(); + if (dispatchEntry->hasForegroundTarget()) { + decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry); + } + delete dispatchEntry; + } + + deactivateConnectionLocked(connection); +} + +int InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) { + InputDispatcher* d = static_cast(data); + + { // acquire lock + AutoMutex _l(d->mLock); + + ssize_t connectionIndex = d->mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + ALOGE("Received spurious receive callback for unknown input channel. " + "fd=%d, events=0x%x", receiveFd, events); + return 0; // remove the callback + } + + bool notify; + sp connection = d->mConnectionsByReceiveFd.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; + } + + bool handled = false; + status_t status = connection->inputPublisher.receiveFinishedSignal(&handled); + if (!status) { + nsecs_t currentTime = now(); + d->finishDispatchCycleLocked(currentTime, connection, handled); + d->runCommandsLockedInterruptible(); + return 1; + } + + ALOGE("channel '%s' ~ Failed to receive finished signal. status=%d", + connection->getInputChannelName(), status); + notify = true; + } 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 < mConnectionsByReceiveFd.size(); i++) { + synthesizeCancelationEventsForConnectionLocked( + mConnectionsByReceiveFd.valueAt(i), options); + } +} + +void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked( + const sp& channel, const CancelationOptions& options) { + ssize_t index = getConnectionIndexLocked(channel); + if (index >= 0) { + synthesizeCancelationEventsForConnectionLocked( + mConnectionsByReceiveFd.valueAt(index), options); + } +} + +void InputDispatcher::synthesizeCancelationEventsForConnectionLocked( + const sp& connection, const CancelationOptions& options) { + nsecs_t currentTime = now(); + + mTempCancelationEvents.clear(); + connection->inputState.synthesizeCancelationEvents(currentTime, + mTempCancelationEvents, options); + + if (! mTempCancelationEvents.isEmpty() + && connection->status != Connection::STATUS_BROKEN) { +#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(), mTempCancelationEvents.size(), + options.reason, options.mode); +#endif + for (size_t i = 0; i < mTempCancelationEvents.size(); i++) { + EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i); + switch (cancelationEventEntry->type) { + case EventEntry::TYPE_KEY: + logOutboundKeyDetailsLocked("cancel - ", + static_cast(cancelationEventEntry)); + break; + case EventEntry::TYPE_MOTION: + logOutboundMotionDetailsLocked("cancel - ", + static_cast(cancelationEventEntry)); + break; + } + + InputTarget target; + sp 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, false, InputTarget::FLAG_DISPATCH_AS_IS); + + cancelationEventEntry->release(); + } + + if (!connection->outboundQueue.head->inProgress) { + startDispatchCycleLocked(currentTime, connection); + } + } +} + +InputDispatcher::MotionEntry* +InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) { + ALOG_ASSERT(pointerIds.value != 0); + + uint32_t splitPointerIndexMap[MAX_POINTERS]; + 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)) { + splitPointerIndexMap[splitPointerCount] = originalPointerIndex; + splitPointerProperties[splitPointerCount].copyFrom(pointerProperties); + splitPointerCoords[splitPointerCount].copyFrom( + originalMotionEntry->firstSample.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, + splitPointerCount, splitPointerProperties, splitPointerCoords); + + for (MotionSample* originalMotionSample = originalMotionEntry->firstSample.next; + originalMotionSample != NULL; originalMotionSample = originalMotionSample->next) { + for (uint32_t splitPointerIndex = 0; splitPointerIndex < splitPointerCount; + splitPointerIndex++) { + uint32_t originalPointerIndex = splitPointerIndexMap[splitPointerIndex]; + splitPointerCoords[splitPointerIndex].copyFrom( + originalMotionSample->pointerCoords[originalPointerIndex]); + } + + splitMotionEntry->appendSample(originalMotionSample->eventTime, 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 (mInputFilterEnabled) { + 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(); + } +} + +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 (mInputFilterEnabled) { + 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(); + } + + // Attempt batching and streaming of move events. + if (args->action == AMOTION_EVENT_ACTION_MOVE + || args->action == AMOTION_EVENT_ACTION_HOVER_MOVE) { + // BATCHING CASE + // + // Try to append a move sample to the tail of the inbound queue for this device. + // Give up if we encounter a non-move motion event for this device since that + // means we cannot append any new samples until a new motion event has started. + for (EventEntry* entry = mInboundQueue.tail; entry; entry = entry->prev) { + if (entry->type != EventEntry::TYPE_MOTION) { + // Keep looking for motion events. + continue; + } + + MotionEntry* motionEntry = static_cast(entry); + if (motionEntry->deviceId != args->deviceId + || motionEntry->source != args->source) { + // Keep looking for this device and source. + continue; + } + + if (!motionEntry->canAppendSamples(args->action, + args->pointerCount, args->pointerProperties)) { + // Last motion event in the queue for this device and source is + // not compatible for appending new samples. Stop here. + goto NoBatchingOrStreaming; + } + + // Do the batching magic. + batchMotionLocked(motionEntry, args->eventTime, + args->metaState, args->pointerCoords, + "most recent motion event for this device and source in the inbound queue"); + mLock.unlock(); + return; // done! + } + + // BATCHING ONTO PENDING EVENT CASE + // + // Try to append a move sample to the currently pending event, if there is one. + // We can do this as long as we are still waiting to find the targets for the + // event. Once the targets are locked-in we can only do streaming. + if (mPendingEvent + && (!mPendingEvent->dispatchInProgress || !mCurrentInputTargetsValid) + && mPendingEvent->type == EventEntry::TYPE_MOTION) { + MotionEntry* motionEntry = static_cast(mPendingEvent); + if (motionEntry->deviceId == args->deviceId + && motionEntry->source == args->source) { + if (!motionEntry->canAppendSamples(args->action, + args->pointerCount, args->pointerProperties)) { + // Pending motion event is for this device and source but it is + // not compatible for appending new samples. Stop here. + goto NoBatchingOrStreaming; + } + + // Do the batching magic. + batchMotionLocked(motionEntry, args->eventTime, + args->metaState, args->pointerCoords, + "pending motion event"); + mLock.unlock(); + return; // done! + } + } + + // STREAMING CASE + // + // There is no pending motion event (of any kind) for this device in the inbound queue. + // Search the outbound queue for the current foreground targets to find a dispatched + // motion event that is still in progress. If found, then, appen the new sample to + // that event and push it out to all current targets. The logic in + // prepareDispatchCycleLocked takes care of the case where some targets may + // already have consumed the motion event by starting a new dispatch cycle if needed. + if (mCurrentInputTargetsValid) { + for (size_t i = 0; i < mCurrentInputTargets.size(); i++) { + const InputTarget& inputTarget = mCurrentInputTargets[i]; + if ((inputTarget.flags & InputTarget::FLAG_FOREGROUND) == 0) { + // Skip non-foreground targets. We only want to stream if there is at + // least one foreground target whose dispatch is still in progress. + continue; + } + + ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel); + if (connectionIndex < 0) { + // Connection must no longer be valid. + continue; + } + + sp connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + if (connection->outboundQueue.isEmpty()) { + // This foreground target has an empty outbound queue. + continue; + } + + DispatchEntry* dispatchEntry = connection->outboundQueue.head; + if (! dispatchEntry->inProgress + || dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION + || dispatchEntry->isSplit()) { + // No motion event is being dispatched, or it is being split across + // windows in which case we cannot stream. + continue; + } + + MotionEntry* motionEntry = static_cast( + dispatchEntry->eventEntry); + if (motionEntry->action != args->action + || motionEntry->deviceId != args->deviceId + || motionEntry->source != args->source + || motionEntry->pointerCount != args->pointerCount + || motionEntry->isInjected()) { + // The motion event is not compatible with this move. + continue; + } + + if (args->action == AMOTION_EVENT_ACTION_HOVER_MOVE) { + if (mLastHoverWindowHandle == NULL) { +#if DEBUG_BATCHING + ALOGD("Not streaming hover move because there is no " + "last hovered window."); +#endif + goto NoBatchingOrStreaming; + } + + sp hoverWindowHandle = findTouchedWindowAtLocked( + args->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), + args->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y)); + if (mLastHoverWindowHandle != hoverWindowHandle) { +#if DEBUG_BATCHING + ALOGD("Not streaming hover move because the last hovered window " + "is '%s' but the currently hovered window is '%s'.", + mLastHoverWindowHandle->getName().string(), + hoverWindowHandle != NULL + ? hoverWindowHandle->getName().string() : ""); +#endif + goto NoBatchingOrStreaming; + } + } + + // Hurray! This foreground target is currently dispatching a move event + // that we can stream onto. Append the motion sample and resume dispatch. + motionEntry->appendSample(args->eventTime, args->pointerCoords); +#if DEBUG_BATCHING + ALOGD("Appended motion sample onto batch for most recently dispatched " + "motion event for this device and source in the outbound queues. " + "Attempting to stream the motion sample."); +#endif + nsecs_t currentTime = now(); + dispatchEventToCurrentInputTargetsLocked(currentTime, motionEntry, + true /*resumeWithAppendedMotionSample*/); + + runCommandsLockedInterruptible(); + mLock.unlock(); + return; // done! + } + } + +NoBatchingOrStreaming:; + } + + // 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->pointerCount, args->pointerProperties, args->pointerCoords); + + needWake = enqueueInboundEventLocked(newEntry); + mLock.unlock(); + } // release lock + + if (needWake) { + mLooper->wake(); + } +} + +void InputDispatcher::batchMotionLocked(MotionEntry* entry, nsecs_t eventTime, + int32_t metaState, const PointerCoords* pointerCoords, const char* eventDescription) { + // Combine meta states. + entry->metaState |= metaState; + + // Coalesce this sample if not enough time has elapsed since the last sample was + // initially appended to the batch. + MotionSample* lastSample = entry->lastSample; + long interval = eventTime - lastSample->eventTimeBeforeCoalescing; + if (interval <= MOTION_SAMPLE_COALESCE_INTERVAL) { + uint32_t pointerCount = entry->pointerCount; + for (uint32_t i = 0; i < pointerCount; i++) { + lastSample->pointerCoords[i].copyFrom(pointerCoords[i]); + } + lastSample->eventTime = eventTime; +#if DEBUG_BATCHING + ALOGD("Coalesced motion into last sample of batch for %s, events were %0.3f ms apart", + eventDescription, interval * 0.000001f); +#endif + return; + } + + // Append the sample. + entry->appendSample(eventTime, pointerCoords); +#if DEBUG_BATCHING + ALOGD("Appended motion sample onto batch for %s, events were %0.3f ms apart", + eventDescription, interval * 0.000001f); +#endif +} + +void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) { +#if DEBUG_INBOUND_EVENT_DETAILS + ALOGD("notifySwitch - eventTime=%lld, policyFlags=0x%x, switchCode=%d, switchValue=%d", + args->eventTime, args->policyFlags, + args->switchCode, args->switchValue); +#endif + + uint32_t policyFlags = args->policyFlags; + policyFlags |= POLICY_FLAG_TRUSTED; + mPolicy->notifySwitch(args->eventTime, + args->switchCode, args->switchValue, 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* injectedEntry; + switch (event->getType()) { + case AINPUT_EVENT_TYPE_KEY: { + const KeyEvent* keyEvent = static_cast(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(); + injectedEntry = new KeyEntry(keyEvent->getEventTime(), + keyEvent->getDeviceId(), keyEvent->getSource(), + policyFlags, action, flags, + keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(), + keyEvent->getRepeatCount(), keyEvent->getDownTime()); + break; + } + + case AINPUT_EVENT_TYPE_MOTION: { + const MotionEvent* motionEvent = static_cast(event); + 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(); + MotionEntry* motionEntry = new MotionEntry(*sampleEventTimes, + motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags, + action, motionEvent->getFlags(), + motionEvent->getMetaState(), motionEvent->getButtonState(), + motionEvent->getEdgeFlags(), + motionEvent->getXPrecision(), motionEvent->getYPrecision(), + motionEvent->getDownTime(), uint32_t(pointerCount), + pointerProperties, samplePointerCoords); + for (size_t i = motionEvent->getHistorySize(); i > 0; i--) { + sampleEventTimes += 1; + samplePointerCoords += pointerCount; + motionEntry->appendSample(*sampleEventTimes, samplePointerCoords); + } + injectedEntry = motionEntry; + 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; + injectedEntry->injectionState = injectionState; + + bool needWake = enqueueInboundEventLocked(injectedEntry); + 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 InputDispatcher::getWindowHandleLocked( + const sp& inputChannel) const { + size_t numWindows = mWindowHandles.size(); + for (size_t i = 0; i < numWindows; i++) { + const sp& windowHandle = mWindowHandles.itemAt(i); + if (windowHandle->getInputChannel() == inputChannel) { + return windowHandle; + } + } + return NULL; +} + +bool InputDispatcher::hasWindowHandleLocked( + const sp& 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 >& inputWindowHandles) { +#if DEBUG_FOCUS + ALOGD("setInputWindows"); +#endif + { // acquire lock + AutoMutex _l(mLock); + + Vector > oldWindowHandles = mWindowHandles; + mWindowHandles = inputWindowHandles; + + sp newFocusedWindowHandle; + bool foundHoveredWindow = false; + for (size_t i = 0; i < mWindowHandles.size(); i++) { + const sp& 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 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 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& 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) { +#if DEBUG_FOCUS + ALOGD("setFocusedApplication"); +#endif + { // acquire lock + AutoMutex _l(mLock); + + if (inputApplicationHandle != NULL && inputApplicationHandle->updateInfo()) { + if (mFocusedApplicationHandle != inputApplicationHandle) { + if (mFocusedApplicationHandle != NULL) { + resetTargetsLocked(); + mFocusedApplicationHandle->releaseInfo(); + } + mFocusedApplicationHandle = inputApplicationHandle; + } + } else if (mFocusedApplicationHandle != NULL) { + resetTargetsLocked(); + 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& fromChannel, + const sp& toChannel) { +#if DEBUG_FOCUS + ALOGD("transferTouchFocus: fromChannel=%s, toChannel=%s", + fromChannel->getName().string(), toChannel->getName().string()); +#endif + { // acquire lock + AutoMutex _l(mLock); + + sp fromWindowHandle = getWindowHandleLocked(fromChannel); + sp 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; + } + + 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 fromConnection = mConnectionsByReceiveFd.valueAt(fromConnectionIndex); + sp toConnection = mConnectionsByReceiveFd.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(); + resetTargetsLocked(); + + 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: \n"); + } + dump.appendFormat(INDENT "FocusedWindow: name='%s'\n", + mFocusedWindowHandle != NULL ? mFocusedWindowHandle->getName().string() : ""); + + 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); + 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: \n"); + } + + if (!mWindowHandles.isEmpty()) { + dump.append(INDENT "Windows:\n"); + for (size_t i = 0; i < mWindowHandles.size(); i++) { + const sp& windowHandle = mWindowHandles.itemAt(i); + const InputWindowInfo* windowInfo = windowHandle->getInfo(); + + dump.appendFormat(INDENT2 "%d: name='%s', 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(), + 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); +#ifdef HAVE_ANDROID_OS + dumpRegion(dump, windowInfo->touchableRegion); +#endif + 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: \n"); + } + + if (!mMonitoringChannels.isEmpty()) { + dump.append(INDENT "MonitoringChannels:\n"); + for (size_t i = 0; i < mMonitoringChannels.size(); i++) { + const sp& channel = mMonitoringChannels[i]; + dump.appendFormat(INDENT2 "%d: '%s'\n", i, channel->getName().string()); + } + } else { + dump.append(INDENT "MonitoringChannels: \n"); + } + + dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count()); + + if (!mActiveConnections.isEmpty()) { + dump.append(INDENT "ActiveConnections:\n"); + for (size_t i = 0; i < mActiveConnections.size(); i++) { + const Connection* connection = mActiveConnections[i]; + dump.appendFormat(INDENT2 "%d: '%s', status=%s, outboundQueueLength=%u, " + "inputState.isNeutral=%s\n", + i, connection->getInputChannelName(), connection->getStatusLabel(), + connection->outboundQueue.count(), + toString(connection->inputState.isNeutral())); + } + } else { + dump.append(INDENT "ActiveConnections: \n"); + } + + if (isAppSwitchPendingLocked()) { + dump.appendFormat(INDENT "AppSwitch: pending, due in %01.1fms\n", + (mAppSwitchDueTime - now()) / 1000000.0); + } else { + dump.append(INDENT "AppSwitch: not pending\n"); + } +} + +status_t InputDispatcher::registerInputChannel(const sp& inputChannel, + const sp& 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 = new Connection(inputChannel, inputWindowHandle, monitor); + status_t status = connection->initialize(); + if (status) { + ALOGE("Failed to initialize input publisher for input channel '%s', status=%d", + inputChannel->getName().string(), status); + return status; + } + + int32_t receiveFd = inputChannel->getReceivePipeFd(); + mConnectionsByReceiveFd.add(receiveFd, connection); + + if (monitor) { + mMonitoringChannels.push(inputChannel); + } + + mLooper->addFd(receiveFd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this); + + runCommandsLockedInterruptible(); + } // release lock + return OK; +} + +status_t InputDispatcher::unregisterInputChannel(const sp& 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, + 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 = mConnectionsByReceiveFd.valueAt(connectionIndex); + mConnectionsByReceiveFd.removeItemsAt(connectionIndex); + + if (connection->monitor) { + removeMonitorChannelLocked(inputChannel); + } + + mLooper->removeFd(inputChannel->getReceivePipeFd()); + + nsecs_t currentTime = now(); + abortBrokenDispatchCycleLocked(currentTime, connection, notify); + + runCommandsLockedInterruptible(); + + connection->status = Connection::STATUS_ZOMBIE; + return OK; +} + +void InputDispatcher::removeMonitorChannelLocked(const sp& 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) { + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd()); + if (connectionIndex >= 0) { + sp connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + if (connection->inputChannel.get() == inputChannel.get()) { + return connectionIndex; + } + } + + return -1; +} + +void InputDispatcher::activateConnectionLocked(Connection* connection) { + for (size_t i = 0; i < mActiveConnections.size(); i++) { + if (mActiveConnections.itemAt(i) == connection) { + return; + } + } + mActiveConnections.add(connection); +} + +void InputDispatcher::deactivateConnectionLocked(Connection* connection) { + for (size_t i = 0; i < mActiveConnections.size(); i++) { + if (mActiveConnections.itemAt(i) == connection) { + mActiveConnections.removeAt(i); + return; + } + } +} + +void InputDispatcher::onDispatchCycleStartedLocked( + nsecs_t currentTime, const sp& connection) { +} + +void InputDispatcher::onDispatchCycleFinishedLocked( + nsecs_t currentTime, const sp& connection, bool handled) { + CommandEntry* commandEntry = postCommandLocked( + & InputDispatcher::doDispatchCycleFinishedLockedInterruptible); + commandEntry->connection = connection; + commandEntry->handled = handled; +} + +void InputDispatcher::onDispatchCycleBrokenLocked( + nsecs_t currentTime, const sp& 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& applicationHandle, + const sp& windowHandle, + nsecs_t eventTime, nsecs_t waitStartTime) { + ALOGI("Application is not responding: %s. " + "%01.1fms since event, %01.1fms since wait started", + getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(), + (currentTime - eventTime) / 1000000.0, + (currentTime - waitStartTime) / 1000000.0); + + 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 = 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 = commandEntry->connection; + bool handled = commandEntry->handled; + + bool skipNext = false; + if (!connection->outboundQueue.isEmpty()) { + DispatchEntry* dispatchEntry = connection->outboundQueue.head; + if (dispatchEntry->inProgress) { + if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) { + KeyEntry* keyEntry = static_cast(dispatchEntry->eventEntry); + skipNext = afterKeyEventLockedInterruptible(connection, + dispatchEntry, keyEntry, handled); + } else if (dispatchEntry->eventEntry->type == EventEntry::TYPE_MOTION) { + MotionEntry* motionEntry = static_cast(dispatchEntry->eventEntry); + skipNext = afterMotionEventLockedInterruptible(connection, + dispatchEntry, motionEntry, handled); + } + } + } + + if (!skipNext) { + startNextDispatchCycleLocked(now(), connection); + } +} + +bool InputDispatcher::afterKeyEventLockedInterruptible(const sp& 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) { + 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", + originalKeyCode, keyEntry->action, keyEntry->repeatCount); +#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", + keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount); +#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 true; // skip next cycle + } + + ALOG_ASSERT(connection->outboundQueue.head == dispatchEntry); + + // 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& 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 + + dispatchEntry->inProgress = false; + startDispatchCycleLocked(now(), connection); + return true; // already started next cycle + } else { +#if DEBUG_OUTBOUND_EVENT_DETAILS + ALOGD("Unhandled key event: No fallback key."); +#endif + } + } + } + return false; +} + +bool InputDispatcher::afterMotionEventLockedInterruptible(const sp& 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::dump(String8& dump) { + AutoMutex _l(mLock); + + dump.append("Input Dispatcher State:\n"); + dumpDispatchStateLocked(dump); + + dump.append(INDENT "Configuration:\n"); + dump.appendFormat(INDENT2 "MaxEventsPerSecond: %d\n", mConfig.maxEventsPerSecond); + dump.appendFormat(INDENT2 "KeyRepeatDelay: %0.1fms\n", mConfig.keyRepeatDelay * 0.000001f); + dump.appendFormat(INDENT2 "KeyRepeatTimeout: %0.1fms\n", mConfig.keyRepeatTimeout * 0.000001f); +} + +void InputDispatcher::monitor() { + // Acquire and release the lock to ensure that the dispatcher has not deadlocked. + mLock.lock(); + mLock.unlock(); +} + + +// --- InputDispatcher::Queue --- + +template +uint32_t InputDispatcher::Queue::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() { +} + + +// --- InputDispatcher::DeviceResetEntry --- + +InputDispatcher::DeviceResetEntry::DeviceResetEntry(nsecs_t eventTime, int32_t deviceId) : + EventEntry(TYPE_DEVICE_RESET, eventTime, 0), + deviceId(deviceId) { +} + +InputDispatcher::DeviceResetEntry::~DeviceResetEntry() { +} + + +// --- 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::recycle() { + releaseInjectionState(); + + dispatchInProgress = false; + syntheticRepeat = false; + interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN; + interceptKeyWakeupTime = 0; +} + + +// --- InputDispatcher::MotionSample --- + +InputDispatcher::MotionSample::MotionSample(nsecs_t eventTime, + const PointerCoords* pointerCoords, uint32_t pointerCount) : + next(NULL), eventTime(eventTime), eventTimeBeforeCoalescing(eventTime) { + for (uint32_t i = 0; i < pointerCount; i++) { + this->pointerCoords[i].copyFrom(pointerCoords[i]); + } +} + + +// --- 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, uint32_t pointerCount, + const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) : + EventEntry(TYPE_MOTION, eventTime, policyFlags), + deviceId(deviceId), source(source), action(action), flags(flags), + metaState(metaState), buttonState(buttonState), edgeFlags(edgeFlags), + xPrecision(xPrecision), yPrecision(yPrecision), + downTime(downTime), pointerCount(pointerCount), + firstSample(eventTime, pointerCoords, pointerCount), + lastSample(&firstSample) { + for (uint32_t i = 0; i < pointerCount; i++) { + this->pointerProperties[i].copyFrom(pointerProperties[i]); + } +} + +InputDispatcher::MotionEntry::~MotionEntry() { + for (MotionSample* sample = firstSample.next; sample != NULL; ) { + MotionSample* next = sample->next; + delete sample; + sample = next; + } +} + +uint32_t InputDispatcher::MotionEntry::countSamples() const { + uint32_t count = 1; + for (MotionSample* sample = firstSample.next; sample != NULL; sample = sample->next) { + count += 1; + } + return count; +} + +bool InputDispatcher::MotionEntry::canAppendSamples(int32_t action, uint32_t pointerCount, + const PointerProperties* pointerProperties) const { + if (this->action != action + || this->pointerCount != pointerCount + || this->isInjected()) { + return false; + } + for (uint32_t i = 0; i < pointerCount; i++) { + if (this->pointerProperties[i] != pointerProperties[i]) { + return false; + } + } + return true; +} + +void InputDispatcher::MotionEntry::appendSample( + nsecs_t eventTime, const PointerCoords* pointerCoords) { + MotionSample* sample = new MotionSample(eventTime, pointerCoords, pointerCount); + + lastSample->next = sample; + lastSample = sample; +} + + +// --- InputDispatcher::DispatchEntry --- + +InputDispatcher::DispatchEntry::DispatchEntry(EventEntry* eventEntry, + int32_t targetFlags, float xOffset, float yOffset, float scaleFactor) : + eventEntry(eventEntry), targetFlags(targetFlags), + xOffset(xOffset), yOffset(yOffset), scaleFactor(scaleFactor), + inProgress(false), + resolvedAction(0), resolvedFlags(0), + headMotionSample(NULL), tailMotionSample(NULL) { + eventEntry->refCount += 1; +} + +InputDispatcher::DispatchEntry::~DispatchEntry() { + eventEntry->release(); +} + + +// --- 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) const { + for (size_t i = 0; i < mMotionMementos.size(); i++) { + const MotionMemento& memento = mMotionMementos.itemAt(i); + if (memento.deviceId == deviceId + && memento.source == source + && 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.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.flags = flags; + memento.downTime = entry->downTime; +} + +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.setPointers(entry); + memento.hovering = hovering; +} + +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->lastSample->pointerCoords[i]); + } +} + +void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime, + Vector& 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, 0, + AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED, + memento.keyCode, memento.scanCode, 0, 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, 0, + memento.hovering + ? AMOTION_EVENT_ACTION_HOVER_EXIT + : AMOTION_EVENT_ACTION_CANCEL, + memento.flags, 0, 0, 0, + memento.xPrecision, memento.yPrecision, memento.downTime, + 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) { + 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, + const sp& inputWindowHandle, bool monitor) : + status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle), + monitor(monitor), + inputPublisher(inputChannel), + lastEventTime(LONG_LONG_MAX), lastDispatchTime(LONG_LONG_MAX) { +} + +InputDispatcher::Connection::~Connection() { +} + +status_t InputDispatcher::Connection::initialize() { + return inputPublisher.initialize(); +} + +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::findQueuedDispatchEntryForEvent( + const EventEntry* eventEntry) const { + for (DispatchEntry* dispatchEntry = outboundQueue.tail; dispatchEntry; + dispatchEntry = dispatchEntry->prev) { + if (dispatchEntry->eventEntry == eventEntry) { + return dispatchEntry; + } + } + return NULL; +} + + +// --- InputDispatcher::CommandEntry --- + +InputDispatcher::CommandEntry::CommandEntry(Command command) : + command(command), eventTime(0), keyEntry(NULL), userActivityEventType(0), handled(false) { +} + +InputDispatcher::CommandEntry::~CommandEntry() { +} + + +// --- InputDispatcher::TouchState --- + +InputDispatcher::TouchState::TouchState() : + down(false), split(false), deviceId(-1), source(0) { +} + +InputDispatcher::TouchState::~TouchState() { +} + +void InputDispatcher::TouchState::reset() { + down = false; + split = false; + deviceId = -1; + source = 0; + windows.clear(); +} + +void InputDispatcher::TouchState::copyFrom(const TouchState& other) { + down = other.down; + split = other.split; + deviceId = other.deviceId; + source = other.source; + windows = other.windows; +} + +void InputDispatcher::TouchState::addOrUpdateWindow(const sp& 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::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 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& 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 00000000000..832e180e0e5 --- /dev/null +++ b/widget/gonk/libui/InputDispatcher.h @@ -0,0 +1,1154 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; + + // 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. + */ +struct InputDispatcherConfiguration { + // The key repeat initial timeout. + nsecs_t keyRepeatTimeout; + + // The key repeat inter-key delay. + nsecs_t keyRepeatDelay; + + // The maximum suggested event delivery rate per second. + // This value is used to throttle motion event movement actions on a per-device + // basis. It is not intended to be a hard limit. + int32_t maxEventsPerSecond; + + InputDispatcherConfiguration() : + keyRepeatTimeout(500 * 1000000LL), + keyRepeatDelay(50 * 1000000LL), + maxEventsPerSecond(60) { } +}; + + +/* + * 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, + const sp& inputWindowHandle) = 0; + + /* Notifies the system that an input channel is unrecoverably broken. */ + virtual void notifyInputChannelBroken(const sp& 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, + 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, + const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0; + + /* Notifies the policy about switch events. + */ + virtual void notifySwitch(nsecs_t when, + int32_t switchCode, int32_t switchValue, 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 >& 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) = 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& fromChannel, + const sp& 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, + const sp& inputWindowHandle, bool monitor) = 0; + virtual status_t unregisterInputChannel(const sp& 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& 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 >& inputWindowHandles); + virtual void setFocusedApplication(const sp& inputApplicationHandle); + virtual void setInputDispatchMode(bool enabled, bool frozen); + virtual void setInputFilterEnabled(bool enabled); + + virtual bool transferTouchFocus(const sp& fromChannel, + const sp& toChannel); + + virtual status_t registerInputChannel(const sp& inputChannel, + const sp& inputWindowHandle, bool monitor); + virtual status_t unregisterInputChannel(const sp& inputChannel); + +private: + template + struct Link { + T* next; + T* prev; + }; + + 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 { + 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(); + + protected: + EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags); + virtual ~EventEntry(); + void releaseInjectionState(); + }; + + struct ConfigurationChangedEntry : EventEntry { + ConfigurationChangedEntry(nsecs_t eventTime); + + protected: + virtual ~ConfigurationChangedEntry(); + }; + + struct DeviceResetEntry : EventEntry { + int32_t deviceId; + + DeviceResetEntry(nsecs_t eventTime, int32_t deviceId); + + 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); + void recycle(); + + protected: + virtual ~KeyEntry(); + }; + + struct MotionSample { + MotionSample* next; + + nsecs_t eventTime; // may be updated during coalescing + nsecs_t eventTimeBeforeCoalescing; // not updated during coalescing + PointerCoords pointerCoords[MAX_POINTERS]; + + MotionSample(nsecs_t eventTime, const PointerCoords* pointerCoords, + uint32_t pointerCount); + }; + + struct MotionEntry : EventEntry { + 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; + uint32_t pointerCount; + PointerProperties pointerProperties[MAX_POINTERS]; + + // Linked list of motion samples associated with this motion event. + MotionSample firstSample; + MotionSample* lastSample; + + 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, uint32_t pointerCount, + const PointerProperties* pointerProperties, const PointerCoords* pointerCoords); + + uint32_t countSamples() const; + + // Checks whether we can append samples, assuming the device id and source are the same. + bool canAppendSamples(int32_t action, uint32_t pointerCount, + const PointerProperties* pointerProperties) const; + + void appendSample(nsecs_t eventTime, const PointerCoords* pointerCoords); + + protected: + virtual ~MotionEntry(); + }; + + // Tracks the progress of dispatching a particular event to a particular connection. + struct DispatchEntry : Link { + EventEntry* eventEntry; // the event to dispatch + int32_t targetFlags; + float xOffset; + float yOffset; + float scaleFactor; + + // True if dispatch has started. + bool inProgress; + + // Set to the resolved action and flags when the event is enqueued. + int32_t resolvedAction; + int32_t resolvedFlags; + + // For motion events: + // Pointer to the first motion sample to dispatch in this cycle. + // Usually NULL to indicate that the list of motion samples begins at + // MotionEntry::firstSample. Otherwise, some samples were dispatched in a previous + // cycle and this pointer indicates the location of the first remainining sample + // to dispatch during the current cycle. + MotionSample* headMotionSample; + // Pointer to a motion sample to dispatch in the next cycle if the dispatcher was + // unable to send all motion samples during this cycle. On the next cycle, + // headMotionSample will be initialized to tailMotionSample and tailMotionSample + // will be set to NULL. + MotionSample* tailMotionSample; + + 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; + } + }; + + // 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(Command command); + ~CommandEntry(); + + Command command; + + // parameters for the command (usage varies by command) + sp connection; + nsecs_t eventTime; + KeyEntry* keyEntry; + sp inputApplicationHandle; + sp inputWindowHandle; + int32_t userActivityEventType; + bool handled; + }; + + // Generic queue implementation. + template + 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) 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& 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& getFallbackKeys() const { + return mFallbackKeys; + } + + private: + struct KeyMemento { + int32_t deviceId; + uint32_t source; + int32_t keyCode; + int32_t scanCode; + int32_t flags; + nsecs_t downTime; + }; + + struct MotionMemento { + int32_t deviceId; + uint32_t source; + int32_t flags; + float xPrecision; + float yPrecision; + nsecs_t downTime; + uint32_t pointerCount; + PointerProperties pointerProperties[MAX_POINTERS]; + PointerCoords pointerCoords[MAX_POINTERS]; + bool hovering; + + void setPointers(const MotionEntry* entry); + }; + + Vector mKeyMementos; + Vector mMotionMementos; + KeyedVector 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; // never null + sp inputWindowHandle; // may be null + bool monitor; + InputPublisher inputPublisher; + InputState inputState; + Queue outboundQueue; + + nsecs_t lastEventTime; // the time when the event was originally captured + nsecs_t lastDispatchTime; // the time when the last event was dispatched + + explicit Connection(const sp& inputChannel, + const sp& inputWindowHandle, bool monitor); + + inline const char* getInputChannelName() const { return inputChannel->getName().string(); } + + const char* getStatusLabel() const; + + // Finds a DispatchEntry in the outbound queue associated with the specified event. + // Returns NULL if not found. + DispatchEntry* findQueuedDispatchEntryForEvent(const EventEntry* eventEntry) const; + + // Gets the time since the current event was originally obtained from the input driver. + inline double getEventLatencyMillis(nsecs_t currentTime) const { + return (currentTime - lastEventTime) / 1000000.0; + } + + // Gets the time since the current event entered the outbound dispatch queue. + inline double getDispatchLatencyMillis(nsecs_t currentTime) const { + return (currentTime - lastDispatchTime) / 1000000.0; + } + + status_t initialize(); + }; + + 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 mPolicy; + InputDispatcherConfiguration mConfig; + + Mutex mLock; + + sp mLooper; + + EventEntry* mPendingEvent; + Queue mInboundQueue; + Queue mCommandQueue; + + Vector mTempCancelationEvents; + + void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime); + void dispatchIdleLocked(); + + // Batches a new sample onto a motion entry. + // Assumes that the we have already checked that we can append samples. + void batchMotionLocked(MotionEntry* entry, nsecs_t eventTime, int32_t metaState, + const PointerCoords* pointerCoords, const char* eventDescription); + + // 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 findTouchedWindowAtLocked(int32_t x, int32_t y); + + // All registered connections mapped by receive pipe file descriptor. + KeyedVector > mConnectionsByReceiveFd; + + ssize_t getConnectionIndexLocked(const sp& inputChannel); + + // Active connections are connections that have a non-empty outbound queue. + // We don't use a ref-counted pointer here because we explicitly abort connections + // during unregistration which causes the connection's outbound queue to be cleared + // and the connection itself to be deactivated. + Vector mActiveConnections; + + // Input channels that will receive a copy of all input events. + Vector > 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); + + // Throttling state. + struct ThrottleState { + nsecs_t minTimeBetweenEvents; + + nsecs_t lastEventTime; + int32_t lastDeviceId; + uint32_t lastSource; + + uint32_t originalSampleCount; // only collected during debugging + } mThrottleState; + + // 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 runCommandsLockedInterruptible(); + CommandEntry* postCommandLocked(Command command); + + // Inbound event processing. + void drainInboundQueueLocked(); + void releasePendingEventLocked(); + void releaseInboundEventLocked(EventEntry* entry); + + // Dispatch state. + bool mDispatchEnabled; + bool mDispatchFrozen; + bool mInputFilterEnabled; + + Vector > mWindowHandles; + + sp getWindowHandleLocked(const sp& inputChannel) const; + bool hasWindowHandleLocked(const sp& windowHandle) const; + + // Focus tracking for keys, trackball, etc. + sp mFocusedWindowHandle; + + // Focus tracking for touch. + struct TouchedWindow { + sp 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 + Vector windows; + + TouchState(); + ~TouchState(); + void reset(); + void copyFrom(const TouchState& other); + void addOrUpdateWindow(const sp& windowHandle, + int32_t targetFlags, BitSet32 pointerIds); + void filterNonAsIsTouchWindows(); + sp getFirstForegroundWindowHandle() const; + bool isSlippery() const; + }; + + TouchState mTouchState; + TouchState mTempTouchState; + + // Focused application. + sp mFocusedApplicationHandle; + + // 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 dispatchEventToCurrentInputTargetsLocked( + nsecs_t currentTime, EventEntry* entry, bool resumeWithAppendedMotionSample); + + void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry); + void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry); + + // The input targets that were most recently identified for dispatch. + bool mCurrentInputTargetsValid; // false while targets are being recomputed + Vector mCurrentInputTargets; + + 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 mInputTargetWaitApplicationHandle; + + // Contains the last window which received a hover event. + sp mLastHoverWindowHandle; + + // Finding targets for input events. + void resetTargetsLocked(); + void commitTargetsLocked(); + int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry, + const sp& applicationHandle, + const sp& windowHandle, + nsecs_t* nextWakeupTime); + void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout, + const sp& inputChannel); + nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime); + void resetANRTimeoutsLocked(); + + int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry, + nsecs_t* nextWakeupTime); + int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry, + nsecs_t* nextWakeupTime, bool* outConflictingPointerActions, + const MotionSample** outSplitBatchAfterSample); + + void addWindowTargetLocked(const sp& windowHandle, + int32_t targetFlags, BitSet32 pointerIds); + void addMonitoringTargetsLocked(); + void pokeUserActivityLocked(const EventEntry* eventEntry); + bool checkInjectionPermission(const sp& windowHandle, + const InjectionState* injectionState); + bool isWindowObscuredAtPointLocked(const sp& windowHandle, + int32_t x, int32_t y) const; + bool isWindowFinishedWithPreviousInputLocked(const sp& windowHandle); + String8 getApplicationWindowLabelLocked(const sp& applicationHandle, + const sp& 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, + EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample); + void enqueueDispatchEntryLocked(const sp& connection, + EventEntry* eventEntry, const InputTarget* inputTarget, + bool resumeWithAppendedMotionSample, int32_t dispatchMode); + void startDispatchCycleLocked(nsecs_t currentTime, const sp& connection); + void finishDispatchCycleLocked(nsecs_t currentTime, const sp& connection, + bool handled); + void startNextDispatchCycleLocked(nsecs_t currentTime, const sp& connection); + void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp& connection, + bool notify); + void drainOutboundQueueLocked(Connection* connection); + static int handleReceiveCallback(int receiveFd, int events, void* data); + + void synthesizeCancelationEventsForAllConnectionsLocked( + const CancelationOptions& options); + void synthesizeCancelationEventsForInputChannelLocked(const sp& channel, + const CancelationOptions& options); + void synthesizeCancelationEventsForConnectionLocked(const sp& 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); + status_t unregisterInputChannelLocked(const sp& 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 onDispatchCycleStartedLocked( + nsecs_t currentTime, const sp& connection); + void onDispatchCycleFinishedLocked( + nsecs_t currentTime, const sp& connection, bool handled); + void onDispatchCycleBrokenLocked( + nsecs_t currentTime, const sp& connection); + void onANRLocked( + nsecs_t currentTime, const sp& applicationHandle, + const sp& windowHandle, + nsecs_t eventTime, nsecs_t waitStartTime); + + // 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, + DispatchEntry* dispatchEntry, KeyEntry* keyEntry, bool handled); + bool afterMotionEventLockedInterruptible(const sp& 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); +}; + +/* Enqueues and dispatches input events, endlessly. */ +class InputDispatcherThread : public Thread { +public: + explicit InputDispatcherThread(const sp& dispatcher); + ~InputDispatcherThread(); + +private: + virtual bool threadLoop(); + + sp 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 00000000000..657a6b98b23 --- /dev/null +++ b/widget/gonk/libui/InputListener.cpp @@ -0,0 +1,181 @@ +/* + * 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 + +namespace android { + +// --- NotifyConfigurationChangedArgs --- + +NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(nsecs_t eventTime) : + eventTime(eventTime) { +} + +NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs( + const NotifyConfigurationChangedArgs& other) : + eventTime(other.eventTime) { +} + +void NotifyConfigurationChangedArgs::notify(const sp& 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& 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, 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), 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), 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& listener) const { + listener->notifyMotion(this); +} + + +// --- NotifySwitchArgs --- + +NotifySwitchArgs::NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags, + int32_t switchCode, int32_t switchValue) : + eventTime(eventTime), policyFlags(policyFlags), + switchCode(switchCode), switchValue(switchValue) { +} + +NotifySwitchArgs::NotifySwitchArgs(const NotifySwitchArgs& other) : + eventTime(other.eventTime), policyFlags(other.policyFlags), + switchCode(other.switchCode), switchValue(other.switchValue) { +} + +void NotifySwitchArgs::notify(const sp& 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& listener) const { + listener->notifyDeviceReset(this); +} + + +// --- QueuedInputListener --- + +QueuedInputListener::QueuedInputListener(const sp& 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 00000000000..c00b1fdf72f --- /dev/null +++ b/widget/gonk/libui/InputListener.h @@ -0,0 +1,195 @@ +/* + * 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 +#include + +namespace android { + +class InputListenerInterface; + + +/* Superclass of all input event argument objects */ +struct NotifyArgs { + virtual ~NotifyArgs() { } + + virtual void notify(const sp& 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& 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& 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; + 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, 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& listener) const; +}; + + +/* Describes a switch event. */ +struct NotifySwitchArgs : public NotifyArgs { + nsecs_t eventTime; + uint32_t policyFlags; + int32_t switchCode; + int32_t switchValue; + + inline NotifySwitchArgs() { } + + NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags, + int32_t switchCode, int32_t switchValue); + + NotifySwitchArgs(const NotifySwitchArgs& other); + + virtual ~NotifySwitchArgs() { } + + virtual void notify(const sp& 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& 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& 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 mInnerListener; + Vector mArgsQueue; +}; + +} // namespace android + +#endif // _UI_INPUT_LISTENER_H diff --git a/widget/gonk/libui/InputReader.cpp b/widget/gonk/libui/InputReader.cpp new file mode 100644 index 00000000000..81998ffab31 --- /dev/null +++ b/widget/gonk/libui/InputReader.cpp @@ -0,0 +1,6065 @@ +/* + * 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 + +// 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 + +#include "InputReader.h" + +#include +#include "Keyboard.h" +#include "VirtualKeyMap.h" + +#include +#include +#include +#include +#include +#include + +#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 +inline static T abs(const T& value) { + return value < 0 ? - value : value; +} + +template +inline static T min(const T& a, const T& b) { + return a < b ? a : b; +} + +template +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(int32_t displayId, bool external, + int32_t* width, int32_t* height, int32_t* orientation) const { + if (displayId == 0) { + const DisplayInfo& info = external ? mExternalDisplay : mInternalDisplay; + if (info.width > 0 && info.height > 0) { + if (width) { + *width = info.width; + } + if (height) { + *height = info.height; + } + if (orientation) { + *orientation = info.orientation; + } + return true; + } + } + return false; +} + +void InputReaderConfiguration::setDisplayInfo(int32_t displayId, bool external, + int32_t width, int32_t height, int32_t orientation) { + if (displayId == 0) { + DisplayInfo& info = external ? mExternalDisplay : mInternalDisplay; + info.width = width; + info.height = height; + info.orientation = orientation; + } +} + + +// --- InputReader --- + +InputReader::InputReader(const sp& eventHub, + const sp& policy, + const sp& listener) : + mContext(this), mEventHub(eventHub), mPolicy(policy), + mGlobalMetaState(0), mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX), + mConfigurationChangesToRefresh(0) { + mQueuedListener = new QueuedInputListener(listener); + + { // acquire lock + AutoMutex _l(mLock); + + refreshConfigurationLocked(0); + updateGlobalMetaStateLocked(); + updateInputConfigurationLocked(); + } // release lock +} + +InputReader::~InputReader() { + for (size_t i = 0; i < mDevices.size(); i++) { + delete mDevices.valueAt(i); + } +} + +void InputReader::loopOnce() { + int32_t timeoutMillis; + { // acquire lock + AutoMutex _l(mLock); + + uint32_t changes = mConfigurationChangesToRefresh; + if (changes) { + mConfigurationChangesToRefresh = 0; + refreshConfigurationLocked(changes); + } + + timeoutMillis = -1; + 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); + + if (count) { + processEventsLocked(mEventBuffer, count); + } + if (!count || timeoutMillis == 0) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); +#if DEBUG_RAW_EVENTS + ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f); +#endif + mNextTimeout = LLONG_MAX; + timeoutExpiredLocked(now); + } + } // release lock + + // 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) { + String8 name = mEventHub->getDeviceName(deviceId); + uint32_t classes = mEventHub->getDeviceClasses(deviceId); + + InputDevice* device = createDeviceLocked(deviceId, name, classes); + device->configure(when, &mConfig, 0); + device->reset(when); + + if (device->isIgnored()) { + ALOGI("Device added: id=%d, name='%s' (ignored non-input device)", deviceId, name.string()); + } else { + ALOGI("Device added: id=%d, name='%s', sources=0x%08x", deviceId, name.string(), + device->getSources()); + } + + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + mDevices.add(deviceId, device); + } else { + ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId); + delete device; + return; + } +} + +void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) { + InputDevice* device = NULL; + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex >= 0) { + device = mDevices.valueAt(deviceIndex); + mDevices.removeItemsAt(deviceIndex, 1); + } else { + ALOGW("Ignoring spurious device removed event for deviceId %d.", deviceId); + return; + } + + 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 String8& name, uint32_t classes) { + InputDevice* device = new InputDevice(&mContext, deviceId, name, 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)); + } + + // 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)); + } + +#if 0 + // Cursor-like devices. + if (classes & INPUT_DEVICE_CLASS_CURSOR) { + device->addMapper(new CursorInputMapper(device)); + } +#endif + + // 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(); + + // Update input configuration. + updateInputConfigurationLocked(); + + // 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::updateInputConfigurationLocked() { + int32_t touchScreenConfig = InputConfiguration::TOUCHSCREEN_NOTOUCH; + int32_t keyboardConfig = InputConfiguration::KEYBOARD_NOKEYS; + int32_t navigationConfig = InputConfiguration::NAVIGATION_NONAV; + InputDeviceInfo deviceInfo; + for (size_t i = 0; i < mDevices.size(); i++) { + InputDevice* device = mDevices.valueAt(i); + device->getDeviceInfo(& deviceInfo); + uint32_t sources = deviceInfo.getSources(); + + if ((sources & AINPUT_SOURCE_TOUCHSCREEN) == AINPUT_SOURCE_TOUCHSCREEN) { + touchScreenConfig = InputConfiguration::TOUCHSCREEN_FINGER; + } + if ((sources & AINPUT_SOURCE_TRACKBALL) == AINPUT_SOURCE_TRACKBALL) { + navigationConfig = InputConfiguration::NAVIGATION_TRACKBALL; + } else if ((sources & AINPUT_SOURCE_DPAD) == AINPUT_SOURCE_DPAD) { + navigationConfig = InputConfiguration::NAVIGATION_DPAD; + } + if (deviceInfo.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) { + keyboardConfig = InputConfiguration::KEYBOARD_QWERTY; + } + } + + mInputConfiguration.touchScreen = touchScreenConfig; + mInputConfiguration.keyboard = keyboardConfig; + mInputConfiguration.navigation = navigationConfig; +} + +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; + } +} + +void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) { + AutoMutex _l(mLock); + + *outConfiguration = mInputConfiguration; +} + +status_t InputReader::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) { + AutoMutex _l(mLock); + + ssize_t deviceIndex = mDevices.indexOfKey(deviceId); + if (deviceIndex < 0) { + return NAME_NOT_FOUND; + } + + InputDevice* device = mDevices.valueAt(deviceIndex); + if (device->isIgnored()) { + return NAME_NOT_FOUND; + } + + device->getDeviceInfo(outDeviceInfo); + return OK; +} + +void InputReader::getInputDeviceIds(Vector& outDeviceIds) { + AutoMutex _l(mLock); + + outDeviceIds.clear(); + + size_t numDevices = mDevices.size(); + for (size_t i = 0; i < numDevices; i++) { + InputDevice* device = mDevices.valueAt(i); + if (!device->isIgnored()) { + outDeviceIds.add(device->getId()); + } + } +} + +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)) { + result = (device->*getStateFunc)(sourceMask, code); + if (result >= AKEY_STATE_DOWN) { + return result; + } + } + } + } + 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::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(); + 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); +} + +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& reader) : + Thread(/*canCallJava*/ true), mReader(reader) { +} + +InputReaderThread::~InputReaderThread() { +} + +bool InputReaderThread::threadLoop() { + mReader->loopOnce(); + return true; +} + + +// --- InputDevice --- + +InputDevice::InputDevice(InputReaderContext* context, int32_t id, const String8& name, + uint32_t classes) : + mContext(context), mId(id), mName(name), 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.getName().string()); + 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& 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\n", + name, range.source, range.min, range.max, range.flat, range.fuzz); + } + } + + 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); + } + + 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 scancode=0x%04x " + "keycode=0x%04x value=0x%08x flags=0x%08x", + rawEvent->deviceId, rawEvent->type, rawEvent->scanCode, rawEvent->keyCode, + rawEvent->value, rawEvent->flags); +#endif + + if (mDropUntilNextSync) { + if (rawEvent->type == EV_SYN && rawEvent->scanCode == 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->scanCode == SYN_DROPPED) { + ALOGI("Detected input event buffer overrun for device %s.", mName.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, mName); + + 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)) { + result = (mapper->*getStateFunc)(sourceMask, code); + if (result >= AKEY_STATE_DOWN) { + return result; + } + } + } + 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; +} + +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::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->scanCode) { + 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->scanCode) { + 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->scanCode) { + case REL_WHEEL: + mRelWheel = rawEvent->value; + break; + case REL_HWHEEL: + mRelHWheel = rawEvent->value; + break; + } + } +} + +void CursorScrollAccumulator::finishSync() { + clearRelativeAxes(); +} + + +// --- TouchButtonAccumulator --- + +TouchButtonAccumulator::TouchButtonAccumulator() : + mHaveBtnTouch(false) { + clearButtons(); +} + +void TouchButtonAccumulator::configure(InputDevice* device) { + mHaveBtnTouch = device->hasKey(BTN_TOUCH); +} + +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->scanCode) { + 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; +} + + +// --- 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->scanCode) { + 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) { +} + +MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() { + delete[] mSlots; +} + +void MultiTouchMotionAccumulator::configure(size_t slotCount, bool usingSlotsProtocol) { + mSlotCount = slotCount; + mUsingSlotsProtocol = usingSlotsProtocol; + + 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) { + bool newSlot = false; + if (mUsingSlotsProtocol) { + if (rawEvent->scanCode == ABS_MT_SLOT) { + mCurrentSlot = rawEvent->value; + newSlot = true; + } + } 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->scanCode) { + 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->scanCode == 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); + } +} + + +// --- 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; +} + +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::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) { +} + +SwitchInputMapper::~SwitchInputMapper() { +} + +uint32_t SwitchInputMapper::getSources() { + return AINPUT_SOURCE_SWITCH; +} + +void SwitchInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_SW: + processSwitch(rawEvent->when, rawEvent->scanCode, rawEvent->value); + break; + } +} + +void SwitchInputMapper::processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue) { + NotifySwitchArgs args(when, 0, switchCode, switchValue); + getListener()->notifySwitch(&args); +} + +int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) { + return getEventHub()->getSwitchState(getDeviceId(), switchCode); +} + + +// --- 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->setKeyCharacterMapFile(getEventHub()->getKeyCharacterMapFile(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.associatedDisplayId >= 0) { + if (!config->getDisplayInfo(mParameters.associatedDisplayId, + false /*external*/, NULL, NULL, &mOrientation)) { + mOrientation = DISPLAY_ORIENTATION_0; + } + } else { + mOrientation = DISPLAY_ORIENTATION_0; + } + } +} + +void KeyboardInputMapper::configureParameters() { + mParameters.orientationAware = false; + getDevice()->getConfiguration().tryGetProperty(String8("keyboard.orientationAware"), + mParameters.orientationAware); + + mParameters.associatedDisplayId = -1; + if (mParameters.orientationAware) { + mParameters.associatedDisplayId = 0; + } +} + +void KeyboardInputMapper::dumpParameters(String8& dump) { + dump.append(INDENT3 "Parameters:\n"); + dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n", + mParameters.associatedDisplayId); + dump.appendFormat(INDENT4 "OrientationAware: %s\n", + toString(mParameters.orientationAware)); +} + +void KeyboardInputMapper::reset(nsecs_t when) { + mMetaState = AMETA_NONE; + mDownTime = 0; + mKeyDowns.clear(); + + resetLedState(); + + InputMapper::reset(when); +} + +void KeyboardInputMapper::process(const RawEvent* rawEvent) { + switch (rawEvent->type) { + case EV_KEY: { + int32_t scanCode = rawEvent->scanCode; + if (isKeyboardOrGamepadKey(scanCode)) { + processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode, + rawEvent->flags); + } + break; + } + } +} + +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.associatedDisplayId >= 0) { + 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 --- + +#ifdef HAVE_ANDROID_OS +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 0 + if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) { + info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f); + info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f); + } +#endif + } else { + info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale); + info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale); + } + info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f); + + if (mCursorScrollAccumulator.haveRelativeVWheel()) { + info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f); + } + if (mCursorScrollAccumulator.haveRelativeHWheel()) { + info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.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.associatedDisplayId >= 0) { + if (!config->getDisplayInfo(mParameters.associatedDisplayId, + false /*external*/, NULL, NULL, &mOrientation)) { + mOrientation = DISPLAY_ORIENTATION_0; + } + } else { + mOrientation = DISPLAY_ORIENTATION_0; + } + } +} + +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.associatedDisplayId = -1; + if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) { + mParameters.associatedDisplayId = 0; + } +} + +void CursorInputMapper::dumpParameters(String8& dump) { + dump.append(INDENT3 "Parameters:\n"); + dump.appendFormat(INDENT4 "AssociatedDisplayId: %d\n", + mParameters.associatedDisplayId); + + 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->scanCode == 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.associatedDisplayId >= 0 + && (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); + +#if 0 + 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); + } else { +#endif + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX); + pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY); +// } + + 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) { + } else if (1) { + motionEventAction = AMOTION_EVENT_ACTION_MOVE; + } else { + motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE; + } + + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + motionEventAction, 0, metaState, currentButtonState, 0, + 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 0 + 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, + 1, &pointerProperties, &pointerCoords, mXPrecision, mYPrecision, downTime); + getListener()->notifyMotion(&hoverArgs); + } +#endif + + // 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, + 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); + } +} + +#endif // HAVE_ANDROID_OS + +// --- TouchInputMapper --- + +TouchInputMapper::TouchInputMapper(InputDevice* device) : + InputMapper(device), + mSource(0), mDeviceMode(DEVICE_MODE_DISABLED), + mSurfaceOrientation(-1), mSurfaceWidth(-1), mSurfaceHeight(-1) { +} + +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); + } + if (mCursorScrollAccumulator.haveRelativeHWheel()) { + info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f); + } + } +} + +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 "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 "OrientationCenter: %0.3f\n", mOrientationCenter); + 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 == "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.associatedDisplayId = -1; + mParameters.associatedDisplayIsExternal = false; + if (mParameters.orientationAware + || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) { + mParameters.associatedDisplayIsExternal = + mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + && getDevice()->isExternal(); + mParameters.associatedDisplayId = 0; + } +} + +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_POINTER: + dump.append(INDENT4 "DeviceType: pointer\n"); + break; + default: + ALOG_ASSERT(false); + } + + dump.appendFormat(INDENT4 "AssociatedDisplay: id=%d, isExternal=%s\n", + mParameters.associatedDisplayId, 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; + } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN + && mParameters.associatedDisplayId >= 0) { + mSource = AINPUT_SOURCE_TOUCHSCREEN; + mDeviceMode = DEVICE_MODE_DIRECT; + } 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; + } + + // Get associated display dimensions. + if (mParameters.associatedDisplayId >= 0) { + if (!mConfig.getDisplayInfo(mParameters.associatedDisplayId, + mParameters.associatedDisplayIsExternal, + &mAssociatedDisplayWidth, &mAssociatedDisplayHeight, + &mAssociatedDisplayOrientation)) { + ALOGI(INDENT "Touch device '%s' could not query the properties of its associated " + "display %d. The device will be inoperable until the display size " + "becomes available.", + getDeviceName().string(), mParameters.associatedDisplayId); + mDeviceMode = DEVICE_MODE_DISABLED; + return; + } + } + + // Configure dimensions. + int32_t width, height, orientation; + if (mDeviceMode == DEVICE_MODE_DIRECT || mDeviceMode == DEVICE_MODE_POINTER) { + width = mAssociatedDisplayWidth; + height = mAssociatedDisplayHeight; + orientation = mParameters.orientationAware ? + mAssociatedDisplayOrientation : DISPLAY_ORIENTATION_0; + } else { + width = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1; + height = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1; + orientation = DISPLAY_ORIENTATION_0; + } + + // If moving between pointer modes, need to reset some state. + bool deviceModeChanged; + if (mDeviceMode != oldDeviceMode) { + deviceModeChanged = true; + mOrientedRanges.clear(); + } + + // Create pointer controller if needed. + if (mDeviceMode == DEVICE_MODE_POINTER || + (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches)) { +#if 0 + if (mPointerController == NULL) { + mPointerController = getPolicy()->obtainPointerController(getDeviceId()); + } +#endif + } else { + //mPointerController.clear(); + } + + bool orientationChanged = mSurfaceOrientation != orientation; + if (orientationChanged) { + mSurfaceOrientation = orientation; + } + + bool sizeChanged = mSurfaceWidth != width || mSurfaceHeight != height; + if (sizeChanged || deviceModeChanged) { + ALOGI("Device reconfigured: id=%d, name='%s', surface size is now %dx%d, mode is %d", + getDeviceId(), getDeviceName().string(), width, height, mDeviceMode); + + mSurfaceWidth = width; + mSurfaceHeight = height; + + // Configure X and Y factors. + mXScale = float(width) / (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1); + mYScale = float(height) / (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1); + 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(width, height); + + // 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.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.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; + } 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; + + // 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; + } + + // Orientation + mOrientationCenter = 0; + 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; + } else if (mCalibration.orientationCalibration != + Calibration::ORIENTATION_CALIBRATION_NONE) { + if (mCalibration.orientationCalibration + == Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) { + if (mRawPointerAxes.orientation.valid) { + mOrientationCenter = avg(mRawPointerAxes.orientation.minValue, + mRawPointerAxes.orientation.maxValue); + mOrientationScale = M_PI / (mRawPointerAxes.orientation.maxValue - + mRawPointerAxes.orientation.minValue); + } + } + + 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; + } + + // 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.minValue * mDistanceScale; + mOrientedRanges.distance.flat = 0; + mOrientedRanges.distance.fuzz = + mRawPointerAxes.distance.fuzz * mDistanceScale; + } + } + + if (orientationChanged || sizeChanged || deviceModeChanged) { + // Compute oriented surface dimensions, 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: + mOrientedSurfaceWidth = mSurfaceHeight; + mOrientedSurfaceHeight = mSurfaceWidth; + + mOrientedXPrecision = mYPrecision; + mOrientedYPrecision = mXPrecision; + + mOrientedRanges.x.min = 0; + mOrientedRanges.x.max = (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue) + * mYScale; + mOrientedRanges.x.flat = 0; + mOrientedRanges.x.fuzz = mYScale; + + mOrientedRanges.y.min = 0; + mOrientedRanges.y.max = (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue) + * mXScale; + mOrientedRanges.y.flat = 0; + mOrientedRanges.y.fuzz = mXScale; + break; + + default: + mOrientedSurfaceWidth = mSurfaceWidth; + mOrientedSurfaceHeight = mSurfaceHeight; + + mOrientedXPrecision = mXPrecision; + mOrientedYPrecision = mYPrecision; + + mOrientedRanges.x.min = 0; + mOrientedRanges.x.max = (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue) + * mXScale; + mOrientedRanges.x.flat = 0; + mOrientedRanges.x.fuzz = mXScale; + + mOrientedRanges.y.min = 0; + mOrientedRanges.y.max = (mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue) + * mYScale; + mOrientedRanges.y.flat = 0; + mOrientedRanges.y.fuzz = mYScale; + break; + } + + // Compute pointer gesture detection parameters. + if (mDeviceMode == DEVICE_MODE_POINTER) { + int32_t rawWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1; + int32_t rawHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1; + float rawDiagonal = hypotf(rawWidth, rawHeight); + float displayDiagonal = hypotf(mAssociatedDisplayWidth, + mAssociatedDisplayHeight); + + // 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; + } +} + +void TouchInputMapper::dumpSurface(String8& dump) { + dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mSurfaceWidth); + dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mSurfaceHeight); + dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mSurfaceOrientation); +} + +void TouchInputMapper::configureVirtualKeys() { + Vector 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, + & 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 == "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); +} + +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; + } +} + +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_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); + } +} + +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 0 + if (mPointerController != NULL) { + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + mPointerController->clearSpots(); + } +#endif + + InputMapper::reset(when); +} + +void TouchInputMapper::process(const RawEvent* rawEvent) { + mCursorButtonAccumulator.process(rawEvent); + mCursorScrollAccumulator.process(rawEvent); + mTouchButtonAccumulator.process(rawEvent); + + if (rawEvent->type == EV_SYN && rawEvent->scanCode == 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 0 + 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); + } +#endif + + 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_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 - mOrientationCenter) * 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; + } + + // X and Y + // Adjust coords for surface orientation. + float x, y; + switch (mSurfaceOrientation) { + case DISPLAY_ORIENTATION_90: + x = float(in.y - mRawPointerAxes.y.minValue) * mYScale; + y = float(mRawPointerAxes.x.maxValue - in.x) * mXScale; + 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; + y = float(mRawPointerAxes.y.maxValue - in.y) * mYScale; + break; + case DISPLAY_ORIENTATION_270: + x = float(mRawPointerAxes.y.maxValue - in.y) * mYScale; + y = float(in.x - mRawPointerAxes.x.minValue) * mXScale; + orientation += M_PI_2; + if (orientation > M_PI_2) { + orientation -= M_PI; + } + break; + default: + x = float(in.x - mRawPointerAxes.x.minValue) * mXScale; + y = float(in.y - mRawPointerAxes.y.minValue) * mYScale; + 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_TOOL_MAJOR, toolMajor); + out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor); + out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation); + out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt); + out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance); + + // 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 0 + 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; + } +#endif + + // 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. +#if 0 + 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, + 1, &pointerProperties, &pointerCoords, 0, 0, mPointerGesture.downTime); + getListener()->notifyMotion(&args); +#endif + } + + // 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 0 + if (mPointerController != NULL) { + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + mPointerController->clearSpots(); + } +#endif +} + +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. + bool activeTouchChanged = false; + int32_t lastActiveTouchId = mPointerGesture.activeTouchId; + int32_t activeTouchId = lastActiveTouchId; + if (activeTouchId < 0) { + if (!mCurrentFingerIdBits.isEmpty()) { + activeTouchChanged = true; + activeTouchId = mPointerGesture.activeTouchId = + mCurrentFingerIdBits.firstMarkedBit(); + mPointerGesture.firstTouchTime = when; + } + } else if (!mCurrentFingerIdBits.hasBit(activeTouchId)) { + activeTouchChanged = true; + 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; + activeTouchChanged = true; +#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 0 + 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); + } + } +#endif + + if (mPointerSimple.down && !down) { + mPointerSimple.down = false; + + // Send up. + NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags, + AMOTION_EVENT_ACTION_UP, 0, metaState, mLastButtonState, 0, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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 0 + if (mPointerController != NULL) { + mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); + } +#endif +} + +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); +} + + +// --- 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(slotCount, true /*usingSlotsProtocol*/); + } else { + mMultiTouchMotionAccumulator.configure(MAX_POINTERS, false /*usingSlotsProtocol*/); + } +} + + +// --- 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); + info->addMotionRange(axis.axisInfo.axis, AINPUT_SOURCE_JOYSTICK, + axis.min, axis.max, axis.flat, axis.fuzz); + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + info->addMotionRange(axis.axisInfo.highAxis, AINPUT_SOURCE_JOYSTICK, + axis.min, axis.max, axis.flat, axis.fuzz); + } + } +} + +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\n", + axis.min, axis.max, axis.flat, axis.fuzz); + 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); + } 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); + } 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); + } + + // 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->scanCode); + 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->scanCode) { + 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); + pointerCoords.setAxisValue(axis.axisInfo.axis, axis.currentValue); + if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) { + pointerCoords.setAxisValue(axis.axisInfo.highAxis, axis.highCurrentValue); + } + } + + // Moving a joystick axis should not wake the devide 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, + 1, &pointerProperties, &pointerCoords, 0, 0, 0); + getListener()->notifyMotion(&args); +} + +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 00000000000..1005528c98d --- /dev/null +++ b/widget/gonk/libui/InputReader.h @@ -0,0 +1,1653 @@ +/* + * 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" +#ifdef HAVE_ANDROID_OS +#include "PointerController.h" +#endif +#include "InputListener.h" + +#include "Input.h" +#include "DisplayInfo.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace android { + +class InputDevice; +class InputMapper; + + +/* + * 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, + + // 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 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(int32_t displayId, bool external, + int32_t* width, int32_t* height, int32_t* orientation) const; + + void setDisplayInfo(int32_t displayId, bool external, + int32_t width, int32_t height, int32_t orientation); + +private: + struct DisplayInfo { + int32_t width; + int32_t height; + int32_t orientation; + + DisplayInfo() : + width(-1), height(-1), orientation(DISPLAY_ORIENTATION_0) { + } + }; + + DisplayInfo mInternalDisplay; + DisplayInfo 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; + +#ifdef HAVE_ANDROID_OS + /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */ + virtual sp obtainPointerController(int32_t deviceId) = 0; +#endif +}; + + +/* 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 the current input device configuration. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0; + + /* Gets information about the specified input device. + * Returns OK if the device information was obtained or NAME_NOT_FOUND if there + * was no such device. + * + * This method may be called on any thread (usually by the input manager). + */ + virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0; + + /* Gets the list of all registered device ids. */ + virtual void getInputDeviceIds(Vector& outDeviceIds) = 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; +}; + + +/* 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 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& eventHub, + const sp& policy, + const sp& listener); + virtual ~InputReader(); + + virtual void dump(String8& dump); + virtual void monitor(); + + virtual void loopOnce(); + + virtual void getInputConfiguration(InputConfiguration* outConfiguration); + + virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo); + virtual void getInputDeviceIds(Vector& outDeviceIds); + + 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); + +protected: + // These members are protected so they can be instrumented by test cases. + virtual InputDevice* createDeviceLocked(int32_t deviceId, + const String8& name, 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 InputReaderPolicyInterface* getPolicy(); + virtual InputListenerInterface* getListener(); + virtual EventHubInterface* getEventHub(); + } mContext; + + friend class ContextImpl; + +private: + Mutex mLock; + + sp mEventHub; + sp mPolicy; + sp mQueuedListener; + + InputReaderConfiguration mConfig; + + // The event queue. + static const int EVENT_BUFFER_SIZE = 256; + RawEvent mEventBuffer[EVENT_BUFFER_SIZE]; + + KeyedVector 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(); + + InputConfiguration mInputConfiguration; + void updateInputConfigurationLocked(); + + 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& reader); + virtual ~InputReaderThread(); + +private: + uint32_t mFoo; + sp mReader; + + virtual bool threadLoop(); +}; + + +/* Represents the state of a single input device. */ +class InputDevice { +public: + InputDevice(InputReaderContext* context, int32_t id, const String8& name, uint32_t classes); + ~InputDevice(); + + inline InputReaderContext* getContext() { return mContext; } + inline int32_t getId() { return mId; } + inline const String8& getName() { return mName; } + 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); + + int32_t getMetaState(); + + void fadePointer(); + + 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 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; + String8 mName; + uint32_t mClasses; + + Vector 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; + +private: + bool mHaveBtnTouch; + + 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 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(size_t slotCount, bool usingSlotsProtocol); + void reset(InputDevice* device); + void process(const RawEvent* rawEvent); + void finishSync(); + + 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; + + 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 int32_t getMetaState(); + + virtual void fadePointer(); + +protected: + InputDevice* mDevice; + InputReaderContext* mContext; + + status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo); + + 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: + void processSwitch(nsecs_t when, int32_t switchCode, int32_t switchValue); +}; + + +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 mKeyDowns; // keys that are down + int32_t mMetaState; + nsecs_t mDownTime; // time of most recent key down + + 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 { + int32_t associatedDisplayId; + 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); +}; + + +#ifdef HAVE_ANDROID_OS +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; + int32_t associatedDisplayId; + 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 mPointerController; + + int32_t mButtonState; + nsecs_t mDownTime; + + void configureParameters(); + void dumpParameters(String8& dump); + + void sync(nsecs_t when); +}; +#endif // HAVE_ANDROID_OS + + +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_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_POINTER, + }; + + DeviceType deviceType; + int32_t associatedDisplayId; + 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_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; + + 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; + +#ifdef HAVE_ANDROID_OS + // The pointer controller, or null if the device is not a pointer. + sp mPointerController; +#endif + + Vector 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 void syncTouch(nsecs_t when, bool* outHavePointerIds) = 0; + +private: + // The surface orientation and width and height set by configureSurface(). + int32_t mSurfaceOrientation; + int32_t mSurfaceWidth; + int32_t mSurfaceHeight; + + // The associated display orientation and width and height set by configureSurface(). + int32_t mAssociatedDisplayOrientation; + int32_t mAssociatedDisplayWidth; + int32_t mAssociatedDisplayHeight; + + // Translation and scaling factors, orientation-independent. + float mXScale; + float mXPrecision; + + float mYScale; + float mYPrecision; + + float mGeometricScale; + + float mPressureScale; + + float mSizeScale; + + float mOrientationCenter; + 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 mOrientedSurfaceWidth; + float mOrientedSurfaceHeight; + 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(); + +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(); + +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 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) { + 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->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 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); +}; + +} // 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 00000000000..4d9c92e40f9 --- /dev/null +++ b/widget/gonk/libui/InputTransport.cpp @@ -0,0 +1,727 @@ +// +// 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 signalling (send signal, receive signal) +#define DEBUG_CHANNEL_SIGNALS 0 + +// Log debug messages whenever InputChannel objects are created/destroyed +#define DEBUG_CHANNEL_LIFECYCLE 0 + +// Log debug messages about transport actions (initialize, reset, publish, ...) +#define DEBUG_TRANSPORT_ACTIONS 0 + + +#include +#include +#include +#include +#include +#include "InputTransport.h" +#include + +namespace android { + +#define ROUND_UP(value, boundary) (((value) + (boundary) - 1) & ~((boundary) - 1)) +#define MIN_HISTORY_DEPTH 20 + +// Must be at least sizeof(InputMessage) + sufficient space for pointer data +static const int DEFAULT_MESSAGE_BUFFER_SIZE = ROUND_UP( + sizeof(InputMessage) + MIN_HISTORY_DEPTH + * (sizeof(InputMessage::SampleData) + MAX_POINTERS * sizeof(PointerCoords)), + 4096); + +// Signal sent by the producer to the consumer to inform it that a new message is +// available to be consumed in the shared memory buffer. +static const char INPUT_SIGNAL_DISPATCH = 'D'; + +// Signal sent by the consumer to the producer to inform it that it has finished +// consuming the most recent message and it handled it. +static const char INPUT_SIGNAL_FINISHED_HANDLED = 'f'; + +// Signal sent by the consumer to the producer to inform it that it has finished +// consuming the most recent message but it did not handle it. +static const char INPUT_SIGNAL_FINISHED_UNHANDLED = 'u'; + + +// --- InputChannel --- + +InputChannel::InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd, + int32_t sendPipeFd) : + mName(name), mAshmemFd(ashmemFd), mReceivePipeFd(receivePipeFd), mSendPipeFd(sendPipeFd) { +#if DEBUG_CHANNEL_LIFECYCLE + ALOGD("Input channel constructed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d", + mName.string(), ashmemFd, receivePipeFd, sendPipeFd); +#endif + + int result = fcntl(mReceivePipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make receive pipe " + "non-blocking. errno=%d", mName.string(), errno); + + result = fcntl(mSendPipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make send pipe " + "non-blocking. errno=%d", mName.string(), errno); +} + +InputChannel::~InputChannel() { +#if DEBUG_CHANNEL_LIFECYCLE + ALOGD("Input channel destroyed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d", + mName.string(), mAshmemFd, mReceivePipeFd, mSendPipeFd); +#endif + + ::close(mAshmemFd); + ::close(mReceivePipeFd); + ::close(mSendPipeFd); +} + +status_t InputChannel::openInputChannelPair(const String8& name, + sp& outServerChannel, sp& outClientChannel) { + status_t result; + + String8 ashmemName("InputChannel "); + ashmemName.append(name); + int serverAshmemFd = ashmem_create_region(ashmemName.string(), DEFAULT_MESSAGE_BUFFER_SIZE); + if (serverAshmemFd < 0) { + result = -errno; + ALOGE("channel '%s' ~ Could not create shared memory region. errno=%d", + name.string(), errno); + } else { + result = ashmem_set_prot_region(serverAshmemFd, PROT_READ | PROT_WRITE); + if (result < 0) { + ALOGE("channel '%s' ~ Error %d trying to set protection of ashmem fd %d.", + name.string(), result, serverAshmemFd); + } else { + // Dup the file descriptor because the server and client input channel objects that + // are returned may have different lifetimes but they share the same shared memory region. + int clientAshmemFd; + clientAshmemFd = dup(serverAshmemFd); + if (clientAshmemFd < 0) { + result = -errno; + ALOGE("channel '%s' ~ Could not dup() shared memory region fd. errno=%d", + name.string(), errno); + } else { + int forward[2]; + if (pipe(forward)) { + result = -errno; + ALOGE("channel '%s' ~ Could not create forward pipe. errno=%d", + name.string(), errno); + } else { + int reverse[2]; + if (pipe(reverse)) { + result = -errno; + ALOGE("channel '%s' ~ Could not create reverse pipe. errno=%d", + name.string(), errno); + } else { + String8 serverChannelName = name; + serverChannelName.append(" (server)"); + outServerChannel = new InputChannel(serverChannelName, + serverAshmemFd, reverse[0], forward[1]); + + String8 clientChannelName = name; + clientChannelName.append(" (client)"); + outClientChannel = new InputChannel(clientChannelName, + clientAshmemFd, forward[0], reverse[1]); + return OK; + } + ::close(forward[0]); + ::close(forward[1]); + } + ::close(clientAshmemFd); + } + } + ::close(serverAshmemFd); + } + + outServerChannel.clear(); + outClientChannel.clear(); + return result; +} + +status_t InputChannel::sendSignal(char signal) { + ssize_t nWrite; + do { + nWrite = ::write(mSendPipeFd, & signal, 1); + } while (nWrite == -1 && errno == EINTR); + + if (nWrite == 1) { +#if DEBUG_CHANNEL_SIGNALS + ALOGD("channel '%s' ~ sent signal '%c'", mName.string(), signal); +#endif + return OK; + } + +#if DEBUG_CHANNEL_SIGNALS + ALOGD("channel '%s' ~ error sending signal '%c', errno=%d", mName.string(), signal, errno); +#endif + return -errno; +} + +status_t InputChannel::receiveSignal(char* outSignal) { + ssize_t nRead; + do { + nRead = ::read(mReceivePipeFd, outSignal, 1); + } while (nRead == -1 && errno == EINTR); + + if (nRead == 1) { +#if DEBUG_CHANNEL_SIGNALS + ALOGD("channel '%s' ~ received signal '%c'", mName.string(), *outSignal); +#endif + return OK; + } + + if (nRead == 0) { // check for EOF +#if DEBUG_CHANNEL_SIGNALS + ALOGD("channel '%s' ~ receive signal failed because peer was closed", mName.string()); +#endif + return DEAD_OBJECT; + } + + if (errno == EAGAIN) { +#if DEBUG_CHANNEL_SIGNALS + ALOGD("channel '%s' ~ receive signal failed because no signal available", mName.string()); +#endif + return WOULD_BLOCK; + } + +#if DEBUG_CHANNEL_SIGNALS + ALOGD("channel '%s' ~ receive signal failed, errno=%d", mName.string(), errno); +#endif + return -errno; +} + + +// --- InputPublisher --- + +InputPublisher::InputPublisher(const sp& channel) : + mChannel(channel), mSharedMessage(NULL), + mPinned(false), mSemaphoreInitialized(false), mWasDispatched(false), + mMotionEventSampleDataTail(NULL) { +} + +InputPublisher::~InputPublisher() { + reset(); + + if (mSharedMessage) { + munmap(mSharedMessage, mAshmemSize); + } +} + +status_t InputPublisher::initialize() { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' publisher ~ initialize", + mChannel->getName().string()); +#endif + + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_get_size_region(ashmemFd); + if (result < 0) { + ALOGE("channel '%s' publisher ~ Error %d getting size of ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + mAshmemSize = (size_t) result; + + mSharedMessage = static_cast(mmap(NULL, mAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); + if (! mSharedMessage) { + ALOGE("channel '%s' publisher ~ mmap failed on ashmem fd %d.", + mChannel->getName().string(), ashmemFd); + return NO_MEMORY; + } + + mPinned = true; + mSharedMessage->consumed = false; + + return reset(); +} + +status_t InputPublisher::reset() { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' publisher ~ reset", + mChannel->getName().string()); +#endif + + if (mPinned) { + // Destroy the semaphore since we are about to unpin the memory region that contains it. + int result; + if (mSemaphoreInitialized) { + if (mSharedMessage->consumed) { + result = sem_post(& mSharedMessage->semaphore); + if (result < 0) { + ALOGE("channel '%s' publisher ~ Error %d in sem_post.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + } + + result = sem_destroy(& mSharedMessage->semaphore); + if (result < 0) { + ALOGE("channel '%s' publisher ~ Error %d in sem_destroy.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + + mSemaphoreInitialized = false; + } + + // Unpin the region since we no longer care about its contents. + int ashmemFd = mChannel->getAshmemFd(); + result = ashmem_unpin_region(ashmemFd, 0, 0); + if (result < 0) { + ALOGE("channel '%s' publisher ~ Error %d unpinning ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + mPinned = false; + } + + mMotionEventSampleDataTail = NULL; + mWasDispatched = false; + return OK; +} + +status_t InputPublisher::publishInputEvent( + int32_t type, + int32_t deviceId, + int32_t source) { + if (mPinned) { + ALOGE("channel '%s' publisher ~ Attempted to publish a new event but publisher has " + "not yet been reset.", mChannel->getName().string()); + return INVALID_OPERATION; + } + + // Pin the region. + // We do not check for ASHMEM_NOT_PURGED because we don't care about the previous + // contents of the buffer so it does not matter whether it was purged in the meantime. + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_pin_region(ashmemFd, 0, 0); + if (result < 0) { + ALOGE("channel '%s' publisher ~ Error %d pinning ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + mPinned = true; + + result = sem_init(& mSharedMessage->semaphore, 1, 1); + if (result < 0) { + ALOGE("channel '%s' publisher ~ Error %d in sem_init.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + + mSemaphoreInitialized = true; + + mSharedMessage->consumed = false; + mSharedMessage->type = type; + mSharedMessage->deviceId = deviceId; + mSharedMessage->source = source; + return OK; +} + +status_t InputPublisher::publishKeyEvent( + 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: 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(), + deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount, + downTime, eventTime); +#endif + + status_t result = publishInputEvent(AINPUT_EVENT_TYPE_KEY, deviceId, source); + if (result < 0) { + return result; + } + + mSharedMessage->key.action = action; + mSharedMessage->key.flags = flags; + mSharedMessage->key.keyCode = keyCode; + mSharedMessage->key.scanCode = scanCode; + mSharedMessage->key.metaState = metaState; + mSharedMessage->key.repeatCount = repeatCount; + mSharedMessage->key.downTime = downTime; + mSharedMessage->key.eventTime = eventTime; + return OK; +} + +status_t InputPublisher::publishMotionEvent( + 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: 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(), + deviceId, source, action, flags, edgeFlags, metaState, buttonState, + xOffset, yOffset, xPrecision, yPrecision, downTime, eventTime, pointerCount); +#endif + + if (pointerCount > MAX_POINTERS || pointerCount < 1) { + ALOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.", + mChannel->getName().string(), pointerCount); + return BAD_VALUE; + } + + status_t result = publishInputEvent(AINPUT_EVENT_TYPE_MOTION, deviceId, source); + if (result < 0) { + return result; + } + + mSharedMessage->motion.action = action; + mSharedMessage->motion.flags = flags; + mSharedMessage->motion.edgeFlags = edgeFlags; + mSharedMessage->motion.metaState = metaState; + mSharedMessage->motion.buttonState = buttonState; + mSharedMessage->motion.xOffset = xOffset; + mSharedMessage->motion.yOffset = yOffset; + mSharedMessage->motion.xPrecision = xPrecision; + mSharedMessage->motion.yPrecision = yPrecision; + mSharedMessage->motion.downTime = downTime; + mSharedMessage->motion.pointerCount = pointerCount; + + mSharedMessage->motion.sampleCount = 1; + mSharedMessage->motion.sampleData[0].eventTime = eventTime; + + for (size_t i = 0; i < pointerCount; i++) { + mSharedMessage->motion.pointerProperties[i].copyFrom(pointerProperties[i]); + mSharedMessage->motion.sampleData[0].coords[i].copyFrom(pointerCoords[i]); + } + + // Cache essential information about the motion event to ensure that a malicious consumer + // cannot confuse the publisher by modifying the contents of the shared memory buffer while + // it is being updated. + if (action == AMOTION_EVENT_ACTION_MOVE + || action == AMOTION_EVENT_ACTION_HOVER_MOVE) { + mMotionEventPointerCount = pointerCount; + mMotionEventSampleDataStride = InputMessage::sampleDataStride(pointerCount); + mMotionEventSampleDataTail = InputMessage::sampleDataPtrIncrement( + mSharedMessage->motion.sampleData, mMotionEventSampleDataStride); + } else { + mMotionEventSampleDataTail = NULL; + } + return OK; +} + +status_t InputPublisher::appendMotionSample( + nsecs_t eventTime, + const PointerCoords* pointerCoords) { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' publisher ~ appendMotionSample: eventTime=%lld", + mChannel->getName().string(), eventTime); +#endif + + if (! mPinned || ! mMotionEventSampleDataTail) { + ALOGE("channel '%s' publisher ~ Cannot append motion sample because there is no current " + "AMOTION_EVENT_ACTION_MOVE or AMOTION_EVENT_ACTION_HOVER_MOVE event.", + mChannel->getName().string()); + return INVALID_OPERATION; + } + + InputMessage::SampleData* newTail = InputMessage::sampleDataPtrIncrement( + mMotionEventSampleDataTail, mMotionEventSampleDataStride); + size_t newBytesUsed = reinterpret_cast(newTail) - + reinterpret_cast(mSharedMessage); + + if (newBytesUsed > mAshmemSize) { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' publisher ~ Cannot append motion sample because the shared memory " + "buffer is full. Buffer size: %d bytes, pointers: %d, samples: %d", + mChannel->getName().string(), + mAshmemSize, mMotionEventPointerCount, mSharedMessage->motion.sampleCount); +#endif + return NO_MEMORY; + } + + int result; + if (mWasDispatched) { + result = sem_trywait(& mSharedMessage->semaphore); + if (result < 0) { + if (errno == EAGAIN) { + // Only possible source of contention is the consumer having consumed (or being in the + // process of consuming) the message and left the semaphore count at 0. +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' publisher ~ Cannot append motion sample because the message has " + "already been consumed.", mChannel->getName().string()); +#endif + return FAILED_TRANSACTION; + } else { + ALOGE("channel '%s' publisher ~ Error %d in sem_trywait.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + } + } + + mMotionEventSampleDataTail->eventTime = eventTime; + for (size_t i = 0; i < mMotionEventPointerCount; i++) { + mMotionEventSampleDataTail->coords[i].copyFrom(pointerCoords[i]); + } + mMotionEventSampleDataTail = newTail; + + mSharedMessage->motion.sampleCount += 1; + + if (mWasDispatched) { + result = sem_post(& mSharedMessage->semaphore); + if (result < 0) { + ALOGE("channel '%s' publisher ~ Error %d in sem_post.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + } + return OK; +} + +status_t InputPublisher::sendDispatchSignal() { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' publisher ~ sendDispatchSignal", + mChannel->getName().string()); +#endif + + mWasDispatched = true; + return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH); +} + +status_t InputPublisher::receiveFinishedSignal(bool* outHandled) { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' publisher ~ receiveFinishedSignal", + mChannel->getName().string()); +#endif + + char signal; + status_t result = mChannel->receiveSignal(& signal); + if (result) { + *outHandled = false; + return result; + } + if (signal == INPUT_SIGNAL_FINISHED_HANDLED) { + *outHandled = true; + } else if (signal == INPUT_SIGNAL_FINISHED_UNHANDLED) { + *outHandled = false; + } else { + ALOGE("channel '%s' publisher ~ Received unexpected signal '%c' from consumer", + mChannel->getName().string(), signal); + return UNKNOWN_ERROR; + } + return OK; +} + +// --- InputConsumer --- + +InputConsumer::InputConsumer(const sp& channel) : + mChannel(channel), mSharedMessage(NULL) { +} + +InputConsumer::~InputConsumer() { + if (mSharedMessage) { + munmap(mSharedMessage, mAshmemSize); + } +} + +status_t InputConsumer::initialize() { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' consumer ~ initialize", + mChannel->getName().string()); +#endif + + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_get_size_region(ashmemFd); + if (result < 0) { + ALOGE("channel '%s' consumer ~ Error %d getting size of ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + mAshmemSize = (size_t) result; + + mSharedMessage = static_cast(mmap(NULL, mAshmemSize, + PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); + if (! mSharedMessage) { + ALOGE("channel '%s' consumer ~ mmap failed on ashmem fd %d.", + mChannel->getName().string(), ashmemFd); + return NO_MEMORY; + } + + return OK; +} + +status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' consumer ~ consume", + mChannel->getName().string()); +#endif + + *outEvent = NULL; + + int ashmemFd = mChannel->getAshmemFd(); + int result = ashmem_pin_region(ashmemFd, 0, 0); + if (result != ASHMEM_NOT_PURGED) { + if (result == ASHMEM_WAS_PURGED) { + ALOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d because it was purged " + "which probably indicates that the publisher and consumer are out of sync.", + mChannel->getName().string(), result, ashmemFd); + return INVALID_OPERATION; + } + + ALOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d.", + mChannel->getName().string(), result, ashmemFd); + return UNKNOWN_ERROR; + } + + if (mSharedMessage->consumed) { + ALOGE("channel '%s' consumer ~ The current message has already been consumed.", + mChannel->getName().string()); + return INVALID_OPERATION; + } + + // Acquire but *never release* the semaphore. Contention on the semaphore is used to signal + // to the publisher that the message has been consumed (or is in the process of being + // consumed). Eventually the publisher will reinitialize the semaphore for the next message. + result = sem_wait(& mSharedMessage->semaphore); + if (result < 0) { + ALOGE("channel '%s' consumer ~ Error %d in sem_wait.", + mChannel->getName().string(), errno); + return UNKNOWN_ERROR; + } + + mSharedMessage->consumed = true; + + switch (mSharedMessage->type) { + case AINPUT_EVENT_TYPE_KEY: { + KeyEvent* keyEvent = factory->createKeyEvent(); + if (! keyEvent) return NO_MEMORY; + + populateKeyEvent(keyEvent); + + *outEvent = keyEvent; + break; + } + + case AINPUT_EVENT_TYPE_MOTION: { + MotionEvent* motionEvent = factory->createMotionEvent(); + if (! motionEvent) return NO_MEMORY; + + populateMotionEvent(motionEvent); + + *outEvent = motionEvent; + break; + } + + default: + ALOGE("channel '%s' consumer ~ Received message of unknown type %d", + mChannel->getName().string(), mSharedMessage->type); + return UNKNOWN_ERROR; + } + + return OK; +} + +status_t InputConsumer::sendFinishedSignal(bool handled) { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' consumer ~ sendFinishedSignal: handled=%d", + mChannel->getName().string(), handled); +#endif + + return mChannel->sendSignal(handled + ? INPUT_SIGNAL_FINISHED_HANDLED + : INPUT_SIGNAL_FINISHED_UNHANDLED); +} + +status_t InputConsumer::receiveDispatchSignal() { +#if DEBUG_TRANSPORT_ACTIONS + ALOGD("channel '%s' consumer ~ receiveDispatchSignal", + mChannel->getName().string()); +#endif + + char signal; + status_t result = mChannel->receiveSignal(& signal); + if (result) { + return result; + } + if (signal != INPUT_SIGNAL_DISPATCH) { + ALOGE("channel '%s' consumer ~ Received unexpected signal '%c' from publisher", + mChannel->getName().string(), signal); + return UNKNOWN_ERROR; + } + return OK; +} + +void InputConsumer::populateKeyEvent(KeyEvent* keyEvent) const { + keyEvent->initialize( + mSharedMessage->deviceId, + mSharedMessage->source, + mSharedMessage->key.action, + mSharedMessage->key.flags, + mSharedMessage->key.keyCode, + mSharedMessage->key.scanCode, + mSharedMessage->key.metaState, + mSharedMessage->key.repeatCount, + mSharedMessage->key.downTime, + mSharedMessage->key.eventTime); +} + +void InputConsumer::populateMotionEvent(MotionEvent* motionEvent) const { + motionEvent->initialize( + mSharedMessage->deviceId, + mSharedMessage->source, + mSharedMessage->motion.action, + mSharedMessage->motion.flags, + mSharedMessage->motion.edgeFlags, + mSharedMessage->motion.metaState, + mSharedMessage->motion.buttonState, + mSharedMessage->motion.xOffset, + mSharedMessage->motion.yOffset, + mSharedMessage->motion.xPrecision, + mSharedMessage->motion.yPrecision, + mSharedMessage->motion.downTime, + mSharedMessage->motion.sampleData[0].eventTime, + mSharedMessage->motion.pointerCount, + mSharedMessage->motion.pointerProperties, + mSharedMessage->motion.sampleData[0].coords); + + size_t sampleCount = mSharedMessage->motion.sampleCount; + if (sampleCount > 1) { + InputMessage::SampleData* sampleData = mSharedMessage->motion.sampleData; + size_t sampleDataStride = InputMessage::sampleDataStride( + mSharedMessage->motion.pointerCount); + + while (--sampleCount > 0) { + sampleData = InputMessage::sampleDataPtrIncrement(sampleData, sampleDataStride); + motionEvent->addSample(sampleData->eventTime, sampleData->coords); + } + } +} + +} // namespace android diff --git a/widget/gonk/libui/InputTransport.h b/widget/gonk/libui/InputTransport.h new file mode 100644 index 00000000000..83948ff2206 --- /dev/null +++ b/widget/gonk/libui/InputTransport.h @@ -0,0 +1,338 @@ +/* + * 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_TRANSPORT_H +#define _UI_INPUT_TRANSPORT_H + +/** + * Native input transport. + * + * Uses anonymous shared memory as a whiteboard for sending input events from an + * InputPublisher to an InputConsumer and ensuring appropriate synchronization. + * One interesting feature is that published events can be updated in place as long as they + * have not yet been consumed. + * + * The InputPublisher and InputConsumer only take care of transferring event data + * over an InputChannel and sending synchronization signals. The InputDispatcher and InputQueue + * build on these abstractions to add multiplexing and queueing. + */ + +#include +#include "Input.h" +#include +#include +#include +#include + +namespace android { + +/* + * An input channel consists of a shared memory buffer and a pair of pipes + * used to send input messages from an InputPublisher to an InputConsumer + * across processes. Each channel has a descriptive name for debugging purposes. + * + * Each endpoint has its own InputChannel object that specifies its own file descriptors. + * + * The input channel is closed when all references to it are released. + */ +class InputChannel : public RefBase { +protected: + virtual ~InputChannel(); + +public: + InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd, + int32_t sendPipeFd); + + /* Creates a pair of input channels and their underlying shared memory buffers + * and pipes. + * + * Returns OK on success. + */ + static status_t openInputChannelPair(const String8& name, + sp& outServerChannel, sp& outClientChannel); + + inline String8 getName() const { return mName; } + inline int32_t getAshmemFd() const { return mAshmemFd; } + inline int32_t getReceivePipeFd() const { return mReceivePipeFd; } + inline int32_t getSendPipeFd() const { return mSendPipeFd; } + + /* Sends a signal to the other endpoint. + * + * Returns OK on success. + * Returns DEAD_OBJECT if the channel's peer has been closed. + * Other errors probably indicate that the channel is broken. + */ + status_t sendSignal(char signal); + + /* Receives a signal send by the other endpoint. + * (Should only call this after poll() indicates that the receivePipeFd has available input.) + * + * 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 receiveSignal(char* outSignal); + +private: + String8 mName; + int32_t mAshmemFd; + int32_t mReceivePipeFd; + int32_t mSendPipeFd; +}; + +/* + * Private intermediate representation of input events as messages written into an + * ashmem buffer. + */ +struct InputMessage { + /* Semaphore count is set to 1 when the message is published. + * It becomes 0 transiently while the publisher updates the message. + * It becomes 0 permanently when the consumer consumes the message. + */ + sem_t semaphore; + + /* Initialized to false by the publisher. + * Set to true by the consumer when it consumes the message. + */ + bool consumed; + + int32_t type; + + struct SampleData { + nsecs_t eventTime; + PointerCoords coords[0]; // variable length + }; + + int32_t deviceId; + int32_t source; + + union { + struct { + int32_t action; + int32_t flags; + int32_t keyCode; + int32_t scanCode; + int32_t metaState; + int32_t repeatCount; + nsecs_t downTime; + nsecs_t eventTime; + } key; + + struct { + 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; + PointerProperties pointerProperties[MAX_POINTERS]; + size_t sampleCount; + SampleData sampleData[0]; // variable length + } motion; + }; + + /* Gets the number of bytes to add to step to the next SampleData object in a motion + * event message for a given number of pointers. + */ + static inline size_t sampleDataStride(size_t pointerCount) { + return sizeof(InputMessage::SampleData) + pointerCount * sizeof(PointerCoords); + } + + /* Adds the SampleData stride to the given pointer. */ + static inline SampleData* sampleDataPtrIncrement(SampleData* ptr, size_t stride) { + return reinterpret_cast(reinterpret_cast(ptr) + stride); + } +}; + +/* + * Publishes input events to an anonymous shared memory buffer. + * Uses atomic operations to coordinate shared access with a single concurrent consumer. + */ +class InputPublisher { +public: + /* Creates a publisher associated with an input channel. */ + explicit InputPublisher(const sp& channel); + + /* Destroys the publisher and releases its input channel. */ + ~InputPublisher(); + + /* Gets the underlying input channel. */ + inline sp getChannel() { return mChannel; } + + /* Prepares the publisher for use. Must be called before it is used. + * Returns OK on success. + * + * This method implicitly calls reset(). */ + status_t initialize(); + + /* Resets the publisher to its initial state and unpins its ashmem buffer. + * Returns OK on success. + * + * Should be called after an event has been consumed to release resources used by the + * publisher until the next event is ready to be published. + */ + status_t reset(); + + /* Publishes a key event to the ashmem buffer. + * + * Returns OK on success. + * Returns INVALID_OPERATION if the publisher has not been reset. + */ + status_t publishKeyEvent( + 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 ashmem buffer. + * + * Returns OK on success. + * Returns INVALID_OPERATION if the publisher has not been reset. + * Returns BAD_VALUE if pointerCount is less than 1 or greater than MAX_POINTERS. + */ + status_t publishMotionEvent( + 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); + + /* Appends a motion sample to a motion event unless already consumed. + * + * Returns OK on success. + * Returns INVALID_OPERATION if the current event is not a AMOTION_EVENT_ACTION_MOVE event. + * Returns FAILED_TRANSACTION if the current event has already been consumed. + * Returns NO_MEMORY if the buffer is full and no additional samples can be added. + */ + status_t appendMotionSample( + nsecs_t eventTime, + const PointerCoords* pointerCoords); + + /* Sends a dispatch signal to the consumer to inform it that a new message is available. + * + * Returns OK on success. + * Errors probably indicate that the channel is broken. + */ + status_t sendDispatchSignal(); + + /* Receives the finished signal from the consumer in reply to the original dispatch signal. + * Returns whether the consumer handled the message. + * + * Returns OK on success. + * Returns WOULD_BLOCK if there is no signal present. + * Other errors probably indicate that the channel is broken. + */ + status_t receiveFinishedSignal(bool* outHandled); + +private: + sp mChannel; + + size_t mAshmemSize; + InputMessage* mSharedMessage; + bool mPinned; + bool mSemaphoreInitialized; + bool mWasDispatched; + + size_t mMotionEventPointerCount; + InputMessage::SampleData* mMotionEventSampleDataTail; + size_t mMotionEventSampleDataStride; + + status_t publishInputEvent( + int32_t type, + int32_t deviceId, + int32_t source); +}; + +/* + * Consumes input events from an anonymous shared memory buffer. + * Uses atomic operations to coordinate shared access with a single concurrent publisher. + */ +class InputConsumer { +public: + /* Creates a consumer associated with an input channel. */ + explicit InputConsumer(const sp& channel); + + /* Destroys the consumer and releases its input channel. */ + ~InputConsumer(); + + /* Gets the underlying input channel. */ + inline sp getChannel() { return mChannel; } + + /* Prepares the consumer for use. Must be called before it is used. */ + status_t initialize(); + + /* Consumes the input event in the buffer and copies its contents into + * an InputEvent object created using the specified factory. + * This operation will block if the publisher is updating the event. + * + * Returns OK on success. + * Returns INVALID_OPERATION if there is no currently published event. + * Returns NO_MEMORY if the event could not be created. + */ + status_t consume(InputEventFactoryInterface* factory, InputEvent** outEvent); + + /* Sends a finished signal to the publisher to inform it that the current message is + * finished processing and specifies whether the message was handled by the consumer. + * + * Returns OK on success. + * Errors probably indicate that the channel is broken. + */ + status_t sendFinishedSignal(bool handled); + + /* Receives the dispatched signal from the publisher. + * + * Returns OK on success. + * Returns WOULD_BLOCK if there is no signal present. + * Other errors probably indicate that the channel is broken. + */ + status_t receiveDispatchSignal(); + +private: + sp mChannel; + + size_t mAshmemSize; + InputMessage* mSharedMessage; + + void populateKeyEvent(KeyEvent* keyEvent) const; + void populateMotionEvent(MotionEvent* motionEvent) const; +}; + +} // namespace android + +#endif // _UI_INPUT_TRANSPORT_H diff --git a/widget/gonk/libui/InputWindow.cpp b/widget/gonk/libui/InputWindow.cpp new file mode 100644 index 00000000000..23c3f0c9a92 --- /dev/null +++ b/widget/gonk/libui/InputWindow.cpp @@ -0,0 +1,68 @@ +/* + * 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 + +namespace android { + +// --- InputWindowInfo --- + +bool InputWindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const { +#ifdef HAVE_ANDROID_OS + return touchableRegion.contains(x, y); +#else + return false; +#endif +} + +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), 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 00000000000..3da7fd0a136 --- /dev/null +++ b/widget/gonk/libui/InputWindow.h @@ -0,0 +1,204 @@ +/* + * 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 +#include +#include + +#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, + }; + + sp 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; +#ifdef HAVE_ANDROID_OS + SkRegion touchableRegion; +#endif + bool visible; + bool canReceiveKeys; + bool hasFocus; + bool hasWallpaper; + bool paused; + int32_t layer; + int32_t ownerPid; + int32_t ownerUid; + int32_t inputFeatures; + + 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; + + inline const InputWindowInfo* getInfo() const { + return mInfo; + } + + inline sp getInputChannel() const { + return mInfo ? mInfo->inputChannel : NULL; + } + + inline String8 getName() const { + return mInfo ? mInfo->name : String8(""); + } + + 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); + 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 00000000000..a1f80c87f77 --- /dev/null +++ b/widget/gonk/libui/KeyCharacterMap.cpp @@ -0,0 +1,838 @@ +/* + * 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 +#include +#include +#include "Keyboard.h" +#include "KeyCharacterMap.h" +#include +#include +#include +#include + +// 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 --- + +KeyCharacterMap::KeyCharacterMap() : + mType(KEYBOARD_TYPE_UNKNOWN) { +} + +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, KeyCharacterMap** outMap) { + *outMap = NULL; + + Tokenizer* tokenizer; + status_t status = Tokenizer::open(filename, &tokenizer); + if (status) { + ALOGE("Error %d opening key character map file %s.", status, filename.string()); + } else { + KeyCharacterMap* map = new KeyCharacterMap(); + if (!map) { + 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, 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; +} + +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& 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; +} + +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 ((behavior->metaState & metaState) == behavior->metaState) { + *outKey = key; + *outBehavior = behavior; + return true; + } + behavior = behavior->next; + } + } + 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& 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& 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& 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& 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& 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); + } +} + + +// --- KeyCharacterMap::Key --- + +KeyCharacterMap::Key::Key() : + label(0), number(0), 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::Parser --- + +KeyCharacterMap::Parser::Parser(KeyCharacterMap* map, Tokenizer* tokenizer) : + mMap(map), mTokenizer(tokenizer), 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 == "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()) { + ALOGE("%s: Expected end of line, 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: Missing required keyboard 'type' 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 { + 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::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() { + String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); + if (token == "}") { + mState = STATE_TOP; + return NO_ERROR; + } + + Vector 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()); + + // Add the behavior. + Key* key = mMap->mKeys.valueFor(mKeyCode); + 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::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 00000000000..1184fa7524d --- /dev/null +++ b/widget/gonk/libui/KeyCharacterMap.h @@ -0,0 +1,199 @@ +/* + * 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 _UI_KEY_CHARACTER_MAP_H +#define _UI_KEY_CHARACTER_MAP_H + +#include + +#include "Input.h" +#include +#include +#include +#include +#include + +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. + */ +class KeyCharacterMap { +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, + }; + + // Substitute key code and meta state for fallback action. + struct FallbackAction { + int32_t keyCode; + int32_t metaState; + }; + + ~KeyCharacterMap(); + + static status_t load(const String8& filename, KeyCharacterMap** outMap); + + /* 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& outEvents) const; + +private: + struct Behavior { + Behavior(); + + /* 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(); + + /* 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; + State mState; + int32_t mKeyCode; + + public: + Parser(KeyCharacterMap* map, Tokenizer* tokenizer); + ~Parser(); + status_t parse(); + + private: + status_t parseType(); + status_t parseKey(); + status_t parseKeyProperty(); + status_t parseModifier(const String8& token, int32_t* outMetaState); + status_t parseCharacterLiteral(char16_t* outCharacter); + }; + + KeyedVector mKeys; + int mType; + + KeyCharacterMap(); + + bool getKey(int32_t keyCode, const Key** outKey) const; + bool getKeyBehavior(int32_t keyCode, int32_t metaState, + const Key** outKey, const Behavior** outBehavior) const; + + bool findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMetaState) const; + + static void addKey(Vector& outEvents, + int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time); + static void addMetaKeys(Vector& outEvents, + int32_t deviceId, int32_t metaState, bool down, nsecs_t time, + int32_t* currentMetaState); + static bool addSingleEphemeralMetaKey(Vector& 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& 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& outEvents, + int32_t deviceId, int32_t metaState, nsecs_t time, + int32_t keyCode, int32_t keyMetaState, + int32_t* currentMetaState); +}; + +} // namespace android + +#endif // _UI_KEY_CHARACTER_MAP_H diff --git a/widget/gonk/libui/KeyLayoutMap.cpp b/widget/gonk/libui/KeyLayoutMap.cpp new file mode 100644 index 00000000000..5f75005aa49 --- /dev/null +++ b/widget/gonk/libui/KeyLayoutMap.cpp @@ -0,0 +1,341 @@ +/* + * 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 +#include +#include "Keyboard.h" +#include "KeyLayoutMap.h" +#include +#include +#include +#include + +// 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, KeyLayoutMap** outMap) { + *outMap = NULL; + + Tokenizer* tokenizer; + status_t status = Tokenizer::open(filename, &tokenizer); + if (status) { + ALOGE("Error %d opening key layout map file %s.", status, filename.string()); + } else { + KeyLayoutMap* map = new KeyLayoutMap(); + if (!map) { + 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, 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) { + delete map; + } else { + *outMap = map; + } + } + delete tokenizer; + } + return status; +} + +status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t* keyCode, uint32_t* flags) const { + ssize_t index = mKeys.indexOfKey(scanCode); + if (index < 0) { +#if DEBUG_MAPPING + ALOGD("mapKey: scanCode=%d ~ Failed.", scanCode); +#endif + *keyCode = AKEYCODE_UNKNOWN; + *flags = 0; + return NAME_NOT_FOUND; + } + + const Key& k = mKeys.valueAt(index); + *keyCode = k.keyCode; + *flags = k.flags; + +#if DEBUG_MAPPING + ALOGD("mapKey: scanCode=%d ~ Result keyCode=%d, flags=0x%08x.", scanCode, *keyCode, *flags); +#endif + return NO_ERROR; +} + +status_t KeyLayoutMap::findScanCodesForKey(int32_t keyCode, Vector* outScanCodes) const { + const size_t N = mKeys.size(); + for (size_t i=0; iadd(mKeys.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; +} + + +// --- 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 { + ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(), + keywordToken.string()); + return BAD_VALUE; + } + + mTokenizer->skipDelimiters(WHITESPACE); + 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; +} + +status_t KeyLayoutMap::Parser::parseKey() { + String8 scanCodeToken = mTokenizer->nextToken(WHITESPACE); + char* end; + int32_t scanCode = int32_t(strtol(scanCodeToken.string(), &end, 0)); + if (*end) { + ALOGE("%s: Expected key scan code number, got '%s'.", mTokenizer->getLocation().string(), + scanCodeToken.string()); + return BAD_VALUE; + } + if (mMap->mKeys.indexOfKey(scanCode) >= 0) { + ALOGE("%s: Duplicate entry for key scan code '%s'.", mTokenizer->getLocation().string(), + scanCodeToken.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()) 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: scanCode=%d, keyCode=%d, flags=0x%08x.", scanCode, keyCode, flags); +#endif + Key key; + key.keyCode = keyCode; + key.flags = flags; + mMap->mKeys.add(scanCode, 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()) { + 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; +} + +}; diff --git a/widget/gonk/libui/KeyLayoutMap.h b/widget/gonk/libui/KeyLayoutMap.h new file mode 100644 index 00000000000..d82d0c8e0ef --- /dev/null +++ b/widget/gonk/libui/KeyLayoutMap.h @@ -0,0 +1,99 @@ +/* + * 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 _UI_KEY_LAYOUT_MAP_H +#define _UI_KEY_LAYOUT_MAP_H + +#include +#include +#include +#include + +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. + */ +class KeyLayoutMap { +public: + ~KeyLayoutMap(); + + static status_t load(const String8& filename, KeyLayoutMap** outMap); + + status_t mapKey(int32_t scanCode, int32_t* keyCode, uint32_t* flags) const; + status_t findScanCodesForKey(int32_t keyCode, Vector* outScanCodes) const; + + status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const; + +private: + struct Key { + int32_t keyCode; + uint32_t flags; + }; + + KeyedVector mKeys; + KeyedVector mAxes; + + KeyLayoutMap(); + + class Parser { + KeyLayoutMap* mMap; + Tokenizer* mTokenizer; + + public: + Parser(KeyLayoutMap* map, Tokenizer* tokenizer); + ~Parser(); + status_t parse(); + + private: + status_t parseKey(); + status_t parseAxis(); + }; +}; + +} // namespace android + +#endif // _UI_KEY_LAYOUT_MAP_H diff --git a/widget/gonk/libui/Keyboard.cpp b/widget/gonk/libui/Keyboard.cpp new file mode 100644 index 00000000000..a08ec95a63c --- /dev/null +++ b/widget/gonk/libui/Keyboard.cpp @@ -0,0 +1,303 @@ +/* + * 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 +#include +#include + +#include "Keyboard.h" +#include "KeycodeLabels.h" +#include "KeyLayoutMap.h" +#include "KeyCharacterMap.h" +#include +#include +#include + +namespace android { + +// --- KeyMap --- + +KeyMap::KeyMap() : + keyLayoutMap(NULL), keyCharacterMap(NULL) { +} + +KeyMap::~KeyMap() { + delete keyLayoutMap; + delete keyCharacterMap; +} + +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; + } + + KeyLayoutMap* map; + status_t status = KeyLayoutMap::load(path, &map); + if (status) { + return status; + } + + keyLayoutFile.setTo(path); + keyLayoutMap = map; + 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; + } + + KeyCharacterMap* map; + status_t status = KeyCharacterMap::load(path, &map); + if (status) { + return status; + } + + keyCharacterMapFile.setTo(path); + keyCharacterMap = map; + 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); +} + +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) { + int32_t mask; + 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 00000000000..3e677a1f086 --- /dev/null +++ b/widget/gonk/libui/Keyboard.h @@ -0,0 +1,119 @@ +/* + * 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_KEYBOARD_H +#define _UI_KEYBOARD_H + +#include "Input.h" +#include +#include +#include + +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; + KeyLayoutMap* keyLayoutMap; + + String8 keyCharacterMapFile; + 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); + +/** + * 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 // _UI_KEYBOARD_H diff --git a/widget/gonk/libui/KeycodeLabels.h b/widget/gonk/libui/KeycodeLabels.h new file mode 100644 index 00000000000..c5bd0c54467 --- /dev/null +++ b/widget/gonk/libui/KeycodeLabels.h @@ -0,0 +1,310 @@ +/* + * 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 _UI_KEYCODE_LABELS_H +#define _UI_KEYCODE_LABELS_H + +#include + +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 }, + + // 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 } +}; + +#endif // _UI_KEYCODE_LABELS_H diff --git a/widget/gonk/libui/PixelFormat.cpp b/widget/gonk/libui/PixelFormat.cpp new file mode 100644 index 00000000000..ba86bf3d864 --- /dev/null +++ b/widget/gonk/libui/PixelFormat.cpp @@ -0,0 +1,119 @@ +/* + * 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. + */ + +#include "PixelFormat.h" +#include +#include + +namespace android { + +static const int COMPONENT_YUV = 0xFF; + +size_t PixelFormatInfo::getScanlineSize(unsigned int width) const +{ + size_t size; + if (components == COMPONENT_YUV) { + // YCbCr formats are different. + size = (width * bitsPerPixel)>>3; + } else { + size = width * bytesPerPixel; + } + return size; +} + +#ifdef HAVE_ANDROID_OS +ssize_t bytesPerPixel(PixelFormat format) +{ + PixelFormatInfo info; + status_t err = getPixelFormatInfo(format, &info); + return (err < 0) ? err : info.bytesPerPixel; +} + +ssize_t bitsPerPixel(PixelFormat format) +{ + PixelFormatInfo info; + status_t err = getPixelFormatInfo(format, &info); + return (err < 0) ? err : info.bitsPerPixel; +} + +status_t getPixelFormatInfo(PixelFormat format, PixelFormatInfo* info) +{ + if (format < 0) + return BAD_VALUE; + + if (info->version != sizeof(PixelFormatInfo)) + return INVALID_OPERATION; + + // YUV format from the HAL are handled here + switch (format) { + case HAL_PIXEL_FORMAT_YCbCr_422_SP: + case HAL_PIXEL_FORMAT_YCbCr_422_I: + info->bitsPerPixel = 16; + goto done; + case HAL_PIXEL_FORMAT_YCrCb_420_SP: + case HAL_PIXEL_FORMAT_YV12: + info->bitsPerPixel = 12; + done: + info->format = format; + info->components = COMPONENT_YUV; + info->bytesPerPixel = 1; + info->h_alpha = 0; + info->l_alpha = 0; + info->h_red = info->h_green = info->h_blue = 8; + info->l_red = info->l_green = info->l_blue = 0; + return NO_ERROR; + } + + size_t numEntries; + const GGLFormat *i = gglGetPixelFormatTable(&numEntries) + format; + bool valid = uint32_t(format) < numEntries; + if (!valid) { + return BAD_INDEX; + } + + #define COMPONENT(name) \ + case GGL_##name: info->components = PixelFormatInfo::name; break; + + switch (i->components) { + COMPONENT(ALPHA) + COMPONENT(RGB) + COMPONENT(RGBA) + COMPONENT(LUMINANCE) + COMPONENT(LUMINANCE_ALPHA) + default: + return BAD_INDEX; + } + + #undef COMPONENT + + info->format = format; + info->bytesPerPixel = i->size; + info->bitsPerPixel = i->bitsPerPixel; + info->h_alpha = i->ah; + info->l_alpha = i->al; + info->h_red = i->rh; + info->l_red = i->rl; + info->h_green = i->gh; + info->l_green = i->gl; + info->h_blue = i->bh; + info->l_blue = i->bl; + + return NO_ERROR; +} +#endif // HAVE_ANDROID_OS + +}; // namespace android + diff --git a/widget/gonk/libui/PixelFormat.h b/widget/gonk/libui/PixelFormat.h new file mode 100644 index 00000000000..2e67754d50e --- /dev/null +++ b/widget/gonk/libui/PixelFormat.h @@ -0,0 +1,139 @@ +/* + * 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. + */ + +// + +// Pixel formats used across the system. +// These formats might not all be supported by all renderers, for instance +// skia or SurfaceFlinger are not required to support all of these formats +// (either as source or destination) + +// XXX: we should consolidate these formats and skia's + +#ifndef UI_PIXELFORMAT_H +#define UI_PIXELFORMAT_H + +#include +#include +#include +#include +#include + +namespace android { + +enum { + // + // these constants need to match those + // in graphics/PixelFormat.java & pixelflinger/format.h + // + PIXEL_FORMAT_UNKNOWN = 0, + PIXEL_FORMAT_NONE = 0, + + // logical pixel formats used by the SurfaceFlinger ----------------------- + PIXEL_FORMAT_CUSTOM = -4, + // Custom pixel-format described by a PixelFormatInfo structure + + PIXEL_FORMAT_TRANSLUCENT = -3, + // System chooses a format that supports translucency (many alpha bits) + + PIXEL_FORMAT_TRANSPARENT = -2, + // System chooses a format that supports transparency + // (at least 1 alpha bit) + + PIXEL_FORMAT_OPAQUE = -1, + // System chooses an opaque format (no alpha bits required) + + // real pixel formats supported for rendering ----------------------------- + + PIXEL_FORMAT_RGBA_8888 = HAL_PIXEL_FORMAT_RGBA_8888, // 4x8-bit RGBA + PIXEL_FORMAT_RGBX_8888 = HAL_PIXEL_FORMAT_RGBX_8888, // 4x8-bit RGB0 + PIXEL_FORMAT_RGB_888 = HAL_PIXEL_FORMAT_RGB_888, // 3x8-bit RGB + PIXEL_FORMAT_RGB_565 = HAL_PIXEL_FORMAT_RGB_565, // 16-bit RGB + PIXEL_FORMAT_BGRA_8888 = HAL_PIXEL_FORMAT_BGRA_8888, // 4x8-bit BGRA + PIXEL_FORMAT_RGBA_5551 = HAL_PIXEL_FORMAT_RGBA_5551, // 16-bit ARGB + PIXEL_FORMAT_RGBA_4444 = HAL_PIXEL_FORMAT_RGBA_4444, // 16-bit ARGB + PIXEL_FORMAT_A_8 = GGL_PIXEL_FORMAT_A_8, // 8-bit A + PIXEL_FORMAT_L_8 = GGL_PIXEL_FORMAT_L_8, // 8-bit L (R=G=B=L) + PIXEL_FORMAT_LA_88 = GGL_PIXEL_FORMAT_LA_88, // 16-bit LA + PIXEL_FORMAT_RGB_332 = GGL_PIXEL_FORMAT_RGB_332, // 8-bit RGB + + // New formats can be added if they're also defined in + // pixelflinger/format.h +}; + +typedef int32_t PixelFormat; + +struct PixelFormatInfo +{ + enum { + INDEX_ALPHA = 0, + INDEX_RED = 1, + INDEX_GREEN = 2, + INDEX_BLUE = 3 + }; + + enum { // components + ALPHA = 1, + RGB = 2, + RGBA = 3, + LUMINANCE = 4, + LUMINANCE_ALPHA = 5, + OTHER = 0xFF + }; + + struct szinfo { + uint8_t h; + uint8_t l; + }; + + inline PixelFormatInfo() : version(sizeof(PixelFormatInfo)) { } + size_t getScanlineSize(unsigned int width) const; + size_t getSize(size_t ci) const { + return (ci <= 3) ? (cinfo[ci].h - cinfo[ci].l) : 0; + } + size_t version; + PixelFormat format; + size_t bytesPerPixel; + size_t bitsPerPixel; + union { + szinfo cinfo[4]; + struct { + uint8_t h_alpha; + uint8_t l_alpha; + uint8_t h_red; + uint8_t l_red; + uint8_t h_green; + uint8_t l_green; + uint8_t h_blue; + uint8_t l_blue; + }; + }; + uint8_t components; + uint8_t reserved0[3]; + uint32_t reserved1; +}; + +#ifdef HAVE_ANDROID_OS +// Consider caching the results of these functions are they're not +// guaranteed to be fast. +ssize_t bytesPerPixel(PixelFormat format); +ssize_t bitsPerPixel(PixelFormat format); +status_t getPixelFormatInfo(PixelFormat format, PixelFormatInfo* info); +#endif + +}; // namespace android + +#endif // UI_PIXELFORMAT_H diff --git a/widget/gonk/libui/PointerController.cpp b/widget/gonk/libui/PointerController.cpp new file mode 100644 index 00000000000..fc828a6afee --- /dev/null +++ b/widget/gonk/libui/PointerController.cpp @@ -0,0 +1,601 @@ +/* + * 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 + +#include +#include +#include +#include +#include + +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& policy, + const sp& looper, const sp& 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::setDisplaySize(int32_t width, int32_t height) { + AutoMutex _l(mLock); + + 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(); + updatePointerLocked(); + } +} + +void PointerController::setDisplayOrientation(int32_t orientation) { + AutoMutex _l(mLock); + + 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; + 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 00000000000..bf7b7bb7aa8 --- /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 "DisplayInfo.h" +#include "Input.h" +#include +#include +#include + +#include + +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& policy, + const sp& looper, const sp& 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 setDisplaySize(int32_t width, int32_t height); + void setDisplayOrientation(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; + float alpha; + float scale; + float x, y; + + inline Spot(uint32_t id, const sp& 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 mPolicy; + sp mLooper; + sp mSpriteController; + sp 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 pointerSprite; + SpriteIcon pointerIcon; + bool pointerIconChanged; + + int32_t buttonState; + + Vector spots; + Vector > 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/SpriteController.cpp b/widget/gonk/libui/SpriteController.cpp new file mode 100644 index 00000000000..b15d4c8d309 --- /dev/null +++ b/widget/gonk/libui/SpriteController.cpp @@ -0,0 +1,481 @@ +/* + * 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 +#include + +#include +#include +#include +#include +#include + +namespace android { + +// --- SpriteController --- + +SpriteController::SpriteController(const sp& looper, int32_t overlayLayer) : + mLooper(looper), mOverlayLayer(overlayLayer) { + mHandler = new WeakMessageHandler(this); + + mLocked.transactionNestingCount = 0; + mLocked.deferredSpriteUpdate = false; +} + +SpriteController::~SpriteController() { + mLooper->removeMessages(mHandler); + + if (mSurfaceComposerClient != NULL) { + mSurfaceComposerClient->dispose(); + mSurfaceComposerClient.clear(); + } +} + +sp 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; + mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); + } +} + +void SpriteController::invalidateSpriteLocked(const sp& sprite) { + bool wasEmpty = mLocked.invalidatedSprites.isEmpty(); + mLocked.invalidatedSprites.push(sprite); + if (wasEmpty) { + if (mLocked.transactionNestingCount != 0) { + mLocked.deferredSpriteUpdate = true; + } else { + mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); + } + } +} + +void SpriteController::disposeSurfaceLocked(const sp& 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; + } +} + +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 updates; + size_t numSprites; + { // acquire lock + AutoMutex _l(mLock); + + numSprites = mLocked.invalidatedSprites.size(); + for (size_t i = 0; i < numSprites; i++) { + const sp& sprite = mLocked.invalidatedSprites.itemAt(i); + + updates.push(SpriteUpdate(sprite, sprite->getStateLocked())); + sprite->resetDirtyLocked(); + } + mLocked.invalidatedSprites.clear(); + } // release lock + + // Create missing surfaces. + bool surfaceChanged = false; + 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; + } + } + } + + // Resize sprites if needed, inside a global transaction. + 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; + } + } + } + } + } + } + if (haveGlobalTransaction) { + SurfaceComposerClient::closeGlobalTransaction(); + } + + // 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; + } + + if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn + && update.state.wantSurfaceVisible()) { + sp surface = update.state.surfaceControl->getSurface(); + Surface::SurfaceInfo surfaceInfo; + status_t status = surface->lock(&surfaceInfo); + if (status) { + ALOGE("Error %d locking sprite surface before drawing.", status); + } else { + SkBitmap surfaceBitmap; + ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format); + surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config, + surfaceInfo.w, surfaceInfo.h, bpr); + surfaceBitmap.setPixels(surfaceInfo.bits); + + SkCanvas surfaceCanvas; + surfaceCanvas.setBitmapDevice(surfaceBitmap); + + SkPaint paint; + paint.setXfermodeMode(SkXfermode::kSrc_Mode); + surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint); + + if (surfaceInfo.w > uint32_t(update.state.icon.bitmap.width())) { + paint.setColor(0); // transparent fill color + surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0, + surfaceInfo.w, update.state.icon.bitmap.height(), paint); + } + if (surfaceInfo.h > uint32_t(update.state.icon.bitmap.height())) { + paint.setColor(0); // transparent fill color + surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(), + surfaceInfo.w, surfaceInfo.h, 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; + } + } + } + } + + // 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(surfaceLayer); + 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; + } + } + } + } + + if (haveTransaction) { + SurfaceComposerClient::closeGlobalTransaction(); + } + + // 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 + + // 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() { + // Collect disposed surfaces. + Vector > 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(); +} + +void SpriteController::ensureSurfaceComposerClient() { + if (mSurfaceComposerClient == NULL) { + mSurfaceComposerClient = new SurfaceComposerClient(); + } +} + +sp SpriteController::obtainSurface(int32_t width, int32_t height) { + ensureSurfaceComposerClient(); + + sp surfaceControl = mSurfaceComposerClient->createSurface( + String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888); + if (surfaceControl == NULL || !surfaceControl->isValid() + || !surfaceControl->getSurface()->isValid()) { + ALOGE("Error creating sprite surface."); + return NULL; + } + return surfaceControl; +} + + +// --- SpriteController::SpriteImpl --- + +SpriteController::SpriteImpl::SpriteImpl(const sp controller) : + mController(controller) { +} + +SpriteController::SpriteImpl::~SpriteImpl() { + AutoMutex _m(mController->mLock); + + // 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(); + } +} + +void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) { + AutoMutex _l(mController->mLock); + + 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); +} + +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 00000000000..50ae8a53886 --- /dev/null +++ b/widget/gonk/libui/SpriteController.h @@ -0,0 +1,295 @@ +/* + * 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 +#include + +#include +#include +#include + +#include + +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) { } + inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) : + bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { } + + SkBitmap bitmap; + float hotSpotX; + float hotSpotY; + + inline SpriteIcon copy() const { + SkBitmap bitmapCopy; + bitmap.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config); + return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY); + } + + inline void reset() { + bitmap.reset(); + hotSpotX = 0; + hotSpotY = 0; + } + + inline bool isValid() const { + return !bitmap.isNull() && !bitmap.empty(); + } +}; + +/* + * 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, int32_t overlayLayer); + + /* Creates a new sprite, initially invisible. */ + sp 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; + + sp surfaceControl; + 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 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; + } + + inline void setSurfaceLocked(const sp& 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; + } + + private: + sp 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 sprite, const SpriteState& state) : + sprite(sprite), state(state), surfaceChanged(false) { + } + + sp sprite; + SpriteState state; + bool surfaceChanged; + }; + + mutable Mutex mLock; + + sp mLooper; + const int32_t mOverlayLayer; + sp mHandler; + + sp mSurfaceComposerClient; + + struct Locked { + Vector > invalidatedSprites; + Vector > disposedSurfaces; + uint32_t transactionNestingCount; + bool deferredSpriteUpdate; + } mLocked; // guarded by mLock + + void invalidateSpriteLocked(const sp& sprite); + void disposeSurfaceLocked(const sp& surfaceControl); + + void handleMessage(const Message& message); + void doUpdateSprites(); + void doDisposeSurfaces(); + + void ensureSurfaceComposerClient(); + sp obtainSurface(int32_t width, int32_t height); +}; + +} // namespace android + +#endif // _UI_SPRITES_H diff --git a/widget/gonk/libui/VirtualKeyMap.cpp b/widget/gonk/libui/VirtualKeyMap.cpp new file mode 100644 index 00000000000..fe283898da1 --- /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 +#include +#include "VirtualKeyMap.h" +#include +#include +#include +#include + +// 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 00000000000..2810b3afe4c --- /dev/null +++ b/widget/gonk/libui/VirtualKeyMap.h @@ -0,0 +1,79 @@ +/* + * 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_VIRTUAL_KEY_MAP_H +#define _UI_VIRTUAL_KEY_MAP_H + +#include + +#include "Input.h" +#include +#include +#include +#include +#include + +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. + */ +class VirtualKeyMap { +public: + ~VirtualKeyMap(); + + static status_t load(const String8& filename, VirtualKeyMap** outMap); + + inline const Vector& 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 mVirtualKeys; + + VirtualKeyMap(); +}; + +} // namespace android + +#endif // _UI_KEY_CHARACTER_MAP_H diff --git a/widget/gonk/nsAppShell.cpp b/widget/gonk/nsAppShell.cpp index 718f306986b..57ba60bcce1 100644 --- a/widget/gonk/nsAppShell.cpp +++ b/widget/gonk/nsAppShell.cpp @@ -66,9 +66,9 @@ #include "nsWindow.h" #include "android/log.h" -#include "ui/EventHub.h" -#include "ui/InputReader.h" -#include "ui/InputDispatcher.h" +#include "libui/EventHub.h" +#include "libui/InputReader.h" +#include "libui/InputDispatcher.h" #define LOG(args...) \ __android_log_print(ANDROID_LOG_INFO, "Gonk" , ## args) @@ -165,10 +165,12 @@ addDOMTouch(UserInputData& data, nsTouchEvent& event, int i) const Touch& touch = data.motion.touches[i]; event.touches.AppendElement( new nsDOMTouch(touch.id, - nsIntPoint(touch.coords.x, touch.coords.y), - nsIntPoint(touch.coords.size, touch.coords.size), + nsIntPoint(touch.coords.getX(), touch.coords.getY()), + nsIntPoint(touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), + touch.coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE)), 0, - touch.coords.pressure)); + touch.coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE)) + ); } static nsEventStatus @@ -272,19 +274,13 @@ maybeSendKeyEvent(int keyCode, bool pressed, uint64_t timeMs) } class GeckoInputReaderPolicy : public InputReaderPolicyInterface { + InputReaderConfiguration mConfig; public: GeckoInputReaderPolicy() {} - virtual bool getDisplayInfo(int32_t displayId, - int32_t* width, int32_t* height, int32_t* orientation); - virtual bool filterTouchEvents(); - virtual bool filterJumpyTouchEvents(); - virtual nsecs_t getVirtualKeyQuietTime(); - virtual void getVirtualKeyDefinitions(const String8& deviceName, - Vector& outVirtualKeyDefinitions); - virtual void getInputDeviceCalibration(const String8& deviceName, - InputDeviceCalibration& outCalibration); - virtual void getExcludedDeviceNames(Vector& outExcludedDeviceNames); + virtual void getReaderConfiguration(InputReaderConfiguration* outConfig); + + void setDisplayInfo(); protected: virtual ~GeckoInputReaderPolicy() {} @@ -298,30 +294,36 @@ public: 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(nsecs_t eventTime); - virtual void notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source, - uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, - int32_t scanCode, int32_t metaState, nsecs_t downTime); - virtual void notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t source, - uint32_t policyFlags, int32_t action, int32_t flags, - int32_t metaState, int32_t edgeFlags, - uint32_t pointerCount, const int32_t* pointerIds, const PointerCoords* pointerCoords, - float xPrecision, float yPrecision, nsecs_t downTime); - virtual void notifySwitch(nsecs_t when, - int32_t switchCode, int32_t switchValue, uint32_t policyFlags); + 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); - virtual void setInputWindows(const Vector& inputWindows); - virtual void setFocusedApplication(const InputApplication* inputApplication); + int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis, + uint32_t policyFlags); + + virtual void setInputWindows(const Vector >& inputWindowHandles); + virtual void setFocusedApplication(const sp& inputApplicationHandle); + virtual void setInputDispatchMode(bool enabled, bool frozen); - virtual status_t registerInputChannel(const sp& inputChannel, bool monitor); + virtual void setInputFilterEnabled(bool enabled) {} + virtual bool transferTouchFocus(const sp& fromChannel, + const sp& toChannel) { return true; } + + virtual status_t registerInputChannel(const sp& inputChannel, + const sp& inputWindowHandle, bool monitor); virtual status_t unregisterInputChannel(const sp& inputChannel); + + protected: virtual ~GeckoInputDispatcher() {} @@ -334,138 +336,30 @@ private: }; // GeckoInputReaderPolicy -bool -GeckoInputReaderPolicy::getDisplayInfo(int32_t displayId, - int32_t* width, - int32_t* height, - int32_t* orientation) +void +GeckoInputReaderPolicy::setDisplayInfo() { MOZ_STATIC_ASSERT(nsIScreen::ROTATION_0_DEG == - InputReaderPolicyInterface::ROTATION_0, + DISPLAY_ORIENTATION_0, "Orientation enums not matched!"); MOZ_STATIC_ASSERT(nsIScreen::ROTATION_90_DEG == - InputReaderPolicyInterface::ROTATION_90, + DISPLAY_ORIENTATION_90, "Orientation enums not matched!"); MOZ_STATIC_ASSERT(nsIScreen::ROTATION_180_DEG == - InputReaderPolicyInterface::ROTATION_180, + DISPLAY_ORIENTATION_180, "Orientation enums not matched!"); MOZ_STATIC_ASSERT(nsIScreen::ROTATION_270_DEG == - InputReaderPolicyInterface::ROTATION_270, + DISPLAY_ORIENTATION_270, "Orientation enums not matched!"); - // 0 is the default displayId. We only support one display - if (displayId) - return false; - - if (width) - *width = gScreenBounds.width; - if (height) - *height = gScreenBounds.height; - if (orientation) - *orientation = nsScreenGonk::GetRotation(); - return true; + mConfig.setDisplayInfo(0, false, gScreenBounds.width, gScreenBounds.height, nsScreenGonk::GetRotation()); } -bool -GeckoInputReaderPolicy::filterTouchEvents() +void GeckoInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) { - return false; + *outConfig = mConfig; } -bool -GeckoInputReaderPolicy::filterJumpyTouchEvents() -{ - return false; -} - -nsecs_t -GeckoInputReaderPolicy::getVirtualKeyQuietTime() -{ - return 0; -} - -void -GeckoInputReaderPolicy::getVirtualKeyDefinitions(const String8& deviceName, - Vector& outVirtualKeyDefinitions) -{ - outVirtualKeyDefinitions.clear(); - - char vbuttonsPath[PATH_MAX]; - snprintf(vbuttonsPath, sizeof(vbuttonsPath), - "/sys/board_properties/virtualkeys.%s", - deviceName.string()); - ScopedClose fd(open(vbuttonsPath, O_RDONLY)); - if (0 > fd.mFd) { - LOG("No vbuttons for mt device %s", deviceName.string()); - return; - } - - // This device has vbuttons. Process the configuration. - char config[1024]; - ssize_t nread; - do { - nread = read(fd.mFd, config, sizeof(config)); - } while (-1 == nread && EINTR == errno); - - if (0 > nread) { - LOG("Error reading virtualkey configuration"); - return; - } - - config[nread] = '\0'; - - LOG("Device %s has vbutton config '%s'", deviceName.string(), config); - - char* first = config; - char* magic; - char* state; - while ((magic = strtok_r(first, ":", &state))) { - // XXX not clear what "0x01" is ... maybe a version - // number? See InputManager.java. - if (strcmp(magic, "0x01")) { - LOG(" magic 0x01 tag missing"); - break; - } - first = NULL; - - const char *scanCode, *centerX, *centerY, *width, *height; - if (!((scanCode = strtok_r(NULL, ":", &state)) && - (centerX = strtok_r(NULL, ":", &state)) && - (centerY = strtok_r(NULL, ":", &state)) && - (width = strtok_r(NULL, ":", &state)) && - (height = strtok_r(NULL, ":", &state)))) { - LOG(" failed to read bound params"); - break; - } - - // NB: these coordinates are in *screen* space, not input - // space. That means the values in /sys/board_config make - // assumptions about how the raw input events are mapped - // ... le sigh. - VirtualKeyDefinition def; - def.scanCode = atoi(scanCode); - def.centerX = atoi(centerX); - def.centerY = atoi(centerY); - def.width = atoi(width); - def.height = atoi(height); - outVirtualKeyDefinitions.push(def); - - LOG(" configured vbutton code=%d at ", - def.scanCode, def.centerX, def.centerY, def.width, def.height); - } -} - -void -GeckoInputReaderPolicy::getInputDeviceCalibration(const String8& deviceName, InputDeviceCalibration& outCalibration) -{ - outCalibration.clear(); -} - -void -GeckoInputReaderPolicy::getExcludedDeviceNames(Vector& outExcludedDeviceNames) -{ - outExcludedDeviceNames.clear(); -} // GeckoInputDispatcher void @@ -511,8 +405,8 @@ GeckoInputDispatcher::dispatchOnce() } sendMouseEvent(msg, data.timeMs, - data.motion.touches[0].coords.x, - data.motion.touches[0].coords.y); + data.motion.touches[0].coords.getX(), + data.motion.touches[0].coords.getY()); break; } case UserInputData::KEY_DATA: @@ -525,7 +419,7 @@ GeckoInputDispatcher::dispatchOnce() void -GeckoInputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) +GeckoInputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs*) { } @@ -536,25 +430,16 @@ nanosecsToMillisecs(nsecs_t nsecs) } void -GeckoInputDispatcher::notifyKey(nsecs_t eventTime, - int32_t deviceId, - int32_t source, - uint32_t policyFlags, - int32_t action, - int32_t flags, - int32_t keyCode, - int32_t scanCode, - int32_t metaState, - nsecs_t downTime) +GeckoInputDispatcher::notifyKey(const NotifyKeyArgs* args) { UserInputData data; - data.timeMs = nanosecsToMillisecs(eventTime); + data.timeMs = nanosecsToMillisecs(args->eventTime); data.type = UserInputData::KEY_DATA; - data.action = action; - data.flags = flags; - data.metaState = metaState; - data.key.keyCode = keyCode; - data.key.scanCode = scanCode; + data.action = args->action; + data.flags = args->flags; + data.metaState = args->metaState; + data.key.keyCode = args->keyCode; + data.key.scanCode = args->scanCode; { MutexAutoLock lock(mQueueLock); mEventQueue.push(data); @@ -562,34 +447,22 @@ GeckoInputDispatcher::notifyKey(nsecs_t eventTime, gAppShell->NotifyNativeEvent(); } + void -GeckoInputDispatcher::notifyMotion(nsecs_t eventTime, - int32_t deviceId, - int32_t source, - uint32_t policyFlags, - int32_t action, - int32_t flags, - int32_t metaState, - int32_t edgeFlags, - uint32_t pointerCount, - const int32_t* pointerIds, - const PointerCoords* pointerCoords, - float xPrecision, - float yPrecision, - nsecs_t downTime) +GeckoInputDispatcher::notifyMotion(const NotifyMotionArgs* args) { UserInputData data; - data.timeMs = nanosecsToMillisecs(eventTime); + data.timeMs = nanosecsToMillisecs(args->eventTime); data.type = UserInputData::MOTION_DATA; - data.action = action; - data.flags = flags; - data.metaState = metaState; - MOZ_ASSERT(pointerCount <= MAX_POINTERS); - data.motion.touchCount = pointerCount; - for (int32_t i = 0; i < pointerCount; ++i) { + data.action = args->action; + data.flags = args->flags; + data.metaState = args->metaState; + MOZ_ASSERT(args->pointerCount <= MAX_POINTERS); + data.motion.touchCount = args->pointerCount; + for (uint32_t i = 0; i < args->pointerCount; ++i) { Touch& touch = data.motion.touches[i]; - touch.id = pointerIds[i]; - memcpy(&touch.coords, &pointerCoords[i], sizeof(*pointerCoords)); + touch.id = args->pointerProperties[i].id; + memcpy(&touch.coords, &args->pointerCoords[i], sizeof(*args->pointerCoords)); } { MutexAutoLock lock(mQueueLock); @@ -604,32 +477,31 @@ GeckoInputDispatcher::notifyMotion(nsecs_t eventTime, gAppShell->NotifyNativeEvent(); } -void -GeckoInputDispatcher::notifySwitch(nsecs_t when, - int32_t switchCode, - int32_t switchValue, - uint32_t policyFlags) + + +void GeckoInputDispatcher::notifySwitch(const NotifySwitchArgs* args) { } +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) +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 Vector& inputWindows) +GeckoInputDispatcher::setInputWindows(const Vector >& inputWindowHandles) { } void -GeckoInputDispatcher::setFocusedApplication(const InputApplication* inputApplication) +GeckoInputDispatcher::setFocusedApplication(const sp& inputApplicationHandle) { } @@ -640,7 +512,7 @@ GeckoInputDispatcher::setInputDispatchMode(bool enabled, bool frozen) status_t GeckoInputDispatcher::registerInputChannel(const sp& inputChannel, - bool monitor) + const sp& inputWindowHandle, bool monitor) { return OK; } @@ -747,16 +619,26 @@ nsAppShell::Init() rv = AddFdHandler(signalfds[0], pipeHandler, ""); NS_ENSURE_SUCCESS(rv, rv); + // Delay initializing input devices until the screen has been + // initialized (and we know the resolution). + return rv; +} + +void +nsAppShell::InitInputDevices() +{ mEventHub = new EventHub(); mReaderPolicy = new GeckoInputReaderPolicy(); + mReaderPolicy->setDisplayInfo(); mDispatcher = new GeckoInputDispatcher(); mReader = new InputReader(mEventHub, mReaderPolicy, mDispatcher); mReaderThread = new InputReaderThread(mReader); status_t result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); - NS_ENSURE_FALSE(result, NS_ERROR_UNEXPECTED); - return rv; + if (result) { + LOG("Failed to initialize InputReader thread, bad things are going to happen..."); + } } nsresult @@ -820,3 +702,8 @@ nsAppShell::NotifyNativeEvent() write(signalfds[1], "w", 1); } +/*static*/ void +nsAppShell::NotifyScreenInitialized() +{ + gAppShell->InitInputDevices(); +} diff --git a/widget/gonk/nsAppShell.h b/widget/gonk/nsAppShell.h index ca47e3fec61..84ef30d2986 100644 --- a/widget/gonk/nsAppShell.h +++ b/widget/gonk/nsAppShell.h @@ -97,6 +97,8 @@ public: void NotifyNativeEvent(); + static void NotifyScreenInitialized(); + protected: virtual ~nsAppShell(); @@ -105,6 +107,7 @@ protected: 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; diff --git a/widget/gonk/nsWindow.cpp b/widget/gonk/nsWindow.cpp index afe5d9fab02..e1d9f0bf064 100644 --- a/widget/gonk/nsWindow.cpp +++ b/widget/gonk/nsWindow.cpp @@ -100,6 +100,8 @@ nsWindow::nsWindow() } } sVirtualBounds = gScreenBounds; + + nsAppShell::NotifyScreenInitialized(); } }