summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmds/atrace/atrace.cpp8
-rw-r--r--cmds/bugreport/bugreport.c2
-rw-r--r--cmds/dumpstate/dumpstate.c4
-rw-r--r--cmds/dumpstate/dumpstate.h7
-rw-r--r--cmds/dumpstate/utils.c39
-rw-r--r--cmds/flatland/GLHelper.cpp8
-rw-r--r--cmds/flatland/Main.cpp6
-rw-r--r--cmds/screenshot/Android.mk12
-rw-r--r--cmds/screenshot/screenshot.c171
-rw-r--r--cmds/servicemanager/service_manager.c2
-rw-r--r--data/etc/android.software.managedprofiles.xml20
-rw-r--r--data/etc/android.software.voice_recognizers.xml19
-rw-r--r--data/etc/handheld_core_hardware.xml10
-rw-r--r--data/etc/tablet_core_hardware.xml12
-rw-r--r--include/android/sensor.h4
-rw-r--r--include/batteryservice/BatteryService.h17
-rw-r--r--include/batteryservice/IBatteryPropertiesRegistrar.h2
-rw-r--r--include/binder/IBatteryStats.h55
-rw-r--r--include/binder/IBinder.h8
-rw-r--r--include/binder/MemoryDealer.h3
-rw-r--r--include/binder/Parcel.h10
-rw-r--r--include/gui/BufferItem.h103
-rw-r--r--include/gui/BufferItemConsumer.h5
-rw-r--r--include/gui/BufferQueue.h532
-rw-r--r--include/gui/BufferQueueConsumer.h181
-rw-r--r--include/gui/BufferQueueCore.h241
-rw-r--r--include/gui/BufferQueueDefs.h35
-rw-r--r--include/gui/BufferQueueProducer.h198
-rw-r--r--include/gui/BufferSlot.h142
-rw-r--r--include/gui/ConsumerBase.h9
-rw-r--r--include/gui/IConsumerListener.h6
-rw-r--r--include/gui/IGraphicBufferConsumer.h133
-rw-r--r--include/gui/IGraphicBufferProducer.h281
-rw-r--r--include/gui/IProducerListener.h67
-rw-r--r--include/gui/ISensorEventConnection.h1
-rw-r--r--include/gui/ISurfaceComposer.h21
-rw-r--r--include/gui/ISurfaceComposerClient.h11
-rw-r--r--include/gui/Sensor.h3
-rw-r--r--include/gui/SensorEventQueue.h5
-rw-r--r--include/gui/StreamSplitter.h184
-rw-r--r--include/gui/Surface.h13
-rw-r--r--include/gui/SurfaceComposerClient.h20
-rw-r--r--include/gui/SurfaceControl.h10
-rw-r--r--include/input/IInputFlinger.h54
-rw-r--r--include/input/Input.h28
-rw-r--r--include/input/InputEventLabels.h396
-rw-r--r--include/input/Keyboard.h31
-rw-r--r--include/input/KeycodeLabels.h346
-rw-r--r--include/media/hardware/HDCPAPI.h5
-rw-r--r--include/media/openmax/OMX_AudioExt.h15
-rw-r--r--include/media/openmax/OMX_IndexExt.h2
-rw-r--r--include/media/openmax/OMX_VideoExt.h37
-rw-r--r--include/powermanager/IPowerManager.h2
-rw-r--r--include/powermanager/PowerManager.h8
-rw-r--r--include/ui/FrameStats.h63
-rw-r--r--include/ui/FramebufferNativeWindow.h2
-rw-r--r--include/ui/GraphicBuffer.h5
-rw-r--r--include/ui/PixelFormat.h16
-rw-r--r--include/ui/Region.h3
-rw-r--r--libs/binder/Android.mk1
-rw-r--r--libs/binder/IBatteryStats.cpp84
-rw-r--r--libs/binder/MemoryDealer.cpp4
-rw-r--r--libs/binder/Parcel.cpp43
-rw-r--r--libs/gui/Android.mk7
-rw-r--r--libs/gui/BufferItem.cpp192
-rw-r--r--libs/gui/BufferItemConsumer.cpp17
-rw-r--r--libs/gui/BufferQueue.cpp1230
-rw-r--r--libs/gui/BufferQueueConsumer.cpp518
-rw-r--r--libs/gui/BufferQueueCore.cpp228
-rw-r--r--libs/gui/BufferQueueProducer.cpp860
-rw-r--r--libs/gui/BufferSlot.cpp31
-rw-r--r--libs/gui/ConsumerBase.cpp11
-rw-r--r--libs/gui/IConsumerListener.cpp13
-rw-r--r--libs/gui/IGraphicBufferConsumer.cpp112
-rw-r--r--libs/gui/IGraphicBufferProducer.cpp141
-rw-r--r--libs/gui/IProducerListener.cpp53
-rw-r--r--libs/gui/ISensorEventConnection.cpp15
-rw-r--r--libs/gui/ISurfaceComposer.cpp38
-rw-r--r--libs/gui/ISurfaceComposerClient.cpp39
-rw-r--r--libs/gui/Sensor.cpp91
-rw-r--r--libs/gui/SensorEventQueue.cpp9
-rw-r--r--libs/gui/StreamSplitter.cpp281
-rw-r--r--libs/gui/Surface.cpp46
-rw-r--r--libs/gui/SurfaceComposerClient.cpp50
-rw-r--r--libs/gui/SurfaceControl.cpp46
-rw-r--r--libs/gui/tests/Android.mk13
-rw-r--r--libs/gui/tests/BufferQueue_test.cpp314
-rw-r--r--libs/gui/tests/CpuConsumer_test.cpp10
-rw-r--r--libs/gui/tests/DisconnectWaiter.h80
-rw-r--r--libs/gui/tests/FillBuffer.cpp108
-rw-r--r--libs/gui/tests/FillBuffer.h43
-rw-r--r--libs/gui/tests/FrameWaiter.h52
-rw-r--r--libs/gui/tests/GLTest.cpp339
-rw-r--r--libs/gui/tests/GLTest.h70
-rw-r--r--libs/gui/tests/IGraphicBufferProducer_test.cpp566
-rw-r--r--libs/gui/tests/MultiTextureConsumer_test.cpp125
-rw-r--r--libs/gui/tests/SRGB_test.cpp477
-rw-r--r--libs/gui/tests/StreamSplitter_test.cpp246
-rw-r--r--libs/gui/tests/SurfaceTextureClient_test.cpp16
-rw-r--r--libs/gui/tests/SurfaceTextureFBO.h75
-rw-r--r--libs/gui/tests/SurfaceTextureFBO_test.cpp87
-rw-r--r--libs/gui/tests/SurfaceTextureGL.h73
-rw-r--r--libs/gui/tests/SurfaceTextureGLThreadToGL.h183
-rw-r--r--libs/gui/tests/SurfaceTextureGLThreadToGL_test.cpp186
-rw-r--r--libs/gui/tests/SurfaceTextureGLToGL.h65
-rw-r--r--libs/gui/tests/SurfaceTextureGLToGL_test.cpp502
-rw-r--r--libs/gui/tests/SurfaceTextureGL_test.cpp703
-rw-r--r--libs/gui/tests/SurfaceTextureMultiContextGL.h84
-rw-r--r--libs/gui/tests/SurfaceTextureMultiContextGL_test.cpp389
-rw-r--r--libs/gui/tests/SurfaceTexture_test.cpp2816
-rw-r--r--libs/gui/tests/Surface_test.cpp22
-rw-r--r--libs/gui/tests/TextureRenderer.cpp116
-rw-r--r--libs/gui/tests/TextureRenderer.h46
-rw-r--r--libs/input/Android.mk1
-rw-r--r--libs/input/IInputFlinger.cpp59
-rw-r--r--libs/input/Input.cpp87
-rw-r--r--libs/input/InputTransport.cpp2
-rw-r--r--libs/input/KeyCharacterMap.cpp1
-rw-r--r--libs/input/KeyLayoutMap.cpp1
-rw-r--r--libs/input/Keyboard.cpp42
-rw-r--r--libs/ui/Android.mk1
-rw-r--r--libs/ui/FrameStats.cpp84
-rw-r--r--libs/ui/GraphicBuffer.cpp49
-rw-r--r--libs/ui/PixelFormat.cpp2
-rw-r--r--libs/ui/Region.cpp16
-rw-r--r--opengl/libs/EGL/eglApi.cpp8
-rw-r--r--opengl/tests/EGLTest/EGL_test.cpp9
-rw-r--r--opengl/tests/angeles/Android.mk3
-rw-r--r--opengl/tests/angeles/app-linux.cpp11
-rw-r--r--opengl/tests/fillrate/Android.mk5
-rw-r--r--opengl/tests/fillrate/fillrate.cpp7
-rw-r--r--opengl/tests/filter/Android.mk6
-rw-r--r--opengl/tests/filter/filter.cpp9
-rw-r--r--opengl/tests/finish/Android.mk5
-rw-r--r--opengl/tests/finish/finish.cpp7
-rw-r--r--opengl/tests/gl2_basic/Android.mk6
-rw-r--r--opengl/tests/gl2_basic/gl2_basic.cpp7
-rw-r--r--opengl/tests/gl2_copyTexImage/Android.mk6
-rw-r--r--opengl/tests/gl2_copyTexImage/gl2_copyTexImage.cpp7
-rw-r--r--opengl/tests/gl2_yuvtex/Android.mk6
-rw-r--r--opengl/tests/gl2_yuvtex/gl2_yuvtex.cpp7
-rw-r--r--opengl/tests/gl_basic/Android.mk6
-rw-r--r--opengl/tests/gl_basic/gl_basic.cpp13
-rw-r--r--opengl/tests/gl_perf/Android.mk6
-rw-r--r--opengl/tests/gl_perf/gl2_perf.cpp7
-rw-r--r--opengl/tests/gl_yuvtex/Android.mk5
-rw-r--r--opengl/tests/gl_yuvtex/gl_yuvtex.cpp7
-rw-r--r--opengl/tests/hwc/hwcColorEquiv.cpp1
-rw-r--r--opengl/tests/hwc/hwcCommit.cpp1
-rw-r--r--opengl/tests/hwc/hwcRects.cpp1
-rw-r--r--opengl/tests/hwc/hwcStress.cpp1
-rw-r--r--opengl/tests/hwc/hwcTestLib.cpp6
-rw-r--r--opengl/tests/hwc/hwcTestLib.h1
-rw-r--r--opengl/tests/include/WindowSurface.h49
-rw-r--r--opengl/tests/lib/Android.mk2
-rw-r--r--opengl/tests/lib/WindowSurface.cpp86
-rw-r--r--opengl/tests/linetex/Android.mk6
-rw-r--r--opengl/tests/linetex/linetex.cpp9
-rw-r--r--opengl/tests/swapinterval/Android.mk5
-rw-r--r--opengl/tests/swapinterval/swapinterval.cpp7
-rw-r--r--opengl/tests/textures/Android.mk6
-rw-r--r--opengl/tests/textures/textures.cpp9
-rw-r--r--opengl/tests/tritex/Android.mk6
-rw-r--r--opengl/tests/tritex/tritex.cpp13
-rw-r--r--opengl/tools/glgen/static/egl/EGLObjectHandle.java29
-rw-r--r--opengl/tools/glgen/stubs/gles11/glGetActiveAttrib.java1
-rw-r--r--opengl/tools/glgen/stubs/gles11/glGetActiveUniform.java1
-rw-r--r--opengl/tools/glgen/stubs/gles11/glGetShaderSource.java1
-rw-r--r--services/batteryservice/Android.mk1
-rw-r--r--services/batteryservice/BatteryProperties.cpp4
-rw-r--r--services/batteryservice/BatteryProperty.cpp (renamed from services/surfaceflinger/SurfaceTextureLayer.h)36
-rw-r--r--services/batteryservice/IBatteryPropertiesRegistrar.cpp28
-rw-r--r--services/inputflinger/Android.mk63
-rw-r--r--services/inputflinger/EventHub.cpp1666
-rw-r--r--services/inputflinger/EventHub.h457
-rw-r--r--services/inputflinger/InputApplication.cpp42
-rw-r--r--services/inputflinger/InputApplication.h83
-rw-r--r--services/inputflinger/InputDispatcher.cpp4487
-rw-r--r--services/inputflinger/InputDispatcher.h1121
-rw-r--r--services/inputflinger/InputFlinger.cpp89
-rw-r--r--services/inputflinger/InputFlinger.h54
-rw-r--r--services/inputflinger/InputListener.cpp182
-rw-r--r--services/inputflinger/InputListener.h196
-rw-r--r--services/inputflinger/InputManager.cpp93
-rw-r--r--services/inputflinger/InputManager.h109
-rw-r--r--services/inputflinger/InputReader.cpp6584
-rw-r--r--services/inputflinger/InputReader.h1857
-rw-r--r--services/inputflinger/InputWindow.cpp72
-rw-r--r--services/inputflinger/InputWindow.h210
-rw-r--r--services/inputflinger/PointerControllerInterface.h105
-rw-r--r--services/inputflinger/main.cpp26
-rw-r--r--services/inputflinger/tests/Android.mk51
-rw-r--r--services/inputflinger/tests/InputDispatcher_test.cpp258
-rw-r--r--services/inputflinger/tests/InputReader_test.cpp5153
-rw-r--r--services/powermanager/IPowerManager.cpp11
-rw-r--r--services/sensorservice/BatteryService.cpp32
-rw-r--r--services/sensorservice/BatteryService.h8
-rw-r--r--services/sensorservice/SensorDevice.cpp2
-rw-r--r--services/sensorservice/SensorService.cpp175
-rw-r--r--services/sensorservice/SensorService.h31
-rw-r--r--services/surfaceflinger/Android.mk2
-rw-r--r--services/surfaceflinger/Client.cpp18
-rw-r--r--services/surfaceflinger/Client.h4
-rw-r--r--services/surfaceflinger/DispSync.cpp23
-rw-r--r--services/surfaceflinger/DispSync.h3
-rw-r--r--services/surfaceflinger/DisplayDevice.cpp2
-rw-r--r--services/surfaceflinger/DisplayHardware/HWComposer.cpp49
-rw-r--r--services/surfaceflinger/DisplayHardware/HWComposer.h4
-rw-r--r--services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp43
-rw-r--r--services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h15
-rw-r--r--services/surfaceflinger/Effects/Daltonizer.cpp11
-rw-r--r--services/surfaceflinger/FrameTracker.cpp31
-rw-r--r--services/surfaceflinger/FrameTracker.h11
-rw-r--r--services/surfaceflinger/Layer.cpp94
-rw-r--r--services/surfaceflinger/Layer.h39
-rw-r--r--services/surfaceflinger/LayerDim.cpp5
-rw-r--r--services/surfaceflinger/LayerDim.h3
-rw-r--r--services/surfaceflinger/MonitoredProducer.cpp113
-rw-r--r--services/surfaceflinger/MonitoredProducer.h63
-rw-r--r--services/surfaceflinger/RenderEngine/ProgramCache.cpp3
-rw-r--r--services/surfaceflinger/SurfaceFlinger.cpp139
-rw-r--r--services/surfaceflinger/SurfaceFlinger.h16
-rw-r--r--services/surfaceflinger/SurfaceFlingerConsumer.cpp27
-rw-r--r--services/surfaceflinger/SurfaceFlingerConsumer.h19
-rw-r--r--services/surfaceflinger/SurfaceTextureLayer.cpp56
225 files changed, 34538 insertions, 5879 deletions
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 34dc9fe..099c3df 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -40,7 +40,7 @@ using namespace android;
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
-enum { MAX_SYS_FILES = 8 };
+enum { MAX_SYS_FILES = 10 };
const char* k_traceTagsProperty = "debug.atrace.tags.enableflags";
const char* k_traceAppCmdlineProperty = "debug.atrace.app_cmdlines";
@@ -99,6 +99,12 @@ static const TracingCategory k_categories[] = {
{ REQ, "/sys/kernel/debug/tracing/events/power/cpu_idle/enable" },
} },
{ "disk", "Disk I/O", 0, {
+ { REQ, "/sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_enter/enable" },
+ { REQ, "/sys/kernel/debug/tracing/events/f2fs/f2fs_sync_file_exit/enable" },
+ { REQ, "/sys/kernel/debug/tracing/events/f2fs/f2fs_write_begin/enable" },
+ { REQ, "/sys/kernel/debug/tracing/events/f2fs/f2fs_write_end/enable" },
+ { REQ, "/sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable" },
+ { REQ, "/sys/kernel/debug/tracing/events/ext4/ext4_da_write_end/enable" },
{ REQ, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_enter/enable" },
{ REQ, "/sys/kernel/debug/tracing/events/ext4/ext4_sync_file_exit/enable" },
{ REQ, "/sys/kernel/debug/tracing/events/block/block_rq_issue/enable" },
diff --git a/cmds/bugreport/bugreport.c b/cmds/bugreport/bugreport.c
index 4a0b511..11e9057 100644
--- a/cmds/bugreport/bugreport.c
+++ b/cmds/bugreport/bugreport.c
@@ -29,7 +29,7 @@ int main(int argc, char *argv[]) {
property_set("ctl.start", "dumpstate");
/* socket will not be available until service starts */
- for (i = 0; i < 10; i++) {
+ for (i = 0; i < 20; i++) {
s = socket_local_client("dumpstate",
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_STREAM);
diff --git a/cmds/dumpstate/dumpstate.c b/cmds/dumpstate/dumpstate.c
index 220af47..7fb5b12 100644
--- a/cmds/dumpstate/dumpstate.c
+++ b/cmds/dumpstate/dumpstate.c
@@ -174,9 +174,7 @@ static void dumpstate() {
dump_file("LAST PANIC CONSOLE", "/data/dontpanic/apanic_console");
dump_file("LAST PANIC THREADS", "/data/dontpanic/apanic_threads");
- run_command("SYSTEM SETTINGS", 20, SU_PATH, "root", "sqlite3",
- "/data/data/com.android.providers.settings/databases/settings.db",
- "pragma user_version; select * from system; select * from secure; select * from global;", NULL);
+ for_each_userid(do_dump_settings, NULL);
/* The following have a tendency to get wedged when wifi drivers/fw goes belly-up. */
run_command("NETWORK INTERFACES", 10, SU_PATH, "root", "netcfg", NULL);
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 67bbd7e..d820495 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -26,6 +26,7 @@
typedef void (for_each_pid_func)(int, const char *);
typedef void (for_each_tid_func)(int, int, const char *);
+typedef void (for_each_userid_func)(int);
/* prints the contents of a file */
int dump_file(const char *title, const char* path);
@@ -51,6 +52,9 @@ void for_each_pid(for_each_pid_func func, const char *header);
/* for each thread in the system, run the specified function */
void for_each_tid(for_each_tid_func func, const char *header);
+/* for each user id in the system, run the specified function */
+void for_each_userid(for_each_userid_func func, const char *header);
+
/* Displays a blocked processes in-kernel wait channel */
void show_wchan(int pid, int tid, const char *name);
@@ -60,6 +64,9 @@ void do_showmap(int pid, const char *name);
/* Gets the dmesg output for the kernel */
void do_dmesg();
+/* Dumps settings for a given user id */
+void do_dump_settings(int userid);
+
/* Play a sound via Stagefright */
void play_sound(const char* path);
diff --git a/cmds/dumpstate/utils.c b/cmds/dumpstate/utils.c
index ef5072a..6b119c3 100644
--- a/cmds/dumpstate/utils.c
+++ b/cmds/dumpstate/utils.c
@@ -51,6 +51,29 @@ static const char* native_processes_to_dump[] = {
NULL,
};
+void for_each_userid(void (*func)(int), const char *header) {
+ DIR *d;
+ struct dirent *de;
+
+ if (header) printf("\n------ %s ------\n", header);
+ func(0);
+
+ if (!(d = opendir("/data/system/users"))) {
+ printf("Failed to open /data/system/users (%s)\n", strerror(errno));
+ return;
+ }
+
+ while ((de = readdir(d))) {
+ int userid;
+ if (de->d_type != DT_DIR || !(userid = atoi(de->d_name))) {
+ continue;
+ }
+ func(userid);
+ }
+
+ closedir(d);
+}
+
static void __for_each_pid(void (*helper)(int, const char *, void *), const char *header, void *arg) {
DIR *d;
struct dirent *de;
@@ -175,6 +198,22 @@ out_close:
return;
}
+void do_dump_settings(int userid) {
+ char title[255];
+ char dbpath[255];
+ char sql[255];
+ sprintf(title, "SYSTEM SETTINGS (user %d)", userid);
+ if (userid == 0) {
+ strcpy(dbpath, "/data/data/com.android.providers.settings/databases/settings.db");
+ strcpy(sql, "pragma user_version; select * from system; select * from secure; select * from global;");
+ } else {
+ sprintf(dbpath, "/data/system/users/%d/settings.db", userid);
+ strcpy(sql, "pragma user_version; select * from system; select * from secure;");
+ }
+ run_command(title, 20, SU_PATH, "root", "sqlite3", dbpath, sql, NULL);
+ return;
+}
+
void do_dmesg() {
printf("------ KERNEL LOG (dmesg) ------\n");
/* Get size of kernel buffer */
diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp
index 05d082b..4b5aba9 100644
--- a/cmds/flatland/GLHelper.cpp
+++ b/cmds/flatland/GLHelper.cpp
@@ -201,14 +201,16 @@ bool GLHelper::getShaderProgram(const char* name, GLuint* outPgm) {
bool GLHelper::createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h,
sp<GLConsumer>* glConsumer, EGLSurface* surface) {
- sp<BufferQueue> bq = new BufferQueue(mGraphicBufferAlloc);
- sp<GLConsumer> glc = new GLConsumer(bq, name,
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer, mGraphicBufferAlloc);
+ sp<GLConsumer> glc = new GLConsumer(consumer, name,
GL_TEXTURE_EXTERNAL_OES, false);
glc->setDefaultBufferSize(w, h);
glc->setDefaultMaxBufferCount(3);
glc->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER);
- sp<ANativeWindow> anw = new Surface(bq);
+ sp<ANativeWindow> anw = new Surface(producer);
EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), NULL);
if (s == EGL_NO_SURFACE) {
fprintf(stderr, "eglCreateWindowSurface error: %#x\n", eglGetError());
diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp
index c0e5b3d..866203f 100644
--- a/cmds/flatland/Main.cpp
+++ b/cmds/flatland/Main.cpp
@@ -73,7 +73,7 @@ static const BenchmarkDesc benchmarks[] = {
},
},
- { "3:2 Single Static Window",
+ { "4:3 Single Static Window",
2048, 1536, { 1536 },
{
{ // Window
@@ -117,7 +117,7 @@ static const BenchmarkDesc benchmarks[] = {
},
},
- { "3:2 App -> Home Transition",
+ { "4:3 App -> Home Transition",
2048, 1536, { 1536 },
{
{ // Wallpaper
@@ -173,7 +173,7 @@ static const BenchmarkDesc benchmarks[] = {
},
},
- { "3:2 SurfaceView -> Home Transition",
+ { "4:3 SurfaceView -> Home Transition",
2048, 1536, { 1536 },
{
{ // Wallpaper
diff --git a/cmds/screenshot/Android.mk b/cmds/screenshot/Android.mk
deleted file mode 100644
index 1ee7807..0000000
--- a/cmds/screenshot/Android.mk
+++ /dev/null
@@ -1,12 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := screenshot.c
-
-LOCAL_MODULE := screenshot
-
-LOCAL_SHARED_LIBRARIES := libcutils libz liblog
-LOCAL_STATIC_LIBRARIES := libpng
-LOCAL_C_INCLUDES += external/zlib external/libpng
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/screenshot/screenshot.c b/cmds/screenshot/screenshot.c
deleted file mode 100644
index be1ecd4..0000000
--- a/cmds/screenshot/screenshot.c
+++ /dev/null
@@ -1,171 +0,0 @@
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <fcntl.h>
-#include <errno.h>
-
-#include <linux/fb.h>
-
-#include <zlib.h>
-#include <png.h>
-
-#include "private/android_filesystem_config.h"
-
-#define LOG_TAG "screenshot"
-#include <utils/Log.h>
-
-void take_screenshot(FILE *fb_in, FILE *fb_out) {
- int fb;
- char imgbuf[0x10000];
- struct fb_var_screeninfo vinfo;
- png_structp png;
- png_infop info;
- unsigned int r,c,rowlen;
- unsigned int bytespp,offset;
-
- fb = fileno(fb_in);
- if(fb < 0) {
- ALOGE("failed to open framebuffer\n");
- return;
- }
- fb_in = fdopen(fb, "r");
-
- if(ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) < 0) {
- ALOGE("failed to get framebuffer info\n");
- return;
- }
- fcntl(fb, F_SETFD, FD_CLOEXEC);
-
- png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
- if (png == NULL) {
- ALOGE("failed png_create_write_struct\n");
- fclose(fb_in);
- return;
- }
-
- png_init_io(png, fb_out);
- info = png_create_info_struct(png);
- if (info == NULL) {
- ALOGE("failed png_create_info_struct\n");
- png_destroy_write_struct(&png, NULL);
- fclose(fb_in);
- return;
- }
- if (setjmp(png_jmpbuf(png))) {
- ALOGE("failed png setjmp\n");
- png_destroy_write_struct(&png, NULL);
- fclose(fb_in);
- return;
- }
-
- bytespp = vinfo.bits_per_pixel / 8;
- png_set_IHDR(png, info,
- vinfo.xres, vinfo.yres, vinfo.bits_per_pixel / 4,
- PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
- PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
- png_write_info(png, info);
-
- rowlen=vinfo.xres * bytespp;
- if (rowlen > sizeof(imgbuf)) {
- ALOGE("crazy rowlen: %d\n", rowlen);
- png_destroy_write_struct(&png, NULL);
- fclose(fb_in);
- return;
- }
-
- offset = vinfo.xoffset * bytespp + vinfo.xres * vinfo.yoffset * bytespp;
- fseek(fb_in, offset, SEEK_SET);
-
- for(r=0; r<vinfo.yres; r++) {
- int len = fread(imgbuf, 1, rowlen, fb_in);
- if (len <= 0) break;
- png_write_row(png, (png_bytep)imgbuf);
- }
-
- png_write_end(png, info);
- fclose(fb_in);
- png_destroy_write_struct(&png, NULL);
-}
-
-void fork_sound(const char* path) {
- pid_t pid = fork();
- if (pid == 0) {
- execl("/system/bin/stagefright", "stagefright", "-o", "-a", path, NULL);
- }
-}
-
-void usage() {
- fprintf(stderr,
- "usage: screenshot [-s soundfile] filename.png\n"
- " -s: play a sound effect to signal success\n"
- " -i: autoincrement to avoid overwriting filename.png\n"
- );
-}
-
-int main(int argc, char**argv) {
- FILE *png = NULL;
- FILE *fb_in = NULL;
- char outfile[PATH_MAX] = "";
-
- char * soundfile = NULL;
- int do_increment = 0;
-
- int c;
- while ((c = getopt(argc, argv, "s:i")) != -1) {
- switch (c) {
- case 's': soundfile = optarg; break;
- case 'i': do_increment = 1; break;
- case '?':
- case 'h':
- usage(); exit(1);
- }
- }
- argc -= optind;
- argv += optind;
-
- if (argc < 1) {
- usage(); exit(1);
- }
-
- strlcpy(outfile, argv[0], PATH_MAX);
- if (do_increment) {
- struct stat st;
- char base[PATH_MAX] = "";
- int i = 0;
- while (stat(outfile, &st) == 0) {
- if (!base[0]) {
- char *p = strrchr(outfile, '.');
- if (p) *p = '\0';
- strcpy(base, outfile);
- }
- snprintf(outfile, PATH_MAX, "%s-%d.png", base, ++i);
- }
- }
-
- fb_in = fopen("/dev/graphics/fb0", "r");
- if (!fb_in) {
- fprintf(stderr, "error: could not read framebuffer\n");
- exit(1);
- }
-
- /* switch to non-root user and group */
- gid_t groups[] = { AID_LOG, AID_SDCARD_RW };
- setgroups(sizeof(groups)/sizeof(groups[0]), groups);
- setuid(AID_SHELL);
-
- png = fopen(outfile, "w");
- if (!png) {
- fprintf(stderr, "error: writing file %s: %s\n",
- outfile, strerror(errno));
- exit(1);
- }
-
- take_screenshot(fb_in, png);
-
- if (soundfile) {
- fork_sound(soundfile);
- }
-
- exit(0);
-}
diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c
index 79ce6ed..cacbea0 100644
--- a/cmds/servicemanager/service_manager.c
+++ b/cmds/servicemanager/service_manager.c
@@ -32,6 +32,8 @@ static struct {
{ AID_MEDIA, "media.player" },
{ AID_MEDIA, "media.camera" },
{ AID_MEDIA, "media.audio_policy" },
+ { AID_AUDIO, "audio" },
+ { AID_INPUT, "input" },
{ AID_DRM, "drm.drmManager" },
{ AID_NFC, "nfc" },
{ AID_BLUETOOTH, "bluetooth" },
diff --git a/data/etc/android.software.managedprofiles.xml b/data/etc/android.software.managedprofiles.xml
new file mode 100644
index 0000000..233a78d
--- /dev/null
+++ b/data/etc/android.software.managedprofiles.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<!-- This is the standard feature indicating that the device supports managed profiles. -->
+<permissions>
+ <feature name="android.software.managedprofiles" />
+</permissions>
diff --git a/data/etc/android.software.voice_recognizers.xml b/data/etc/android.software.voice_recognizers.xml
new file mode 100644
index 0000000..7e72177
--- /dev/null
+++ b/data/etc/android.software.voice_recognizers.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<permissions>
+ <feature name="android.software.voice_recognizers" />
+</permissions>
diff --git a/data/etc/handheld_core_hardware.xml b/data/etc/handheld_core_hardware.xml
index 4d81fb6..f0c9e67 100644
--- a/data/etc/handheld_core_hardware.xml
+++ b/data/etc/handheld_core_hardware.xml
@@ -4,9 +4,9 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -36,6 +36,7 @@
<!-- basic system services -->
<feature name="android.software.app_widgets" />
+ <feature name="android.software.voice_recognizers" />
<feature name="android.software.backup" />
<feature name="android.software.home_screen" />
<feature name="android.software.input_methods" />
@@ -44,9 +45,12 @@
<!-- Feature to specify if the device supports adding device admins. -->
<feature name="android.software.device_admin" />
+ <!-- Feature to specify if the device support managed profiles. -->
+ <feature name="android.software.managedprofiles" />
+
<!-- devices with GPS must include android.hardware.location.gps.xml -->
<!-- devices with an autofocus camera and/or flash must include either
- android.hardware.camera.autofocus.xml or
+ android.hardware.camera.autofocus.xml or
android.hardware.camera.autofocus-flash.xml -->
<!-- devices with a front facing camera must include
android.hardware.camera.front.xml -->
diff --git a/data/etc/tablet_core_hardware.xml b/data/etc/tablet_core_hardware.xml
index 2a74b0f..9d217ea 100644
--- a/data/etc/tablet_core_hardware.xml
+++ b/data/etc/tablet_core_hardware.xml
@@ -4,9 +4,9 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -37,6 +37,7 @@
<!-- basic system services -->
<feature name="android.software.app_widgets" />
+ <feature name="android.software.voice_recognizers" />
<feature name="android.software.backup" />
<feature name="android.software.home_screen" />
<feature name="android.software.input_methods" />
@@ -45,10 +46,13 @@
<!-- Feature to specify if the device supports adding device admins. -->
<feature name="android.software.device_admin" />
+ <!-- Feature to specify if the device support managed profiles. -->
+ <feature name="android.software.managedprofiles" />
+
<!-- devices with GPS must include android.hardware.location.gps.xml -->
<!-- devices with a rear-facing camera must include one of these as appropriate:
- android.hardware.camera.xml or
- android.hardware.camera.autofocus.xml or
+ android.hardware.camera.xml or
+ android.hardware.camera.autofocus.xml or
android.hardware.camera.autofocus-flash.xml -->
<!-- devices with a front facing camera must include
android.hardware.camera.front.xml -->
diff --git a/include/android/sensor.h b/include/android/sensor.h
index b4e7ebe..86de930 100644
--- a/include/android/sensor.h
+++ b/include/android/sensor.h
@@ -157,7 +157,9 @@ typedef struct ASensorEvent {
uint64_t step_counter;
} u64;
};
- int32_t reserved1[4];
+
+ uint32_t flags;
+ int32_t reserved1[3];
} ASensorEvent;
struct ASensorManager;
diff --git a/include/batteryservice/BatteryService.h b/include/batteryservice/BatteryService.h
index 829061a..2902d17 100644
--- a/include/batteryservice/BatteryService.h
+++ b/include/batteryservice/BatteryService.h
@@ -43,6 +43,14 @@ enum {
BATTERY_HEALTH_COLD = 7, // equals BatteryManager.BATTERY_HEALTH_COLD constant
};
+// must be kept in sync with definitions in BatteryProperty.java
+enum {
+ BATTERY_PROP_CHARGE_COUNTER = 1, // equals BatteryProperty.BATTERY_PROP_CHARGE_COUNTER constant
+ BATTERY_PROP_CURRENT_NOW = 2, // equals BatteryProperty.BATTERY_PROP_CURRENT_NOW constant
+ BATTERY_PROP_CURRENT_AVG = 3, // equals BatteryProperty.BATTERY_PROP_CURRENT_AVG constant
+ BATTERY_PROP_CAPACITY = 4, // equals BatteryProperty.BATTERY_PROP_CAPACITY constant
+};
+
struct BatteryProperties {
bool chargerAcOnline;
bool chargerUsbOnline;
@@ -52,8 +60,6 @@ struct BatteryProperties {
bool batteryPresent;
int batteryLevel;
int batteryVoltage;
- int batteryCurrentNow;
- int batteryChargeCounter;
int batteryTemperature;
String8 batteryTechnology;
@@ -61,6 +67,13 @@ struct BatteryProperties {
status_t readFromParcel(Parcel* parcel);
};
+struct BatteryProperty {
+ int valueInt;
+
+ status_t writeToParcel(Parcel* parcel) const;
+ status_t readFromParcel(Parcel* parcel);
+};
+
}; // namespace android
#endif // ANDROID_BATTERYSERVICE_H
diff --git a/include/batteryservice/IBatteryPropertiesRegistrar.h b/include/batteryservice/IBatteryPropertiesRegistrar.h
index 8d28b1d..eca075d 100644
--- a/include/batteryservice/IBatteryPropertiesRegistrar.h
+++ b/include/batteryservice/IBatteryPropertiesRegistrar.h
@@ -26,6 +26,7 @@ namespace android {
enum {
REGISTER_LISTENER = IBinder::FIRST_CALL_TRANSACTION,
UNREGISTER_LISTENER,
+ GET_PROPERTY,
};
class IBatteryPropertiesRegistrar : public IInterface {
@@ -34,6 +35,7 @@ public:
virtual void registerListener(const sp<IBatteryPropertiesListener>& listener) = 0;
virtual void unregisterListener(const sp<IBatteryPropertiesListener>& listener) = 0;
+ virtual status_t getProperty(int id, struct BatteryProperty *val) = 0;
};
class BnBatteryPropertiesRegistrar : public BnInterface<IBatteryPropertiesRegistrar> {
diff --git a/include/binder/IBatteryStats.h b/include/binder/IBatteryStats.h
new file mode 100644
index 0000000..f4a8aa3
--- /dev/null
+++ b/include/binder/IBatteryStats.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IBATTERYSTATS_H
+#define ANDROID_IBATTERYSTATS_H
+
+#include <binder/IInterface.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------
+
+class IBatteryStats : public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(BatteryStats);
+
+ virtual void noteStartSensor(int uid, int sensor) = 0;
+ virtual void noteStopSensor(int uid, int sensor) = 0;
+
+ enum {
+ NOTE_START_SENSOR_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
+ NOTE_STOP_SENSOR_TRANSACTION,
+ };
+};
+
+// ----------------------------------------------------------------------
+
+class BnBatteryStats : public BnInterface<IBatteryStats>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_IBATTERYSTATS_H
diff --git a/include/binder/IBinder.h b/include/binder/IBinder.h
index 8b84951..43b6543 100644
--- a/include/binder/IBinder.h
+++ b/include/binder/IBinder.h
@@ -81,14 +81,6 @@ public:
Parcel* reply,
uint32_t flags = 0) = 0;
- /**
- * This method allows you to add data that is transported through
- * IPC along with your IBinder pointer. When implementing a Binder
- * object, override it to write your desired data in to @a outData.
- * You can then call getConstantData() on your IBinder to retrieve
- * that data, from any process. You MUST return the number of bytes
- * written in to the parcel (including padding).
- */
class DeathRecipient : public virtual RefBase
{
public:
diff --git a/include/binder/MemoryDealer.h b/include/binder/MemoryDealer.h
index 170f20d..aa415d5 100644
--- a/include/binder/MemoryDealer.h
+++ b/include/binder/MemoryDealer.h
@@ -34,7 +34,8 @@ class SimpleBestFitAllocator;
class MemoryDealer : public RefBase
{
public:
- MemoryDealer(size_t size, const char* name = 0);
+ MemoryDealer(size_t size, const char* name = 0,
+ uint32_t flags = 0 /* or bits such as MemoryHeapBase::READ_ONLY */ );
virtual sp<IMemory> allocate(size_t size);
virtual void deallocate(size_t offset);
diff --git a/include/binder/Parcel.h b/include/binder/Parcel.h
index ce630bd..548fbf8 100644
--- a/include/binder/Parcel.h
+++ b/include/binder/Parcel.h
@@ -129,6 +129,11 @@ public:
// will be closed once the parcel is destroyed.
status_t writeDupFileDescriptor(int fd);
+ // Writes a raw fd and optional comm channel fd to the parcel as a ParcelFileDescriptor.
+ // A dup's of the fds are made, which will be closed once the parcel is destroyed.
+ // Null values are passed as -1.
+ status_t writeParcelFileDescriptor(int fd, int commChannel = -1);
+
// Writes a blob to the parcel.
// If the blob is small, then it is stored in-place, otherwise it is
// transferred by way of an anonymous shared memory region.
@@ -188,6 +193,11 @@ public:
// in the parcel, which you do not own -- use dup() to get your own copy.
int readFileDescriptor() const;
+ // Reads a ParcelFileDescriptor from the parcel. Returns the raw fd as
+ // the result, and the optional comm channel fd in outCommChannel.
+ // Null values are returned as -1.
+ int readParcelFileDescriptor(int& outCommChannel) const;
+
// Reads a blob from the parcel.
// The caller should call release() on the blob after reading its contents.
status_t readBlob(size_t len, ReadableBlob* outBlob) const;
diff --git a/include/gui/BufferItem.h b/include/gui/BufferItem.h
new file mode 100644
index 0000000..5effd10
--- /dev/null
+++ b/include/gui/BufferItem.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2014 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_GUI_BUFFERITEM_H
+#define ANDROID_GUI_BUFFERITEM_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <gui/IGraphicBufferConsumer.h>
+
+#include <ui/Rect.h>
+
+#include <utils/Flattenable.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class Fence;
+class GraphicBuffer;
+
+class BufferItem : public Flattenable<BufferItem> {
+ friend class Flattenable<BufferItem>;
+ size_t getPodSize() const;
+ size_t getFlattenedSize() const;
+ size_t getFdCount() const;
+ status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+ status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+ public:
+ // The default value of mBuf, used to indicate this doesn't correspond to a slot.
+ enum { INVALID_BUFFER_SLOT = -1 };
+ BufferItem();
+ operator IGraphicBufferConsumer::BufferItem() const;
+
+ static const char* scalingModeName(uint32_t scalingMode);
+
+ // mGraphicBuffer points to the buffer allocated for this slot, or is NULL
+ // if the buffer in this slot has been acquired in the past (see
+ // BufferSlot.mAcquireCalled).
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mFence is a fence that will signal when the buffer is idle.
+ sp<Fence> mFence;
+
+ // mCrop is the current crop rectangle for this buffer slot.
+ Rect mCrop;
+
+ // mTransform is the current transform flags for this buffer slot.
+ // refer to NATIVE_WINDOW_TRANSFORM_* in <window.h>
+ uint32_t mTransform;
+
+ // mScalingMode is the current scaling mode for this buffer slot.
+ // refer to NATIVE_WINDOW_SCALING_* in <window.h>
+ uint32_t mScalingMode;
+
+ // mTimestamp is the current timestamp for this buffer slot. This gets
+ // to set by queueBuffer each time this slot is queued. This value
+ // is guaranteed to be monotonically increasing for each newly
+ // acquired buffer.
+ int64_t mTimestamp;
+
+ // mIsAutoTimestamp indicates whether mTimestamp was generated
+ // automatically when the buffer was queued.
+ bool mIsAutoTimestamp;
+
+ // mFrameNumber is the number of the queued frame for this slot.
+ uint64_t mFrameNumber;
+
+ // mSlot is the slot index of this buffer (default INVALID_BUFFER_SLOT).
+ int mSlot;
+
+ // mIsDroppable whether this buffer was queued with the
+ // property that it can be replaced by a new buffer for the purpose of
+ // making sure dequeueBuffer() won't block.
+ // i.e.: was the BufferQueue in "mDequeueBufferCannotBlock" when this buffer
+ // was queued.
+ bool mIsDroppable;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates this buffer must be transformed by the inverse transform of the screen
+ // it is displayed onto. This is applied after mTransform.
+ bool mTransformToDisplayInverse;
+};
+
+} // namespace android
+
+#endif
diff --git a/include/gui/BufferItemConsumer.h b/include/gui/BufferItemConsumer.h
index 52edf17..5494ff1 100644
--- a/include/gui/BufferItemConsumer.h
+++ b/include/gui/BufferItemConsumer.h
@@ -44,6 +44,7 @@ class BufferItemConsumer: public ConsumerBase
typedef BufferQueue::BufferItem BufferItem;
+ enum { DEFAULT_MAX_BUFFERS = -1 };
enum { INVALID_BUFFER_SLOT = BufferQueue::INVALID_BUFFER_SLOT };
enum { NO_BUFFER_AVAILABLE = BufferQueue::NO_BUFFER_AVAILABLE };
@@ -53,8 +54,8 @@ class BufferItemConsumer: public ConsumerBase
// access at the same time.
// controlledByApp tells whether this consumer is controlled by the
// application.
- BufferItemConsumer(const sp<BufferQueue>& bq, uint32_t consumerUsage,
- int bufferCount = BufferQueue::MIN_UNDEQUEUED_BUFFERS,
+ BufferItemConsumer(const sp<IGraphicBufferConsumer>& consumer,
+ uint32_t consumerUsage, int bufferCount = DEFAULT_MAX_BUFFERS,
bool controlledByApp = false);
virtual ~BufferItemConsumer();
diff --git a/include/gui/BufferQueue.h b/include/gui/BufferQueue.h
index 408956b..3297b10 100644
--- a/include/gui/BufferQueue.h
+++ b/include/gui/BufferQueue.h
@@ -17,35 +17,29 @@
#ifndef ANDROID_GUI_BUFFERQUEUE_H
#define ANDROID_GUI_BUFFERQUEUE_H
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
-#include <binder/IBinder.h>
-
-#include <gui/IConsumerListener.h>
-#include <gui/IGraphicBufferAlloc.h>
-#include <gui/IGraphicBufferProducer.h>
+#include <gui/BufferQueueDefs.h>
#include <gui/IGraphicBufferConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/IConsumerListener.h>
-#include <ui/Fence.h>
-#include <ui/GraphicBuffer.h>
-
-#include <utils/String8.h>
-#include <utils/Vector.h>
-#include <utils/threads.h>
+// These are only required to keep other parts of the framework with incomplete
+// dependencies building successfully
+#include <gui/IGraphicBufferAlloc.h>
namespace android {
-// ----------------------------------------------------------------------------
-class BufferQueue : public BnGraphicBufferProducer,
- public BnGraphicBufferConsumer,
- private IBinder::DeathRecipient {
+class BufferQueue {
public:
- enum { MIN_UNDEQUEUED_BUFFERS = 2 };
- enum { NUM_BUFFER_SLOTS = 32 };
- enum { NO_CONNECTED_API = 0 };
- enum { INVALID_BUFFER_SLOT = -1 };
- enum { STALE_BUFFER_SLOT = 1, NO_BUFFER_AVAILABLE, PRESENT_LATER };
+ // BufferQueue will keep track of at most this value of buffers.
+ // Attempts at runtime to increase the number of buffers past this will fail.
+ enum { NUM_BUFFER_SLOTS = BufferQueueDefs::NUM_BUFFER_SLOTS };
+ // Used as a placeholder slot# when the value isn't pointing to an existing buffer.
+ enum { INVALID_BUFFER_SLOT = IGraphicBufferConsumer::BufferItem::INVALID_BUFFER_SLOT };
+ // Alias to <IGraphicBufferConsumer.h> -- please scope from there in future code!
+ enum {
+ NO_BUFFER_AVAILABLE = IGraphicBufferConsumer::NO_BUFFER_AVAILABLE,
+ PRESENT_LATER = IGraphicBufferConsumer::PRESENT_LATER,
+ };
// When in async mode we reserve two slots in order to guarantee that the
// producer and consumer can run asynchronously.
@@ -53,6 +47,7 @@ public:
// for backward source compatibility
typedef ::android::ConsumerListener ConsumerListener;
+ typedef IGraphicBufferConsumer::BufferItem BufferItem;
// ProxyConsumerListener is a ConsumerListener implementation that keeps a weak
// reference to the actual consumer object. It forwards all calls to that
@@ -69,503 +64,22 @@ public:
virtual ~ProxyConsumerListener();
virtual void onFrameAvailable();
virtual void onBuffersReleased();
+ virtual void onSidebandStreamChanged();
private:
// mConsumerListener is a weak reference to the IConsumerListener. This is
// the raison d'etre of ProxyConsumerListener.
wp<ConsumerListener> mConsumerListener;
};
-
// BufferQueue manages a pool of gralloc memory slots to be used by
// producers and consumers. allocator is used to allocate all the
// needed gralloc buffers.
- BufferQueue(const sp<IGraphicBufferAlloc>& allocator = NULL);
- virtual ~BufferQueue();
-
- /*
- * IBinder::DeathRecipient interface
- */
-
- virtual void binderDied(const wp<IBinder>& who);
-
- /*
- * IGraphicBufferProducer interface
- */
-
- // Query native window attributes. The "what" values are enumerated in
- // window.h (e.g. NATIVE_WINDOW_FORMAT).
- virtual int query(int what, int* value);
-
- // setBufferCount updates the number of available buffer slots. If this
- // method succeeds, buffer slots will be both unallocated and owned by
- // the BufferQueue object (i.e. they are not owned by the producer or
- // consumer).
- //
- // This will fail if the producer has dequeued any buffers, or if
- // bufferCount is invalid. bufferCount must generally be a value
- // between the minimum undequeued buffer count and NUM_BUFFER_SLOTS
- // (inclusive). It may also be set to zero (the default) to indicate
- // that the producer does not wish to set a value. The minimum value
- // can be obtained by calling query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
- // ...).
- //
- // This may only be called by the producer. The consumer will be told
- // to discard buffers through the onBuffersReleased callback.
- virtual status_t setBufferCount(int bufferCount);
-
- // requestBuffer returns the GraphicBuffer for slot N.
- //
- // In normal operation, this is called the first time slot N is returned
- // by dequeueBuffer. It must be called again if dequeueBuffer returns
- // flags indicating that previously-returned buffers are no longer valid.
- virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
-
- // dequeueBuffer gets the next buffer slot index for the producer to use.
- // If a buffer slot is available then that slot index is written to the
- // location pointed to by the buf argument and a status of OK is returned.
- // If no slot is available then a status of -EBUSY is returned and buf is
- // unmodified.
- //
- // The fence parameter will be updated to hold the fence associated with
- // the buffer. The contents of the buffer must not be overwritten until the
- // fence signals. If the fence is Fence::NO_FENCE, the buffer may be
- // written immediately.
- //
- // The width and height parameters must be no greater than the minimum of
- // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv).
- // An error due to invalid dimensions might not be reported until
- // updateTexImage() is called. If width and height are both zero, the
- // default values specified by setDefaultBufferSize() are used instead.
- //
- // The pixel formats are enumerated in graphics.h, e.g.
- // HAL_PIXEL_FORMAT_RGBA_8888. If the format is 0, the default format
- // will be used.
- //
- // The usage argument specifies gralloc buffer usage flags. The values
- // are enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER. These
- // will be merged with the usage flags specified by setConsumerUsageBits.
- //
- // The return value may be a negative error value or a non-negative
- // collection of flags. If the flags are set, the return values are
- // valid, but additional actions must be performed.
- //
- // If IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION is set, the
- // producer must discard cached GraphicBuffer references for the slot
- // returned in buf.
- // If IGraphicBufferProducer::RELEASE_ALL_BUFFERS is set, the producer
- // must discard cached GraphicBuffer references for all slots.
- //
- // In both cases, the producer will need to call requestBuffer to get a
- // GraphicBuffer handle for the returned slot.
- virtual status_t dequeueBuffer(int *buf, sp<Fence>* fence, bool async,
- uint32_t width, uint32_t height, uint32_t format, uint32_t usage);
-
- // queueBuffer returns a filled buffer to the BufferQueue.
- //
- // Additional data is provided in the QueueBufferInput struct. Notably,
- // a timestamp must be provided for the buffer. The timestamp is in
- // nanoseconds, and must be monotonically increasing. Its other semantics
- // (zero point, etc) are producer-specific and should be documented by the
- // producer.
- //
- // The caller may provide a fence that signals when all rendering
- // operations have completed. Alternatively, NO_FENCE may be used,
- // indicating that the buffer is ready immediately.
- //
- // Some values are returned in the output struct: the current settings
- // for default width and height, the current transform hint, and the
- // number of queued buffers.
- virtual status_t queueBuffer(int buf,
- const QueueBufferInput& input, QueueBufferOutput* output);
-
- // cancelBuffer returns a dequeued buffer to the BufferQueue, but doesn't
- // queue it for use by the consumer.
- //
- // The buffer will not be overwritten until the fence signals. The fence
- // will usually be the one obtained from dequeueBuffer.
- virtual void cancelBuffer(int buf, const sp<Fence>& fence);
-
- // connect attempts to connect a producer API to the BufferQueue. This
- // must be called before any other IGraphicBufferProducer methods are
- // called except for getAllocator. A consumer must already be connected.
- //
- // This method will fail if connect was previously called on the
- // BufferQueue and no corresponding disconnect call was made (i.e. if
- // it's still connected to a producer).
- //
- // APIs are enumerated in window.h (e.g. NATIVE_WINDOW_API_CPU).
- virtual status_t connect(const sp<IBinder>& token,
- int api, bool producerControlledByApp, QueueBufferOutput* output);
-
- // disconnect attempts to disconnect a producer API from the BufferQueue.
- // Calling this method will cause any subsequent calls to other
- // IGraphicBufferProducer methods to fail except for getAllocator and connect.
- // Successfully calling connect after this will allow the other methods to
- // succeed again.
- //
- // This method will fail if the the BufferQueue is not currently
- // connected to the specified producer API.
- virtual status_t disconnect(int api);
-
- /*
- * IGraphicBufferConsumer interface
- */
-
- // acquireBuffer attempts to acquire ownership of the next pending buffer in
- // the BufferQueue. If no buffer is pending then it returns -EINVAL. If a
- // buffer is successfully acquired, the information about the buffer is
- // returned in BufferItem. If the buffer returned had previously been
- // acquired then the BufferItem::mGraphicBuffer field of buffer is set to
- // NULL and it is assumed that the consumer still holds a reference to the
- // buffer.
- //
- // If presentWhen is nonzero, it indicates the time when the buffer will
- // be displayed on screen. If the buffer's timestamp is farther in the
- // future, the buffer won't be acquired, and PRESENT_LATER will be
- // returned. The presentation time is in nanoseconds, and the time base
- // is CLOCK_MONOTONIC.
- virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen);
-
- // releaseBuffer releases a buffer slot from the consumer back to the
- // BufferQueue. This may be done while the buffer's contents are still
- // being accessed. The fence will signal when the buffer is no longer
- // in use. frameNumber is used to indentify the exact buffer returned.
- //
- // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
- // any references to the just-released buffer that it might have, as if it
- // had received a onBuffersReleased() call with a mask set for the released
- // buffer.
- //
- // Note that the dependencies on EGL will be removed once we switch to using
- // the Android HW Sync HAL.
- virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
- EGLDisplay display, EGLSyncKHR fence,
- const sp<Fence>& releaseFence);
-
- // consumerConnect connects a consumer to the BufferQueue. Only one
- // consumer may be connected, and when that consumer disconnects the
- // BufferQueue is placed into the "abandoned" state, causing most
- // interactions with the BufferQueue by the producer to fail.
- // controlledByApp indicates whether the consumer is controlled by
- // the application.
- //
- // consumer may not be NULL.
- virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp);
-
- // consumerDisconnect disconnects a consumer from the BufferQueue. All
- // buffers will be freed and the BufferQueue is placed in the "abandoned"
- // state, causing most interactions with the BufferQueue by the producer to
- // fail.
- virtual status_t consumerDisconnect();
-
- // getReleasedBuffers sets the value pointed to by slotMask to a bit mask
- // indicating which buffer slots have been released by the BufferQueue
- // but have not yet been released by the consumer.
- //
- // This should be called from the onBuffersReleased() callback.
- virtual status_t getReleasedBuffers(uint32_t* slotMask);
-
- // setDefaultBufferSize is used to set the size of buffers returned by
- // dequeueBuffer when a width and height of zero is requested. Default
- // is 1x1.
- virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h);
-
- // setDefaultMaxBufferCount sets the default value for the maximum buffer
- // count (the initial default is 2). If the producer has requested a
- // buffer count using setBufferCount, the default buffer count will only
- // take effect if the producer sets the count back to zero.
- //
- // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
- virtual status_t setDefaultMaxBufferCount(int bufferCount);
-
- // disableAsyncBuffer disables the extra buffer used in async mode
- // (when both producer and consumer have set their "isControlledByApp"
- // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
- //
- // This can only be called before consumerConnect().
- virtual status_t disableAsyncBuffer();
-
- // setMaxAcquiredBufferCount sets the maximum number of buffers that can
- // be acquired by the consumer at one time (default 1). This call will
- // fail if a producer is connected to the BufferQueue.
- virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
-
- // setConsumerName sets the name used in logging
- virtual void setConsumerName(const String8& name);
-
- // setDefaultBufferFormat allows the BufferQueue to create
- // GraphicBuffers of a defaultFormat if no format is specified
- // in dequeueBuffer. Formats are enumerated in graphics.h; the
- // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
- virtual status_t setDefaultBufferFormat(uint32_t defaultFormat);
-
- // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
- // These are merged with the bits passed to dequeueBuffer. The values are
- // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
- virtual status_t setConsumerUsageBits(uint32_t usage);
-
- // setTransformHint bakes in rotation to buffers so overlays can be used.
- // The values are enumerated in window.h, e.g.
- // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
- virtual status_t setTransformHint(uint32_t hint);
-
- // dump our state in a String
- virtual void dump(String8& result, const char* prefix) const;
-
+ static void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
+ sp<IGraphicBufferConsumer>* outConsumer,
+ const sp<IGraphicBufferAlloc>& allocator = NULL);
private:
- // freeBufferLocked frees the GraphicBuffer and sync resources for the
- // given slot.
- void freeBufferLocked(int index);
-
- // freeAllBuffersLocked frees the GraphicBuffer and sync resources for
- // all slots.
- void freeAllBuffersLocked();
-
- // setDefaultMaxBufferCountLocked sets the maximum number of buffer slots
- // that will be used if the producer does not override the buffer slot
- // count. The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
- // The initial default is 2.
- status_t setDefaultMaxBufferCountLocked(int count);
-
- // getMinUndequeuedBufferCount returns the minimum number of buffers
- // that must remain in a state other than DEQUEUED.
- // The async parameter tells whether we're in asynchronous mode.
- int getMinUndequeuedBufferCount(bool async) const;
-
- // getMinBufferCountLocked returns the minimum number of buffers allowed
- // given the current BufferQueue state.
- // The async parameter tells whether we're in asynchronous mode.
- int getMinMaxBufferCountLocked(bool async) const;
-
- // getMaxBufferCountLocked returns the maximum number of buffers that can
- // be allocated at once. This value depends upon the following member
- // variables:
- //
- // mDequeueBufferCannotBlock
- // mMaxAcquiredBufferCount
- // mDefaultMaxBufferCount
- // mOverrideMaxBufferCount
- // async parameter
- //
- // Any time one of these member variables is changed while a producer is
- // connected, mDequeueCondition must be broadcast.
- int getMaxBufferCountLocked(bool async) const;
-
- // stillTracking returns true iff the buffer item is still being tracked
- // in one of the slots.
- bool stillTracking(const BufferItem *item) const;
-
- struct BufferSlot {
-
- BufferSlot()
- : mEglDisplay(EGL_NO_DISPLAY),
- mBufferState(BufferSlot::FREE),
- mRequestBufferCalled(false),
- mFrameNumber(0),
- mEglFence(EGL_NO_SYNC_KHR),
- mAcquireCalled(false),
- mNeedsCleanupOnRelease(false) {
- }
-
- // mGraphicBuffer points to the buffer allocated for this slot or is NULL
- // if no buffer has been allocated.
- sp<GraphicBuffer> mGraphicBuffer;
-
- // mEglDisplay is the EGLDisplay used to create EGLSyncKHR objects.
- EGLDisplay mEglDisplay;
-
- // BufferState represents the different states in which a buffer slot
- // can be. All slots are initially FREE.
- enum BufferState {
- // FREE indicates that the buffer is available to be dequeued
- // by the producer. The buffer may be in use by the consumer for
- // a finite time, so the buffer must not be modified until the
- // associated fence is signaled.
- //
- // The slot is "owned" by BufferQueue. It transitions to DEQUEUED
- // when dequeueBuffer is called.
- FREE = 0,
-
- // DEQUEUED indicates that the buffer has been dequeued by the
- // producer, but has not yet been queued or canceled. The
- // producer may modify the buffer's contents as soon as the
- // associated ready fence is signaled.
- //
- // The slot is "owned" by the producer. It can transition to
- // QUEUED (via queueBuffer) or back to FREE (via cancelBuffer).
- DEQUEUED = 1,
-
- // QUEUED indicates that the buffer has been filled by the
- // producer and queued for use by the consumer. The buffer
- // contents may continue to be modified for a finite time, so
- // the contents must not be accessed until the associated fence
- // is signaled.
- //
- // The slot is "owned" by BufferQueue. It can transition to
- // ACQUIRED (via acquireBuffer) or to FREE (if another buffer is
- // queued in asynchronous mode).
- QUEUED = 2,
-
- // ACQUIRED indicates that the buffer has been acquired by the
- // consumer. As with QUEUED, the contents must not be accessed
- // by the consumer until the fence is signaled.
- //
- // The slot is "owned" by the consumer. It transitions to FREE
- // when releaseBuffer is called.
- ACQUIRED = 3
- };
-
- // mBufferState is the current state of this buffer slot.
- BufferState mBufferState;
-
- // mRequestBufferCalled is used for validating that the producer did
- // call requestBuffer() when told to do so. Technically this is not
- // needed but useful for debugging and catching producer bugs.
- bool mRequestBufferCalled;
-
- // mFrameNumber is the number of the queued frame for this slot. This
- // is used to dequeue buffers in LRU order (useful because buffers
- // may be released before their release fence is signaled).
- uint64_t mFrameNumber;
-
- // mEglFence is the EGL sync object that must signal before the buffer
- // associated with this buffer slot may be dequeued. It is initialized
- // to EGL_NO_SYNC_KHR when the buffer is created and may be set to a
- // new sync object in releaseBuffer. (This is deprecated in favor of
- // mFence, below.)
- EGLSyncKHR mEglFence;
-
- // mFence is a fence which will signal when work initiated by the
- // previous owner of the buffer is finished. When the buffer is FREE,
- // the fence indicates when the consumer has finished reading
- // from the buffer, or when the producer has finished writing if it
- // called cancelBuffer after queueing some writes. When the buffer is
- // QUEUED, it indicates when the producer has finished filling the
- // buffer. When the buffer is DEQUEUED or ACQUIRED, the fence has been
- // passed to the consumer or producer along with ownership of the
- // buffer, and mFence is set to NO_FENCE.
- sp<Fence> mFence;
-
- // Indicates whether this buffer has been seen by a consumer yet
- bool mAcquireCalled;
-
- // Indicates whether this buffer needs to be cleaned up by the
- // consumer. This is set when a buffer in ACQUIRED state is freed.
- // It causes releaseBuffer to return STALE_BUFFER_SLOT.
- bool mNeedsCleanupOnRelease;
- };
-
- // mSlots is the array of buffer slots that must be mirrored on the
- // producer side. This allows buffer ownership to be transferred between
- // the producer and consumer without sending a GraphicBuffer over binder.
- // The entire array is initialized to NULL at construction time, and
- // buffers are allocated for a slot when requestBuffer is called with
- // that slot's index.
- BufferSlot mSlots[NUM_BUFFER_SLOTS];
-
- // mDefaultWidth holds the default width of allocated buffers. It is used
- // in dequeueBuffer() if a width and height of zero is specified.
- uint32_t mDefaultWidth;
-
- // mDefaultHeight holds the default height of allocated buffers. It is used
- // in dequeueBuffer() if a width and height of zero is specified.
- uint32_t mDefaultHeight;
-
- // mMaxAcquiredBufferCount is the number of buffers that the consumer may
- // acquire at one time. It defaults to 1 and can be changed by the
- // consumer via the setMaxAcquiredBufferCount method, but this may only be
- // done when no producer is connected to the BufferQueue.
- //
- // This value is used to derive the value returned for the
- // MIN_UNDEQUEUED_BUFFERS query by the producer.
- int mMaxAcquiredBufferCount;
-
- // mDefaultMaxBufferCount is the default limit on the number of buffers
- // that will be allocated at one time. This default limit is set by the
- // consumer. The limit (as opposed to the default limit) may be
- // overridden by the producer.
- int mDefaultMaxBufferCount;
-
- // mOverrideMaxBufferCount is the limit on the number of buffers that will
- // be allocated at one time. This value is set by the image producer by
- // calling setBufferCount. The default is zero, which means the producer
- // doesn't care about the number of buffers in the pool. In that case
- // mDefaultMaxBufferCount is used as the limit.
- int mOverrideMaxBufferCount;
-
- // mGraphicBufferAlloc is the connection to SurfaceFlinger that is used to
- // allocate new GraphicBuffer objects.
- sp<IGraphicBufferAlloc> mGraphicBufferAlloc;
-
- // mConsumerListener is used to notify the connected consumer of
- // asynchronous events that it may wish to react to. It is initially set
- // to NULL and is written by consumerConnect and consumerDisconnect.
- sp<IConsumerListener> mConsumerListener;
-
- // mConsumerControlledByApp whether the connected consumer is controlled by the
- // application.
- bool mConsumerControlledByApp;
-
- // mDequeueBufferCannotBlock whether dequeueBuffer() isn't allowed to block.
- // this flag is set during connect() when both consumer and producer are controlled
- // by the application.
- bool mDequeueBufferCannotBlock;
-
- // mUseAsyncBuffer whether an extra buffer is used in async mode to prevent
- // dequeueBuffer() from ever blocking.
- bool mUseAsyncBuffer;
-
- // mConnectedApi indicates the producer API that is currently connected
- // to this BufferQueue. It defaults to NO_CONNECTED_API (= 0), and gets
- // updated by the connect and disconnect methods.
- int mConnectedApi;
-
- // mDequeueCondition condition used for dequeueBuffer in synchronous mode
- mutable Condition mDequeueCondition;
-
- // mQueue is a FIFO of queued buffers used in synchronous mode
- typedef Vector<BufferItem> Fifo;
- Fifo mQueue;
-
- // mAbandoned indicates that the BufferQueue will no longer be used to
- // consume image buffers pushed to it using the IGraphicBufferProducer
- // interface. It is initialized to false, and set to true in the
- // consumerDisconnect method. A BufferQueue that has been abandoned will
- // return the NO_INIT error from all IGraphicBufferProducer methods
- // capable of returning an error.
- bool mAbandoned;
-
- // mConsumerName is a string used to identify the BufferQueue in log
- // messages. It is set by the setConsumerName method.
- String8 mConsumerName;
-
- // mMutex is the mutex used to prevent concurrent access to the member
- // variables of BufferQueue objects. It must be locked whenever the
- // member variables are accessed.
- mutable Mutex mMutex;
-
- // mFrameCounter is the free running counter, incremented on every
- // successful queueBuffer call, and buffer allocation.
- uint64_t mFrameCounter;
-
- // mBufferHasBeenQueued is true once a buffer has been queued. It is
- // reset when something causes all buffers to be freed (e.g. changing the
- // buffer count).
- bool mBufferHasBeenQueued;
-
- // mDefaultBufferFormat can be set so it will override
- // the buffer format when it isn't specified in dequeueBuffer
- uint32_t mDefaultBufferFormat;
-
- // mConsumerUsageBits contains flags the consumer wants for GraphicBuffers
- uint32_t mConsumerUsageBits;
-
- // mTransformHint is used to optimize for screen rotations
- uint32_t mTransformHint;
-
- // mConnectedProducerToken is used to set a binder death notification on the producer
- sp<IBinder> mConnectedProducerToken;
+ BufferQueue(); // Create through createBufferQueue
};
// ----------------------------------------------------------------------------
diff --git a/include/gui/BufferQueueConsumer.h b/include/gui/BufferQueueConsumer.h
new file mode 100644
index 0000000..1912ed0
--- /dev/null
+++ b/include/gui/BufferQueueConsumer.h
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2014 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_GUI_BUFFERQUEUECONSUMER_H
+#define ANDROID_GUI_BUFFERQUEUECONSUMER_H
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <gui/BufferQueueDefs.h>
+#include <gui/IGraphicBufferConsumer.h>
+
+namespace android {
+
+class BufferQueueCore;
+
+class BufferQueueConsumer : public BnGraphicBufferConsumer {
+
+public:
+ BufferQueueConsumer(const sp<BufferQueueCore>& core);
+ virtual ~BufferQueueConsumer();
+
+ // acquireBuffer attempts to acquire ownership of the next pending buffer in
+ // the BufferQueue. If no buffer is pending then it returns
+ // NO_BUFFER_AVAILABLE. If a buffer is successfully acquired, the
+ // information about the buffer is returned in BufferItem. If the buffer
+ // returned had previously been acquired then the BufferItem::mGraphicBuffer
+ // field of buffer is set to NULL and it is assumed that the consumer still
+ // holds a reference to the buffer.
+ //
+ // If expectedPresent is nonzero, it indicates the time when the buffer
+ // will be displayed on screen. If the buffer's timestamp is farther in the
+ // future, the buffer won't be acquired, and PRESENT_LATER will be
+ // returned. The presentation time is in nanoseconds, and the time base
+ // is CLOCK_MONOTONIC.
+ virtual status_t acquireBuffer(BufferItem* outBuffer,
+ nsecs_t expectedPresent);
+
+ // See IGraphicBufferConsumer::detachBuffer
+ virtual status_t detachBuffer(int slot);
+
+ // See IGraphicBufferConsumer::attachBuffer
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer);
+
+ // releaseBuffer releases a buffer slot from the consumer back to the
+ // BufferQueue. This may be done while the buffer's contents are still
+ // being accessed. The fence will signal when the buffer is no longer
+ // in use. frameNumber is used to indentify the exact buffer returned.
+ //
+ // If releaseBuffer returns STALE_BUFFER_SLOT, then the consumer must free
+ // any references to the just-released buffer that it might have, as if it
+ // had received a onBuffersReleased() call with a mask set for the released
+ // buffer.
+ //
+ // Note that the dependencies on EGL will be removed once we switch to using
+ // the Android HW Sync HAL.
+ virtual status_t releaseBuffer(int slot, uint64_t frameNumber,
+ const sp<Fence>& releaseFence, EGLDisplay display,
+ EGLSyncKHR fence);
+
+ // connect connects a consumer to the BufferQueue. Only one
+ // consumer may be connected, and when that consumer disconnects the
+ // BufferQueue is placed into the "abandoned" state, causing most
+ // interactions with the BufferQueue by the producer to fail.
+ // controlledByApp indicates whether the consumer is controlled by
+ // the application.
+ //
+ // consumerListener may not be NULL.
+ virtual status_t connect(const sp<IConsumerListener>& consumerListener,
+ bool controlledByApp);
+
+ // disconnect disconnects a consumer from the BufferQueue. All
+ // buffers will be freed and the BufferQueue is placed in the "abandoned"
+ // state, causing most interactions with the BufferQueue by the producer to
+ // fail.
+ virtual status_t disconnect();
+
+ // getReleasedBuffers sets the value pointed to by outSlotMask to a bit mask
+ // indicating which buffer slots have been released by the BufferQueue
+ // but have not yet been released by the consumer.
+ //
+ // This should be called from the onBuffersReleased() callback.
+ virtual status_t getReleasedBuffers(uint64_t* outSlotMask);
+
+ // setDefaultBufferSize is used to set the size of buffers returned by
+ // dequeueBuffer when a width and height of zero is requested. Default
+ // is 1x1.
+ virtual status_t setDefaultBufferSize(uint32_t width, uint32_t height);
+
+ // setDefaultMaxBufferCount sets the default value for the maximum buffer
+ // count (the initial default is 2). If the producer has requested a
+ // buffer count using setBufferCount, the default buffer count will only
+ // take effect if the producer sets the count back to zero.
+ //
+ // The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ virtual status_t setDefaultMaxBufferCount(int bufferCount);
+
+ // disableAsyncBuffer disables the extra buffer used in async mode
+ // (when both producer and consumer have set their "isControlledByApp"
+ // flag) and has dequeueBuffer() return WOULD_BLOCK instead.
+ //
+ // This can only be called before connect().
+ virtual status_t disableAsyncBuffer();
+
+ // setMaxAcquiredBufferCount sets the maximum number of buffers that can
+ // be acquired by the consumer at one time (default 1). This call will
+ // fail if a producer is connected to the BufferQueue.
+ virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
+
+ // setConsumerName sets the name used in logging
+ virtual void setConsumerName(const String8& name);
+
+ // setDefaultBufferFormat allows the BufferQueue to create
+ // GraphicBuffers of a defaultFormat if no format is specified
+ // in dequeueBuffer. Formats are enumerated in graphics.h; the
+ // initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ virtual status_t setDefaultBufferFormat(uint32_t defaultFormat);
+
+ // setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
+ // These are merged with the bits passed to dequeueBuffer. The values are
+ // enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ virtual status_t setConsumerUsageBits(uint32_t usage);
+
+ // setTransformHint bakes in rotation to buffers so overlays can be used.
+ // The values are enumerated in window.h, e.g.
+ // NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ virtual status_t setTransformHint(uint32_t hint);
+
+ // Retrieve the sideband buffer stream, if any.
+ virtual sp<NativeHandle> getSidebandStream() const;
+
+ // dump our state in a String
+ virtual void dump(String8& result, const char* prefix) const;
+
+ // Functions required for backwards compatibility.
+ // These will be modified/renamed in IGraphicBufferConsumer and will be
+ // removed from this class at that time. See b/13306289.
+
+ virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
+ EGLDisplay display, EGLSyncKHR fence,
+ const sp<Fence>& releaseFence) {
+ return releaseBuffer(buf, frameNumber, releaseFence, display, fence);
+ }
+
+ virtual status_t consumerConnect(const sp<IConsumerListener>& consumer,
+ bool controlledByApp) {
+ return connect(consumer, controlledByApp);
+ }
+
+ virtual status_t consumerDisconnect() { return disconnect(); }
+
+ // End functions required for backwards compatibility
+
+private:
+ sp<BufferQueueCore> mCore;
+
+ // This references mCore->mSlots. Lock mCore->mMutex while accessing.
+ BufferQueueDefs::SlotsType& mSlots;
+
+ // This is a cached copy of the name stored in the BufferQueueCore.
+ // It's updated during setConsumerName.
+ String8 mConsumerName;
+
+}; // class BufferQueueConsumer
+
+} // namespace android
+
+#endif
diff --git a/include/gui/BufferQueueCore.h b/include/gui/BufferQueueCore.h
new file mode 100644
index 0000000..cfcb7a5
--- /dev/null
+++ b/include/gui/BufferQueueCore.h
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2014 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_GUI_BUFFERQUEUECORE_H
+#define ANDROID_GUI_BUFFERQUEUECORE_H
+
+#include <gui/BufferQueueDefs.h>
+#include <gui/BufferSlot.h>
+
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+#include <utils/NativeHandle.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+#include <utils/Trace.h>
+#include <utils/Vector.h>
+
+#define BQ_LOGV(x, ...) ALOGV("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
+#define BQ_LOGD(x, ...) ALOGD("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
+#define BQ_LOGI(x, ...) ALOGI("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
+#define BQ_LOGW(x, ...) ALOGW("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
+#define BQ_LOGE(x, ...) ALOGE("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
+
+#define ATRACE_BUFFER_INDEX(index) \
+ if (ATRACE_ENABLED()) { \
+ char ___traceBuf[1024]; \
+ snprintf(___traceBuf, 1024, "%s: %d", \
+ mCore->mConsumerName.string(), (index)); \
+ android::ScopedTrace ___bufTracer(ATRACE_TAG, ___traceBuf); \
+ }
+
+namespace android {
+
+class BufferItem;
+class IConsumerListener;
+class IGraphicBufferAlloc;
+class IProducerListener;
+
+class BufferQueueCore : public virtual RefBase {
+
+ friend class BufferQueueProducer;
+ friend class BufferQueueConsumer;
+
+public:
+ // Used as a placeholder slot number when the value isn't pointing to an
+ // existing buffer.
+ enum { INVALID_BUFFER_SLOT = -1 }; // TODO: Extract from IGBC::BufferItem
+
+ // We reserve two slots in order to guarantee that the producer and
+ // consumer can run asynchronously.
+ enum { MAX_MAX_ACQUIRED_BUFFERS = BufferQueueDefs::NUM_BUFFER_SLOTS - 2 };
+
+ // The default API number used to indicate that no producer is connected
+ enum { NO_CONNECTED_API = 0 };
+
+ typedef Vector<BufferItem> Fifo;
+
+ // BufferQueueCore manages a pool of gralloc memory slots to be used by
+ // producers and consumers. allocator is used to allocate all the needed
+ // gralloc buffers.
+ BufferQueueCore(const sp<IGraphicBufferAlloc>& allocator = NULL);
+ virtual ~BufferQueueCore();
+
+private:
+ // Dump our state in a string
+ void dump(String8& result, const char* prefix) const;
+
+ // getMinUndequeuedBufferCountLocked returns the minimum number of buffers
+ // that must remain in a state other than DEQUEUED. The async parameter
+ // tells whether we're in asynchronous mode.
+ int getMinUndequeuedBufferCountLocked(bool async) const;
+
+ // getMinMaxBufferCountLocked returns the minimum number of buffers allowed
+ // given the current BufferQueue state. The async parameter tells whether
+ // we're in asynchonous mode.
+ int getMinMaxBufferCountLocked(bool async) const;
+
+ // getMaxBufferCountLocked returns the maximum number of buffers that can be
+ // allocated at once. This value depends on the following member variables:
+ //
+ // mDequeueBufferCannotBlock
+ // mMaxAcquiredBufferCount
+ // mDefaultMaxBufferCount
+ // mOverrideMaxBufferCount
+ // async parameter
+ //
+ // Any time one of these member variables is changed while a producer is
+ // connected, mDequeueCondition must be broadcast.
+ int getMaxBufferCountLocked(bool async) const;
+
+ // setDefaultMaxBufferCountLocked sets the maximum number of buffer slots
+ // that will be used if the producer does not override the buffer slot
+ // count. The count must be between 2 and NUM_BUFFER_SLOTS, inclusive. The
+ // initial default is 2.
+ status_t setDefaultMaxBufferCountLocked(int count);
+
+ // freeBufferLocked frees the GraphicBuffer and sync resources for the
+ // given slot.
+ void freeBufferLocked(int slot);
+
+ // freeAllBuffersLocked frees the GraphicBuffer and sync resources for
+ // all slots.
+ void freeAllBuffersLocked();
+
+ // stillTracking returns true iff the buffer item is still being tracked
+ // in one of the slots.
+ bool stillTracking(const BufferItem* item) const;
+
+ // mAllocator is the connection to SurfaceFlinger that is used to allocate
+ // new GraphicBuffer objects.
+ sp<IGraphicBufferAlloc> mAllocator;
+
+ // mMutex is the mutex used to prevent concurrent access to the member
+ // variables of BufferQueueCore objects. It must be locked whenever any
+ // member variable is accessed.
+ mutable Mutex mMutex;
+
+ // mIsAbandoned indicates that the BufferQueue will no longer be used to
+ // consume image buffers pushed to it using the IGraphicBufferProducer
+ // interface. It is initialized to false, and set to true in the
+ // consumerDisconnect method. A BufferQueue that is abandoned will return
+ // the NO_INIT error from all IGraphicBufferProducer methods capable of
+ // returning an error.
+ bool mIsAbandoned;
+
+ // mConsumerControlledByApp indicates whether the connected consumer is
+ // controlled by the application.
+ bool mConsumerControlledByApp;
+
+ // mConsumerName is a string used to identify the BufferQueue in log
+ // messages. It is set by the IGraphicBufferConsumer::setConsumerName
+ // method.
+ String8 mConsumerName;
+
+ // mConsumerListener is used to notify the connected consumer of
+ // asynchronous events that it may wish to react to. It is initially
+ // set to NULL and is written by consumerConnect and consumerDisconnect.
+ sp<IConsumerListener> mConsumerListener;
+
+ // mConsumerUsageBits contains flags that the consumer wants for
+ // GraphicBuffers.
+ uint32_t mConsumerUsageBits;
+
+ // mConnectedApi indicates the producer API that is currently connected
+ // to this BufferQueue. It defaults to NO_CONNECTED_API, and gets updated
+ // by the connect and disconnect methods.
+ int mConnectedApi;
+
+ // mConnectedProducerToken is used to set a binder death notification on
+ // the producer.
+ sp<IProducerListener> mConnectedProducerListener;
+
+ // mSlots is an array of buffer slots that must be mirrored on the producer
+ // side. This allows buffer ownership to be transferred between the producer
+ // and consumer without sending a GraphicBuffer over Binder. The entire
+ // array is initialized to NULL at construction time, and buffers are
+ // allocated for a slot when requestBuffer is called with that slot's index.
+ BufferQueueDefs::SlotsType mSlots;
+
+ // mQueue is a FIFO of queued buffers used in synchronous mode.
+ Fifo mQueue;
+
+ // mOverrideMaxBufferCount is the limit on the number of buffers that will
+ // be allocated at one time. This value is set by the producer by calling
+ // setBufferCount. The default is 0, which means that the producer doesn't
+ // care about the number of buffers in the pool. In that case,
+ // mDefaultMaxBufferCount is used as the limit.
+ int mOverrideMaxBufferCount;
+
+ // mDequeueCondition is a condition variable used for dequeueBuffer in
+ // synchronous mode.
+ mutable Condition mDequeueCondition;
+
+ // mUseAsyncBuffer indicates whether an extra buffer is used in async mode
+ // to prevent dequeueBuffer from blocking.
+ bool mUseAsyncBuffer;
+
+ // mDequeueBufferCannotBlock indicates whether dequeueBuffer is allowed to
+ // block. This flag is set during connect when both the producer and
+ // consumer are controlled by the application.
+ bool mDequeueBufferCannotBlock;
+
+ // mDefaultBufferFormat can be set so it will override the buffer format
+ // when it isn't specified in dequeueBuffer.
+ uint32_t mDefaultBufferFormat;
+
+ // mDefaultWidth holds the default width of allocated buffers. It is used
+ // in dequeueBuffer if a width and height of 0 are specified.
+ int mDefaultWidth;
+
+ // mDefaultHeight holds the default height of allocated buffers. It is used
+ // in dequeueBuffer if a width and height of 0 are specified.
+ int mDefaultHeight;
+
+ // mDefaultMaxBufferCount is the default limit on the number of buffers that
+ // will be allocated at one time. This default limit is set by the consumer.
+ // The limit (as opposed to the default limit) may be overriden by the
+ // producer.
+ int mDefaultMaxBufferCount;
+
+ // mMaxAcquiredBufferCount is the number of buffers that the consumer may
+ // acquire at one time. It defaults to 1, and can be changed by the consumer
+ // via setMaxAcquiredBufferCount, but this may only be done while no
+ // producer is connected to the BufferQueue. This value is used to derive
+ // the value returned for the MIN_UNDEQUEUED_BUFFERS query to the producer.
+ int mMaxAcquiredBufferCount;
+
+ // mBufferHasBeenQueued is true once a buffer has been queued. It is reset
+ // when something causes all buffers to be freed (e.g., changing the buffer
+ // count).
+ bool mBufferHasBeenQueued;
+
+ // mFrameCounter is the free running counter, incremented on every
+ // successful queueBuffer call and buffer allocation.
+ uint64_t mFrameCounter;
+
+ // mTransformHint is used to optimize for screen rotations.
+ uint32_t mTransformHint;
+
+ // mSidebandStream is a handle to the sideband buffer stream, if any
+ sp<NativeHandle> mSidebandStream;
+
+}; // class BufferQueueCore
+
+} // namespace android
+
+#endif
diff --git a/include/gui/BufferQueueDefs.h b/include/gui/BufferQueueDefs.h
new file mode 100644
index 0000000..83e9580
--- /dev/null
+++ b/include/gui/BufferQueueDefs.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 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_GUI_BUFFERQUEUECOREDEFS_H
+#define ANDROID_GUI_BUFFERQUEUECOREDEFS_H
+
+#include <gui/BufferSlot.h>
+
+namespace android {
+ class BufferQueueCore;
+
+ namespace BufferQueueDefs {
+ // BufferQueue will keep track of at most this value of buffers.
+ // Attempts at runtime to increase the number of buffers past this
+ // will fail.
+ enum { NUM_BUFFER_SLOTS = 64 };
+
+ typedef BufferSlot SlotsType[NUM_BUFFER_SLOTS];
+ } // namespace BufferQueueDefs
+} // namespace android
+
+#endif
diff --git a/include/gui/BufferQueueProducer.h b/include/gui/BufferQueueProducer.h
new file mode 100644
index 0000000..9df3633
--- /dev/null
+++ b/include/gui/BufferQueueProducer.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2014 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_GUI_BUFFERQUEUEPRODUCER_H
+#define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
+
+#include <gui/BufferQueueDefs.h>
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+
+class BufferSlot;
+
+class BufferQueueProducer : public BnGraphicBufferProducer,
+ private IBinder::DeathRecipient {
+public:
+ friend class BufferQueue; // Needed to access binderDied
+
+ BufferQueueProducer(const sp<BufferQueueCore>& core);
+ virtual ~BufferQueueProducer();
+
+ // requestBuffer returns the GraphicBuffer for slot N.
+ //
+ // In normal operation, this is called the first time slot N is returned
+ // by dequeueBuffer. It must be called again if dequeueBuffer returns
+ // flags indicating that previously-returned buffers are no longer valid.
+ virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
+
+ // setBufferCount updates the number of available buffer slots. If this
+ // method succeeds, buffer slots will be both unallocated and owned by
+ // the BufferQueue object (i.e. they are not owned by the producer or
+ // consumer).
+ //
+ // This will fail if the producer has dequeued any buffers, or if
+ // bufferCount is invalid. bufferCount must generally be a value
+ // between the minimum undequeued buffer count (exclusive) and NUM_BUFFER_SLOTS
+ // (inclusive). It may also be set to zero (the default) to indicate
+ // that the producer does not wish to set a value. The minimum value
+ // can be obtained by calling query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ // ...).
+ //
+ // This may only be called by the producer. The consumer will be told
+ // to discard buffers through the onBuffersReleased callback.
+ virtual status_t setBufferCount(int bufferCount);
+
+ // dequeueBuffer gets the next buffer slot index for the producer to use.
+ // If a buffer slot is available then that slot index is written to the
+ // location pointed to by the buf argument and a status of OK is returned.
+ // If no slot is available then a status of -EBUSY is returned and buf is
+ // unmodified.
+ //
+ // The outFence parameter will be updated to hold the fence associated with
+ // the buffer. The contents of the buffer must not be overwritten until the
+ // fence signals. If the fence is Fence::NO_FENCE, the buffer may be
+ // written immediately.
+ //
+ // The width and height parameters must be no greater than the minimum of
+ // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv).
+ // An error due to invalid dimensions might not be reported until
+ // updateTexImage() is called. If width and height are both zero, the
+ // default values specified by setDefaultBufferSize() are used instead.
+ //
+ // The pixel formats are enumerated in graphics.h, e.g.
+ // HAL_PIXEL_FORMAT_RGBA_8888. If the format is 0, the default format
+ // will be used.
+ //
+ // The usage argument specifies gralloc buffer usage flags. The values
+ // are enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER. These
+ // will be merged with the usage flags specified by setConsumerUsageBits.
+ //
+ // The return value may be a negative error value or a non-negative
+ // collection of flags. If the flags are set, the return values are
+ // valid, but additional actions must be performed.
+ //
+ // If IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION is set, the
+ // producer must discard cached GraphicBuffer references for the slot
+ // returned in buf.
+ // If IGraphicBufferProducer::RELEASE_ALL_BUFFERS is set, the producer
+ // must discard cached GraphicBuffer references for all slots.
+ //
+ // In both cases, the producer will need to call requestBuffer to get a
+ // GraphicBuffer handle for the returned slot.
+ virtual status_t dequeueBuffer(int *outSlot, sp<Fence>* outFence, bool async,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage);
+
+ // See IGraphicBufferProducer::detachBuffer
+ virtual status_t detachBuffer(int slot);
+
+ // See IGraphicBufferProducer::detachNextBuffer
+ virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence);
+
+ // See IGraphicBufferProducer::attachBuffer
+ virtual status_t attachBuffer(int* outSlot, const sp<GraphicBuffer>& buffer);
+
+ // queueBuffer returns a filled buffer to the BufferQueue.
+ //
+ // Additional data is provided in the QueueBufferInput struct. Notably,
+ // a timestamp must be provided for the buffer. The timestamp is in
+ // nanoseconds, and must be monotonically increasing. Its other semantics
+ // (zero point, etc) are producer-specific and should be documented by the
+ // producer.
+ //
+ // The caller may provide a fence that signals when all rendering
+ // operations have completed. Alternatively, NO_FENCE may be used,
+ // indicating that the buffer is ready immediately.
+ //
+ // Some values are returned in the output struct: the current settings
+ // for default width and height, the current transform hint, and the
+ // number of queued buffers.
+ virtual status_t queueBuffer(int slot,
+ const QueueBufferInput& input, QueueBufferOutput* output);
+
+ // cancelBuffer returns a dequeued buffer to the BufferQueue, but doesn't
+ // queue it for use by the consumer.
+ //
+ // The buffer will not be overwritten until the fence signals. The fence
+ // will usually be the one obtained from dequeueBuffer.
+ virtual void cancelBuffer(int slot, const sp<Fence>& fence);
+
+ // Query native window attributes. The "what" values are enumerated in
+ // window.h (e.g. NATIVE_WINDOW_FORMAT).
+ virtual int query(int what, int* outValue);
+
+ // connect attempts to connect a producer API to the BufferQueue. This
+ // must be called before any other IGraphicBufferProducer methods are
+ // called except for getAllocator. A consumer must already be connected.
+ //
+ // This method will fail if connect was previously called on the
+ // BufferQueue and no corresponding disconnect call was made (i.e. if
+ // it's still connected to a producer).
+ //
+ // APIs are enumerated in window.h (e.g. NATIVE_WINDOW_API_CPU).
+ virtual status_t connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp, QueueBufferOutput* output);
+
+ // disconnect attempts to disconnect a producer API from the BufferQueue.
+ // Calling this method will cause any subsequent calls to other
+ // IGraphicBufferProducer methods to fail except for getAllocator and connect.
+ // Successfully calling connect after this will allow the other methods to
+ // succeed again.
+ //
+ // This method will fail if the the BufferQueue is not currently
+ // connected to the specified producer API.
+ virtual status_t disconnect(int api);
+
+ // Attaches a sideband buffer stream to the IGraphicBufferProducer.
+ //
+ // A sideband stream is a device-specific mechanism for passing buffers
+ // from the producer to the consumer without using dequeueBuffer/
+ // queueBuffer. If a sideband stream is present, the consumer can choose
+ // whether to acquire buffers from the sideband stream or from the queued
+ // buffers.
+ //
+ // Passing NULL or a different stream handle will detach the previous
+ // handle if any.
+ virtual status_t setSidebandStream(const sp<NativeHandle>& stream);
+
+private:
+ // This is required by the IBinder::DeathRecipient interface
+ virtual void binderDied(const wp<IBinder>& who);
+
+ // waitForFreeSlotThenRelock finds the oldest slot in the FREE state. It may
+ // block if there are no available slots and we are not in non-blocking
+ // mode (producer and consumer controlled by the application). If it blocks,
+ // it will release mCore->mMutex while blocked so that other operations on
+ // the BufferQueue may succeed.
+ status_t waitForFreeSlotThenRelock(const char* caller, bool async,
+ int* found, status_t* returnFlags) const;
+
+ sp<BufferQueueCore> mCore;
+
+ // This references mCore->mSlots. Lock mCore->mMutex while accessing.
+ BufferQueueDefs::SlotsType& mSlots;
+
+ // This is a cached copy of the name stored in the BufferQueueCore.
+ // It's updated during connect and dequeueBuffer (which should catch
+ // most updates).
+ String8 mConsumerName;
+
+}; // class BufferQueueProducer
+
+} // namespace android
+
+#endif
diff --git a/include/gui/BufferSlot.h b/include/gui/BufferSlot.h
new file mode 100644
index 0000000..6085e11
--- /dev/null
+++ b/include/gui/BufferSlot.h
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2014 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_GUI_BUFFERSLOT_H
+#define ANDROID_GUI_BUFFERSLOT_H
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class Fence;
+
+struct BufferSlot {
+
+ BufferSlot()
+ : mEglDisplay(EGL_NO_DISPLAY),
+ mBufferState(BufferSlot::FREE),
+ mRequestBufferCalled(false),
+ mFrameNumber(0),
+ mEglFence(EGL_NO_SYNC_KHR),
+ mAcquireCalled(false),
+ mNeedsCleanupOnRelease(false),
+ mAttachedByConsumer(false) {
+ }
+
+ // mGraphicBuffer points to the buffer allocated for this slot or is NULL
+ // if no buffer has been allocated.
+ sp<GraphicBuffer> mGraphicBuffer;
+
+ // mEglDisplay is the EGLDisplay used to create EGLSyncKHR objects.
+ EGLDisplay mEglDisplay;
+
+ // BufferState represents the different states in which a buffer slot
+ // can be. All slots are initially FREE.
+ enum BufferState {
+ // FREE indicates that the buffer is available to be dequeued
+ // by the producer. The buffer may be in use by the consumer for
+ // a finite time, so the buffer must not be modified until the
+ // associated fence is signaled.
+ //
+ // The slot is "owned" by BufferQueue. It transitions to DEQUEUED
+ // when dequeueBuffer is called.
+ FREE = 0,
+
+ // DEQUEUED indicates that the buffer has been dequeued by the
+ // producer, but has not yet been queued or canceled. The
+ // producer may modify the buffer's contents as soon as the
+ // associated ready fence is signaled.
+ //
+ // The slot is "owned" by the producer. It can transition to
+ // QUEUED (via queueBuffer) or back to FREE (via cancelBuffer).
+ DEQUEUED = 1,
+
+ // QUEUED indicates that the buffer has been filled by the
+ // producer and queued for use by the consumer. The buffer
+ // contents may continue to be modified for a finite time, so
+ // the contents must not be accessed until the associated fence
+ // is signaled.
+ //
+ // The slot is "owned" by BufferQueue. It can transition to
+ // ACQUIRED (via acquireBuffer) or to FREE (if another buffer is
+ // queued in asynchronous mode).
+ QUEUED = 2,
+
+ // ACQUIRED indicates that the buffer has been acquired by the
+ // consumer. As with QUEUED, the contents must not be accessed
+ // by the consumer until the fence is signaled.
+ //
+ // The slot is "owned" by the consumer. It transitions to FREE
+ // when releaseBuffer is called.
+ ACQUIRED = 3
+ };
+
+ static const char* bufferStateName(BufferState state);
+
+ // mBufferState is the current state of this buffer slot.
+ BufferState mBufferState;
+
+ // mRequestBufferCalled is used for validating that the producer did
+ // call requestBuffer() when told to do so. Technically this is not
+ // needed but useful for debugging and catching producer bugs.
+ bool mRequestBufferCalled;
+
+ // mFrameNumber is the number of the queued frame for this slot. This
+ // is used to dequeue buffers in LRU order (useful because buffers
+ // may be released before their release fence is signaled).
+ uint64_t mFrameNumber;
+
+ // mEglFence is the EGL sync object that must signal before the buffer
+ // associated with this buffer slot may be dequeued. It is initialized
+ // to EGL_NO_SYNC_KHR when the buffer is created and may be set to a
+ // new sync object in releaseBuffer. (This is deprecated in favor of
+ // mFence, below.)
+ EGLSyncKHR mEglFence;
+
+ // mFence is a fence which will signal when work initiated by the
+ // previous owner of the buffer is finished. When the buffer is FREE,
+ // the fence indicates when the consumer has finished reading
+ // from the buffer, or when the producer has finished writing if it
+ // called cancelBuffer after queueing some writes. When the buffer is
+ // QUEUED, it indicates when the producer has finished filling the
+ // buffer. When the buffer is DEQUEUED or ACQUIRED, the fence has been
+ // passed to the consumer or producer along with ownership of the
+ // buffer, and mFence is set to NO_FENCE.
+ sp<Fence> mFence;
+
+ // Indicates whether this buffer has been seen by a consumer yet
+ bool mAcquireCalled;
+
+ // Indicates whether this buffer needs to be cleaned up by the
+ // consumer. This is set when a buffer in ACQUIRED state is freed.
+ // It causes releaseBuffer to return STALE_BUFFER_SLOT.
+ bool mNeedsCleanupOnRelease;
+
+ // Indicates whether the buffer was attached on the consumer side.
+ // If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when dequeued
+ // to prevent the producer from using a stale cached buffer.
+ bool mAttachedByConsumer;
+};
+
+} // namespace android
+
+#endif
diff --git a/include/gui/ConsumerBase.h b/include/gui/ConsumerBase.h
index fb21185..100bb26 100644
--- a/include/gui/ConsumerBase.h
+++ b/include/gui/ConsumerBase.h
@@ -101,11 +101,14 @@ protected:
// Implementation of the IConsumerListener interface. These
// calls are used to notify the ConsumerBase of asynchronous events in the
- // BufferQueue. These methods should not need to be overridden by derived
- // classes, but if they are overridden the ConsumerBase implementation
- // must be called from the derived class.
+ // BufferQueue. The onFrameAvailable and onBuffersReleased methods should
+ // not need to be overridden by derived classes, but if they are overridden
+ // the ConsumerBase implementation must be called from the derived class.
+ // The ConsumerBase version of onSidebandStreamChanged does nothing and can
+ // be overriden by derived classes if they want the notification.
virtual void onFrameAvailable();
virtual void onBuffersReleased();
+ virtual void onSidebandStreamChanged();
// freeBufferLocked frees up the given buffer slot. If the slot has been
// initialized this will release the reference to the GraphicBuffer in that
diff --git a/include/gui/IConsumerListener.h b/include/gui/IConsumerListener.h
index ac2f9bb..260099e 100644
--- a/include/gui/IConsumerListener.h
+++ b/include/gui/IConsumerListener.h
@@ -57,6 +57,12 @@ public:
// This is called without any lock held and can be called concurrently
// by multiple threads.
virtual void onBuffersReleased() = 0; /* Asynchronous */
+
+ // onSidebandStreamChanged is called to notify the buffer consumer that the
+ // BufferQueue's sideband buffer stream has changed. This is called when a
+ // stream is first attached and when it is either detached or replaced by a
+ // different stream.
+ virtual void onSidebandStreamChanged() = 0; /* Asynchronous */
};
diff --git a/include/gui/IGraphicBufferConsumer.h b/include/gui/IGraphicBufferConsumer.h
index 0e35f13..15f51fe 100644
--- a/include/gui/IGraphicBufferConsumer.h
+++ b/include/gui/IGraphicBufferConsumer.h
@@ -27,12 +27,16 @@
#include <binder/IInterface.h>
#include <ui/Rect.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
namespace android {
// ----------------------------------------------------------------------------
-class IConsumerListener;
-class GraphicBuffer;
class Fence;
+class GraphicBuffer;
+class IConsumerListener;
+class NativeHandle;
class IGraphicBufferConsumer : public IInterface {
@@ -48,6 +52,7 @@ public:
status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
public:
+ // The default value of mBuf, used to indicate this doesn't correspond to a slot.
enum { INVALID_BUFFER_SLOT = -1 };
BufferItem();
@@ -63,13 +68,17 @@ public:
Rect mCrop;
// mTransform is the current transform flags for this buffer slot.
+ // refer to NATIVE_WINDOW_TRANSFORM_* in <window.h>
uint32_t mTransform;
// mScalingMode is the current scaling mode for this buffer slot.
+ // refer to NATIVE_WINDOW_SCALING_* in <window.h>
uint32_t mScalingMode;
// mTimestamp is the current timestamp for this buffer slot. This gets
- // to set by queueBuffer each time this slot is queued.
+ // to set by queueBuffer each time this slot is queued. This value
+ // is guaranteed to be monotonically increasing for each newly
+ // acquired buffer.
int64_t mTimestamp;
// mIsAutoTimestamp indicates whether mTimestamp was generated
@@ -79,7 +88,7 @@ public:
// mFrameNumber is the number of the queued frame for this slot.
uint64_t mFrameNumber;
- // mBuf is the slot index of this buffer
+ // mBuf is the slot index of this buffer (default INVALID_BUFFER_SLOT).
int mBuf;
// mIsDroppable whether this buffer was queued with the
@@ -97,21 +106,72 @@ public:
bool mTransformToDisplayInverse;
};
+ enum {
+ // Returned by releaseBuffer, after which the consumer must
+ // free any references to the just-released buffer that it might have.
+ STALE_BUFFER_SLOT = 1,
+ // Returned by dequeueBuffer if there are no pending buffers available.
+ NO_BUFFER_AVAILABLE,
+ // Returned by dequeueBuffer if it's too early for the buffer to be acquired.
+ PRESENT_LATER,
+ };
// acquireBuffer attempts to acquire ownership of the next pending buffer in
- // the BufferQueue. If no buffer is pending then it returns -EINVAL. If a
- // buffer is successfully acquired, the information about the buffer is
- // returned in BufferItem. If the buffer returned had previously been
+ // the BufferQueue. If no buffer is pending then it returns
+ // NO_BUFFER_AVAILABLE. If a buffer is successfully acquired, the
+ // information about the buffer is returned in BufferItem.
+ //
+ // If the buffer returned had previously been
// acquired then the BufferItem::mGraphicBuffer field of buffer is set to
// NULL and it is assumed that the consumer still holds a reference to the
// buffer.
//
- // If presentWhen is nonzero, it indicates the time when the buffer will
+ // If presentWhen is non-zero, it indicates the time when the buffer will
// be displayed on screen. If the buffer's timestamp is farther in the
// future, the buffer won't be acquired, and PRESENT_LATER will be
// returned. The presentation time is in nanoseconds, and the time base
// is CLOCK_MONOTONIC.
- virtual status_t acquireBuffer(BufferItem *buffer, nsecs_t presentWhen) = 0;
+ //
+ // Return of NO_ERROR means the operation completed as normal.
+ //
+ // Return of a positive value means the operation could not be completed
+ // at this time, but the user should try again later:
+ // * NO_BUFFER_AVAILABLE - no buffer is pending (nothing queued by producer)
+ // * PRESENT_LATER - the buffer's timestamp is farther in the future
+ //
+ // Return of a negative value means an error has occurred:
+ // * INVALID_OPERATION - too many buffers have been acquired
+ virtual status_t acquireBuffer(BufferItem* buffer, nsecs_t presentWhen) = 0;
+
+ // detachBuffer attempts to remove all ownership of the buffer in the given
+ // slot from the buffer queue. If this call succeeds, the slot will be
+ // freed, and there will be no way to obtain the buffer from this interface.
+ // The freed slot will remain unallocated until either it is selected to
+ // hold a freshly allocated buffer in dequeueBuffer or a buffer is attached
+ // to the slot. The buffer must have already been acquired.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - the given slot number is invalid, either because it is
+ // out of the range [0, NUM_BUFFER_SLOTS) or because the slot
+ // it refers to is not currently acquired.
+ virtual status_t detachBuffer(int slot) = 0;
+
+ // attachBuffer attempts to transfer ownership of a buffer to the buffer
+ // queue. If this call succeeds, it will be as if this buffer was acquired
+ // from the returned slot number. As such, this call will fail if attaching
+ // this buffer would cause too many buffers to be simultaneously acquired.
+ //
+ // If the buffer is successfully attached, its frameNumber is initialized
+ // to 0. This must be passed into the releaseBuffer call or else the buffer
+ // will be deallocated as stale.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - outSlot or buffer were NULL
+ // * INVALID_OPERATION - cannot attach the buffer because it would cause too
+ // many buffers to be acquired.
+ // * NO_MEMORY - no free slots available
+ virtual status_t attachBuffer(int *outSlot,
+ const sp<GraphicBuffer>& buffer) = 0;
// releaseBuffer releases a buffer slot from the consumer back to the
// BufferQueue. This may be done while the buffer's contents are still
@@ -125,6 +185,18 @@ public:
//
// Note that the dependencies on EGL will be removed once we switch to using
// the Android HW Sync HAL.
+ //
+ // Return of NO_ERROR means the operation completed as normal.
+ //
+ // Return of a positive value means the operation could not be completed
+ // at this time, but the user should try again later:
+ // * STALE_BUFFER_SLOT - see above (second paragraph)
+ //
+ // Return of a negative value means an error has occurred:
+ // * BAD_VALUE - one of the following could've happened:
+ // * the buffer slot was invalid
+ // * the fence was NULL
+ // * the buffer slot specified is not in the acquired state
virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
EGLDisplay display, EGLSyncKHR fence,
const sp<Fence>& releaseFence) = 0;
@@ -137,24 +209,38 @@ public:
// the application.
//
// consumer may not be NULL.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned
+ // * BAD_VALUE - a NULL consumer was provided
virtual status_t consumerConnect(const sp<IConsumerListener>& consumer, bool controlledByApp) = 0;
// consumerDisconnect disconnects a consumer from the BufferQueue. All
// buffers will be freed and the BufferQueue is placed in the "abandoned"
// state, causing most interactions with the BufferQueue by the producer to
// fail.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - no consumer is currently connected
virtual status_t consumerDisconnect() = 0;
- // getReleasedBuffers sets the value pointed to by slotMask to a bit mask
- // indicating which buffer slots have been released by the BufferQueue
- // but have not yet been released by the consumer.
+ // getReleasedBuffers sets the value pointed to by slotMask to a bit set.
+ // Each bit index with a 1 corresponds to a released buffer slot with that
+ // index value. In particular, a released buffer is one that has
+ // been released by the BufferQueue but have not yet been released by the consumer.
//
// This should be called from the onBuffersReleased() callback.
- virtual status_t getReleasedBuffers(uint32_t* slotMask) = 0;
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ virtual status_t getReleasedBuffers(uint64_t* slotMask) = 0;
// setDefaultBufferSize is used to set the size of buffers returned by
// dequeueBuffer when a width and height of zero is requested. Default
// is 1x1.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - either w or h was zero
virtual status_t setDefaultBufferSize(uint32_t w, uint32_t h) = 0;
// setDefaultMaxBufferCount sets the default value for the maximum buffer
@@ -163,6 +249,9 @@ public:
// take effect if the producer sets the count back to zero.
//
// The count must be between 2 and NUM_BUFFER_SLOTS, inclusive.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - bufferCount was out of range (see above).
virtual status_t setDefaultMaxBufferCount(int bufferCount) = 0;
// disableAsyncBuffer disables the extra buffer used in async mode
@@ -170,11 +259,20 @@ public:
// flag) and has dequeueBuffer() return WOULD_BLOCK instead.
//
// This can only be called before consumerConnect().
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * INVALID_OPERATION - attempting to call this after consumerConnect.
virtual status_t disableAsyncBuffer() = 0;
// setMaxAcquiredBufferCount sets the maximum number of buffers that can
// be acquired by the consumer at one time (default 1). This call will
// fail if a producer is connected to the BufferQueue.
+ //
+ // maxAcquiredBuffers must be (inclusive) between 1 and MAX_MAX_ACQUIRED_BUFFERS.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - maxAcquiredBuffers was out of range (see above).
+ // * INVALID_OPERATION - attempting to call this after a producer connected.
virtual status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers) = 0;
// setConsumerName sets the name used in logging
@@ -184,18 +282,27 @@ public:
// GraphicBuffers of a defaultFormat if no format is specified
// in dequeueBuffer. Formats are enumerated in graphics.h; the
// initial default is HAL_PIXEL_FORMAT_RGBA_8888.
+ //
+ // Return of a value other than NO_ERROR means an unknown error has occurred.
virtual status_t setDefaultBufferFormat(uint32_t defaultFormat) = 0;
// setConsumerUsageBits will turn on additional usage bits for dequeueBuffer.
// These are merged with the bits passed to dequeueBuffer. The values are
// enumerated in gralloc.h, e.g. GRALLOC_USAGE_HW_RENDER; the default is 0.
+ //
+ // Return of a value other than NO_ERROR means an unknown error has occurred.
virtual status_t setConsumerUsageBits(uint32_t usage) = 0;
// setTransformHint bakes in rotation to buffers so overlays can be used.
// The values are enumerated in window.h, e.g.
// NATIVE_WINDOW_TRANSFORM_ROT_90. The default is 0 (no transform).
+ //
+ // Return of a value other than NO_ERROR means an unknown error has occurred.
virtual status_t setTransformHint(uint32_t hint) = 0;
+ // Retrieve the sideband buffer stream, if any.
+ virtual sp<NativeHandle> getSidebandStream() const = 0;
+
// dump state into a string
virtual void dump(String8& result, const char* prefix) const = 0;
diff --git a/include/gui/IGraphicBufferProducer.h b/include/gui/IGraphicBufferProducer.h
index 342ba08..d9e116b 100644
--- a/include/gui/IGraphicBufferProducer.h
+++ b/include/gui/IGraphicBufferProducer.h
@@ -32,6 +32,8 @@
namespace android {
// ----------------------------------------------------------------------------
+class IProducerListener;
+class NativeHandle;
class Surface;
/*
@@ -54,7 +56,11 @@ public:
DECLARE_META_INTERFACE(GraphicBufferProducer);
enum {
+ // A flag returned by dequeueBuffer when the client needs to call
+ // requestBuffer immediately thereafter.
BUFFER_NEEDS_REALLOCATION = 0x1,
+ // A flag returned by dequeueBuffer when all mirrored slots should be
+ // released by the client. This flag should always be processed first.
RELEASE_ALL_BUFFERS = 0x2,
};
@@ -63,51 +69,210 @@ public:
// buffer to the given slot index, and the client is expected to mirror the
// slot->buffer mapping so that it's not necessary to transfer a
// GraphicBuffer for every dequeue operation.
+ //
+ // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ // * BAD_VALUE - one of the two conditions occurred:
+ // * slot was out of range (see above)
+ // * buffer specified by the slot is not dequeued
virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf) = 0;
// setBufferCount sets the number of buffer slots available. Calling this
// will also cause all buffer slots to be emptied. The caller should empty
// its mirrored copy of the buffer slots when calling this method.
+ //
+ // This function should not be called when there are any dequeued buffer
+ // slots, doing so will result in a BAD_VALUE error returned.
+ //
+ // The buffer count should be at most NUM_BUFFER_SLOTS (inclusive), but at least
+ // the minimum undequeued buffer count (exclusive). The minimum value
+ // can be obtained by calling query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS).
+ // In particular the range is (minUndequeudBuffers, NUM_BUFFER_SLOTS].
+ //
+ // The buffer count may also be set to 0 (the default), to indicate that
+ // the producer does not wish to set a value.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ // * BAD_VALUE - one of the below conditions occurred:
+ // * bufferCount was out of range (see above)
+ // * client has one or more buffers dequeued
virtual status_t setBufferCount(int bufferCount) = 0;
// dequeueBuffer requests a new buffer slot for the client to use. Ownership
// of the slot is transfered to the client, meaning that the server will not
- // use the contents of the buffer associated with that slot. The slot index
- // returned may or may not contain a buffer. If the slot is empty the client
- // should call requestBuffer to assign a new buffer to that slot. The client
- // is expected to either call cancelBuffer on the dequeued slot or to fill
- // in the contents of its associated buffer contents and call queueBuffer.
- // If dequeueBuffer return BUFFER_NEEDS_REALLOCATION, the client is
+ // use the contents of the buffer associated with that slot.
+ //
+ // The slot index returned may or may not contain a buffer (client-side).
+ // If the slot is empty the client should call requestBuffer to assign a new
+ // buffer to that slot.
+ //
+ // Once the client is done filling this buffer, it is expected to transfer
+ // buffer ownership back to the server with either cancelBuffer on
+ // the dequeued slot or to fill in the contents of its associated buffer
+ // contents and call queueBuffer.
+ //
+ // If dequeueBuffer returns the BUFFER_NEEDS_REALLOCATION flag, the client is
// expected to call requestBuffer immediately.
//
+ // If dequeueBuffer returns the RELEASE_ALL_BUFFERS flag, the client is
+ // expected to release all of the mirrored slot->buffer mappings.
+ //
// The fence parameter will be updated to hold the fence associated with
// the buffer. The contents of the buffer must not be overwritten until the
- // fence signals. If the fence is NULL, the buffer may be written
+ // fence signals. If the fence is Fence::NO_FENCE, the buffer may be written
// immediately.
//
- // The async parameter sets whether we're in asynchrnous mode for this
- // deququeBuffer() call.
- virtual status_t dequeueBuffer(int *slot, sp<Fence>* fence, bool async,
+ // The async parameter sets whether we're in asynchronous mode for this
+ // dequeueBuffer() call.
+ //
+ // The width and height parameters must be no greater than the minimum of
+ // GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see: glGetIntegerv).
+ // An error due to invalid dimensions might not be reported until
+ // updateTexImage() is called. If width and height are both zero, the
+ // default values specified by setDefaultBufferSize() are used instead.
+ //
+ // The pixel formats are enumerated in <graphics.h>, e.g.
+ // HAL_PIXEL_FORMAT_RGBA_8888. If the format is 0, the default format
+ // will be used.
+ //
+ // The usage argument specifies gralloc buffer usage flags. The values
+ // are enumerated in <gralloc.h>, e.g. GRALLOC_USAGE_HW_RENDER. These
+ // will be merged with the usage flags specified by
+ // IGraphicBufferConsumer::setConsumerUsageBits.
+ //
+ // This call will block until a buffer is available to be dequeued. If
+ // both the producer and consumer are controlled by the app, then this call
+ // can never block and will return WOULD_BLOCK if no buffer is available.
+ //
+ // A non-negative value with flags set (see above) will be returned upon
+ // success.
+ //
+ // Return of a negative means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ // * BAD_VALUE - both in async mode and buffer count was less than the
+ // max numbers of buffers that can be allocated at once.
+ // * INVALID_OPERATION - cannot attach the buffer because it would cause
+ // too many buffers to be dequeued, either because
+ // the producer already has a single buffer dequeued
+ // and did not set a buffer count, or because a
+ // buffer count was set and this call would cause
+ // it to be exceeded.
+ // * WOULD_BLOCK - no buffer is currently available, and blocking is disabled
+ // since both the producer/consumer are controlled by app
+ // * NO_MEMORY - out of memory, cannot allocate the graphics buffer.
+ //
+ // All other negative values are an unknown error returned downstream
+ // from the graphics allocator (typically errno).
+ virtual status_t dequeueBuffer(int* slot, sp<Fence>* fence, bool async,
uint32_t w, uint32_t h, uint32_t format, uint32_t usage) = 0;
+ // detachBuffer attempts to remove all ownership of the buffer in the given
+ // slot from the buffer queue. If this call succeeds, the slot will be
+ // freed, and there will be no way to obtain the buffer from this interface.
+ // The freed slot will remain unallocated until either it is selected to
+ // hold a freshly allocated buffer in dequeueBuffer or a buffer is attached
+ // to the slot. The buffer must have already been dequeued, and the caller
+ // must already possesses the sp<GraphicBuffer> (i.e., must have called
+ // requestBuffer).
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ // * BAD_VALUE - the given slot number is invalid, either because it is
+ // out of the range [0, NUM_BUFFER_SLOTS), or because the slot
+ // it refers to is not currently dequeued and requested.
+ virtual status_t detachBuffer(int slot) = 0;
+
+ // detachNextBuffer is equivalent to calling dequeueBuffer, requestBuffer,
+ // and detachBuffer in sequence, except for two things:
+ //
+ // 1) It is unnecessary to know the dimensions, format, or usage of the
+ // next buffer.
+ // 2) It will not block, since if it cannot find an appropriate buffer to
+ // return, it will return an error instead.
+ //
+ // Only slots that are free but still contain a GraphicBuffer will be
+ // considered, and the oldest of those will be returned. outBuffer is
+ // equivalent to outBuffer from the requestBuffer call, and outFence is
+ // equivalent to fence from the dequeueBuffer call.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ // * BAD_VALUE - either outBuffer or outFence were NULL.
+ // * NO_MEMORY - no slots were found that were both free and contained a
+ // GraphicBuffer.
+ virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence) = 0;
+
+ // attachBuffer attempts to transfer ownership of a buffer to the buffer
+ // queue. If this call succeeds, it will be as if this buffer was dequeued
+ // from the returned slot number. As such, this call will fail if attaching
+ // this buffer would cause too many buffers to be simultaneously dequeued.
+ //
+ // If attachBuffer returns the RELEASE_ALL_BUFFERS flag, the caller is
+ // expected to release all of the mirrored slot->buffer mappings.
+ //
+ // A non-negative value with flags set (see above) will be returned upon
+ // success.
+ //
+ // Return of a negative value means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ // * BAD_VALUE - outSlot or buffer were NULL or invalid combination of
+ // async mode and buffer count override.
+ // * INVALID_OPERATION - cannot attach the buffer because it would cause
+ // too many buffers to be dequeued, either because
+ // the producer already has a single buffer dequeued
+ // and did not set a buffer count, or because a
+ // buffer count was set and this call would cause
+ // it to be exceeded.
+ // * WOULD_BLOCK - no buffer slot is currently available, and blocking is
+ // disabled since both the producer/consumer are
+ // controlled by the app.
+ virtual status_t attachBuffer(int* outSlot,
+ const sp<GraphicBuffer>& buffer) = 0;
+
// queueBuffer indicates that the client has finished filling in the
// contents of the buffer associated with slot and transfers ownership of
- // that slot back to the server. It is not valid to call queueBuffer on a
- // slot that is not owned by the client or one for which a buffer associated
- // via requestBuffer. In addition, a timestamp must be provided by the
- // client for this buffer. The timestamp is measured in nanoseconds, and
- // must be monotonically increasing. Its other properties (zero point, etc)
+ // that slot back to the server.
+ //
+ // It is not valid to call queueBuffer on a slot that is not owned
+ // by the client or one for which a buffer associated via requestBuffer
+ // (an attempt to do so will fail with a return value of BAD_VALUE).
+ //
+ // In addition, the input must be described by the client (as documented
+ // below). Any other properties (zero point, etc)
// are client-dependent, and should be documented by the client.
//
- // The async parameter sets whether we're queuing a buffer in asynchronous mode.
+ // The slot must be in the range of [0, NUM_BUFFER_SLOTS).
+ //
+ // Upon success, the output will be filled with meaningful values
+ // (refer to the documentation below).
//
- // outWidth, outHeight and outTransform are filled with the default width
- // and height of the window and current transform applied to buffers,
- // respectively.
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ // * BAD_VALUE - one of the below conditions occurred:
+ // * fence was NULL
+ // * scaling mode was unknown
+ // * both in async mode and buffer count was less than the
+ // max numbers of buffers that can be allocated at once
+ // * slot index was out of range (see above).
+ // * the slot was not in the dequeued state
+ // * the slot was enqueued without requesting a buffer
+ // * crop rect is out of bounds of the buffer dimensions
struct QueueBufferInput : public Flattenable<QueueBufferInput> {
friend class Flattenable<QueueBufferInput>;
inline QueueBufferInput(const Parcel& parcel);
+ // timestamp - a monotonically increasing value in nanoseconds
+ // isAutoTimestamp - if the timestamp was synthesized at queue time
+ // crop - a crop rectangle that's used as a hint to the consumer
+ // scalingMode - a set of flags from NATIVE_WINDOW_SCALING_* in <window.h>
+ // transform - a set of flags from NATIVE_WINDOW_TRANSFORM_* in <window.h>
+ // async - if the buffer is queued in asynchronous mode
+ // fence - a fence that the consumer must wait on before reading the buffer,
+ // set this to Fence::NO_FENCE if the buffer is ready immediately
inline QueueBufferInput(int64_t timestamp, bool isAutoTimestamp,
const Rect& crop, int scalingMode, uint32_t transform, bool async,
const sp<Fence>& fence)
@@ -143,8 +308,13 @@ public:
};
// QueueBufferOutput must be a POD structure
- struct QueueBufferOutput {
+ struct __attribute__ ((__packed__)) QueueBufferOutput {
inline QueueBufferOutput() { }
+ // outWidth - filled with default width applied to the buffer
+ // outHeight - filled with default height applied to the buffer
+ // outTransformHint - filled with default transform applied to the buffer
+ // outNumPendingBuffers - num buffers queued that haven't yet been acquired
+ // (counting the currently queued buffer)
inline void deflate(uint32_t* outWidth,
uint32_t* outHeight,
uint32_t* outTransformHint,
@@ -174,25 +344,57 @@ public:
// cancelBuffer indicates that the client does not wish to fill in the
// buffer associated with slot and transfers ownership of the slot back to
// the server.
+ //
+ // The buffer is not queued for use by the consumer.
+ //
+ // The buffer will not be overwritten until the fence signals. The fence
+ // will usually be the one obtained from dequeueBuffer.
virtual void cancelBuffer(int slot, const sp<Fence>& fence) = 0;
// query retrieves some information for this surface
- // 'what' tokens allowed are that of android_natives.h
+ // 'what' tokens allowed are that of NATIVE_WINDOW_* in <window.h>
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - the buffer queue has been abandoned.
+ // * BAD_VALUE - what was out of range
virtual int query(int what, int* value) = 0;
// connect attempts to connect a client API to the IGraphicBufferProducer.
// This must be called before any other IGraphicBufferProducer methods are
- // called except for getAllocator.
+ // called except for getAllocator. A consumer must be already connected.
//
// This method will fail if the connect was previously called on the
// IGraphicBufferProducer and no corresponding disconnect call was made.
//
- // outWidth, outHeight and outTransform are filled with the default width
- // and height of the window and current transform applied to buffers,
- // respectively. The token needs to be any binder object that lives in the
- // producer process -- it is solely used for obtaining a death notification
- // when the producer is killed.
- virtual status_t connect(const sp<IBinder>& token,
+ // The listener is an optional binder callback object that can be used if
+ // the producer wants to be notified when the consumer releases a buffer
+ // back to the BufferQueue. It is also used to detect the death of the
+ // producer. If only the latter functionality is desired, there is a
+ // DummyProducerListener class in IProducerListener.h that can be used.
+ //
+ // The api should be one of the NATIVE_WINDOW_API_* values in <window.h>
+ //
+ // The producerControlledByApp should be set to true if the producer is hosted
+ // by an untrusted process (typically app_process-forked processes). If both
+ // the producer and the consumer are app-controlled then all buffer queues
+ // will operate in async mode regardless of the async flag.
+ //
+ // Upon success, the output will be filled with meaningful data
+ // (refer to QueueBufferOutput documentation above).
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * NO_INIT - one of the following occurred:
+ // * the buffer queue was abandoned
+ // * no consumer has yet connected
+ // * BAD_VALUE - one of the following has occurred:
+ // * the producer is already connected
+ // * api was out of range (see above).
+ // * output was NULL.
+ // * DEAD_OBJECT - the token is hosted by an already-dead process
+ //
+ // Additional negative errors may be returned by the internals, they
+ // should be treated as opaque fatal unrecoverable errors.
+ virtual status_t connect(const sp<IProducerListener>& listener,
int api, bool producerControlledByApp, QueueBufferOutput* output) = 0;
// disconnect attempts to disconnect a client API from the
@@ -203,7 +405,30 @@ public:
//
// This method will fail if the the IGraphicBufferProducer is not currently
// connected to the specified client API.
+ //
+ // The api should be one of the NATIVE_WINDOW_API_* values in <window.h>
+ //
+ // Disconnecting from an abandoned IGraphicBufferProducer is legal and
+ // is considered a no-op.
+ //
+ // Return of a value other than NO_ERROR means an error has occurred:
+ // * BAD_VALUE - one of the following has occurred:
+ // * the api specified does not match the one that was connected
+ // * api was out of range (see above).
+ // * DEAD_OBJECT - the token is hosted by an already-dead process
virtual status_t disconnect(int api) = 0;
+
+ // Attaches a sideband buffer stream to the IGraphicBufferProducer.
+ //
+ // A sideband stream is a device-specific mechanism for passing buffers
+ // from the producer to the consumer without using dequeueBuffer/
+ // queueBuffer. If a sideband stream is present, the consumer can choose
+ // whether to acquire buffers from the sideband stream or from the queued
+ // buffers.
+ //
+ // Passing NULL or a different stream handle will detach the previous
+ // handle if any.
+ virtual status_t setSidebandStream(const sp<NativeHandle>& stream) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/gui/IProducerListener.h b/include/gui/IProducerListener.h
new file mode 100644
index 0000000..3848a6c
--- /dev/null
+++ b/include/gui/IProducerListener.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2014 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_GUI_IPRODUCERLISTENER_H
+#define ANDROID_GUI_IPRODUCERLISTENER_H
+
+#include <binder/IInterface.h>
+
+#include <utils/RefBase.h>
+
+namespace android {
+
+// ProducerListener is the interface through which the BufferQueue notifies the
+// producer of events that the producer may wish to react to. Because the
+// producer will generally have a mutex that is locked during calls from the
+// producer to the BufferQueue, these calls from the BufferQueue to the
+// producer *MUST* be called only when the BufferQueue mutex is NOT locked.
+
+class ProducerListener : public virtual RefBase
+{
+public:
+ ProducerListener() {}
+ virtual ~ProducerListener() {}
+
+ // onBufferReleased is called from IGraphicBufferConsumer::releaseBuffer to
+ // notify the producer that a new buffer is free and ready to be dequeued.
+ //
+ // This is called without any lock held and can be called concurrently by
+ // multiple threads.
+ virtual void onBufferReleased() = 0; // Asynchronous
+};
+
+class IProducerListener : public ProducerListener, public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(ProducerListener)
+};
+
+class BnProducerListener : public BnInterface<IProducerListener>
+{
+public:
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+class DummyProducerListener : public BnProducerListener
+{
+public:
+ virtual void onBufferReleased() {}
+};
+
+} // namespace android
+
+#endif
diff --git a/include/gui/ISensorEventConnection.h b/include/gui/ISensorEventConnection.h
index f64c6b8..b296797 100644
--- a/include/gui/ISensorEventConnection.h
+++ b/include/gui/ISensorEventConnection.h
@@ -40,6 +40,7 @@ public:
nsecs_t maxBatchReportLatencyNs, int reservedFlags) = 0;
virtual status_t setEventRate(int handle, nsecs_t ns) = 0;
virtual status_t flush() = 0;
+ virtual void decreaseWakeLockRefCount() = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/gui/ISurfaceComposer.h b/include/gui/ISurfaceComposer.h
index 5c3c99c..efde5db 100644
--- a/include/gui/ISurfaceComposer.h
+++ b/include/gui/ISurfaceComposer.h
@@ -22,9 +22,12 @@
#include <utils/RefBase.h>
#include <utils/Errors.h>
+#include <utils/Timers.h>
+#include <utils/Vector.h>
#include <binder/IInterface.h>
+#include <ui/FrameStats.h>
#include <ui/PixelFormat.h>
#include <gui/IGraphicBufferAlloc.h>
@@ -120,7 +123,21 @@ public:
virtual status_t captureScreen(const sp<IBinder>& display,
const sp<IGraphicBufferProducer>& producer,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ) = 0;
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform) = 0;
+
+
+ /* Clears the frame statistics for animations.
+ *
+ * Requires the ACCESS_SURFACE_FLINGER permission.
+ */
+ virtual status_t clearAnimationFrameStats() = 0;
+
+ /* Gets the frame statistics for animations.
+ *
+ * Requires the ACCESS_SURFACE_FLINGER permission.
+ */
+ virtual status_t getAnimationFrameStats(FrameStats* outStats) const = 0;
};
// ----------------------------------------------------------------------------
@@ -144,6 +161,8 @@ public:
GET_DISPLAY_INFO,
CONNECT_DISPLAY,
CAPTURE_SCREEN,
+ CLEAR_ANIMATION_FRAME_STATS,
+ GET_ANIMATION_FRAME_STATS
};
virtual status_t onTransact(uint32_t code, const Parcel& data,
diff --git a/include/gui/ISurfaceComposerClient.h b/include/gui/ISurfaceComposerClient.h
index cb9816f..58c1f6a 100644
--- a/include/gui/ISurfaceComposerClient.h
+++ b/include/gui/ISurfaceComposerClient.h
@@ -25,6 +25,7 @@
#include <binder/IInterface.h>
+#include <ui/FrameStats.h>
#include <ui/PixelFormat.h>
namespace android {
@@ -65,6 +66,16 @@ public:
* Requires ACCESS_SURFACE_FLINGER permission
*/
virtual status_t destroySurface(const sp<IBinder>& handle) = 0;
+
+ /*
+ * Requires ACCESS_SURFACE_FLINGER permission
+ */
+ virtual status_t clearLayerFrameStats(const sp<IBinder>& handle) const = 0;
+
+ /*
+ * Requires ACCESS_SURFACE_FLINGER permission
+ */
+ virtual status_t getLayerFrameStats(const sp<IBinder>& handle, FrameStats* outStats) const = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/gui/Sensor.h b/include/gui/Sensor.h
index 033b262..41a6cc6 100644
--- a/include/gui/Sensor.h
+++ b/include/gui/Sensor.h
@@ -71,6 +71,7 @@ public:
int32_t getFifoMaxEventCount() const;
const String8& getStringType() const;
const String8& getRequiredPermission() const;
+ bool isWakeUpSensor() const;
// LightFlattenable protocol
inline bool isFixedSize() const { return false; }
@@ -93,6 +94,8 @@ private:
int32_t mFifoMaxEventCount;
String8 mStringType;
String8 mRequiredPermission;
+ // Todo: Surface this in java SDK.
+ bool mWakeUpSensor;
static void flattenString8(void*& buffer, size_t& size, const String8& string8);
static bool unflattenString8(void const*& buffer, size_t& size, String8& outputString8);
};
diff --git a/include/gui/SensorEventQueue.h b/include/gui/SensorEventQueue.h
index 0bfc7a0..4e8a2d2 100644
--- a/include/gui/SensorEventQueue.h
+++ b/include/gui/SensorEventQueue.h
@@ -27,7 +27,7 @@
#include <gui/BitTube.h>
// ----------------------------------------------------------------------------
-
+#define WAKE_UP_SENSOR_EVENT_NEEDS_ACK (1 << 31)
struct ALooper;
struct ASensorEvent;
@@ -75,7 +75,8 @@ public:
int reservedFlags) const;
status_t disableSensor(int32_t handle) const;
status_t flush() const;
-
+ // Send an ack for every wake_up sensor event that is set to WAKE_UP_SENSOR_EVENT_NEEDS_ACK.
+ void sendAck(const ASensorEvent* events, int count);
private:
sp<Looper> getLooper() const;
sp<ISensorEventConnection> mSensorEventConnection;
diff --git a/include/gui/StreamSplitter.h b/include/gui/StreamSplitter.h
new file mode 100644
index 0000000..f927953
--- /dev/null
+++ b/include/gui/StreamSplitter.h
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2014 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_GUI_STREAMSPLITTER_H
+#define ANDROID_GUI_STREAMSPLITTER_H
+
+#include <gui/IConsumerListener.h>
+#include <gui/IProducerListener.h>
+
+#include <utils/Condition.h>
+#include <utils/KeyedVector.h>
+#include <utils/Mutex.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class GraphicBuffer;
+class IGraphicBufferConsumer;
+class IGraphicBufferProducer;
+
+// StreamSplitter is an autonomous class that manages one input BufferQueue
+// and multiple output BufferQueues. By using the buffer attach and detach logic
+// in BufferQueue, it is able to present the illusion of a single split
+// BufferQueue, where each buffer queued to the input is available to be
+// acquired by each of the outputs, and is able to be dequeued by the input
+// again only once all of the outputs have released it.
+class StreamSplitter : public BnConsumerListener {
+public:
+ // createSplitter creates a new splitter, outSplitter, using inputQueue as
+ // the input BufferQueue. Output BufferQueues must be added using addOutput
+ // before queueing any buffers to the input.
+ //
+ // A return value other than NO_ERROR means that an error has occurred and
+ // outSplitter has not been modified. BAD_VALUE is returned if inputQueue or
+ // outSplitter is NULL. See IGraphicBufferConsumer::consumerConnect for
+ // explanations of other error codes.
+ static status_t createSplitter(const sp<IGraphicBufferConsumer>& inputQueue,
+ sp<StreamSplitter>* outSplitter);
+
+ // addOutput adds an output BufferQueue to the splitter. The splitter
+ // connects to outputQueue as a CPU producer, and any buffers queued
+ // to the input will be queued to each output. It is assumed that all of the
+ // outputs are added before any buffers are queued on the input. If any
+ // output is abandoned by its consumer, the splitter will abandon its input
+ // queue (see onAbandoned).
+ //
+ // A return value other than NO_ERROR means that an error has occurred and
+ // outputQueue has not been added to the splitter. BAD_VALUE is returned if
+ // outputQueue is NULL. See IGraphicBufferProducer::connect for explanations
+ // of other error codes.
+ status_t addOutput(const sp<IGraphicBufferProducer>& outputQueue);
+
+ // setName sets the consumer name of the input queue
+ void setName(const String8& name);
+
+private:
+ // From IConsumerListener
+ //
+ // During this callback, we store some tracking information, detach the
+ // buffer from the input, and attach it to each of the outputs. This call
+ // can block if there are too many outstanding buffers. If it blocks, it
+ // will resume when onBufferReleasedByOutput releases a buffer back to the
+ // input.
+ virtual void onFrameAvailable();
+
+ // From IConsumerListener
+ // We don't care about released buffers because we detach each buffer as
+ // soon as we acquire it. See the comment for onBufferReleased below for
+ // some clarifying notes about the name.
+ virtual void onBuffersReleased() {}
+
+ // From IConsumerListener
+ // We don't care about sideband streams, since we won't be splitting them
+ virtual void onSidebandStreamChanged() {}
+
+ // This is the implementation of the onBufferReleased callback from
+ // IProducerListener. It gets called from an OutputListener (see below), and
+ // 'from' is which producer interface from which the callback was received.
+ //
+ // During this callback, we detach the buffer from the output queue that
+ // generated the callback, update our state tracking to see if this is the
+ // last output releasing the buffer, and if so, release it to the input.
+ // If we release the buffer to the input, we allow a blocked
+ // onFrameAvailable call to proceed.
+ void onBufferReleasedByOutput(const sp<IGraphicBufferProducer>& from);
+
+ // When this is called, the splitter disconnects from (i.e., abandons) its
+ // input queue and signals any waiting onFrameAvailable calls to wake up.
+ // It still processes callbacks from other outputs, but only detaches their
+ // buffers so they can continue operating until they run out of buffers to
+ // acquire. This must be called with mMutex locked.
+ void onAbandonedLocked();
+
+ // This is a thin wrapper class that lets us determine which BufferQueue
+ // the IProducerListener::onBufferReleased callback is associated with. We
+ // create one of these per output BufferQueue, and then pass the producer
+ // into onBufferReleasedByOutput above.
+ class OutputListener : public BnProducerListener,
+ public IBinder::DeathRecipient {
+ public:
+ OutputListener(const sp<StreamSplitter>& splitter,
+ const sp<IGraphicBufferProducer>& output);
+ virtual ~OutputListener();
+
+ // From IProducerListener
+ virtual void onBufferReleased();
+
+ // From IBinder::DeathRecipient
+ virtual void binderDied(const wp<IBinder>& who);
+
+ private:
+ sp<StreamSplitter> mSplitter;
+ sp<IGraphicBufferProducer> mOutput;
+ };
+
+ class BufferTracker : public LightRefBase<BufferTracker> {
+ public:
+ BufferTracker(const sp<GraphicBuffer>& buffer);
+
+ const sp<GraphicBuffer>& getBuffer() const { return mBuffer; }
+ const sp<Fence>& getMergedFence() const { return mMergedFence; }
+
+ void mergeFence(const sp<Fence>& with);
+
+ // Returns the new value
+ // Only called while mMutex is held
+ size_t incrementReleaseCountLocked() { return ++mReleaseCount; }
+
+ private:
+ // Only destroy through LightRefBase
+ friend LightRefBase<BufferTracker>;
+ ~BufferTracker();
+
+ // Disallow copying
+ BufferTracker(const BufferTracker& other);
+ BufferTracker& operator=(const BufferTracker& other);
+
+ sp<GraphicBuffer> mBuffer; // One instance that holds this native handle
+ sp<Fence> mMergedFence;
+ size_t mReleaseCount;
+ };
+
+ // Only called from createSplitter
+ StreamSplitter(const sp<IGraphicBufferConsumer>& inputQueue);
+
+ // Must be accessed through RefBase
+ virtual ~StreamSplitter();
+
+ static const int MAX_OUTSTANDING_BUFFERS = 2;
+
+ // mIsAbandoned is set to true when an output dies. Once the StreamSplitter
+ // has been abandoned, it will continue to detach buffers from other
+ // outputs, but it will disconnect from the input and not attempt to
+ // communicate with it further.
+ bool mIsAbandoned;
+
+ Mutex mMutex;
+ Condition mReleaseCondition;
+ int mOutstandingBuffers;
+ sp<IGraphicBufferConsumer> mInput;
+ Vector<sp<IGraphicBufferProducer> > mOutputs;
+
+ // Map of GraphicBuffer IDs (GraphicBuffer::getId()) to buffer tracking
+ // objects (which are mostly for counting how many outputs have released the
+ // buffer, but also contain merged release fences).
+ KeyedVector<uint64_t, sp<BufferTracker> > mBuffers;
+};
+
+} // namespace android
+
+#endif
diff --git a/include/gui/Surface.h b/include/gui/Surface.h
index 6f8a97c..d8e9756 100644
--- a/include/gui/Surface.h
+++ b/include/gui/Surface.h
@@ -78,6 +78,19 @@ public:
return surface != NULL && surface->getIGraphicBufferProducer() != NULL;
}
+ /* Attaches a sideband buffer stream to the Surface's IGraphicBufferProducer.
+ *
+ * A sideband stream is a device-specific mechanism for passing buffers
+ * from the producer to the consumer without using dequeueBuffer/
+ * queueBuffer. If a sideband stream is present, the consumer can choose
+ * whether to acquire buffers from the sideband stream or from the queued
+ * buffers.
+ *
+ * Passing NULL or a different stream handle will detach the previous
+ * handle if any.
+ */
+ void setSidebandStream(const sp<NativeHandle>& stream);
+
protected:
virtual ~Surface();
diff --git a/include/gui/SurfaceComposerClient.h b/include/gui/SurfaceComposerClient.h
index e982bcd..6a53ccb 100644
--- a/include/gui/SurfaceComposerClient.h
+++ b/include/gui/SurfaceComposerClient.h
@@ -28,6 +28,7 @@
#include <utils/SortedVector.h>
#include <utils/threads.h>
+#include <ui/FrameStats.h>
#include <ui/PixelFormat.h>
#include <gui/CpuConsumer.h>
@@ -125,6 +126,12 @@ public:
status_t setLayerStack(const sp<IBinder>& id, uint32_t layerStack);
status_t destroySurface(const sp<IBinder>& id);
+ status_t clearLayerFrameStats(const sp<IBinder>& token) const;
+ status_t getLayerFrameStats(const sp<IBinder>& token, FrameStats* outStats) const;
+
+ static status_t clearAnimationFrameStats();
+ static status_t getAnimationFrameStats(FrameStats* outStats);
+
static void setDisplaySurface(const sp<IBinder>& token,
const sp<IGraphicBufferProducer>& bufferProducer);
static void setDisplayLayerStack(const sp<IBinder>& token,
@@ -164,11 +171,12 @@ public:
const sp<IBinder>& display,
const sp<IGraphicBufferProducer>& producer,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ);
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform);
private:
mutable sp<CpuConsumer> mCpuConsumer;
- mutable sp<BufferQueue> mBufferQueue;
+ mutable sp<IGraphicBufferProducer> mProducer;
CpuConsumer::LockedBuffer mBuffer;
bool mHaveBuffer;
@@ -177,12 +185,14 @@ public:
~ScreenshotClient();
// frees the previous screenshot and capture a new one
- status_t update(const sp<IBinder>& display);
+ status_t update(const sp<IBinder>& display, bool useIdentityTransform);
status_t update(const sp<IBinder>& display,
- uint32_t reqWidth, uint32_t reqHeight);
+ uint32_t reqWidth, uint32_t reqHeight,
+ bool useIdentityTransform);
status_t update(const sp<IBinder>& display,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ);
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform);
sp<CpuConsumer> getCpuConsumer() const;
diff --git a/include/gui/SurfaceControl.h b/include/gui/SurfaceControl.h
index f27754c..84fb9f9 100644
--- a/include/gui/SurfaceControl.h
+++ b/include/gui/SurfaceControl.h
@@ -24,6 +24,7 @@
#include <utils/RefBase.h>
#include <utils/threads.h>
+#include <ui/FrameStats.h>
#include <ui/PixelFormat.h>
#include <ui/Region.h>
@@ -52,10 +53,10 @@ public:
static bool isSameSurface(
const sp<SurfaceControl>& lhs, const sp<SurfaceControl>& rhs);
-
+
// release surface data from java
void clear();
-
+
status_t setLayerStack(int32_t layerStack);
status_t setLayer(int32_t layer);
status_t setPosition(float x, float y);
@@ -73,6 +74,9 @@ public:
sp<Surface> getSurface() const;
+ status_t clearLayerFrameStats() const;
+ status_t getLayerFrameStats(FrameStats* outStats) const;
+
private:
// can't be copied
SurfaceControl& operator = (SurfaceControl& rhs);
@@ -90,7 +94,7 @@ private:
status_t validate() const;
void destroy();
-
+
sp<SurfaceComposerClient> mClient;
sp<IBinder> mHandle;
sp<IGraphicBufferProducer> mGraphicBufferProducer;
diff --git a/include/input/IInputFlinger.h b/include/input/IInputFlinger.h
new file mode 100644
index 0000000..79ff12a
--- /dev/null
+++ b/include/input/IInputFlinger.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _LIBINPUT_IINPUT_FLINGER_H
+#define _LIBINPUT_IINPUT_FLINGER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <binder/IInterface.h>
+
+namespace android {
+
+/*
+ * This class defines the Binder IPC interface for accessing various
+ * InputFlinger features.
+ */
+class IInputFlinger : public IInterface {
+public:
+ DECLARE_META_INTERFACE(InputFlinger);
+
+ virtual status_t doSomething() = 0;
+};
+
+
+/**
+ * Binder implementation.
+ */
+class BnInputFlinger : public BnInterface<IInputFlinger> {
+public:
+ enum {
+ DO_SOMETHING_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
+ };
+
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+} // namespace android
+
+#endif // _LIBINPUT_IINPUT_FLINGER_H
diff --git a/include/input/Input.h b/include/input/Input.h
index 077a03b..a4fa317 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -71,7 +71,7 @@ enum {
* Constants for LEDs. Hidden from the API since we don't actually expose a way to interact
* with LEDs to developers
*
- * NOTE: If you add LEDs here, you must also add them to KeycodeLabels.h
+ * NOTE: If you add LEDs here, you must also add them to InputEventLabels.h
*/
ALED_NUM_LOCK = 0x00,
@@ -146,18 +146,12 @@ class Parcel;
*/
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. */
+ * NOTE: If you want a flag to be able to set in a keylayout file, then you must add it to
+ * InputEventLabels.h as well. */
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_VIRTUAL = 0x00000002,
+ POLICY_FLAG_FUNCTION = 0x00000004,
POLICY_FLAG_RAW_MASK = 0x0000ffff,
@@ -312,13 +306,8 @@ public:
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;
+ static const char* getLabel(int32_t keyCode);
+ static int32_t getKeyCodeFromLabel(const char* label);
void initialize(
int32_t deviceId,
@@ -578,6 +567,9 @@ public:
return mSamplePointerCoords.array();
}
+ static const char* getLabel(int32_t axis);
+ static int32_t getAxisFromLabel(const char* label);
+
protected:
int32_t mAction;
int32_t mFlags;
diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h
new file mode 100644
index 0000000..c6eef9b
--- /dev/null
+++ b/include/input/InputEventLabels.h
@@ -0,0 +1,396 @@
+/*
+ * 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 _LIBINPUT_INPUT_EVENT_LABELS_H
+#define _LIBINPUT_INPUT_EVENT_LABELS_H
+
+#include <input/Input.h>
+#include <android/keycodes.h>
+
+#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
+#define DEFINE_AXIS(axis) { #axis, AMOTION_EVENT_AXIS_##axis }
+#define DEFINE_LED(led) { #led, ALED_##led }
+#define DEFINE_FLAG(flag) { #flag, POLICY_FLAG_##flag }
+
+namespace android {
+
+template<typename T, size_t N>
+size_t size(T (&)[N]) { return N; }
+
+struct InputEventLabel {
+ const char *literal;
+ int value;
+};
+
+
+static const InputEventLabel KEYCODES[] = {
+ // 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.
+ DEFINE_KEYCODE(UNKNOWN),
+ DEFINE_KEYCODE(SOFT_LEFT),
+ DEFINE_KEYCODE(SOFT_RIGHT),
+ DEFINE_KEYCODE(HOME),
+ DEFINE_KEYCODE(BACK),
+ DEFINE_KEYCODE(CALL),
+ DEFINE_KEYCODE(ENDCALL),
+ DEFINE_KEYCODE(0),
+ DEFINE_KEYCODE(1),
+ DEFINE_KEYCODE(2),
+ DEFINE_KEYCODE(3),
+ DEFINE_KEYCODE(4),
+ DEFINE_KEYCODE(5),
+ DEFINE_KEYCODE(6),
+ DEFINE_KEYCODE(7),
+ DEFINE_KEYCODE(8),
+ DEFINE_KEYCODE(9),
+ DEFINE_KEYCODE(STAR),
+ DEFINE_KEYCODE(POUND),
+ DEFINE_KEYCODE(DPAD_UP),
+ DEFINE_KEYCODE(DPAD_DOWN),
+ DEFINE_KEYCODE(DPAD_LEFT),
+ DEFINE_KEYCODE(DPAD_RIGHT),
+ DEFINE_KEYCODE(DPAD_CENTER),
+ DEFINE_KEYCODE(VOLUME_UP),
+ DEFINE_KEYCODE(VOLUME_DOWN),
+ DEFINE_KEYCODE(POWER),
+ DEFINE_KEYCODE(CAMERA),
+ DEFINE_KEYCODE(CLEAR),
+ DEFINE_KEYCODE(A),
+ DEFINE_KEYCODE(B),
+ DEFINE_KEYCODE(C),
+ DEFINE_KEYCODE(D),
+ DEFINE_KEYCODE(E),
+ DEFINE_KEYCODE(F),
+ DEFINE_KEYCODE(G),
+ DEFINE_KEYCODE(H),
+ DEFINE_KEYCODE(I),
+ DEFINE_KEYCODE(J),
+ DEFINE_KEYCODE(K),
+ DEFINE_KEYCODE(L),
+ DEFINE_KEYCODE(M),
+ DEFINE_KEYCODE(N),
+ DEFINE_KEYCODE(O),
+ DEFINE_KEYCODE(P),
+ DEFINE_KEYCODE(Q),
+ DEFINE_KEYCODE(R),
+ DEFINE_KEYCODE(S),
+ DEFINE_KEYCODE(T),
+ DEFINE_KEYCODE(U),
+ DEFINE_KEYCODE(V),
+ DEFINE_KEYCODE(W),
+ DEFINE_KEYCODE(X),
+ DEFINE_KEYCODE(Y),
+ DEFINE_KEYCODE(Z),
+ DEFINE_KEYCODE(COMMA),
+ DEFINE_KEYCODE(PERIOD),
+ DEFINE_KEYCODE(ALT_LEFT),
+ DEFINE_KEYCODE(ALT_RIGHT),
+ DEFINE_KEYCODE(SHIFT_LEFT),
+ DEFINE_KEYCODE(SHIFT_RIGHT),
+ DEFINE_KEYCODE(TAB),
+ DEFINE_KEYCODE(SPACE),
+ DEFINE_KEYCODE(SYM),
+ DEFINE_KEYCODE(EXPLORER),
+ DEFINE_KEYCODE(ENVELOPE),
+ DEFINE_KEYCODE(ENTER),
+ DEFINE_KEYCODE(DEL),
+ DEFINE_KEYCODE(GRAVE),
+ DEFINE_KEYCODE(MINUS),
+ DEFINE_KEYCODE(EQUALS),
+ DEFINE_KEYCODE(LEFT_BRACKET),
+ DEFINE_KEYCODE(RIGHT_BRACKET),
+ DEFINE_KEYCODE(BACKSLASH),
+ DEFINE_KEYCODE(SEMICOLON),
+ DEFINE_KEYCODE(APOSTROPHE),
+ DEFINE_KEYCODE(SLASH),
+ DEFINE_KEYCODE(AT),
+ DEFINE_KEYCODE(NUM),
+ DEFINE_KEYCODE(HEADSETHOOK),
+ DEFINE_KEYCODE(FOCUS), // *Camera* focus
+ DEFINE_KEYCODE(PLUS),
+ DEFINE_KEYCODE(MENU),
+ DEFINE_KEYCODE(NOTIFICATION),
+ DEFINE_KEYCODE(SEARCH),
+ DEFINE_KEYCODE(MEDIA_PLAY_PAUSE),
+ DEFINE_KEYCODE(MEDIA_STOP),
+ DEFINE_KEYCODE(MEDIA_NEXT),
+ DEFINE_KEYCODE(MEDIA_PREVIOUS),
+ DEFINE_KEYCODE(MEDIA_REWIND),
+ DEFINE_KEYCODE(MEDIA_FAST_FORWARD),
+ DEFINE_KEYCODE(MUTE),
+ DEFINE_KEYCODE(PAGE_UP),
+ DEFINE_KEYCODE(PAGE_DOWN),
+ DEFINE_KEYCODE(PICTSYMBOLS),
+ DEFINE_KEYCODE(SWITCH_CHARSET),
+ DEFINE_KEYCODE(BUTTON_A),
+ DEFINE_KEYCODE(BUTTON_B),
+ DEFINE_KEYCODE(BUTTON_C),
+ DEFINE_KEYCODE(BUTTON_X),
+ DEFINE_KEYCODE(BUTTON_Y),
+ DEFINE_KEYCODE(BUTTON_Z),
+ DEFINE_KEYCODE(BUTTON_L1),
+ DEFINE_KEYCODE(BUTTON_R1),
+ DEFINE_KEYCODE(BUTTON_L2),
+ DEFINE_KEYCODE(BUTTON_R2),
+ DEFINE_KEYCODE(BUTTON_THUMBL),
+ DEFINE_KEYCODE(BUTTON_THUMBR),
+ DEFINE_KEYCODE(BUTTON_START),
+ DEFINE_KEYCODE(BUTTON_SELECT),
+ DEFINE_KEYCODE(BUTTON_MODE),
+ DEFINE_KEYCODE(ESCAPE),
+ DEFINE_KEYCODE(FORWARD_DEL),
+ DEFINE_KEYCODE(CTRL_LEFT),
+ DEFINE_KEYCODE(CTRL_RIGHT),
+ DEFINE_KEYCODE(CAPS_LOCK),
+ DEFINE_KEYCODE(SCROLL_LOCK),
+ DEFINE_KEYCODE(META_LEFT),
+ DEFINE_KEYCODE(META_RIGHT),
+ DEFINE_KEYCODE(FUNCTION),
+ DEFINE_KEYCODE(SYSRQ),
+ DEFINE_KEYCODE(BREAK),
+ DEFINE_KEYCODE(MOVE_HOME),
+ DEFINE_KEYCODE(MOVE_END),
+ DEFINE_KEYCODE(INSERT),
+ DEFINE_KEYCODE(FORWARD),
+ DEFINE_KEYCODE(MEDIA_PLAY),
+ DEFINE_KEYCODE(MEDIA_PAUSE),
+ DEFINE_KEYCODE(MEDIA_CLOSE),
+ DEFINE_KEYCODE(MEDIA_EJECT),
+ DEFINE_KEYCODE(MEDIA_RECORD),
+ DEFINE_KEYCODE(F1),
+ DEFINE_KEYCODE(F2),
+ DEFINE_KEYCODE(F3),
+ DEFINE_KEYCODE(F4),
+ DEFINE_KEYCODE(F5),
+ DEFINE_KEYCODE(F6),
+ DEFINE_KEYCODE(F7),
+ DEFINE_KEYCODE(F8),
+ DEFINE_KEYCODE(F9),
+ DEFINE_KEYCODE(F10),
+ DEFINE_KEYCODE(F11),
+ DEFINE_KEYCODE(F12),
+ DEFINE_KEYCODE(NUM_LOCK),
+ DEFINE_KEYCODE(NUMPAD_0),
+ DEFINE_KEYCODE(NUMPAD_1),
+ DEFINE_KEYCODE(NUMPAD_2),
+ DEFINE_KEYCODE(NUMPAD_3),
+ DEFINE_KEYCODE(NUMPAD_4),
+ DEFINE_KEYCODE(NUMPAD_5),
+ DEFINE_KEYCODE(NUMPAD_6),
+ DEFINE_KEYCODE(NUMPAD_7),
+ DEFINE_KEYCODE(NUMPAD_8),
+ DEFINE_KEYCODE(NUMPAD_9),
+ DEFINE_KEYCODE(NUMPAD_DIVIDE),
+ DEFINE_KEYCODE(NUMPAD_MULTIPLY),
+ DEFINE_KEYCODE(NUMPAD_SUBTRACT),
+ DEFINE_KEYCODE(NUMPAD_ADD),
+ DEFINE_KEYCODE(NUMPAD_DOT),
+ DEFINE_KEYCODE(NUMPAD_COMMA),
+ DEFINE_KEYCODE(NUMPAD_ENTER),
+ DEFINE_KEYCODE(NUMPAD_EQUALS),
+ DEFINE_KEYCODE(NUMPAD_LEFT_PAREN),
+ DEFINE_KEYCODE(NUMPAD_RIGHT_PAREN),
+ DEFINE_KEYCODE(VOLUME_MUTE),
+ DEFINE_KEYCODE(INFO),
+ DEFINE_KEYCODE(CHANNEL_UP),
+ DEFINE_KEYCODE(CHANNEL_DOWN),
+ DEFINE_KEYCODE(ZOOM_IN),
+ DEFINE_KEYCODE(ZOOM_OUT),
+ DEFINE_KEYCODE(TV),
+ DEFINE_KEYCODE(WINDOW),
+ DEFINE_KEYCODE(GUIDE),
+ DEFINE_KEYCODE(DVR),
+ DEFINE_KEYCODE(BOOKMARK),
+ DEFINE_KEYCODE(CAPTIONS),
+ DEFINE_KEYCODE(SETTINGS),
+ DEFINE_KEYCODE(TV_POWER),
+ DEFINE_KEYCODE(TV_INPUT),
+ DEFINE_KEYCODE(STB_POWER),
+ DEFINE_KEYCODE(STB_INPUT),
+ DEFINE_KEYCODE(AVR_POWER),
+ DEFINE_KEYCODE(AVR_INPUT),
+ DEFINE_KEYCODE(PROG_RED),
+ DEFINE_KEYCODE(PROG_GREEN),
+ DEFINE_KEYCODE(PROG_YELLOW),
+ DEFINE_KEYCODE(PROG_BLUE),
+ DEFINE_KEYCODE(APP_SWITCH),
+ DEFINE_KEYCODE(BUTTON_1),
+ DEFINE_KEYCODE(BUTTON_2),
+ DEFINE_KEYCODE(BUTTON_3),
+ DEFINE_KEYCODE(BUTTON_4),
+ DEFINE_KEYCODE(BUTTON_5),
+ DEFINE_KEYCODE(BUTTON_6),
+ DEFINE_KEYCODE(BUTTON_7),
+ DEFINE_KEYCODE(BUTTON_8),
+ DEFINE_KEYCODE(BUTTON_9),
+ DEFINE_KEYCODE(BUTTON_10),
+ DEFINE_KEYCODE(BUTTON_11),
+ DEFINE_KEYCODE(BUTTON_12),
+ DEFINE_KEYCODE(BUTTON_13),
+ DEFINE_KEYCODE(BUTTON_14),
+ DEFINE_KEYCODE(BUTTON_15),
+ DEFINE_KEYCODE(BUTTON_16),
+ DEFINE_KEYCODE(LANGUAGE_SWITCH),
+ DEFINE_KEYCODE(MANNER_MODE),
+ DEFINE_KEYCODE(3D_MODE),
+ DEFINE_KEYCODE(CONTACTS),
+ DEFINE_KEYCODE(CALENDAR),
+ DEFINE_KEYCODE(MUSIC),
+ DEFINE_KEYCODE(CALCULATOR),
+ DEFINE_KEYCODE(ZENKAKU_HANKAKU),
+ DEFINE_KEYCODE(EISU),
+ DEFINE_KEYCODE(MUHENKAN),
+ DEFINE_KEYCODE(HENKAN),
+ DEFINE_KEYCODE(KATAKANA_HIRAGANA),
+ DEFINE_KEYCODE(YEN),
+ DEFINE_KEYCODE(RO),
+ DEFINE_KEYCODE(KANA),
+ DEFINE_KEYCODE(ASSIST),
+ DEFINE_KEYCODE(BRIGHTNESS_DOWN),
+ DEFINE_KEYCODE(BRIGHTNESS_UP),
+ DEFINE_KEYCODE(MEDIA_AUDIO_TRACK),
+ DEFINE_KEYCODE(SLEEP),
+ DEFINE_KEYCODE(WAKEUP),
+
+ { NULL, 0 }
+};
+
+static const InputEventLabel AXES[] = {
+ DEFINE_AXIS(X),
+ DEFINE_AXIS(Y),
+ DEFINE_AXIS(PRESSURE),
+ DEFINE_AXIS(SIZE),
+ DEFINE_AXIS(TOUCH_MAJOR),
+ DEFINE_AXIS(TOUCH_MINOR),
+ DEFINE_AXIS(TOOL_MAJOR),
+ DEFINE_AXIS(TOOL_MINOR),
+ DEFINE_AXIS(ORIENTATION),
+ DEFINE_AXIS(VSCROLL),
+ DEFINE_AXIS(HSCROLL),
+ DEFINE_AXIS(Z),
+ DEFINE_AXIS(RX),
+ DEFINE_AXIS(RY),
+ DEFINE_AXIS(RZ),
+ DEFINE_AXIS(HAT_X),
+ DEFINE_AXIS(HAT_Y),
+ DEFINE_AXIS(LTRIGGER),
+ DEFINE_AXIS(RTRIGGER),
+ DEFINE_AXIS(THROTTLE),
+ DEFINE_AXIS(RUDDER),
+ DEFINE_AXIS(WHEEL),
+ DEFINE_AXIS(GAS),
+ DEFINE_AXIS(BRAKE),
+ DEFINE_AXIS(DISTANCE),
+ DEFINE_AXIS(TILT),
+ DEFINE_AXIS(GENERIC_1),
+ DEFINE_AXIS(GENERIC_2),
+ DEFINE_AXIS(GENERIC_3),
+ DEFINE_AXIS(GENERIC_4),
+ DEFINE_AXIS(GENERIC_5),
+ DEFINE_AXIS(GENERIC_6),
+ DEFINE_AXIS(GENERIC_7),
+ DEFINE_AXIS(GENERIC_8),
+ DEFINE_AXIS(GENERIC_9),
+ DEFINE_AXIS(GENERIC_10),
+ DEFINE_AXIS(GENERIC_11),
+ DEFINE_AXIS(GENERIC_12),
+ DEFINE_AXIS(GENERIC_13),
+ DEFINE_AXIS(GENERIC_14),
+ DEFINE_AXIS(GENERIC_15),
+ DEFINE_AXIS(GENERIC_16),
+
+ // 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, 0 }
+};
+
+static const InputEventLabel LEDS[] = {
+ DEFINE_LED(NUM_LOCK),
+ DEFINE_LED(CAPS_LOCK),
+ DEFINE_LED(SCROLL_LOCK),
+ DEFINE_LED(COMPOSE),
+ DEFINE_LED(KANA),
+ DEFINE_LED(SLEEP),
+ DEFINE_LED(SUSPEND),
+ DEFINE_LED(MUTE),
+ DEFINE_LED(MISC),
+ DEFINE_LED(MAIL),
+ DEFINE_LED(CHARGING),
+ DEFINE_LED(CONTROLLER_1),
+ DEFINE_LED(CONTROLLER_2),
+ DEFINE_LED(CONTROLLER_3),
+ DEFINE_LED(CONTROLLER_4),
+
+ // NOTE: If you add new LEDs here, you must also add them to Input.h
+ { NULL, 0 }
+};
+
+static const InputEventLabel FLAGS[] = {
+ DEFINE_FLAG(FUNCTION),
+
+ { NULL, 0 }
+};
+
+static int lookupValueByLabel(const char* literal, const InputEventLabel *list) {
+ while (list->literal) {
+ if (strcmp(literal, list->literal) == 0) {
+ return list->value;
+ }
+ list++;
+ }
+ return list->value;
+}
+
+static const char* lookupLabelByValue(int value, const InputEventLabel* list) {
+ while (list->literal) {
+ if (list->value == value) {
+ return list->literal;
+ }
+ list++;
+ }
+ return NULL;
+}
+
+static int32_t getKeyCodeByLabel(const char* label) {
+ return int32_t(lookupValueByLabel(label, KEYCODES));
+}
+
+static const char* getLabelByKeyCode(int32_t keyCode) {
+ if (keyCode >= 0 && keyCode < size(KEYCODES)) {
+ return KEYCODES[keyCode].literal;
+ }
+ return NULL;
+}
+
+static uint32_t getKeyFlagByLabel(const char* label) {
+ return uint32_t(lookupValueByLabel(label, FLAGS));
+}
+
+static int32_t getAxisByLabel(const char* label) {
+ return int32_t(lookupValueByLabel(label, AXES));
+}
+
+static const char* getAxisLabel(int32_t axisId) {
+ return lookupLabelByValue(axisId, AXES);
+}
+
+static int32_t getLedByLabel(const char* label) {
+ return int32_t(lookupValueByLabel(label, LEDS));
+}
+
+
+} // namespace android
+#endif // _LIBINPUT_INPUT_EVENT_LABELS_H
diff --git a/include/input/Keyboard.h b/include/input/Keyboard.h
index 25b2f07..519bb22 100644
--- a/include/input/Keyboard.h
+++ b/include/input/Keyboard.h
@@ -19,6 +19,7 @@
#include <input/Input.h>
#include <input/InputDevice.h>
+#include <input/InputEventLabels.h>
#include <utils/Errors.h>
#include <utils/String8.h>
#include <utils/PropertyMap.h>
@@ -82,36 +83,6 @@ extern bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentif
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 an axis by its short form label, eg. "X".
- * Returns -1 if unknown.
- */
-extern int32_t getAxisByLabel(const char* label);
-
-/**
- * Gets an axis label by its id.
- * Returns NULL if unknown.
- */
-extern const char* getAxisLabel(int32_t axisId);
-
-/**
- * Gets an LED by its short form label, eg. "CAPS_LOCK".
- * Returns -1 if unknown.
- */
-extern int32_t getLedByLabel(const char* label);
-
-/**
* Updates a meta state field when a key is pressed or released.
*/
extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState);
diff --git a/include/input/KeycodeLabels.h b/include/input/KeycodeLabels.h
deleted file mode 100644
index a8d63da..0000000
--- a/include/input/KeycodeLabels.h
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * 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 _LIBINPUT_KEYCODE_LABELS_H
-#define _LIBINPUT_KEYCODE_LABELS_H
-
-#include <android/keycodes.h>
-
-struct KeycodeLabel {
- const char *literal;
- int value;
-};
-
-static const KeycodeLabel KEYCODES[] = {
- { "SOFT_LEFT", 1 },
- { "SOFT_RIGHT", 2 },
- { "HOME", 3 },
- { "BACK", 4 },
- { "CALL", 5 },
- { "ENDCALL", 6 },
- { "0", 7 },
- { "1", 8 },
- { "2", 9 },
- { "3", 10 },
- { "4", 11 },
- { "5", 12 },
- { "6", 13 },
- { "7", 14 },
- { "8", 15 },
- { "9", 16 },
- { "STAR", 17 },
- { "POUND", 18 },
- { "DPAD_UP", 19 },
- { "DPAD_DOWN", 20 },
- { "DPAD_LEFT", 21 },
- { "DPAD_RIGHT", 22 },
- { "DPAD_CENTER", 23 },
- { "VOLUME_UP", 24 },
- { "VOLUME_DOWN", 25 },
- { "POWER", 26 },
- { "CAMERA", 27 },
- { "CLEAR", 28 },
- { "A", 29 },
- { "B", 30 },
- { "C", 31 },
- { "D", 32 },
- { "E", 33 },
- { "F", 34 },
- { "G", 35 },
- { "H", 36 },
- { "I", 37 },
- { "J", 38 },
- { "K", 39 },
- { "L", 40 },
- { "M", 41 },
- { "N", 42 },
- { "O", 43 },
- { "P", 44 },
- { "Q", 45 },
- { "R", 46 },
- { "S", 47 },
- { "T", 48 },
- { "U", 49 },
- { "V", 50 },
- { "W", 51 },
- { "X", 52 },
- { "Y", 53 },
- { "Z", 54 },
- { "COMMA", 55 },
- { "PERIOD", 56 },
- { "ALT_LEFT", 57 },
- { "ALT_RIGHT", 58 },
- { "SHIFT_LEFT", 59 },
- { "SHIFT_RIGHT", 60 },
- { "TAB", 61 },
- { "SPACE", 62 },
- { "SYM", 63 },
- { "EXPLORER", 64 },
- { "ENVELOPE", 65 },
- { "ENTER", 66 },
- { "DEL", 67 },
- { "GRAVE", 68 },
- { "MINUS", 69 },
- { "EQUALS", 70 },
- { "LEFT_BRACKET", 71 },
- { "RIGHT_BRACKET", 72 },
- { "BACKSLASH", 73 },
- { "SEMICOLON", 74 },
- { "APOSTROPHE", 75 },
- { "SLASH", 76 },
- { "AT", 77 },
- { "NUM", 78 },
- { "HEADSETHOOK", 79 },
- { "FOCUS", 80 },
- { "PLUS", 81 },
- { "MENU", 82 },
- { "NOTIFICATION", 83 },
- { "SEARCH", 84 },
- { "MEDIA_PLAY_PAUSE", 85 },
- { "MEDIA_STOP", 86 },
- { "MEDIA_NEXT", 87 },
- { "MEDIA_PREVIOUS", 88 },
- { "MEDIA_REWIND", 89 },
- { "MEDIA_FAST_FORWARD", 90 },
- { "MUTE", 91 },
- { "PAGE_UP", 92 },
- { "PAGE_DOWN", 93 },
- { "PICTSYMBOLS", 94 },
- { "SWITCH_CHARSET", 95 },
- { "BUTTON_A", 96 },
- { "BUTTON_B", 97 },
- { "BUTTON_C", 98 },
- { "BUTTON_X", 99 },
- { "BUTTON_Y", 100 },
- { "BUTTON_Z", 101 },
- { "BUTTON_L1", 102 },
- { "BUTTON_R1", 103 },
- { "BUTTON_L2", 104 },
- { "BUTTON_R2", 105 },
- { "BUTTON_THUMBL", 106 },
- { "BUTTON_THUMBR", 107 },
- { "BUTTON_START", 108 },
- { "BUTTON_SELECT", 109 },
- { "BUTTON_MODE", 110 },
- { "ESCAPE", 111 },
- { "FORWARD_DEL", 112 },
- { "CTRL_LEFT", 113 },
- { "CTRL_RIGHT", 114 },
- { "CAPS_LOCK", 115 },
- { "SCROLL_LOCK", 116 },
- { "META_LEFT", 117 },
- { "META_RIGHT", 118 },
- { "FUNCTION", 119 },
- { "SYSRQ", 120 },
- { "BREAK", 121 },
- { "MOVE_HOME", 122 },
- { "MOVE_END", 123 },
- { "INSERT", 124 },
- { "FORWARD", 125 },
- { "MEDIA_PLAY", 126 },
- { "MEDIA_PAUSE", 127 },
- { "MEDIA_CLOSE", 128 },
- { "MEDIA_EJECT", 129 },
- { "MEDIA_RECORD", 130 },
- { "F1", 131 },
- { "F2", 132 },
- { "F3", 133 },
- { "F4", 134 },
- { "F5", 135 },
- { "F6", 136 },
- { "F7", 137 },
- { "F8", 138 },
- { "F9", 139 },
- { "F10", 140 },
- { "F11", 141 },
- { "F12", 142 },
- { "NUM_LOCK", 143 },
- { "NUMPAD_0", 144 },
- { "NUMPAD_1", 145 },
- { "NUMPAD_2", 146 },
- { "NUMPAD_3", 147 },
- { "NUMPAD_4", 148 },
- { "NUMPAD_5", 149 },
- { "NUMPAD_6", 150 },
- { "NUMPAD_7", 151 },
- { "NUMPAD_8", 152 },
- { "NUMPAD_9", 153 },
- { "NUMPAD_DIVIDE", 154 },
- { "NUMPAD_MULTIPLY", 155 },
- { "NUMPAD_SUBTRACT", 156 },
- { "NUMPAD_ADD", 157 },
- { "NUMPAD_DOT", 158 },
- { "NUMPAD_COMMA", 159 },
- { "NUMPAD_ENTER", 160 },
- { "NUMPAD_EQUALS", 161 },
- { "NUMPAD_LEFT_PAREN", 162 },
- { "NUMPAD_RIGHT_PAREN", 163 },
- { "VOLUME_MUTE", 164 },
- { "INFO", 165 },
- { "CHANNEL_UP", 166 },
- { "CHANNEL_DOWN", 167 },
- { "ZOOM_IN", 168 },
- { "ZOOM_OUT", 169 },
- { "TV", 170 },
- { "WINDOW", 171 },
- { "GUIDE", 172 },
- { "DVR", 173 },
- { "BOOKMARK", 174 },
- { "CAPTIONS", 175 },
- { "SETTINGS", 176 },
- { "TV_POWER", 177 },
- { "TV_INPUT", 178 },
- { "STB_POWER", 179 },
- { "STB_INPUT", 180 },
- { "AVR_POWER", 181 },
- { "AVR_INPUT", 182 },
- { "PROG_RED", 183 },
- { "PROG_GREEN", 184 },
- { "PROG_YELLOW", 185 },
- { "PROG_BLUE", 186 },
- { "APP_SWITCH", 187 },
- { "BUTTON_1", 188 },
- { "BUTTON_2", 189 },
- { "BUTTON_3", 190 },
- { "BUTTON_4", 191 },
- { "BUTTON_5", 192 },
- { "BUTTON_6", 193 },
- { "BUTTON_7", 194 },
- { "BUTTON_8", 195 },
- { "BUTTON_9", 196 },
- { "BUTTON_10", 197 },
- { "BUTTON_11", 198 },
- { "BUTTON_12", 199 },
- { "BUTTON_13", 200 },
- { "BUTTON_14", 201 },
- { "BUTTON_15", 202 },
- { "BUTTON_16", 203 },
- { "LANGUAGE_SWITCH", 204 },
- { "MANNER_MODE", 205 },
- { "3D_MODE", 206 },
- { "CONTACTS", 207 },
- { "CALENDAR", 208 },
- { "MUSIC", 209 },
- { "CALCULATOR", 210 },
- { "ZENKAKU_HANKAKU", 211 },
- { "EISU", 212 },
- { "MUHENKAN", 213 },
- { "HENKAN", 214 },
- { "KATAKANA_HIRAGANA", 215 },
- { "YEN", 216 },
- { "RO", 217 },
- { "KANA", 218 },
- { "ASSIST", 219 },
- { "BRIGHTNESS_DOWN", 220 },
- { "BRIGHTNESS_UP", 221 },
- { "MEDIA_AUDIO_TRACK", 222 },
- { "SLEEP", 223 },
- { "WAKEUP", 224 },
-
- // NOTE: If you add a new keycode here you must also add it to several other files.
- // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
-
- { NULL, 0 }
-};
-
-// NOTE: If you edit these flags, also edit policy flags in Input.h.
-static const KeycodeLabel FLAGS[] = {
- { "WAKE", 0x00000001 },
- { "WAKE_DROPPED", 0x00000002 },
- { "SHIFT", 0x00000004 },
- { "CAPS_LOCK", 0x00000008 },
- { "ALT", 0x00000010 },
- { "ALT_GR", 0x00000020 },
- { "MENU", 0x00000040 },
- { "LAUNCHER", 0x00000080 },
- { "VIRTUAL", 0x00000100 },
- { "FUNCTION", 0x00000200 },
- { NULL, 0 }
-};
-
-static const KeycodeLabel AXES[] = {
- { "X", 0 },
- { "Y", 1 },
- { "PRESSURE", 2 },
- { "SIZE", 3 },
- { "TOUCH_MAJOR", 4 },
- { "TOUCH_MINOR", 5 },
- { "TOOL_MAJOR", 6 },
- { "TOOL_MINOR", 7 },
- { "ORIENTATION", 8 },
- { "VSCROLL", 9 },
- { "HSCROLL", 10 },
- { "Z", 11 },
- { "RX", 12 },
- { "RY", 13 },
- { "RZ", 14 },
- { "HAT_X", 15 },
- { "HAT_Y", 16 },
- { "LTRIGGER", 17 },
- { "RTRIGGER", 18 },
- { "THROTTLE", 19 },
- { "RUDDER", 20 },
- { "WHEEL", 21 },
- { "GAS", 22 },
- { "BRAKE", 23 },
- { "DISTANCE", 24 },
- { "TILT", 25 },
- { "GENERIC_1", 32 },
- { "GENERIC_2", 33 },
- { "GENERIC_3", 34 },
- { "GENERIC_4", 35 },
- { "GENERIC_5", 36 },
- { "GENERIC_6", 37 },
- { "GENERIC_7", 38 },
- { "GENERIC_8", 39 },
- { "GENERIC_9", 40 },
- { "GENERIC_10", 41 },
- { "GENERIC_11", 42 },
- { "GENERIC_12", 43 },
- { "GENERIC_13", 44 },
- { "GENERIC_14", 45 },
- { "GENERIC_15", 46 },
- { "GENERIC_16", 47 },
-
- // NOTE: If you add a new axis here you must also add it to several other files.
- // Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
-
- { NULL, -1 }
-};
-
-static const KeycodeLabel LEDS[] = {
- { "NUM_LOCK", 0x00 },
- { "CAPS_LOCK", 0x01 },
- { "SCROLL_LOCK", 0x02 },
- { "COMPOSE", 0x03 },
- { "KANA", 0x04 },
- { "SLEEP", 0x05 },
- { "SUSPEND", 0x06 },
- { "MUTE", 0x07 },
- { "MISC", 0x08 },
- { "MAIL", 0x09 },
- { "CHARGING", 0x0a },
- { "CONTROLLER_1", 0x10 },
- { "CONTROLLER_2", 0x11 },
- { "CONTROLLER_3", 0x12 },
- { "CONTROLLER_4", 0x13 },
-
- // NOTE: If you add new LEDs here, you must also add them to Input.h
-
- { NULL, -1 }
-};
-
-#endif // _LIBINPUT_KEYCODE_LABELS_H
diff --git a/include/media/hardware/HDCPAPI.h b/include/media/hardware/HDCPAPI.h
index d4abb3f..3a53e9f 100644
--- a/include/media/hardware/HDCPAPI.h
+++ b/include/media/hardware/HDCPAPI.h
@@ -88,6 +88,11 @@ struct HDCPModule {
// Request to shutdown the active HDCP session.
virtual status_t shutdownAsync() = 0;
+ // Returns the capability bitmask of this HDCP session.
+ virtual uint32_t getCaps() {
+ return HDCP_CAPS_ENCRYPT;
+ }
+
// ENCRYPTION only:
// Encrypt data according to the HDCP spec. "size" bytes of data are
// available at "inData" (virtual address), "size" may not be a multiple
diff --git a/include/media/openmax/OMX_AudioExt.h b/include/media/openmax/OMX_AudioExt.h
index aa6e6d0..dc6457b 100644
--- a/include/media/openmax/OMX_AudioExt.h
+++ b/include/media/openmax/OMX_AudioExt.h
@@ -43,6 +43,7 @@ extern "C" {
typedef enum OMX_AUDIO_CODINGEXTTYPE {
OMX_AUDIO_CodingAndroidUnused = OMX_AUDIO_CodingKhronosExtensions + 0x00100000,
OMX_AUDIO_CodingAndroidAC3, /**< AC3 encoded data */
+ OMX_AUDIO_CodingAndroidOPUS, /**< OPUS encoded data */
} OMX_AUDIO_CODINGEXTTYPE;
typedef struct OMX_AUDIO_PARAM_ANDROID_AC3TYPE {
@@ -54,6 +55,20 @@ typedef struct OMX_AUDIO_PARAM_ANDROID_AC3TYPE {
variable or unknown sampling rate. */
} OMX_AUDIO_PARAM_ANDROID_AC3TYPE;
+typedef struct OMX_AUDIO_PARAM_ANDROID_OPUSTYPE {
+ OMX_U32 nSize; /**< size of the structure in bytes */
+ OMX_VERSIONTYPE nVersion; /**< OMX specification version information */
+ OMX_U32 nPortIndex; /**< port that this structure applies to */
+ OMX_U32 nChannels; /**< Number of channels */
+ OMX_U32 nBitRate; /**< Bit rate of the encoded data data. Use 0 for variable
+ rate or unknown bit rates. Encoding is set to the
+ bitrate closest to specified value (in bps) */
+ OMX_U32 nSampleRate; /**< Sampling rate of the source data. Use 0 for
+ variable or unknown sampling rate. */
+ OMX_U32 nAudioBandWidth; /**< Audio band width (in Hz) to which an encoder should
+ limit the audio signal. Use 0 to let encoder decide */
+} OMX_AUDIO_PARAM_ANDROID_OPUSTYPE;
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/include/media/openmax/OMX_IndexExt.h b/include/media/openmax/OMX_IndexExt.h
index c47a885..c503dcf 100644
--- a/include/media/openmax/OMX_IndexExt.h
+++ b/include/media/openmax/OMX_IndexExt.h
@@ -58,6 +58,7 @@ typedef enum OMX_INDEXEXTTYPE {
/* Audio parameters and configurations */
OMX_IndexExtAudioStartUnused = OMX_IndexKhronosExtensions + 0x00400000,
OMX_IndexParamAudioAndroidAc3, /**< reference: OMX_AUDIO_PARAM_ANDROID_AC3TYPE */
+ OMX_IndexParamAudioAndroidOpus, /**< reference: OMX_AUDIO_PARAM_ANDROID_OPUSTYPE */
/* Image parameters and configurations */
OMX_IndexExtImageStartUnused = OMX_IndexKhronosExtensions + 0x00500000,
@@ -70,6 +71,7 @@ typedef enum OMX_INDEXEXTTYPE {
OMX_IndexParamVideoVp8, /**< reference: OMX_VIDEO_PARAM_VP8TYPE */
OMX_IndexConfigVideoVp8ReferenceFrame, /**< reference: OMX_VIDEO_VP8REFERENCEFRAMETYPE */
OMX_IndexConfigVideoVp8ReferenceFrameType, /**< reference: OMX_VIDEO_VP8REFERENCEFRAMEINFOTYPE */
+ OMX_IndexParamVideoAndroidVp8Encoder, /**< reference: OMX_VIDEO_PARAM_ANDROID_VP8ENCODERTYPE */
/* Image & Video common configurations */
OMX_IndexExtCommonStartUnused = OMX_IndexKhronosExtensions + 0x00700000,
diff --git a/include/media/openmax/OMX_VideoExt.h b/include/media/openmax/OMX_VideoExt.h
index fa24168..3f18b43 100644
--- a/include/media/openmax/OMX_VideoExt.h
+++ b/include/media/openmax/OMX_VideoExt.h
@@ -108,6 +108,43 @@ typedef struct OMX_VIDEO_VP8REFERENCEFRAMEINFOTYPE {
OMX_BOOL bIsGoldenOrAlternateFrame;
} OMX_VIDEO_VP8REFERENCEFRAMEINFOTYPE;
+/** Maximum number of VP8 temporal layers */
+#define OMX_VIDEO_ANDROID_MAXVP8TEMPORALLAYERS 3
+
+/** VP8 temporal layer patterns */
+typedef enum OMX_VIDEO_ANDROID_VPXTEMPORALLAYERPATTERNTYPE {
+ OMX_VIDEO_VPXTemporalLayerPatternNone = 0,
+ OMX_VIDEO_VPXTemporalLayerPatternWebRTC = 1,
+ OMX_VIDEO_VPXTemporalLayerPatternMax = 0x7FFFFFFF
+} OMX_VIDEO_ANDROID_VPXTEMPORALLAYERPATTERNTYPE;
+
+/**
+ * Android specific VP8 encoder params
+ *
+ * STRUCT MEMBERS:
+ * nSize : Size of the structure in bytes
+ * nVersion : OMX specification version information
+ * nPortIndex : Port that this structure applies to
+ * nKeyFrameInterval : Key frame interval in frames
+ * eTemporalPattern : Type of temporal layer pattern
+ * nTemporalLayerCount : Number of temporal coding layers
+ * nTemporalLayerBitrateRatio : Bitrate ratio allocation between temporal
+ * streams in percentage
+ * nMinQuantizer : Minimum (best quality) quantizer
+ * nMaxQuantizer : Maximum (worst quality) quantizer
+ */
+typedef struct OMX_VIDEO_PARAM_ANDROID_VP8ENCODERTYPE {
+ OMX_U32 nSize;
+ OMX_VERSIONTYPE nVersion;
+ OMX_U32 nPortIndex;
+ OMX_U32 nKeyFrameInterval;
+ OMX_VIDEO_ANDROID_VPXTEMPORALLAYERPATTERNTYPE eTemporalPattern;
+ OMX_U32 nTemporalLayerCount;
+ OMX_U32 nTemporalLayerBitrateRatio[OMX_VIDEO_ANDROID_MAXVP8TEMPORALLAYERS];
+ OMX_U32 nMinQuantizer;
+ OMX_U32 nMaxQuantizer;
+};
+
#ifdef __cplusplus
}
diff --git a/include/powermanager/IPowerManager.h b/include/powermanager/IPowerManager.h
index d85003f..511797a 100644
--- a/include/powermanager/IPowerManager.h
+++ b/include/powermanager/IPowerManager.h
@@ -19,6 +19,7 @@
#include <utils/Errors.h>
#include <binder/IInterface.h>
+#include <hardware/power.h>
namespace android {
@@ -36,6 +37,7 @@ public:
const String16& packageName, int uid) = 0;
virtual status_t releaseWakeLock(const sp<IBinder>& lock, int flags) = 0;
virtual status_t updateWakeLockUids(const sp<IBinder>& lock, int len, const int *uids) = 0;
+ virtual status_t powerHint(int hintId, int data) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/powermanager/PowerManager.h b/include/powermanager/PowerManager.h
index 4590174..cbddc11 100644
--- a/include/powermanager/PowerManager.h
+++ b/include/powermanager/PowerManager.h
@@ -24,6 +24,14 @@ enum {
POWERMANAGER_PARTIAL_WAKE_LOCK = 1, // equals PowerManager.PARTIAL_WAKE_LOCK constant
};
+enum {
+ USER_ACTIVITY_EVENT_OTHER = 0,
+ USER_ACTIVITY_EVENT_BUTTON = 1,
+ USER_ACTIVITY_EVENT_TOUCH = 2,
+
+ USER_ACTIVITY_EVENT_LAST = USER_ACTIVITY_EVENT_TOUCH, // Last valid event code.
+};
+
}; // namespace android
#endif // ANDROID_POWERMANAGER_H
diff --git a/include/ui/FrameStats.h b/include/ui/FrameStats.h
new file mode 100644
index 0000000..5fdf94d
--- /dev/null
+++ b/include/ui/FrameStats.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 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_FRAME_STATS_H
+#define ANDROID_UI_FRAME_STATS_H
+
+#include <utils/Flattenable.h>
+#include <utils/Timers.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+class FrameStats : public LightFlattenable<FrameStats> {
+public:
+
+ /*
+ * Approximate refresh time, in nanoseconds.
+ */
+ nsecs_t refreshPeriodNano;
+
+ /*
+ * The times in nanoseconds for when the frame contents were posted by the producer (e.g.
+ * the application). They are either explicitly set or defaulted to the time when
+ * Surface::queueBuffer() was called.
+ */
+ Vector<nsecs_t> desiredPresentTimesNano;
+
+ /*
+ * The times in milliseconds for when the frame contents were presented on the screen.
+ */
+ Vector<nsecs_t> actualPresentTimesNano;
+
+ /*
+ * The times in nanoseconds for when the frame contents were ready to be presented. Note that
+ * a frame can be posted and still it contents being rendered asynchronously in GL. In such a
+ * case these are the times when the frame contents were completely rendered (i.e. their fences
+ * signaled).
+ */
+ Vector<nsecs_t> frameReadyTimesNano;
+
+ // LightFlattenable
+ bool isFixedSize() const;
+ size_t getFlattenedSize() const;
+ status_t flatten(void* buffer, size_t size) const;
+ status_t unflatten(void const* buffer, size_t size);
+};
+
+}; // namespace android
+
+#endif // ANDROID_UI_FRAME_STATS_H
diff --git a/include/ui/FramebufferNativeWindow.h b/include/ui/FramebufferNativeWindow.h
index 5ec738f..5cd8101 100644
--- a/include/ui/FramebufferNativeWindow.h
+++ b/include/ui/FramebufferNativeWindow.h
@@ -17,6 +17,8 @@
#ifndef ANDROID_FRAMEBUFFER_NATIVE_WINDOW_H
#define ANDROID_FRAMEBUFFER_NATIVE_WINDOW_H
+#warning "FramebufferNativeWindow is deprecated"
+
#include <stdint.h>
#include <sys/types.h>
diff --git a/include/ui/GraphicBuffer.h b/include/ui/GraphicBuffer.h
index 3cf628c..a92424c 100644
--- a/include/ui/GraphicBuffer.h
+++ b/include/ui/GraphicBuffer.h
@@ -88,7 +88,8 @@ public:
uint32_t getUsage() const { return usage; }
PixelFormat getPixelFormat() const { return format; }
Rect getBounds() const { return Rect(width, height); }
-
+ uint64_t getId() const { return mId; }
+
status_t reallocate(uint32_t w, uint32_t h, PixelFormat f, uint32_t usage);
status_t lock(uint32_t usage, void** vaddr);
@@ -146,6 +147,8 @@ private:
// If we're wrapping another buffer then this reference will make sure it
// doesn't get freed.
sp<ANativeWindowBuffer> mWrappedBuffer;
+
+ uint64_t mId;
};
}; // namespace android
diff --git a/include/ui/PixelFormat.h b/include/ui/PixelFormat.h
index 627cfb6..7e46945 100644
--- a/include/ui/PixelFormat.h
+++ b/include/ui/PixelFormat.h
@@ -56,13 +56,15 @@ enum {
// 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 = 6, // 16-bit ARGB
- PIXEL_FORMAT_RGBA_4444 = 7, // 16-bit ARGB
+ 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 = 6, // 16-bit ARGB
+ PIXEL_FORMAT_RGBA_4444 = 7, // 16-bit ARGB
+ PIXEL_FORMAT_sRGB_A_8888 = HAL_PIXEL_FORMAT_sRGB_A_8888, // 4x8-bit sRGB + A
+ PIXEL_FORMAT_sRGB_X_8888 = HAL_PIXEL_FORMAT_sRGB_X_8888, // 4x8-bit sRGB, no A
};
typedef int32_t PixelFormat;
diff --git a/include/ui/Region.h b/include/ui/Region.h
index d906dbb..0d1c68c 100644
--- a/include/ui/Region.h
+++ b/include/ui/Region.h
@@ -50,6 +50,9 @@ public:
inline Rect getBounds() const { return mStorage[mStorage.size() - 1]; }
inline Rect bounds() const { return getBounds(); }
+ bool contains(const Point& point) const;
+ bool contains(int x, int y) const;
+
// the region becomes its bounds
Region& makeBoundsSelf();
diff --git a/libs/binder/Android.mk b/libs/binder/Android.mk
index 673fc82..d8ae0aa 100644
--- a/libs/binder/Android.mk
+++ b/libs/binder/Android.mk
@@ -21,6 +21,7 @@ sources := \
Debug.cpp \
IAppOpsCallback.cpp \
IAppOpsService.cpp \
+ IBatteryStats.cpp \
IInterface.cpp \
IMemory.cpp \
IPCThreadState.cpp \
diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp
new file mode 100644
index 0000000..6469b08
--- /dev/null
+++ b/libs/binder/IBatteryStats.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/IBatteryStats.h>
+
+#include <utils/Debug.h>
+#include <utils/Log.h>
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+
+#include <private/binder/Static.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------
+
+class BpBatteryStats : public BpInterface<IBatteryStats>
+{
+public:
+ BpBatteryStats(const sp<IBinder>& impl)
+ : BpInterface<IBatteryStats>(impl)
+ {
+ }
+
+ virtual void noteStartSensor(int uid, int sensor) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
+ data.writeInt32(uid);
+ data.writeInt32(sensor);
+ remote()->transact(NOTE_START_SENSOR_TRANSACTION, data, &reply);
+ }
+
+ virtual void noteStopSensor(int uid, int sensor) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
+ data.writeInt32(uid);
+ data.writeInt32(sensor);
+ remote()->transact(NOTE_STOP_SENSOR_TRANSACTION, data, &reply);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(BatteryStats, "com.android.internal.app.IBatteryStats");
+
+// ----------------------------------------------------------------------
+
+status_t BnBatteryStats::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ switch(code) {
+ case NOTE_START_SENSOR_TRANSACTION: {
+ CHECK_INTERFACE(IBatteryStats, data, reply);
+ int uid = data.readInt32();
+ int sensor = data.readInt32();
+ noteStartSensor(uid, sensor);
+ reply->writeNoException();
+ return NO_ERROR;
+ } break;
+ case NOTE_STOP_SENSOR_TRANSACTION: {
+ CHECK_INTERFACE(IBatteryStats, data, reply);
+ int uid = data.readInt32();
+ int sensor = data.readInt32();
+ noteStopSensor(uid, sensor);
+ reply->writeNoException();
+ return NO_ERROR;
+ } break;
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+}; // namespace android
diff --git a/libs/binder/MemoryDealer.cpp b/libs/binder/MemoryDealer.cpp
index a14c100..8739625 100644
--- a/libs/binder/MemoryDealer.cpp
+++ b/libs/binder/MemoryDealer.cpp
@@ -225,8 +225,8 @@ Allocation::~Allocation()
// ----------------------------------------------------------------------------
-MemoryDealer::MemoryDealer(size_t size, const char* name)
- : mHeap(new MemoryHeapBase(size, 0, name)),
+MemoryDealer::MemoryDealer(size_t size, const char* name, uint32_t flags)
+ : mHeap(new MemoryHeapBase(size, flags, name)),
mAllocator(new SimpleBestFitAllocator(size))
{
}
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 67cb428..4298522 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -780,6 +780,32 @@ status_t Parcel::writeDupFileDescriptor(int fd)
return err;
}
+// WARNING: This method must stay in sync with
+// Parcelable.Creator<ParcelFileDescriptor> CREATOR
+// in frameworks/base/core/java/android/os/ParcelFileDescriptor.java
+status_t Parcel::writeParcelFileDescriptor(int fd, int commChannel) {
+ status_t status;
+
+ if (fd < 0) {
+ status = writeInt32(0); // ParcelFileDescriptor is null
+ if (status) return status;
+ } else {
+ status = writeInt32(1); // ParcelFileDescriptor is not null
+ if (status) return status;
+ status = writeDupFileDescriptor(fd);
+ if (status) return status;
+ if (commChannel < 0) {
+ status = writeInt32(0); // commChannel is null
+ if (status) return status;
+ } else {
+ status = writeInt32(1); // commChannel is not null
+ if (status) return status;
+ status = writeDupFileDescriptor(commChannel);
+ }
+ }
+ return status;
+}
+
status_t Parcel::writeBlob(size_t len, WritableBlob* outBlob)
{
status_t status;
@@ -1196,6 +1222,23 @@ int Parcel::readFileDescriptor() const
return BAD_TYPE;
}
+// WARNING: This method must stay in sync with writeToParcel()
+// in frameworks/base/core/java/android/os/ParcelFileDescriptor.java
+int Parcel::readParcelFileDescriptor(int& outCommChannel) const {
+ int fd;
+ outCommChannel = -1;
+
+ if (readInt32() == 0) {
+ fd = -1;
+ } else {
+ fd = readFileDescriptor();
+ if (fd >= 0 && readInt32() != 0) {
+ outCommChannel = readFileDescriptor();
+ }
+ }
+ return fd;
+}
+
status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const
{
int32_t useAshmem;
diff --git a/libs/gui/Android.mk b/libs/gui/Android.mk
index c14c950..ca94aa3 100644
--- a/libs/gui/Android.mk
+++ b/libs/gui/Android.mk
@@ -5,8 +5,13 @@ LOCAL_SRC_FILES:= \
IGraphicBufferConsumer.cpp \
IConsumerListener.cpp \
BitTube.cpp \
+ BufferItem.cpp \
BufferItemConsumer.cpp \
BufferQueue.cpp \
+ BufferQueueConsumer.cpp \
+ BufferQueueCore.cpp \
+ BufferQueueProducer.cpp \
+ BufferSlot.cpp \
ConsumerBase.cpp \
CpuConsumer.cpp \
DisplayEventReceiver.cpp \
@@ -16,6 +21,7 @@ LOCAL_SRC_FILES:= \
IDisplayEventConnection.cpp \
IGraphicBufferAlloc.cpp \
IGraphicBufferProducer.cpp \
+ IProducerListener.cpp \
ISensorEventConnection.cpp \
ISensorServer.cpp \
ISurfaceComposer.cpp \
@@ -24,6 +30,7 @@ LOCAL_SRC_FILES:= \
Sensor.cpp \
SensorEventQueue.cpp \
SensorManager.cpp \
+ StreamSplitter.cpp \
Surface.cpp \
SurfaceControl.cpp \
SurfaceComposerClient.cpp \
diff --git a/libs/gui/BufferItem.cpp b/libs/gui/BufferItem.cpp
new file mode 100644
index 0000000..d3fa43e
--- /dev/null
+++ b/libs/gui/BufferItem.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2014 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 <gui/BufferItem.h>
+
+#include <ui/Fence.h>
+#include <ui/GraphicBuffer.h>
+
+#include <system/window.h>
+
+namespace android {
+
+BufferItem::BufferItem() :
+ mTransform(0),
+ mScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+ mTimestamp(0),
+ mIsAutoTimestamp(false),
+ mFrameNumber(0),
+ mSlot(INVALID_BUFFER_SLOT),
+ mIsDroppable(false),
+ mAcquireCalled(false),
+ mTransformToDisplayInverse(false) {
+ mCrop.makeInvalid();
+}
+
+BufferItem::operator IGraphicBufferConsumer::BufferItem() const {
+ IGraphicBufferConsumer::BufferItem bufferItem;
+ bufferItem.mGraphicBuffer = mGraphicBuffer;
+ bufferItem.mFence = mFence;
+ bufferItem.mCrop = mCrop;
+ bufferItem.mTransform = mTransform;
+ bufferItem.mScalingMode = mScalingMode;
+ bufferItem.mTimestamp = mTimestamp;
+ bufferItem.mIsAutoTimestamp = mIsAutoTimestamp;
+ bufferItem.mFrameNumber = mFrameNumber;
+ bufferItem.mBuf = mSlot;
+ bufferItem.mIsDroppable = mIsDroppable;
+ bufferItem.mAcquireCalled = mAcquireCalled;
+ bufferItem.mTransformToDisplayInverse = mTransformToDisplayInverse;
+ return bufferItem;
+}
+
+size_t BufferItem::getPodSize() const {
+ size_t c = sizeof(mCrop) +
+ sizeof(mTransform) +
+ sizeof(mScalingMode) +
+ sizeof(mTimestamp) +
+ sizeof(mIsAutoTimestamp) +
+ sizeof(mFrameNumber) +
+ sizeof(mSlot) +
+ sizeof(mIsDroppable) +
+ sizeof(mAcquireCalled) +
+ sizeof(mTransformToDisplayInverse);
+ return c;
+}
+
+size_t BufferItem::getFlattenedSize() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ if (mFence != 0) {
+ c += mFence->getFlattenedSize();
+ FlattenableUtils::align<4>(c);
+ }
+ return sizeof(int32_t) + c + getPodSize();
+}
+
+size_t BufferItem::getFdCount() const {
+ size_t c = 0;
+ if (mGraphicBuffer != 0) {
+ c += mGraphicBuffer->getFdCount();
+ }
+ if (mFence != 0) {
+ c += mFence->getFdCount();
+ }
+ return c;
+}
+
+status_t BufferItem::flatten(
+ void*& buffer, size_t& size, int*& fds, size_t& count) const {
+
+ // make sure we have enough space
+ if (count < BufferItem::getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ // content flags are stored first
+ uint32_t& flags = *static_cast<uint32_t*>(buffer);
+
+ // advance the pointer
+ FlattenableUtils::advance(buffer, size, sizeof(uint32_t));
+
+ flags = 0;
+ if (mGraphicBuffer != 0) {
+ status_t err = mGraphicBuffer->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 1;
+ }
+ if (mFence != 0) {
+ status_t err = mFence->flatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ flags |= 2;
+ }
+
+ // check we have enough space (in case flattening the fence/graphicbuffer lied to us)
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::write(buffer, size, mCrop);
+ FlattenableUtils::write(buffer, size, mTransform);
+ FlattenableUtils::write(buffer, size, mScalingMode);
+ FlattenableUtils::write(buffer, size, mTimestamp);
+ FlattenableUtils::write(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::write(buffer, size, mFrameNumber);
+ FlattenableUtils::write(buffer, size, mSlot);
+ FlattenableUtils::write(buffer, size, mIsDroppable);
+ FlattenableUtils::write(buffer, size, mAcquireCalled);
+ FlattenableUtils::write(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+status_t BufferItem::unflatten(
+ void const*& buffer, size_t& size, int const*& fds, size_t& count) {
+
+ if (size < sizeof(uint32_t))
+ return NO_MEMORY;
+
+ uint32_t flags = 0;
+ FlattenableUtils::read(buffer, size, flags);
+
+ if (flags & 1) {
+ mGraphicBuffer = new GraphicBuffer();
+ status_t err = mGraphicBuffer->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ if (flags & 2) {
+ mFence = new Fence();
+ status_t err = mFence->unflatten(buffer, size, fds, count);
+ if (err) return err;
+ size -= FlattenableUtils::align<4>(buffer);
+ }
+
+ // check we have enough space
+ if (size < getPodSize()) {
+ return NO_MEMORY;
+ }
+
+ FlattenableUtils::read(buffer, size, mCrop);
+ FlattenableUtils::read(buffer, size, mTransform);
+ FlattenableUtils::read(buffer, size, mScalingMode);
+ FlattenableUtils::read(buffer, size, mTimestamp);
+ FlattenableUtils::read(buffer, size, mIsAutoTimestamp);
+ FlattenableUtils::read(buffer, size, mFrameNumber);
+ FlattenableUtils::read(buffer, size, mSlot);
+ FlattenableUtils::read(buffer, size, mIsDroppable);
+ FlattenableUtils::read(buffer, size, mAcquireCalled);
+ FlattenableUtils::read(buffer, size, mTransformToDisplayInverse);
+
+ return NO_ERROR;
+}
+
+const char* BufferItem::scalingModeName(uint32_t scalingMode) {
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW";
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP";
+ default: return "Unknown";
+ }
+}
+
+} // namespace android
diff --git a/libs/gui/BufferItemConsumer.cpp b/libs/gui/BufferItemConsumer.cpp
index 350887a..fe50c55 100644
--- a/libs/gui/BufferItemConsumer.cpp
+++ b/libs/gui/BufferItemConsumer.cpp
@@ -29,12 +29,19 @@
namespace android {
-BufferItemConsumer::BufferItemConsumer(const sp<BufferQueue>& bq,
- uint32_t consumerUsage, int bufferCount, bool controlledByApp) :
- ConsumerBase(bq, controlledByApp)
+BufferItemConsumer::BufferItemConsumer(
+ const sp<IGraphicBufferConsumer>& consumer, uint32_t consumerUsage,
+ int bufferCount, bool controlledByApp) :
+ ConsumerBase(consumer, controlledByApp)
{
- mConsumer->setConsumerUsageBits(consumerUsage);
- mConsumer->setMaxAcquiredBufferCount(bufferCount);
+ status_t err = mConsumer->setConsumerUsageBits(consumerUsage);
+ LOG_ALWAYS_FATAL_IF(err != OK,
+ "Failed to set consumer usage bits to %#x", consumerUsage);
+ if (bufferCount != DEFAULT_MAX_BUFFERS) {
+ err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
+ LOG_ALWAYS_FATAL_IF(err != OK,
+ "Failed to set max acquired buffer count to %d", bufferCount);
+ }
}
BufferItemConsumer::~BufferItemConsumer() {
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index 2aecb67..c49a886 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -18,1226 +18,62 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
//#define LOG_NDEBUG 0
-#define GL_GLEXT_PROTOTYPES
-#define EGL_EGLEXT_PROTOTYPES
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
#include <gui/BufferQueue.h>
-#include <gui/IConsumerListener.h>
-#include <gui/ISurfaceComposer.h>
-#include <private/gui/ComposerService.h>
-
-#include <utils/Log.h>
-#include <utils/Trace.h>
-#include <utils/CallStack.h>
-
-// Macros for including the BufferQueue name in log messages
-#define ST_LOGV(x, ...) ALOGV("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
-#define ST_LOGD(x, ...) ALOGD("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
-#define ST_LOGI(x, ...) ALOGI("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
-#define ST_LOGW(x, ...) ALOGW("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
-#define ST_LOGE(x, ...) ALOGE("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
-
-#define ATRACE_BUFFER_INDEX(index) \
- if (ATRACE_ENABLED()) { \
- char ___traceBuf[1024]; \
- snprintf(___traceBuf, 1024, "%s: %d", mConsumerName.string(), \
- (index)); \
- android::ScopedTrace ___bufTracer(ATRACE_TAG, ___traceBuf); \
- }
+#include <gui/BufferQueueConsumer.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/BufferQueueProducer.h>
namespace android {
-// Get an ID that's unique within this process.
-static int32_t createProcessUniqueId() {
- static volatile int32_t globalCounter = 0;
- return android_atomic_inc(&globalCounter);
-}
-
-static const char* scalingModeName(int scalingMode) {
- switch (scalingMode) {
- case NATIVE_WINDOW_SCALING_MODE_FREEZE: return "FREEZE";
- case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW: return "SCALE_TO_WINDOW";
- case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP: return "SCALE_CROP";
- default: return "Unknown";
- }
-}
-
-BufferQueue::BufferQueue(const sp<IGraphicBufferAlloc>& allocator) :
- mDefaultWidth(1),
- mDefaultHeight(1),
- mMaxAcquiredBufferCount(1),
- mDefaultMaxBufferCount(2),
- mOverrideMaxBufferCount(0),
- mConsumerControlledByApp(false),
- mDequeueBufferCannotBlock(false),
- mUseAsyncBuffer(true),
- mConnectedApi(NO_CONNECTED_API),
- mAbandoned(false),
- mFrameCounter(0),
- mBufferHasBeenQueued(false),
- mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
- mConsumerUsageBits(0),
- mTransformHint(0)
-{
- // Choose a name using the PID and a process-unique ID.
- mConsumerName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
-
- ST_LOGV("BufferQueue");
- if (allocator == NULL) {
- sp<ISurfaceComposer> composer(ComposerService::getComposerService());
- mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
- if (mGraphicBufferAlloc == 0) {
- ST_LOGE("createGraphicBufferAlloc() failed in BufferQueue()");
- }
- } else {
- mGraphicBufferAlloc = allocator;
- }
-}
-
-BufferQueue::~BufferQueue() {
- ST_LOGV("~BufferQueue");
-}
-
-status_t BufferQueue::setDefaultMaxBufferCountLocked(int count) {
- const int minBufferCount = mUseAsyncBuffer ? 2 : 1;
- if (count < minBufferCount || count > NUM_BUFFER_SLOTS)
- return BAD_VALUE;
-
- mDefaultMaxBufferCount = count;
- mDequeueCondition.broadcast();
-
- return NO_ERROR;
-}
-
-void BufferQueue::setConsumerName(const String8& name) {
- Mutex::Autolock lock(mMutex);
- mConsumerName = name;
-}
-
-status_t BufferQueue::setDefaultBufferFormat(uint32_t defaultFormat) {
- Mutex::Autolock lock(mMutex);
- mDefaultBufferFormat = defaultFormat;
- return NO_ERROR;
-}
-
-status_t BufferQueue::setConsumerUsageBits(uint32_t usage) {
- Mutex::Autolock lock(mMutex);
- mConsumerUsageBits = usage;
- return NO_ERROR;
-}
-
-status_t BufferQueue::setTransformHint(uint32_t hint) {
- ST_LOGV("setTransformHint: %02x", hint);
- Mutex::Autolock lock(mMutex);
- mTransformHint = hint;
- return NO_ERROR;
-}
-
-status_t BufferQueue::setBufferCount(int bufferCount) {
- ST_LOGV("setBufferCount: count=%d", bufferCount);
-
- sp<IConsumerListener> listener;
- {
- Mutex::Autolock lock(mMutex);
-
- if (mAbandoned) {
- ST_LOGE("setBufferCount: BufferQueue has been abandoned!");
- return NO_INIT;
- }
- if (bufferCount > NUM_BUFFER_SLOTS) {
- ST_LOGE("setBufferCount: bufferCount too large (max %d)",
- NUM_BUFFER_SLOTS);
- return BAD_VALUE;
- }
-
- // Error out if the user has dequeued buffers
- for (int i=0 ; i<NUM_BUFFER_SLOTS; i++) {
- if (mSlots[i].mBufferState == BufferSlot::DEQUEUED) {
- ST_LOGE("setBufferCount: client owns some buffers");
- return -EINVAL;
- }
- }
-
- if (bufferCount == 0) {
- mOverrideMaxBufferCount = 0;
- mDequeueCondition.broadcast();
- return NO_ERROR;
- }
-
- // fine to assume async to false before we're setting the buffer count
- const int minBufferSlots = getMinMaxBufferCountLocked(false);
- if (bufferCount < minBufferSlots) {
- ST_LOGE("setBufferCount: requested buffer count (%d) is less than "
- "minimum (%d)", bufferCount, minBufferSlots);
- return BAD_VALUE;
- }
+BufferQueue::ProxyConsumerListener::ProxyConsumerListener(
+ const wp<ConsumerListener>& consumerListener):
+ mConsumerListener(consumerListener) {}
- // here we're guaranteed that the client doesn't have dequeued buffers
- // and will release all of its buffer references. We don't clear the
- // queue, however, so currently queued buffers still get displayed.
- freeAllBuffersLocked();
- mOverrideMaxBufferCount = bufferCount;
- mDequeueCondition.broadcast();
- listener = mConsumerListener;
- } // scope for lock
+BufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {}
+void BufferQueue::ProxyConsumerListener::onFrameAvailable() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
if (listener != NULL) {
- listener->onBuffersReleased();
- }
-
- return NO_ERROR;
-}
-
-int BufferQueue::query(int what, int* outValue)
-{
- ATRACE_CALL();
- Mutex::Autolock lock(mMutex);
-
- if (mAbandoned) {
- ST_LOGE("query: BufferQueue has been abandoned!");
- return NO_INIT;
- }
-
- int value;
- switch (what) {
- case NATIVE_WINDOW_WIDTH:
- value = mDefaultWidth;
- break;
- case NATIVE_WINDOW_HEIGHT:
- value = mDefaultHeight;
- break;
- case NATIVE_WINDOW_FORMAT:
- value = mDefaultBufferFormat;
- break;
- case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
- value = getMinUndequeuedBufferCount(false);
- break;
- case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
- value = (mQueue.size() >= 2);
- break;
- case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
- value = mConsumerUsageBits;
- break;
- default:
- return BAD_VALUE;
- }
- outValue[0] = value;
- return NO_ERROR;
-}
-
-status_t BufferQueue::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
- ATRACE_CALL();
- ST_LOGV("requestBuffer: slot=%d", slot);
- Mutex::Autolock lock(mMutex);
- if (mAbandoned) {
- ST_LOGE("requestBuffer: BufferQueue has been abandoned!");
- return NO_INIT;
- }
- if (slot < 0 || slot >= NUM_BUFFER_SLOTS) {
- ST_LOGE("requestBuffer: slot index out of range [0, %d]: %d",
- NUM_BUFFER_SLOTS, slot);
- return BAD_VALUE;
- } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
- ST_LOGE("requestBuffer: slot %d is not owned by the client (state=%d)",
- slot, mSlots[slot].mBufferState);
- return BAD_VALUE;
- }
- mSlots[slot].mRequestBufferCalled = true;
- *buf = mSlots[slot].mGraphicBuffer;
- return NO_ERROR;
-}
-
-status_t BufferQueue::dequeueBuffer(int *outBuf, sp<Fence>* outFence, bool async,
- uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
- ATRACE_CALL();
- ST_LOGV("dequeueBuffer: w=%d h=%d fmt=%#x usage=%#x", w, h, format, usage);
-
- if ((w && !h) || (!w && h)) {
- ST_LOGE("dequeueBuffer: invalid size: w=%u, h=%u", w, h);
- return BAD_VALUE;
- }
-
- status_t returnFlags(OK);
- EGLDisplay dpy = EGL_NO_DISPLAY;
- EGLSyncKHR eglFence = EGL_NO_SYNC_KHR;
-
- { // Scope for the lock
- Mutex::Autolock lock(mMutex);
-
- if (format == 0) {
- format = mDefaultBufferFormat;
- }
- // turn on usage bits the consumer requested
- usage |= mConsumerUsageBits;
-
- int found = -1;
- bool tryAgain = true;
- while (tryAgain) {
- if (mAbandoned) {
- ST_LOGE("dequeueBuffer: BufferQueue has been abandoned!");
- return NO_INIT;
- }
-
- const int maxBufferCount = getMaxBufferCountLocked(async);
- if (async && mOverrideMaxBufferCount) {
- // FIXME: some drivers are manually setting the buffer-count (which they
- // shouldn't), so we do this extra test here to handle that case.
- // This is TEMPORARY, until we get this fixed.
- if (mOverrideMaxBufferCount < maxBufferCount) {
- ST_LOGE("dequeueBuffer: async mode is invalid with buffercount override");
- return BAD_VALUE;
- }
- }
-
- // Free up any buffers that are in slots beyond the max buffer
- // count.
- for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
- assert(mSlots[i].mBufferState == BufferSlot::FREE);
- if (mSlots[i].mGraphicBuffer != NULL) {
- freeBufferLocked(i);
- returnFlags |= IGraphicBufferProducer::RELEASE_ALL_BUFFERS;
- }
- }
-
- // look for a free buffer to give to the client
- found = INVALID_BUFFER_SLOT;
- int dequeuedCount = 0;
- int acquiredCount = 0;
- for (int i = 0; i < maxBufferCount; i++) {
- const int state = mSlots[i].mBufferState;
- switch (state) {
- case BufferSlot::DEQUEUED:
- dequeuedCount++;
- break;
- case BufferSlot::ACQUIRED:
- acquiredCount++;
- break;
- case BufferSlot::FREE:
- /* We return the oldest of the free buffers to avoid
- * stalling the producer if possible. This is because
- * the consumer may still have pending reads of the
- * buffers in flight.
- */
- if ((found < 0) ||
- mSlots[i].mFrameNumber < mSlots[found].mFrameNumber) {
- found = i;
- }
- break;
- }
- }
-
- // clients are not allowed to dequeue more than one buffer
- // if they didn't set a buffer count.
- if (!mOverrideMaxBufferCount && dequeuedCount) {
- ST_LOGE("dequeueBuffer: can't dequeue multiple buffers without "
- "setting the buffer count");
- return -EINVAL;
- }
-
- // See whether a buffer has been queued since the last
- // setBufferCount so we know whether to perform the min undequeued
- // buffers check below.
- if (mBufferHasBeenQueued) {
- // make sure the client is not trying to dequeue more buffers
- // than allowed.
- const int newUndequeuedCount = maxBufferCount - (dequeuedCount+1);
- const int minUndequeuedCount = getMinUndequeuedBufferCount(async);
- if (newUndequeuedCount < minUndequeuedCount) {
- ST_LOGE("dequeueBuffer: min undequeued buffer count (%d) "
- "exceeded (dequeued=%d undequeudCount=%d)",
- minUndequeuedCount, dequeuedCount,
- newUndequeuedCount);
- return -EBUSY;
- }
- }
-
- // If no buffer is found, wait for a buffer to be released or for
- // the max buffer count to change.
- tryAgain = found == INVALID_BUFFER_SLOT;
- if (tryAgain) {
- // return an error if we're in "cannot block" mode (producer and consumer
- // are controlled by the application) -- however, the consumer is allowed
- // to acquire briefly an extra buffer (which could cause us to have to wait here)
- // and that's okay because we know the wait will be brief (it happens
- // if we dequeue a buffer while the consumer has acquired one but not released
- // the old one yet -- for e.g.: see GLConsumer::updateTexImage()).
- if (mDequeueBufferCannotBlock && (acquiredCount <= mMaxAcquiredBufferCount)) {
- ST_LOGE("dequeueBuffer: would block! returning an error instead.");
- return WOULD_BLOCK;
- }
- mDequeueCondition.wait(mMutex);
- }
- }
-
-
- if (found == INVALID_BUFFER_SLOT) {
- // This should not happen.
- ST_LOGE("dequeueBuffer: no available buffer slots");
- return -EBUSY;
- }
-
- const int buf = found;
- *outBuf = found;
-
- ATRACE_BUFFER_INDEX(buf);
-
- const bool useDefaultSize = !w && !h;
- if (useDefaultSize) {
- // use the default size
- w = mDefaultWidth;
- h = mDefaultHeight;
- }
-
- mSlots[buf].mBufferState = BufferSlot::DEQUEUED;
-
- const sp<GraphicBuffer>& buffer(mSlots[buf].mGraphicBuffer);
- if ((buffer == NULL) ||
- (uint32_t(buffer->width) != w) ||
- (uint32_t(buffer->height) != h) ||
- (uint32_t(buffer->format) != format) ||
- ((uint32_t(buffer->usage) & usage) != usage))
- {
- mSlots[buf].mAcquireCalled = false;
- mSlots[buf].mGraphicBuffer = NULL;
- mSlots[buf].mRequestBufferCalled = false;
- mSlots[buf].mEglFence = EGL_NO_SYNC_KHR;
- mSlots[buf].mFence = Fence::NO_FENCE;
- mSlots[buf].mEglDisplay = EGL_NO_DISPLAY;
-
- returnFlags |= IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION;
- }
-
-
- if (CC_UNLIKELY(mSlots[buf].mFence == NULL)) {
- ST_LOGE("dequeueBuffer: about to return a NULL fence from mSlot. "
- "buf=%d, w=%d, h=%d, format=%d",
- buf, buffer->width, buffer->height, buffer->format);
- }
-
- dpy = mSlots[buf].mEglDisplay;
- eglFence = mSlots[buf].mEglFence;
- *outFence = mSlots[buf].mFence;
- mSlots[buf].mEglFence = EGL_NO_SYNC_KHR;
- mSlots[buf].mFence = Fence::NO_FENCE;
- } // end lock scope
-
- if (returnFlags & IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
- status_t error;
- sp<GraphicBuffer> graphicBuffer(
- mGraphicBufferAlloc->createGraphicBuffer(w, h, format, usage, &error));
- if (graphicBuffer == 0) {
- ST_LOGE("dequeueBuffer: SurfaceComposer::createGraphicBuffer failed");
- return error;
- }
-
- { // Scope for the lock
- Mutex::Autolock lock(mMutex);
-
- if (mAbandoned) {
- ST_LOGE("dequeueBuffer: BufferQueue has been abandoned!");
- return NO_INIT;
- }
-
- mSlots[*outBuf].mFrameNumber = ~0;
- mSlots[*outBuf].mGraphicBuffer = graphicBuffer;
- }
- }
-
- if (eglFence != EGL_NO_SYNC_KHR) {
- EGLint result = eglClientWaitSyncKHR(dpy, eglFence, 0, 1000000000);
- // If something goes wrong, log the error, but return the buffer without
- // synchronizing access to it. It's too late at this point to abort the
- // dequeue operation.
- if (result == EGL_FALSE) {
- ST_LOGE("dequeueBuffer: error waiting for fence: %#x", eglGetError());
- } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
- ST_LOGE("dequeueBuffer: timeout waiting for fence");
- }
- eglDestroySyncKHR(dpy, eglFence);
- }
-
- ST_LOGV("dequeueBuffer: returning slot=%d/%llu buf=%p flags=%#x", *outBuf,
- mSlots[*outBuf].mFrameNumber,
- mSlots[*outBuf].mGraphicBuffer->handle, returnFlags);
-
- return returnFlags;
-}
-
-status_t BufferQueue::queueBuffer(int buf,
- const QueueBufferInput& input, QueueBufferOutput* output) {
- ATRACE_CALL();
- ATRACE_BUFFER_INDEX(buf);
-
- Rect crop;
- uint32_t transform;
- int scalingMode;
- int64_t timestamp;
- bool isAutoTimestamp;
- bool async;
- sp<Fence> fence;
-
- input.deflate(&timestamp, &isAutoTimestamp, &crop, &scalingMode, &transform,
- &async, &fence);
-
- if (fence == NULL) {
- ST_LOGE("queueBuffer: fence is NULL");
- return BAD_VALUE;
- }
-
- switch (scalingMode) {
- case NATIVE_WINDOW_SCALING_MODE_FREEZE:
- case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
- case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
- case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
- break;
- default:
- ST_LOGE("unknown scaling mode: %d", scalingMode);
- return -EINVAL;
- }
-
- sp<IConsumerListener> listener;
-
- { // scope for the lock
- Mutex::Autolock lock(mMutex);
-
- if (mAbandoned) {
- ST_LOGE("queueBuffer: BufferQueue has been abandoned!");
- return NO_INIT;
- }
-
- const int maxBufferCount = getMaxBufferCountLocked(async);
- if (async && mOverrideMaxBufferCount) {
- // FIXME: some drivers are manually setting the buffer-count (which they
- // shouldn't), so we do this extra test here to handle that case.
- // This is TEMPORARY, until we get this fixed.
- if (mOverrideMaxBufferCount < maxBufferCount) {
- ST_LOGE("queueBuffer: async mode is invalid with buffercount override");
- return BAD_VALUE;
- }
- }
- if (buf < 0 || buf >= maxBufferCount) {
- ST_LOGE("queueBuffer: slot index out of range [0, %d]: %d",
- maxBufferCount, buf);
- return -EINVAL;
- } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
- ST_LOGE("queueBuffer: slot %d is not owned by the client "
- "(state=%d)", buf, mSlots[buf].mBufferState);
- return -EINVAL;
- } else if (!mSlots[buf].mRequestBufferCalled) {
- ST_LOGE("queueBuffer: slot %d was enqueued without requesting a "
- "buffer", buf);
- return -EINVAL;
- }
-
- ST_LOGV("queueBuffer: slot=%d/%llu time=%#llx crop=[%d,%d,%d,%d] "
- "tr=%#x scale=%s",
- buf, mFrameCounter + 1, timestamp,
- crop.left, crop.top, crop.right, crop.bottom,
- transform, scalingModeName(scalingMode));
-
- const sp<GraphicBuffer>& graphicBuffer(mSlots[buf].mGraphicBuffer);
- Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
- Rect croppedCrop;
- crop.intersect(bufferRect, &croppedCrop);
- if (croppedCrop != crop) {
- ST_LOGE("queueBuffer: crop rect is not contained within the "
- "buffer in slot %d", buf);
- return -EINVAL;
- }
-
- mSlots[buf].mFence = fence;
- mSlots[buf].mBufferState = BufferSlot::QUEUED;
- mFrameCounter++;
- mSlots[buf].mFrameNumber = mFrameCounter;
-
- BufferItem item;
- item.mAcquireCalled = mSlots[buf].mAcquireCalled;
- item.mGraphicBuffer = mSlots[buf].mGraphicBuffer;
- item.mCrop = crop;
- item.mTransform = transform & ~NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
- item.mTransformToDisplayInverse = bool(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
- item.mScalingMode = scalingMode;
- item.mTimestamp = timestamp;
- item.mIsAutoTimestamp = isAutoTimestamp;
- item.mFrameNumber = mFrameCounter;
- item.mBuf = buf;
- item.mFence = fence;
- item.mIsDroppable = mDequeueBufferCannotBlock || async;
-
- if (mQueue.empty()) {
- // when the queue is empty, we can ignore "mDequeueBufferCannotBlock", and
- // simply queue this buffer.
- mQueue.push_back(item);
- listener = mConsumerListener;
- } else {
- // when the queue is not empty, we need to look at the front buffer
- // state and see if we need to replace it.
- Fifo::iterator front(mQueue.begin());
- if (front->mIsDroppable) {
- // buffer slot currently queued is marked free if still tracked
- if (stillTracking(front)) {
- mSlots[front->mBuf].mBufferState = BufferSlot::FREE;
- // reset the frame number of the freed buffer so that it is the first in
- // line to be dequeued again.
- mSlots[front->mBuf].mFrameNumber = 0;
- }
- // and we record the new buffer in the queued list
- *front = item;
- } else {
- mQueue.push_back(item);
- listener = mConsumerListener;
- }
- }
-
- mBufferHasBeenQueued = true;
- mDequeueCondition.broadcast();
-
- output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint,
- mQueue.size());
-
- ATRACE_INT(mConsumerName.string(), mQueue.size());
- } // scope for the lock
-
- // call back without lock held
- if (listener != 0) {
listener->onFrameAvailable();
}
- return NO_ERROR;
-}
-
-void BufferQueue::cancelBuffer(int buf, const sp<Fence>& fence) {
- ATRACE_CALL();
- ST_LOGV("cancelBuffer: slot=%d", buf);
- Mutex::Autolock lock(mMutex);
-
- if (mAbandoned) {
- ST_LOGW("cancelBuffer: BufferQueue has been abandoned!");
- return;
- }
-
- if (buf < 0 || buf >= NUM_BUFFER_SLOTS) {
- ST_LOGE("cancelBuffer: slot index out of range [0, %d]: %d",
- NUM_BUFFER_SLOTS, buf);
- return;
- } else if (mSlots[buf].mBufferState != BufferSlot::DEQUEUED) {
- ST_LOGE("cancelBuffer: slot %d is not owned by the client (state=%d)",
- buf, mSlots[buf].mBufferState);
- return;
- } else if (fence == NULL) {
- ST_LOGE("cancelBuffer: fence is NULL");
- return;
- }
- mSlots[buf].mBufferState = BufferSlot::FREE;
- mSlots[buf].mFrameNumber = 0;
- mSlots[buf].mFence = fence;
- mDequeueCondition.broadcast();
-}
-
-
-status_t BufferQueue::connect(const sp<IBinder>& token,
- int api, bool producerControlledByApp, QueueBufferOutput* output) {
- ATRACE_CALL();
- ST_LOGV("connect: api=%d producerControlledByApp=%s", api,
- producerControlledByApp ? "true" : "false");
- Mutex::Autolock lock(mMutex);
-
-retry:
- if (mAbandoned) {
- ST_LOGE("connect: BufferQueue has been abandoned!");
- return NO_INIT;
- }
-
- if (mConsumerListener == NULL) {
- ST_LOGE("connect: BufferQueue has no consumer!");
- return NO_INIT;
- }
-
- if (mConnectedApi != NO_CONNECTED_API) {
- ST_LOGE("connect: already connected (cur=%d, req=%d)",
- mConnectedApi, api);
- return -EINVAL;
- }
-
- // If we disconnect and reconnect quickly, we can be in a state where our slots are
- // empty but we have many buffers in the queue. This can cause us to run out of
- // memory if we outrun the consumer. Wait here if it looks like we have too many
- // buffers queued up.
- int maxBufferCount = getMaxBufferCountLocked(false); // worst-case, i.e. largest value
- if (mQueue.size() > (size_t) maxBufferCount) {
- // TODO: make this bound tighter?
- ST_LOGV("queue size is %d, waiting", mQueue.size());
- mDequeueCondition.wait(mMutex);
- goto retry;
- }
-
- int err = NO_ERROR;
- switch (api) {
- case NATIVE_WINDOW_API_EGL:
- case NATIVE_WINDOW_API_CPU:
- case NATIVE_WINDOW_API_MEDIA:
- case NATIVE_WINDOW_API_CAMERA:
- mConnectedApi = api;
- output->inflate(mDefaultWidth, mDefaultHeight, mTransformHint, mQueue.size());
-
- // set-up a death notification so that we can disconnect
- // automatically when/if the remote producer dies.
- if (token != NULL && token->remoteBinder() != NULL) {
- status_t err = token->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));
- if (err == NO_ERROR) {
- mConnectedProducerToken = token;
- } else {
- ALOGE("linkToDeath failed: %s (%d)", strerror(-err), err);
- }
- }
- break;
- default:
- err = -EINVAL;
- break;
- }
-
- mBufferHasBeenQueued = false;
- mDequeueBufferCannotBlock = mConsumerControlledByApp && producerControlledByApp;
-
- return err;
-}
-
-void BufferQueue::binderDied(const wp<IBinder>& who) {
- // If we're here, it means that a producer we were connected to died.
- // We're GUARANTEED that we still are connected to it because it has no other way
- // to get disconnected -- or -- we wouldn't be here because we're removing this
- // callback upon disconnect. Therefore, it's okay to read mConnectedApi without
- // synchronization here.
- int api = mConnectedApi;
- this->disconnect(api);
}
-status_t BufferQueue::disconnect(int api) {
- ATRACE_CALL();
- ST_LOGV("disconnect: api=%d", api);
-
- int err = NO_ERROR;
- sp<IConsumerListener> listener;
-
- { // Scope for the lock
- Mutex::Autolock lock(mMutex);
-
- if (mAbandoned) {
- // it is not really an error to disconnect after the surface
- // has been abandoned, it should just be a no-op.
- return NO_ERROR;
- }
-
- switch (api) {
- case NATIVE_WINDOW_API_EGL:
- case NATIVE_WINDOW_API_CPU:
- case NATIVE_WINDOW_API_MEDIA:
- case NATIVE_WINDOW_API_CAMERA:
- if (mConnectedApi == api) {
- freeAllBuffersLocked();
- // remove our death notification callback if we have one
- sp<IBinder> token = mConnectedProducerToken;
- if (token != NULL) {
- // this can fail if we're here because of the death notification
- // either way, we just ignore.
- token->unlinkToDeath(static_cast<IBinder::DeathRecipient*>(this));
- }
- mConnectedProducerToken = NULL;
- mConnectedApi = NO_CONNECTED_API;
- mDequeueCondition.broadcast();
- listener = mConsumerListener;
- } else {
- ST_LOGE("disconnect: connected to another api (cur=%d, req=%d)",
- mConnectedApi, api);
- err = -EINVAL;
- }
- break;
- default:
- ST_LOGE("disconnect: unknown API %d", api);
- err = -EINVAL;
- break;
- }
- }
-
+void BufferQueue::ProxyConsumerListener::onBuffersReleased() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
if (listener != NULL) {
listener->onBuffersReleased();
}
-
- return err;
-}
-
-void BufferQueue::dump(String8& result, const char* prefix) const {
- Mutex::Autolock _l(mMutex);
-
- String8 fifo;
- int fifoSize = 0;
- Fifo::const_iterator i(mQueue.begin());
- while (i != mQueue.end()) {
- fifo.appendFormat("%02d:%p crop=[%d,%d,%d,%d], "
- "xform=0x%02x, time=%#llx, scale=%s\n",
- i->mBuf, i->mGraphicBuffer.get(),
- i->mCrop.left, i->mCrop.top, i->mCrop.right,
- i->mCrop.bottom, i->mTransform, i->mTimestamp,
- scalingModeName(i->mScalingMode)
- );
- i++;
- fifoSize++;
- }
-
-
- result.appendFormat(
- "%s-BufferQueue mMaxAcquiredBufferCount=%d, mDequeueBufferCannotBlock=%d, default-size=[%dx%d], "
- "default-format=%d, transform-hint=%02x, FIFO(%d)={%s}\n",
- prefix, mMaxAcquiredBufferCount, mDequeueBufferCannotBlock, mDefaultWidth,
- mDefaultHeight, mDefaultBufferFormat, mTransformHint,
- fifoSize, fifo.string());
-
- struct {
- const char * operator()(int state) const {
- switch (state) {
- case BufferSlot::DEQUEUED: return "DEQUEUED";
- case BufferSlot::QUEUED: return "QUEUED";
- case BufferSlot::FREE: return "FREE";
- case BufferSlot::ACQUIRED: return "ACQUIRED";
- default: return "Unknown";
- }
- }
- } stateName;
-
- // just trim the free buffers to not spam the dump
- int maxBufferCount = 0;
- for (int i=NUM_BUFFER_SLOTS-1 ; i>=0 ; i--) {
- const BufferSlot& slot(mSlots[i]);
- if ((slot.mBufferState != BufferSlot::FREE) || (slot.mGraphicBuffer != NULL)) {
- maxBufferCount = i+1;
- break;
- }
- }
-
- for (int i=0 ; i<maxBufferCount ; i++) {
- const BufferSlot& slot(mSlots[i]);
- const sp<GraphicBuffer>& buf(slot.mGraphicBuffer);
- result.appendFormat(
- "%s%s[%02d:%p] state=%-8s",
- prefix, (slot.mBufferState == BufferSlot::ACQUIRED)?">":" ", i, buf.get(),
- stateName(slot.mBufferState)
- );
-
- if (buf != NULL) {
- result.appendFormat(
- ", %p [%4ux%4u:%4u,%3X]",
- buf->handle, buf->width, buf->height, buf->stride,
- buf->format);
- }
- result.append("\n");
- }
-}
-
-void BufferQueue::freeBufferLocked(int slot) {
- ST_LOGV("freeBufferLocked: slot=%d", slot);
- mSlots[slot].mGraphicBuffer = 0;
- if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) {
- mSlots[slot].mNeedsCleanupOnRelease = true;
- }
- mSlots[slot].mBufferState = BufferSlot::FREE;
- mSlots[slot].mFrameNumber = 0;
- mSlots[slot].mAcquireCalled = false;
-
- // destroy fence as BufferQueue now takes ownership
- if (mSlots[slot].mEglFence != EGL_NO_SYNC_KHR) {
- eglDestroySyncKHR(mSlots[slot].mEglDisplay, mSlots[slot].mEglFence);
- mSlots[slot].mEglFence = EGL_NO_SYNC_KHR;
- }
- mSlots[slot].mFence = Fence::NO_FENCE;
-}
-
-void BufferQueue::freeAllBuffersLocked() {
- mBufferHasBeenQueued = false;
- for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
- freeBufferLocked(i);
- }
-}
-
-status_t BufferQueue::acquireBuffer(BufferItem *buffer, nsecs_t expectedPresent) {
- ATRACE_CALL();
- Mutex::Autolock _l(mMutex);
-
- // Check that the consumer doesn't currently have the maximum number of
- // buffers acquired. We allow the max buffer count to be exceeded by one
- // buffer, so that the consumer can successfully set up the newly acquired
- // buffer before releasing the old one.
- int numAcquiredBuffers = 0;
- for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
- if (mSlots[i].mBufferState == BufferSlot::ACQUIRED) {
- numAcquiredBuffers++;
- }
- }
- if (numAcquiredBuffers >= mMaxAcquiredBufferCount+1) {
- ST_LOGE("acquireBuffer: max acquired buffer count reached: %d (max=%d)",
- numAcquiredBuffers, mMaxAcquiredBufferCount);
- return INVALID_OPERATION;
- }
-
- // check if queue is empty
- // In asynchronous mode the list is guaranteed to be one buffer
- // deep, while in synchronous mode we use the oldest buffer.
- if (mQueue.empty()) {
- return NO_BUFFER_AVAILABLE;
- }
-
- Fifo::iterator front(mQueue.begin());
-
- // If expectedPresent is specified, we may not want to return a buffer yet.
- // If it's specified and there's more than one buffer queued, we may
- // want to drop a buffer.
- if (expectedPresent != 0) {
- const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second
-
- // The "expectedPresent" argument indicates when the buffer is expected
- // to be presented on-screen. If the buffer's desired-present time
- // is earlier (less) than expectedPresent, meaning it'll be displayed
- // on time or possibly late if we show it ASAP, we acquire and return
- // it. If we don't want to display it until after the expectedPresent
- // time, we return PRESENT_LATER without acquiring it.
- //
- // To be safe, we don't defer acquisition if expectedPresent is
- // more than one second in the future beyond the desired present time
- // (i.e. we'd be holding the buffer for a long time).
- //
- // NOTE: code assumes monotonic time values from the system clock are
- // positive.
-
- // Start by checking to see if we can drop frames. We skip this check
- // if the timestamps are being auto-generated by Surface -- if the
- // app isn't generating timestamps explicitly, they probably don't
- // want frames to be discarded based on them.
- while (mQueue.size() > 1 && !mQueue[0].mIsAutoTimestamp) {
- // If entry[1] is timely, drop entry[0] (and repeat). We apply
- // an additional criteria here: we only drop the earlier buffer if
- // our desiredPresent falls within +/- 1 second of the expected
- // present. Otherwise, bogus desiredPresent times (e.g. 0 or
- // a small relative timestamp), which normally mean "ignore the
- // timestamp and acquire immediately", would cause us to drop
- // frames.
- //
- // We may want to add an additional criteria: don't drop the
- // earlier buffer if entry[1]'s fence hasn't signaled yet.
- //
- // (Vector front is [0], back is [size()-1])
- const BufferItem& bi(mQueue[1]);
- nsecs_t desiredPresent = bi.mTimestamp;
- if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
- desiredPresent > expectedPresent) {
- // This buffer is set to display in the near future, or
- // desiredPresent is garbage. Either way we don't want to
- // drop the previous buffer just to get this on screen sooner.
- ST_LOGV("pts nodrop: des=%lld expect=%lld (%lld) now=%lld",
- desiredPresent, expectedPresent, desiredPresent - expectedPresent,
- systemTime(CLOCK_MONOTONIC));
- break;
- }
- ST_LOGV("pts drop: queue1des=%lld expect=%lld size=%d",
- desiredPresent, expectedPresent, mQueue.size());
- if (stillTracking(front)) {
- // front buffer is still in mSlots, so mark the slot as free
- mSlots[front->mBuf].mBufferState = BufferSlot::FREE;
- }
- mQueue.erase(front);
- front = mQueue.begin();
- }
-
- // See if the front buffer is due.
- nsecs_t desiredPresent = front->mTimestamp;
- if (desiredPresent > expectedPresent &&
- desiredPresent < expectedPresent + MAX_REASONABLE_NSEC) {
- ST_LOGV("pts defer: des=%lld expect=%lld (%lld) now=%lld",
- desiredPresent, expectedPresent, desiredPresent - expectedPresent,
- systemTime(CLOCK_MONOTONIC));
- return PRESENT_LATER;
- }
-
- ST_LOGV("pts accept: des=%lld expect=%lld (%lld) now=%lld",
- desiredPresent, expectedPresent, desiredPresent - expectedPresent,
- systemTime(CLOCK_MONOTONIC));
- }
-
- int buf = front->mBuf;
- *buffer = *front;
- ATRACE_BUFFER_INDEX(buf);
-
- ST_LOGV("acquireBuffer: acquiring { slot=%d/%llu, buffer=%p }",
- front->mBuf, front->mFrameNumber,
- front->mGraphicBuffer->handle);
- // if front buffer still being tracked update slot state
- if (stillTracking(front)) {
- mSlots[buf].mAcquireCalled = true;
- mSlots[buf].mNeedsCleanupOnRelease = false;
- mSlots[buf].mBufferState = BufferSlot::ACQUIRED;
- mSlots[buf].mFence = Fence::NO_FENCE;
- }
-
- // If the buffer has previously been acquired by the consumer, set
- // mGraphicBuffer to NULL to avoid unnecessarily remapping this
- // buffer on the consumer side.
- if (buffer->mAcquireCalled) {
- buffer->mGraphicBuffer = NULL;
- }
-
- mQueue.erase(front);
- mDequeueCondition.broadcast();
-
- ATRACE_INT(mConsumerName.string(), mQueue.size());
-
- return NO_ERROR;
-}
-
-status_t BufferQueue::releaseBuffer(
- int buf, uint64_t frameNumber, EGLDisplay display,
- EGLSyncKHR eglFence, const sp<Fence>& fence) {
- ATRACE_CALL();
- ATRACE_BUFFER_INDEX(buf);
-
- if (buf == INVALID_BUFFER_SLOT || fence == NULL) {
- return BAD_VALUE;
- }
-
- Mutex::Autolock _l(mMutex);
-
- // If the frame number has changed because buffer has been reallocated,
- // we can ignore this releaseBuffer for the old buffer.
- if (frameNumber != mSlots[buf].mFrameNumber) {
- return STALE_BUFFER_SLOT;
- }
-
-
- // Internal state consistency checks:
- // Make sure this buffers hasn't been queued while we were owning it (acquired)
- Fifo::iterator front(mQueue.begin());
- Fifo::const_iterator const end(mQueue.end());
- while (front != end) {
- if (front->mBuf == buf) {
- LOG_ALWAYS_FATAL("[%s] received new buffer(#%lld) on slot #%d that has not yet been "
- "acquired", mConsumerName.string(), frameNumber, buf);
- break; // never reached
- }
- front++;
- }
-
- // The buffer can now only be released if its in the acquired state
- if (mSlots[buf].mBufferState == BufferSlot::ACQUIRED) {
- mSlots[buf].mEglDisplay = display;
- mSlots[buf].mEglFence = eglFence;
- mSlots[buf].mFence = fence;
- mSlots[buf].mBufferState = BufferSlot::FREE;
- } else if (mSlots[buf].mNeedsCleanupOnRelease) {
- ST_LOGV("releasing a stale buf %d its state was %d", buf, mSlots[buf].mBufferState);
- mSlots[buf].mNeedsCleanupOnRelease = false;
- return STALE_BUFFER_SLOT;
- } else {
- ST_LOGE("attempted to release buf %d but its state was %d", buf, mSlots[buf].mBufferState);
- return -EINVAL;
- }
-
- mDequeueCondition.broadcast();
- return NO_ERROR;
}
-status_t BufferQueue::consumerConnect(const sp<IConsumerListener>& consumerListener,
- bool controlledByApp) {
- ST_LOGV("consumerConnect controlledByApp=%s",
- controlledByApp ? "true" : "false");
- Mutex::Autolock lock(mMutex);
-
- if (mAbandoned) {
- ST_LOGE("consumerConnect: BufferQueue has been abandoned!");
- return NO_INIT;
- }
- if (consumerListener == NULL) {
- ST_LOGE("consumerConnect: consumerListener may not be NULL");
- return BAD_VALUE;
- }
-
- mConsumerListener = consumerListener;
- mConsumerControlledByApp = controlledByApp;
-
- return NO_ERROR;
-}
-
-status_t BufferQueue::consumerDisconnect() {
- ST_LOGV("consumerDisconnect");
- Mutex::Autolock lock(mMutex);
-
- if (mConsumerListener == NULL) {
- ST_LOGE("consumerDisconnect: No consumer is connected!");
- return -EINVAL;
- }
-
- mAbandoned = true;
- mConsumerListener = NULL;
- mQueue.clear();
- freeAllBuffersLocked();
- mDequeueCondition.broadcast();
- return NO_ERROR;
-}
-
-status_t BufferQueue::getReleasedBuffers(uint32_t* slotMask) {
- ST_LOGV("getReleasedBuffers");
- Mutex::Autolock lock(mMutex);
-
- if (mAbandoned) {
- ST_LOGE("getReleasedBuffers: BufferQueue has been abandoned!");
- return NO_INIT;
- }
-
- uint32_t mask = 0;
- for (int i = 0; i < NUM_BUFFER_SLOTS; i++) {
- if (!mSlots[i].mAcquireCalled) {
- mask |= 1 << i;
- }
- }
-
- // Remove buffers in flight (on the queue) from the mask where acquire has
- // been called, as the consumer will not receive the buffer address, so
- // it should not free these slots.
- Fifo::iterator front(mQueue.begin());
- while (front != mQueue.end()) {
- if (front->mAcquireCalled)
- mask &= ~(1 << front->mBuf);
- front++;
- }
-
- *slotMask = mask;
-
- ST_LOGV("getReleasedBuffers: returning mask %#x", mask);
- return NO_ERROR;
-}
-
-status_t BufferQueue::setDefaultBufferSize(uint32_t w, uint32_t h) {
- ST_LOGV("setDefaultBufferSize: w=%d, h=%d", w, h);
- if (!w || !h) {
- ST_LOGE("setDefaultBufferSize: dimensions cannot be 0 (w=%d, h=%d)",
- w, h);
- return BAD_VALUE;
- }
-
- Mutex::Autolock lock(mMutex);
- mDefaultWidth = w;
- mDefaultHeight = h;
- return NO_ERROR;
-}
-
-status_t BufferQueue::setDefaultMaxBufferCount(int bufferCount) {
- ATRACE_CALL();
- Mutex::Autolock lock(mMutex);
- return setDefaultMaxBufferCountLocked(bufferCount);
-}
-
-status_t BufferQueue::disableAsyncBuffer() {
- ATRACE_CALL();
- Mutex::Autolock lock(mMutex);
- if (mConsumerListener != NULL) {
- ST_LOGE("disableAsyncBuffer: consumer already connected!");
- return INVALID_OPERATION;
- }
- mUseAsyncBuffer = false;
- return NO_ERROR;
-}
-
-status_t BufferQueue::setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
- ATRACE_CALL();
- Mutex::Autolock lock(mMutex);
- if (maxAcquiredBuffers < 1 || maxAcquiredBuffers > MAX_MAX_ACQUIRED_BUFFERS) {
- ST_LOGE("setMaxAcquiredBufferCount: invalid count specified: %d",
- maxAcquiredBuffers);
- return BAD_VALUE;
- }
- if (mConnectedApi != NO_CONNECTED_API) {
- return INVALID_OPERATION;
- }
- mMaxAcquiredBufferCount = maxAcquiredBuffers;
- return NO_ERROR;
-}
-
-int BufferQueue::getMinUndequeuedBufferCount(bool async) const {
- // if dequeueBuffer is allowed to error out, we don't have to
- // add an extra buffer.
- if (!mUseAsyncBuffer)
- return mMaxAcquiredBufferCount;
-
- // we're in async mode, or we want to prevent the app to
- // deadlock itself, we throw-in an extra buffer to guarantee it.
- if (mDequeueBufferCannotBlock || async)
- return mMaxAcquiredBufferCount+1;
-
- return mMaxAcquiredBufferCount;
-}
-
-int BufferQueue::getMinMaxBufferCountLocked(bool async) const {
- return getMinUndequeuedBufferCount(async) + 1;
-}
-
-int BufferQueue::getMaxBufferCountLocked(bool async) const {
- int minMaxBufferCount = getMinMaxBufferCountLocked(async);
-
- int maxBufferCount = mDefaultMaxBufferCount;
- if (maxBufferCount < minMaxBufferCount) {
- maxBufferCount = minMaxBufferCount;
- }
- if (mOverrideMaxBufferCount != 0) {
- assert(mOverrideMaxBufferCount >= minMaxBufferCount);
- maxBufferCount = mOverrideMaxBufferCount;
- }
-
- // Any buffers that are dequeued by the producer or sitting in the queue
- // waiting to be consumed need to have their slots preserved. Such
- // buffers will temporarily keep the max buffer count up until the slots
- // no longer need to be preserved.
- for (int i = maxBufferCount; i < NUM_BUFFER_SLOTS; i++) {
- BufferSlot::BufferState state = mSlots[i].mBufferState;
- if (state == BufferSlot::QUEUED || state == BufferSlot::DEQUEUED) {
- maxBufferCount = i + 1;
- }
+void BufferQueue::ProxyConsumerListener::onSidebandStreamChanged() {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != NULL) {
+ listener->onSidebandStreamChanged();
}
-
- return maxBufferCount;
}
-bool BufferQueue::stillTracking(const BufferItem *item) const {
- const BufferSlot &slot = mSlots[item->mBuf];
-
- ST_LOGV("stillTracking?: item: { slot=%d/%llu, buffer=%p }, "
- "slot: { slot=%d/%llu, buffer=%p }",
- item->mBuf, item->mFrameNumber,
- (item->mGraphicBuffer.get() ? item->mGraphicBuffer->handle : 0),
- item->mBuf, slot.mFrameNumber,
- (slot.mGraphicBuffer.get() ? slot.mGraphicBuffer->handle : 0));
-
- // Compare item with its original buffer slot. We can check the slot
- // as the buffer would not be moved to a different slot by the producer.
- return (slot.mGraphicBuffer != NULL &&
- item->mGraphicBuffer->handle == slot.mGraphicBuffer->handle);
-}
+void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
+ sp<IGraphicBufferConsumer>* outConsumer,
+ const sp<IGraphicBufferAlloc>& allocator) {
+ LOG_ALWAYS_FATAL_IF(outProducer == NULL,
+ "BufferQueue: outProducer must not be NULL");
+ LOG_ALWAYS_FATAL_IF(outConsumer == NULL,
+ "BufferQueue: outConsumer must not be NULL");
-BufferQueue::ProxyConsumerListener::ProxyConsumerListener(
- const wp<ConsumerListener>& consumerListener):
- mConsumerListener(consumerListener) {}
+ sp<BufferQueueCore> core(new BufferQueueCore(allocator));
+ LOG_ALWAYS_FATAL_IF(core == NULL,
+ "BufferQueue: failed to create BufferQueueCore");
-BufferQueue::ProxyConsumerListener::~ProxyConsumerListener() {}
+ sp<IGraphicBufferProducer> producer(new BufferQueueProducer(core));
+ LOG_ALWAYS_FATAL_IF(producer == NULL,
+ "BufferQueue: failed to create BufferQueueProducer");
-void BufferQueue::ProxyConsumerListener::onFrameAvailable() {
- sp<ConsumerListener> listener(mConsumerListener.promote());
- if (listener != NULL) {
- listener->onFrameAvailable();
- }
-}
+ sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
+ LOG_ALWAYS_FATAL_IF(consumer == NULL,
+ "BufferQueue: failed to create BufferQueueConsumer");
-void BufferQueue::ProxyConsumerListener::onBuffersReleased() {
- sp<ConsumerListener> listener(mConsumerListener.promote());
- if (listener != NULL) {
- listener->onBuffersReleased();
- }
+ *outProducer = producer;
+ *outConsumer = consumer;
}
}; // namespace android
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
new file mode 100644
index 0000000..f3f26ac
--- /dev/null
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2014 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 "BufferQueueConsumer"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include <gui/BufferItem.h>
+#include <gui/BufferQueueConsumer.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/IConsumerListener.h>
+#include <gui/IProducerListener.h>
+
+namespace android {
+
+BufferQueueConsumer::BufferQueueConsumer(const sp<BufferQueueCore>& core) :
+ mCore(core),
+ mSlots(core->mSlots),
+ mConsumerName() {}
+
+BufferQueueConsumer::~BufferQueueConsumer() {}
+
+status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
+ nsecs_t expectedPresent) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+
+ // Check that the consumer doesn't currently have the maximum number of
+ // buffers acquired. We allow the max buffer count to be exceeded by one
+ // buffer so that the consumer can successfully set up the newly acquired
+ // buffer before releasing the old one.
+ int numAcquiredBuffers = 0;
+ for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == BufferSlot::ACQUIRED) {
+ ++numAcquiredBuffers;
+ }
+ }
+ if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
+ BQ_LOGE("acquireBuffer: max acquired buffer count reached: %d (max %d)",
+ numAcquiredBuffers, mCore->mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+
+ // Check if the queue is empty.
+ // In asynchronous mode the list is guaranteed to be one buffer deep,
+ // while in synchronous mode we use the oldest buffer.
+ if (mCore->mQueue.empty()) {
+ return NO_BUFFER_AVAILABLE;
+ }
+
+ BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
+
+ // If expectedPresent is specified, we may not want to return a buffer yet.
+ // If it's specified and there's more than one buffer queued, we may want
+ // to drop a buffer.
+ if (expectedPresent != 0) {
+ const int MAX_REASONABLE_NSEC = 1000000000ULL; // 1 second
+
+ // The 'expectedPresent' argument indicates when the buffer is expected
+ // to be presented on-screen. If the buffer's desired present time is
+ // earlier (less) than expectedPresent -- meaning it will be displayed
+ // on time or possibly late if we show it as soon as possible -- we
+ // acquire and return it. If we don't want to display it until after the
+ // expectedPresent time, we return PRESENT_LATER without acquiring it.
+ //
+ // To be safe, we don't defer acquisition if expectedPresent is more
+ // than one second in the future beyond the desired present time
+ // (i.e., we'd be holding the buffer for a long time).
+ //
+ // NOTE: Code assumes monotonic time values from the system clock
+ // are positive.
+
+ // Start by checking to see if we can drop frames. We skip this check if
+ // the timestamps are being auto-generated by Surface. If the app isn't
+ // generating timestamps explicitly, it probably doesn't want frames to
+ // be discarded based on them.
+ while (mCore->mQueue.size() > 1 && !mCore->mQueue[0].mIsAutoTimestamp) {
+ // If entry[1] is timely, drop entry[0] (and repeat). We apply an
+ // additional criterion here: we only drop the earlier buffer if our
+ // desiredPresent falls within +/- 1 second of the expected present.
+ // Otherwise, bogus desiredPresent times (e.g., 0 or a small
+ // relative timestamp), which normally mean "ignore the timestamp
+ // and acquire immediately", would cause us to drop frames.
+ //
+ // We may want to add an additional criterion: don't drop the
+ // earlier buffer if entry[1]'s fence hasn't signaled yet.
+ const BufferItem& bufferItem(mCore->mQueue[1]);
+ nsecs_t desiredPresent = bufferItem.mTimestamp;
+ if (desiredPresent < expectedPresent - MAX_REASONABLE_NSEC ||
+ desiredPresent > expectedPresent) {
+ // This buffer is set to display in the near future, or
+ // desiredPresent is garbage. Either way we don't want to drop
+ // the previous buffer just to get this on the screen sooner.
+ BQ_LOGV("acquireBuffer: nodrop desire=%lld expect=%lld "
+ "(%lld) now=%lld", desiredPresent, expectedPresent,
+ desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ break;
+ }
+
+ BQ_LOGV("acquireBuffer: drop desire=%lld expect=%lld size=%d",
+ desiredPresent, expectedPresent, mCore->mQueue.size());
+ if (mCore->stillTracking(front)) {
+ // Front buffer is still in mSlots, so mark the slot as free
+ mSlots[front->mSlot].mBufferState = BufferSlot::FREE;
+ }
+ mCore->mQueue.erase(front);
+ front = mCore->mQueue.begin();
+ }
+
+ // See if the front buffer is due
+ nsecs_t desiredPresent = front->mTimestamp;
+ if (desiredPresent > expectedPresent &&
+ desiredPresent < expectedPresent + MAX_REASONABLE_NSEC) {
+ BQ_LOGV("acquireBuffer: defer desire=%lld expect=%lld "
+ "(%lld) now=%lld", desiredPresent, expectedPresent,
+ desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ return PRESENT_LATER;
+ }
+
+ BQ_LOGV("acquireBuffer: accept desire=%lld expect=%lld "
+ "(%lld) now=%lld", desiredPresent, expectedPresent,
+ desiredPresent - expectedPresent,
+ systemTime(CLOCK_MONOTONIC));
+ }
+
+ int slot = front->mSlot;
+ *outBuffer = *front;
+ ATRACE_BUFFER_INDEX(slot);
+
+ BQ_LOGV("acquireBuffer: acquiring { slot=%d/%llu buffer=%p }",
+ slot, front->mFrameNumber, front->mGraphicBuffer->handle);
+ // If the front buffer is still being tracked, update its slot state
+ if (mCore->stillTracking(front)) {
+ mSlots[slot].mAcquireCalled = true;
+ mSlots[slot].mNeedsCleanupOnRelease = false;
+ mSlots[slot].mBufferState = BufferSlot::ACQUIRED;
+ mSlots[slot].mFence = Fence::NO_FENCE;
+ }
+
+ // If the buffer has previously been acquired by the consumer, set
+ // mGraphicBuffer to NULL to avoid unnecessarily remapping this buffer
+ // on the consumer side
+ if (outBuffer->mAcquireCalled) {
+ outBuffer->mGraphicBuffer = NULL;
+ }
+
+ mCore->mQueue.erase(front);
+
+ // We might have freed a slot while dropping old buffers, or the producer
+ // may be blocked waiting for the number of buffers in the queue to
+ // decrease.
+ mCore->mDequeueCondition.broadcast();
+
+ ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size());
+
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::detachBuffer(int slot) {
+ ATRACE_CALL();
+ ATRACE_BUFFER_INDEX(slot);
+ BQ_LOGV("detachBuffer(C): slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("detachBuffer(C): BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ BQ_LOGE("detachBuffer(C): slot index %d out of range [0, %d)",
+ slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != BufferSlot::ACQUIRED) {
+ BQ_LOGE("detachBuffer(C): slot %d is not owned by the consumer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+
+ mCore->freeBufferLocked(slot);
+ mCore->mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::attachBuffer(int* outSlot,
+ const sp<android::GraphicBuffer>& buffer) {
+ ATRACE_CALL();
+
+ if (outSlot == NULL) {
+ BQ_LOGE("attachBuffer(P): outSlot must not be NULL");
+ return BAD_VALUE;
+ } else if (buffer == NULL) {
+ BQ_LOGE("attachBuffer(P): cannot attach NULL buffer");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ // Make sure we don't have too many acquired buffers and find a free slot
+ // to put the buffer into (the oldest if there are multiple).
+ int numAcquiredBuffers = 0;
+ int found = BufferQueueCore::INVALID_BUFFER_SLOT;
+ for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == BufferSlot::ACQUIRED) {
+ ++numAcquiredBuffers;
+ } else if (mSlots[s].mBufferState == BufferSlot::FREE) {
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT ||
+ mSlots[s].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = s;
+ }
+ }
+ }
+
+ if (numAcquiredBuffers >= mCore->mMaxAcquiredBufferCount + 1) {
+ BQ_LOGE("attachBuffer(P): max acquired buffer count reached: %d "
+ "(max %d)", numAcquiredBuffers,
+ mCore->mMaxAcquiredBufferCount);
+ return INVALID_OPERATION;
+ }
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+ BQ_LOGE("attachBuffer(P): could not find free buffer slot");
+ return NO_MEMORY;
+ }
+
+ *outSlot = found;
+ ATRACE_BUFFER_INDEX(*outSlot);
+ BQ_LOGV("attachBuffer(C): returning slot %d", *outSlot);
+
+ mSlots[*outSlot].mGraphicBuffer = buffer;
+ mSlots[*outSlot].mBufferState = BufferSlot::ACQUIRED;
+ mSlots[*outSlot].mAttachedByConsumer = true;
+ mSlots[*outSlot].mNeedsCleanupOnRelease = false;
+ mSlots[*outSlot].mFence = Fence::NO_FENCE;
+ mSlots[*outSlot].mFrameNumber = 0;
+
+ // mAcquireCalled tells BufferQueue that it doesn't need to send a valid
+ // GraphicBuffer pointer on the next acquireBuffer call, which decreases
+ // Binder traffic by not un/flattening the GraphicBuffer. However, it
+ // requires that the consumer maintain a cached copy of the slot <--> buffer
+ // mappings, which is why the consumer doesn't need the valid pointer on
+ // acquire.
+ //
+ // The StreamSplitter is one of the primary users of the attach/detach
+ // logic, and while it is running, all buffers it acquires are immediately
+ // detached, and all buffers it eventually releases are ones that were
+ // attached (as opposed to having been obtained from acquireBuffer), so it
+ // doesn't make sense to maintain the slot/buffer mappings, which would
+ // become invalid for every buffer during detach/attach. By setting this to
+ // false, the valid GraphicBuffer pointer will always be sent with acquire
+ // for attached buffers.
+ mSlots[*outSlot].mAcquireCalled = false;
+
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::releaseBuffer(int slot, uint64_t frameNumber,
+ const sp<Fence>& releaseFence, EGLDisplay eglDisplay,
+ EGLSyncKHR eglFence) {
+ ATRACE_CALL();
+ ATRACE_BUFFER_INDEX(slot);
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS ||
+ releaseFence == NULL) {
+ return BAD_VALUE;
+ }
+
+ sp<IProducerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ // If the frame number has changed because the buffer has been reallocated,
+ // we can ignore this releaseBuffer for the old buffer
+ if (frameNumber != mSlots[slot].mFrameNumber) {
+ return STALE_BUFFER_SLOT;
+ }
+
+ // Make sure this buffer hasn't been queued while acquired by the consumer
+ BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
+ while (current != mCore->mQueue.end()) {
+ if (current->mSlot == slot) {
+ BQ_LOGE("releaseBuffer: buffer slot %d pending release is "
+ "currently queued", slot);
+ return BAD_VALUE;
+ }
+ ++current;
+ }
+
+ if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[slot].mEglDisplay = eglDisplay;
+ mSlots[slot].mEglFence = eglFence;
+ mSlots[slot].mFence = releaseFence;
+ mSlots[slot].mBufferState = BufferSlot::FREE;
+ listener = mCore->mConnectedProducerListener;
+ BQ_LOGV("releaseBuffer: releasing slot %d", slot);
+ } else if (mSlots[slot].mNeedsCleanupOnRelease) {
+ BQ_LOGV("releaseBuffer: releasing a stale buffer slot %d "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ mSlots[slot].mNeedsCleanupOnRelease = false;
+ return STALE_BUFFER_SLOT;
+ } else {
+ BQ_LOGV("releaseBuffer: attempted to release buffer slot %d "
+ "but its state was %d", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+
+ mCore->mDequeueCondition.broadcast();
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onBufferReleased();
+ }
+
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::connect(
+ const sp<IConsumerListener>& consumerListener, bool controlledByApp) {
+ ATRACE_CALL();
+
+ if (consumerListener == NULL) {
+ BQ_LOGE("connect(C): consumerListener may not be NULL");
+ return BAD_VALUE;
+ }
+
+ BQ_LOGV("connect(C): controlledByApp=%s",
+ controlledByApp ? "true" : "false");
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("connect(C): BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ mCore->mConsumerListener = consumerListener;
+ mCore->mConsumerControlledByApp = controlledByApp;
+
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::disconnect() {
+ ATRACE_CALL();
+
+ BQ_LOGV("disconnect(C)");
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mConsumerListener == NULL) {
+ BQ_LOGE("disconnect(C): no consumer is connected");
+ return BAD_VALUE;
+ }
+
+ mCore->mIsAbandoned = true;
+ mCore->mConsumerListener = NULL;
+ mCore->mQueue.clear();
+ mCore->freeAllBuffersLocked();
+ mCore->mDequeueCondition.broadcast();
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::getReleasedBuffers(uint64_t *outSlotMask) {
+ ATRACE_CALL();
+
+ if (outSlotMask == NULL) {
+ BQ_LOGE("getReleasedBuffers: outSlotMask may not be NULL");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("getReleasedBuffers: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ uint64_t mask = 0;
+ for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (!mSlots[s].mAcquireCalled) {
+ mask |= (1ULL << s);
+ }
+ }
+
+ // Remove from the mask queued buffers for which acquire has been called,
+ // since the consumer will not receive their buffer addresses and so must
+ // retain their cached information
+ BufferQueueCore::Fifo::iterator current(mCore->mQueue.begin());
+ while (current != mCore->mQueue.end()) {
+ if (current->mAcquireCalled) {
+ mask &= ~(1ULL << current->mSlot);
+ }
+ ++current;
+ }
+
+ BQ_LOGV("getReleasedBuffers: returning mask %#" PRIx64, mask);
+ *outSlotMask = mask;
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::setDefaultBufferSize(uint32_t width,
+ uint32_t height) {
+ ATRACE_CALL();
+
+ if (width == 0 || height == 0) {
+ BQ_LOGV("setDefaultBufferSize: dimensions cannot be 0 (width=%u "
+ "height=%u)", width, height);
+ return BAD_VALUE;
+ }
+
+ BQ_LOGV("setDefaultBufferSize: width=%u height=%u", width, height);
+
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mDefaultWidth = width;
+ mCore->mDefaultHeight = height;
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::setDefaultMaxBufferCount(int bufferCount) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+ return mCore->setDefaultMaxBufferCountLocked(bufferCount);
+}
+
+status_t BufferQueueConsumer::disableAsyncBuffer() {
+ ATRACE_CALL();
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mConsumerListener != NULL) {
+ BQ_LOGE("disableAsyncBuffer: consumer already connected");
+ return INVALID_OPERATION;
+ }
+
+ BQ_LOGV("disableAsyncBuffer");
+ mCore->mUseAsyncBuffer = false;
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::setMaxAcquiredBufferCount(
+ int maxAcquiredBuffers) {
+ ATRACE_CALL();
+
+ if (maxAcquiredBuffers < 1 ||
+ maxAcquiredBuffers > BufferQueueCore::MAX_MAX_ACQUIRED_BUFFERS) {
+ BQ_LOGE("setMaxAcquiredBufferCount: invalid count %d",
+ maxAcquiredBuffers);
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) {
+ BQ_LOGE("setMaxAcquiredBufferCount: producer is already connected");
+ return INVALID_OPERATION;
+ }
+
+ BQ_LOGV("setMaxAcquiredBufferCount: %d", maxAcquiredBuffers);
+ mCore->mMaxAcquiredBufferCount = maxAcquiredBuffers;
+ return NO_ERROR;
+}
+
+void BufferQueueConsumer::setConsumerName(const String8& name) {
+ ATRACE_CALL();
+ BQ_LOGV("setConsumerName: '%s'", name.string());
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mConsumerName = name;
+ mConsumerName = name;
+}
+
+status_t BufferQueueConsumer::setDefaultBufferFormat(uint32_t defaultFormat) {
+ ATRACE_CALL();
+ BQ_LOGV("setDefaultBufferFormat: %u", defaultFormat);
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mDefaultBufferFormat = defaultFormat;
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::setConsumerUsageBits(uint32_t usage) {
+ ATRACE_CALL();
+ BQ_LOGV("setConsumerUsageBits: %#x", usage);
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mConsumerUsageBits = usage;
+ return NO_ERROR;
+}
+
+status_t BufferQueueConsumer::setTransformHint(uint32_t hint) {
+ ATRACE_CALL();
+ BQ_LOGV("setTransformHint: %#x", hint);
+ Mutex::Autolock lock(mCore->mMutex);
+ mCore->mTransformHint = hint;
+ return NO_ERROR;
+}
+
+sp<NativeHandle> BufferQueueConsumer::getSidebandStream() const {
+ return mCore->mSidebandStream;
+}
+
+void BufferQueueConsumer::dump(String8& result, const char* prefix) const {
+ mCore->dump(result, prefix);
+}
+
+} // namespace android
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
new file mode 100644
index 0000000..593b6f1
--- /dev/null
+++ b/libs/gui/BufferQueueCore.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2014 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 "BufferQueueCore"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <inttypes.h>
+
+#include <gui/BufferItem.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/IProducerListener.h>
+#include <gui/ISurfaceComposer.h>
+#include <private/gui/ComposerService.h>
+
+template <typename T>
+static inline T max(T a, T b) { return a > b ? a : b; }
+
+namespace android {
+
+static String8 getUniqueName() {
+ static volatile int32_t counter = 0;
+ return String8::format("unnamed-%d-%d", getpid(),
+ android_atomic_inc(&counter));
+}
+
+BufferQueueCore::BufferQueueCore(const sp<IGraphicBufferAlloc>& allocator) :
+ mAllocator(allocator),
+ mMutex(),
+ mIsAbandoned(false),
+ mConsumerControlledByApp(false),
+ mConsumerName(getUniqueName()),
+ mConsumerListener(),
+ mConsumerUsageBits(0),
+ mConnectedApi(NO_CONNECTED_API),
+ mConnectedProducerListener(),
+ mSlots(),
+ mQueue(),
+ mOverrideMaxBufferCount(0),
+ mDequeueCondition(),
+ mUseAsyncBuffer(true),
+ mDequeueBufferCannotBlock(false),
+ mDefaultBufferFormat(PIXEL_FORMAT_RGBA_8888),
+ mDefaultWidth(1),
+ mDefaultHeight(1),
+ mDefaultMaxBufferCount(2),
+ mMaxAcquiredBufferCount(1),
+ mBufferHasBeenQueued(false),
+ mFrameCounter(0),
+ mTransformHint(0)
+{
+ if (allocator == NULL) {
+ sp<ISurfaceComposer> composer(ComposerService::getComposerService());
+ mAllocator = composer->createGraphicBufferAlloc();
+ if (mAllocator == NULL) {
+ BQ_LOGE("createGraphicBufferAlloc failed");
+ }
+ }
+}
+
+BufferQueueCore::~BufferQueueCore() {}
+
+void BufferQueueCore::dump(String8& result, const char* prefix) const {
+ Mutex::Autolock lock(mMutex);
+
+ String8 fifo;
+ Fifo::const_iterator current(mQueue.begin());
+ while (current != mQueue.end()) {
+ fifo.appendFormat("%02d:%p crop=[%d,%d,%d,%d], "
+ "xform=0x%02x, time=%#" PRIx64 ", scale=%s\n",
+ current->mSlot, current->mGraphicBuffer.get(),
+ current->mCrop.left, current->mCrop.top, current->mCrop.right,
+ current->mCrop.bottom, current->mTransform, current->mTimestamp,
+ BufferItem::scalingModeName(current->mScalingMode));
+ ++current;
+ }
+
+ result.appendFormat("%s-BufferQueue mMaxAcquiredBufferCount=%d, "
+ "mDequeueBufferCannotBlock=%d, default-size=[%dx%d], "
+ "default-format=%d, transform-hint=%02x, FIFO(%zu)={%s}\n",
+ prefix, mMaxAcquiredBufferCount, mDequeueBufferCannotBlock,
+ mDefaultWidth, mDefaultHeight, mDefaultBufferFormat, mTransformHint,
+ mQueue.size(), fifo.string());
+
+ // Trim the free buffers so as to not spam the dump
+ int maxBufferCount = 0;
+ for (int s = BufferQueueDefs::NUM_BUFFER_SLOTS - 1; s >= 0; --s) {
+ const BufferSlot& slot(mSlots[s]);
+ if (slot.mBufferState != BufferSlot::FREE ||
+ slot.mGraphicBuffer != NULL) {
+ maxBufferCount = s + 1;
+ break;
+ }
+ }
+
+ for (int s = 0; s < maxBufferCount; ++s) {
+ const BufferSlot& slot(mSlots[s]);
+ const sp<GraphicBuffer>& buffer(slot.mGraphicBuffer);
+ result.appendFormat("%s%s[%02d:%p] state=%-8s", prefix,
+ (slot.mBufferState == BufferSlot::ACQUIRED) ? ">" : " ",
+ s, buffer.get(),
+ BufferSlot::bufferStateName(slot.mBufferState));
+
+ if (buffer != NULL) {
+ result.appendFormat(", %p [%4ux%4u:%4u,%3X]", buffer->handle,
+ buffer->width, buffer->height, buffer->stride,
+ buffer->format);
+ }
+
+ result.append("\n");
+ }
+}
+
+int BufferQueueCore::getMinUndequeuedBufferCountLocked(bool async) const {
+ // If dequeueBuffer is allowed to error out, we don't have to add an
+ // extra buffer.
+ if (!mUseAsyncBuffer) {
+ return mMaxAcquiredBufferCount;
+ }
+
+ if (mDequeueBufferCannotBlock || async) {
+ return mMaxAcquiredBufferCount + 1;
+ }
+
+ return mMaxAcquiredBufferCount;
+}
+
+int BufferQueueCore::getMinMaxBufferCountLocked(bool async) const {
+ return getMinUndequeuedBufferCountLocked(async) + 1;
+}
+
+int BufferQueueCore::getMaxBufferCountLocked(bool async) const {
+ int minMaxBufferCount = getMinMaxBufferCountLocked(async);
+
+ int maxBufferCount = max(mDefaultMaxBufferCount, minMaxBufferCount);
+ if (mOverrideMaxBufferCount != 0) {
+ assert(mOverrideMaxBufferCount >= minMaxBufferCount);
+ maxBufferCount = mOverrideMaxBufferCount;
+ }
+
+ // Any buffers that are dequeued by the producer or sitting in the queue
+ // waiting to be consumed need to have their slots preserved. Such buffers
+ // will temporarily keep the max buffer count up until the slots no longer
+ // need to be preserved.
+ for (int s = maxBufferCount; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ BufferSlot::BufferState state = mSlots[s].mBufferState;
+ if (state == BufferSlot::QUEUED || state == BufferSlot::DEQUEUED) {
+ maxBufferCount = s + 1;
+ }
+ }
+
+ return maxBufferCount;
+}
+
+status_t BufferQueueCore::setDefaultMaxBufferCountLocked(int count) {
+ const int minBufferCount = mUseAsyncBuffer ? 2 : 1;
+ if (count < minBufferCount || count > BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ BQ_LOGV("setDefaultMaxBufferCount: invalid count %d, should be in "
+ "[%d, %d]", minBufferCount, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ BQ_LOGV("setDefaultMaxBufferCount: setting count to %d", count);
+ mDefaultMaxBufferCount = count;
+ mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+void BufferQueueCore::freeBufferLocked(int slot) {
+ BQ_LOGV("freeBufferLocked: slot %d", slot);
+ mSlots[slot].mGraphicBuffer.clear();
+ if (mSlots[slot].mBufferState == BufferSlot::ACQUIRED) {
+ mSlots[slot].mNeedsCleanupOnRelease = true;
+ }
+ mSlots[slot].mBufferState = BufferSlot::FREE;
+ mSlots[slot].mFrameNumber = UINT32_MAX;
+ mSlots[slot].mAcquireCalled = false;
+
+ // Destroy fence as BufferQueue now takes ownership
+ if (mSlots[slot].mEglFence != EGL_NO_SYNC_KHR) {
+ eglDestroySyncKHR(mSlots[slot].mEglDisplay, mSlots[slot].mEglFence);
+ mSlots[slot].mEglFence = EGL_NO_SYNC_KHR;
+ }
+ mSlots[slot].mFence = Fence::NO_FENCE;
+}
+
+void BufferQueueCore::freeAllBuffersLocked() {
+ mBufferHasBeenQueued = false;
+ for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ freeBufferLocked(s);
+ }
+}
+
+bool BufferQueueCore::stillTracking(const BufferItem* item) const {
+ const BufferSlot& slot = mSlots[item->mSlot];
+
+ BQ_LOGV("stillTracking: item { slot=%d/%llu buffer=%p } "
+ "slot { slot=%d/%llu buffer=%p }",
+ item->mSlot, item->mFrameNumber,
+ (item->mGraphicBuffer.get() ? item->mGraphicBuffer->handle : 0),
+ item->mSlot, slot.mFrameNumber,
+ (slot.mGraphicBuffer.get() ? slot.mGraphicBuffer->handle : 0));
+
+ // Compare item with its original buffer slot. We can check the slot as
+ // the buffer would not be moved to a different slot by the producer.
+ return (slot.mGraphicBuffer != NULL) &&
+ (item->mGraphicBuffer->handle == slot.mGraphicBuffer->handle);
+}
+
+} // namespace android
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
new file mode 100644
index 0000000..f536a59
--- /dev/null
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -0,0 +1,860 @@
+/*
+ * Copyright 2014 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 "BufferQueueProducer"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#define EGL_EGLEXT_PROTOTYPES
+
+#include <gui/BufferItem.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/BufferQueueProducer.h>
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferAlloc.h>
+#include <gui/IProducerListener.h>
+
+#include <utils/Log.h>
+#include <utils/Trace.h>
+
+namespace android {
+
+BufferQueueProducer::BufferQueueProducer(const sp<BufferQueueCore>& core) :
+ mCore(core),
+ mSlots(core->mSlots),
+ mConsumerName() {}
+
+BufferQueueProducer::~BufferQueueProducer() {}
+
+status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
+ ATRACE_CALL();
+ BQ_LOGV("requestBuffer: slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("requestBuffer: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ BQ_LOGE("requestBuffer: slot index %d out of range [0, %d)",
+ slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+ BQ_LOGE("requestBuffer: slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ }
+
+ mSlots[slot].mRequestBufferCalled = true;
+ *buf = mSlots[slot].mGraphicBuffer;
+ return NO_ERROR;
+}
+
+status_t BufferQueueProducer::setBufferCount(int bufferCount) {
+ ATRACE_CALL();
+ BQ_LOGV("setBufferCount: count = %d", bufferCount);
+
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("setBufferCount: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (bufferCount > BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ BQ_LOGE("setBufferCount: bufferCount %d too large (max %d)",
+ bufferCount, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ }
+
+ // There must be no dequeued buffers when changing the buffer count.
+ for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == BufferSlot::DEQUEUED) {
+ BQ_LOGE("setBufferCount: buffer owned by producer");
+ return BAD_VALUE;
+ }
+ }
+
+ if (bufferCount == 0) {
+ mCore->mOverrideMaxBufferCount = 0;
+ mCore->mDequeueCondition.broadcast();
+ return NO_ERROR;
+ }
+
+ const int minBufferSlots = mCore->getMinMaxBufferCountLocked(false);
+ if (bufferCount < minBufferSlots) {
+ BQ_LOGE("setBufferCount: requested buffer count %d is less than "
+ "minimum %d", bufferCount, minBufferSlots);
+ return BAD_VALUE;
+ }
+
+ // Here we are guaranteed that the producer doesn't have any dequeued
+ // buffers and will release all of its buffer references. We don't
+ // clear the queue, however, so that currently queued buffers still
+ // get displayed.
+ mCore->freeAllBuffersLocked();
+ mCore->mOverrideMaxBufferCount = bufferCount;
+ mCore->mDequeueCondition.broadcast();
+ listener = mCore->mConsumerListener;
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return NO_ERROR;
+}
+
+status_t BufferQueueProducer::waitForFreeSlotThenRelock(const char* caller,
+ bool async, int* found, status_t* returnFlags) const {
+ bool tryAgain = true;
+ while (tryAgain) {
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("%s: BufferQueue has been abandoned", caller);
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = mCore->getMaxBufferCountLocked(async);
+ if (async && mCore->mOverrideMaxBufferCount) {
+ // FIXME: Some drivers are manually setting the buffer count
+ // (which they shouldn't), so we do this extra test here to
+ // handle that case. This is TEMPORARY until we get this fixed.
+ if (mCore->mOverrideMaxBufferCount < maxBufferCount) {
+ BQ_LOGE("%s: async mode is invalid with buffer count override",
+ caller);
+ return BAD_VALUE;
+ }
+ }
+
+ // Free up any buffers that are in slots beyond the max buffer count
+ for (int s = maxBufferCount; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ assert(mSlots[s].mBufferState == BufferSlot::FREE);
+ if (mSlots[s].mGraphicBuffer != NULL) {
+ mCore->freeBufferLocked(s);
+ *returnFlags |= RELEASE_ALL_BUFFERS;
+ }
+ }
+
+ // Look for a free buffer to give to the client
+ *found = BufferQueueCore::INVALID_BUFFER_SLOT;
+ int dequeuedCount = 0;
+ int acquiredCount = 0;
+ for (int s = 0; s < maxBufferCount; ++s) {
+ switch (mSlots[s].mBufferState) {
+ case BufferSlot::DEQUEUED:
+ ++dequeuedCount;
+ break;
+ case BufferSlot::ACQUIRED:
+ ++acquiredCount;
+ break;
+ case BufferSlot::FREE:
+ // We return the oldest of the free buffers to avoid
+ // stalling the producer if possible, since the consumer
+ // may still have pending reads of in-flight buffers
+ if (*found == BufferQueueCore::INVALID_BUFFER_SLOT ||
+ mSlots[s].mFrameNumber < mSlots[*found].mFrameNumber) {
+ *found = s;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Producers are not allowed to dequeue more than one buffer if they
+ // did not set a buffer count
+ if (!mCore->mOverrideMaxBufferCount && dequeuedCount) {
+ BQ_LOGE("%s: can't dequeue multiple buffers without setting the "
+ "buffer count", caller);
+ return INVALID_OPERATION;
+ }
+
+ // See whether a buffer has been queued since the last
+ // setBufferCount so we know whether to perform the min undequeued
+ // buffers check below
+ if (mCore->mBufferHasBeenQueued) {
+ // Make sure the producer is not trying to dequeue more buffers
+ // than allowed
+ const int newUndequeuedCount =
+ maxBufferCount - (dequeuedCount + 1);
+ const int minUndequeuedCount =
+ mCore->getMinUndequeuedBufferCountLocked(async);
+ if (newUndequeuedCount < minUndequeuedCount) {
+ BQ_LOGE("%s: min undequeued buffer count (%d) exceeded "
+ "(dequeued=%d undequeued=%d)",
+ caller, minUndequeuedCount,
+ dequeuedCount, newUndequeuedCount);
+ return INVALID_OPERATION;
+ }
+ }
+
+ // If we disconnect and reconnect quickly, we can be in a state where
+ // our slots are empty but we have many buffers in the queue. This can
+ // cause us to run out of memory if we outrun the consumer. Wait here if
+ // it looks like we have too many buffers queued up.
+ bool tooManyBuffers = mCore->mQueue.size() > maxBufferCount;
+ if (tooManyBuffers) {
+ BQ_LOGV("%s: queue size is %d, waiting", caller,
+ mCore->mQueue.size());
+ }
+
+ // If no buffer is found, or if the queue has too many buffers
+ // outstanding, wait for a buffer to be acquired or released, or for the
+ // max buffer count to change.
+ tryAgain = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) ||
+ tooManyBuffers;
+ if (tryAgain) {
+ // Return an error if we're in non-blocking mode (producer and
+ // consumer are controlled by the application).
+ // However, the consumer is allowed to briefly acquire an extra
+ // buffer (which could cause us to have to wait here), which is
+ // okay, since it is only used to implement an atomic acquire +
+ // release (e.g., in GLConsumer::updateTexImage())
+ if (mCore->mDequeueBufferCannotBlock &&
+ (acquiredCount <= mCore->mMaxAcquiredBufferCount)) {
+ return WOULD_BLOCK;
+ }
+ mCore->mDequeueCondition.wait(mCore->mMutex);
+ }
+ } // while (tryAgain)
+
+ return NO_ERROR;
+}
+
+status_t BufferQueueProducer::dequeueBuffer(int *outSlot,
+ sp<android::Fence> *outFence, bool async,
+ uint32_t width, uint32_t height, uint32_t format, uint32_t usage) {
+ ATRACE_CALL();
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+ mConsumerName = mCore->mConsumerName;
+ } // Autolock scope
+
+ BQ_LOGV("dequeueBuffer: async=%s w=%u h=%u format=%#x, usage=%#x",
+ async ? "true" : "false", width, height, format, usage);
+
+ if ((width && !height) || (!width && height)) {
+ BQ_LOGE("dequeueBuffer: invalid size: w=%u h=%u", width, height);
+ return BAD_VALUE;
+ }
+
+ status_t returnFlags = NO_ERROR;
+ EGLDisplay eglDisplay = EGL_NO_DISPLAY;
+ EGLSyncKHR eglFence = EGL_NO_SYNC_KHR;
+ bool attachedByConsumer = false;
+
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (format == 0) {
+ format = mCore->mDefaultBufferFormat;
+ }
+
+ // Enable the usage bits the consumer requested
+ usage |= mCore->mConsumerUsageBits;
+
+ int found;
+ status_t status = waitForFreeSlotThenRelock("dequeueBuffer", async,
+ &found, &returnFlags);
+ if (status != NO_ERROR) {
+ return status;
+ }
+
+ // This should not happen
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+ BQ_LOGE("dequeueBuffer: no available buffer slots");
+ return -EBUSY;
+ }
+
+ *outSlot = found;
+ ATRACE_BUFFER_INDEX(found);
+
+ attachedByConsumer = mSlots[found].mAttachedByConsumer;
+
+ const bool useDefaultSize = !width && !height;
+ if (useDefaultSize) {
+ width = mCore->mDefaultWidth;
+ height = mCore->mDefaultHeight;
+ }
+
+ mSlots[found].mBufferState = BufferSlot::DEQUEUED;
+
+ const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
+ if ((buffer == NULL) ||
+ (static_cast<uint32_t>(buffer->width) != width) ||
+ (static_cast<uint32_t>(buffer->height) != height) ||
+ (static_cast<uint32_t>(buffer->format) != format) ||
+ ((static_cast<uint32_t>(buffer->usage) & usage) != usage))
+ {
+ mSlots[found].mAcquireCalled = false;
+ mSlots[found].mGraphicBuffer = NULL;
+ mSlots[found].mRequestBufferCalled = false;
+ mSlots[found].mEglDisplay = EGL_NO_DISPLAY;
+ mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
+ mSlots[found].mFence = Fence::NO_FENCE;
+
+ returnFlags |= BUFFER_NEEDS_REALLOCATION;
+ }
+
+ if (CC_UNLIKELY(mSlots[found].mFence == NULL)) {
+ BQ_LOGE("dequeueBuffer: about to return a NULL fence - "
+ "slot=%d w=%d h=%d format=%u",
+ found, buffer->width, buffer->height, buffer->format);
+ }
+
+ eglDisplay = mSlots[found].mEglDisplay;
+ eglFence = mSlots[found].mEglFence;
+ *outFence = mSlots[found].mFence;
+ mSlots[found].mEglFence = EGL_NO_SYNC_KHR;
+ mSlots[found].mFence = Fence::NO_FENCE;
+ } // Autolock scope
+
+ if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
+ status_t error;
+ sp<GraphicBuffer> graphicBuffer(mCore->mAllocator->createGraphicBuffer(
+ width, height, format, usage, &error));
+ if (graphicBuffer == NULL) {
+ BQ_LOGE("dequeueBuffer: createGraphicBuffer failed");
+ return error;
+ }
+
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("dequeueBuffer: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ mSlots[*outSlot].mFrameNumber = UINT32_MAX;
+ mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
+ } // Autolock scope
+ }
+
+ if (attachedByConsumer) {
+ returnFlags |= BUFFER_NEEDS_REALLOCATION;
+ }
+
+ if (eglFence != EGL_NO_SYNC_KHR) {
+ EGLint result = eglClientWaitSyncKHR(eglDisplay, eglFence, 0,
+ 1000000000);
+ // If something goes wrong, log the error, but return the buffer without
+ // synchronizing access to it. It's too late at this point to abort the
+ // dequeue operation.
+ if (result == EGL_FALSE) {
+ BQ_LOGE("dequeueBuffer: error %#x waiting for fence",
+ eglGetError());
+ } else if (result == EGL_TIMEOUT_EXPIRED_KHR) {
+ BQ_LOGE("dequeueBuffer: timeout waiting for fence");
+ }
+ eglDestroySyncKHR(eglDisplay, eglFence);
+ }
+
+ BQ_LOGV("dequeueBuffer: returning slot=%d/%llu buf=%p flags=%#x", *outSlot,
+ mSlots[*outSlot].mFrameNumber,
+ mSlots[*outSlot].mGraphicBuffer->handle, returnFlags);
+
+ return returnFlags;
+}
+
+status_t BufferQueueProducer::detachBuffer(int slot) {
+ ATRACE_CALL();
+ ATRACE_BUFFER_INDEX(slot);
+ BQ_LOGV("detachBuffer(P): slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("detachBuffer(P): BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ BQ_LOGE("detachBuffer(P): slot index %d out of range [0, %d)",
+ slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+ BQ_LOGE("detachBuffer(P): slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ } else if (!mSlots[slot].mRequestBufferCalled) {
+ BQ_LOGE("detachBuffer(P): buffer in slot %d has not been requested",
+ slot);
+ return BAD_VALUE;
+ }
+
+ mCore->freeBufferLocked(slot);
+ mCore->mDequeueCondition.broadcast();
+
+ return NO_ERROR;
+}
+
+status_t BufferQueueProducer::detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence) {
+ ATRACE_CALL();
+
+ if (outBuffer == NULL) {
+ BQ_LOGE("detachNextBuffer: outBuffer must not be NULL");
+ return BAD_VALUE;
+ } else if (outFence == NULL) {
+ BQ_LOGE("detachNextBuffer: outFence must not be NULL");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("detachNextBuffer: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ // Find the oldest valid slot
+ int found = BufferQueueCore::INVALID_BUFFER_SLOT;
+ for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
+ if (mSlots[s].mBufferState == BufferSlot::FREE &&
+ mSlots[s].mGraphicBuffer != NULL) {
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT ||
+ mSlots[s].mFrameNumber < mSlots[found].mFrameNumber) {
+ found = s;
+ }
+ }
+ }
+
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+ return NO_MEMORY;
+ }
+
+ BQ_LOGV("detachNextBuffer detached slot %d", found);
+
+ *outBuffer = mSlots[found].mGraphicBuffer;
+ *outFence = mSlots[found].mFence;
+ mCore->freeBufferLocked(found);
+
+ return NO_ERROR;
+}
+
+status_t BufferQueueProducer::attachBuffer(int* outSlot,
+ const sp<android::GraphicBuffer>& buffer) {
+ ATRACE_CALL();
+
+ if (outSlot == NULL) {
+ BQ_LOGE("attachBuffer(P): outSlot must not be NULL");
+ return BAD_VALUE;
+ } else if (buffer == NULL) {
+ BQ_LOGE("attachBuffer(P): cannot attach NULL buffer");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mCore->mMutex);
+
+ status_t returnFlags = NO_ERROR;
+ int found;
+ // TODO: Should we provide an async flag to attachBuffer? It seems
+ // unlikely that buffers which we are attaching to a BufferQueue will
+ // be asynchronous (droppable), but it may not be impossible.
+ status_t status = waitForFreeSlotThenRelock("attachBuffer(P)", false,
+ &found, &returnFlags);
+ if (status != NO_ERROR) {
+ return status;
+ }
+
+ // This should not happen
+ if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
+ BQ_LOGE("attachBuffer(P): no available buffer slots");
+ return -EBUSY;
+ }
+
+ *outSlot = found;
+ ATRACE_BUFFER_INDEX(*outSlot);
+ BQ_LOGV("attachBuffer(P): returning slot %d flags=%#x",
+ *outSlot, returnFlags);
+
+ mSlots[*outSlot].mGraphicBuffer = buffer;
+ mSlots[*outSlot].mBufferState = BufferSlot::DEQUEUED;
+ mSlots[*outSlot].mEglFence = EGL_NO_SYNC_KHR;
+ mSlots[*outSlot].mFence = Fence::NO_FENCE;
+ mSlots[*outSlot].mRequestBufferCalled = true;
+
+ return returnFlags;
+}
+
+status_t BufferQueueProducer::queueBuffer(int slot,
+ const QueueBufferInput &input, QueueBufferOutput *output) {
+ ATRACE_CALL();
+ ATRACE_BUFFER_INDEX(slot);
+
+ int64_t timestamp;
+ bool isAutoTimestamp;
+ Rect crop;
+ int scalingMode;
+ uint32_t transform;
+ bool async;
+ sp<Fence> fence;
+ input.deflate(&timestamp, &isAutoTimestamp, &crop, &scalingMode, &transform,
+ &async, &fence);
+
+ if (fence == NULL) {
+ BQ_LOGE("queueBuffer: fence is NULL");
+ return BAD_VALUE;
+ }
+
+ switch (scalingMode) {
+ case NATIVE_WINDOW_SCALING_MODE_FREEZE:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW:
+ case NATIVE_WINDOW_SCALING_MODE_SCALE_CROP:
+ case NATIVE_WINDOW_SCALING_MODE_NO_SCALE_CROP:
+ break;
+ default:
+ BQ_LOGE("queueBuffer: unknown scaling mode %d", scalingMode);
+ return BAD_VALUE;
+ }
+
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("queueBuffer: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ const int maxBufferCount = mCore->getMaxBufferCountLocked(async);
+ if (async && mCore->mOverrideMaxBufferCount) {
+ // FIXME: Some drivers are manually setting the buffer count
+ // (which they shouldn't), so we do this extra test here to
+ // handle that case. This is TEMPORARY until we get this fixed.
+ if (mCore->mOverrideMaxBufferCount < maxBufferCount) {
+ BQ_LOGE("queueBuffer: async mode is invalid with "
+ "buffer count override");
+ return BAD_VALUE;
+ }
+ }
+
+ if (slot < 0 || slot >= maxBufferCount) {
+ BQ_LOGE("queueBuffer: slot index %d out of range [0, %d)",
+ slot, maxBufferCount);
+ return BAD_VALUE;
+ } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+ BQ_LOGE("queueBuffer: slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return BAD_VALUE;
+ } else if (!mSlots[slot].mRequestBufferCalled) {
+ BQ_LOGE("queueBuffer: slot %d was queued without requesting "
+ "a buffer", slot);
+ return BAD_VALUE;
+ }
+
+ BQ_LOGV("queueBuffer: slot=%d/%llu time=%llu crop=[%d,%d,%d,%d] "
+ "transform=%#x scale=%s",
+ slot, mCore->mFrameCounter + 1, timestamp,
+ crop.left, crop.top, crop.right, crop.bottom,
+ transform, BufferItem::scalingModeName(scalingMode));
+
+ const sp<GraphicBuffer>& graphicBuffer(mSlots[slot].mGraphicBuffer);
+ Rect bufferRect(graphicBuffer->getWidth(), graphicBuffer->getHeight());
+ Rect croppedRect;
+ crop.intersect(bufferRect, &croppedRect);
+ if (croppedRect != crop) {
+ BQ_LOGE("queueBuffer: crop rect is not contained within the "
+ "buffer in slot %d", slot);
+ return BAD_VALUE;
+ }
+
+ mSlots[slot].mFence = fence;
+ mSlots[slot].mBufferState = BufferSlot::QUEUED;
+ ++mCore->mFrameCounter;
+ mSlots[slot].mFrameNumber = mCore->mFrameCounter;
+
+ BufferItem item;
+ item.mAcquireCalled = mSlots[slot].mAcquireCalled;
+ item.mGraphicBuffer = mSlots[slot].mGraphicBuffer;
+ item.mCrop = crop;
+ item.mTransform = transform & ~NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+ item.mTransformToDisplayInverse =
+ bool(transform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY);
+ item.mScalingMode = scalingMode;
+ item.mTimestamp = timestamp;
+ item.mIsAutoTimestamp = isAutoTimestamp;
+ item.mFrameNumber = mCore->mFrameCounter;
+ item.mSlot = slot;
+ item.mFence = fence;
+ item.mIsDroppable = mCore->mDequeueBufferCannotBlock || async;
+
+ if (mCore->mQueue.empty()) {
+ // When the queue is empty, we can ignore mDequeueBufferCannotBlock
+ // and simply queue this buffer
+ mCore->mQueue.push_back(item);
+ listener = mCore->mConsumerListener;
+ } else {
+ // When the queue is not empty, we need to look at the front buffer
+ // state to see if we need to replace it
+ BufferQueueCore::Fifo::iterator front(mCore->mQueue.begin());
+ if (front->mIsDroppable) {
+ // If the front queued buffer is still being tracked, we first
+ // mark it as freed
+ if (mCore->stillTracking(front)) {
+ mSlots[front->mSlot].mBufferState = BufferSlot::FREE;
+ // Reset the frame number of the freed buffer so that it is
+ // the first in line to be dequeued again
+ mSlots[front->mSlot].mFrameNumber = 0;
+ }
+ // Overwrite the droppable buffer with the incoming one
+ *front = item;
+ } else {
+ mCore->mQueue.push_back(item);
+ listener = mCore->mConsumerListener;
+ }
+ }
+
+ mCore->mBufferHasBeenQueued = true;
+ mCore->mDequeueCondition.broadcast();
+
+ output->inflate(mCore->mDefaultWidth, mCore->mDefaultHeight,
+ mCore->mTransformHint, mCore->mQueue.size());
+
+ ATRACE_INT(mCore->mConsumerName.string(), mCore->mQueue.size());
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onFrameAvailable();
+ }
+
+ return NO_ERROR;
+}
+
+void BufferQueueProducer::cancelBuffer(int slot, const sp<Fence>& fence) {
+ ATRACE_CALL();
+ BQ_LOGV("cancelBuffer: slot %d", slot);
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("cancelBuffer: BufferQueue has been abandoned");
+ return;
+ }
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)",
+ slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return;
+ } else if (mSlots[slot].mBufferState != BufferSlot::DEQUEUED) {
+ BQ_LOGE("cancelBuffer: slot %d is not owned by the producer "
+ "(state = %d)", slot, mSlots[slot].mBufferState);
+ return;
+ } else if (fence == NULL) {
+ BQ_LOGE("cancelBuffer: fence is NULL");
+ return;
+ }
+
+ mSlots[slot].mBufferState = BufferSlot::FREE;
+ mSlots[slot].mFrameNumber = 0;
+ mSlots[slot].mFence = fence;
+ mCore->mDequeueCondition.broadcast();
+}
+
+int BufferQueueProducer::query(int what, int *outValue) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (outValue == NULL) {
+ BQ_LOGE("query: outValue was NULL");
+ return BAD_VALUE;
+ }
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("query: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ int value;
+ switch (what) {
+ case NATIVE_WINDOW_WIDTH:
+ value = mCore->mDefaultWidth;
+ break;
+ case NATIVE_WINDOW_HEIGHT:
+ value = mCore->mDefaultHeight;
+ break;
+ case NATIVE_WINDOW_FORMAT:
+ value = mCore->mDefaultBufferFormat;
+ break;
+ case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
+ value = mCore->getMinUndequeuedBufferCountLocked(false);
+ break;
+ case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND:
+ value = (mCore->mQueue.size() > 1);
+ break;
+ case NATIVE_WINDOW_CONSUMER_USAGE_BITS:
+ value = mCore->mConsumerUsageBits;
+ break;
+ default:
+ return BAD_VALUE;
+ }
+
+ BQ_LOGV("query: %d? %d", what, value);
+ *outValue = value;
+ return NO_ERROR;
+}
+
+status_t BufferQueueProducer::connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp, QueueBufferOutput *output) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mCore->mMutex);
+ mConsumerName = mCore->mConsumerName;
+ BQ_LOGV("connect(P): api=%d producerControlledByApp=%s", api,
+ producerControlledByApp ? "true" : "false");
+
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("connect(P): BufferQueue has been abandoned");
+ return NO_INIT;
+ }
+
+ if (mCore->mConsumerListener == NULL) {
+ BQ_LOGE("connect(P): BufferQueue has no consumer");
+ return NO_INIT;
+ }
+
+ if (output == NULL) {
+ BQ_LOGE("connect(P): output was NULL");
+ return BAD_VALUE;
+ }
+
+ if (mCore->mConnectedApi != BufferQueueCore::NO_CONNECTED_API) {
+ BQ_LOGE("connect(P): already connected (cur=%d req=%d)",
+ mCore->mConnectedApi, api);
+ return BAD_VALUE;
+ }
+
+ int status = NO_ERROR;
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ mCore->mConnectedApi = api;
+ output->inflate(mCore->mDefaultWidth, mCore->mDefaultHeight,
+ mCore->mTransformHint, mCore->mQueue.size());
+
+ // Set up a death notification so that we can disconnect
+ // automatically if the remote producer dies
+ if (listener != NULL &&
+ listener->asBinder()->remoteBinder() != NULL) {
+ status = listener->asBinder()->linkToDeath(
+ static_cast<IBinder::DeathRecipient*>(this));
+ if (status != NO_ERROR) {
+ BQ_LOGE("connect(P): linkToDeath failed: %s (%d)",
+ strerror(-status), status);
+ }
+ }
+ mCore->mConnectedProducerListener = listener;
+ break;
+ default:
+ BQ_LOGE("connect(P): unknown API %d", api);
+ status = BAD_VALUE;
+ break;
+ }
+
+ mCore->mBufferHasBeenQueued = false;
+ mCore->mDequeueBufferCannotBlock =
+ mCore->mConsumerControlledByApp && producerControlledByApp;
+
+ return status;
+}
+
+status_t BufferQueueProducer::disconnect(int api) {
+ ATRACE_CALL();
+ BQ_LOGV("disconnect(P): api %d", api);
+
+ int status = NO_ERROR;
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock lock(mCore->mMutex);
+
+ if (mCore->mIsAbandoned) {
+ // It's not really an error to disconnect after the surface has
+ // been abandoned; it should just be a no-op.
+ return NO_ERROR;
+ }
+
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ case NATIVE_WINDOW_API_CPU:
+ case NATIVE_WINDOW_API_MEDIA:
+ case NATIVE_WINDOW_API_CAMERA:
+ if (mCore->mConnectedApi == api) {
+ mCore->freeAllBuffersLocked();
+
+ // Remove our death notification callback if we have one
+ if (mCore->mConnectedProducerListener != NULL) {
+ sp<IBinder> token =
+ mCore->mConnectedProducerListener->asBinder();
+ // This can fail if we're here because of the death
+ // notification, but we just ignore it
+ token->unlinkToDeath(
+ static_cast<IBinder::DeathRecipient*>(this));
+ }
+ mCore->mConnectedProducerListener = NULL;
+ mCore->mConnectedApi = BufferQueueCore::NO_CONNECTED_API;
+ mCore->mSidebandStream.clear();
+ mCore->mDequeueCondition.broadcast();
+ listener = mCore->mConsumerListener;
+ } else {
+ BQ_LOGE("disconnect(P): connected to another API "
+ "(cur=%d req=%d)", mCore->mConnectedApi, api);
+ status = BAD_VALUE;
+ }
+ break;
+ default:
+ BQ_LOGE("disconnect(P): unknown API %d", api);
+ status = BAD_VALUE;
+ break;
+ }
+ } // Autolock scope
+
+ // Call back without lock held
+ if (listener != NULL) {
+ listener->onBuffersReleased();
+ }
+
+ return status;
+}
+
+status_t BufferQueueProducer::setSidebandStream(const sp<NativeHandle>& stream) {
+ sp<IConsumerListener> listener;
+ { // Autolock scope
+ Mutex::Autolock _l(mCore->mMutex);
+ mCore->mSidebandStream = stream;
+ listener = mCore->mConsumerListener;
+ } // Autolock scope
+
+ if (listener != NULL) {
+ listener->onSidebandStreamChanged();
+ }
+ return NO_ERROR;
+}
+
+void BufferQueueProducer::binderDied(const wp<android::IBinder>& /* who */) {
+ // If we're here, it means that a producer we were connected to died.
+ // We're guaranteed that we are still connected to it because we remove
+ // this callback upon disconnect. It's therefore safe to read mConnectedApi
+ // without synchronization here.
+ int api = mCore->mConnectedApi;
+ disconnect(api);
+}
+
+} // namespace android
diff --git a/libs/gui/BufferSlot.cpp b/libs/gui/BufferSlot.cpp
new file mode 100644
index 0000000..b8877fe
--- /dev/null
+++ b/libs/gui/BufferSlot.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 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 <gui/BufferSlot.h>
+
+namespace android {
+
+const char* BufferSlot::bufferStateName(BufferState state) {
+ switch (state) {
+ case BufferSlot::DEQUEUED: return "DEQUEUED";
+ case BufferSlot::QUEUED: return "QUEUED";
+ case BufferSlot::FREE: return "FREE";
+ case BufferSlot::ACQUIRED: return "ACQUIRED";
+ default: return "Unknown";
+ }
+}
+
+} // namespace android
diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp
index c4ec857..f1b8fa8 100644
--- a/libs/gui/ConsumerBase.cpp
+++ b/libs/gui/ConsumerBase.cpp
@@ -85,7 +85,7 @@ ConsumerBase::~ConsumerBase() {
"consumer is not abandoned!", mName.string());
}
-void ConsumerBase::onLastStrongRef(const void* id) {
+void ConsumerBase::onLastStrongRef(const void* id __attribute__((unused))) {
abandon();
}
@@ -121,15 +121,18 @@ void ConsumerBase::onBuffersReleased() {
return;
}
- uint32_t mask = 0;
+ uint64_t mask = 0;
mConsumer->getReleasedBuffers(&mask);
for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
- if (mask & (1 << i)) {
+ if (mask & (1ULL << i)) {
freeBufferLocked(i);
}
}
}
+void ConsumerBase::onSidebandStreamChanged() {
+}
+
void ConsumerBase::abandon() {
CB_LOGV("abandon");
Mutex::Autolock lock(mMutex);
@@ -243,7 +246,7 @@ status_t ConsumerBase::releaseBufferLocked(
slot, mSlots[slot].mFrameNumber);
status_t err = mConsumer->releaseBuffer(slot, mSlots[slot].mFrameNumber,
display, eglFence, mSlots[slot].mFence);
- if (err == BufferQueue::STALE_BUFFER_SLOT) {
+ if (err == IGraphicBufferConsumer::STALE_BUFFER_SLOT) {
freeBufferLocked(slot);
}
diff --git a/libs/gui/IConsumerListener.cpp b/libs/gui/IConsumerListener.cpp
index 5304462..4ccf0ac 100644
--- a/libs/gui/IConsumerListener.cpp
+++ b/libs/gui/IConsumerListener.cpp
@@ -28,7 +28,8 @@ namespace android {
enum {
ON_FRAME_AVAILABLE = IBinder::FIRST_CALL_TRANSACTION,
- ON_BUFFER_RELEASED
+ ON_BUFFER_RELEASED,
+ ON_SIDEBAND_STREAM_CHANGED,
};
class BpConsumerListener : public BpInterface<IConsumerListener>
@@ -49,6 +50,12 @@ public:
data.writeInterfaceToken(IConsumerListener::getInterfaceDescriptor());
remote()->transact(ON_BUFFER_RELEASED, data, &reply, IBinder::FLAG_ONEWAY);
}
+
+ virtual void onSidebandStreamChanged() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IConsumerListener::getInterfaceDescriptor());
+ remote()->transact(ON_SIDEBAND_STREAM_CHANGED, data, &reply, IBinder::FLAG_ONEWAY);
+ }
};
IMPLEMENT_META_INTERFACE(ConsumerListener, "android.gui.IConsumerListener");
@@ -67,6 +74,10 @@ status_t BnConsumerListener::onTransact(
CHECK_INTERFACE(IConsumerListener, data, reply);
onBuffersReleased();
return NO_ERROR;
+ case ON_SIDEBAND_STREAM_CHANGED:
+ CHECK_INTERFACE(IConsumerListener, data, reply);
+ onSidebandStreamChanged();
+ return NO_ERROR;
}
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/IGraphicBufferConsumer.cpp b/libs/gui/IGraphicBufferConsumer.cpp
index 9574b61..f6d087d 100644
--- a/libs/gui/IGraphicBufferConsumer.cpp
+++ b/libs/gui/IGraphicBufferConsumer.cpp
@@ -14,16 +14,11 @@
* limitations under the License.
*/
-#define EGL_EGLEXT_PROTOTYPES
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
-
#include <stdint.h>
#include <sys/types.h>
#include <utils/Errors.h>
+#include <utils/NativeHandle.h>
#include <binder/Parcel.h>
#include <binder/IInterface.h>
@@ -70,11 +65,11 @@ size_t IGraphicBufferConsumer::BufferItem::getFlattenedSize() const {
size_t c = 0;
if (mGraphicBuffer != 0) {
c += mGraphicBuffer->getFlattenedSize();
- FlattenableUtils::align<4>(c);
+ c = FlattenableUtils::align<4>(c);
}
if (mFence != 0) {
c += mFence->getFlattenedSize();
- FlattenableUtils::align<4>(c);
+ c = FlattenableUtils::align<4>(c);
}
return sizeof(int32_t) + c + getPodSize();
}
@@ -90,11 +85,21 @@ size_t IGraphicBufferConsumer::BufferItem::getFdCount() const {
return c;
}
+static void writeBoolAsInt(void*& buffer, size_t& size, bool b) {
+ FlattenableUtils::write(buffer, size, static_cast<int32_t>(b));
+}
+
+static bool readBoolFromInt(void const*& buffer, size_t& size) {
+ int32_t i;
+ FlattenableUtils::read(buffer, size, i);
+ return static_cast<bool>(i);
+}
+
status_t IGraphicBufferConsumer::BufferItem::flatten(
void*& buffer, size_t& size, int*& fds, size_t& count) const {
// make sure we have enough space
- if (count < BufferItem::getFlattenedSize()) {
+ if (size < BufferItem::getFlattenedSize()) {
return NO_MEMORY;
}
@@ -127,12 +132,12 @@ status_t IGraphicBufferConsumer::BufferItem::flatten(
FlattenableUtils::write(buffer, size, mTransform);
FlattenableUtils::write(buffer, size, mScalingMode);
FlattenableUtils::write(buffer, size, mTimestamp);
- FlattenableUtils::write(buffer, size, mIsAutoTimestamp);
+ writeBoolAsInt(buffer, size, mIsAutoTimestamp);
FlattenableUtils::write(buffer, size, mFrameNumber);
FlattenableUtils::write(buffer, size, mBuf);
- FlattenableUtils::write(buffer, size, mIsDroppable);
- FlattenableUtils::write(buffer, size, mAcquireCalled);
- FlattenableUtils::write(buffer, size, mTransformToDisplayInverse);
+ writeBoolAsInt(buffer, size, mIsDroppable);
+ writeBoolAsInt(buffer, size, mAcquireCalled);
+ writeBoolAsInt(buffer, size, mTransformToDisplayInverse);
return NO_ERROR;
}
@@ -169,12 +174,12 @@ status_t IGraphicBufferConsumer::BufferItem::unflatten(
FlattenableUtils::read(buffer, size, mTransform);
FlattenableUtils::read(buffer, size, mScalingMode);
FlattenableUtils::read(buffer, size, mTimestamp);
- FlattenableUtils::read(buffer, size, mIsAutoTimestamp);
+ mIsAutoTimestamp = readBoolFromInt(buffer, size);
FlattenableUtils::read(buffer, size, mFrameNumber);
FlattenableUtils::read(buffer, size, mBuf);
- FlattenableUtils::read(buffer, size, mIsDroppable);
- FlattenableUtils::read(buffer, size, mAcquireCalled);
- FlattenableUtils::read(buffer, size, mTransformToDisplayInverse);
+ mIsDroppable = readBoolFromInt(buffer, size);
+ mAcquireCalled = readBoolFromInt(buffer, size);
+ mTransformToDisplayInverse = readBoolFromInt(buffer, size);
return NO_ERROR;
}
@@ -183,6 +188,8 @@ status_t IGraphicBufferConsumer::BufferItem::unflatten(
enum {
ACQUIRE_BUFFER = IBinder::FIRST_CALL_TRANSACTION,
+ DETACH_BUFFER,
+ ATTACH_BUFFER,
RELEASE_BUFFER,
CONSUMER_CONNECT,
CONSUMER_DISCONNECT,
@@ -195,6 +202,7 @@ enum {
SET_DEFAULT_BUFFER_FORMAT,
SET_CONSUMER_USAGE_BITS,
SET_TRANSFORM_HINT,
+ GET_SIDEBAND_STREAM,
DUMP,
};
@@ -222,8 +230,33 @@ public:
return reply.readInt32();
}
+ virtual status_t detachBuffer(int slot) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor());
+ data.writeInt32(slot);
+ status_t result = remote()->transact(DETACH_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.readInt32();
+ return result;
+ }
+
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor());
+ data.write(*buffer.get());
+ status_t result = remote()->transact(ATTACH_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ *slot = reply.readInt32();
+ result = reply.readInt32();
+ return result;
+ }
+
virtual status_t releaseBuffer(int buf, uint64_t frameNumber,
- EGLDisplay display, EGLSyncKHR fence,
+ EGLDisplay display __attribute__((unused)), EGLSyncKHR fence __attribute__((unused)),
const sp<Fence>& releaseFence) {
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor());
@@ -259,14 +292,18 @@ public:
return reply.readInt32();
}
- virtual status_t getReleasedBuffers(uint32_t* slotMask) {
+ virtual status_t getReleasedBuffers(uint64_t* slotMask) {
Parcel data, reply;
+ if (slotMask == NULL) {
+ ALOGE("getReleasedBuffers: slotMask must not be NULL");
+ return BAD_VALUE;
+ }
data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor());
status_t result = remote()->transact(GET_RELEASED_BUFFERS, data, &reply);
if (result != NO_ERROR) {
return result;
}
- *slotMask = reply.readInt32();
+ *slotMask = reply.readInt64();
return reply.readInt32();
}
@@ -354,6 +391,20 @@ public:
return reply.readInt32();
}
+ virtual sp<NativeHandle> getSidebandStream() const {
+ Parcel data, reply;
+ status_t err;
+ data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor());
+ if ((err = remote()->transact(GET_SIDEBAND_STREAM, data, &reply)) != NO_ERROR) {
+ return NULL;
+ }
+ sp<NativeHandle> stream;
+ if (reply.readInt32()) {
+ stream = NativeHandle::create(reply.readNativeHandle(), true);
+ }
+ return stream;
+ }
+
virtual void dump(String8& result, const char* prefix) const {
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferConsumer::getInterfaceDescriptor());
@@ -382,6 +433,23 @@ status_t BnGraphicBufferConsumer::onTransact(
reply->writeInt32(result);
return NO_ERROR;
} break;
+ case DETACH_BUFFER: {
+ CHECK_INTERFACE(IGraphicBufferConsumer, data, reply);
+ int slot = data.readInt32();
+ int result = detachBuffer(slot);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case ATTACH_BUFFER: {
+ CHECK_INTERFACE(IGraphicBufferConsumer, data, reply);
+ sp<GraphicBuffer> buffer = new GraphicBuffer();
+ data.read(*buffer.get());
+ int slot;
+ int result = attachBuffer(&slot, buffer);
+ reply->writeInt32(slot);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
case RELEASE_BUFFER: {
CHECK_INTERFACE(IGraphicBufferConsumer, data, reply);
int buf = data.readInt32();
@@ -410,9 +478,9 @@ status_t BnGraphicBufferConsumer::onTransact(
} break;
case GET_RELEASED_BUFFERS: {
CHECK_INTERFACE(IGraphicBufferConsumer, data, reply);
- uint32_t slotMask;
+ uint64_t slotMask;
status_t result = getReleasedBuffers(&slotMask);
- reply->writeInt32(slotMask);
+ reply->writeInt64(slotMask);
reply->writeInt32(result);
return NO_ERROR;
} break;
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 0f461e5..aa6acb9 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -18,14 +18,16 @@
#include <sys/types.h>
#include <utils/Errors.h>
+#include <utils/NativeHandle.h>
#include <utils/RefBase.h>
-#include <utils/Vector.h>
#include <utils/Timers.h>
+#include <utils/Vector.h>
#include <binder/Parcel.h>
#include <binder/IInterface.h>
#include <gui/IGraphicBufferProducer.h>
+#include <gui/IProducerListener.h>
namespace android {
// ----------------------------------------------------------------------------
@@ -34,11 +36,15 @@ enum {
REQUEST_BUFFER = IBinder::FIRST_CALL_TRANSACTION,
SET_BUFFER_COUNT,
DEQUEUE_BUFFER,
+ DETACH_BUFFER,
+ DETACH_NEXT_BUFFER,
+ ATTACH_BUFFER,
QUEUE_BUFFER,
CANCEL_BUFFER,
QUERY,
CONNECT,
DISCONNECT,
+ SET_SIDEBAND_STREAM,
};
class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -106,6 +112,62 @@ public:
return result;
}
+ virtual status_t detachBuffer(int slot) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeInt32(slot);
+ status_t result = remote()->transact(DETACH_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.readInt32();
+ return result;
+ }
+
+ virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence) {
+ if (outBuffer == NULL) {
+ ALOGE("detachNextBuffer: outBuffer must not be NULL");
+ return BAD_VALUE;
+ } else if (outFence == NULL) {
+ ALOGE("detachNextBuffer: outFence must not be NULL");
+ return BAD_VALUE;
+ }
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ status_t result = remote()->transact(DETACH_NEXT_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ result = reply.readInt32();
+ if (result == NO_ERROR) {
+ bool nonNull = reply.readInt32();
+ if (nonNull) {
+ *outBuffer = new GraphicBuffer;
+ reply.read(**outBuffer);
+ }
+ nonNull = reply.readInt32();
+ if (nonNull) {
+ *outFence = new Fence;
+ reply.read(**outFence);
+ }
+ }
+ return result;
+ }
+
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.write(*buffer.get());
+ status_t result = remote()->transact(ATTACH_BUFFER, data, &reply);
+ if (result != NO_ERROR) {
+ return result;
+ }
+ *slot = reply.readInt32();
+ result = reply.readInt32();
+ return result;
+ }
+
virtual status_t queueBuffer(int buf,
const QueueBufferInput& input, QueueBufferOutput* output) {
Parcel data, reply;
@@ -142,11 +204,16 @@ public:
return result;
}
- virtual status_t connect(const sp<IBinder>& token,
+ virtual status_t connect(const sp<IProducerListener>& listener,
int api, bool producerControlledByApp, QueueBufferOutput* output) {
Parcel data, reply;
data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
- data.writeStrongBinder(token);
+ if (listener != NULL) {
+ data.writeInt32(1);
+ data.writeStrongBinder(listener->asBinder());
+ } else {
+ data.writeInt32(0);
+ }
data.writeInt32(api);
data.writeInt32(producerControlledByApp);
status_t result = remote()->transact(CONNECT, data, &reply);
@@ -169,6 +236,22 @@ public:
result = reply.readInt32();
return result;
}
+
+ virtual status_t setSidebandStream(const sp<NativeHandle>& stream) {
+ Parcel data, reply;
+ status_t result;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ if (stream.get()) {
+ data.writeInt32(true);
+ data.writeNativeHandle(stream->handle());
+ } else {
+ data.writeInt32(false);
+ }
+ if ((result = remote()->transact(SET_SIDEBAND_STREAM, data, &reply)) == NO_ERROR) {
+ result = reply.readInt32();
+ }
+ return result;
+ }
};
IMPLEMENT_META_INTERFACE(GraphicBufferProducer, "android.gui.IGraphicBufferProducer");
@@ -216,6 +299,41 @@ status_t BnGraphicBufferProducer::onTransact(
reply->writeInt32(result);
return NO_ERROR;
} break;
+ case DETACH_BUFFER: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ int slot = data.readInt32();
+ int result = detachBuffer(slot);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case DETACH_NEXT_BUFFER: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ sp<GraphicBuffer> buffer;
+ sp<Fence> fence;
+ int32_t result = detachNextBuffer(&buffer, &fence);
+ reply->writeInt32(result);
+ if (result == NO_ERROR) {
+ reply->writeInt32(buffer != NULL);
+ if (buffer != NULL) {
+ reply->write(*buffer);
+ }
+ reply->writeInt32(fence != NULL);
+ if (fence != NULL) {
+ reply->write(*fence);
+ }
+ }
+ return NO_ERROR;
+ } break;
+ case ATTACH_BUFFER: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ sp<GraphicBuffer> buffer = new GraphicBuffer();
+ data.read(*buffer.get());
+ int slot;
+ int result = attachBuffer(&slot, buffer);
+ reply->writeInt32(slot);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
case QUEUE_BUFFER: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
int buf = data.readInt32();
@@ -246,13 +364,16 @@ status_t BnGraphicBufferProducer::onTransact(
} break;
case CONNECT: {
CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
- sp<IBinder> token = data.readStrongBinder();
+ sp<IProducerListener> listener;
+ if (data.readInt32() == 1) {
+ listener = IProducerListener::asInterface(data.readStrongBinder());
+ }
int api = data.readInt32();
bool producerControlledByApp = data.readInt32();
QueueBufferOutput* const output =
reinterpret_cast<QueueBufferOutput *>(
reply->writeInplace(sizeof(QueueBufferOutput)));
- status_t res = connect(token, api, producerControlledByApp, output);
+ status_t res = connect(listener, api, producerControlledByApp, output);
reply->writeInt32(res);
return NO_ERROR;
} break;
@@ -263,6 +384,16 @@ status_t BnGraphicBufferProducer::onTransact(
reply->writeInt32(res);
return NO_ERROR;
} break;
+ case SET_SIDEBAND_STREAM: {
+ CHECK_INTERFACE(IGraphicBufferProducer, data, reply);
+ sp<NativeHandle> stream;
+ if (data.readInt32()) {
+ stream = NativeHandle::create(data.readNativeHandle(), true);
+ }
+ status_t result = setSidebandStream(stream);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
}
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/IProducerListener.cpp b/libs/gui/IProducerListener.cpp
new file mode 100644
index 0000000..efe4069
--- /dev/null
+++ b/libs/gui/IProducerListener.cpp
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2014 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 <binder/Parcel.h>
+
+#include <gui/IProducerListener.h>
+
+namespace android {
+
+enum {
+ ON_BUFFER_RELEASED = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpProducerListener : public BpInterface<IProducerListener>
+{
+public:
+ BpProducerListener(const sp<IBinder>& impl)
+ : BpInterface<IProducerListener>(impl) {}
+
+ virtual void onBufferReleased() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IProducerListener::getInterfaceDescriptor());
+ remote()->transact(ON_BUFFER_RELEASED, data, &reply, IBinder::FLAG_ONEWAY);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(ProducerListener, "android.gui.IProducerListener")
+
+status_t BnProducerListener::onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags) {
+ switch (code) {
+ case ON_BUFFER_RELEASED:
+ CHECK_INTERFACE(IProducerListener, data, reply);
+ onBufferReleased();
+ return NO_ERROR;
+ }
+ return BBinder::onTransact(code, data, reply, flags);
+}
+
+} // namespace android
diff --git a/libs/gui/ISensorEventConnection.cpp b/libs/gui/ISensorEventConnection.cpp
index 28fcb53..8f88141 100644
--- a/libs/gui/ISensorEventConnection.cpp
+++ b/libs/gui/ISensorEventConnection.cpp
@@ -34,7 +34,8 @@ enum {
GET_SENSOR_CHANNEL = IBinder::FIRST_CALL_TRANSACTION,
ENABLE_DISABLE,
SET_EVENT_RATE,
- FLUSH_SENSOR
+ FLUSH_SENSOR,
+ DECREASE_WAKE_LOCK_REFCOUNT
};
class BpSensorEventConnection : public BpInterface<ISensorEventConnection>
@@ -83,6 +84,13 @@ public:
remote()->transact(FLUSH_SENSOR, data, &reply);
return reply.readInt32();
}
+
+ virtual void decreaseWakeLockRefCount() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISensorEventConnection::getInterfaceDescriptor());
+ remote()->transact(DECREASE_WAKE_LOCK_REFCOUNT, data, &reply, IBinder::FLAG_ONEWAY);
+ return;
+ }
};
IMPLEMENT_META_INTERFACE(SensorEventConnection, "android.gui.SensorEventConnection");
@@ -125,6 +133,11 @@ status_t BnSensorEventConnection::onTransact(
reply->writeInt32(result);
return NO_ERROR;
} break;
+ case DECREASE_WAKE_LOCK_REFCOUNT: {
+ CHECK_INTERFACE(ISensorEventConnection, data, reply);
+ decreaseWakeLockRefCount();
+ return NO_ERROR;
+ } break;
}
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index aab0604..c067244 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -105,7 +105,8 @@ public:
virtual status_t captureScreen(const sp<IBinder>& display,
const sp<IGraphicBufferProducer>& producer,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ)
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform)
{
Parcel data, reply;
data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
@@ -115,6 +116,7 @@ public:
data.writeInt32(reqHeight);
data.writeInt32(minLayerZ);
data.writeInt32(maxLayerZ);
+ data.writeInt32(static_cast<int32_t>(useIdentityTransform));
remote()->transact(BnSurfaceComposer::CAPTURE_SCREEN, data, &reply);
return reply.readInt32();
}
@@ -227,6 +229,21 @@ public:
memcpy(info, reply.readInplace(sizeof(DisplayInfo)), sizeof(DisplayInfo));
return reply.readInt32();
}
+
+ virtual status_t clearAnimationFrameStats() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+ remote()->transact(BnSurfaceComposer::CLEAR_ANIMATION_FRAME_STATS, data, &reply);
+ return reply.readInt32();
+ }
+
+ virtual status_t getAnimationFrameStats(FrameStats* outStats) const {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
+ remote()->transact(BnSurfaceComposer::GET_ANIMATION_FRAME_STATS, data, &reply);
+ reply.read(*outStats);
+ return reply.readInt32();
+ }
};
IMPLEMENT_META_INTERFACE(SurfaceComposer, "android.ui.ISurfaceComposer");
@@ -285,8 +302,11 @@ status_t BnSurfaceComposer::onTransact(
uint32_t reqHeight = data.readInt32();
uint32_t minLayerZ = data.readInt32();
uint32_t maxLayerZ = data.readInt32();
+ bool useIdentityTransform = static_cast<bool>(data.readInt32());
+
status_t res = captureScreen(display, producer,
- reqWidth, reqHeight, minLayerZ, maxLayerZ);
+ reqWidth, reqHeight, minLayerZ, maxLayerZ,
+ useIdentityTransform);
reply->writeInt32(res);
return NO_ERROR;
}
@@ -346,6 +366,20 @@ status_t BnSurfaceComposer::onTransact(
reply->writeInt32(result);
return NO_ERROR;
}
+ case CLEAR_ANIMATION_FRAME_STATS: {
+ CHECK_INTERFACE(ISurfaceComposer, data, reply);
+ status_t result = clearAnimationFrameStats();
+ reply->writeInt32(result);
+ return NO_ERROR;
+ }
+ case GET_ANIMATION_FRAME_STATS: {
+ CHECK_INTERFACE(ISurfaceComposer, data, reply);
+ FrameStats stats;
+ status_t result = getAnimationFrameStats(&stats);
+ reply->write(stats);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ }
default: {
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/ISurfaceComposerClient.cpp b/libs/gui/ISurfaceComposerClient.cpp
index 1adc134..3da6423 100644
--- a/libs/gui/ISurfaceComposerClient.cpp
+++ b/libs/gui/ISurfaceComposerClient.cpp
@@ -39,7 +39,9 @@ namespace android {
enum {
CREATE_SURFACE = IBinder::FIRST_CALL_TRANSACTION,
- DESTROY_SURFACE
+ DESTROY_SURFACE,
+ CLEAR_LAYER_FRAME_STATS,
+ GET_LAYER_FRAME_STATS
};
class BpSurfaceComposerClient : public BpInterface<ISurfaceComposerClient>
@@ -73,6 +75,23 @@ public:
remote()->transact(DESTROY_SURFACE, data, &reply);
return reply.readInt32();
}
+
+ virtual status_t clearLayerFrameStats(const sp<IBinder>& handle) const {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISurfaceComposerClient::getInterfaceDescriptor());
+ data.writeStrongBinder(handle);
+ remote()->transact(CLEAR_LAYER_FRAME_STATS, data, &reply);
+ return reply.readInt32();
+ }
+
+ virtual status_t getLayerFrameStats(const sp<IBinder>& handle, FrameStats* outStats) const {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISurfaceComposerClient::getInterfaceDescriptor());
+ data.writeStrongBinder(handle);
+ remote()->transact(GET_LAYER_FRAME_STATS, data, &reply);
+ reply.read(*outStats);
+ return reply.readInt32();
+ }
};
IMPLEMENT_META_INTERFACE(SurfaceComposerClient, "android.ui.ISurfaceComposerClient");
@@ -101,7 +120,23 @@ status_t BnSurfaceComposerClient::onTransact(
} break;
case DESTROY_SURFACE: {
CHECK_INTERFACE(ISurfaceComposerClient, data, reply);
- reply->writeInt32( destroySurface( data.readStrongBinder() ) );
+ reply->writeInt32(destroySurface( data.readStrongBinder() ) );
+ return NO_ERROR;
+ } break;
+ case CLEAR_LAYER_FRAME_STATS: {
+ CHECK_INTERFACE(ISurfaceComposerClient, data, reply);
+ sp<IBinder> handle = data.readStrongBinder();
+ status_t result = clearLayerFrameStats(handle);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ } break;
+ case GET_LAYER_FRAME_STATS: {
+ CHECK_INTERFACE(ISurfaceComposerClient, data, reply);
+ sp<IBinder> handle = data.readStrongBinder();
+ FrameStats stats;
+ status_t result = getLayerFrameStats(handle, &stats);
+ reply->write(stats);
+ reply->writeInt32(result);
return NO_ERROR;
} break;
default:
diff --git a/libs/gui/Sensor.cpp b/libs/gui/Sensor.cpp
index 6f1a3f2..b363411 100644
--- a/libs/gui/Sensor.cpp
+++ b/libs/gui/Sensor.cpp
@@ -32,7 +32,8 @@ namespace android {
Sensor::Sensor()
: mHandle(0), mType(0),
mMinValue(0), mMaxValue(0), mResolution(0),
- mPower(0), mMinDelay(0), mFifoReservedEventCount(0), mFifoMaxEventCount(0)
+ mPower(0), mMinDelay(0), mFifoReservedEventCount(0), mFifoMaxEventCount(0),
+ mWakeUpSensor(false)
{
}
@@ -48,6 +49,7 @@ Sensor::Sensor(struct sensor_t const* hwSensor, int halVersion)
mResolution = hwSensor->resolution;
mPower = hwSensor->power;
mMinDelay = hwSensor->minDelay;
+ mWakeUpSensor = false;
// Set fifo event count zero for older devices which do not support batching. Fused
// sensors also have their fifo counts set to zero.
@@ -104,6 +106,7 @@ Sensor::Sensor(struct sensor_t const* hwSensor, int halVersion)
break;
case SENSOR_TYPE_PROXIMITY:
mStringType = SENSOR_STRING_TYPE_PROXIMITY;
+ mWakeUpSensor = true;
break;
case SENSOR_TYPE_RELATIVE_HUMIDITY:
mStringType = SENSOR_STRING_TYPE_RELATIVE_HUMIDITY;
@@ -113,6 +116,7 @@ Sensor::Sensor(struct sensor_t const* hwSensor, int halVersion)
break;
case SENSOR_TYPE_SIGNIFICANT_MOTION:
mStringType = SENSOR_STRING_TYPE_SIGNIFICANT_MOTION;
+ mWakeUpSensor = true;
break;
case SENSOR_TYPE_STEP_COUNTER:
mStringType = SENSOR_STRING_TYPE_STEP_COUNTER;
@@ -123,14 +127,93 @@ Sensor::Sensor(struct sensor_t const* hwSensor, int halVersion)
case SENSOR_TYPE_TEMPERATURE:
mStringType = SENSOR_STRING_TYPE_TEMPERATURE;
break;
+ case SENSOR_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR:
+ mStringType = SENSOR_STRING_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR;
+ break;
+ case SENSOR_TYPE_WAKE_UP_ACCELEROMETER:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_ACCELEROMETER;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_MAGNETIC_FIELD;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_ORIENTATION:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_ORIENTATION;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_GYROSCOPE:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_GYROSCOPE;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_LIGHT:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_LIGHT;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_PRESSURE:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_PRESSURE;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_GRAVITY:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_GRAVITY;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_LINEAR_ACCELERATION:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_LINEAR_ACCELERATION;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_ROTATION_VECTOR:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_ROTATION_VECTOR;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_RELATIVE_HUMIDITY:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_RELATIVE_HUMIDITY;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_AMBIENT_TEMPERATURE:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_AMBIENT_TEMPERATURE;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_GAME_ROTATION_VECTOR:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_GAME_ROTATION_VECTOR;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_STEP_DETECTOR:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_STEP_DETECTOR;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_STEP_COUNTER:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_STEP_COUNTER;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR;
+ mWakeUpSensor = true;
+ break;
+ case SENSOR_TYPE_WAKE_UP_HEART_RATE:
+ mStringType = SENSOR_STRING_TYPE_WAKE_UP_HEART_RATE;
+ mRequiredPermission = SENSOR_PERMISSION_BODY_SENSORS;
+ mWakeUpSensor = true;
+ break;
default:
- // Only pipe the stringType and requiredPermission for custom sensors.
+ // Only pipe the stringType, requiredPermission and flags for custom sensors.
if (halVersion >= SENSORS_DEVICE_API_VERSION_1_2 && hwSensor->stringType) {
mStringType = hwSensor->stringType;
}
if (halVersion >= SENSORS_DEVICE_API_VERSION_1_2 && hwSensor->requiredPermission) {
mRequiredPermission = hwSensor->requiredPermission;
}
+ if (halVersion >= SENSORS_DEVICE_API_VERSION_1_3) {
+ mWakeUpSensor = hwSensor->flags & SENSOR_FLAG_WAKE_UP;
+ }
break;
}
}
@@ -199,6 +282,10 @@ const String8& Sensor::getRequiredPermission() const {
return mRequiredPermission;
}
+bool Sensor::isWakeUpSensor() const {
+ return mWakeUpSensor;
+}
+
size_t Sensor::getFlattenedSize() const
{
size_t fixedSize =
diff --git a/libs/gui/SensorEventQueue.cpp b/libs/gui/SensorEventQueue.cpp
index c365671..c2eaf4e 100644
--- a/libs/gui/SensorEventQueue.cpp
+++ b/libs/gui/SensorEventQueue.cpp
@@ -144,6 +144,15 @@ status_t SensorEventQueue::setEventRate(Sensor const* sensor, nsecs_t ns) const
return mSensorEventConnection->setEventRate(sensor->getHandle(), ns);
}
+void SensorEventQueue::sendAck(const ASensorEvent* events, int count) {
+ for (int i = 0; i < count; ++i) {
+ if (events[i].flags & WAKE_UP_SENSOR_EVENT_NEEDS_ACK) {
+ mSensorEventConnection->decreaseWakeLockRefCount();
+ }
+ }
+ return;
+}
+
// ----------------------------------------------------------------------------
}; // namespace android
diff --git a/libs/gui/StreamSplitter.cpp b/libs/gui/StreamSplitter.cpp
new file mode 100644
index 0000000..83e08fb
--- /dev/null
+++ b/libs/gui/StreamSplitter.cpp
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2014 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 "StreamSplitter"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+//#define LOG_NDEBUG 0
+
+#include <gui/IGraphicBufferConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/StreamSplitter.h>
+
+#include <ui/GraphicBuffer.h>
+
+#include <binder/ProcessState.h>
+
+#include <utils/Trace.h>
+
+namespace android {
+
+status_t StreamSplitter::createSplitter(
+ const sp<IGraphicBufferConsumer>& inputQueue,
+ sp<StreamSplitter>* outSplitter) {
+ if (inputQueue == NULL) {
+ ALOGE("createSplitter: inputQueue must not be NULL");
+ return BAD_VALUE;
+ }
+ if (outSplitter == NULL) {
+ ALOGE("createSplitter: outSplitter must not be NULL");
+ return BAD_VALUE;
+ }
+
+ sp<StreamSplitter> splitter(new StreamSplitter(inputQueue));
+ status_t status = splitter->mInput->consumerConnect(splitter, false);
+ if (status == NO_ERROR) {
+ splitter->mInput->setConsumerName(String8("StreamSplitter"));
+ *outSplitter = splitter;
+ }
+ return status;
+}
+
+StreamSplitter::StreamSplitter(const sp<IGraphicBufferConsumer>& inputQueue)
+ : mIsAbandoned(false), mMutex(), mReleaseCondition(),
+ mOutstandingBuffers(0), mInput(inputQueue), mOutputs(), mBuffers() {}
+
+StreamSplitter::~StreamSplitter() {
+ mInput->consumerDisconnect();
+ Vector<sp<IGraphicBufferProducer> >::iterator output = mOutputs.begin();
+ for (; output != mOutputs.end(); ++output) {
+ (*output)->disconnect(NATIVE_WINDOW_API_CPU);
+ }
+
+ if (mBuffers.size() > 0) {
+ ALOGE("%d buffers still being tracked", mBuffers.size());
+ }
+}
+
+status_t StreamSplitter::addOutput(
+ const sp<IGraphicBufferProducer>& outputQueue) {
+ if (outputQueue == NULL) {
+ ALOGE("addOutput: outputQueue must not be NULL");
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mMutex);
+
+ IGraphicBufferProducer::QueueBufferOutput queueBufferOutput;
+ sp<OutputListener> listener(new OutputListener(this, outputQueue));
+ outputQueue->asBinder()->linkToDeath(listener);
+ status_t status = outputQueue->connect(listener, NATIVE_WINDOW_API_CPU,
+ /* producerControlledByApp */ false, &queueBufferOutput);
+ if (status != NO_ERROR) {
+ ALOGE("addOutput: failed to connect (%d)", status);
+ return status;
+ }
+
+ mOutputs.push_back(outputQueue);
+
+ return NO_ERROR;
+}
+
+void StreamSplitter::setName(const String8 &name) {
+ Mutex::Autolock lock(mMutex);
+ mInput->setConsumerName(name);
+}
+
+void StreamSplitter::onFrameAvailable() {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+
+ // The current policy is that if any one consumer is consuming buffers too
+ // slowly, the splitter will stall the rest of the outputs by not acquiring
+ // any more buffers from the input. This will cause back pressure on the
+ // input queue, slowing down its producer.
+
+ // If there are too many outstanding buffers, we block until a buffer is
+ // released back to the input in onBufferReleased
+ while (mOutstandingBuffers >= MAX_OUTSTANDING_BUFFERS) {
+ mReleaseCondition.wait(mMutex);
+
+ // If the splitter is abandoned while we are waiting, the release
+ // condition variable will be broadcast, and we should just return
+ // without attempting to do anything more (since the input queue will
+ // also be abandoned).
+ if (mIsAbandoned) {
+ return;
+ }
+ }
+ ++mOutstandingBuffers;
+
+ // Acquire and detach the buffer from the input
+ IGraphicBufferConsumer::BufferItem bufferItem;
+ status_t status = mInput->acquireBuffer(&bufferItem, /* presentWhen */ 0);
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+ "acquiring buffer from input failed (%d)", status);
+
+ ALOGV("acquired buffer %#llx from input",
+ bufferItem.mGraphicBuffer->getId());
+
+ status = mInput->detachBuffer(bufferItem.mBuf);
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+ "detaching buffer from input failed (%d)", status);
+
+ // Initialize our reference count for this buffer
+ mBuffers.add(bufferItem.mGraphicBuffer->getId(),
+ new BufferTracker(bufferItem.mGraphicBuffer));
+
+ IGraphicBufferProducer::QueueBufferInput queueInput(
+ bufferItem.mTimestamp, bufferItem.mIsAutoTimestamp,
+ bufferItem.mCrop, bufferItem.mScalingMode,
+ bufferItem.mTransform, bufferItem.mIsDroppable,
+ bufferItem.mFence);
+
+ // Attach and queue the buffer to each of the outputs
+ Vector<sp<IGraphicBufferProducer> >::iterator output = mOutputs.begin();
+ for (; output != mOutputs.end(); ++output) {
+ int slot;
+ status = (*output)->attachBuffer(&slot, bufferItem.mGraphicBuffer);
+ if (status == NO_INIT) {
+ // If we just discovered that this output has been abandoned, note
+ // that, increment the release count so that we still release this
+ // buffer eventually, and move on to the next output
+ onAbandonedLocked();
+ mBuffers.editValueFor(bufferItem.mGraphicBuffer->getId())->
+ incrementReleaseCountLocked();
+ continue;
+ } else {
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+ "attaching buffer to output failed (%d)", status);
+ }
+
+ IGraphicBufferProducer::QueueBufferOutput queueOutput;
+ status = (*output)->queueBuffer(slot, queueInput, &queueOutput);
+ if (status == NO_INIT) {
+ // If we just discovered that this output has been abandoned, note
+ // that, increment the release count so that we still release this
+ // buffer eventually, and move on to the next output
+ onAbandonedLocked();
+ mBuffers.editValueFor(bufferItem.mGraphicBuffer->getId())->
+ incrementReleaseCountLocked();
+ continue;
+ } else {
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+ "queueing buffer to output failed (%d)", status);
+ }
+
+ ALOGV("queued buffer %#llx to output %p",
+ bufferItem.mGraphicBuffer->getId(), output->get());
+ }
+}
+
+void StreamSplitter::onBufferReleasedByOutput(
+ const sp<IGraphicBufferProducer>& from) {
+ ATRACE_CALL();
+ Mutex::Autolock lock(mMutex);
+
+ sp<GraphicBuffer> buffer;
+ sp<Fence> fence;
+ status_t status = from->detachNextBuffer(&buffer, &fence);
+ if (status == NO_INIT) {
+ // If we just discovered that this output has been abandoned, note that,
+ // but we can't do anything else, since buffer is invalid
+ onAbandonedLocked();
+ return;
+ } else {
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+ "detaching buffer from output failed (%d)", status);
+ }
+
+ ALOGV("detached buffer %#llx from output %p", buffer->getId(), from.get());
+
+ const sp<BufferTracker>& tracker = mBuffers.editValueFor(buffer->getId());
+
+ // Merge the release fence of the incoming buffer so that the fence we send
+ // back to the input includes all of the outputs' fences
+ tracker->mergeFence(fence);
+
+ // Check to see if this is the last outstanding reference to this buffer
+ size_t releaseCount = tracker->incrementReleaseCountLocked();
+ ALOGV("buffer %#llx reference count %d (of %d)", buffer->getId(),
+ releaseCount, mOutputs.size());
+ if (releaseCount < mOutputs.size()) {
+ return;
+ }
+
+ // If we've been abandoned, we can't return the buffer to the input, so just
+ // stop tracking it and move on
+ if (mIsAbandoned) {
+ mBuffers.removeItem(buffer->getId());
+ return;
+ }
+
+ // Attach and release the buffer back to the input
+ int consumerSlot;
+ status = mInput->attachBuffer(&consumerSlot, tracker->getBuffer());
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+ "attaching buffer to input failed (%d)", status);
+
+ status = mInput->releaseBuffer(consumerSlot, /* frameNumber */ 0,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, tracker->getMergedFence());
+ LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+ "releasing buffer to input failed (%d)", status);
+
+ ALOGV("released buffer %#llx to input", buffer->getId());
+
+ // We no longer need to track the buffer once it has been returned to the
+ // input
+ mBuffers.removeItem(buffer->getId());
+
+ // Notify any waiting onFrameAvailable calls
+ --mOutstandingBuffers;
+ mReleaseCondition.signal();
+}
+
+void StreamSplitter::onAbandonedLocked() {
+ ALOGE("one of my outputs has abandoned me");
+ if (!mIsAbandoned) {
+ mInput->consumerDisconnect();
+ }
+ mIsAbandoned = true;
+ mReleaseCondition.broadcast();
+}
+
+StreamSplitter::OutputListener::OutputListener(
+ const sp<StreamSplitter>& splitter,
+ const sp<IGraphicBufferProducer>& output)
+ : mSplitter(splitter), mOutput(output) {}
+
+StreamSplitter::OutputListener::~OutputListener() {}
+
+void StreamSplitter::OutputListener::onBufferReleased() {
+ mSplitter->onBufferReleasedByOutput(mOutput);
+}
+
+void StreamSplitter::OutputListener::binderDied(const wp<IBinder>& /* who */) {
+ Mutex::Autolock lock(mSplitter->mMutex);
+ mSplitter->onAbandonedLocked();
+}
+
+StreamSplitter::BufferTracker::BufferTracker(const sp<GraphicBuffer>& buffer)
+ : mBuffer(buffer), mMergedFence(Fence::NO_FENCE), mReleaseCount(0) {}
+
+StreamSplitter::BufferTracker::~BufferTracker() {}
+
+void StreamSplitter::BufferTracker::mergeFence(const sp<Fence>& with) {
+ mMergedFence = Fence::merge(String8("StreamSplitter"), mMergedFence, with);
+}
+
+} // namespace android
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 27dbc4e..d1ef503 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -27,6 +27,7 @@
#include <ui/Fence.h>
+#include <gui/IProducerListener.h>
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/GLConsumer.h>
@@ -86,6 +87,10 @@ sp<IGraphicBufferProducer> Surface::getIGraphicBufferProducer() const {
return mGraphicBufferProducer;
}
+void Surface::setSidebandStream(const sp<NativeHandle>& stream) {
+ mGraphicBufferProducer->setSidebandStream(stream);
+}
+
int Surface::hook_setSwapInterval(ANativeWindow* window, int interval) {
Surface* c = getSelf(window);
return c->setSwapInterval(interval);
@@ -178,19 +183,38 @@ int Surface::setSwapInterval(int interval) {
int Surface::dequeueBuffer(android_native_buffer_t** buffer, int* fenceFd) {
ATRACE_CALL();
ALOGV("Surface::dequeueBuffer");
- Mutex::Autolock lock(mMutex);
+
+ int reqW;
+ int reqH;
+ bool swapIntervalZero;
+ uint32_t reqFormat;
+ uint32_t reqUsage;
+
+ {
+ Mutex::Autolock lock(mMutex);
+
+ reqW = mReqWidth ? mReqWidth : mUserWidth;
+ reqH = mReqHeight ? mReqHeight : mUserHeight;
+
+ swapIntervalZero = mSwapIntervalZero;
+ reqFormat = mReqFormat;
+ reqUsage = mReqUsage;
+ } // Drop the lock so that we can still touch the Surface while blocking in IGBP::dequeueBuffer
+
int buf = -1;
- int reqW = mReqWidth ? mReqWidth : mUserWidth;
- int reqH = mReqHeight ? mReqHeight : mUserHeight;
sp<Fence> fence;
- status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, mSwapIntervalZero,
- reqW, reqH, mReqFormat, mReqUsage);
+ status_t result = mGraphicBufferProducer->dequeueBuffer(&buf, &fence, swapIntervalZero,
+ reqW, reqH, reqFormat, reqUsage);
+
if (result < 0) {
- ALOGV("dequeueBuffer: IGraphicBufferProducer::dequeueBuffer(%d, %d, %d, %d)"
- "failed: %d", mReqWidth, mReqHeight, mReqFormat, mReqUsage,
+ ALOGV("dequeueBuffer: IGraphicBufferProducer::dequeueBuffer(%d, %d, %d, %d, %d)"
+ "failed: %d", swapIntervalZero, reqW, reqH, reqFormat, reqUsage,
result);
return result;
}
+
+ Mutex::Autolock lock(mMutex);
+
sp<GraphicBuffer>& gbuf(mSlots[buf].buffer);
// this should never happen
@@ -251,7 +275,7 @@ int Surface::getSlotFromBufferLocked(
return BAD_VALUE;
}
-int Surface::lockBuffer_DEPRECATED(android_native_buffer_t* buffer) {
+int Surface::lockBuffer_DEPRECATED(android_native_buffer_t* buffer __attribute__((unused))) {
ALOGV("Surface::lockBuffer");
Mutex::Autolock lock(mMutex);
return OK;
@@ -482,7 +506,7 @@ int Surface::dispatchLock(va_list args) {
return lock(outBuffer, inOutDirtyBounds);
}
-int Surface::dispatchUnlockAndPost(va_list args) {
+int Surface::dispatchUnlockAndPost(va_list args __attribute__((unused))) {
return unlockAndPost();
}
@@ -490,10 +514,10 @@ int Surface::dispatchUnlockAndPost(va_list args) {
int Surface::connect(int api) {
ATRACE_CALL();
ALOGV("Surface::connect");
- static sp<BBinder> sLife = new BBinder();
+ static sp<IProducerListener> listener = new DummyProducerListener();
Mutex::Autolock lock(mMutex);
IGraphicBufferProducer::QueueBufferOutput output;
- int err = mGraphicBufferProducer->connect(sLife, api, mProducerControlledByApp, &output);
+ int err = mGraphicBufferProducer->connect(listener, api, mProducerControlledByApp, &output);
if (err == NO_ERROR) {
uint32_t numPendingBuffers = 0;
output.deflate(&mDefaultWidth, &mDefaultHeight, &mTransformHint,
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 2246f5f..b7af415 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -515,6 +515,21 @@ status_t SurfaceComposerClient::destroySurface(const sp<IBinder>& sid) {
return err;
}
+status_t SurfaceComposerClient::clearLayerFrameStats(const sp<IBinder>& token) const {
+ if (mStatus != NO_ERROR) {
+ return mStatus;
+ }
+ return mClient->clearLayerFrameStats(token);
+}
+
+status_t SurfaceComposerClient::getLayerFrameStats(const sp<IBinder>& token,
+ FrameStats* outStats) const {
+ if (mStatus != NO_ERROR) {
+ return mStatus;
+ }
+ return mClient->getLayerFrameStats(token, outStats);
+}
+
inline Composer& SurfaceComposerClient::getComposer() {
return mComposer;
}
@@ -622,17 +637,25 @@ void SurfaceComposerClient::unblankDisplay(const sp<IBinder>& token) {
ComposerService::getComposerService()->unblank(token);
}
+status_t SurfaceComposerClient::clearAnimationFrameStats() {
+ return ComposerService::getComposerService()->clearAnimationFrameStats();
+}
+
+status_t SurfaceComposerClient::getAnimationFrameStats(FrameStats* outStats) {
+ return ComposerService::getComposerService()->getAnimationFrameStats(outStats);
+}
+
// ----------------------------------------------------------------------------
status_t ScreenshotClient::capture(
const sp<IBinder>& display,
const sp<IGraphicBufferProducer>& producer,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ) {
+ uint32_t minLayerZ, uint32_t maxLayerZ, bool useIdentityTransform) {
sp<ISurfaceComposer> s(ComposerService::getComposerService());
if (s == NULL) return NO_INIT;
return s->captureScreen(display, producer,
- reqWidth, reqHeight, minLayerZ, maxLayerZ);
+ reqWidth, reqHeight, minLayerZ, maxLayerZ, useIdentityTransform);
}
ScreenshotClient::ScreenshotClient()
@@ -646,8 +669,9 @@ ScreenshotClient::~ScreenshotClient() {
sp<CpuConsumer> ScreenshotClient::getCpuConsumer() const {
if (mCpuConsumer == NULL) {
- mBufferQueue = new BufferQueue();
- mCpuConsumer = new CpuConsumer(mBufferQueue, 1);
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&mProducer, &consumer);
+ mCpuConsumer = new CpuConsumer(consumer, 1);
mCpuConsumer->setName(String8("ScreenshotClient"));
}
return mCpuConsumer;
@@ -655,7 +679,8 @@ sp<CpuConsumer> ScreenshotClient::getCpuConsumer() const {
status_t ScreenshotClient::update(const sp<IBinder>& display,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ) {
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform) {
sp<ISurfaceComposer> s(ComposerService::getComposerService());
if (s == NULL) return NO_INIT;
sp<CpuConsumer> cpuConsumer = getCpuConsumer();
@@ -666,8 +691,8 @@ status_t ScreenshotClient::update(const sp<IBinder>& display,
mHaveBuffer = false;
}
- status_t err = s->captureScreen(display, mBufferQueue,
- reqWidth, reqHeight, minLayerZ, maxLayerZ);
+ status_t err = s->captureScreen(display, mProducer,
+ reqWidth, reqHeight, minLayerZ, maxLayerZ, useIdentityTransform);
if (err == NO_ERROR) {
err = mCpuConsumer->lockNextBuffer(&mBuffer);
@@ -678,13 +703,16 @@ status_t ScreenshotClient::update(const sp<IBinder>& display,
return err;
}
-status_t ScreenshotClient::update(const sp<IBinder>& display) {
- return ScreenshotClient::update(display, 0, 0, 0, -1UL);
+status_t ScreenshotClient::update(const sp<IBinder>& display,
+ bool useIdentityTransform) {
+ return ScreenshotClient::update(display, 0, 0, 0, -1UL,
+ useIdentityTransform);
}
status_t ScreenshotClient::update(const sp<IBinder>& display,
- uint32_t reqWidth, uint32_t reqHeight) {
- return ScreenshotClient::update(display, reqWidth, reqHeight, 0, -1UL);
+ uint32_t reqWidth, uint32_t reqHeight, bool useIdentityTransform) {
+ return ScreenshotClient::update(display, reqWidth, reqHeight, 0, -1UL,
+ useIdentityTransform);
}
void ScreenshotClient::release() {
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index 16e533c..7597c99 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -23,7 +23,6 @@
#include <android/native_window.h>
-#include <utils/CallStack.h>
#include <utils/Errors.h>
#include <utils/Log.h>
#include <utils/threads.h>
@@ -93,68 +92,71 @@ bool SurfaceControl::isSameSurface(
status_t SurfaceControl::setLayerStack(int32_t layerStack) {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->setLayerStack(mHandle, layerStack);
+ return mClient->setLayerStack(mHandle, layerStack);
}
status_t SurfaceControl::setLayer(int32_t layer) {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->setLayer(mHandle, layer);
+ return mClient->setLayer(mHandle, layer);
}
status_t SurfaceControl::setPosition(float x, float y) {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->setPosition(mHandle, x, y);
+ return mClient->setPosition(mHandle, x, y);
}
status_t SurfaceControl::setSize(uint32_t w, uint32_t h) {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->setSize(mHandle, w, h);
+ return mClient->setSize(mHandle, w, h);
}
status_t SurfaceControl::hide() {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->hide(mHandle);
+ return mClient->hide(mHandle);
}
status_t SurfaceControl::show() {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->show(mHandle);
+ return mClient->show(mHandle);
}
status_t SurfaceControl::setFlags(uint32_t flags, uint32_t mask) {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->setFlags(mHandle, flags, mask);
+ return mClient->setFlags(mHandle, flags, mask);
}
status_t SurfaceControl::setTransparentRegionHint(const Region& transparent) {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->setTransparentRegionHint(mHandle, transparent);
+ return mClient->setTransparentRegionHint(mHandle, transparent);
}
status_t SurfaceControl::setAlpha(float alpha) {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->setAlpha(mHandle, alpha);
+ return mClient->setAlpha(mHandle, alpha);
}
status_t SurfaceControl::setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
status_t err = validate();
if (err < 0) return err;
- const sp<SurfaceComposerClient>& client(mClient);
- return client->setMatrix(mHandle, dsdx, dtdx, dsdy, dtdy);
+ return mClient->setMatrix(mHandle, dsdx, dtdx, dsdy, dtdy);
}
status_t SurfaceControl::setCrop(const Rect& crop) {
status_t err = validate();
if (err < 0) return err;
+ return mClient->setCrop(mHandle, crop);
+}
+
+status_t SurfaceControl::clearLayerFrameStats() const {
+ status_t err = validate();
+ if (err < 0) return err;
+ const sp<SurfaceComposerClient>& client(mClient);
+ return client->clearLayerFrameStats(mHandle);
+}
+
+status_t SurfaceControl::getLayerFrameStats(FrameStats* outStats) const {
+ status_t err = validate();
+ if (err < 0) return err;
const sp<SurfaceComposerClient>& client(mClient);
- return client->setCrop(mHandle, crop);
+ return client->getLayerFrameStats(mHandle, outStats);
}
status_t SurfaceControl::validate() const
diff --git a/libs/gui/tests/Android.mk b/libs/gui/tests/Android.mk
index 21bd875..e460290 100644
--- a/libs/gui/tests/Android.mk
+++ b/libs/gui/tests/Android.mk
@@ -9,9 +9,20 @@ LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := \
BufferQueue_test.cpp \
CpuConsumer_test.cpp \
+ FillBuffer.cpp \
+ GLTest.cpp \
+ IGraphicBufferProducer_test.cpp \
+ MultiTextureConsumer_test.cpp \
+ SRGB_test.cpp \
+ StreamSplitter_test.cpp \
SurfaceTextureClient_test.cpp \
- SurfaceTexture_test.cpp \
+ SurfaceTextureFBO_test.cpp \
+ SurfaceTextureGLThreadToGL_test.cpp \
+ SurfaceTextureGLToGL_test.cpp \
+ SurfaceTextureGL_test.cpp \
+ SurfaceTextureMultiContextGL_test.cpp \
Surface_test.cpp \
+ TextureRenderer.cpp \
LOCAL_SHARED_LIBRARIES := \
libEGL \
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index 03c1a29..c781366 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -17,55 +17,135 @@
#define LOG_TAG "BufferQueue_test"
//#define LOG_NDEBUG 0
-#include <gtest/gtest.h>
+#include <gui/BufferQueue.h>
+#include <gui/IProducerListener.h>
+
+#include <ui/GraphicBuffer.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
#include <utils/String8.h>
#include <utils/threads.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/FramebufferNativeWindow.h>
-
-#include <gui/BufferQueue.h>
+#include <gtest/gtest.h>
namespace android {
class BufferQueueTest : public ::testing::Test {
-protected:
-
- BufferQueueTest() {}
- virtual void SetUp() {
+public:
+protected:
+ BufferQueueTest() {
const ::testing::TestInfo* const testInfo =
::testing::UnitTest::GetInstance()->current_test_info();
ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
testInfo->name());
-
- mBQ = new BufferQueue();
}
- virtual void TearDown() {
- mBQ.clear();
-
+ ~BufferQueueTest() {
const ::testing::TestInfo* const testInfo =
::testing::UnitTest::GetInstance()->current_test_info();
ALOGV("End test: %s.%s", testInfo->test_case_name(),
testInfo->name());
}
- sp<BufferQueue> mBQ;
+ void GetMinUndequeuedBufferCount(int* bufferCount) {
+ ASSERT_TRUE(bufferCount != NULL);
+ ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ bufferCount));
+ ASSERT_GE(*bufferCount, 0);
+ }
+
+ void createBufferQueue() {
+ BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+ }
+
+ sp<IGraphicBufferProducer> mProducer;
+ sp<IGraphicBufferConsumer> mConsumer;
};
struct DummyConsumer : public BnConsumerListener {
virtual void onFrameAvailable() {}
virtual void onBuffersReleased() {}
+ virtual void onSidebandStreamChanged() {}
};
+// XXX: Tests that fork a process to hold the BufferQueue must run before tests
+// that use a local BufferQueue, or else Binder will get unhappy
+TEST_F(BufferQueueTest, BufferQueueInAnotherProcess) {
+ const String16 PRODUCER_NAME = String16("BQTestProducer");
+ const String16 CONSUMER_NAME = String16("BQTestConsumer");
+
+ pid_t forkPid = fork();
+ ASSERT_NE(forkPid, -1);
+
+ if (forkPid == 0) {
+ // Child process
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ sp<IServiceManager> serviceManager = defaultServiceManager();
+ serviceManager->addService(PRODUCER_NAME, producer->asBinder());
+ serviceManager->addService(CONSUMER_NAME, consumer->asBinder());
+ ProcessState::self()->startThreadPool();
+ IPCThreadState::self()->joinThreadPool();
+ LOG_ALWAYS_FATAL("Shouldn't be here");
+ }
+
+ sp<IServiceManager> serviceManager = defaultServiceManager();
+ sp<IBinder> binderProducer =
+ serviceManager->getService(PRODUCER_NAME);
+ mProducer = interface_cast<IGraphicBufferProducer>(binderProducer);
+ EXPECT_TRUE(mProducer != NULL);
+ sp<IBinder> binderConsumer =
+ serviceManager->getService(CONSUMER_NAME);
+ mConsumer = interface_cast<IGraphicBufferConsumer>(binderConsumer);
+ EXPECT_TRUE(mConsumer != NULL);
+
+ sp<DummyConsumer> dc(new DummyConsumer);
+ ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false));
+ IGraphicBufferProducer::QueueBufferOutput output;
+ ASSERT_EQ(OK,
+ mProducer->connect(NULL, NATIVE_WINDOW_API_CPU, false, &output));
+
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buffer;
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+ ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
+
+ uint32_t* dataIn;
+ ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+ reinterpret_cast<void**>(&dataIn)));
+ *dataIn = 0x12345678;
+ ASSERT_EQ(OK, buffer->unlock());
+
+ IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE);
+ ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+ IGraphicBufferConsumer::BufferItem item;
+ ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+
+ uint32_t* dataOut;
+ ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
+ reinterpret_cast<void**>(&dataOut)));
+ ASSERT_EQ(*dataOut, 0x12345678);
+ ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
+}
+
TEST_F(BufferQueueTest, AcquireBuffer_ExceedsMaxAcquireCount_Fails) {
+ createBufferQueue();
sp<DummyConsumer> dc(new DummyConsumer);
- mBQ->consumerConnect(dc, false);
+ mConsumer->consumerConnect(dc, false);
IGraphicBufferProducer::QueueBufferOutput qbo;
- mBQ->connect(NULL, NATIVE_WINDOW_API_CPU, false, &qbo);
- mBQ->setBufferCount(4);
+ mProducer->connect(new DummyProducerListener, NATIVE_WINDOW_API_CPU, false,
+ &qbo);
+ mProducer->setBufferCount(4);
int slot;
sp<Fence> fence;
@@ -76,42 +156,206 @@ TEST_F(BufferQueueTest, AcquireBuffer_ExceedsMaxAcquireCount_Fails) {
for (int i = 0; i < 2; i++) {
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mBQ->dequeueBuffer(&slot, &fence, false, 1, 1, 0,
+ mProducer->dequeueBuffer(&slot, &fence, false, 1, 1, 0,
GRALLOC_USAGE_SW_READ_OFTEN));
- ASSERT_EQ(OK, mBQ->requestBuffer(slot, &buf));
- ASSERT_EQ(OK, mBQ->queueBuffer(slot, qbi, &qbo));
- ASSERT_EQ(OK, mBQ->acquireBuffer(&item, 0));
+ ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buf));
+ ASSERT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo));
+ ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
}
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mBQ->dequeueBuffer(&slot, &fence, false, 1, 1, 0,
+ mProducer->dequeueBuffer(&slot, &fence, false, 1, 1, 0,
GRALLOC_USAGE_SW_READ_OFTEN));
- ASSERT_EQ(OK, mBQ->requestBuffer(slot, &buf));
- ASSERT_EQ(OK, mBQ->queueBuffer(slot, qbi, &qbo));
+ ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buf));
+ ASSERT_EQ(OK, mProducer->queueBuffer(slot, qbi, &qbo));
// Acquire the third buffer, which should fail.
- ASSERT_EQ(INVALID_OPERATION, mBQ->acquireBuffer(&item, 0));
+ ASSERT_EQ(INVALID_OPERATION, mConsumer->acquireBuffer(&item, 0));
}
TEST_F(BufferQueueTest, SetMaxAcquiredBufferCountWithIllegalValues_ReturnsError) {
+ createBufferQueue();
sp<DummyConsumer> dc(new DummyConsumer);
- mBQ->consumerConnect(dc, false);
+ mConsumer->consumerConnect(dc, false);
+
+ int minBufferCount;
+ ASSERT_NO_FATAL_FAILURE(GetMinUndequeuedBufferCount(&minBufferCount));
+ EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount(
+ minBufferCount - 1));
- ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount(0));
- ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount(-3));
- ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount(
+ EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount(0));
+ EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount(-3));
+ EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount(
BufferQueue::MAX_MAX_ACQUIRED_BUFFERS+1));
- ASSERT_EQ(BAD_VALUE, mBQ->setMaxAcquiredBufferCount(100));
+ EXPECT_EQ(BAD_VALUE, mConsumer->setMaxAcquiredBufferCount(100));
}
TEST_F(BufferQueueTest, SetMaxAcquiredBufferCountWithLegalValues_Succeeds) {
+ createBufferQueue();
sp<DummyConsumer> dc(new DummyConsumer);
- mBQ->consumerConnect(dc, false);
+ mConsumer->consumerConnect(dc, false);
- ASSERT_EQ(OK, mBQ->setMaxAcquiredBufferCount(1));
- ASSERT_EQ(OK, mBQ->setMaxAcquiredBufferCount(2));
- ASSERT_EQ(OK, mBQ->setMaxAcquiredBufferCount(
+ int minBufferCount;
+ ASSERT_NO_FATAL_FAILURE(GetMinUndequeuedBufferCount(&minBufferCount));
+
+ EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(1));
+ EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(2));
+ EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(minBufferCount));
+ EXPECT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(
BufferQueue::MAX_MAX_ACQUIRED_BUFFERS));
}
+TEST_F(BufferQueueTest, DetachAndReattachOnProducerSide) {
+ createBufferQueue();
+ sp<DummyConsumer> dc(new DummyConsumer);
+ ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false));
+ IGraphicBufferProducer::QueueBufferOutput output;
+ ASSERT_EQ(OK, mProducer->connect(new DummyProducerListener,
+ NATIVE_WINDOW_API_CPU, false, &output));
+
+ ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer(-1)); // Index too low
+ ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer(
+ BufferQueueDefs::NUM_BUFFER_SLOTS)); // Index too high
+ ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer(0)); // Not dequeued
+
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buffer;
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+ ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer(slot)); // Not requested
+ ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
+ ASSERT_EQ(OK, mProducer->detachBuffer(slot));
+ ASSERT_EQ(BAD_VALUE, mProducer->detachBuffer(slot)); // Not dequeued
+
+ sp<GraphicBuffer> safeToClobberBuffer;
+ // Can no longer request buffer from this slot
+ ASSERT_EQ(BAD_VALUE, mProducer->requestBuffer(slot, &safeToClobberBuffer));
+
+ uint32_t* dataIn;
+ ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+ reinterpret_cast<void**>(&dataIn)));
+ *dataIn = 0x12345678;
+ ASSERT_EQ(OK, buffer->unlock());
+
+ int newSlot;
+ ASSERT_EQ(BAD_VALUE, mProducer->attachBuffer(NULL, safeToClobberBuffer));
+ ASSERT_EQ(BAD_VALUE, mProducer->attachBuffer(&newSlot, NULL));
+
+ ASSERT_EQ(OK, mProducer->attachBuffer(&newSlot, buffer));
+ IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE);
+ ASSERT_EQ(OK, mProducer->queueBuffer(newSlot, input, &output));
+
+ IGraphicBufferConsumer::BufferItem item;
+ ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, static_cast<nsecs_t>(0)));
+
+ uint32_t* dataOut;
+ ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
+ reinterpret_cast<void**>(&dataOut)));
+ ASSERT_EQ(*dataOut, 0x12345678);
+ ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
+}
+
+TEST_F(BufferQueueTest, DetachAndReattachOnConsumerSide) {
+ createBufferQueue();
+ sp<DummyConsumer> dc(new DummyConsumer);
+ ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false));
+ IGraphicBufferProducer::QueueBufferOutput output;
+ ASSERT_EQ(OK, mProducer->connect(new DummyProducerListener,
+ NATIVE_WINDOW_API_CPU, false, &output));
+
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buffer;
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+ ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
+ IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE);
+ ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+ ASSERT_EQ(BAD_VALUE, mConsumer->detachBuffer(-1)); // Index too low
+ ASSERT_EQ(BAD_VALUE, mConsumer->detachBuffer(
+ BufferQueueDefs::NUM_BUFFER_SLOTS)); // Index too high
+ ASSERT_EQ(BAD_VALUE, mConsumer->detachBuffer(0)); // Not acquired
+
+ IGraphicBufferConsumer::BufferItem item;
+ ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, static_cast<nsecs_t>(0)));
+
+ ASSERT_EQ(OK, mConsumer->detachBuffer(item.mBuf));
+ ASSERT_EQ(BAD_VALUE, mConsumer->detachBuffer(item.mBuf)); // Not acquired
+
+ uint32_t* dataIn;
+ ASSERT_EQ(OK, item.mGraphicBuffer->lock(
+ GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+ reinterpret_cast<void**>(&dataIn)));
+ *dataIn = 0x12345678;
+ ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
+
+ int newSlot;
+ sp<GraphicBuffer> safeToClobberBuffer;
+ ASSERT_EQ(BAD_VALUE, mConsumer->attachBuffer(NULL, safeToClobberBuffer));
+ ASSERT_EQ(BAD_VALUE, mConsumer->attachBuffer(&newSlot, NULL));
+ ASSERT_EQ(OK, mConsumer->attachBuffer(&newSlot, item.mGraphicBuffer));
+
+ ASSERT_EQ(OK, mConsumer->releaseBuffer(newSlot, 0, EGL_NO_DISPLAY,
+ EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+ ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
+
+ uint32_t* dataOut;
+ ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
+ reinterpret_cast<void**>(&dataOut)));
+ ASSERT_EQ(*dataOut, 0x12345678);
+ ASSERT_EQ(OK, buffer->unlock());
+}
+
+TEST_F(BufferQueueTest, MoveFromConsumerToProducer) {
+ createBufferQueue();
+ sp<DummyConsumer> dc(new DummyConsumer);
+ ASSERT_EQ(OK, mConsumer->consumerConnect(dc, false));
+ IGraphicBufferProducer::QueueBufferOutput output;
+ ASSERT_EQ(OK, mProducer->connect(new DummyProducerListener,
+ NATIVE_WINDOW_API_CPU, false, &output));
+
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buffer;
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+ ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
+
+ uint32_t* dataIn;
+ ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+ reinterpret_cast<void**>(&dataIn)));
+ *dataIn = 0x12345678;
+ ASSERT_EQ(OK, buffer->unlock());
+
+ IGraphicBufferProducer::QueueBufferInput input(0, false, Rect(0, 0, 1, 1),
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false, Fence::NO_FENCE);
+ ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
+
+ IGraphicBufferConsumer::BufferItem item;
+ ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, static_cast<nsecs_t>(0)));
+ ASSERT_EQ(OK, mConsumer->detachBuffer(item.mBuf));
+
+ int newSlot;
+ ASSERT_EQ(OK, mProducer->attachBuffer(&newSlot, item.mGraphicBuffer));
+ ASSERT_EQ(OK, mProducer->queueBuffer(newSlot, input, &output));
+ ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, static_cast<nsecs_t>(0)));
+
+ uint32_t* dataOut;
+ ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
+ reinterpret_cast<void**>(&dataOut)));
+ ASSERT_EQ(*dataOut, 0x12345678);
+ ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
+}
+
} // namespace android
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
index b370a2d..abd3724 100644
--- a/libs/gui/tests/CpuConsumer_test.cpp
+++ b/libs/gui/tests/CpuConsumer_test.cpp
@@ -33,8 +33,6 @@
#include <utils/Mutex.h>
#include <utils/Condition.h>
-#include <ui/FramebufferNativeWindow.h>
-
#define CPU_CONSUMER_TEST_FORMAT_RAW 0
#define CPU_CONSUMER_TEST_FORMAT_Y8 0
#define CPU_CONSUMER_TEST_FORMAT_Y16 0
@@ -66,11 +64,13 @@ protected:
test_info->name(),
params.width, params.height,
params.maxLockedBuffers, params.format);
- sp<BufferQueue> bq = new BufferQueue();
- mCC = new CpuConsumer(bq, params.maxLockedBuffers);
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ mCC = new CpuConsumer(consumer, params.maxLockedBuffers);
String8 name("CpuConsumer_Under_Test");
mCC->setName(name);
- mSTC = new Surface(bq);
+ mSTC = new Surface(producer);
mANW = mSTC;
}
diff --git a/libs/gui/tests/DisconnectWaiter.h b/libs/gui/tests/DisconnectWaiter.h
new file mode 100644
index 0000000..56e96c2
--- /dev/null
+++ b/libs/gui/tests/DisconnectWaiter.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_DISCONNECT_WAITER_H
+#define ANDROID_DISCONNECT_WAITER_H
+
+#include <gui/IConsumerListener.h>
+
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+// Note that GLConsumer will lose the notifications
+// onBuffersReleased and onFrameAvailable as there is currently
+// no way to forward the events. This DisconnectWaiter will not let the
+// disconnect finish until finishDisconnect() is called. It will
+// also block until a disconnect is called
+class DisconnectWaiter : public BnConsumerListener {
+public:
+ DisconnectWaiter () :
+ mWaitForDisconnect(false),
+ mPendingFrames(0) {
+ }
+
+ void waitForFrame() {
+ Mutex::Autolock lock(mMutex);
+ while (mPendingFrames == 0) {
+ mFrameCondition.wait(mMutex);
+ }
+ mPendingFrames--;
+ }
+
+ virtual void onFrameAvailable() {
+ Mutex::Autolock lock(mMutex);
+ mPendingFrames++;
+ mFrameCondition.signal();
+ }
+
+ virtual void onBuffersReleased() {
+ Mutex::Autolock lock(mMutex);
+ while (!mWaitForDisconnect) {
+ mDisconnectCondition.wait(mMutex);
+ }
+ }
+
+ virtual void onSidebandStreamChanged() {}
+
+ void finishDisconnect() {
+ Mutex::Autolock lock(mMutex);
+ mWaitForDisconnect = true;
+ mDisconnectCondition.signal();
+ }
+
+private:
+ Mutex mMutex;
+
+ bool mWaitForDisconnect;
+ Condition mDisconnectCondition;
+
+ int mPendingFrames;
+ Condition mFrameCondition;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/gui/tests/FillBuffer.cpp b/libs/gui/tests/FillBuffer.cpp
new file mode 100644
index 0000000..079962c
--- /dev/null
+++ b/libs/gui/tests/FillBuffer.cpp
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "FillBuffer.h"
+
+#include <ui/GraphicBuffer.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+void fillYV12Buffer(uint8_t* buf, int w, int h, int stride) {
+ const int blockWidth = w > 16 ? w / 16 : 1;
+ const int blockHeight = h > 16 ? h / 16 : 1;
+ const int yuvTexOffsetY = 0;
+ int yuvTexStrideY = stride;
+ int yuvTexOffsetV = yuvTexStrideY * h;
+ int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
+ int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2;
+ int yuvTexStrideU = yuvTexStrideV;
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ int parityX = (x / blockWidth) & 1;
+ int parityY = (y / blockHeight) & 1;
+ unsigned char intensity = (parityX ^ parityY) ? 63 : 191;
+ buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = intensity;
+ if (x < w / 2 && y < h / 2) {
+ buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = intensity;
+ if (x * 2 < w / 2 && y * 2 < h / 2) {
+ buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 0] =
+ buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 1] =
+ buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 0] =
+ buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 1] =
+ intensity;
+ }
+ }
+ }
+ }
+}
+
+void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride,
+ const android_native_rect_t& rect) {
+ const int yuvTexOffsetY = 0;
+ int yuvTexStrideY = stride;
+ int yuvTexOffsetV = yuvTexStrideY * h;
+ int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
+ int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2;
+ int yuvTexStrideU = yuvTexStrideV;
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ bool inside = rect.left <= x && x < rect.right &&
+ rect.top <= y && y < rect.bottom;
+ buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = inside ? 240 : 64;
+ if (x < w / 2 && y < h / 2) {
+ bool inside = rect.left <= 2*x && 2*x < rect.right &&
+ rect.top <= 2*y && 2*y < rect.bottom;
+ buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = 16;
+ buf[yuvTexOffsetV + (y * yuvTexStrideV) + x] =
+ inside ? 16 : 255;
+ }
+ }
+ }
+}
+
+void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
+ const size_t PIXEL_SIZE = 4;
+ for (int x = 0; x < w; x++) {
+ for (int y = 0; y < h; y++) {
+ off_t offset = (y * stride + x) * PIXEL_SIZE;
+ for (int c = 0; c < 4; c++) {
+ int parityX = (x / (1 << (c+2))) & 1;
+ int parityY = (y / (1 << (c+2))) & 1;
+ buf[offset + c] = (parityX ^ parityY) ? 231 : 35;
+ }
+ }
+ }
+}
+
+void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) {
+ android_native_buffer_t* anb;
+ ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
+ &anb));
+ ASSERT_TRUE(anb != NULL);
+
+ sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
+
+ uint8_t* img = NULL;
+ ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN,
+ (void**)(&img)));
+ fillRGBA8Buffer(img, buf->getWidth(), buf->getHeight(), buf->getStride());
+ ASSERT_EQ(NO_ERROR, buf->unlock());
+ ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf->getNativeBuffer(),
+ -1));
+}
+} // namespace android
diff --git a/libs/gui/tests/FillBuffer.h b/libs/gui/tests/FillBuffer.h
new file mode 100644
index 0000000..b584179
--- /dev/null
+++ b/libs/gui/tests/FillBuffer.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FILL_BUFFER_H
+#define ANDROID_FILL_BUFFER_H
+
+#include <system/window.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+// Fill a YV12 buffer with a multi-colored checkerboard pattern
+void fillYV12Buffer(uint8_t* buf, int w, int h, int stride);
+
+// Fill a YV12 buffer with red outside a given rectangle and green inside it.
+void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride,
+ const android_native_rect_t& rect);
+
+void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride);
+
+// Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern
+// using the CPU. This assumes that the ANativeWindow is already configured to
+// allow this to be done (e.g. the format is set to RGBA8).
+//
+// Calls to this function should be wrapped in an ASSERT_NO_FATAL_FAILURE().
+void produceOneRGBA8Frame(const sp<ANativeWindow>& anw);
+
+} // namespace android
+
+#endif
diff --git a/libs/gui/tests/FrameWaiter.h b/libs/gui/tests/FrameWaiter.h
new file mode 100644
index 0000000..bdedba6
--- /dev/null
+++ b/libs/gui/tests/FrameWaiter.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_FRAME_WAITER_H
+#define ANDROID_FRAME_WAITER_H
+
+#include <gui/GLConsumer.h>
+
+namespace android {
+
+class FrameWaiter : public GLConsumer::FrameAvailableListener {
+public:
+ FrameWaiter():
+ mPendingFrames(0) {
+ }
+
+ void waitForFrame() {
+ Mutex::Autolock lock(mMutex);
+ while (mPendingFrames == 0) {
+ mCondition.wait(mMutex);
+ }
+ mPendingFrames--;
+ }
+
+ virtual void onFrameAvailable() {
+ Mutex::Autolock lock(mMutex);
+ mPendingFrames++;
+ mCondition.signal();
+ }
+
+private:
+ int mPendingFrames;
+ Mutex mMutex;
+ Condition mCondition;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp
new file mode 100644
index 0000000..1739d9c
--- /dev/null
+++ b/libs/gui/tests/GLTest.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GLTest.h"
+
+#include <gui/Surface.h>
+
+#include <GLES2/gl2.h>
+
+namespace android {
+
+static int abs(int value) {
+ return value > 0 ? value : -value;
+}
+
+void GLTest::SetUp() {
+ const ::testing::TestInfo* const testInfo =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGV("Begin test: %s.%s", testInfo->test_case_name(), testInfo->name());
+
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
+
+ EGLint majorVersion;
+ EGLint minorVersion;
+ EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ RecordProperty("EglVersionMajor", majorVersion);
+ RecordProperty("EglVersionMinor", minorVersion);
+
+ EGLint numConfigs = 0;
+ EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mGlConfig, 1,
+ &numConfigs));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS");
+ if (displaySecsEnv != NULL) {
+ mDisplaySecs = atoi(displaySecsEnv);
+ if (mDisplaySecs < 0) {
+ mDisplaySecs = 0;
+ }
+ } else {
+ mDisplaySecs = 0;
+ }
+
+ if (mDisplaySecs > 0) {
+ mComposerClient = new SurfaceComposerClient;
+ ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
+
+ mSurfaceControl = mComposerClient->createSurface(
+ String8("Test Surface"), getSurfaceWidth(), getSurfaceHeight(),
+ PIXEL_FORMAT_RGB_888, 0);
+
+ ASSERT_TRUE(mSurfaceControl != NULL);
+ ASSERT_TRUE(mSurfaceControl->isValid());
+
+ SurfaceComposerClient::openGlobalTransaction();
+ ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF));
+ ASSERT_EQ(NO_ERROR, mSurfaceControl->show());
+ SurfaceComposerClient::closeGlobalTransaction();
+
+ sp<ANativeWindow> window = mSurfaceControl->getSurface();
+ mEglSurface = createWindowSurface(mEglDisplay, mGlConfig, window);
+ } else {
+ EGLint pbufferAttribs[] = {
+ EGL_WIDTH, getSurfaceWidth(),
+ EGL_HEIGHT, getSurfaceHeight(),
+ EGL_NONE };
+
+ mEglSurface = eglCreatePbufferSurface(mEglDisplay, mGlConfig,
+ pbufferAttribs);
+ }
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
+
+ mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT,
+ getContextAttribs());
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ EGLint w, h;
+ EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ RecordProperty("EglSurfaceWidth", w);
+ RecordProperty("EglSurfaceHeight", h);
+
+ glViewport(0, 0, w, h);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+}
+
+void GLTest::TearDown() {
+ // Display the result
+ if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) {
+ eglSwapBuffers(mEglDisplay, mEglSurface);
+ sleep(mDisplaySecs);
+ }
+
+ if (mComposerClient != NULL) {
+ mComposerClient->dispose();
+ }
+ if (mEglContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEglDisplay, mEglContext);
+ }
+ if (mEglSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ }
+ if (mEglDisplay != EGL_NO_DISPLAY) {
+ eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglTerminate(mEglDisplay);
+ }
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ const ::testing::TestInfo* const testInfo =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGV("End test: %s.%s", testInfo->test_case_name(), testInfo->name());
+}
+
+EGLint const* GLTest::getConfigAttribs() {
+ static const EGLint sDefaultConfigAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_DEPTH_SIZE, 16,
+ EGL_STENCIL_SIZE, 8,
+ EGL_NONE };
+
+ return sDefaultConfigAttribs;
+}
+
+EGLint const* GLTest::getContextAttribs() {
+ static const EGLint sDefaultContextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE };
+
+ return sDefaultContextAttribs;
+}
+
+EGLint GLTest::getSurfaceWidth() {
+ return 512;
+}
+
+EGLint GLTest::getSurfaceHeight() {
+ return 512;
+}
+
+EGLSurface GLTest::createWindowSurface(EGLDisplay display, EGLConfig config,
+ sp<ANativeWindow>& window) const {
+ return eglCreateWindowSurface(display, config, window.get(), NULL);
+}
+
+::testing::AssertionResult GLTest::checkPixel(int x, int y,
+ int r, int g, int b, int a, int tolerance) {
+ GLubyte pixel[4];
+ String8 msg;
+ glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
+ GLenum err = glGetError();
+ if (err != GL_NO_ERROR) {
+ msg += String8::format("error reading pixel: %#x", err);
+ while ((err = glGetError()) != GL_NO_ERROR) {
+ msg += String8::format(", %#x", err);
+ }
+ return ::testing::AssertionFailure(::testing::Message(msg.string()));
+ }
+ if (r >= 0 && abs(r - int(pixel[0])) > tolerance) {
+ msg += String8::format("r(%d isn't %d)", pixel[0], r);
+ }
+ if (g >= 0 && abs(g - int(pixel[1])) > tolerance) {
+ if (!msg.isEmpty()) {
+ msg += " ";
+ }
+ msg += String8::format("g(%d isn't %d)", pixel[1], g);
+ }
+ if (b >= 0 && abs(b - int(pixel[2])) > tolerance) {
+ if (!msg.isEmpty()) {
+ msg += " ";
+ }
+ msg += String8::format("b(%d isn't %d)", pixel[2], b);
+ }
+ if (a >= 0 && abs(a - int(pixel[3])) > tolerance) {
+ if (!msg.isEmpty()) {
+ msg += " ";
+ }
+ msg += String8::format("a(%d isn't %d)", pixel[3], a);
+ }
+ if (!msg.isEmpty()) {
+ return ::testing::AssertionFailure(::testing::Message(msg.string()));
+ } else {
+ return ::testing::AssertionSuccess();
+ }
+}
+
+::testing::AssertionResult GLTest::assertRectEq(const Rect &r1, const Rect &r2,
+ int tolerance) {
+ String8 msg;
+
+ if (abs(r1.left - r2.left) > tolerance) {
+ msg += String8::format("left(%d isn't %d)", r1.left, r2.left);
+ }
+ if (abs(r1.top - r2.top) > tolerance) {
+ if (!msg.isEmpty()) {
+ msg += " ";
+ }
+ msg += String8::format("top(%d isn't %d)", r1.top, r2.top);
+ }
+ if (abs(r1.right - r2.right) > tolerance) {
+ if (!msg.isEmpty()) {
+ msg += " ";
+ }
+ msg += String8::format("right(%d isn't %d)", r1.right, r2.right);
+ }
+ if (abs(r1.bottom - r2.bottom) > tolerance) {
+ if (!msg.isEmpty()) {
+ msg += " ";
+ }
+ msg += String8::format("bottom(%d isn't %d)", r1.bottom, r2.bottom);
+ }
+ if (!msg.isEmpty()) {
+ msg += String8::format(" R1: [%d %d %d %d] R2: [%d %d %d %d]",
+ r1.left, r1.top, r1.right, r1.bottom,
+ r2.left, r2.top, r2.right, r2.bottom);
+ fprintf(stderr, "assertRectEq: %s\n", msg.string());
+ return ::testing::AssertionFailure(::testing::Message(msg.string()));
+ } else {
+ return ::testing::AssertionSuccess();
+ }
+}
+
+void GLTest::loadShader(GLenum shaderType, const char* pSource,
+ GLuint* outShader) {
+ GLuint shader = glCreateShader(shaderType);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ if (shader) {
+ glShaderSource(shader, 1, &pSource, NULL);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glCompileShader(shader);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ GLint compiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ if (!compiled) {
+ GLint infoLen = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ if (infoLen) {
+ char* buf = (char*) malloc(infoLen);
+ if (buf) {
+ glGetShaderInfoLog(shader, infoLen, NULL, buf);
+ printf("Shader compile log:\n%s\n", buf);
+ free(buf);
+ FAIL();
+ }
+ } else {
+ char* buf = (char*) malloc(0x1000);
+ if (buf) {
+ glGetShaderInfoLog(shader, 0x1000, NULL, buf);
+ printf("Shader compile log:\n%s\n", buf);
+ free(buf);
+ FAIL();
+ }
+ }
+ glDeleteShader(shader);
+ shader = 0;
+ }
+ }
+ ASSERT_TRUE(shader != 0);
+ *outShader = shader;
+}
+
+void GLTest::createProgram(const char* pVertexSource,
+ const char* pFragmentSource, GLuint* outPgm) {
+ GLuint vertexShader, fragmentShader;
+ {
+ SCOPED_TRACE("compiling vertex shader");
+ ASSERT_NO_FATAL_FAILURE(loadShader(GL_VERTEX_SHADER, pVertexSource,
+ &vertexShader));
+ }
+ {
+ SCOPED_TRACE("compiling fragment shader");
+ ASSERT_NO_FATAL_FAILURE(loadShader(GL_FRAGMENT_SHADER, pFragmentSource,
+ &fragmentShader));
+ }
+
+ GLuint program = glCreateProgram();
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ if (program) {
+ glAttachShader(program, vertexShader);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glAttachShader(program, fragmentShader);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glLinkProgram(program);
+ GLint linkStatus = GL_FALSE;
+ glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ if (linkStatus != GL_TRUE) {
+ GLint bufLength = 0;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+ if (bufLength) {
+ char* buf = (char*) malloc(bufLength);
+ if (buf) {
+ glGetProgramInfoLog(program, bufLength, NULL, buf);
+ printf("Program link log:\n%s\n", buf);
+ free(buf);
+ FAIL();
+ }
+ }
+ glDeleteProgram(program);
+ program = 0;
+ }
+ }
+ glDeleteShader(vertexShader);
+ glDeleteShader(fragmentShader);
+ ASSERT_TRUE(program != 0);
+ *outPgm = program;
+}
+
+} // namespace android
diff --git a/libs/gui/tests/GLTest.h b/libs/gui/tests/GLTest.h
new file mode 100644
index 0000000..d3c4a95
--- /dev/null
+++ b/libs/gui/tests/GLTest.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_GL_TEST_H
+#define ANDROID_GL_TEST_H
+
+#include <gtest/gtest.h>
+
+#include <gui/SurfaceComposerClient.h>
+
+#include <EGL/egl.h>
+#include <GLES/gl.h>
+
+namespace android {
+
+class GLTest : public ::testing::Test {
+public:
+ static void loadShader(GLenum shaderType, const char* pSource,
+ GLuint* outShader);
+ static void createProgram(const char* pVertexSource,
+ const char* pFragmentSource, GLuint* outPgm);
+
+protected:
+ GLTest() :
+ mEglDisplay(EGL_NO_DISPLAY),
+ mEglSurface(EGL_NO_SURFACE),
+ mEglContext(EGL_NO_CONTEXT) {
+ }
+
+ virtual void SetUp();
+ virtual void TearDown();
+
+ virtual EGLint const* getConfigAttribs();
+ virtual EGLint const* getContextAttribs();
+ virtual EGLint getSurfaceWidth();
+ virtual EGLint getSurfaceHeight();
+ virtual EGLSurface createWindowSurface(EGLDisplay display, EGLConfig config,
+ sp<ANativeWindow>& window) const;
+
+ ::testing::AssertionResult checkPixel(int x, int y,
+ int r, int g, int b, int a, int tolerance = 2);
+ ::testing::AssertionResult assertRectEq(const Rect &r1, const Rect &r2,
+ int tolerance = 1);
+
+ int mDisplaySecs;
+ sp<SurfaceComposerClient> mComposerClient;
+ sp<SurfaceControl> mSurfaceControl;
+
+ EGLDisplay mEglDisplay;
+ EGLSurface mEglSurface;
+ EGLContext mEglContext;
+ EGLConfig mGlConfig;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp
new file mode 100644
index 0000000..aadfe61
--- /dev/null
+++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "IGraphicBufferProducer_test"
+//#define LOG_NDEBUG 0
+
+#include <gtest/gtest.h>
+
+#include <utils/String8.h>
+#include <utils/threads.h>
+
+#include <ui/GraphicBuffer.h>
+
+#include <gui/BufferQueue.h>
+#include <gui/IProducerListener.h>
+
+#include <vector>
+
+#define ASSERT_OK(x) ASSERT_EQ(OK, (x))
+#define EXPECT_OK(x) EXPECT_EQ(OK, (x))
+
+#define TEST_TOKEN ((IProducerListener*)(NULL))
+#define TEST_API NATIVE_WINDOW_API_CPU
+#define TEST_API_OTHER NATIVE_WINDOW_API_EGL // valid API that's not TEST_API
+#define TEST_CONTROLLED_BY_APP false
+#define TEST_PRODUCER_USAGE_BITS (0)
+
+// TODO: Make these public constants in a header
+enum {
+ // Default dimensions before setDefaultBufferSize is called
+ DEFAULT_WIDTH = 1,
+ DEFAULT_HEIGHT = 1,
+
+ // Default format before setDefaultBufferFormat is called
+ DEFAULT_FORMAT = HAL_PIXEL_FORMAT_RGBA_8888,
+
+ // Default transform hint before setTransformHint is called
+ DEFAULT_TRANSFORM_HINT = 0,
+};
+
+namespace android {
+
+namespace {
+// Parameters for a generic "valid" input for queueBuffer.
+const int64_t QUEUE_BUFFER_INPUT_TIMESTAMP = 1384888611;
+const bool QUEUE_BUFFER_INPUT_IS_AUTO_TIMESTAMP = false;
+const Rect QUEUE_BUFFER_INPUT_RECT = Rect(DEFAULT_WIDTH, DEFAULT_HEIGHT);
+const int QUEUE_BUFFER_INPUT_SCALING_MODE = 0;
+const int QUEUE_BUFFER_INPUT_TRANSFORM = 0;
+const bool QUEUE_BUFFER_INPUT_ASYNC = false;
+const sp<Fence> QUEUE_BUFFER_INPUT_FENCE = Fence::NO_FENCE;
+}; // namespace anonymous
+
+struct DummyConsumer : public BnConsumerListener {
+ virtual void onFrameAvailable() {}
+ virtual void onBuffersReleased() {}
+ virtual void onSidebandStreamChanged() {}
+};
+
+class IGraphicBufferProducerTest : public ::testing::Test {
+protected:
+
+ IGraphicBufferProducerTest() {}
+
+ virtual void SetUp() {
+ const ::testing::TestInfo* const testInfo =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
+ testInfo->name());
+
+ mDC = new DummyConsumer;
+
+ BufferQueue::createBufferQueue(&mProducer, &mConsumer);
+
+ // Test check: Can't connect producer if no consumer yet
+ ASSERT_EQ(NO_INIT, TryConnectProducer());
+
+ // Must connect consumer before producer connects will succeed.
+ ASSERT_OK(mConsumer->consumerConnect(mDC, /*controlledByApp*/false));
+ }
+
+ virtual void TearDown() {
+ const ::testing::TestInfo* const testInfo =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGV("End test: %s.%s", testInfo->test_case_name(),
+ testInfo->name());
+ }
+
+ status_t TryConnectProducer() {
+ IGraphicBufferProducer::QueueBufferOutput output;
+ return mProducer->connect(TEST_TOKEN,
+ TEST_API,
+ TEST_CONTROLLED_BY_APP,
+ &output);
+ // TODO: use params to vary token, api, producercontrolledbyapp, etc
+ }
+
+ // Connect to a producer in a 'correct' fashion.
+ // Precondition: Consumer is connected.
+ void ConnectProducer() {
+ ASSERT_OK(TryConnectProducer());
+ }
+
+ // Create a generic "valid" input for queueBuffer
+ // -- uses the default buffer format, width, etc.
+ static IGraphicBufferProducer::QueueBufferInput CreateBufferInput() {
+ return QueueBufferInputBuilder().build();
+ }
+
+ // Builder pattern to slightly vary *almost* correct input
+ // -- avoids copying and pasting
+ struct QueueBufferInputBuilder {
+ QueueBufferInputBuilder() {
+ timestamp = QUEUE_BUFFER_INPUT_TIMESTAMP;
+ isAutoTimestamp = QUEUE_BUFFER_INPUT_IS_AUTO_TIMESTAMP;
+ crop = QUEUE_BUFFER_INPUT_RECT;
+ scalingMode = QUEUE_BUFFER_INPUT_SCALING_MODE;
+ transform = QUEUE_BUFFER_INPUT_TRANSFORM;
+ async = QUEUE_BUFFER_INPUT_ASYNC;
+ fence = QUEUE_BUFFER_INPUT_FENCE;
+ }
+
+ IGraphicBufferProducer::QueueBufferInput build() {
+ return IGraphicBufferProducer::QueueBufferInput(
+ timestamp,
+ isAutoTimestamp,
+ crop,
+ scalingMode,
+ transform,
+ async,
+ fence);
+ }
+
+ QueueBufferInputBuilder& setTimestamp(int64_t timestamp) {
+ this->timestamp = timestamp;
+ return *this;
+ }
+
+ QueueBufferInputBuilder& setIsAutoTimestamp(bool isAutoTimestamp) {
+ this->isAutoTimestamp = isAutoTimestamp;
+ return *this;
+ }
+
+ QueueBufferInputBuilder& setCrop(Rect crop) {
+ this->crop = crop;
+ return *this;
+ }
+
+ QueueBufferInputBuilder& setScalingMode(int scalingMode) {
+ this->scalingMode = scalingMode;
+ return *this;
+ }
+
+ QueueBufferInputBuilder& setTransform(uint32_t transform) {
+ this->transform = transform;
+ return *this;
+ }
+
+ QueueBufferInputBuilder& setAsync(bool async) {
+ this->async = async;
+ return *this;
+ }
+
+ QueueBufferInputBuilder& setFence(sp<Fence> fence) {
+ this->fence = fence;
+ return *this;
+ }
+
+ private:
+ int64_t timestamp;
+ bool isAutoTimestamp;
+ Rect crop;
+ int scalingMode;
+ uint32_t transform;
+ int async;
+ sp<Fence> fence;
+ }; // struct QueueBufferInputBuilder
+
+ // To easily store dequeueBuffer results into containers
+ struct DequeueBufferResult {
+ int slot;
+ sp<Fence> fence;
+ };
+
+ status_t dequeueBuffer(bool async, uint32_t w, uint32_t h, uint32_t format, uint32_t usage, DequeueBufferResult* result) {
+ return mProducer->dequeueBuffer(&result->slot, &result->fence, async, w, h, format, usage);
+ }
+
+private: // hide from test body
+ sp<DummyConsumer> mDC;
+
+protected: // accessible from test body
+ sp<IGraphicBufferProducer> mProducer;
+ sp<IGraphicBufferConsumer> mConsumer;
+};
+
+TEST_F(IGraphicBufferProducerTest, ConnectFirst_ReturnsError) {
+ IGraphicBufferProducer::QueueBufferOutput output;
+
+ // NULL output returns BAD_VALUE
+ EXPECT_EQ(BAD_VALUE, mProducer->connect(TEST_TOKEN,
+ TEST_API,
+ TEST_CONTROLLED_BY_APP,
+ /*output*/NULL));
+
+ // Invalid API returns bad value
+ EXPECT_EQ(BAD_VALUE, mProducer->connect(TEST_TOKEN,
+ /*api*/0xDEADBEEF,
+ TEST_CONTROLLED_BY_APP,
+ &output));
+
+ // TODO: get a token from a dead process somehow
+}
+
+TEST_F(IGraphicBufferProducerTest, ConnectAgain_ReturnsError) {
+ ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+ // Can't connect when there is already a producer connected
+ IGraphicBufferProducer::QueueBufferOutput output;
+ EXPECT_EQ(BAD_VALUE, mProducer->connect(TEST_TOKEN,
+ TEST_API,
+ TEST_CONTROLLED_BY_APP,
+ &output));
+
+ ASSERT_OK(mConsumer->consumerDisconnect());
+ // Can't connect when IGBP is abandoned
+ EXPECT_EQ(NO_INIT, mProducer->connect(TEST_TOKEN,
+ TEST_API,
+ TEST_CONTROLLED_BY_APP,
+ &output));
+}
+
+TEST_F(IGraphicBufferProducerTest, Disconnect_Succeeds) {
+ ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+ ASSERT_OK(mProducer->disconnect(TEST_API));
+}
+
+
+TEST_F(IGraphicBufferProducerTest, Disconnect_ReturnsError) {
+ ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+ // Must disconnect with same API number
+ ASSERT_EQ(BAD_VALUE, mProducer->disconnect(TEST_API_OTHER));
+ // API must not be out of range
+ ASSERT_EQ(BAD_VALUE, mProducer->disconnect(/*api*/0xDEADBEEF));
+
+ // TODO: somehow kill mProducer so that this returns DEAD_OBJECT
+}
+
+TEST_F(IGraphicBufferProducerTest, Query_Succeeds) {
+ ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+ // TODO: Make these constants in header
+ const int DEFAULT_CONSUMER_USAGE_BITS = 0;
+
+ int value = -1;
+ EXPECT_OK(mProducer->query(NATIVE_WINDOW_WIDTH, &value));
+ EXPECT_EQ(DEFAULT_WIDTH, value);
+
+ EXPECT_OK(mProducer->query(NATIVE_WINDOW_HEIGHT, &value));
+ EXPECT_EQ(DEFAULT_HEIGHT, value);
+
+ EXPECT_OK(mProducer->query(NATIVE_WINDOW_FORMAT, &value));
+ EXPECT_EQ(DEFAULT_FORMAT, value);
+
+ EXPECT_OK(mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &value));
+ EXPECT_LE(0, value);
+ EXPECT_GE(BufferQueue::NUM_BUFFER_SLOTS, value);
+
+ EXPECT_OK(mProducer->query(NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &value));
+ EXPECT_FALSE(value); // Can't run behind when we haven't touched the queue
+
+ EXPECT_OK(mProducer->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &value));
+ EXPECT_EQ(DEFAULT_CONSUMER_USAGE_BITS, value);
+
+}
+
+TEST_F(IGraphicBufferProducerTest, Query_ReturnsError) {
+ ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+ // One past the end of the last 'query' enum value. Update this if we add more enums.
+ const int NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE = NATIVE_WINDOW_CONSUMER_USAGE_BITS + 1;
+
+ int value;
+ // What was out of range
+ EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/-1, &value));
+ EXPECT_EQ(BAD_VALUE, mProducer->query(/*what*/0xDEADBEEF, &value));
+ EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_QUERY_LAST_OFF_BY_ONE, &value));
+
+ // Some enums from window.h are 'invalid'
+ EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER, &value));
+ EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_CONCRETE_TYPE, &value));
+ EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_WIDTH, &value));
+ EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_DEFAULT_HEIGHT, &value));
+ EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_TRANSFORM_HINT, &value));
+ // TODO: Consider documented the above enums as unsupported or make a new enum for IGBP
+
+ // Value was NULL
+ EXPECT_EQ(BAD_VALUE, mProducer->query(NATIVE_WINDOW_FORMAT, /*value*/NULL));
+
+ ASSERT_OK(mConsumer->consumerDisconnect());
+
+ // BQ was abandoned
+ EXPECT_EQ(NO_INIT, mProducer->query(NATIVE_WINDOW_FORMAT, &value));
+
+ // TODO: other things in window.h that are supported by Surface::query
+ // but not by BufferQueue::query
+}
+
+// TODO: queue under more complicated situations not involving just a single buffer
+TEST_F(IGraphicBufferProducerTest, Queue_Succeeds) {
+ ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+ int dequeuedSlot = -1;
+ sp<Fence> dequeuedFence;
+
+ // XX: OK to assume first call returns this flag or not? Not really documented.
+ ASSERT_EQ(OK | IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence,
+ QUEUE_BUFFER_INPUT_ASYNC,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
+ TEST_PRODUCER_USAGE_BITS));
+
+ EXPECT_LE(0, dequeuedSlot);
+ EXPECT_GT(BufferQueue::NUM_BUFFER_SLOTS, dequeuedSlot);
+
+ // Request the buffer (pre-requisite for queueing)
+ sp<GraphicBuffer> dequeuedBuffer;
+ ASSERT_OK(mProducer->requestBuffer(dequeuedSlot, &dequeuedBuffer));
+
+ // A generic "valid" input
+ IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+ IGraphicBufferProducer::QueueBufferOutput output;
+
+ // Queue the buffer back into the BQ
+ ASSERT_OK(mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+ {
+ uint32_t width;
+ uint32_t height;
+ uint32_t transformHint;
+ uint32_t numPendingBuffers;
+
+ output.deflate(&width, &height, &transformHint, &numPendingBuffers);
+
+ EXPECT_EQ(DEFAULT_WIDTH, width);
+ EXPECT_EQ(DEFAULT_HEIGHT, height);
+ EXPECT_EQ(DEFAULT_TRANSFORM_HINT, transformHint);
+ EXPECT_EQ(1, numPendingBuffers); // since queueBuffer was called exactly once
+ }
+
+ // Buffer was not in the dequeued state
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+}
+
+TEST_F(IGraphicBufferProducerTest, Queue_ReturnsError) {
+ ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+ // Invalid slot number
+ {
+ // A generic "valid" input
+ IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+ IGraphicBufferProducer::QueueBufferOutput output;
+
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/-1, input, &output));
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/0xDEADBEEF, input, &output));
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(BufferQueue::NUM_BUFFER_SLOTS,
+ input, &output));
+ }
+
+ // Slot was not in the dequeued state (all slots start out in Free state)
+ {
+ IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+ IGraphicBufferProducer::QueueBufferOutput output;
+
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(/*slot*/0, input, &output));
+ }
+
+ // Put the slot into the "dequeued" state for the rest of the test
+ int dequeuedSlot = -1;
+ sp<Fence> dequeuedFence;
+
+ ASSERT_EQ(OK | IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence,
+ QUEUE_BUFFER_INPUT_ASYNC,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
+ TEST_PRODUCER_USAGE_BITS));
+
+ // Slot was enqueued without requesting a buffer
+ {
+ IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+ IGraphicBufferProducer::QueueBufferOutput output;
+
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+ }
+
+ // Request the buffer so that the rest of the tests don't fail on earlier checks.
+ sp<GraphicBuffer> dequeuedBuffer;
+ ASSERT_OK(mProducer->requestBuffer(dequeuedSlot, &dequeuedBuffer));
+
+ // Fence was NULL
+ {
+ sp<Fence> nullFence = NULL;
+
+ IGraphicBufferProducer::QueueBufferInput input =
+ QueueBufferInputBuilder().setFence(nullFence).build();
+ IGraphicBufferProducer::QueueBufferOutput output;
+
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+ }
+
+ // Scaling mode was unknown
+ {
+ IGraphicBufferProducer::QueueBufferInput input =
+ QueueBufferInputBuilder().setScalingMode(-1).build();
+ IGraphicBufferProducer::QueueBufferOutput output;
+
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+
+ input = QueueBufferInputBuilder().setScalingMode(0xDEADBEEF).build();
+
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+ }
+
+ // Crop rect is out of bounds of the buffer dimensions
+ {
+ IGraphicBufferProducer::QueueBufferInput input =
+ QueueBufferInputBuilder().setCrop(Rect(DEFAULT_WIDTH + 1, DEFAULT_HEIGHT + 1))
+ .build();
+ IGraphicBufferProducer::QueueBufferOutput output;
+
+ EXPECT_EQ(BAD_VALUE, mProducer->queueBuffer(dequeuedSlot, input, &output));
+ }
+
+ // Abandon the buffer queue so that the last test fails
+ ASSERT_OK(mConsumer->consumerDisconnect());
+
+ // The buffer queue has been abandoned.
+ {
+ IGraphicBufferProducer::QueueBufferInput input = CreateBufferInput();
+ IGraphicBufferProducer::QueueBufferOutput output;
+
+ EXPECT_EQ(NO_INIT, mProducer->queueBuffer(dequeuedSlot, input, &output));
+ }
+}
+
+TEST_F(IGraphicBufferProducerTest, CancelBuffer_DoesntCrash) {
+ ASSERT_NO_FATAL_FAILURE(ConnectProducer());
+
+ int dequeuedSlot = -1;
+ sp<Fence> dequeuedFence;
+
+ ASSERT_EQ(OK | IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence,
+ QUEUE_BUFFER_INPUT_ASYNC,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
+ TEST_PRODUCER_USAGE_BITS));
+
+ // No return code, but at least test that it doesn't blow up...
+ // TODO: add a return code
+ mProducer->cancelBuffer(dequeuedSlot, dequeuedFence);
+}
+
+TEST_F(IGraphicBufferProducerTest, SetBufferCount_Succeeds) {
+
+ // The producer does not wish to set a buffer count
+ EXPECT_OK(mProducer->setBufferCount(0)) << "bufferCount: " << 0;
+ // TODO: how to test "0" buffer count?
+
+ int minBuffers;
+ ASSERT_OK(mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minBuffers));
+
+ // The MIN_UNDEQUEUED_BUFFERS limit is exclusive, so need to increment by at least 1
+ minBuffers++;
+
+ ASSERT_OK(mProducer->setBufferCount(minBuffers)) << "bufferCount: " << minBuffers;
+
+ std::vector<DequeueBufferResult> dequeueList;
+
+ // Should now be able to dequeue up to minBuffers times
+ for (int i = 0; i < minBuffers; ++i) {
+ DequeueBufferResult result;
+
+ EXPECT_LE(OK,
+ dequeueBuffer(QUEUE_BUFFER_INPUT_ASYNC,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
+ TEST_PRODUCER_USAGE_BITS, &result))
+ << "iteration: " << i << ", slot: " << result.slot;
+
+ dequeueList.push_back(result);
+ }
+
+ // Cancel every buffer, so we can set buffer count again
+ for (int i = 0; i < minBuffers; ++i) {
+ DequeueBufferResult& result = dequeueList[i];
+ mProducer->cancelBuffer(result.slot, result.fence);
+ }
+
+ ASSERT_OK(mProducer->setBufferCount(BufferQueue::NUM_BUFFER_SLOTS));
+
+ // Should now be able to dequeue up to NUM_BUFFER_SLOTS times
+ for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; ++i) {
+ int dequeuedSlot = -1;
+ sp<Fence> dequeuedFence;
+
+ EXPECT_LE(OK,
+ mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence,
+ QUEUE_BUFFER_INPUT_ASYNC,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
+ TEST_PRODUCER_USAGE_BITS))
+ << "iteration: " << i << ", slot: " << dequeuedSlot;
+ }
+}
+
+TEST_F(IGraphicBufferProducerTest, SetBufferCount_Fails) {
+ int minBuffers;
+ ASSERT_OK(mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minBuffers));
+
+ // The MIN_UNDEQUEUED_BUFFERS limit is exclusive, so need to increment by at least 1
+ minBuffers++;
+
+ // Buffer count was out of range
+ EXPECT_EQ(BAD_VALUE, mProducer->setBufferCount(-1)) << "bufferCount: " << -1;
+ EXPECT_EQ(BAD_VALUE, mProducer->setBufferCount(minBuffers - 1)) << "bufferCount: " << minBuffers - 1;
+ EXPECT_EQ(BAD_VALUE, mProducer->setBufferCount(BufferQueue::NUM_BUFFER_SLOTS + 1))
+ << "bufferCount: " << BufferQueue::NUM_BUFFER_SLOTS + 1;
+
+ // Pre-requisite to fail out a valid setBufferCount call
+ {
+ int dequeuedSlot = -1;
+ sp<Fence> dequeuedFence;
+
+ ASSERT_LE(OK,
+ mProducer->dequeueBuffer(&dequeuedSlot, &dequeuedFence,
+ QUEUE_BUFFER_INPUT_ASYNC,
+ DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_FORMAT,
+ TEST_PRODUCER_USAGE_BITS))
+ << "slot: " << dequeuedSlot;
+ }
+
+ // Client has one or more buffers dequeued
+ EXPECT_EQ(BAD_VALUE, mProducer->setBufferCount(minBuffers)) << "bufferCount: " << minBuffers;
+
+ // Abandon buffer queue
+ ASSERT_OK(mConsumer->consumerDisconnect());
+
+ // Fail because the buffer queue was abandoned
+ EXPECT_EQ(NO_INIT, mProducer->setBufferCount(minBuffers)) << "bufferCount: " << minBuffers;
+
+}
+
+} // namespace android
diff --git a/libs/gui/tests/MultiTextureConsumer_test.cpp b/libs/gui/tests/MultiTextureConsumer_test.cpp
new file mode 100644
index 0000000..1eb6ef6
--- /dev/null
+++ b/libs/gui/tests/MultiTextureConsumer_test.cpp
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "MultiTextureConsumer_test"
+//#define LOG_NDEBUG 0
+
+#include "GLTest.h"
+
+#include <gui/GLConsumer.h>
+#include <gui/Surface.h>
+
+#include <android/native_window.h>
+
+#include <GLES/glext.h>
+
+namespace android {
+
+class MultiTextureConsumerTest : public GLTest {
+protected:
+ enum { TEX_ID = 123 };
+
+ virtual void SetUp() {
+ GLTest::SetUp();
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ mGlConsumer = new GLConsumer(consumer, TEX_ID);
+ mSurface = new Surface(producer);
+ mANW = mSurface.get();
+
+ }
+ virtual void TearDown() {
+ GLTest::TearDown();
+ }
+ virtual EGLint const* getContextAttribs() {
+ return NULL;
+ }
+ virtual EGLint const* getConfigAttribs() {
+ static EGLint sDefaultConfigAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_NONE };
+
+ return sDefaultConfigAttribs;
+ }
+ sp<GLConsumer> mGlConsumer;
+ sp<Surface> mSurface;
+ ANativeWindow* mANW;
+};
+
+TEST_F(MultiTextureConsumerTest, EGLImageTargetWorks) {
+ ANativeWindow_Buffer buffer;
+
+ ASSERT_EQ(native_window_set_usage(mANW, GRALLOC_USAGE_SW_WRITE_OFTEN), NO_ERROR);
+ ASSERT_EQ(native_window_set_buffers_format(mANW, HAL_PIXEL_FORMAT_RGBA_8888), NO_ERROR);
+
+ glShadeModel(GL_FLAT);
+ glDisable(GL_DITHER);
+ glDisable(GL_CULL_FACE);
+ glViewport(0, 0, getSurfaceWidth(), getSurfaceHeight());
+ glOrthof(0, getSurfaceWidth(), 0, getSurfaceHeight(), 0, 1);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glColor4f(1, 1, 1, 1);
+
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID);
+ glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+ uint32_t texel = 0x80808080;
+ glBindTexture(GL_TEXTURE_2D, TEX_ID+1);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texel);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, TEX_ID+1);
+ glEnable(GL_TEXTURE_2D);
+ glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID);
+ glEnable(GL_TEXTURE_EXTERNAL_OES);
+ glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+ for (int i=0 ; i<8 ; i++) {
+ mSurface->lock(&buffer, NULL);
+ memset(buffer.bits, (i&7) * 0x20, buffer.stride * buffer.height * 4);
+ mSurface->unlockAndPost();
+
+ mGlConsumer->updateTexImage();
+
+ GLfloat vertices[][2] = { {i*16.0f, 0}, {(i+1)*16.0f, 0}, {(i+1)*16.0f, 16.0f}, {i*16.0f, 16.0f} };
+ glVertexPointer(2, GL_FLOAT, 0, vertices);
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ }
+
+ for (int i=0 ; i<8 ; i++) {
+ EXPECT_TRUE(checkPixel(i*16 + 8, 8, i*16, i*16, i*16, i*16, 0));
+ }
+}
+
+} // namespace android
diff --git a/libs/gui/tests/SRGB_test.cpp b/libs/gui/tests/SRGB_test.cpp
new file mode 100644
index 0000000..2d5b8aa
--- /dev/null
+++ b/libs/gui/tests/SRGB_test.cpp
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SRGB_test"
+//#define LOG_NDEBUG 0
+
+#include "GLTest.h"
+
+#include <gui/CpuConsumer.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
+
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GLES3/gl3.h>
+
+#include <android/native_window.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+class SRGBTest : public ::testing::Test {
+protected:
+ // Class constants
+ enum {
+ DISPLAY_WIDTH = 512,
+ DISPLAY_HEIGHT = 512,
+ PIXEL_SIZE = 4, // bytes or components
+ DISPLAY_SIZE = DISPLAY_WIDTH * DISPLAY_HEIGHT * PIXEL_SIZE,
+ ALPHA_VALUE = 223, // should be in [0, 255]
+ TOLERANCE = 1,
+ };
+ static const char SHOW_DEBUG_STRING[];
+
+ SRGBTest() :
+ mInputSurface(), mCpuConsumer(), mLockedBuffer(),
+ mEglDisplay(EGL_NO_DISPLAY), mEglConfig(),
+ mEglContext(EGL_NO_CONTEXT), mEglSurface(EGL_NO_SURFACE),
+ mComposerClient(), mSurfaceControl(), mOutputSurface() {
+ }
+
+ virtual ~SRGBTest() {
+ if (mEglDisplay != EGL_NO_DISPLAY) {
+ if (mEglSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(mEglDisplay, mEglSurface);
+ }
+ if (mEglContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEglDisplay, mEglContext);
+ }
+ eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ eglTerminate(mEglDisplay);
+ }
+ }
+
+ virtual void SetUp() {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ ASSERT_EQ(NO_ERROR, consumer->setDefaultBufferSize(
+ DISPLAY_WIDTH, DISPLAY_HEIGHT));
+ mCpuConsumer = new CpuConsumer(consumer, 1);
+ String8 name("CpuConsumer_for_SRGBTest");
+ mCpuConsumer->setName(name);
+ mInputSurface = new Surface(producer);
+
+ ASSERT_NO_FATAL_FAILURE(createEGLSurface(mInputSurface.get()));
+ ASSERT_NO_FATAL_FAILURE(createDebugSurface());
+ }
+
+ virtual void TearDown() {
+ ASSERT_NO_FATAL_FAILURE(copyToDebugSurface());
+ ASSERT_TRUE(mLockedBuffer.data != NULL);
+ ASSERT_EQ(NO_ERROR, mCpuConsumer->unlockBuffer(mLockedBuffer));
+ }
+
+ static float linearToSRGB(float l) {
+ if (l <= 0.0031308f) {
+ return l * 12.92f;
+ } else {
+ return 1.055f * pow(l, (1 / 2.4f)) - 0.055f;
+ }
+ }
+
+ static float srgbToLinear(float s) {
+ if (s <= 0.04045) {
+ return s / 12.92f;
+ } else {
+ return pow(((s + 0.055f) / 1.055f), 2.4f);
+ }
+ }
+
+ static uint8_t srgbToLinear(uint8_t u) {
+ float f = u / 255.0f;
+ return static_cast<uint8_t>(srgbToLinear(f) * 255.0f + 0.5f);
+ }
+
+ void fillTexture(bool writeAsSRGB) {
+ uint8_t* textureData = new uint8_t[DISPLAY_SIZE];
+
+ for (int y = 0; y < DISPLAY_HEIGHT; ++y) {
+ for (int x = 0; x < DISPLAY_WIDTH; ++x) {
+ float realValue = static_cast<float>(x) / (DISPLAY_WIDTH - 1);
+ realValue *= ALPHA_VALUE / 255.0f; // Premultiply by alpha
+ if (writeAsSRGB) {
+ realValue = linearToSRGB(realValue);
+ }
+
+ int offset = (y * DISPLAY_WIDTH + x) * PIXEL_SIZE;
+ for (int c = 0; c < 3; ++c) {
+ uint8_t intValue = static_cast<uint8_t>(
+ realValue * 255.0f + 0.5f);
+ textureData[offset + c] = intValue;
+ }
+ textureData[offset + 3] = ALPHA_VALUE;
+ }
+ }
+
+ glTexImage2D(GL_TEXTURE_2D, 0, writeAsSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE,
+ textureData);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+
+ delete[] textureData;
+ }
+
+ void initShaders() {
+ static const char vertexSource[] =
+ "attribute vec4 vPosition;\n"
+ "varying vec2 texCoords;\n"
+ "void main() {\n"
+ " texCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n"
+ " gl_Position = vPosition;\n"
+ "}\n";
+
+ static const char fragmentSource[] =
+ "precision mediump float;\n"
+ "uniform sampler2D texSampler;\n"
+ "varying vec2 texCoords;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(texSampler, texCoords);\n"
+ "}\n";
+
+ GLuint program;
+ {
+ SCOPED_TRACE("Creating shader program");
+ ASSERT_NO_FATAL_FAILURE(GLTest::createProgram(
+ vertexSource, fragmentSource, &program));
+ }
+
+ GLint positionHandle = glGetAttribLocation(program, "vPosition");
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ ASSERT_NE(-1, positionHandle);
+
+ GLint samplerHandle = glGetUniformLocation(program, "texSampler");
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ ASSERT_NE(-1, samplerHandle);
+
+ static const GLfloat vertices[] = {
+ -1.0f, 1.0f,
+ -1.0f, -1.0f,
+ 1.0f, -1.0f,
+ 1.0f, 1.0f,
+ };
+
+ glVertexAttribPointer(positionHandle, 2, GL_FLOAT, GL_FALSE, 0, vertices);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ glEnableVertexAttribArray(positionHandle);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+
+ glUseProgram(program);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ glUniform1i(samplerHandle, 0);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+
+ GLuint textureHandle;
+ glGenTextures(1, &textureHandle);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ glBindTexture(GL_TEXTURE_2D, textureHandle);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ }
+
+ void drawTexture(bool asSRGB, GLint x, GLint y, GLsizei width,
+ GLsizei height) {
+ ASSERT_NO_FATAL_FAILURE(fillTexture(asSRGB));
+ glViewport(x, y, width, height);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ ASSERT_EQ(GL_NO_ERROR, glGetError());
+ }
+
+ void checkLockedBuffer(PixelFormat format) {
+ ASSERT_EQ(mLockedBuffer.format, format);
+ ASSERT_EQ(mLockedBuffer.width, DISPLAY_WIDTH);
+ ASSERT_EQ(mLockedBuffer.height, DISPLAY_HEIGHT);
+ }
+
+ static bool withinTolerance(int a, int b) {
+ int diff = a - b;
+ return diff >= 0 ? diff <= TOLERANCE : -diff <= TOLERANCE;
+ }
+
+ // Primary producer and consumer
+ sp<Surface> mInputSurface;
+ sp<CpuConsumer> mCpuConsumer;
+ CpuConsumer::LockedBuffer mLockedBuffer;
+
+ EGLDisplay mEglDisplay;
+ EGLConfig mEglConfig;
+ EGLContext mEglContext;
+ EGLSurface mEglSurface;
+
+ // Auxiliary display output
+ sp<SurfaceComposerClient> mComposerClient;
+ sp<SurfaceControl> mSurfaceControl;
+ sp<Surface> mOutputSurface;
+
+private:
+ void createEGLSurface(Surface* inputSurface) {
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
+
+ EXPECT_TRUE(eglInitialize(mEglDisplay, NULL, NULL));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ static const EGLint configAttribs[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_NONE };
+
+ EGLint numConfigs = 0;
+ EXPECT_TRUE(eglChooseConfig(mEglDisplay, configAttribs, &mEglConfig, 1,
+ &numConfigs));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_GT(numConfigs, 0);
+
+ static const EGLint contextAttribs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 3,
+ EGL_NONE } ;
+
+ mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT,
+ contextAttribs);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
+
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig,
+ inputSurface, NULL);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ }
+
+ void createDebugSurface() {
+ if (getenv(SHOW_DEBUG_STRING) == NULL) return;
+
+ mComposerClient = new SurfaceComposerClient;
+ ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
+
+ mSurfaceControl = mComposerClient->createSurface(
+ String8("SRGBTest Surface"), DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ PIXEL_FORMAT_RGBA_8888);
+
+ ASSERT_TRUE(mSurfaceControl != NULL);
+ ASSERT_TRUE(mSurfaceControl->isValid());
+
+ SurfaceComposerClient::openGlobalTransaction();
+ ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF));
+ ASSERT_EQ(NO_ERROR, mSurfaceControl->show());
+ SurfaceComposerClient::closeGlobalTransaction();
+
+ ANativeWindow_Buffer outBuffer;
+ ARect inOutDirtyBounds;
+ mOutputSurface = mSurfaceControl->getSurface();
+ mOutputSurface->lock(&outBuffer, &inOutDirtyBounds);
+ uint8_t* bytePointer = reinterpret_cast<uint8_t*>(outBuffer.bits);
+ for (int y = 0; y < outBuffer.height; ++y) {
+ int rowOffset = y * outBuffer.stride; // pixels
+ for (int x = 0; x < outBuffer.width; ++x) {
+ int colOffset = (rowOffset + x) * PIXEL_SIZE; // bytes
+ for (int c = 0; c < PIXEL_SIZE; ++c) {
+ int offset = colOffset + c;
+ bytePointer[offset] = ((c + 1) * 56) - 1;
+ }
+ }
+ }
+ mOutputSurface->unlockAndPost();
+ }
+
+ void copyToDebugSurface() {
+ if (!mOutputSurface.get()) return;
+
+ size_t bufferSize = mLockedBuffer.height * mLockedBuffer.stride *
+ PIXEL_SIZE;
+
+ ANativeWindow_Buffer outBuffer;
+ ARect outBufferBounds;
+ mOutputSurface->lock(&outBuffer, &outBufferBounds);
+ ASSERT_EQ(mLockedBuffer.width, outBuffer.width);
+ ASSERT_EQ(mLockedBuffer.height, outBuffer.height);
+ ASSERT_EQ(mLockedBuffer.stride, outBuffer.stride);
+
+ if (mLockedBuffer.format == outBuffer.format) {
+ memcpy(outBuffer.bits, mLockedBuffer.data, bufferSize);
+ } else {
+ ASSERT_EQ(mLockedBuffer.format, PIXEL_FORMAT_sRGB_A_8888);
+ ASSERT_EQ(outBuffer.format, PIXEL_FORMAT_RGBA_8888);
+ uint8_t* outPointer = reinterpret_cast<uint8_t*>(outBuffer.bits);
+ for (int y = 0; y < outBuffer.height; ++y) {
+ int rowOffset = y * outBuffer.stride; // pixels
+ for (int x = 0; x < outBuffer.width; ++x) {
+ int colOffset = (rowOffset + x) * PIXEL_SIZE; // bytes
+
+ // RGB are converted
+ for (int c = 0; c < (PIXEL_SIZE - 1); ++c) {
+ outPointer[colOffset + c] = srgbToLinear(
+ mLockedBuffer.data[colOffset + c]);
+ }
+
+ // Alpha isn't converted
+ outPointer[colOffset + 3] =
+ mLockedBuffer.data[colOffset + 3];
+ }
+ }
+ }
+ mOutputSurface->unlockAndPost();
+
+ int sleepSeconds = atoi(getenv(SHOW_DEBUG_STRING));
+ sleep(sleepSeconds);
+ }
+};
+
+const char SRGBTest::SHOW_DEBUG_STRING[] = "DEBUG_OUTPUT_SECONDS";
+
+TEST_F(SRGBTest, GLRenderFromSRGBTexture) {
+ ASSERT_NO_FATAL_FAILURE(initShaders());
+
+ // The RGB texture is displayed in the top half
+ ASSERT_NO_FATAL_FAILURE(drawTexture(false, 0, DISPLAY_HEIGHT / 2,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT / 2));
+
+ // The SRGB texture is displayed in the bottom half
+ ASSERT_NO_FATAL_FAILURE(drawTexture(true, 0, 0,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT / 2));
+
+ eglSwapBuffers(mEglDisplay, mEglSurface);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Lock
+ ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer));
+ ASSERT_NO_FATAL_FAILURE(checkLockedBuffer(PIXEL_FORMAT_RGBA_8888));
+
+ // Compare a pixel in the middle of each texture
+ int midSRGBOffset = (DISPLAY_HEIGHT / 4) * mLockedBuffer.stride *
+ PIXEL_SIZE;
+ int midRGBOffset = midSRGBOffset * 3;
+ midRGBOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE;
+ midSRGBOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE;
+ for (int c = 0; c < PIXEL_SIZE; ++c) {
+ int expectedValue = mLockedBuffer.data[midRGBOffset + c];
+ int actualValue = mLockedBuffer.data[midSRGBOffset + c];
+ ASSERT_PRED2(withinTolerance, expectedValue, actualValue);
+ }
+
+ // mLockedBuffer is unlocked in TearDown so we can copy data from it to
+ // the debug surface if necessary
+}
+
+TEST_F(SRGBTest, RenderToSRGBSurface) {
+ ASSERT_NO_FATAL_FAILURE(initShaders());
+
+ // By default, the first buffer we write into will be RGB
+
+ // Render an RGB texture across the whole surface
+ ASSERT_NO_FATAL_FAILURE(drawTexture(false, 0, 0,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT));
+ eglSwapBuffers(mEglDisplay, mEglSurface);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Lock
+ ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer));
+ ASSERT_NO_FATAL_FAILURE(checkLockedBuffer(PIXEL_FORMAT_RGBA_8888));
+
+ // Save the values of the middle pixel for later comparison against SRGB
+ uint8_t values[PIXEL_SIZE] = {};
+ int middleOffset = (DISPLAY_HEIGHT / 2) * mLockedBuffer.stride *
+ PIXEL_SIZE;
+ middleOffset += (DISPLAY_WIDTH / 2) * PIXEL_SIZE;
+ for (int c = 0; c < PIXEL_SIZE; ++c) {
+ values[c] = mLockedBuffer.data[middleOffset + c];
+ }
+
+ // Unlock
+ ASSERT_EQ(NO_ERROR, mCpuConsumer->unlockBuffer(mLockedBuffer));
+
+ // Switch to SRGB window surface
+#define EGL_GL_COLORSPACE_KHR EGL_VG_COLORSPACE
+#define EGL_GL_COLORSPACE_SRGB_KHR EGL_VG_COLORSPACE_sRGB
+
+ static const int srgbAttribs[] = {
+ EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR,
+ EGL_NONE,
+ };
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ EXPECT_TRUE(eglDestroySurface(mEglDisplay, mEglSurface));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig,
+ mInputSurface.get(), srgbAttribs);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Render the texture again
+ ASSERT_NO_FATAL_FAILURE(drawTexture(false, 0, 0,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT));
+ eglSwapBuffers(mEglDisplay, mEglSurface);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Lock
+ ASSERT_EQ(NO_ERROR, mCpuConsumer->lockNextBuffer(&mLockedBuffer));
+
+ // Make sure we actually got the SRGB buffer on the consumer side
+ ASSERT_NO_FATAL_FAILURE(checkLockedBuffer(PIXEL_FORMAT_sRGB_A_8888));
+
+ // Verify that the stored value is the same, accounting for RGB/SRGB
+ for (int c = 0; c < PIXEL_SIZE; ++c) {
+ // The alpha value should be equivalent before linear->SRGB
+ float rgbAsSRGB = (c == 3) ? values[c] / 255.0f :
+ linearToSRGB(values[c] / 255.0f);
+ int expectedValue = rgbAsSRGB * 255.0f + 0.5f;
+ int actualValue = mLockedBuffer.data[middleOffset + c];
+ ASSERT_PRED2(withinTolerance, expectedValue, actualValue);
+ }
+
+ // mLockedBuffer is unlocked in TearDown so we can copy data from it to
+ // the debug surface if necessary
+}
+
+} // namespace android
diff --git a/libs/gui/tests/StreamSplitter_test.cpp b/libs/gui/tests/StreamSplitter_test.cpp
new file mode 100644
index 0000000..32ec90d
--- /dev/null
+++ b/libs/gui/tests/StreamSplitter_test.cpp
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2014 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 "StreamSplitter_test"
+//#define LOG_NDEBUG 0
+
+#include <gui/BufferQueue.h>
+#include <gui/IConsumerListener.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/StreamSplitter.h>
+#include <private/gui/ComposerService.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+class StreamSplitterTest : public ::testing::Test {
+
+protected:
+ StreamSplitterTest() {
+ const ::testing::TestInfo* const testInfo =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
+ testInfo->name());
+ }
+
+ ~StreamSplitterTest() {
+ const ::testing::TestInfo* const testInfo =
+ ::testing::UnitTest::GetInstance()->current_test_info();
+ ALOGV("End test: %s.%s", testInfo->test_case_name(),
+ testInfo->name());
+ }
+};
+
+struct DummyListener : public BnConsumerListener {
+ virtual void onFrameAvailable() {}
+ virtual void onBuffersReleased() {}
+ virtual void onSidebandStreamChanged() {}
+};
+
+class CountedAllocator : public BnGraphicBufferAlloc {
+public:
+ CountedAllocator() : mAllocCount(0) {
+ sp<ISurfaceComposer> composer(ComposerService::getComposerService());
+ mAllocator = composer->createGraphicBufferAlloc();
+ }
+
+ virtual ~CountedAllocator() {}
+
+ virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t w, uint32_t h,
+ PixelFormat format, uint32_t usage, status_t* error) {
+ ++mAllocCount;
+ sp<GraphicBuffer> buffer = mAllocator->createGraphicBuffer(w, h, format,
+ usage, error);
+ return buffer;
+ }
+
+ int getAllocCount() const { return mAllocCount; }
+
+private:
+ sp<IGraphicBufferAlloc> mAllocator;
+ int mAllocCount;
+};
+
+TEST_F(StreamSplitterTest, OneInputOneOutput) {
+ sp<CountedAllocator> allocator(new CountedAllocator);
+
+ sp<IGraphicBufferProducer> inputProducer;
+ sp<IGraphicBufferConsumer> inputConsumer;
+ BufferQueue::createBufferQueue(&inputProducer, &inputConsumer, allocator);
+
+ sp<IGraphicBufferProducer> outputProducer;
+ sp<IGraphicBufferConsumer> outputConsumer;
+ BufferQueue::createBufferQueue(&outputProducer, &outputConsumer, allocator);
+ ASSERT_EQ(OK, outputConsumer->consumerConnect(new DummyListener, false));
+
+ sp<StreamSplitter> splitter;
+ status_t status = StreamSplitter::createSplitter(inputConsumer, &splitter);
+ ASSERT_EQ(OK, status);
+ ASSERT_EQ(OK, splitter->addOutput(outputProducer));
+
+ IGraphicBufferProducer::QueueBufferOutput qbOutput;
+ ASSERT_EQ(OK, inputProducer->connect(new DummyProducerListener,
+ NATIVE_WINDOW_API_CPU, false, &qbOutput));
+
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buffer;
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+ ASSERT_EQ(OK, inputProducer->requestBuffer(slot, &buffer));
+
+ uint32_t* dataIn;
+ ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+ reinterpret_cast<void**>(&dataIn)));
+ *dataIn = 0x12345678;
+ ASSERT_EQ(OK, buffer->unlock());
+
+ IGraphicBufferProducer::QueueBufferInput qbInput(0, false,
+ Rect(0, 0, 1, 1), NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false,
+ Fence::NO_FENCE);
+ ASSERT_EQ(OK, inputProducer->queueBuffer(slot, qbInput, &qbOutput));
+
+ IGraphicBufferConsumer::BufferItem item;
+ ASSERT_EQ(OK, outputConsumer->acquireBuffer(&item, 0));
+
+ uint32_t* dataOut;
+ ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
+ reinterpret_cast<void**>(&dataOut)));
+ ASSERT_EQ(*dataOut, 0x12345678);
+ ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
+
+ ASSERT_EQ(OK, outputConsumer->releaseBuffer(item.mBuf, item.mFrameNumber,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
+
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+ ASSERT_EQ(1, allocator->getAllocCount());
+}
+
+TEST_F(StreamSplitterTest, OneInputMultipleOutputs) {
+ const int NUM_OUTPUTS = 4;
+ sp<CountedAllocator> allocator(new CountedAllocator);
+
+ sp<IGraphicBufferProducer> inputProducer;
+ sp<IGraphicBufferConsumer> inputConsumer;
+ BufferQueue::createBufferQueue(&inputProducer, &inputConsumer, allocator);
+
+ sp<IGraphicBufferProducer> outputProducers[NUM_OUTPUTS] = {};
+ sp<IGraphicBufferConsumer> outputConsumers[NUM_OUTPUTS] = {};
+ for (int output = 0; output < NUM_OUTPUTS; ++output) {
+ BufferQueue::createBufferQueue(&outputProducers[output],
+ &outputConsumers[output], allocator);
+ ASSERT_EQ(OK, outputConsumers[output]->consumerConnect(
+ new DummyListener, false));
+ }
+
+ sp<StreamSplitter> splitter;
+ status_t status = StreamSplitter::createSplitter(inputConsumer, &splitter);
+ ASSERT_EQ(OK, status);
+ for (int output = 0; output < NUM_OUTPUTS; ++output) {
+ ASSERT_EQ(OK, splitter->addOutput(outputProducers[output]));
+ }
+
+ IGraphicBufferProducer::QueueBufferOutput qbOutput;
+ ASSERT_EQ(OK, inputProducer->connect(new DummyProducerListener,
+ NATIVE_WINDOW_API_CPU, false, &qbOutput));
+
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buffer;
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+ ASSERT_EQ(OK, inputProducer->requestBuffer(slot, &buffer));
+
+ uint32_t* dataIn;
+ ASSERT_EQ(OK, buffer->lock(GraphicBuffer::USAGE_SW_WRITE_OFTEN,
+ reinterpret_cast<void**>(&dataIn)));
+ *dataIn = 0x12345678;
+ ASSERT_EQ(OK, buffer->unlock());
+
+ IGraphicBufferProducer::QueueBufferInput qbInput(0, false,
+ Rect(0, 0, 1, 1), NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false,
+ Fence::NO_FENCE);
+ ASSERT_EQ(OK, inputProducer->queueBuffer(slot, qbInput, &qbOutput));
+
+ for (int output = 0; output < NUM_OUTPUTS; ++output) {
+ IGraphicBufferConsumer::BufferItem item;
+ ASSERT_EQ(OK, outputConsumers[output]->acquireBuffer(&item, 0));
+
+ uint32_t* dataOut;
+ ASSERT_EQ(OK, item.mGraphicBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN,
+ reinterpret_cast<void**>(&dataOut)));
+ ASSERT_EQ(*dataOut, 0x12345678);
+ ASSERT_EQ(OK, item.mGraphicBuffer->unlock());
+
+ ASSERT_EQ(OK, outputConsumers[output]->releaseBuffer(item.mBuf,
+ item.mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
+ Fence::NO_FENCE));
+ }
+
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+ ASSERT_EQ(1, allocator->getAllocCount());
+}
+
+TEST_F(StreamSplitterTest, OutputAbandonment) {
+ sp<IGraphicBufferProducer> inputProducer;
+ sp<IGraphicBufferConsumer> inputConsumer;
+ BufferQueue::createBufferQueue(&inputProducer, &inputConsumer);
+
+ sp<IGraphicBufferProducer> outputProducer;
+ sp<IGraphicBufferConsumer> outputConsumer;
+ BufferQueue::createBufferQueue(&outputProducer, &outputConsumer);
+ ASSERT_EQ(OK, outputConsumer->consumerConnect(new DummyListener, false));
+
+ sp<StreamSplitter> splitter;
+ status_t status = StreamSplitter::createSplitter(inputConsumer, &splitter);
+ ASSERT_EQ(OK, status);
+ ASSERT_EQ(OK, splitter->addOutput(outputProducer));
+
+ IGraphicBufferProducer::QueueBufferOutput qbOutput;
+ ASSERT_EQ(OK, inputProducer->connect(new DummyProducerListener,
+ NATIVE_WINDOW_API_CPU, false, &qbOutput));
+
+ int slot;
+ sp<Fence> fence;
+ sp<GraphicBuffer> buffer;
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
+ inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0, 0,
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+ ASSERT_EQ(OK, inputProducer->requestBuffer(slot, &buffer));
+
+ // Abandon the output
+ outputConsumer->consumerDisconnect();
+
+ IGraphicBufferProducer::QueueBufferInput qbInput(0, false,
+ Rect(0, 0, 1, 1), NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, false,
+ Fence::NO_FENCE);
+ ASSERT_EQ(OK, inputProducer->queueBuffer(slot, qbInput, &qbOutput));
+
+ // Input should be abandoned
+ ASSERT_EQ(NO_INIT, inputProducer->dequeueBuffer(&slot, &fence, false, 0, 0,
+ 0, GRALLOC_USAGE_SW_WRITE_OFTEN));
+}
+
+} // namespace android
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index 989fcef..7f9fcc4 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -43,9 +43,11 @@ protected:
ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
testInfo->name());
- sp<BufferQueue> bq = new BufferQueue();
- mST = new GLConsumer(bq, 123);
- mSTC = new Surface(bq);
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ mST = new GLConsumer(consumer, 123);
+ mSTC = new Surface(producer);
mANW = mSTC;
// We need a valid GL context so we can test updateTexImage()
@@ -711,9 +713,11 @@ protected:
ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
for (int i = 0; i < NUM_SURFACE_TEXTURES; i++) {
- sp<BufferQueue> bq = new BufferQueue();
- sp<GLConsumer> st(new GLConsumer(bq, i));
- sp<Surface> stc(new Surface(bq));
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ sp<GLConsumer> st(new GLConsumer(consumer, i));
+ sp<Surface> stc(new Surface(producer));
mEglSurfaces[i] = eglCreateWindowSurface(mEglDisplay, myConfig,
static_cast<ANativeWindow*>(stc.get()), NULL);
ASSERT_EQ(EGL_SUCCESS, eglGetError());
diff --git a/libs/gui/tests/SurfaceTextureFBO.h b/libs/gui/tests/SurfaceTextureFBO.h
new file mode 100644
index 0000000..7f1ae84
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureFBO.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SURFACE_TEXTURE_FBO_H
+#define ANDROID_SURFACE_TEXTURE_FBO_H
+
+#include "SurfaceTextureGL.h"
+
+#include <GLES2/gl2.h>
+
+namespace android {
+
+class SurfaceTextureFBOTest : public SurfaceTextureGLTest {
+protected:
+ virtual void SetUp() {
+ SurfaceTextureGLTest::SetUp();
+
+ glGenFramebuffers(1, &mFbo);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+ glGenTextures(1, &mFboTex);
+ glBindTexture(GL_TEXTURE_2D, mFboTex);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getSurfaceWidth(),
+ getSurfaceHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+ glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, mFboTex, 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ }
+
+ virtual void TearDown() {
+ SurfaceTextureGLTest::TearDown();
+
+ glDeleteTextures(1, &mFboTex);
+ glDeleteFramebuffers(1, &mFbo);
+ }
+
+ GLuint mFbo;
+ GLuint mFboTex;
+};
+
+void fillRGBA8BufferSolid(uint8_t* buf, int w, int h, int stride,
+ uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
+ const size_t PIXEL_SIZE = 4;
+ for (int y = 0; y < h; y++) {
+ for (int x = 0; x < w; x++) {
+ off_t offset = (y * stride + x) * PIXEL_SIZE;
+ buf[offset + 0] = r;
+ buf[offset + 1] = g;
+ buf[offset + 2] = b;
+ buf[offset + 3] = a;
+ }
+ }
+}
+
+} // namespace android
+
+#endif
diff --git a/libs/gui/tests/SurfaceTextureFBO_test.cpp b/libs/gui/tests/SurfaceTextureFBO_test.cpp
new file mode 100644
index 0000000..b165ae6
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureFBO_test.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SurfaceTextureFBO_test"
+//#define LOG_NDEBUG 0
+
+#include "SurfaceTextureFBO.h"
+
+namespace android {
+
+// This test is intended to verify that proper synchronization is done when
+// rendering into an FBO.
+TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) {
+ const int texWidth = 64;
+ const int texHeight = 64;
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
+ texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
+ GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+ android_native_buffer_t* anb;
+ ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb));
+ ASSERT_TRUE(anb != NULL);
+
+ sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
+
+ // Fill the buffer with green
+ uint8_t* img = NULL;
+ buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
+ fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 0, 255,
+ 0, 255);
+ buf->unlock();
+ ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(),
+ -1));
+
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
+ drawTexture();
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ for (int i = 0; i < 4; i++) {
+ SCOPED_TRACE(String8::format("frame %d", i).string());
+
+ ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb));
+ ASSERT_TRUE(anb != NULL);
+
+ buf = new GraphicBuffer(anb, false);
+
+ // Fill the buffer with red
+ ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN,
+ (void**)(&img)));
+ fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 255, 0,
+ 0, 255);
+ ASSERT_EQ(NO_ERROR, buf->unlock());
+ ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(),
+ buf->getNativeBuffer(), -1));
+
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 24, 39, 255, 0, 0, 255));
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
+
+ EXPECT_TRUE(checkPixel( 24, 39, 0, 255, 0, 255));
+}
+
+} // namespace android
diff --git a/libs/gui/tests/SurfaceTextureGL.h b/libs/gui/tests/SurfaceTextureGL.h
new file mode 100644
index 0000000..3bff192
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureGL.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SURFACE_TEXTURE_GL_H
+#define ANDROID_SURFACE_TEXTURE_GL_H
+
+#include "GLTest.h"
+
+#include "FrameWaiter.h"
+#include "TextureRenderer.h"
+
+#include <gui/GLConsumer.h>
+#include <gui/Surface.h>
+
+namespace android {
+
+class FrameWaiter;
+class GLConsumer;
+class TextureRenderer;
+
+class SurfaceTextureGLTest : public GLTest {
+protected:
+ enum { TEX_ID = 123 };
+
+ void SetUp() {
+ GLTest::SetUp();
+ sp<IGraphicBufferProducer> producer;
+ BufferQueue::createBufferQueue(&producer, &mConsumer);
+ mST = new GLConsumer(mConsumer, TEX_ID);
+ mSTC = new Surface(producer);
+ mANW = mSTC;
+ mTextureRenderer = new TextureRenderer(TEX_ID, mST);
+ ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp());
+ mFW = new FrameWaiter;
+ mST->setFrameAvailableListener(mFW);
+ }
+
+ void TearDown() {
+ mTextureRenderer.clear();
+ mANW.clear();
+ mSTC.clear();
+ mST.clear();
+ GLTest::TearDown();
+ }
+
+ void drawTexture() {
+ mTextureRenderer->drawTexture();
+ }
+
+ sp<IGraphicBufferConsumer> mConsumer;
+ sp<GLConsumer> mST;
+ sp<Surface> mSTC;
+ sp<ANativeWindow> mANW;
+ sp<TextureRenderer> mTextureRenderer;
+ sp<FrameWaiter> mFW;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/gui/tests/SurfaceTextureGLThreadToGL.h b/libs/gui/tests/SurfaceTextureGLThreadToGL.h
new file mode 100644
index 0000000..6410516
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureGLThreadToGL.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SURFACE_TEXTURE_GL_THREAD_TO_GL_H
+#define ANDROID_SURFACE_TEXTURE_GL_THREAD_TO_GL_H
+
+#include "SurfaceTextureGLToGL.h"
+
+namespace android {
+
+/*
+ * This test fixture is for testing GL -> GL texture streaming from one thread
+ * to another. It contains functionality to create a producer thread that will
+ * perform GL rendering to an ANativeWindow that feeds frames to a
+ * GLConsumer. Additionally it supports interlocking the producer and
+ * consumer threads so that a specific sequence of calls can be
+ * deterministically created by the test.
+ *
+ * The intended usage is as follows:
+ *
+ * TEST_F(...) {
+ * class PT : public ProducerThread {
+ * virtual void render() {
+ * ...
+ * swapBuffers();
+ * }
+ * };
+ *
+ * runProducerThread(new PT());
+ *
+ * // The order of these calls will vary from test to test and may include
+ * // multiple frames and additional operations (e.g. GL rendering from the
+ * // texture).
+ * fc->waitForFrame();
+ * mST->updateTexImage();
+ * fc->finishFrame();
+ * }
+ *
+ */
+class SurfaceTextureGLThreadToGLTest : public SurfaceTextureGLToGLTest {
+protected:
+
+ // ProducerThread is an abstract base class to simplify the creation of
+ // OpenGL ES frame producer threads.
+ class ProducerThread : public Thread {
+ public:
+ virtual ~ProducerThread() {
+ }
+
+ void setEglObjects(EGLDisplay producerEglDisplay,
+ EGLSurface producerEglSurface,
+ EGLContext producerEglContext) {
+ mProducerEglDisplay = producerEglDisplay;
+ mProducerEglSurface = producerEglSurface;
+ mProducerEglContext = producerEglContext;
+ }
+
+ virtual bool threadLoop() {
+ eglMakeCurrent(mProducerEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext);
+ render();
+ eglMakeCurrent(mProducerEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
+ return false;
+ }
+
+ protected:
+ virtual void render() = 0;
+
+ void swapBuffers() {
+ eglSwapBuffers(mProducerEglDisplay, mProducerEglSurface);
+ }
+
+ EGLDisplay mProducerEglDisplay;
+ EGLSurface mProducerEglSurface;
+ EGLContext mProducerEglContext;
+ };
+
+ // FrameCondition is a utility class for interlocking between the producer
+ // and consumer threads. The FrameCondition object should be created and
+ // destroyed in the consumer thread only. The consumer thread should set
+ // the FrameCondition as the FrameAvailableListener of the GLConsumer,
+ // and should call both waitForFrame and finishFrame once for each expected
+ // frame.
+ //
+ // This interlocking relies on the fact that onFrameAvailable gets called
+ // synchronously from GLConsumer::queueBuffer.
+ class FrameCondition : public GLConsumer::FrameAvailableListener {
+ public:
+ FrameCondition():
+ mFrameAvailable(false),
+ mFrameFinished(false) {
+ }
+
+ // waitForFrame waits for the next frame to arrive. This should be
+ // called from the consumer thread once for every frame expected by the
+ // test.
+ void waitForFrame() {
+ Mutex::Autolock lock(mMutex);
+ ALOGV("+waitForFrame");
+ while (!mFrameAvailable) {
+ mFrameAvailableCondition.wait(mMutex);
+ }
+ mFrameAvailable = false;
+ ALOGV("-waitForFrame");
+ }
+
+ // Allow the producer to return from its swapBuffers call and continue
+ // on to produce the next frame. This should be called by the consumer
+ // thread once for every frame expected by the test.
+ void finishFrame() {
+ Mutex::Autolock lock(mMutex);
+ ALOGV("+finishFrame");
+ mFrameFinished = true;
+ mFrameFinishCondition.signal();
+ ALOGV("-finishFrame");
+ }
+
+ // This should be called by GLConsumer on the producer thread.
+ virtual void onFrameAvailable() {
+ Mutex::Autolock lock(mMutex);
+ ALOGV("+onFrameAvailable");
+ mFrameAvailable = true;
+ mFrameAvailableCondition.signal();
+ while (!mFrameFinished) {
+ mFrameFinishCondition.wait(mMutex);
+ }
+ mFrameFinished = false;
+ ALOGV("-onFrameAvailable");
+ }
+
+ protected:
+ bool mFrameAvailable;
+ bool mFrameFinished;
+
+ Mutex mMutex;
+ Condition mFrameAvailableCondition;
+ Condition mFrameFinishCondition;
+ };
+
+ virtual void SetUp() {
+ SurfaceTextureGLToGLTest::SetUp();
+ mFC = new FrameCondition();
+ mST->setFrameAvailableListener(mFC);
+ }
+
+ virtual void TearDown() {
+ if (mProducerThread != NULL) {
+ mProducerThread->requestExitAndWait();
+ }
+ mProducerThread.clear();
+ mFC.clear();
+ SurfaceTextureGLToGLTest::TearDown();
+ }
+
+ void runProducerThread(const sp<ProducerThread> producerThread) {
+ ASSERT_TRUE(mProducerThread == NULL);
+ mProducerThread = producerThread;
+ producerThread->setEglObjects(mEglDisplay, mProducerEglSurface,
+ mProducerEglContext);
+ producerThread->run();
+ }
+
+ sp<ProducerThread> mProducerThread;
+ sp<FrameCondition> mFC;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/gui/tests/SurfaceTextureGLThreadToGL_test.cpp b/libs/gui/tests/SurfaceTextureGLThreadToGL_test.cpp
new file mode 100644
index 0000000..9776733
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureGLThreadToGL_test.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SurfaceTextureGLThreadToGL_test"
+//#define LOG_NDEBUG 0
+
+#include "SurfaceTextureGLThreadToGL.h"
+
+namespace android {
+
+TEST_F(SurfaceTextureGLThreadToGLTest,
+ UpdateTexImageBeforeFrameFinishedCompletes) {
+ class PT : public ProducerThread {
+ virtual void render() {
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ swapBuffers();
+ }
+ };
+
+ runProducerThread(new PT());
+
+ mFC->waitForFrame();
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ mFC->finishFrame();
+
+ // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
+}
+
+TEST_F(SurfaceTextureGLThreadToGLTest,
+ UpdateTexImageAfterFrameFinishedCompletes) {
+ class PT : public ProducerThread {
+ virtual void render() {
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ swapBuffers();
+ }
+ };
+
+ runProducerThread(new PT());
+
+ mFC->waitForFrame();
+ mFC->finishFrame();
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
+}
+
+TEST_F(SurfaceTextureGLThreadToGLTest,
+ RepeatedUpdateTexImageBeforeFrameFinishedCompletes) {
+ enum { NUM_ITERATIONS = 1024 };
+
+ class PT : public ProducerThread {
+ virtual void render() {
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ALOGV("+swapBuffers");
+ swapBuffers();
+ ALOGV("-swapBuffers");
+ }
+ }
+ };
+
+ runProducerThread(new PT());
+
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ mFC->waitForFrame();
+ ALOGV("+updateTexImage");
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ ALOGV("-updateTexImage");
+ mFC->finishFrame();
+
+ // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
+ }
+}
+
+TEST_F(SurfaceTextureGLThreadToGLTest,
+ RepeatedUpdateTexImageAfterFrameFinishedCompletes) {
+ enum { NUM_ITERATIONS = 1024 };
+
+ class PT : public ProducerThread {
+ virtual void render() {
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ALOGV("+swapBuffers");
+ swapBuffers();
+ ALOGV("-swapBuffers");
+ }
+ }
+ };
+
+ runProducerThread(new PT());
+
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ mFC->waitForFrame();
+ mFC->finishFrame();
+ ALOGV("+updateTexImage");
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ ALOGV("-updateTexImage");
+
+ // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
+ }
+}
+
+// XXX: This test is disabled because it is currently hanging on some devices.
+TEST_F(SurfaceTextureGLThreadToGLTest,
+ DISABLED_RepeatedSwapBuffersWhileDequeueStalledCompletes) {
+ enum { NUM_ITERATIONS = 64 };
+
+ class PT : public ProducerThread {
+ virtual void render() {
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ALOGV("+swapBuffers");
+ swapBuffers();
+ ALOGV("-swapBuffers");
+ }
+ }
+ };
+
+ ASSERT_EQ(OK, mST->setDefaultMaxBufferCount(2));
+
+ runProducerThread(new PT());
+
+ // Allow three frames to be rendered and queued before starting the
+ // rendering in this thread. For the latter two frames we don't call
+ // updateTexImage so the next dequeue from the producer thread will block
+ // waiting for a frame to become available.
+ mFC->waitForFrame();
+ mFC->finishFrame();
+
+ // We must call updateTexImage to consume the first frame so that the
+ // SurfaceTexture is able to reduce the buffer count to 2. This is because
+ // the GL driver may dequeue a buffer when the EGLSurface is created, and
+ // that happens before we call setDefaultMaxBufferCount. It's possible that the
+ // driver does not dequeue a buffer at EGLSurface creation time, so we
+ // cannot rely on this to cause the second dequeueBuffer call to block.
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ mFC->waitForFrame();
+ mFC->finishFrame();
+ mFC->waitForFrame();
+ mFC->finishFrame();
+
+ // Sleep for 100ms to allow the producer thread's dequeueBuffer call to
+ // block waiting for a buffer to become available.
+ usleep(100000);
+
+ // Render and present a number of images. This thread should not be blocked
+ // by the fact that the producer thread is blocking in dequeue.
+ for (int i = 0; i < NUM_ITERATIONS; i++) {
+ glClear(GL_COLOR_BUFFER_BIT);
+ eglSwapBuffers(mEglDisplay, mEglSurface);
+ }
+
+ // Consume the two pending buffers to unblock the producer thread.
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ // Consume the remaining buffers from the producer thread.
+ for (int i = 0; i < NUM_ITERATIONS-3; i++) {
+ mFC->waitForFrame();
+ mFC->finishFrame();
+ ALOGV("+updateTexImage");
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ ALOGV("-updateTexImage");
+ }
+}
+
+} // namespace android
diff --git a/libs/gui/tests/SurfaceTextureGLToGL.h b/libs/gui/tests/SurfaceTextureGLToGL.h
new file mode 100644
index 0000000..5a2eff3
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureGLToGL.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SURFACE_TEXTURE_GL_TO_GL_H
+#define ANDROID_SURFACE_TEXTURE_GL_TO_GL_H
+
+#include "SurfaceTextureGL.h"
+
+namespace android {
+
+/*
+ * This test fixture is for testing GL -> GL texture streaming. It creates an
+ * EGLSurface and an EGLContext for the image producer to use.
+ */
+class SurfaceTextureGLToGLTest : public SurfaceTextureGLTest {
+protected:
+ SurfaceTextureGLToGLTest():
+ mProducerEglSurface(EGL_NO_SURFACE),
+ mProducerEglContext(EGL_NO_CONTEXT) {
+ }
+
+ virtual void SetUp() {
+ SurfaceTextureGLTest::SetUp();
+
+ mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
+ mANW.get(), NULL);
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface);
+
+ mProducerEglContext = eglCreateContext(mEglDisplay, mGlConfig,
+ EGL_NO_CONTEXT, getContextAttribs());
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext);
+ }
+
+ virtual void TearDown() {
+ if (mProducerEglContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEglDisplay, mProducerEglContext);
+ }
+ if (mProducerEglSurface != EGL_NO_SURFACE) {
+ eglDestroySurface(mEglDisplay, mProducerEglSurface);
+ }
+ SurfaceTextureGLTest::TearDown();
+ }
+
+ EGLSurface mProducerEglSurface;
+ EGLContext mProducerEglContext;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/gui/tests/SurfaceTextureGLToGL_test.cpp b/libs/gui/tests/SurfaceTextureGLToGL_test.cpp
new file mode 100644
index 0000000..f4c7961
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureGLToGL_test.cpp
@@ -0,0 +1,502 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SurfaceTextureGLToGL_test"
+//#define LOG_NDEBUG 0
+
+#include "SurfaceTextureGLToGL.h"
+
+namespace android {
+
+TEST_F(SurfaceTextureGLToGLTest, TransformHintGetsRespected) {
+ const uint32_t texWidth = 32;
+ const uint32_t texHeight = 64;
+
+ mST->setDefaultBufferSize(texWidth, texHeight);
+ mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90);
+
+ // This test requires 3 buffers to avoid deadlock because we're
+ // both producer and consumer, and only using one thread.
+ mST->setDefaultMaxBufferCount(3);
+
+ // Do the producer side of things
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Start a buffer with our chosen size and transform hint moving
+ // through the system.
+ glClear(GL_COLOR_BUFFER_BIT); // give the driver something to do
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+ mST->updateTexImage(); // consume it
+ // Swap again.
+ glClear(GL_COLOR_BUFFER_BIT);
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+ mST->updateTexImage();
+
+ // The current buffer should either show the effects of the transform
+ // hint (in the form of an inverse transform), or show that the
+ // transform hint has been ignored.
+ sp<GraphicBuffer> buf = mST->getCurrentBuffer();
+ if (mST->getCurrentTransform() == NATIVE_WINDOW_TRANSFORM_ROT_270) {
+ ASSERT_EQ(texWidth, buf->getHeight());
+ ASSERT_EQ(texHeight, buf->getWidth());
+ } else {
+ ASSERT_EQ(texWidth, buf->getWidth());
+ ASSERT_EQ(texHeight, buf->getHeight());
+ }
+
+ // Reset the transform hint and confirm that it takes.
+ mST->setTransformHint(0);
+ glClear(GL_COLOR_BUFFER_BIT);
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+ mST->updateTexImage();
+ glClear(GL_COLOR_BUFFER_BIT);
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+ mST->updateTexImage();
+
+ buf = mST->getCurrentBuffer();
+ ASSERT_EQ((uint32_t) 0, mST->getCurrentTransform());
+ ASSERT_EQ(texWidth, buf->getWidth());
+ ASSERT_EQ(texHeight, buf->getHeight());
+}
+
+TEST_F(SurfaceTextureGLToGLTest, TexturingFromGLFilledRGBABufferPow2) {
+ const int texWidth = 64;
+ const int texHeight = 64;
+
+ mST->setDefaultBufferSize(texWidth, texHeight);
+
+ // This test requires 3 buffers to complete run on a single thread.
+ mST->setDefaultMaxBufferCount(3);
+
+ // Do the producer side of things
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // This is needed to ensure we pick up a buffer of the correct size.
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+
+ glClearColor(0.6, 0.6, 0.6, 0.6);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(4, 4, 4, 4);
+ glClearColor(1.0, 0.0, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glScissor(24, 48, 4, 4);
+ glClearColor(0.0, 1.0, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glScissor(37, 17, 4, 4);
+ glClearColor(0.0, 0.0, 1.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+
+ // Do the consumer side of things
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ glDisable(GL_SCISSOR_TEST);
+
+ // Skip the first frame, which was empty
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, texWidth, texHeight);
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153));
+
+ EXPECT_TRUE(checkPixel( 4, 7, 255, 0, 0, 255));
+ EXPECT_TRUE(checkPixel(25, 51, 0, 255, 0, 255));
+ EXPECT_TRUE(checkPixel(40, 19, 0, 0, 255, 255));
+ EXPECT_TRUE(checkPixel(29, 51, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 5, 32, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(13, 8, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(46, 3, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(30, 33, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 6, 52, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(55, 33, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(16, 29, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 1, 30, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(41, 37, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(46, 29, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(15, 25, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 3, 52, 153, 153, 153, 153));
+}
+
+TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceUnrefsBuffers) {
+ sp<GraphicBuffer> buffers[2];
+
+ // This test requires async mode to run on a single thread.
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ EXPECT_TRUE(eglSwapInterval(mEglDisplay, 0));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ for (int i = 0; i < 2; i++) {
+ // Produce a frame
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ glClear(GL_COLOR_BUFFER_BIT);
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+
+ // Consume a frame
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ mFW->waitForFrame();
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ buffers[i] = mST->getCurrentBuffer();
+ }
+
+ // Destroy the GL texture object to release its ref on buffers[2].
+ GLuint texID = TEX_ID;
+ glDeleteTextures(1, &texID);
+
+ // Destroy the EGLSurface
+ EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ mProducerEglSurface = EGL_NO_SURFACE;
+
+ // This test should have the only reference to buffer 0.
+ EXPECT_EQ(1, buffers[0]->getStrongCount());
+
+ // The GLConsumer should hold a single reference to buffer 1 in its
+ // mCurrentBuffer member. All of the references in the slots should have
+ // been released.
+ EXPECT_EQ(2, buffers[1]->getStrongCount());
+}
+
+TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceAfterAbandonUnrefsBuffers) {
+ sp<GraphicBuffer> buffers[3];
+
+ // This test requires async mode to run on a single thread.
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ EXPECT_TRUE(eglSwapInterval(mEglDisplay, 0));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ for (int i = 0; i < 3; i++) {
+ // Produce a frame
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Consume a frame
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ mFW->waitForFrame();
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ buffers[i] = mST->getCurrentBuffer();
+ }
+
+ // Abandon the GLConsumer, releasing the ref that the GLConsumer has
+ // on buffers[2].
+ mST->abandon();
+
+ // Destroy the GL texture object to release its ref on buffers[2].
+ GLuint texID = TEX_ID;
+ glDeleteTextures(1, &texID);
+
+ // Destroy the EGLSurface.
+ EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ mProducerEglSurface = EGL_NO_SURFACE;
+
+ EXPECT_EQ(1, buffers[0]->getStrongCount());
+ EXPECT_EQ(1, buffers[1]->getStrongCount());
+
+ // Depending on how lazily the GL driver dequeues buffers, we may end up
+ // with either two or three total buffers. If there are three, make sure
+ // the last one was properly down-ref'd.
+ if (buffers[2] != buffers[0]) {
+ EXPECT_EQ(1, buffers[2]->getStrongCount());
+ }
+}
+
+TEST_F(SurfaceTextureGLToGLTest, EglMakeCurrentBeforeConsumerDeathUnrefsBuffers) {
+ sp<GraphicBuffer> buffer;
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+
+ // Produce a frame
+ glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Destroy the EGLSurface.
+ EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ mProducerEglSurface = EGL_NO_SURFACE;
+ mSTC.clear();
+ mANW.clear();
+ mTextureRenderer.clear();
+
+ // Consume a frame
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ buffer = mST->getCurrentBuffer();
+
+ // Destroy the GL texture object to release its ref
+ GLuint texID = TEX_ID;
+ glDeleteTextures(1, &texID);
+
+ // make un-current, all references to buffer should be gone
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE,
+ EGL_NO_SURFACE, EGL_NO_CONTEXT));
+
+ // Destroy consumer
+ mST.clear();
+
+ EXPECT_EQ(1, buffer->getStrongCount());
+}
+
+TEST_F(SurfaceTextureGLToGLTest, EglMakeCurrentAfterConsumerDeathUnrefsBuffers) {
+ sp<GraphicBuffer> buffer;
+
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+
+ // Produce a frame
+ glClear(GL_COLOR_BUFFER_BIT);
+ EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Destroy the EGLSurface.
+ EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ mProducerEglSurface = EGL_NO_SURFACE;
+ mSTC.clear();
+ mANW.clear();
+ mTextureRenderer.clear();
+
+ // Consume a frame
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ buffer = mST->getCurrentBuffer();
+
+ // Destroy the GL texture object to release its ref
+ GLuint texID = TEX_ID;
+ glDeleteTextures(1, &texID);
+
+ // Destroy consumer
+ mST.clear();
+
+ // make un-current, all references to buffer should be gone
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE,
+ EGL_NO_SURFACE, EGL_NO_CONTEXT));
+
+ EXPECT_EQ(1, buffer->getStrongCount());
+}
+
+TEST_F(SurfaceTextureGLToGLTest, TexturingFromUserSizedGLFilledBuffer) {
+ enum { texWidth = 64 };
+ enum { texHeight = 64 };
+
+ // This test requires 3 buffers to complete run on a single thread.
+ mST->setDefaultMaxBufferCount(3);
+
+ // Set the user buffer size.
+ native_window_set_buffers_user_dimensions(mANW.get(), texWidth, texHeight);
+
+ // Do the producer side of things
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // This is needed to ensure we pick up a buffer of the correct size.
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+
+ glClearColor(0.6, 0.6, 0.6, 0.6);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(4, 4, 1, 1);
+ glClearColor(1.0, 0.0, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+
+ // Do the consumer side of things
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ glDisable(GL_SCISSOR_TEST);
+
+ // Skip the first frame, which was empty
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, texWidth, texHeight);
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153));
+
+ EXPECT_TRUE(checkPixel( 4, 4, 255, 0, 0, 255));
+ EXPECT_TRUE(checkPixel( 5, 5, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 3, 3, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(45, 52, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(12, 36, 153, 153, 153, 153));
+}
+
+TEST_F(SurfaceTextureGLToGLTest, TexturingFromPreRotatedUserSizedGLFilledBuffer) {
+ enum { texWidth = 64 };
+ enum { texHeight = 16 };
+
+ // This test requires 3 buffers to complete run on a single thread.
+ mST->setDefaultMaxBufferCount(3);
+
+ // Set the transform hint.
+ mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90);
+
+ // Set the user buffer size.
+ native_window_set_buffers_user_dimensions(mANW.get(), texWidth, texHeight);
+
+ // Do the producer side of things
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // This is needed to ensure we pick up a buffer of the correct size and the
+ // new rotation hint.
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+
+ glClearColor(0.6, 0.6, 0.6, 0.6);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(24, 4, 1, 1);
+ glClearColor(1.0, 0.0, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+
+ // Do the consumer side of things
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ glDisable(GL_SCISSOR_TEST);
+
+ // Skip the first frame, which was empty
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, texWidth, texHeight);
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 15, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 0, 15, 153, 153, 153, 153));
+
+ EXPECT_TRUE(checkPixel(24, 4, 255, 0, 0, 255));
+ EXPECT_TRUE(checkPixel(25, 5, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(23, 3, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(45, 13, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(12, 8, 153, 153, 153, 153));
+}
+
+TEST_F(SurfaceTextureGLToGLTest, TexturingFromPreRotatedGLFilledBuffer) {
+ enum { texWidth = 64 };
+ enum { texHeight = 16 };
+
+ // This test requires 3 buffers to complete run on a single thread.
+ mST->setDefaultMaxBufferCount(3);
+
+ // Set the transform hint.
+ mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90);
+
+ // Set the default buffer size.
+ mST->setDefaultBufferSize(texWidth, texHeight);
+
+ // Do the producer side of things
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
+ mProducerEglSurface, mProducerEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // This is needed to ensure we pick up a buffer of the correct size and the
+ // new rotation hint.
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+
+ glClearColor(0.6, 0.6, 0.6, 0.6);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glEnable(GL_SCISSOR_TEST);
+ glScissor(24, 4, 1, 1);
+ glClearColor(1.0, 0.0, 0.0, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ eglSwapBuffers(mEglDisplay, mProducerEglSurface);
+
+ // Do the consumer side of things
+ EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ glDisable(GL_SCISSOR_TEST);
+
+ // Skip the first frame, which was empty
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, texWidth, texHeight);
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(63, 15, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel( 0, 15, 153, 153, 153, 153));
+
+ EXPECT_TRUE(checkPixel(24, 4, 255, 0, 0, 255));
+ EXPECT_TRUE(checkPixel(25, 5, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(23, 3, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(45, 13, 153, 153, 153, 153));
+ EXPECT_TRUE(checkPixel(12, 8, 153, 153, 153, 153));
+}
+
+} // namespace android
diff --git a/libs/gui/tests/SurfaceTextureGL_test.cpp b/libs/gui/tests/SurfaceTextureGL_test.cpp
new file mode 100644
index 0000000..fa1e1b7
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureGL_test.cpp
@@ -0,0 +1,703 @@
+/*
+ * 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 "SurfaceTextureGL_test"
+//#define LOG_NDEBUG 0
+
+#include "SurfaceTextureGL.h"
+
+#include "DisconnectWaiter.h"
+#include "FillBuffer.h"
+
+namespace android {
+
+TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) {
+ const int texWidth = 64;
+ const int texHeight = 66;
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
+ texWidth, texHeight, HAL_PIXEL_FORMAT_YV12));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
+ GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+ ANativeWindowBuffer* anb;
+ ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb));
+ ASSERT_TRUE(anb != NULL);
+
+ sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
+
+ // Fill the buffer with the a checkerboard pattern
+ uint8_t* img = NULL;
+ buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
+ fillYV12Buffer(img, texWidth, texHeight, buf->getStride());
+ buf->unlock();
+ ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(),
+ -1));
+
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, texWidth, texHeight);
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 0, 0, 255, 127, 255, 255, 3));
+ EXPECT_TRUE(checkPixel(63, 0, 0, 133, 0, 255, 3));
+ EXPECT_TRUE(checkPixel(63, 65, 0, 133, 0, 255, 3));
+ EXPECT_TRUE(checkPixel( 0, 65, 255, 127, 255, 255, 3));
+
+ EXPECT_TRUE(checkPixel(22, 44, 255, 127, 255, 255, 3));
+ EXPECT_TRUE(checkPixel(45, 52, 255, 127, 255, 255, 3));
+ EXPECT_TRUE(checkPixel(52, 51, 98, 255, 73, 255, 3));
+ EXPECT_TRUE(checkPixel( 7, 31, 155, 0, 118, 255, 3));
+ EXPECT_TRUE(checkPixel(31, 9, 107, 24, 87, 255, 3));
+ EXPECT_TRUE(checkPixel(29, 35, 255, 127, 255, 255, 3));
+ EXPECT_TRUE(checkPixel(36, 22, 155, 29, 0, 255, 3));
+}
+
+TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferPow2) {
+ const int texWidth = 64;
+ const int texHeight = 64;
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
+ texWidth, texHeight, HAL_PIXEL_FORMAT_YV12));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
+ GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+ ANativeWindowBuffer* anb;
+ ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb));
+ ASSERT_TRUE(anb != NULL);
+
+ sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
+
+ // Fill the buffer with the a checkerboard pattern
+ uint8_t* img = NULL;
+ buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
+ fillYV12Buffer(img, texWidth, texHeight, buf->getStride());
+ buf->unlock();
+ ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(),
+ -1));
+
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, texWidth, texHeight);
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 0, 0, 0, 133, 0, 255));
+ EXPECT_TRUE(checkPixel(63, 0, 255, 127, 255, 255));
+ EXPECT_TRUE(checkPixel(63, 63, 0, 133, 0, 255));
+ EXPECT_TRUE(checkPixel( 0, 63, 255, 127, 255, 255));
+
+ EXPECT_TRUE(checkPixel(22, 19, 100, 255, 74, 255));
+ EXPECT_TRUE(checkPixel(45, 11, 100, 255, 74, 255));
+ EXPECT_TRUE(checkPixel(52, 12, 155, 0, 181, 255));
+ EXPECT_TRUE(checkPixel( 7, 32, 150, 237, 170, 255));
+ EXPECT_TRUE(checkPixel(31, 54, 0, 71, 117, 255));
+ EXPECT_TRUE(checkPixel(29, 28, 0, 133, 0, 255));
+ EXPECT_TRUE(checkPixel(36, 41, 100, 232, 255, 255));
+}
+
+TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) {
+ const int texWidth = 64;
+ const int texHeight = 66;
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
+ texWidth, texHeight, HAL_PIXEL_FORMAT_YV12));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
+ GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+ android_native_rect_t crops[] = {
+ {4, 6, 22, 36},
+ {0, 6, 22, 36},
+ {4, 0, 22, 36},
+ {4, 6, texWidth, 36},
+ {4, 6, 22, texHeight},
+ };
+
+ for (int i = 0; i < 5; i++) {
+ const android_native_rect_t& crop(crops[i]);
+ SCOPED_TRACE(String8::format("rect{ l: %d t: %d r: %d b: %d }",
+ crop.left, crop.top, crop.right, crop.bottom).string());
+
+ ASSERT_EQ(NO_ERROR, native_window_set_crop(mANW.get(), &crop));
+
+ ANativeWindowBuffer* anb;
+ ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb));
+ ASSERT_TRUE(anb != NULL);
+
+ sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
+
+ uint8_t* img = NULL;
+ buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
+ fillYV12BufferRect(img, texWidth, texHeight, buf->getStride(), crop);
+ buf->unlock();
+ ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(),
+ buf->getNativeBuffer(), -1));
+
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, 64, 64);
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 0, 0, 82, 255, 35, 255));
+ EXPECT_TRUE(checkPixel(63, 0, 82, 255, 35, 255));
+ EXPECT_TRUE(checkPixel(63, 63, 82, 255, 35, 255));
+ EXPECT_TRUE(checkPixel( 0, 63, 82, 255, 35, 255));
+
+ EXPECT_TRUE(checkPixel(25, 14, 82, 255, 35, 255));
+ EXPECT_TRUE(checkPixel(35, 31, 82, 255, 35, 255));
+ EXPECT_TRUE(checkPixel(57, 6, 82, 255, 35, 255));
+ EXPECT_TRUE(checkPixel( 5, 42, 82, 255, 35, 255));
+ EXPECT_TRUE(checkPixel(32, 33, 82, 255, 35, 255));
+ EXPECT_TRUE(checkPixel(16, 26, 82, 255, 35, 255));
+ EXPECT_TRUE(checkPixel(46, 51, 82, 255, 35, 255));
+ }
+}
+
+// This test is intended to catch synchronization bugs between the CPU-written
+// and GPU-read buffers.
+TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BuffersRepeatedly) {
+ enum { texWidth = 16 };
+ enum { texHeight = 16 };
+ enum { numFrames = 1024 };
+
+ ASSERT_EQ(NO_ERROR, mST->setDefaultMaxBufferCount(2));
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
+ texWidth, texHeight, HAL_PIXEL_FORMAT_YV12));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
+ GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+ struct TestPixel {
+ int x;
+ int y;
+ };
+ const TestPixel testPixels[] = {
+ { 4, 11 },
+ { 12, 14 },
+ { 7, 2 },
+ };
+ enum {numTestPixels = sizeof(testPixels) / sizeof(testPixels[0])};
+
+ class ProducerThread : public Thread {
+ public:
+ ProducerThread(const sp<ANativeWindow>& anw,
+ const TestPixel* testPixels):
+ mANW(anw),
+ mTestPixels(testPixels) {
+ }
+
+ virtual ~ProducerThread() {
+ }
+
+ virtual bool threadLoop() {
+ for (int i = 0; i < numFrames; i++) {
+ ANativeWindowBuffer* anb;
+ if (native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb) != NO_ERROR) {
+ return false;
+ }
+ if (anb == NULL) {
+ return false;
+ }
+
+ sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
+
+ const int yuvTexOffsetY = 0;
+ int stride = buf->getStride();
+ int yuvTexStrideY = stride;
+ int yuvTexOffsetV = yuvTexStrideY * texHeight;
+ int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
+ int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * texHeight/2;
+ int yuvTexStrideU = yuvTexStrideV;
+
+ uint8_t* img = NULL;
+ buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
+
+ // Gray out all the test pixels first, so we're more likely to
+ // see a failure if GL is still texturing from the buffer we
+ // just dequeued.
+ for (int j = 0; j < numTestPixels; j++) {
+ int x = mTestPixels[j].x;
+ int y = mTestPixels[j].y;
+ uint8_t value = 128;
+ img[y*stride + x] = value;
+ }
+
+ // Fill the buffer with gray.
+ for (int y = 0; y < texHeight; y++) {
+ for (int x = 0; x < texWidth; x++) {
+ img[yuvTexOffsetY + y*yuvTexStrideY + x] = 128;
+ img[yuvTexOffsetU + (y/2)*yuvTexStrideU + x/2] = 128;
+ img[yuvTexOffsetV + (y/2)*yuvTexStrideV + x/2] = 128;
+ }
+ }
+
+ // Set the test pixels to either white or black.
+ for (int j = 0; j < numTestPixels; j++) {
+ int x = mTestPixels[j].x;
+ int y = mTestPixels[j].y;
+ uint8_t value = 0;
+ if (j == (i % numTestPixels)) {
+ value = 255;
+ }
+ img[y*stride + x] = value;
+ }
+
+ buf->unlock();
+ if (mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), -1)
+ != NO_ERROR) {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ sp<ANativeWindow> mANW;
+ const TestPixel* mTestPixels;
+ };
+
+ sp<Thread> pt(new ProducerThread(mANW, testPixels));
+ pt->run();
+
+ glViewport(0, 0, texWidth, texHeight);
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ // We wait for the first two frames up front so that the producer will be
+ // likely to dequeue the buffer that's currently being textured from.
+ mFW->waitForFrame();
+ mFW->waitForFrame();
+
+ for (int i = 0; i < numFrames; i++) {
+ SCOPED_TRACE(String8::format("frame %d", i).string());
+
+ // We must wait for each frame to come in because if we ever do an
+ // updateTexImage call that doesn't consume a newly available buffer
+ // then the producer and consumer will get out of sync, which will cause
+ // a deadlock.
+ if (i > 1) {
+ mFW->waitForFrame();
+ }
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+ drawTexture();
+
+ for (int j = 0; j < numTestPixels; j++) {
+ int x = testPixels[j].x;
+ int y = testPixels[j].y;
+ uint8_t value = 0;
+ if (j == (i % numTestPixels)) {
+ // We must y-invert the texture coords
+ EXPECT_TRUE(checkPixel(x, texHeight-y-1, 255, 255, 255, 255));
+ } else {
+ // We must y-invert the texture coords
+ EXPECT_TRUE(checkPixel(x, texHeight-y-1, 0, 0, 0, 255));
+ }
+ }
+ }
+
+ pt->requestExitAndWait();
+}
+
+TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferNpot) {
+ const int texWidth = 64;
+ const int texHeight = 66;
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
+ texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
+ GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, texWidth, texHeight);
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
+ EXPECT_TRUE(checkPixel(63, 0, 231, 231, 231, 231));
+ EXPECT_TRUE(checkPixel(63, 65, 231, 231, 231, 231));
+ EXPECT_TRUE(checkPixel( 0, 65, 35, 35, 35, 35));
+
+ EXPECT_TRUE(checkPixel(15, 10, 35, 231, 231, 231));
+ EXPECT_TRUE(checkPixel(23, 65, 231, 35, 231, 35));
+ EXPECT_TRUE(checkPixel(19, 40, 35, 231, 35, 35));
+ EXPECT_TRUE(checkPixel(38, 30, 231, 35, 35, 35));
+ EXPECT_TRUE(checkPixel(42, 54, 35, 35, 35, 231));
+ EXPECT_TRUE(checkPixel(37, 34, 35, 231, 231, 231));
+ EXPECT_TRUE(checkPixel(31, 8, 231, 35, 35, 231));
+ EXPECT_TRUE(checkPixel(37, 47, 231, 35, 231, 231));
+ EXPECT_TRUE(checkPixel(25, 38, 35, 35, 35, 35));
+ EXPECT_TRUE(checkPixel(49, 6, 35, 231, 35, 35));
+ EXPECT_TRUE(checkPixel(54, 50, 35, 231, 231, 231));
+ EXPECT_TRUE(checkPixel(27, 26, 231, 231, 231, 231));
+ EXPECT_TRUE(checkPixel(10, 6, 35, 35, 231, 231));
+ EXPECT_TRUE(checkPixel(29, 4, 35, 35, 35, 231));
+ EXPECT_TRUE(checkPixel(55, 28, 35, 35, 231, 35));
+ EXPECT_TRUE(checkPixel(58, 55, 35, 35, 231, 231));
+}
+
+TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferPow2) {
+ const int texWidth = 64;
+ const int texHeight = 64;
+
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
+ texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
+ GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
+
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ ASSERT_EQ(NO_ERROR, mST->updateTexImage());
+
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ glViewport(0, 0, texWidth, texHeight);
+ drawTexture();
+
+ EXPECT_TRUE(checkPixel( 0, 0, 231, 231, 231, 231));
+ EXPECT_TRUE(checkPixel(63, 0, 35, 35, 35, 35));
+ EXPECT_TRUE(checkPixel(63, 63, 231, 231, 231, 231));
+ EXPECT_TRUE(checkPixel( 0, 63, 35, 35, 35, 35));
+
+ EXPECT_TRUE(checkPixel(12, 46, 231, 231, 231, 35));
+ EXPECT_TRUE(checkPixel(16, 1, 231, 231, 35, 231));
+ EXPECT_TRUE(checkPixel(21, 12, 231, 35, 35, 231));
+ EXPECT_TRUE(checkPixel(26, 51, 231, 35, 231, 35));
+ EXPECT_TRUE(checkPixel( 5, 32, 35, 231, 231, 35));
+ EXPECT_TRUE(checkPixel(13, 8, 35, 231, 231, 231));
+ EXPECT_TRUE(checkPixel(46, 3, 35, 35, 231, 35));
+ EXPECT_TRUE(checkPixel(30, 33, 35, 35, 35, 35));
+ EXPECT_TRUE(checkPixel( 6, 52, 231, 231, 35, 35));
+ EXPECT_TRUE(checkPixel(55, 33, 35, 231, 35, 231));
+ EXPECT_TRUE(checkPixel(16, 29, 35, 35, 231, 231));
+ EXPECT_TRUE(checkPixel( 1, 30, 35, 35, 35, 231));
+ EXPECT_TRUE(checkPixel(41, 37, 35, 35, 231, 231));
+ EXPECT_TRUE(checkPixel(46, 29, 231, 231, 35, 35));
+ EXPECT_TRUE(checkPixel(15, 25, 35, 231, 35, 231));
+ EXPECT_TRUE(checkPixel( 3, 52, 35, 231, 35, 35));
+}
+
+// Tests if GLConsumer and BufferQueue are robust enough
+// to handle a special case where updateTexImage is called
+// in the middle of disconnect. This ordering is enforced
+// by blocking in the disconnect callback.
+TEST_F(SurfaceTextureGLTest, DisconnectStressTest) {
+
+ class ProducerThread : public Thread {
+ public:
+ ProducerThread(const sp<ANativeWindow>& anw):
+ mANW(anw) {
+ }
+
+ virtual ~ProducerThread() {
+ }
+
+ virtual bool threadLoop() {
+ ANativeWindowBuffer* anb;
+
+ native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_EGL);
+
+ for (int numFrames =0 ; numFrames < 2; numFrames ++) {
+
+ if (native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb) != NO_ERROR) {
+ return false;
+ }
+ if (anb == NULL) {
+ return false;
+ }
+ if (mANW->queueBuffer(mANW.get(), anb, -1)
+ != NO_ERROR) {
+ return false;
+ }
+ }
+
+ native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL);
+
+ return false;
+ }
+
+ private:
+ sp<ANativeWindow> mANW;
+ };
+
+ sp<DisconnectWaiter> dw(new DisconnectWaiter());
+ mConsumer->consumerConnect(dw, false);
+
+
+ sp<Thread> pt(new ProducerThread(mANW));
+ pt->run();
+
+ // eat a frame so GLConsumer will own an at least one slot
+ dw->waitForFrame();
+ EXPECT_EQ(OK,mST->updateTexImage());
+
+ dw->waitForFrame();
+ // Could fail here as GLConsumer thinks it still owns the slot
+ // but bufferQueue has released all slots
+ EXPECT_EQ(OK,mST->updateTexImage());
+
+ dw->finishDisconnect();
+}
+
+
+// This test ensures that the GLConsumer clears the mCurrentTexture
+// when it is disconnected and reconnected. Otherwise it will
+// attempt to release a buffer that it does not owned
+TEST_F(SurfaceTextureGLTest, DisconnectClearsCurrentTexture) {
+ ASSERT_EQ(OK, native_window_api_connect(mANW.get(),
+ NATIVE_WINDOW_API_EGL));
+
+ ANativeWindowBuffer *anb;
+
+ EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+ EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
+
+ EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+ EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
+
+ EXPECT_EQ(OK,mST->updateTexImage());
+ EXPECT_EQ(OK,mST->updateTexImage());
+
+ ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(),
+ NATIVE_WINDOW_API_EGL));
+ ASSERT_EQ(OK, native_window_api_connect(mANW.get(),
+ NATIVE_WINDOW_API_EGL));
+
+ EXPECT_EQ(OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+ EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
+
+ // Will fail here if mCurrentTexture is not cleared properly
+ mFW->waitForFrame();
+ EXPECT_EQ(OK,mST->updateTexImage());
+
+ ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(),
+ NATIVE_WINDOW_API_EGL));
+}
+
+TEST_F(SurfaceTextureGLTest, ScaleToWindowMode) {
+ ASSERT_EQ(OK, native_window_set_scaling_mode(mANW.get(),
+ NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW));
+
+ // The producer image size
+ ASSERT_EQ(OK, native_window_set_buffers_dimensions(mANW.get(), 512, 512));
+
+ // The consumer image size (16 x 9) ratio
+ mST->setDefaultBufferSize(1280, 720);
+
+ ASSERT_EQ(OK, native_window_api_connect(mANW.get(),
+ NATIVE_WINDOW_API_CPU));
+
+ ANativeWindowBuffer *anb;
+
+ android_native_rect_t odd = {23, 78, 123, 477};
+ ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &odd));
+ EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+ EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
+ mFW->waitForFrame();
+ EXPECT_EQ(OK, mST->updateTexImage());
+ Rect r = mST->getCurrentCrop();
+ assertRectEq(Rect(23, 78, 123, 477), r);
+
+ ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(),
+ NATIVE_WINDOW_API_CPU));
+}
+
+// This test ensures the scaling mode does the right thing
+// ie NATIVE_WINDOW_SCALING_MODE_CROP should crop
+// the image such that it has the same aspect ratio as the
+// default buffer size
+TEST_F(SurfaceTextureGLTest, CroppedScalingMode) {
+ ASSERT_EQ(OK, native_window_set_scaling_mode(mANW.get(),
+ NATIVE_WINDOW_SCALING_MODE_SCALE_CROP));
+
+ // The producer image size
+ ASSERT_EQ(OK, native_window_set_buffers_dimensions(mANW.get(), 512, 512));
+
+ // The consumer image size (16 x 9) ratio
+ mST->setDefaultBufferSize(1280, 720);
+
+ native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_CPU);
+
+ ANativeWindowBuffer *anb;
+
+ // The crop is in the shape of (320, 180) === 16 x 9
+ android_native_rect_t standard = {10, 20, 330, 200};
+ ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &standard));
+ EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+ EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
+ mFW->waitForFrame();
+ EXPECT_EQ(OK, mST->updateTexImage());
+ Rect r = mST->getCurrentCrop();
+ // crop should be the same as crop (same aspect ratio)
+ assertRectEq(Rect(10, 20, 330, 200), r);
+
+ // make this wider then desired aspect 239 x 100 (2.39:1)
+ android_native_rect_t wide = {20, 30, 259, 130};
+ ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &wide));
+ EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+ EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
+ mFW->waitForFrame();
+ EXPECT_EQ(OK, mST->updateTexImage());
+ r = mST->getCurrentCrop();
+ // crop should be the same height, but have cropped left and right borders
+ // offset is 30.6 px L+, R-
+ assertRectEq(Rect(51, 30, 228, 130), r);
+
+ // This image is taller then desired aspect 400 x 300 (4:3)
+ android_native_rect_t narrow = {0, 0, 400, 300};
+ ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &narrow));
+ EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
+ EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
+ mFW->waitForFrame();
+ EXPECT_EQ(OK, mST->updateTexImage());
+ r = mST->getCurrentCrop();
+ // crop should be the same width, but have cropped top and bottom borders
+ // offset is 37.5 px
+ assertRectEq(Rect(0, 37, 400, 262), r);
+
+ native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
+}
+
+TEST_F(SurfaceTextureGLTest, AbandonUnblocksDequeueBuffer) {
+ class ProducerThread : public Thread {
+ public:
+ ProducerThread(const sp<ANativeWindow>& anw):
+ mANW(anw),
+ mDequeueError(NO_ERROR) {
+ }
+
+ virtual ~ProducerThread() {
+ }
+
+ virtual bool threadLoop() {
+ Mutex::Autolock lock(mMutex);
+ ANativeWindowBuffer* anb;
+
+ // Frame 1
+ if (native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb) != NO_ERROR) {
+ return false;
+ }
+ if (anb == NULL) {
+ return false;
+ }
+ if (mANW->queueBuffer(mANW.get(), anb, -1)
+ != NO_ERROR) {
+ return false;
+ }
+
+ // Frame 2
+ if (native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb) != NO_ERROR) {
+ return false;
+ }
+ if (anb == NULL) {
+ return false;
+ }
+ if (mANW->queueBuffer(mANW.get(), anb, -1)
+ != NO_ERROR) {
+ return false;
+ }
+
+ // Frame 3 - error expected
+ mDequeueError = native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb);
+ return false;
+ }
+
+ status_t getDequeueError() {
+ Mutex::Autolock lock(mMutex);
+ return mDequeueError;
+ }
+
+ private:
+ sp<ANativeWindow> mANW;
+ status_t mDequeueError;
+ Mutex mMutex;
+ };
+
+ ASSERT_EQ(OK, mST->setDefaultMaxBufferCount(2));
+
+ sp<Thread> pt(new ProducerThread(mANW));
+ pt->run();
+
+ mFW->waitForFrame();
+ mFW->waitForFrame();
+
+ // Sleep for 100ms to allow the producer thread's dequeueBuffer call to
+ // block waiting for a buffer to become available.
+ usleep(100000);
+
+ mST->abandon();
+
+ pt->requestExitAndWait();
+ ASSERT_EQ(NO_INIT,
+ reinterpret_cast<ProducerThread*>(pt.get())->getDequeueError());
+}
+
+TEST_F(SurfaceTextureGLTest, InvalidWidthOrHeightFails) {
+ int texHeight = 16;
+ ANativeWindowBuffer* anb;
+
+ GLint maxTextureSize;
+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
+
+ // make sure it works with small textures
+ mST->setDefaultBufferSize(16, texHeight);
+ EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb));
+ EXPECT_EQ(16, anb->width);
+ EXPECT_EQ(texHeight, anb->height);
+ EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1));
+ EXPECT_EQ(NO_ERROR, mST->updateTexImage());
+
+ // make sure it works with GL_MAX_TEXTURE_SIZE
+ mST->setDefaultBufferSize(maxTextureSize, texHeight);
+ EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb));
+ EXPECT_EQ(maxTextureSize, anb->width);
+ EXPECT_EQ(texHeight, anb->height);
+ EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1));
+ EXPECT_EQ(NO_ERROR, mST->updateTexImage());
+
+ // make sure it fails with GL_MAX_TEXTURE_SIZE+1
+ mST->setDefaultBufferSize(maxTextureSize+1, texHeight);
+ EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
+ &anb));
+ EXPECT_EQ(maxTextureSize+1, anb->width);
+ EXPECT_EQ(texHeight, anb->height);
+ EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1));
+ ASSERT_NE(NO_ERROR, mST->updateTexImage());
+}
+
+} // namespace android
diff --git a/libs/gui/tests/SurfaceTextureMultiContextGL.h b/libs/gui/tests/SurfaceTextureMultiContextGL.h
new file mode 100644
index 0000000..7934bbc
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureMultiContextGL.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SURFACE_TEXTURE_MULTI_CONTEXT_GL_H
+#define ANDROID_SURFACE_TEXTURE_MULTI_CONTEXT_GL_H
+
+#include "SurfaceTextureGL.h"
+
+namespace android {
+
+class SurfaceTextureMultiContextGLTest : public SurfaceTextureGLTest {
+protected:
+ enum { SECOND_TEX_ID = 123 };
+ enum { THIRD_TEX_ID = 456 };
+
+ SurfaceTextureMultiContextGLTest():
+ mSecondEglContext(EGL_NO_CONTEXT) {
+ }
+
+ virtual void SetUp() {
+ SurfaceTextureGLTest::SetUp();
+
+ // Set up the secondary context and texture renderer.
+ mSecondEglContext = eglCreateContext(mEglDisplay, mGlConfig,
+ EGL_NO_CONTEXT, getContextAttribs());
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_CONTEXT, mSecondEglContext);
+
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mSecondEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ mSecondTextureRenderer = new TextureRenderer(SECOND_TEX_ID, mST);
+ ASSERT_NO_FATAL_FAILURE(mSecondTextureRenderer->SetUp());
+
+ // Set up the tertiary context and texture renderer.
+ mThirdEglContext = eglCreateContext(mEglDisplay, mGlConfig,
+ EGL_NO_CONTEXT, getContextAttribs());
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_NE(EGL_NO_CONTEXT, mThirdEglContext);
+
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mThirdEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ mThirdTextureRenderer = new TextureRenderer(THIRD_TEX_ID, mST);
+ ASSERT_NO_FATAL_FAILURE(mThirdTextureRenderer->SetUp());
+
+ // Switch back to the primary context to start the tests.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mEglContext));
+ }
+
+ virtual void TearDown() {
+ if (mThirdEglContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEglDisplay, mThirdEglContext);
+ }
+ if (mSecondEglContext != EGL_NO_CONTEXT) {
+ eglDestroyContext(mEglDisplay, mSecondEglContext);
+ }
+ SurfaceTextureGLTest::TearDown();
+ }
+
+ EGLContext mSecondEglContext;
+ sp<TextureRenderer> mSecondTextureRenderer;
+
+ EGLContext mThirdEglContext;
+ sp<TextureRenderer> mThirdTextureRenderer;
+};
+
+}
+
+#endif
diff --git a/libs/gui/tests/SurfaceTextureMultiContextGL_test.cpp b/libs/gui/tests/SurfaceTextureMultiContextGL_test.cpp
new file mode 100644
index 0000000..115a47d
--- /dev/null
+++ b/libs/gui/tests/SurfaceTextureMultiContextGL_test.cpp
@@ -0,0 +1,389 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SurfaceTextureMultiContextGL_test"
+//#define LOG_NDEBUG 0
+
+#include "SurfaceTextureMultiContextGL.h"
+
+#include "FillBuffer.h"
+
+#include <GLES/glext.h>
+
+namespace android {
+
+TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Attempt to latch the texture on the secondary context.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mSecondEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+ ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextSucceeds) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Detach from the primary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Check that the GL texture was deleted.
+ EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+ DetachFromContextSucceedsAfterProducerDisconnect) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Detach from the primary context.
+ native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Check that the GL texture was deleted.
+ EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenAbandoned) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Attempt to detach from the primary context.
+ mST->abandon();
+ ASSERT_EQ(NO_INIT, mST->detachFromContext());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenDetached) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Detach from the primary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attempt to detach from the primary context again.
+ ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoDisplay) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Make there be no current display.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Attempt to detach from the primary context.
+ ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoContext) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Make current context be incorrect.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mSecondEglContext));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Attempt to detach from the primary context.
+ ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, UpdateTexImageFailsWhenDetached) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Detach from the primary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attempt to latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceeds) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Detach from the primary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attach to the secondary context.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mSecondEglContext));
+ ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+ // Verify that the texture object was created and bound.
+ GLint texBinding = -1;
+ glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+ EXPECT_EQ(SECOND_TEX_ID, texBinding);
+
+ // Try to use the texture from the secondary context.
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glViewport(0, 0, 1, 1);
+ mSecondTextureRenderer->drawTexture();
+ ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+ AttachToContextSucceedsAfterProducerDisconnect) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Detach from the primary context.
+ native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attach to the secondary context.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mSecondEglContext));
+ ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+ // Verify that the texture object was created and bound.
+ GLint texBinding = -1;
+ glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+ EXPECT_EQ(SECOND_TEX_ID, texBinding);
+
+ // Try to use the texture from the secondary context.
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glViewport(0, 0, 1, 1);
+ mSecondTextureRenderer->drawTexture();
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+ AttachToContextSucceedsBeforeUpdateTexImage) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Detach from the primary context.
+ native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attach to the secondary context.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mSecondEglContext));
+ ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+ // Verify that the texture object was created and bound.
+ GLint texBinding = -1;
+ glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+ EXPECT_EQ(SECOND_TEX_ID, texBinding);
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Try to use the texture from the secondary context.
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glViewport(0, 0, 1, 1);
+ mSecondTextureRenderer->drawTexture();
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAbandoned) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Detach from the primary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attempt to attach to the secondary context.
+ mST->abandon();
+
+ // Attempt to attach to the primary context.
+ ASSERT_EQ(NO_INIT, mST->attachToContext(SECOND_TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAttached) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Attempt to attach to the primary context.
+ ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+ AttachToContextFailsWhenAttachedBeforeUpdateTexImage) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Attempt to attach to the primary context.
+ ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWithNoDisplay) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Detach from the primary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Make there be no current display.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT));
+ ASSERT_EQ(EGL_SUCCESS, eglGetError());
+
+ // Attempt to attach with no context current.
+ ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceedsTwice) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Latch the texture contents on the primary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Detach from the primary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attach to the secondary context.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mSecondEglContext));
+ ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+ // Detach from the secondary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attach to the tertiary context.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mThirdEglContext));
+ ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID));
+
+ // Verify that the texture object was created and bound.
+ GLint texBinding = -1;
+ glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+ EXPECT_EQ(THIRD_TEX_ID, texBinding);
+
+ // Try to use the texture from the tertiary context.
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glViewport(0, 0, 1, 1);
+ mThirdTextureRenderer->drawTexture();
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+ AttachToContextSucceedsTwiceBeforeUpdateTexImage) {
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Detach from the primary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attach to the secondary context.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mSecondEglContext));
+ ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+ // Detach from the secondary context.
+ ASSERT_EQ(OK, mST->detachFromContext());
+
+ // Attach to the tertiary context.
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mThirdEglContext));
+ ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID));
+
+ // Verify that the texture object was created and bound.
+ GLint texBinding = -1;
+ glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
+ EXPECT_EQ(THIRD_TEX_ID, texBinding);
+
+ // Latch the texture contents on the tertiary context.
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // Try to use the texture from the tertiary context.
+ glClearColor(0.2, 0.2, 0.2, 0.2);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glViewport(0, 0, 1, 1);
+ mThirdTextureRenderer->drawTexture();
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
+}
+
+TEST_F(SurfaceTextureMultiContextGLTest,
+ UpdateTexImageSucceedsForBufferConsumedBeforeDetach) {
+ ASSERT_EQ(NO_ERROR, mST->setDefaultMaxBufferCount(2));
+
+ // produce two frames and consume them both on the primary context
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+
+ // produce one more frame
+ ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
+
+ // Detach from the primary context and attach to the secondary context
+ ASSERT_EQ(OK, mST->detachFromContext());
+ ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
+ mSecondEglContext));
+ ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
+
+ // Consume final frame on secondary context
+ mFW->waitForFrame();
+ ASSERT_EQ(OK, mST->updateTexImage());
+}
+
+} // namespace android
diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp
deleted file mode 100644
index e4fba15..0000000
--- a/libs/gui/tests/SurfaceTexture_test.cpp
+++ /dev/null
@@ -1,2816 +0,0 @@
-/*
- * 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 "SurfaceTexture_test"
-//#define LOG_NDEBUG 0
-
-#include <gtest/gtest.h>
-#include <gui/GLConsumer.h>
-#include <ui/GraphicBuffer.h>
-#include <utils/String8.h>
-#include <utils/threads.h>
-
-#include <gui/ISurfaceComposer.h>
-#include <gui/Surface.h>
-#include <gui/SurfaceComposerClient.h>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-
-#include <ui/FramebufferNativeWindow.h>
-#include <android/native_window.h>
-
-namespace android {
-
-class GLTest : public ::testing::Test {
-protected:
-
- GLTest():
- mEglDisplay(EGL_NO_DISPLAY),
- mEglSurface(EGL_NO_SURFACE),
- mEglContext(EGL_NO_CONTEXT) {
- }
-
- virtual void SetUp() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
-
- mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
-
- EGLint majorVersion;
- EGLint minorVersion;
- EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- RecordProperty("EglVersionMajor", majorVersion);
- RecordProperty("EglVersionMajor", minorVersion);
-
- EGLint numConfigs = 0;
- EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mGlConfig,
- 1, &numConfigs));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS");
- if (displaySecsEnv != NULL) {
- mDisplaySecs = atoi(displaySecsEnv);
- if (mDisplaySecs < 0) {
- mDisplaySecs = 0;
- }
- } else {
- mDisplaySecs = 0;
- }
-
- if (mDisplaySecs > 0) {
- mComposerClient = new SurfaceComposerClient;
- ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
-
- mSurfaceControl = mComposerClient->createSurface(
- String8("Test Surface"),
- getSurfaceWidth(), getSurfaceHeight(),
- PIXEL_FORMAT_RGB_888, 0);
-
- ASSERT_TRUE(mSurfaceControl != NULL);
- ASSERT_TRUE(mSurfaceControl->isValid());
-
- SurfaceComposerClient::openGlobalTransaction();
- ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF));
- ASSERT_EQ(NO_ERROR, mSurfaceControl->show());
- SurfaceComposerClient::closeGlobalTransaction();
-
- sp<ANativeWindow> window = mSurfaceControl->getSurface();
- mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
- window.get(), NULL);
- } else {
- EGLint pbufferAttribs[] = {
- EGL_WIDTH, getSurfaceWidth(),
- EGL_HEIGHT, getSurfaceHeight(),
- EGL_NONE };
-
- mEglSurface = eglCreatePbufferSurface(mEglDisplay, mGlConfig,
- pbufferAttribs);
- }
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_SURFACE, mEglSurface);
-
- mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT,
- getContextAttribs());
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
-
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- EGLint w, h;
- EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- RecordProperty("EglSurfaceWidth", w);
- RecordProperty("EglSurfaceHeight", h);
-
- glViewport(0, 0, w, h);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- }
-
- virtual void TearDown() {
- // Display the result
- if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) {
- eglSwapBuffers(mEglDisplay, mEglSurface);
- sleep(mDisplaySecs);
- }
-
- if (mComposerClient != NULL) {
- mComposerClient->dispose();
- }
- if (mEglContext != EGL_NO_CONTEXT) {
- eglDestroyContext(mEglDisplay, mEglContext);
- }
- if (mEglSurface != EGL_NO_SURFACE) {
- eglDestroySurface(mEglDisplay, mEglSurface);
- }
- if (mEglDisplay != EGL_NO_DISPLAY) {
- eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
- EGL_NO_CONTEXT);
- eglTerminate(mEglDisplay);
- }
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("End test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
- }
-
- virtual EGLint const* getConfigAttribs() {
- static EGLint sDefaultConfigAttribs[] = {
- EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
- EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 8,
- EGL_DEPTH_SIZE, 16,
- EGL_STENCIL_SIZE, 8,
- EGL_NONE };
-
- return sDefaultConfigAttribs;
- }
-
- virtual EGLint const* getContextAttribs() {
- static EGLint sDefaultContextAttribs[] = {
- EGL_CONTEXT_CLIENT_VERSION, 2,
- EGL_NONE };
-
- return sDefaultContextAttribs;
- }
-
- virtual EGLint getSurfaceWidth() {
- return 512;
- }
-
- virtual EGLint getSurfaceHeight() {
- return 512;
- }
-
- ::testing::AssertionResult checkPixel(int x, int y, int r,
- int g, int b, int a, int tolerance=2) {
- GLubyte pixel[4];
- String8 msg;
- glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
- GLenum err = glGetError();
- if (err != GL_NO_ERROR) {
- msg += String8::format("error reading pixel: %#x", err);
- while ((err = glGetError()) != GL_NO_ERROR) {
- msg += String8::format(", %#x", err);
- }
- return ::testing::AssertionFailure(
- ::testing::Message(msg.string()));
- }
- if (r >= 0 && abs(r - int(pixel[0])) > tolerance) {
- msg += String8::format("r(%d isn't %d)", pixel[0], r);
- }
- if (g >= 0 && abs(g - int(pixel[1])) > tolerance) {
- if (!msg.isEmpty()) {
- msg += " ";
- }
- msg += String8::format("g(%d isn't %d)", pixel[1], g);
- }
- if (b >= 0 && abs(b - int(pixel[2])) > tolerance) {
- if (!msg.isEmpty()) {
- msg += " ";
- }
- msg += String8::format("b(%d isn't %d)", pixel[2], b);
- }
- if (a >= 0 && abs(a - int(pixel[3])) > tolerance) {
- if (!msg.isEmpty()) {
- msg += " ";
- }
- msg += String8::format("a(%d isn't %d)", pixel[3], a);
- }
- if (!msg.isEmpty()) {
- return ::testing::AssertionFailure(
- ::testing::Message(msg.string()));
- } else {
- return ::testing::AssertionSuccess();
- }
- }
-
- ::testing::AssertionResult assertRectEq(const Rect &r1,
- const Rect &r2, int tolerance=1) {
-
- String8 msg;
-
- if (abs(r1.left - r2.left) > tolerance) {
- msg += String8::format("left(%d isn't %d)", r1.left, r2.left);
- }
- if (abs(r1.top - r2.top) > tolerance) {
- if (!msg.isEmpty()) {
- msg += " ";
- }
- msg += String8::format("top(%d isn't %d)", r1.top, r2.top);
- }
- if (abs(r1.right - r2.right) > tolerance) {
- if (!msg.isEmpty()) {
- msg += " ";
- }
- msg += String8::format("right(%d isn't %d)", r1.right, r2.right);
- }
- if (abs(r1.bottom - r2.bottom) > tolerance) {
- if (!msg.isEmpty()) {
- msg += " ";
- }
- msg += String8::format("bottom(%d isn't %d)", r1.bottom, r2.bottom);
- }
- if (!msg.isEmpty()) {
- msg += String8::format(" R1: [%d %d %d %d] R2: [%d %d %d %d]",
- r1.left, r1.top, r1.right, r1.bottom,
- r2.left, r2.top, r2.right, r2.bottom);
- fprintf(stderr, "assertRectEq: %s\n", msg.string());
- return ::testing::AssertionFailure(
- ::testing::Message(msg.string()));
- } else {
- return ::testing::AssertionSuccess();
- }
- }
-
- int mDisplaySecs;
- sp<SurfaceComposerClient> mComposerClient;
- sp<SurfaceControl> mSurfaceControl;
-
- EGLDisplay mEglDisplay;
- EGLSurface mEglSurface;
- EGLContext mEglContext;
- EGLConfig mGlConfig;
-};
-
-static void loadShader(GLenum shaderType, const char* pSource,
- GLuint* outShader) {
- GLuint shader = glCreateShader(shaderType);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- if (shader) {
- glShaderSource(shader, 1, &pSource, NULL);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- glCompileShader(shader);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- GLint compiled = 0;
- glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- if (!compiled) {
- GLint infoLen = 0;
- glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- if (infoLen) {
- char* buf = (char*) malloc(infoLen);
- if (buf) {
- glGetShaderInfoLog(shader, infoLen, NULL, buf);
- printf("Shader compile log:\n%s\n", buf);
- free(buf);
- FAIL();
- }
- } else {
- char* buf = (char*) malloc(0x1000);
- if (buf) {
- glGetShaderInfoLog(shader, 0x1000, NULL, buf);
- printf("Shader compile log:\n%s\n", buf);
- free(buf);
- FAIL();
- }
- }
- glDeleteShader(shader);
- shader = 0;
- }
- }
- ASSERT_TRUE(shader != 0);
- *outShader = shader;
-}
-
-static void createProgram(const char* pVertexSource,
- const char* pFragmentSource, GLuint* outPgm) {
- GLuint vertexShader, fragmentShader;
- {
- SCOPED_TRACE("compiling vertex shader");
- ASSERT_NO_FATAL_FAILURE(loadShader(GL_VERTEX_SHADER, pVertexSource,
- &vertexShader));
- }
- {
- SCOPED_TRACE("compiling fragment shader");
- ASSERT_NO_FATAL_FAILURE(loadShader(GL_FRAGMENT_SHADER, pFragmentSource,
- &fragmentShader));
- }
-
- GLuint program = glCreateProgram();
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- if (program) {
- glAttachShader(program, vertexShader);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- glAttachShader(program, fragmentShader);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- glLinkProgram(program);
- GLint linkStatus = GL_FALSE;
- glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
- if (linkStatus != GL_TRUE) {
- GLint bufLength = 0;
- glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
- if (bufLength) {
- char* buf = (char*) malloc(bufLength);
- if (buf) {
- glGetProgramInfoLog(program, bufLength, NULL, buf);
- printf("Program link log:\n%s\n", buf);
- free(buf);
- FAIL();
- }
- }
- glDeleteProgram(program);
- program = 0;
- }
- }
- glDeleteShader(vertexShader);
- glDeleteShader(fragmentShader);
- ASSERT_TRUE(program != 0);
- *outPgm = program;
-}
-
-static int abs(int value) {
- return value > 0 ? value : -value;
-}
-
-
-// XXX: Code above this point should live elsewhere
-
-class MultiTextureConsumerTest : public GLTest {
-protected:
- enum { TEX_ID = 123 };
-
- virtual void SetUp() {
- GLTest::SetUp();
- sp<BufferQueue> bq = new BufferQueue();
- mGlConsumer = new GLConsumer(bq, TEX_ID);
- mSurface = new Surface(bq);
- mANW = mSurface.get();
-
- }
- virtual void TearDown() {
- GLTest::TearDown();
- }
- virtual EGLint const* getContextAttribs() {
- return NULL;
- }
- virtual EGLint const* getConfigAttribs() {
- static EGLint sDefaultConfigAttribs[] = {
- EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 8,
- EGL_NONE };
-
- return sDefaultConfigAttribs;
- }
- sp<GLConsumer> mGlConsumer;
- sp<Surface> mSurface;
- ANativeWindow* mANW;
-};
-
-
-TEST_F(MultiTextureConsumerTest, EGLImageTargetWorks) {
- ANativeWindow_Buffer buffer;
-
- ASSERT_EQ(native_window_set_usage(mANW, GRALLOC_USAGE_SW_WRITE_OFTEN), NO_ERROR);
- ASSERT_EQ(native_window_set_buffers_format(mANW, HAL_PIXEL_FORMAT_RGBA_8888), NO_ERROR);
-
- glShadeModel(GL_FLAT);
- glDisable(GL_DITHER);
- glDisable(GL_CULL_FACE);
- glViewport(0, 0, getSurfaceWidth(), getSurfaceHeight());
- glOrthof(0, getSurfaceWidth(), 0, getSurfaceHeight(), 0, 1);
- glEnableClientState(GL_VERTEX_ARRAY);
- glColor4f(1, 1, 1, 1);
-
- glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID);
- glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-
- uint32_t texel = 0x80808080;
- glBindTexture(GL_TEXTURE_2D, TEX_ID+1);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &texel);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-
- glActiveTexture(GL_TEXTURE1);
- glBindTexture(GL_TEXTURE_2D, TEX_ID+1);
- glEnable(GL_TEXTURE_2D);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_EXTERNAL_OES, TEX_ID);
- glEnable(GL_TEXTURE_EXTERNAL_OES);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-
- glClear(GL_COLOR_BUFFER_BIT);
- for (int i=0 ; i<8 ; i++) {
- mSurface->lock(&buffer, NULL);
- memset(buffer.bits, (i&7) * 0x20, buffer.stride * buffer.height * 4);
- mSurface->unlockAndPost();
-
- mGlConsumer->updateTexImage();
-
- GLfloat vertices[][2] = { {i*16.0f, 0}, {(i+1)*16.0f, 0}, {(i+1)*16.0f, 16.0f}, {i*16.0f, 16.0f} };
- glVertexPointer(2, GL_FLOAT, 0, vertices);
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
-
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- }
-
- for (int i=0 ; i<8 ; i++) {
- EXPECT_TRUE(checkPixel(i*16 + 8, 8, i*16, i*16, i*16, i*16, 0));
- }
-}
-
-
-
-class SurfaceTextureGLTest : public GLTest {
-protected:
- enum { TEX_ID = 123 };
-
- virtual void SetUp() {
- GLTest::SetUp();
- sp<BufferQueue> bq = new BufferQueue();
- mBQ = bq;
- mST = new GLConsumer(bq, TEX_ID);
- mSTC = new Surface(bq);
- mANW = mSTC;
- mTextureRenderer = new TextureRenderer(TEX_ID, mST);
- ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp());
- mFW = new FrameWaiter;
- mST->setFrameAvailableListener(mFW);
- }
-
- virtual void TearDown() {
- mANW.clear();
- mSTC.clear();
- mST.clear();
- GLTest::TearDown();
- }
-
- void drawTexture() {
- mTextureRenderer->drawTexture();
- }
-
- class TextureRenderer: public RefBase {
- public:
- TextureRenderer(GLuint texName, const sp<GLConsumer>& st):
- mTexName(texName),
- mST(st) {
- }
-
- void SetUp() {
- const char vsrc[] =
- "attribute vec4 vPosition;\n"
- "varying vec2 texCoords;\n"
- "uniform mat4 texMatrix;\n"
- "void main() {\n"
- " vec2 vTexCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n"
- " texCoords = (texMatrix * vec4(vTexCoords, 0.0, 1.0)).xy;\n"
- " gl_Position = vPosition;\n"
- "}\n";
-
- const char fsrc[] =
- "#extension GL_OES_EGL_image_external : require\n"
- "precision mediump float;\n"
- "uniform samplerExternalOES texSampler;\n"
- "varying vec2 texCoords;\n"
- "void main() {\n"
- " gl_FragColor = texture2D(texSampler, texCoords);\n"
- "}\n";
-
- {
- SCOPED_TRACE("creating shader program");
- ASSERT_NO_FATAL_FAILURE(createProgram(vsrc, fsrc, &mPgm));
- }
-
- mPositionHandle = glGetAttribLocation(mPgm, "vPosition");
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- ASSERT_NE(-1, mPositionHandle);
- mTexSamplerHandle = glGetUniformLocation(mPgm, "texSampler");
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- ASSERT_NE(-1, mTexSamplerHandle);
- mTexMatrixHandle = glGetUniformLocation(mPgm, "texMatrix");
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- ASSERT_NE(-1, mTexMatrixHandle);
- }
-
- // drawTexture draws the GLConsumer over the entire GL viewport.
- void drawTexture() {
- static const GLfloat triangleVertices[] = {
- -1.0f, 1.0f,
- -1.0f, -1.0f,
- 1.0f, -1.0f,
- 1.0f, 1.0f,
- };
-
- glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0,
- triangleVertices);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- glEnableVertexAttribArray(mPositionHandle);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-
- glUseProgram(mPgm);
- glUniform1i(mTexSamplerHandle, 0);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-
- // XXX: These calls are not needed for GL_TEXTURE_EXTERNAL_OES as
- // they're setting the defautls for that target, but when hacking
- // things to use GL_TEXTURE_2D they are needed to achieve the same
- // behavior.
- glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER,
- GL_LINEAR);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER,
- GL_LINEAR);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S,
- GL_CLAMP_TO_EDGE);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T,
- GL_CLAMP_TO_EDGE);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-
- GLfloat texMatrix[16];
- mST->getTransformMatrix(texMatrix);
- glUniformMatrix4fv(mTexMatrixHandle, 1, GL_FALSE, texMatrix);
-
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- }
-
- GLuint mTexName;
- sp<GLConsumer> mST;
- GLuint mPgm;
- GLint mPositionHandle;
- GLint mTexSamplerHandle;
- GLint mTexMatrixHandle;
- };
-
- class FrameWaiter : public GLConsumer::FrameAvailableListener {
- public:
- FrameWaiter():
- mPendingFrames(0) {
- }
-
- void waitForFrame() {
- Mutex::Autolock lock(mMutex);
- while (mPendingFrames == 0) {
- mCondition.wait(mMutex);
- }
- mPendingFrames--;
- }
-
- virtual void onFrameAvailable() {
- Mutex::Autolock lock(mMutex);
- mPendingFrames++;
- mCondition.signal();
- }
-
- int mPendingFrames;
- Mutex mMutex;
- Condition mCondition;
- };
-
- // Note that GLConsumer will lose the notifications
- // onBuffersReleased and onFrameAvailable as there is currently
- // no way to forward the events. This DisconnectWaiter will not let the
- // disconnect finish until finishDisconnect() is called. It will
- // also block until a disconnect is called
- class DisconnectWaiter : public BnConsumerListener {
- public:
- DisconnectWaiter () :
- mWaitForDisconnect(false),
- mPendingFrames(0) {
- }
-
- void waitForFrame() {
- Mutex::Autolock lock(mMutex);
- while (mPendingFrames == 0) {
- mFrameCondition.wait(mMutex);
- }
- mPendingFrames--;
- }
-
- virtual void onFrameAvailable() {
- Mutex::Autolock lock(mMutex);
- mPendingFrames++;
- mFrameCondition.signal();
- }
-
- virtual void onBuffersReleased() {
- Mutex::Autolock lock(mMutex);
- while (!mWaitForDisconnect) {
- mDisconnectCondition.wait(mMutex);
- }
- }
-
- void finishDisconnect() {
- Mutex::Autolock lock(mMutex);
- mWaitForDisconnect = true;
- mDisconnectCondition.signal();
- }
-
- private:
- Mutex mMutex;
-
- bool mWaitForDisconnect;
- Condition mDisconnectCondition;
-
- int mPendingFrames;
- Condition mFrameCondition;
- };
-
- sp<BufferQueue> mBQ;
- sp<GLConsumer> mST;
- sp<Surface> mSTC;
- sp<ANativeWindow> mANW;
- sp<TextureRenderer> mTextureRenderer;
- sp<FrameWaiter> mFW;
-};
-
-// Fill a YV12 buffer with a multi-colored checkerboard pattern
-void fillYV12Buffer(uint8_t* buf, int w, int h, int stride) {
- const int blockWidth = w > 16 ? w / 16 : 1;
- const int blockHeight = h > 16 ? h / 16 : 1;
- const int yuvTexOffsetY = 0;
- int yuvTexStrideY = stride;
- int yuvTexOffsetV = yuvTexStrideY * h;
- int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
- int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2;
- int yuvTexStrideU = yuvTexStrideV;
- for (int x = 0; x < w; x++) {
- for (int y = 0; y < h; y++) {
- int parityX = (x / blockWidth) & 1;
- int parityY = (y / blockHeight) & 1;
- unsigned char intensity = (parityX ^ parityY) ? 63 : 191;
- buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = intensity;
- if (x < w / 2 && y < h / 2) {
- buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = intensity;
- if (x * 2 < w / 2 && y * 2 < h / 2) {
- buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 0] =
- buf[yuvTexOffsetV + (y*2 * yuvTexStrideV) + x*2 + 1] =
- buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 0] =
- buf[yuvTexOffsetV + ((y*2+1) * yuvTexStrideV) + x*2 + 1] =
- intensity;
- }
- }
- }
- }
-}
-
-// Fill a YV12 buffer with red outside a given rectangle and green inside it.
-void fillYV12BufferRect(uint8_t* buf, int w, int h, int stride,
- const android_native_rect_t& rect) {
- const int yuvTexOffsetY = 0;
- int yuvTexStrideY = stride;
- int yuvTexOffsetV = yuvTexStrideY * h;
- int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
- int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * h/2;
- int yuvTexStrideU = yuvTexStrideV;
- for (int x = 0; x < w; x++) {
- for (int y = 0; y < h; y++) {
- bool inside = rect.left <= x && x < rect.right &&
- rect.top <= y && y < rect.bottom;
- buf[yuvTexOffsetY + (y * yuvTexStrideY) + x] = inside ? 240 : 64;
- if (x < w / 2 && y < h / 2) {
- bool inside = rect.left <= 2*x && 2*x < rect.right &&
- rect.top <= 2*y && 2*y < rect.bottom;
- buf[yuvTexOffsetU + (y * yuvTexStrideU) + x] = 16;
- buf[yuvTexOffsetV + (y * yuvTexStrideV) + x] =
- inside ? 16 : 255;
- }
- }
- }
-}
-
-void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
- const size_t PIXEL_SIZE = 4;
- for (int x = 0; x < w; x++) {
- for (int y = 0; y < h; y++) {
- off_t offset = (y * stride + x) * PIXEL_SIZE;
- for (int c = 0; c < 4; c++) {
- int parityX = (x / (1 << (c+2))) & 1;
- int parityY = (y / (1 << (c+2))) & 1;
- buf[offset + c] = (parityX ^ parityY) ? 231 : 35;
- }
- }
- }
-}
-
-void fillRGBA8BufferSolid(uint8_t* buf, int w, int h, int stride, uint8_t r,
- uint8_t g, uint8_t b, uint8_t a) {
- const size_t PIXEL_SIZE = 4;
- for (int y = 0; y < h; y++) {
- for (int x = 0; x < h; x++) {
- off_t offset = (y * stride + x) * PIXEL_SIZE;
- buf[offset + 0] = r;
- buf[offset + 1] = g;
- buf[offset + 2] = b;
- buf[offset + 3] = a;
- }
- }
-}
-
-// Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern
-// using the CPU. This assumes that the ANativeWindow is already configured to
-// allow this to be done (e.g. the format is set to RGBA8).
-//
-// Calls to this function should be wrapped in an ASSERT_NO_FATAL_FAILURE().
-void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) {
- android_native_buffer_t* anb;
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
- &anb));
- ASSERT_TRUE(anb != NULL);
-
- sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
-
- uint8_t* img = NULL;
- ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN,
- (void**)(&img)));
- fillRGBA8Buffer(img, buf->getWidth(), buf->getHeight(), buf->getStride());
- ASSERT_EQ(NO_ERROR, buf->unlock());
- ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf->getNativeBuffer(),
- -1));
-}
-
-TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferNpot) {
- const int texWidth = 64;
- const int texHeight = 66;
-
- ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
- texWidth, texHeight, HAL_PIXEL_FORMAT_YV12));
- ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
- GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
-
- ANativeWindowBuffer* anb;
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb));
- ASSERT_TRUE(anb != NULL);
-
- sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
-
- // Fill the buffer with the a checkerboard pattern
- uint8_t* img = NULL;
- buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
- fillYV12Buffer(img, texWidth, texHeight, buf->getStride());
- buf->unlock();
- ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(),
- -1));
-
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glViewport(0, 0, texWidth, texHeight);
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 0, 0, 255, 127, 255, 255, 3));
- EXPECT_TRUE(checkPixel(63, 0, 0, 133, 0, 255, 3));
- EXPECT_TRUE(checkPixel(63, 65, 0, 133, 0, 255, 3));
- EXPECT_TRUE(checkPixel( 0, 65, 255, 127, 255, 255, 3));
-
- EXPECT_TRUE(checkPixel(22, 44, 255, 127, 255, 255, 3));
- EXPECT_TRUE(checkPixel(45, 52, 255, 127, 255, 255, 3));
- EXPECT_TRUE(checkPixel(52, 51, 98, 255, 73, 255, 3));
- EXPECT_TRUE(checkPixel( 7, 31, 155, 0, 118, 255, 3));
- EXPECT_TRUE(checkPixel(31, 9, 107, 24, 87, 255, 3));
- EXPECT_TRUE(checkPixel(29, 35, 255, 127, 255, 255, 3));
- EXPECT_TRUE(checkPixel(36, 22, 155, 29, 0, 255, 3));
-}
-
-TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferPow2) {
- const int texWidth = 64;
- const int texHeight = 64;
-
- ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
- texWidth, texHeight, HAL_PIXEL_FORMAT_YV12));
- ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
- GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
-
- ANativeWindowBuffer* anb;
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb));
- ASSERT_TRUE(anb != NULL);
-
- sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
-
- // Fill the buffer with the a checkerboard pattern
- uint8_t* img = NULL;
- buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
- fillYV12Buffer(img, texWidth, texHeight, buf->getStride());
- buf->unlock();
- ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(),
- -1));
-
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glViewport(0, 0, texWidth, texHeight);
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 0, 0, 0, 133, 0, 255));
- EXPECT_TRUE(checkPixel(63, 0, 255, 127, 255, 255));
- EXPECT_TRUE(checkPixel(63, 63, 0, 133, 0, 255));
- EXPECT_TRUE(checkPixel( 0, 63, 255, 127, 255, 255));
-
- EXPECT_TRUE(checkPixel(22, 19, 100, 255, 74, 255));
- EXPECT_TRUE(checkPixel(45, 11, 100, 255, 74, 255));
- EXPECT_TRUE(checkPixel(52, 12, 155, 0, 181, 255));
- EXPECT_TRUE(checkPixel( 7, 32, 150, 237, 170, 255));
- EXPECT_TRUE(checkPixel(31, 54, 0, 71, 117, 255));
- EXPECT_TRUE(checkPixel(29, 28, 0, 133, 0, 255));
- EXPECT_TRUE(checkPixel(36, 41, 100, 232, 255, 255));
-}
-
-TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) {
- const int texWidth = 64;
- const int texHeight = 66;
-
- ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
- texWidth, texHeight, HAL_PIXEL_FORMAT_YV12));
- ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
- GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
-
- android_native_rect_t crops[] = {
- {4, 6, 22, 36},
- {0, 6, 22, 36},
- {4, 0, 22, 36},
- {4, 6, texWidth, 36},
- {4, 6, 22, texHeight},
- };
-
- for (int i = 0; i < 5; i++) {
- const android_native_rect_t& crop(crops[i]);
- SCOPED_TRACE(String8::format("rect{ l: %d t: %d r: %d b: %d }",
- crop.left, crop.top, crop.right, crop.bottom).string());
-
- ASSERT_EQ(NO_ERROR, native_window_set_crop(mANW.get(), &crop));
-
- ANativeWindowBuffer* anb;
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb));
- ASSERT_TRUE(anb != NULL);
-
- sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
-
- uint8_t* img = NULL;
- buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
- fillYV12BufferRect(img, texWidth, texHeight, buf->getStride(), crop);
- buf->unlock();
- ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(),
- buf->getNativeBuffer(), -1));
-
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glViewport(0, 0, 64, 64);
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 0, 0, 82, 255, 35, 255));
- EXPECT_TRUE(checkPixel(63, 0, 82, 255, 35, 255));
- EXPECT_TRUE(checkPixel(63, 63, 82, 255, 35, 255));
- EXPECT_TRUE(checkPixel( 0, 63, 82, 255, 35, 255));
-
- EXPECT_TRUE(checkPixel(25, 14, 82, 255, 35, 255));
- EXPECT_TRUE(checkPixel(35, 31, 82, 255, 35, 255));
- EXPECT_TRUE(checkPixel(57, 6, 82, 255, 35, 255));
- EXPECT_TRUE(checkPixel( 5, 42, 82, 255, 35, 255));
- EXPECT_TRUE(checkPixel(32, 33, 82, 255, 35, 255));
- EXPECT_TRUE(checkPixel(16, 26, 82, 255, 35, 255));
- EXPECT_TRUE(checkPixel(46, 51, 82, 255, 35, 255));
- }
-}
-
-// This test is intended to catch synchronization bugs between the CPU-written
-// and GPU-read buffers.
-TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BuffersRepeatedly) {
- enum { texWidth = 16 };
- enum { texHeight = 16 };
- enum { numFrames = 1024 };
-
- ASSERT_EQ(NO_ERROR, mST->setDefaultMaxBufferCount(2));
- ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
- texWidth, texHeight, HAL_PIXEL_FORMAT_YV12));
- ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
- GRALLOC_USAGE_SW_WRITE_OFTEN));
-
- struct TestPixel {
- int x;
- int y;
- };
- const TestPixel testPixels[] = {
- { 4, 11 },
- { 12, 14 },
- { 7, 2 },
- };
- enum {numTestPixels = sizeof(testPixels) / sizeof(testPixels[0])};
-
- class ProducerThread : public Thread {
- public:
- ProducerThread(const sp<ANativeWindow>& anw,
- const TestPixel* testPixels):
- mANW(anw),
- mTestPixels(testPixels) {
- }
-
- virtual ~ProducerThread() {
- }
-
- virtual bool threadLoop() {
- for (int i = 0; i < numFrames; i++) {
- ANativeWindowBuffer* anb;
- if (native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb) != NO_ERROR) {
- return false;
- }
- if (anb == NULL) {
- return false;
- }
-
- sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
-
- const int yuvTexOffsetY = 0;
- int stride = buf->getStride();
- int yuvTexStrideY = stride;
- int yuvTexOffsetV = yuvTexStrideY * texHeight;
- int yuvTexStrideV = (yuvTexStrideY/2 + 0xf) & ~0xf;
- int yuvTexOffsetU = yuvTexOffsetV + yuvTexStrideV * texHeight/2;
- int yuvTexStrideU = yuvTexStrideV;
-
- uint8_t* img = NULL;
- buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
-
- // Gray out all the test pixels first, so we're more likely to
- // see a failure if GL is still texturing from the buffer we
- // just dequeued.
- for (int j = 0; j < numTestPixels; j++) {
- int x = mTestPixels[j].x;
- int y = mTestPixels[j].y;
- uint8_t value = 128;
- img[y*stride + x] = value;
- }
-
- // Fill the buffer with gray.
- for (int y = 0; y < texHeight; y++) {
- for (int x = 0; x < texWidth; x++) {
- img[yuvTexOffsetY + y*yuvTexStrideY + x] = 128;
- img[yuvTexOffsetU + (y/2)*yuvTexStrideU + x/2] = 128;
- img[yuvTexOffsetV + (y/2)*yuvTexStrideV + x/2] = 128;
- }
- }
-
- // Set the test pixels to either white or black.
- for (int j = 0; j < numTestPixels; j++) {
- int x = mTestPixels[j].x;
- int y = mTestPixels[j].y;
- uint8_t value = 0;
- if (j == (i % numTestPixels)) {
- value = 255;
- }
- img[y*stride + x] = value;
- }
-
- buf->unlock();
- if (mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(), -1)
- != NO_ERROR) {
- return false;
- }
- }
- return false;
- }
-
- sp<ANativeWindow> mANW;
- const TestPixel* mTestPixels;
- };
-
- sp<Thread> pt(new ProducerThread(mANW, testPixels));
- pt->run();
-
- glViewport(0, 0, texWidth, texHeight);
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- // We wait for the first two frames up front so that the producer will be
- // likely to dequeue the buffer that's currently being textured from.
- mFW->waitForFrame();
- mFW->waitForFrame();
-
- for (int i = 0; i < numFrames; i++) {
- SCOPED_TRACE(String8::format("frame %d", i).string());
-
- // We must wait for each frame to come in because if we ever do an
- // updateTexImage call that doesn't consume a newly available buffer
- // then the producer and consumer will get out of sync, which will cause
- // a deadlock.
- if (i > 1) {
- mFW->waitForFrame();
- }
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- drawTexture();
-
- for (int j = 0; j < numTestPixels; j++) {
- int x = testPixels[j].x;
- int y = testPixels[j].y;
- uint8_t value = 0;
- if (j == (i % numTestPixels)) {
- // We must y-invert the texture coords
- EXPECT_TRUE(checkPixel(x, texHeight-y-1, 255, 255, 255, 255));
- } else {
- // We must y-invert the texture coords
- EXPECT_TRUE(checkPixel(x, texHeight-y-1, 0, 0, 0, 255));
- }
- }
- }
-
- pt->requestExitAndWait();
-}
-
-TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferNpot) {
- const int texWidth = 64;
- const int texHeight = 66;
-
- ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
- texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888));
- ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
- GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
-
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glViewport(0, 0, texWidth, texHeight);
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
- EXPECT_TRUE(checkPixel(63, 0, 231, 231, 231, 231));
- EXPECT_TRUE(checkPixel(63, 65, 231, 231, 231, 231));
- EXPECT_TRUE(checkPixel( 0, 65, 35, 35, 35, 35));
-
- EXPECT_TRUE(checkPixel(15, 10, 35, 231, 231, 231));
- EXPECT_TRUE(checkPixel(23, 65, 231, 35, 231, 35));
- EXPECT_TRUE(checkPixel(19, 40, 35, 231, 35, 35));
- EXPECT_TRUE(checkPixel(38, 30, 231, 35, 35, 35));
- EXPECT_TRUE(checkPixel(42, 54, 35, 35, 35, 231));
- EXPECT_TRUE(checkPixel(37, 34, 35, 231, 231, 231));
- EXPECT_TRUE(checkPixel(31, 8, 231, 35, 35, 231));
- EXPECT_TRUE(checkPixel(37, 47, 231, 35, 231, 231));
- EXPECT_TRUE(checkPixel(25, 38, 35, 35, 35, 35));
- EXPECT_TRUE(checkPixel(49, 6, 35, 231, 35, 35));
- EXPECT_TRUE(checkPixel(54, 50, 35, 231, 231, 231));
- EXPECT_TRUE(checkPixel(27, 26, 231, 231, 231, 231));
- EXPECT_TRUE(checkPixel(10, 6, 35, 35, 231, 231));
- EXPECT_TRUE(checkPixel(29, 4, 35, 35, 35, 231));
- EXPECT_TRUE(checkPixel(55, 28, 35, 35, 231, 35));
- EXPECT_TRUE(checkPixel(58, 55, 35, 35, 231, 231));
-}
-
-TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledRGBABufferPow2) {
- const int texWidth = 64;
- const int texHeight = 64;
-
- ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
- texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888));
- ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
- GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
-
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glViewport(0, 0, texWidth, texHeight);
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 0, 0, 231, 231, 231, 231));
- EXPECT_TRUE(checkPixel(63, 0, 35, 35, 35, 35));
- EXPECT_TRUE(checkPixel(63, 63, 231, 231, 231, 231));
- EXPECT_TRUE(checkPixel( 0, 63, 35, 35, 35, 35));
-
- EXPECT_TRUE(checkPixel(12, 46, 231, 231, 231, 35));
- EXPECT_TRUE(checkPixel(16, 1, 231, 231, 35, 231));
- EXPECT_TRUE(checkPixel(21, 12, 231, 35, 35, 231));
- EXPECT_TRUE(checkPixel(26, 51, 231, 35, 231, 35));
- EXPECT_TRUE(checkPixel( 5, 32, 35, 231, 231, 35));
- EXPECT_TRUE(checkPixel(13, 8, 35, 231, 231, 231));
- EXPECT_TRUE(checkPixel(46, 3, 35, 35, 231, 35));
- EXPECT_TRUE(checkPixel(30, 33, 35, 35, 35, 35));
- EXPECT_TRUE(checkPixel( 6, 52, 231, 231, 35, 35));
- EXPECT_TRUE(checkPixel(55, 33, 35, 231, 35, 231));
- EXPECT_TRUE(checkPixel(16, 29, 35, 35, 231, 231));
- EXPECT_TRUE(checkPixel( 1, 30, 35, 35, 35, 231));
- EXPECT_TRUE(checkPixel(41, 37, 35, 35, 231, 231));
- EXPECT_TRUE(checkPixel(46, 29, 231, 231, 35, 35));
- EXPECT_TRUE(checkPixel(15, 25, 35, 231, 35, 231));
- EXPECT_TRUE(checkPixel( 3, 52, 35, 231, 35, 35));
-}
-
-// Tests if GLConsumer and BufferQueue are robust enough
-// to handle a special case where updateTexImage is called
-// in the middle of disconnect. This ordering is enforced
-// by blocking in the disconnect callback.
-TEST_F(SurfaceTextureGLTest, DisconnectStressTest) {
-
- class ProducerThread : public Thread {
- public:
- ProducerThread(const sp<ANativeWindow>& anw):
- mANW(anw) {
- }
-
- virtual ~ProducerThread() {
- }
-
- virtual bool threadLoop() {
- ANativeWindowBuffer* anb;
-
- native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_EGL);
-
- for (int numFrames =0 ; numFrames < 2; numFrames ++) {
-
- if (native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb) != NO_ERROR) {
- return false;
- }
- if (anb == NULL) {
- return false;
- }
- if (mANW->queueBuffer(mANW.get(), anb, -1)
- != NO_ERROR) {
- return false;
- }
- }
-
- native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_EGL);
-
- return false;
- }
-
- private:
- sp<ANativeWindow> mANW;
- };
-
- sp<DisconnectWaiter> dw(new DisconnectWaiter());
- mBQ->consumerConnect(dw, false);
-
-
- sp<Thread> pt(new ProducerThread(mANW));
- pt->run();
-
- // eat a frame so GLConsumer will own an at least one slot
- dw->waitForFrame();
- EXPECT_EQ(OK,mST->updateTexImage());
-
- dw->waitForFrame();
- // Could fail here as GLConsumer thinks it still owns the slot
- // but bufferQueue has released all slots
- EXPECT_EQ(OK,mST->updateTexImage());
-
- dw->finishDisconnect();
-}
-
-
-// This test ensures that the GLConsumer clears the mCurrentTexture
-// when it is disconnected and reconnected. Otherwise it will
-// attempt to release a buffer that it does not owned
-TEST_F(SurfaceTextureGLTest, DisconnectClearsCurrentTexture) {
- ASSERT_EQ(OK, native_window_api_connect(mANW.get(),
- NATIVE_WINDOW_API_EGL));
-
- ANativeWindowBuffer *anb;
-
- EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
- EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
-
- EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
- EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
-
- EXPECT_EQ(OK,mST->updateTexImage());
- EXPECT_EQ(OK,mST->updateTexImage());
-
- ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(),
- NATIVE_WINDOW_API_EGL));
- ASSERT_EQ(OK, native_window_api_connect(mANW.get(),
- NATIVE_WINDOW_API_EGL));
-
- EXPECT_EQ(OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
- EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
-
- // Will fail here if mCurrentTexture is not cleared properly
- mFW->waitForFrame();
- EXPECT_EQ(OK,mST->updateTexImage());
-
- ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(),
- NATIVE_WINDOW_API_EGL));
-}
-
-TEST_F(SurfaceTextureGLTest, ScaleToWindowMode) {
- ASSERT_EQ(OK, native_window_set_scaling_mode(mANW.get(),
- NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW));
-
- // The producer image size
- ASSERT_EQ(OK, native_window_set_buffers_dimensions(mANW.get(), 512, 512));
-
- // The consumer image size (16 x 9) ratio
- mST->setDefaultBufferSize(1280, 720);
-
- ASSERT_EQ(OK, native_window_api_connect(mANW.get(),
- NATIVE_WINDOW_API_CPU));
-
- ANativeWindowBuffer *anb;
-
- android_native_rect_t odd = {23, 78, 123, 477};
- ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &odd));
- EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
- EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
- mFW->waitForFrame();
- EXPECT_EQ(OK, mST->updateTexImage());
- Rect r = mST->getCurrentCrop();
- assertRectEq(Rect(23, 78, 123, 477), r);
-
- ASSERT_EQ(OK, native_window_api_disconnect(mANW.get(),
- NATIVE_WINDOW_API_CPU));
-}
-
-// This test ensures the scaling mode does the right thing
-// ie NATIVE_WINDOW_SCALING_MODE_CROP should crop
-// the image such that it has the same aspect ratio as the
-// default buffer size
-TEST_F(SurfaceTextureGLTest, CroppedScalingMode) {
- ASSERT_EQ(OK, native_window_set_scaling_mode(mANW.get(),
- NATIVE_WINDOW_SCALING_MODE_SCALE_CROP));
-
- // The producer image size
- ASSERT_EQ(OK, native_window_set_buffers_dimensions(mANW.get(), 512, 512));
-
- // The consumer image size (16 x 9) ratio
- mST->setDefaultBufferSize(1280, 720);
-
- native_window_api_connect(mANW.get(), NATIVE_WINDOW_API_CPU);
-
- ANativeWindowBuffer *anb;
-
- // The crop is in the shape of (320, 180) === 16 x 9
- android_native_rect_t standard = {10, 20, 330, 200};
- ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &standard));
- EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
- EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
- mFW->waitForFrame();
- EXPECT_EQ(OK, mST->updateTexImage());
- Rect r = mST->getCurrentCrop();
- // crop should be the same as crop (same aspect ratio)
- assertRectEq(Rect(10, 20, 330, 200), r);
-
- // make this wider then desired aspect 239 x 100 (2.39:1)
- android_native_rect_t wide = {20, 30, 259, 130};
- ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &wide));
- EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
- EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
- mFW->waitForFrame();
- EXPECT_EQ(OK, mST->updateTexImage());
- r = mST->getCurrentCrop();
- // crop should be the same height, but have cropped left and right borders
- // offset is 30.6 px L+, R-
- assertRectEq(Rect(51, 30, 228, 130), r);
-
- // This image is taller then desired aspect 400 x 300 (4:3)
- android_native_rect_t narrow = {0, 0, 400, 300};
- ASSERT_EQ(OK, native_window_set_crop(mANW.get(), &narrow));
- EXPECT_EQ (OK, native_window_dequeue_buffer_and_wait(mANW.get(), &anb));
- EXPECT_EQ(OK, mANW->queueBuffer(mANW.get(), anb, -1));
- mFW->waitForFrame();
- EXPECT_EQ(OK, mST->updateTexImage());
- r = mST->getCurrentCrop();
- // crop should be the same width, but have cropped top and bottom borders
- // offset is 37.5 px
- assertRectEq(Rect(0, 37, 400, 262), r);
-
- native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
-}
-
-TEST_F(SurfaceTextureGLTest, AbandonUnblocksDequeueBuffer) {
- class ProducerThread : public Thread {
- public:
- ProducerThread(const sp<ANativeWindow>& anw):
- mANW(anw),
- mDequeueError(NO_ERROR) {
- }
-
- virtual ~ProducerThread() {
- }
-
- virtual bool threadLoop() {
- Mutex::Autolock lock(mMutex);
- ANativeWindowBuffer* anb;
-
- // Frame 1
- if (native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb) != NO_ERROR) {
- return false;
- }
- if (anb == NULL) {
- return false;
- }
- if (mANW->queueBuffer(mANW.get(), anb, -1)
- != NO_ERROR) {
- return false;
- }
-
- // Frame 2
- if (native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb) != NO_ERROR) {
- return false;
- }
- if (anb == NULL) {
- return false;
- }
- if (mANW->queueBuffer(mANW.get(), anb, -1)
- != NO_ERROR) {
- return false;
- }
-
- // Frame 3 - error expected
- mDequeueError = native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb);
- return false;
- }
-
- status_t getDequeueError() {
- Mutex::Autolock lock(mMutex);
- return mDequeueError;
- }
-
- private:
- sp<ANativeWindow> mANW;
- status_t mDequeueError;
- Mutex mMutex;
- };
-
- ASSERT_EQ(OK, mST->setDefaultMaxBufferCount(2));
-
- sp<Thread> pt(new ProducerThread(mANW));
- pt->run();
-
- mFW->waitForFrame();
- mFW->waitForFrame();
-
- // Sleep for 100ms to allow the producer thread's dequeueBuffer call to
- // block waiting for a buffer to become available.
- usleep(100000);
-
- mST->abandon();
-
- pt->requestExitAndWait();
- ASSERT_EQ(NO_INIT,
- reinterpret_cast<ProducerThread*>(pt.get())->getDequeueError());
-}
-
-TEST_F(SurfaceTextureGLTest, InvalidWidthOrHeightFails) {
- int texHeight = 16;
- ANativeWindowBuffer* anb;
-
- GLint maxTextureSize;
- glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
-
- // make sure it works with small textures
- mST->setDefaultBufferSize(16, texHeight);
- EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb));
- EXPECT_EQ(16, anb->width);
- EXPECT_EQ(texHeight, anb->height);
- EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1));
- EXPECT_EQ(NO_ERROR, mST->updateTexImage());
-
- // make sure it works with GL_MAX_TEXTURE_SIZE
- mST->setDefaultBufferSize(maxTextureSize, texHeight);
- EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb));
- EXPECT_EQ(maxTextureSize, anb->width);
- EXPECT_EQ(texHeight, anb->height);
- EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1));
- EXPECT_EQ(NO_ERROR, mST->updateTexImage());
-
- // make sure it fails with GL_MAX_TEXTURE_SIZE+1
- mST->setDefaultBufferSize(maxTextureSize+1, texHeight);
- EXPECT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb));
- EXPECT_EQ(maxTextureSize+1, anb->width);
- EXPECT_EQ(texHeight, anb->height);
- EXPECT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), anb, -1));
- ASSERT_NE(NO_ERROR, mST->updateTexImage());
-}
-
-/*
- * This test fixture is for testing GL -> GL texture streaming. It creates an
- * EGLSurface and an EGLContext for the image producer to use.
- */
-class SurfaceTextureGLToGLTest : public SurfaceTextureGLTest {
-protected:
- SurfaceTextureGLToGLTest():
- mProducerEglSurface(EGL_NO_SURFACE),
- mProducerEglContext(EGL_NO_CONTEXT) {
- }
-
- virtual void SetUp() {
- SurfaceTextureGLTest::SetUp();
-
- mProducerEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
- mANW.get(), NULL);
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_SURFACE, mProducerEglSurface);
-
- mProducerEglContext = eglCreateContext(mEglDisplay, mGlConfig,
- EGL_NO_CONTEXT, getContextAttribs());
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_CONTEXT, mProducerEglContext);
- }
-
- virtual void TearDown() {
- if (mProducerEglContext != EGL_NO_CONTEXT) {
- eglDestroyContext(mEglDisplay, mProducerEglContext);
- }
- if (mProducerEglSurface != EGL_NO_SURFACE) {
- eglDestroySurface(mEglDisplay, mProducerEglSurface);
- }
- SurfaceTextureGLTest::TearDown();
- }
-
- EGLSurface mProducerEglSurface;
- EGLContext mProducerEglContext;
-};
-
-TEST_F(SurfaceTextureGLToGLTest, TransformHintGetsRespected) {
- const uint32_t texWidth = 32;
- const uint32_t texHeight = 64;
-
- mST->setDefaultBufferSize(texWidth, texHeight);
- mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90);
-
- // This test requires 3 buffers to avoid deadlock because we're
- // both producer and consumer, and only using one thread.
- mST->setDefaultMaxBufferCount(3);
-
- // Do the producer side of things
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // Start a buffer with our chosen size and transform hint moving
- // through the system.
- glClear(GL_COLOR_BUFFER_BIT); // give the driver something to do
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
- mST->updateTexImage(); // consume it
- // Swap again.
- glClear(GL_COLOR_BUFFER_BIT);
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
- mST->updateTexImage();
-
- // The current buffer should either show the effects of the transform
- // hint (in the form of an inverse transform), or show that the
- // transform hint has been ignored.
- sp<GraphicBuffer> buf = mST->getCurrentBuffer();
- if (mST->getCurrentTransform() == NATIVE_WINDOW_TRANSFORM_ROT_270) {
- ASSERT_EQ(texWidth, buf->getHeight());
- ASSERT_EQ(texHeight, buf->getWidth());
- } else {
- ASSERT_EQ(texWidth, buf->getWidth());
- ASSERT_EQ(texHeight, buf->getHeight());
- }
-
- // Reset the transform hint and confirm that it takes.
- mST->setTransformHint(0);
- glClear(GL_COLOR_BUFFER_BIT);
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
- mST->updateTexImage();
- glClear(GL_COLOR_BUFFER_BIT);
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
- mST->updateTexImage();
-
- buf = mST->getCurrentBuffer();
- ASSERT_EQ((uint32_t) 0, mST->getCurrentTransform());
- ASSERT_EQ(texWidth, buf->getWidth());
- ASSERT_EQ(texHeight, buf->getHeight());
-}
-
-TEST_F(SurfaceTextureGLToGLTest, TexturingFromGLFilledRGBABufferPow2) {
- const int texWidth = 64;
- const int texHeight = 64;
-
- mST->setDefaultBufferSize(texWidth, texHeight);
-
- // This test requires 3 buffers to complete run on a single thread.
- mST->setDefaultMaxBufferCount(3);
-
- // Do the producer side of things
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // This is needed to ensure we pick up a buffer of the correct size.
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
-
- glClearColor(0.6, 0.6, 0.6, 0.6);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glEnable(GL_SCISSOR_TEST);
- glScissor(4, 4, 4, 4);
- glClearColor(1.0, 0.0, 0.0, 1.0);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glScissor(24, 48, 4, 4);
- glClearColor(0.0, 1.0, 0.0, 1.0);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glScissor(37, 17, 4, 4);
- glClearColor(0.0, 0.0, 1.0, 1.0);
- glClear(GL_COLOR_BUFFER_BIT);
-
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
-
- // Do the consumer side of things
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- glDisable(GL_SCISSOR_TEST);
-
- // Skip the first frame, which was empty
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glViewport(0, 0, texWidth, texHeight);
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153));
-
- EXPECT_TRUE(checkPixel( 4, 7, 255, 0, 0, 255));
- EXPECT_TRUE(checkPixel(25, 51, 0, 255, 0, 255));
- EXPECT_TRUE(checkPixel(40, 19, 0, 0, 255, 255));
- EXPECT_TRUE(checkPixel(29, 51, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel( 5, 32, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(13, 8, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(46, 3, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(30, 33, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel( 6, 52, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(55, 33, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(16, 29, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel( 1, 30, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(41, 37, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(46, 29, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(15, 25, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel( 3, 52, 153, 153, 153, 153));
-}
-
-TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceUnrefsBuffers) {
- sp<GraphicBuffer> buffers[2];
-
- // This test requires async mode to run on a single thread.
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- EXPECT_TRUE(eglSwapInterval(mEglDisplay, 0));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- for (int i = 0; i < 2; i++) {
- // Produce a frame
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- glClear(GL_COLOR_BUFFER_BIT);
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
-
- // Consume a frame
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- mFW->waitForFrame();
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- buffers[i] = mST->getCurrentBuffer();
- }
-
- // Destroy the GL texture object to release its ref on buffers[2].
- GLuint texID = TEX_ID;
- glDeleteTextures(1, &texID);
-
- // Destroy the EGLSurface
- EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- mProducerEglSurface = EGL_NO_SURFACE;
-
- // This test should have the only reference to buffer 0.
- EXPECT_EQ(1, buffers[0]->getStrongCount());
-
- // The GLConsumer should hold a single reference to buffer 1 in its
- // mCurrentBuffer member. All of the references in the slots should have
- // been released.
- EXPECT_EQ(2, buffers[1]->getStrongCount());
-}
-
-TEST_F(SurfaceTextureGLToGLTest, EglDestroySurfaceAfterAbandonUnrefsBuffers) {
- sp<GraphicBuffer> buffers[3];
-
- // This test requires async mode to run on a single thread.
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- EXPECT_TRUE(eglSwapInterval(mEglDisplay, 0));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- for (int i = 0; i < 3; i++) {
- // Produce a frame
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- glClear(GL_COLOR_BUFFER_BIT);
- EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // Consume a frame
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- mFW->waitForFrame();
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- buffers[i] = mST->getCurrentBuffer();
- }
-
- // Abandon the GLConsumer, releasing the ref that the GLConsumer has
- // on buffers[2].
- mST->abandon();
-
- // Destroy the GL texture object to release its ref on buffers[2].
- GLuint texID = TEX_ID;
- glDeleteTextures(1, &texID);
-
- // Destroy the EGLSurface.
- EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- mProducerEglSurface = EGL_NO_SURFACE;
-
- EXPECT_EQ(1, buffers[0]->getStrongCount());
- EXPECT_EQ(1, buffers[1]->getStrongCount());
-
- // Depending on how lazily the GL driver dequeues buffers, we may end up
- // with either two or three total buffers. If there are three, make sure
- // the last one was properly down-ref'd.
- if (buffers[2] != buffers[0]) {
- EXPECT_EQ(1, buffers[2]->getStrongCount());
- }
-}
-
-TEST_F(SurfaceTextureGLToGLTest, EglMakeCurrentBeforeConsumerDeathUnrefsBuffers) {
- sp<GraphicBuffer> buffer;
-
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
-
- // Produce a frame
- glClear(GL_COLOR_BUFFER_BIT);
- EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // Destroy the EGLSurface.
- EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- mProducerEglSurface = EGL_NO_SURFACE;
- mSTC.clear();
- mANW.clear();
- mTextureRenderer.clear();
-
- // Consume a frame
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- buffer = mST->getCurrentBuffer();
-
- // Destroy the GL texture object to release its ref
- GLuint texID = TEX_ID;
- glDeleteTextures(1, &texID);
-
- // make un-current, all references to buffer should be gone
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT));
-
- // Destroy consumer
- mST.clear();
-
- EXPECT_EQ(1, buffer->getStrongCount());
-}
-
-TEST_F(SurfaceTextureGLToGLTest, EglMakeCurrentAfterConsumerDeathUnrefsBuffers) {
- sp<GraphicBuffer> buffer;
-
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
-
- // Produce a frame
- glClear(GL_COLOR_BUFFER_BIT);
- EXPECT_TRUE(eglSwapBuffers(mEglDisplay, mProducerEglSurface));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // Destroy the EGLSurface.
- EXPECT_TRUE(eglDestroySurface(mEglDisplay, mProducerEglSurface));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- mProducerEglSurface = EGL_NO_SURFACE;
- mSTC.clear();
- mANW.clear();
- mTextureRenderer.clear();
-
- // Consume a frame
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- buffer = mST->getCurrentBuffer();
-
- // Destroy the GL texture object to release its ref
- GLuint texID = TEX_ID;
- glDeleteTextures(1, &texID);
-
- // Destroy consumer
- mST.clear();
-
- // make un-current, all references to buffer should be gone
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT));
-
- EXPECT_EQ(1, buffer->getStrongCount());
-}
-
-TEST_F(SurfaceTextureGLToGLTest, TexturingFromUserSizedGLFilledBuffer) {
- enum { texWidth = 64 };
- enum { texHeight = 64 };
-
- // This test requires 3 buffers to complete run on a single thread.
- mST->setDefaultMaxBufferCount(3);
-
- // Set the user buffer size.
- native_window_set_buffers_user_dimensions(mANW.get(), texWidth, texHeight);
-
- // Do the producer side of things
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // This is needed to ensure we pick up a buffer of the correct size.
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
-
- glClearColor(0.6, 0.6, 0.6, 0.6);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glEnable(GL_SCISSOR_TEST);
- glScissor(4, 4, 1, 1);
- glClearColor(1.0, 0.0, 0.0, 1.0);
- glClear(GL_COLOR_BUFFER_BIT);
-
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
-
- // Do the consumer side of things
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- glDisable(GL_SCISSOR_TEST);
-
- // Skip the first frame, which was empty
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glViewport(0, 0, texWidth, texHeight);
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(63, 63, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel( 0, 63, 153, 153, 153, 153));
-
- EXPECT_TRUE(checkPixel( 4, 4, 255, 0, 0, 255));
- EXPECT_TRUE(checkPixel( 5, 5, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel( 3, 3, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(45, 52, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(12, 36, 153, 153, 153, 153));
-}
-
-TEST_F(SurfaceTextureGLToGLTest, TexturingFromPreRotatedUserSizedGLFilledBuffer) {
- enum { texWidth = 64 };
- enum { texHeight = 16 };
-
- // This test requires 3 buffers to complete run on a single thread.
- mST->setDefaultMaxBufferCount(3);
-
- // Set the transform hint.
- mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90);
-
- // Set the user buffer size.
- native_window_set_buffers_user_dimensions(mANW.get(), texWidth, texHeight);
-
- // Do the producer side of things
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // This is needed to ensure we pick up a buffer of the correct size and the
- // new rotation hint.
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
-
- glClearColor(0.6, 0.6, 0.6, 0.6);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glEnable(GL_SCISSOR_TEST);
- glScissor(24, 4, 1, 1);
- glClearColor(1.0, 0.0, 0.0, 1.0);
- glClear(GL_COLOR_BUFFER_BIT);
-
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
-
- // Do the consumer side of things
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- glDisable(GL_SCISSOR_TEST);
-
- // Skip the first frame, which was empty
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glViewport(0, 0, texWidth, texHeight);
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(63, 15, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel( 0, 15, 153, 153, 153, 153));
-
- EXPECT_TRUE(checkPixel(24, 4, 255, 0, 0, 255));
- EXPECT_TRUE(checkPixel(25, 5, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(23, 3, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(45, 13, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(12, 8, 153, 153, 153, 153));
-}
-
-TEST_F(SurfaceTextureGLToGLTest, TexturingFromPreRotatedGLFilledBuffer) {
- enum { texWidth = 64 };
- enum { texHeight = 16 };
-
- // This test requires 3 buffers to complete run on a single thread.
- mST->setDefaultMaxBufferCount(3);
-
- // Set the transform hint.
- mST->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_90);
-
- // Set the default buffer size.
- mST->setDefaultBufferSize(texWidth, texHeight);
-
- // Do the producer side of things
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // This is needed to ensure we pick up a buffer of the correct size and the
- // new rotation hint.
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
-
- glClearColor(0.6, 0.6, 0.6, 0.6);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glEnable(GL_SCISSOR_TEST);
- glScissor(24, 4, 1, 1);
- glClearColor(1.0, 0.0, 0.0, 1.0);
- glClear(GL_COLOR_BUFFER_BIT);
-
- eglSwapBuffers(mEglDisplay, mProducerEglSurface);
-
- // Do the consumer side of things
- EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- glDisable(GL_SCISSOR_TEST);
-
- // Skip the first frame, which was empty
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
-
- glViewport(0, 0, texWidth, texHeight);
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 0, 0, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(63, 0, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(63, 15, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel( 0, 15, 153, 153, 153, 153));
-
- EXPECT_TRUE(checkPixel(24, 4, 255, 0, 0, 255));
- EXPECT_TRUE(checkPixel(25, 5, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(23, 3, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(45, 13, 153, 153, 153, 153));
- EXPECT_TRUE(checkPixel(12, 8, 153, 153, 153, 153));
-}
-
-/*
- * This test fixture is for testing GL -> GL texture streaming from one thread
- * to another. It contains functionality to create a producer thread that will
- * perform GL rendering to an ANativeWindow that feeds frames to a
- * GLConsumer. Additionally it supports interlocking the producer and
- * consumer threads so that a specific sequence of calls can be
- * deterministically created by the test.
- *
- * The intended usage is as follows:
- *
- * TEST_F(...) {
- * class PT : public ProducerThread {
- * virtual void render() {
- * ...
- * swapBuffers();
- * }
- * };
- *
- * runProducerThread(new PT());
- *
- * // The order of these calls will vary from test to test and may include
- * // multiple frames and additional operations (e.g. GL rendering from the
- * // texture).
- * fc->waitForFrame();
- * mST->updateTexImage();
- * fc->finishFrame();
- * }
- *
- */
-class SurfaceTextureGLThreadToGLTest : public SurfaceTextureGLToGLTest {
-protected:
-
- // ProducerThread is an abstract base class to simplify the creation of
- // OpenGL ES frame producer threads.
- class ProducerThread : public Thread {
- public:
- virtual ~ProducerThread() {
- }
-
- void setEglObjects(EGLDisplay producerEglDisplay,
- EGLSurface producerEglSurface,
- EGLContext producerEglContext) {
- mProducerEglDisplay = producerEglDisplay;
- mProducerEglSurface = producerEglSurface;
- mProducerEglContext = producerEglContext;
- }
-
- virtual bool threadLoop() {
- eglMakeCurrent(mProducerEglDisplay, mProducerEglSurface,
- mProducerEglSurface, mProducerEglContext);
- render();
- eglMakeCurrent(mProducerEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
- EGL_NO_CONTEXT);
- return false;
- }
-
- protected:
- virtual void render() = 0;
-
- void swapBuffers() {
- eglSwapBuffers(mProducerEglDisplay, mProducerEglSurface);
- }
-
- EGLDisplay mProducerEglDisplay;
- EGLSurface mProducerEglSurface;
- EGLContext mProducerEglContext;
- };
-
- // FrameCondition is a utility class for interlocking between the producer
- // and consumer threads. The FrameCondition object should be created and
- // destroyed in the consumer thread only. The consumer thread should set
- // the FrameCondition as the FrameAvailableListener of the GLConsumer,
- // and should call both waitForFrame and finishFrame once for each expected
- // frame.
- //
- // This interlocking relies on the fact that onFrameAvailable gets called
- // synchronously from GLConsumer::queueBuffer.
- class FrameCondition : public GLConsumer::FrameAvailableListener {
- public:
- FrameCondition():
- mFrameAvailable(false),
- mFrameFinished(false) {
- }
-
- // waitForFrame waits for the next frame to arrive. This should be
- // called from the consumer thread once for every frame expected by the
- // test.
- void waitForFrame() {
- Mutex::Autolock lock(mMutex);
- ALOGV("+waitForFrame");
- while (!mFrameAvailable) {
- mFrameAvailableCondition.wait(mMutex);
- }
- mFrameAvailable = false;
- ALOGV("-waitForFrame");
- }
-
- // Allow the producer to return from its swapBuffers call and continue
- // on to produce the next frame. This should be called by the consumer
- // thread once for every frame expected by the test.
- void finishFrame() {
- Mutex::Autolock lock(mMutex);
- ALOGV("+finishFrame");
- mFrameFinished = true;
- mFrameFinishCondition.signal();
- ALOGV("-finishFrame");
- }
-
- // This should be called by GLConsumer on the producer thread.
- virtual void onFrameAvailable() {
- Mutex::Autolock lock(mMutex);
- ALOGV("+onFrameAvailable");
- mFrameAvailable = true;
- mFrameAvailableCondition.signal();
- while (!mFrameFinished) {
- mFrameFinishCondition.wait(mMutex);
- }
- mFrameFinished = false;
- ALOGV("-onFrameAvailable");
- }
-
- protected:
- bool mFrameAvailable;
- bool mFrameFinished;
-
- Mutex mMutex;
- Condition mFrameAvailableCondition;
- Condition mFrameFinishCondition;
- };
-
- virtual void SetUp() {
- SurfaceTextureGLToGLTest::SetUp();
- mFC = new FrameCondition();
- mST->setFrameAvailableListener(mFC);
- }
-
- virtual void TearDown() {
- if (mProducerThread != NULL) {
- mProducerThread->requestExitAndWait();
- }
- mProducerThread.clear();
- mFC.clear();
- SurfaceTextureGLToGLTest::TearDown();
- }
-
- void runProducerThread(const sp<ProducerThread> producerThread) {
- ASSERT_TRUE(mProducerThread == NULL);
- mProducerThread = producerThread;
- producerThread->setEglObjects(mEglDisplay, mProducerEglSurface,
- mProducerEglContext);
- producerThread->run();
- }
-
- sp<ProducerThread> mProducerThread;
- sp<FrameCondition> mFC;
-};
-
-TEST_F(SurfaceTextureGLThreadToGLTest,
- UpdateTexImageBeforeFrameFinishedCompletes) {
- class PT : public ProducerThread {
- virtual void render() {
- glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- swapBuffers();
- }
- };
-
- runProducerThread(new PT());
-
- mFC->waitForFrame();
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- mFC->finishFrame();
-
- // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
-}
-
-TEST_F(SurfaceTextureGLThreadToGLTest,
- UpdateTexImageAfterFrameFinishedCompletes) {
- class PT : public ProducerThread {
- virtual void render() {
- glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- swapBuffers();
- }
- };
-
- runProducerThread(new PT());
-
- mFC->waitForFrame();
- mFC->finishFrame();
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
-}
-
-TEST_F(SurfaceTextureGLThreadToGLTest,
- RepeatedUpdateTexImageBeforeFrameFinishedCompletes) {
- enum { NUM_ITERATIONS = 1024 };
-
- class PT : public ProducerThread {
- virtual void render() {
- for (int i = 0; i < NUM_ITERATIONS; i++) {
- glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- ALOGV("+swapBuffers");
- swapBuffers();
- ALOGV("-swapBuffers");
- }
- }
- };
-
- runProducerThread(new PT());
-
- for (int i = 0; i < NUM_ITERATIONS; i++) {
- mFC->waitForFrame();
- ALOGV("+updateTexImage");
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- ALOGV("-updateTexImage");
- mFC->finishFrame();
-
- // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
- }
-}
-
-TEST_F(SurfaceTextureGLThreadToGLTest,
- RepeatedUpdateTexImageAfterFrameFinishedCompletes) {
- enum { NUM_ITERATIONS = 1024 };
-
- class PT : public ProducerThread {
- virtual void render() {
- for (int i = 0; i < NUM_ITERATIONS; i++) {
- glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- ALOGV("+swapBuffers");
- swapBuffers();
- ALOGV("-swapBuffers");
- }
- }
- };
-
- runProducerThread(new PT());
-
- for (int i = 0; i < NUM_ITERATIONS; i++) {
- mFC->waitForFrame();
- mFC->finishFrame();
- ALOGV("+updateTexImage");
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- ALOGV("-updateTexImage");
-
- // TODO: Add frame verification once RGB TEX_EXTERNAL_OES is supported!
- }
-}
-
-// XXX: This test is disabled because it is currently hanging on some devices.
-TEST_F(SurfaceTextureGLThreadToGLTest,
- DISABLED_RepeatedSwapBuffersWhileDequeueStalledCompletes) {
- enum { NUM_ITERATIONS = 64 };
-
- class PT : public ProducerThread {
- virtual void render() {
- for (int i = 0; i < NUM_ITERATIONS; i++) {
- glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- ALOGV("+swapBuffers");
- swapBuffers();
- ALOGV("-swapBuffers");
- }
- }
- };
-
- ASSERT_EQ(OK, mST->setDefaultMaxBufferCount(2));
-
- runProducerThread(new PT());
-
- // Allow three frames to be rendered and queued before starting the
- // rendering in this thread. For the latter two frames we don't call
- // updateTexImage so the next dequeue from the producer thread will block
- // waiting for a frame to become available.
- mFC->waitForFrame();
- mFC->finishFrame();
-
- // We must call updateTexImage to consume the first frame so that the
- // SurfaceTexture is able to reduce the buffer count to 2. This is because
- // the GL driver may dequeue a buffer when the EGLSurface is created, and
- // that happens before we call setDefaultMaxBufferCount. It's possible that the
- // driver does not dequeue a buffer at EGLSurface creation time, so we
- // cannot rely on this to cause the second dequeueBuffer call to block.
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- mFC->waitForFrame();
- mFC->finishFrame();
- mFC->waitForFrame();
- mFC->finishFrame();
-
- // Sleep for 100ms to allow the producer thread's dequeueBuffer call to
- // block waiting for a buffer to become available.
- usleep(100000);
-
- // Render and present a number of images. This thread should not be blocked
- // by the fact that the producer thread is blocking in dequeue.
- for (int i = 0; i < NUM_ITERATIONS; i++) {
- glClear(GL_COLOR_BUFFER_BIT);
- eglSwapBuffers(mEglDisplay, mEglSurface);
- }
-
- // Consume the two pending buffers to unblock the producer thread.
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- // Consume the remaining buffers from the producer thread.
- for (int i = 0; i < NUM_ITERATIONS-3; i++) {
- mFC->waitForFrame();
- mFC->finishFrame();
- ALOGV("+updateTexImage");
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
- ALOGV("-updateTexImage");
- }
-}
-
-class SurfaceTextureFBOTest : public SurfaceTextureGLTest {
-protected:
-
- virtual void SetUp() {
- SurfaceTextureGLTest::SetUp();
-
- glGenFramebuffers(1, &mFbo);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-
- glGenTextures(1, &mFboTex);
- glBindTexture(GL_TEXTURE_2D, mFboTex);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getSurfaceWidth(),
- getSurfaceHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
- glBindTexture(GL_TEXTURE_2D, 0);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-
- glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_TEXTURE_2D, mFboTex, 0);
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- }
-
- virtual void TearDown() {
- SurfaceTextureGLTest::TearDown();
-
- glDeleteTextures(1, &mFboTex);
- glDeleteFramebuffers(1, &mFbo);
- }
-
- GLuint mFbo;
- GLuint mFboTex;
-};
-
-// This test is intended to verify that proper synchronization is done when
-// rendering into an FBO.
-TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) {
- const int texWidth = 64;
- const int texHeight = 64;
-
- ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
- texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888));
- ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
- GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));
-
- android_native_buffer_t* anb;
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb));
- ASSERT_TRUE(anb != NULL);
-
- sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));
-
- // Fill the buffer with green
- uint8_t* img = NULL;
- buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN, (void**)(&img));
- fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 0, 255,
- 0, 255);
- buf->unlock();
- ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(), buf->getNativeBuffer(),
- -1));
-
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
- drawTexture();
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
-
- for (int i = 0; i < 4; i++) {
- SCOPED_TRACE(String8::format("frame %d", i).string());
-
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
- &anb));
- ASSERT_TRUE(anb != NULL);
-
- buf = new GraphicBuffer(anb, false);
-
- // Fill the buffer with red
- ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN,
- (void**)(&img)));
- fillRGBA8BufferSolid(img, texWidth, texHeight, buf->getStride(), 255, 0,
- 0, 255);
- ASSERT_EQ(NO_ERROR, buf->unlock());
- ASSERT_EQ(NO_ERROR, mANW->queueBuffer(mANW.get(),
- buf->getNativeBuffer(), -1));
-
- ASSERT_EQ(NO_ERROR, mST->updateTexImage());
-
- drawTexture();
-
- EXPECT_TRUE(checkPixel( 24, 39, 255, 0, 0, 255));
- }
-
- glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
-
- EXPECT_TRUE(checkPixel( 24, 39, 0, 255, 0, 255));
-}
-
-class SurfaceTextureMultiContextGLTest : public SurfaceTextureGLTest {
-protected:
- enum { SECOND_TEX_ID = 123 };
- enum { THIRD_TEX_ID = 456 };
-
- SurfaceTextureMultiContextGLTest():
- mSecondEglContext(EGL_NO_CONTEXT) {
- }
-
- virtual void SetUp() {
- SurfaceTextureGLTest::SetUp();
-
- // Set up the secondary context and texture renderer.
- mSecondEglContext = eglCreateContext(mEglDisplay, mGlConfig,
- EGL_NO_CONTEXT, getContextAttribs());
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_CONTEXT, mSecondEglContext);
-
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mSecondEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- mSecondTextureRenderer = new TextureRenderer(SECOND_TEX_ID, mST);
- ASSERT_NO_FATAL_FAILURE(mSecondTextureRenderer->SetUp());
-
- // Set up the tertiary context and texture renderer.
- mThirdEglContext = eglCreateContext(mEglDisplay, mGlConfig,
- EGL_NO_CONTEXT, getContextAttribs());
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_NE(EGL_NO_CONTEXT, mThirdEglContext);
-
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mThirdEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- mThirdTextureRenderer = new TextureRenderer(THIRD_TEX_ID, mST);
- ASSERT_NO_FATAL_FAILURE(mThirdTextureRenderer->SetUp());
-
- // Switch back to the primary context to start the tests.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mEglContext));
- }
-
- virtual void TearDown() {
- if (mThirdEglContext != EGL_NO_CONTEXT) {
- eglDestroyContext(mEglDisplay, mThirdEglContext);
- }
- if (mSecondEglContext != EGL_NO_CONTEXT) {
- eglDestroyContext(mEglDisplay, mSecondEglContext);
- }
- SurfaceTextureGLTest::TearDown();
- }
-
- EGLContext mSecondEglContext;
- sp<TextureRenderer> mSecondTextureRenderer;
-
- EGLContext mThirdEglContext;
- sp<TextureRenderer> mThirdTextureRenderer;
-};
-
-TEST_F(SurfaceTextureMultiContextGLTest, UpdateFromMultipleContextsFails) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Attempt to latch the texture on the secondary context.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mSecondEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
- ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage());
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextSucceeds) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Detach from the primary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Check that the GL texture was deleted.
- EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest,
- DetachFromContextSucceedsAfterProducerDisconnect) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Detach from the primary context.
- native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Check that the GL texture was deleted.
- EXPECT_EQ(GL_FALSE, glIsTexture(TEX_ID));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenAbandoned) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Attempt to detach from the primary context.
- mST->abandon();
- ASSERT_EQ(NO_INIT, mST->detachFromContext());
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWhenDetached) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Detach from the primary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attempt to detach from the primary context again.
- ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext());
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoDisplay) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Make there be no current display.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
- EGL_NO_CONTEXT));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // Attempt to detach from the primary context.
- ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext());
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, DetachFromContextFailsWithNoContext) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Make current context be incorrect.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mSecondEglContext));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // Attempt to detach from the primary context.
- ASSERT_EQ(INVALID_OPERATION, mST->detachFromContext());
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, UpdateTexImageFailsWhenDetached) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Detach from the primary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attempt to latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(INVALID_OPERATION, mST->updateTexImage());
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceeds) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Detach from the primary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attach to the secondary context.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mSecondEglContext));
- ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
-
- // Verify that the texture object was created and bound.
- GLint texBinding = -1;
- glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
- EXPECT_EQ(SECOND_TEX_ID, texBinding);
-
- // Try to use the texture from the secondary context.
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
- glViewport(0, 0, 1, 1);
- mSecondTextureRenderer->drawTexture();
- ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest,
- AttachToContextSucceedsAfterProducerDisconnect) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Detach from the primary context.
- native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attach to the secondary context.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mSecondEglContext));
- ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
-
- // Verify that the texture object was created and bound.
- GLint texBinding = -1;
- glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
- EXPECT_EQ(SECOND_TEX_ID, texBinding);
-
- // Try to use the texture from the secondary context.
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
- glViewport(0, 0, 1, 1);
- mSecondTextureRenderer->drawTexture();
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest,
- AttachToContextSucceedsBeforeUpdateTexImage) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Detach from the primary context.
- native_window_api_disconnect(mANW.get(), NATIVE_WINDOW_API_CPU);
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attach to the secondary context.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mSecondEglContext));
- ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
-
- // Verify that the texture object was created and bound.
- GLint texBinding = -1;
- glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
- EXPECT_EQ(SECOND_TEX_ID, texBinding);
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Try to use the texture from the secondary context.
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
- glViewport(0, 0, 1, 1);
- mSecondTextureRenderer->drawTexture();
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAbandoned) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Detach from the primary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attempt to attach to the secondary context.
- mST->abandon();
-
- // Attempt to attach to the primary context.
- ASSERT_EQ(NO_INIT, mST->attachToContext(SECOND_TEX_ID));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWhenAttached) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Attempt to attach to the primary context.
- ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest,
- AttachToContextFailsWhenAttachedBeforeUpdateTexImage) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Attempt to attach to the primary context.
- ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextFailsWithNoDisplay) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Detach from the primary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Make there be no current display.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
- EGL_NO_CONTEXT));
- ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- // Attempt to attach with no context current.
- ASSERT_EQ(INVALID_OPERATION, mST->attachToContext(SECOND_TEX_ID));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest, AttachToContextSucceedsTwice) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Latch the texture contents on the primary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Detach from the primary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attach to the secondary context.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mSecondEglContext));
- ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
-
- // Detach from the secondary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attach to the tertiary context.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mThirdEglContext));
- ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID));
-
- // Verify that the texture object was created and bound.
- GLint texBinding = -1;
- glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
- EXPECT_EQ(THIRD_TEX_ID, texBinding);
-
- // Try to use the texture from the tertiary context.
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
- glViewport(0, 0, 1, 1);
- mThirdTextureRenderer->drawTexture();
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest,
- AttachToContextSucceedsTwiceBeforeUpdateTexImage) {
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Detach from the primary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attach to the secondary context.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mSecondEglContext));
- ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
-
- // Detach from the secondary context.
- ASSERT_EQ(OK, mST->detachFromContext());
-
- // Attach to the tertiary context.
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mThirdEglContext));
- ASSERT_EQ(OK, mST->attachToContext(THIRD_TEX_ID));
-
- // Verify that the texture object was created and bound.
- GLint texBinding = -1;
- glGetIntegerv(GL_TEXTURE_BINDING_EXTERNAL_OES, &texBinding);
- EXPECT_EQ(THIRD_TEX_ID, texBinding);
-
- // Latch the texture contents on the tertiary context.
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // Try to use the texture from the tertiary context.
- glClearColor(0.2, 0.2, 0.2, 0.2);
- glClear(GL_COLOR_BUFFER_BIT);
- glViewport(0, 0, 1, 1);
- mThirdTextureRenderer->drawTexture();
- ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
- ASSERT_TRUE(checkPixel( 0, 0, 35, 35, 35, 35));
-}
-
-TEST_F(SurfaceTextureMultiContextGLTest,
- UpdateTexImageSucceedsForBufferConsumedBeforeDetach) {
- ASSERT_EQ(NO_ERROR, mST->setDefaultMaxBufferCount(2));
-
- // produce two frames and consume them both on the primary context
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-
- // produce one more frame
- ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));
-
- // Detach from the primary context and attach to the secondary context
- ASSERT_EQ(OK, mST->detachFromContext());
- ASSERT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
- mSecondEglContext));
- ASSERT_EQ(OK, mST->attachToContext(SECOND_TEX_ID));
-
- // Consume final frame on secondary context
- mFW->waitForFrame();
- ASSERT_EQ(OK, mST->updateTexImage());
-}
-
-} // namespace android
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index e0272ba..bf87fad 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -88,12 +88,14 @@ TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersSucceed) {
sp<ANativeWindow> anw(mSurface);
// Verify the screenshot works with no protected buffers.
- sp<BufferQueue> bq = new BufferQueue();
- sp<CpuConsumer> consumer = new CpuConsumer(bq, 1);
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1);
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
sp<IBinder> display(sf->getBuiltInDisplay(ISurfaceComposer::eDisplayIdMain));
- ASSERT_EQ(NO_ERROR, sf->captureScreen(display, bq,
- 64, 64, 0, 0x7fffffff));
+ ASSERT_EQ(NO_ERROR, sf->captureScreen(display, producer,
+ 64, 64, 0, 0x7fffffff, false));
// Set the PROTECTED usage bit and verify that the screenshot fails. Note
// that we need to dequeue a buffer in order for it to actually get
@@ -121,8 +123,8 @@ TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersSucceed) {
&buf));
ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf, -1));
}
- ASSERT_EQ(NO_ERROR, sf->captureScreen(display, bq,
- 64, 64, 0, 0x7fffffff));
+ ASSERT_EQ(NO_ERROR, sf->captureScreen(display, producer,
+ 64, 64, 0, 0x7fffffff, false));
}
TEST_F(SurfaceTest, ConcreteTypeIsSurface) {
@@ -136,10 +138,12 @@ TEST_F(SurfaceTest, ConcreteTypeIsSurface) {
TEST_F(SurfaceTest, QueryConsumerUsage) {
const int TEST_USAGE_FLAGS =
GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER;
- sp<BufferQueue> bq = new BufferQueue();
- sp<BufferItemConsumer> c = new BufferItemConsumer(bq,
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ sp<BufferItemConsumer> c = new BufferItemConsumer(consumer,
TEST_USAGE_FLAGS);
- sp<Surface> s = new Surface(bq);
+ sp<Surface> s = new Surface(producer);
sp<ANativeWindow> anw(s);
diff --git a/libs/gui/tests/TextureRenderer.cpp b/libs/gui/tests/TextureRenderer.cpp
new file mode 100644
index 0000000..90951b3
--- /dev/null
+++ b/libs/gui/tests/TextureRenderer.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TextureRenderer.h"
+
+#include "GLTest.h"
+
+#include <gui/GLConsumer.h>
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+TextureRenderer::TextureRenderer(GLuint texName,
+ const sp<GLConsumer>& st) : mTexName(texName), mST(st) {
+}
+
+void TextureRenderer::SetUp() {
+ const char vsrc[] =
+ "attribute vec4 vPosition;\n"
+ "varying vec2 texCoords;\n"
+ "uniform mat4 texMatrix;\n"
+ "void main() {\n"
+ " vec2 vTexCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n"
+ " texCoords = (texMatrix * vec4(vTexCoords, 0.0, 1.0)).xy;\n"
+ " gl_Position = vPosition;\n"
+ "}\n";
+
+ const char fsrc[] =
+ "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES texSampler;\n"
+ "varying vec2 texCoords;\n"
+ "void main() {\n"
+ " gl_FragColor = texture2D(texSampler, texCoords);\n"
+ "}\n";
+
+ {
+ SCOPED_TRACE("creating shader program");
+ ASSERT_NO_FATAL_FAILURE(GLTest::createProgram(vsrc, fsrc, &mPgm));
+ }
+
+ mPositionHandle = glGetAttribLocation(mPgm, "vPosition");
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ ASSERT_NE(-1, mPositionHandle);
+ mTexSamplerHandle = glGetUniformLocation(mPgm, "texSampler");
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ ASSERT_NE(-1, mTexSamplerHandle);
+ mTexMatrixHandle = glGetUniformLocation(mPgm, "texMatrix");
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ ASSERT_NE(-1, mTexMatrixHandle);
+}
+
+// drawTexture draws the GLConsumer over the entire GL viewport.
+void TextureRenderer::drawTexture() {
+ static const GLfloat triangleVertices[] = {
+ -1.0f, 1.0f,
+ -1.0f, -1.0f,
+ 1.0f, -1.0f,
+ 1.0f, 1.0f,
+ };
+
+ glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0,
+ triangleVertices);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glEnableVertexAttribArray(mPositionHandle);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+ glUseProgram(mPgm);
+ glUniform1i(mTexSamplerHandle, 0);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+ // XXX: These calls are not needed for GL_TEXTURE_EXTERNAL_OES as
+ // they're setting the defautls for that target, but when hacking
+ // things to use GL_TEXTURE_2D they are needed to achieve the same
+ // behavior.
+ glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER,
+ GL_LINEAR);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER,
+ GL_LINEAR);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S,
+ GL_CLAMP_TO_EDGE);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+ glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T,
+ GL_CLAMP_TO_EDGE);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+
+ GLfloat texMatrix[16];
+ mST->getTransformMatrix(texMatrix);
+ glUniformMatrix4fv(mTexMatrixHandle, 1, GL_FALSE, texMatrix);
+
+ glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+ ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
+}
+
+} // namespace android
diff --git a/libs/gui/tests/TextureRenderer.h b/libs/gui/tests/TextureRenderer.h
new file mode 100644
index 0000000..37b2b47
--- /dev/null
+++ b/libs/gui/tests/TextureRenderer.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_TEXTURE_RENDERER_H
+#define ANDROID_TEXTURE_RENDERER_H
+
+#include <GLES/gl.h>
+
+#include <utils/RefBase.h>
+
+namespace android {
+
+class GLConsumer;
+
+class TextureRenderer : public RefBase {
+public:
+ TextureRenderer(GLuint texName, const sp<GLConsumer>& st);
+
+ void SetUp();
+ void drawTexture();
+
+private:
+ GLuint mTexName;
+ sp<GLConsumer> mST;
+ GLuint mPgm;
+ GLint mPositionHandle;
+ GLint mTexSamplerHandle;
+ GLint mTexMatrixHandle;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/input/Android.mk b/libs/input/Android.mk
index f1921a4..944ac7f 100644
--- a/libs/input/Android.mk
+++ b/libs/input/Android.mk
@@ -27,6 +27,7 @@ commonSources := \
deviceSources := \
$(commonSources) \
+ IInputFlinger.cpp \
InputTransport.cpp \
VelocityControl.cpp \
VelocityTracker.cpp
diff --git a/libs/input/IInputFlinger.cpp b/libs/input/IInputFlinger.cpp
new file mode 100644
index 0000000..e009731
--- /dev/null
+++ b/libs/input/IInputFlinger.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <binder/Parcel.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+
+#include <input/IInputFlinger.h>
+
+
+namespace android {
+
+class BpInputFlinger : public BpInterface<IInputFlinger> {
+public:
+ BpInputFlinger(const sp<IBinder>& impl) :
+ BpInterface<IInputFlinger>(impl) { }
+
+ virtual status_t doSomething() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IInputFlinger::getInterfaceDescriptor());
+ remote()->transact(BnInputFlinger::DO_SOMETHING_TRANSACTION, data, &reply);
+ return reply.readInt32();
+ }
+};
+
+IMPLEMENT_META_INTERFACE(InputFlinger, "android.input.IInputFlinger");
+
+
+status_t BnInputFlinger::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ switch(code) {
+ case DO_SOMETHING_TRANSACTION: {
+ CHECK_INTERFACE(IInputFlinger, data, reply);
+ reply->writeInt32(0);
+ break;
+ }
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+ return NO_ERROR;
+}
+
+};
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index d9f22e9..3a7afe9 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -21,6 +21,7 @@
#include <limits.h>
#include <input/Input.h>
+#include <input/InputEventLabels.h>
#ifdef HAVE_ANDROID_OS
#include <binder/Parcel.h>
@@ -42,82 +43,12 @@ void InputEvent::initialize(const InputEvent& from) {
// --- KeyEvent ---
-bool KeyEvent::hasDefaultAction(int32_t keyCode) {
- switch (keyCode) {
- case AKEYCODE_HOME:
- case AKEYCODE_BACK:
- case AKEYCODE_CALL:
- case AKEYCODE_ENDCALL:
- case AKEYCODE_VOLUME_UP:
- case AKEYCODE_VOLUME_DOWN:
- case AKEYCODE_VOLUME_MUTE:
- case AKEYCODE_POWER:
- case AKEYCODE_CAMERA:
- case AKEYCODE_HEADSETHOOK:
- case AKEYCODE_MENU:
- case AKEYCODE_NOTIFICATION:
- case AKEYCODE_FOCUS:
- case AKEYCODE_SEARCH:
- case AKEYCODE_MEDIA_PLAY:
- case AKEYCODE_MEDIA_PAUSE:
- case AKEYCODE_MEDIA_PLAY_PAUSE:
- case AKEYCODE_MEDIA_STOP:
- case AKEYCODE_MEDIA_NEXT:
- case AKEYCODE_MEDIA_PREVIOUS:
- case AKEYCODE_MEDIA_REWIND:
- case AKEYCODE_MEDIA_RECORD:
- case AKEYCODE_MEDIA_FAST_FORWARD:
- case AKEYCODE_MUTE:
- case AKEYCODE_BRIGHTNESS_DOWN:
- case AKEYCODE_BRIGHTNESS_UP:
- case AKEYCODE_MEDIA_AUDIO_TRACK:
- return true;
- }
-
- return false;
-}
-
-bool KeyEvent::hasDefaultAction() const {
- return hasDefaultAction(getKeyCode());
-}
-
-bool KeyEvent::isSystemKey(int32_t keyCode) {
- switch (keyCode) {
- case AKEYCODE_MENU:
- case AKEYCODE_SOFT_RIGHT:
- case AKEYCODE_HOME:
- case AKEYCODE_BACK:
- case AKEYCODE_CALL:
- case AKEYCODE_ENDCALL:
- case AKEYCODE_VOLUME_UP:
- case AKEYCODE_VOLUME_DOWN:
- case AKEYCODE_VOLUME_MUTE:
- case AKEYCODE_MUTE:
- case AKEYCODE_POWER:
- case AKEYCODE_HEADSETHOOK:
- case AKEYCODE_MEDIA_PLAY:
- case AKEYCODE_MEDIA_PAUSE:
- case AKEYCODE_MEDIA_PLAY_PAUSE:
- case AKEYCODE_MEDIA_STOP:
- case AKEYCODE_MEDIA_NEXT:
- case AKEYCODE_MEDIA_PREVIOUS:
- case AKEYCODE_MEDIA_REWIND:
- case AKEYCODE_MEDIA_RECORD:
- case AKEYCODE_MEDIA_FAST_FORWARD:
- case AKEYCODE_CAMERA:
- case AKEYCODE_FOCUS:
- case AKEYCODE_SEARCH:
- case AKEYCODE_BRIGHTNESS_DOWN:
- case AKEYCODE_BRIGHTNESS_UP:
- case AKEYCODE_MEDIA_AUDIO_TRACK:
- return true;
- }
-
- return false;
+const char* KeyEvent::getLabel(int32_t keyCode) {
+ return getLabelByKeyCode(keyCode);
}
-bool KeyEvent::isSystemKey() const {
- return isSystemKey(getKeyCode());
+int32_t KeyEvent::getKeyCodeFromLabel(const char* label) {
+ return getKeyCodeByLabel(label);
}
void KeyEvent::initialize(
@@ -591,6 +522,14 @@ bool MotionEvent::isTouchEvent(int32_t source, int32_t action) {
return false;
}
+const char* MotionEvent::getLabel(int32_t axis) {
+ return getAxisLabel(axis);
+}
+
+int32_t MotionEvent::getAxisFromLabel(const char* label) {
+ return getAxisByLabel(label);
+}
+
// --- PooledInputEventFactory ---
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 09b2e7c..a73780d 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -312,7 +312,7 @@ status_t InputPublisher::publishMotionEvent(
}
if (pointerCount > MAX_POINTERS || pointerCount < 1) {
- ALOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.",
+ ALOGE("channel '%s' publisher ~ Invalid number of pointers provided: %zu.",
mChannel->getName().string(), pointerCount);
return BAD_VALUE;
}
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index 15a8774..b03e01e 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -24,6 +24,7 @@
#endif
#include <android/keycodes.h>
+#include <input/InputEventLabels.h>
#include <input/Keyboard.h>
#include <input/KeyCharacterMap.h>
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index 0800a31..2b2f13e 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -19,6 +19,7 @@
#include <stdlib.h>
#include <android/keycodes.h>
+#include <input/InputEventLabels.h>
#include <input/Keyboard.h>
#include <input/KeyLayoutMap.h>
#include <utils/Log.h>
diff --git a/libs/input/Keyboard.cpp b/libs/input/Keyboard.cpp
index 7d4ac92..f4d9507 100644
--- a/libs/input/Keyboard.cpp
+++ b/libs/input/Keyboard.cpp
@@ -21,7 +21,7 @@
#include <limits.h>
#include <input/Keyboard.h>
-#include <input/KeycodeLabels.h>
+#include <input/InputEventLabels.h>
#include <input/KeyLayoutMap.h>
#include <input/KeyCharacterMap.h>
#include <input/InputDevice.h>
@@ -167,46 +167,6 @@ bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
return strstr(deviceIdentifier.name.string(), "-keypad");
}
-static int lookupValueByLabel(const char* literal, const KeycodeLabel *list) {
- while (list->literal) {
- if (strcmp(literal, list->literal) == 0) {
- return list->value;
- }
- list++;
- }
- return list->value;
-}
-
-static const char* lookupLabelByValue(int value, const KeycodeLabel *list) {
- while (list->literal) {
- if (list->value == value) {
- return list->literal;
- }
- list++;
- }
- return NULL;
-}
-
-int32_t getKeyCodeByLabel(const char* label) {
- return int32_t(lookupValueByLabel(label, KEYCODES));
-}
-
-uint32_t getKeyFlagByLabel(const char* label) {
- return uint32_t(lookupValueByLabel(label, FLAGS));
-}
-
-int32_t getAxisByLabel(const char* label) {
- return int32_t(lookupValueByLabel(label, AXES));
-}
-
-const char* getAxisLabel(int32_t axisId) {
- return lookupLabelByValue(axisId, AXES);
-}
-
-int32_t getLedByLabel(const char* label) {
- return int32_t(lookupValueByLabel(label, LEDS));
-}
-
static int32_t setEphemeralMetaState(int32_t mask, bool down, int32_t oldMetaState) {
int32_t newMetaState;
if (down) {
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index 008446b..eec97be 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -18,6 +18,7 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
Fence.cpp \
FramebufferNativeWindow.cpp \
+ FrameStats.cpp \
GraphicBuffer.cpp \
GraphicBufferAllocator.cpp \
GraphicBufferMapper.cpp \
diff --git a/libs/ui/FrameStats.cpp b/libs/ui/FrameStats.cpp
new file mode 100644
index 0000000..acbe27e
--- /dev/null
+++ b/libs/ui/FrameStats.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2014 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 <ui/FrameStats.h>
+
+namespace android {
+
+bool FrameStats::isFixedSize() const {
+ return false;
+}
+
+size_t FrameStats::getFlattenedSize() const {
+ const size_t timestampSize = sizeof(nsecs_t);
+
+ size_t size = timestampSize;
+ size += 3 * desiredPresentTimesNano.size() * timestampSize;
+
+ return size;
+}
+
+status_t FrameStats::flatten(void* buffer, size_t size) const {
+ if (size < getFlattenedSize()) {
+ return NO_MEMORY;
+ }
+
+ nsecs_t* timestamps = reinterpret_cast<nsecs_t*>(buffer);
+ const size_t timestampSize = sizeof(nsecs_t);
+ size_t frameCount = desiredPresentTimesNano.size();
+
+ memcpy(timestamps, &refreshPeriodNano, timestampSize);
+ timestamps += 1;
+
+ memcpy(timestamps, desiredPresentTimesNano.array(), frameCount * timestampSize);
+ timestamps += frameCount;
+
+ memcpy(timestamps, actualPresentTimesNano.array(), frameCount * timestampSize);
+ timestamps += frameCount;
+
+ memcpy(timestamps, frameReadyTimesNano.array(), frameCount * timestampSize);
+
+ return NO_ERROR;
+}
+
+status_t FrameStats::unflatten(void const* buffer, size_t size) {
+ const size_t timestampSize = sizeof(nsecs_t);
+
+ if (size < timestampSize) {
+ return NO_MEMORY;
+ }
+
+ nsecs_t const* timestamps = reinterpret_cast<nsecs_t const*>(buffer);
+ size_t frameCount = (size - timestampSize) / (3 * timestampSize);
+
+ memcpy(&refreshPeriodNano, timestamps, timestampSize);
+ timestamps += 1;
+
+ desiredPresentTimesNano.resize(frameCount);
+ memcpy(desiredPresentTimesNano.editArray(), timestamps, frameCount * timestampSize);
+ timestamps += frameCount;
+
+ actualPresentTimesNano.resize(frameCount);
+ memcpy(actualPresentTimesNano.editArray(), timestamps, frameCount * timestampSize);
+ timestamps += frameCount;
+
+ frameReadyTimesNano.resize(frameCount);
+ memcpy(frameReadyTimesNano.editArray(), timestamps, frameCount * timestampSize);
+
+ return NO_ERROR;
+}
+
+} // namespace android
diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index 96a7188..99601ed 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -34,9 +34,17 @@ namespace android {
// Buffer and implementation of ANativeWindowBuffer
// ===========================================================================
+static uint64_t getUniqueId() {
+ static volatile int32_t nextId = 0;
+ uint64_t id = static_cast<uint64_t>(getpid()) << 32;
+ id |= static_cast<uint32_t>(android_atomic_inc(&nextId));
+ return id;
+}
+
GraphicBuffer::GraphicBuffer()
: BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),
- mInitCheck(NO_ERROR) {
+ mInitCheck(NO_ERROR), mId(getUniqueId())
+{
width =
height =
stride =
@@ -48,7 +56,7 @@ GraphicBuffer::GraphicBuffer()
GraphicBuffer::GraphicBuffer(uint32_t w, uint32_t h,
PixelFormat reqFormat, uint32_t reqUsage)
: BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),
- mInitCheck(NO_ERROR)
+ mInitCheck(NO_ERROR), mId(getUniqueId())
{
width =
height =
@@ -64,7 +72,7 @@ GraphicBuffer::GraphicBuffer(uint32_t w, uint32_t h,
uint32_t inStride, native_handle_t* inHandle, bool keepOwnership)
: BASE(), mOwner(keepOwnership ? ownHandle : ownNone),
mBufferMapper(GraphicBufferMapper::get()),
- mInitCheck(NO_ERROR)
+ mInitCheck(NO_ERROR), mId(getUniqueId())
{
width = w;
height = h;
@@ -77,7 +85,7 @@ GraphicBuffer::GraphicBuffer(uint32_t w, uint32_t h,
GraphicBuffer::GraphicBuffer(ANativeWindowBuffer* buffer, bool keepOwnership)
: BASE(), mOwner(keepOwnership ? ownHandle : ownNone),
mBufferMapper(GraphicBufferMapper::get()),
- mInitCheck(NO_ERROR), mWrappedBuffer(buffer)
+ mInitCheck(NO_ERROR), mWrappedBuffer(buffer), mId(getUniqueId())
{
width = buffer->width;
height = buffer->height;
@@ -201,7 +209,7 @@ status_t GraphicBuffer::unlock()
}
size_t GraphicBuffer::getFlattenedSize() const {
- return (8 + (handle ? handle->numInts : 0))*sizeof(int);
+ return (10 + (handle ? handle->numInts : 0))*sizeof(int);
}
size_t GraphicBuffer::getFdCount() const {
@@ -215,28 +223,32 @@ status_t GraphicBuffer::flatten(void*& buffer, size_t& size, int*& fds, size_t&
size_t fdCountNeeded = GraphicBuffer::getFdCount();
if (count < fdCountNeeded) return NO_MEMORY;
- int* buf = static_cast<int*>(buffer);
+ int32_t* buf = static_cast<int32_t*>(buffer);
buf[0] = 'GBFR';
buf[1] = width;
buf[2] = height;
buf[3] = stride;
buf[4] = format;
buf[5] = usage;
- buf[6] = 0;
- buf[7] = 0;
+ buf[6] = static_cast<int32_t>(mId >> 32);
+ buf[7] = static_cast<int32_t>(mId & 0xFFFFFFFFull);
+ buf[8] = 0;
+ buf[9] = 0;
if (handle) {
- buf[6] = handle->numFds;
- buf[7] = handle->numInts;
+ buf[8] = handle->numFds;
+ buf[9] = handle->numInts;
native_handle_t const* const h = handle;
memcpy(fds, h->data, h->numFds*sizeof(int));
- memcpy(&buf[8], h->data + h->numFds, h->numInts*sizeof(int));
+ memcpy(&buf[10], h->data + h->numFds, h->numInts*sizeof(int));
}
buffer = reinterpret_cast<void*>(static_cast<int*>(buffer) + sizeNeeded);
size -= sizeNeeded;
- fds += handle->numFds;
- count -= handle->numFds;
+ if (handle) {
+ fds += handle->numFds;
+ count -= handle->numFds;
+ }
return NO_ERROR;
}
@@ -248,10 +260,10 @@ status_t GraphicBuffer::unflatten(
int const* buf = static_cast<int const*>(buffer);
if (buf[0] != 'GBFR') return BAD_TYPE;
- const size_t numFds = buf[6];
- const size_t numInts = buf[7];
+ const size_t numFds = buf[8];
+ const size_t numInts = buf[9];
- const size_t sizeNeeded = (8 + numInts) * sizeof(int);
+ const size_t sizeNeeded = (10 + numInts) * sizeof(int);
if (size < sizeNeeded) return NO_MEMORY;
size_t fdCountNeeded = 0;
@@ -270,13 +282,16 @@ status_t GraphicBuffer::unflatten(
usage = buf[5];
native_handle* h = native_handle_create(numFds, numInts);
memcpy(h->data, fds, numFds*sizeof(int));
- memcpy(h->data + numFds, &buf[8], numInts*sizeof(int));
+ memcpy(h->data + numFds, &buf[10], numInts*sizeof(int));
handle = h;
} else {
width = height = stride = format = usage = 0;
handle = NULL;
}
+ mId = static_cast<uint64_t>(buf[6]) << 32;
+ mId |= static_cast<uint32_t>(buf[7]);
+
mOwner = ownHandle;
if (handle != 0) {
diff --git a/libs/ui/PixelFormat.cpp b/libs/ui/PixelFormat.cpp
index d2d103a..5ce7fba 100644
--- a/libs/ui/PixelFormat.cpp
+++ b/libs/ui/PixelFormat.cpp
@@ -26,6 +26,8 @@ ssize_t bytesPerPixel(PixelFormat format) {
case PIXEL_FORMAT_RGBA_8888:
case PIXEL_FORMAT_RGBX_8888:
case PIXEL_FORMAT_BGRA_8888:
+ case PIXEL_FORMAT_sRGB_A_8888:
+ case PIXEL_FORMAT_sRGB_X_8888:
return 4;
case PIXEL_FORMAT_RGB_888:
return 3;
diff --git a/libs/ui/Region.cpp b/libs/ui/Region.cpp
index 6d58f56..fa812f4 100644
--- a/libs/ui/Region.cpp
+++ b/libs/ui/Region.cpp
@@ -222,6 +222,22 @@ Region& Region::makeBoundsSelf()
return *this;
}
+bool Region::contains(const Point& point) const {
+ return contains(point.x, point.y);
+}
+
+bool Region::contains(int x, int y) const {
+ const_iterator cur = begin();
+ const_iterator const tail = end();
+ while (cur != tail) {
+ if (y >= cur->top && y < cur->bottom && x >= cur->left && x < cur->right) {
+ return true;
+ }
+ cur++;
+ }
+ return false;
+}
+
void Region::clear()
{
mStorage.clear();
diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp
index 22990f3..6e77e45 100644
--- a/opengl/libs/EGL/eglApi.cpp
+++ b/opengl/libs/EGL/eglApi.cpp
@@ -408,9 +408,11 @@ EGLSurface eglCreateWindowSurface( EGLDisplay dpy, EGLConfig config,
if (dp) {
EGLDisplay iDpy = dp->disp.dpy;
- if (native_window_api_connect(window, NATIVE_WINDOW_API_EGL) != OK) {
- ALOGE("EGLNativeWindowType %p already connected to another API",
- window);
+ int result = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
+ if (result != OK) {
+ ALOGE("eglCreateWindowSurface: native_window_api_connect (win=%p) "
+ "failed (%#x) (already connected to another API?)",
+ window, result);
return setError(EGL_BAD_ALLOC, EGL_NO_SURFACE);
}
diff --git a/opengl/tests/EGLTest/EGL_test.cpp b/opengl/tests/EGLTest/EGL_test.cpp
index f6644fb..a4364c6 100644
--- a/opengl/tests/EGLTest/EGL_test.cpp
+++ b/opengl/tests/EGLTest/EGL_test.cpp
@@ -103,12 +103,15 @@ TEST_F(EGLTest, EGLTerminateSucceedsWithRemainingObjects) {
struct DummyConsumer : public BnConsumerListener {
virtual void onFrameAvailable() {}
virtual void onBuffersReleased() {}
+ virtual void onSidebandStreamChanged() {}
};
// Create a EGLSurface
- sp<BufferQueue> bq = new BufferQueue();
- bq->consumerConnect(new DummyConsumer, false);
- sp<Surface> mSTC = new Surface(static_cast<sp<IGraphicBufferProducer> >( bq));
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ consumer->consumerConnect(new DummyConsumer, false);
+ sp<Surface> mSTC = new Surface(producer);
sp<ANativeWindow> mANW = mSTC;
EGLSurface eglSurface = eglCreateWindowSurface(mEglDisplay, config,
diff --git a/opengl/tests/angeles/Android.mk b/opengl/tests/angeles/Android.mk
index ae4f76d..c78224e 100644
--- a/opengl/tests/angeles/Android.mk
+++ b/opengl/tests/angeles/Android.mk
@@ -3,7 +3,8 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= app-linux.cpp demo.c.arm
-LOCAL_SHARED_LIBRARIES := libEGL libGLESv1_CM libui
+LOCAL_SHARED_LIBRARIES := libEGL libGLESv1_CM libui libgui libutils
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
LOCAL_MODULE:= angeles
LOCAL_MODULE_TAGS := optional
diff --git a/opengl/tests/angeles/app-linux.cpp b/opengl/tests/angeles/app-linux.cpp
index 6ac68a2..e490351 100644
--- a/opengl/tests/angeles/app-linux.cpp
+++ b/opengl/tests/angeles/app-linux.cpp
@@ -52,8 +52,8 @@
#include <EGL/egl.h>
#include <GLES/gl.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <EGLUtils.h>
+#include <WindowSurface.h>
using namespace android;
@@ -118,7 +118,7 @@ static void checkEGLErrors()
fprintf(stderr, "EGL Error: 0x%04x\n", (int)error);
}
-static int initGraphics(unsigned samples)
+static int initGraphics(unsigned samples, const WindowSurface& windowSurface)
{
EGLint configAttribs[] = {
EGL_DEPTH_SIZE, 16,
@@ -135,7 +135,7 @@ static int initGraphics(unsigned samples)
EGLint w, h;
EGLDisplay dpy;
- EGLNativeWindowType window = android_createDisplaySurface();
+ EGLNativeWindowType window = windowSurface.getSurface();
dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(dpy, &majorVersion, &minorVersion);
@@ -193,7 +193,8 @@ int main(int argc, char *argv[])
printf("Multisample enabled: GL_SAMPLES = %u\n", samples);
}
- if (!initGraphics(samples))
+ WindowSurface windowSurface;
+ if (!initGraphics(samples, windowSurface))
{
fprintf(stderr, "Graphics initialization failed.\n");
return EXIT_FAILURE;
diff --git a/opengl/tests/fillrate/Android.mk b/opengl/tests/fillrate/Android.mk
index 4dade21..21ff52a 100644
--- a/opengl/tests/fillrate/Android.mk
+++ b/opengl/tests/fillrate/Android.mk
@@ -9,7 +9,10 @@ LOCAL_SHARED_LIBRARIES := \
libutils \
libEGL \
libGLESv1_CM \
- libui
+ libui \
+ libgui
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/fillrate/fillrate.cpp b/opengl/tests/fillrate/fillrate.cpp
index a708647..1d9b026 100644
--- a/opengl/tests/fillrate/fillrate.cpp
+++ b/opengl/tests/fillrate/fillrate.cpp
@@ -25,8 +25,8 @@
#include <GLES/glext.h>
#include <utils/StopWatch.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
using namespace android;
@@ -45,7 +45,8 @@ int main(int argc, char** argv)
EGLint w, h;
EGLDisplay dpy;
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(dpy, &majorVersion, &minorVersion);
diff --git a/opengl/tests/filter/Android.mk b/opengl/tests/filter/Android.mk
index d3e4d38..4cf9c96 100644
--- a/opengl/tests/filter/Android.mk
+++ b/opengl/tests/filter/Android.mk
@@ -8,7 +8,11 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libEGL \
libGLESv1_CM \
- libui
+ libui \
+ libgui \
+ libutils
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/filter/filter.cpp b/opengl/tests/filter/filter.cpp
index 0067327..289e6cc 100644
--- a/opengl/tests/filter/filter.cpp
+++ b/opengl/tests/filter/filter.cpp
@@ -5,8 +5,8 @@
#include <GLES/gl.h>
#include <GLES/glext.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
using namespace android;
@@ -40,8 +40,10 @@ int main(int argc, char** argv)
EGLDisplay dpy;
EGLNativeWindowType window = 0;
+ WindowSurface* windowSurface = NULL;
if (!usePbuffer) {
- window = android_createDisplaySurface();
+ windowSurface = new WindowSurface();
+ window = windowSurface->getSurface();
}
dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
@@ -186,5 +188,6 @@ int main(int argc, char** argv)
}
eglTerminate(dpy);
+ delete windowSurface;
return 0;
}
diff --git a/opengl/tests/finish/Android.mk b/opengl/tests/finish/Android.mk
index aa8adca..0b9b7ea 100644
--- a/opengl/tests/finish/Android.mk
+++ b/opengl/tests/finish/Android.mk
@@ -9,7 +9,10 @@ LOCAL_SHARED_LIBRARIES := \
libutils \
libEGL \
libGLESv1_CM \
- libui
+ libui \
+ libgui
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/finish/finish.cpp b/opengl/tests/finish/finish.cpp
index 11f0c22..ea3a60f 100644
--- a/opengl/tests/finish/finish.cpp
+++ b/opengl/tests/finish/finish.cpp
@@ -26,8 +26,8 @@
#include <utils/Timers.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
using namespace android;
@@ -46,7 +46,8 @@ int main(int argc, char** argv)
EGLint w, h;
EGLDisplay dpy;
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(dpy, &majorVersion, &minorVersion);
diff --git a/opengl/tests/gl2_basic/Android.mk b/opengl/tests/gl2_basic/Android.mk
index d7819a1..520395c 100644
--- a/opengl/tests/gl2_basic/Android.mk
+++ b/opengl/tests/gl2_basic/Android.mk
@@ -8,7 +8,11 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libEGL \
libGLESv2 \
- libui
+ libui \
+ libgui \
+ libutils
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/gl2_basic/gl2_basic.cpp b/opengl/tests/gl2_basic/gl2_basic.cpp
index 7007871..cdbf1cf 100644
--- a/opengl/tests/gl2_basic/gl2_basic.cpp
+++ b/opengl/tests/gl2_basic/gl2_basic.cpp
@@ -26,8 +26,8 @@
#include <utils/Timers.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
using namespace android;
@@ -298,7 +298,8 @@ int main(int argc, char** argv) {
checkEglError("printEGLConfigurations");
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
returnValue = EGLUtils::selectConfigForNativeWindow(dpy, s_configAttribs, window, &myConfig);
if (returnValue) {
printf("EGLUtils::selectConfigForNativeWindow() returned %d", returnValue);
diff --git a/opengl/tests/gl2_copyTexImage/Android.mk b/opengl/tests/gl2_copyTexImage/Android.mk
index 005c383..ff43558 100644
--- a/opengl/tests/gl2_copyTexImage/Android.mk
+++ b/opengl/tests/gl2_copyTexImage/Android.mk
@@ -8,7 +8,11 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libEGL \
libGLESv2 \
- libui
+ libui \
+ libgui \
+ libutils
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/gl2_copyTexImage/gl2_copyTexImage.cpp b/opengl/tests/gl2_copyTexImage/gl2_copyTexImage.cpp
index 988d7ac..405a3f0 100644
--- a/opengl/tests/gl2_copyTexImage/gl2_copyTexImage.cpp
+++ b/opengl/tests/gl2_copyTexImage/gl2_copyTexImage.cpp
@@ -26,8 +26,8 @@
#include <utils/Timers.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
using namespace android;
@@ -406,7 +406,8 @@ int main(int argc, char** argv) {
checkEglError("printEGLConfigurations");
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
EGLint numConfigs = -1, n = 0;
eglChooseConfig(dpy, s_configAttribs, 0, 0, &numConfigs);
if (numConfigs) {
diff --git a/opengl/tests/gl2_yuvtex/Android.mk b/opengl/tests/gl2_yuvtex/Android.mk
index bb3cc0c..42cf771 100644
--- a/opengl/tests/gl2_yuvtex/Android.mk
+++ b/opengl/tests/gl2_yuvtex/Android.mk
@@ -9,7 +9,11 @@ LOCAL_SHARED_LIBRARIES := \
libEGL \
libGLESv2 \
libutils \
- libui
+ libui \
+ libgui \
+ libutils
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/gl2_yuvtex/gl2_yuvtex.cpp b/opengl/tests/gl2_yuvtex/gl2_yuvtex.cpp
index d3e4932..98d8aa8 100644
--- a/opengl/tests/gl2_yuvtex/gl2_yuvtex.cpp
+++ b/opengl/tests/gl2_yuvtex/gl2_yuvtex.cpp
@@ -27,9 +27,9 @@
#include <utils/Timers.h>
-#include <ui/FramebufferNativeWindow.h>
+#include <WindowSurface.h>
#include <ui/GraphicBuffer.h>
-#include "EGLUtils.h"
+#include <EGLUtils.h>
using namespace android;
@@ -364,7 +364,8 @@ int main(int argc, char** argv) {
return 0;
}
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
returnValue = EGLUtils::selectConfigForNativeWindow(dpy, s_configAttribs, window, &myConfig);
if (returnValue) {
printf("EGLUtils::selectConfigForNativeWindow() returned %d", returnValue);
diff --git a/opengl/tests/gl_basic/Android.mk b/opengl/tests/gl_basic/Android.mk
index 46bcc60..7f2259e 100644
--- a/opengl/tests/gl_basic/Android.mk
+++ b/opengl/tests/gl_basic/Android.mk
@@ -8,7 +8,11 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libEGL \
libGLESv1_CM \
- libui
+ libui \
+ libgui \
+ libutils
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/gl_basic/gl_basic.cpp b/opengl/tests/gl_basic/gl_basic.cpp
index 23ce934..e50d88f 100644
--- a/opengl/tests/gl_basic/gl_basic.cpp
+++ b/opengl/tests/gl_basic/gl_basic.cpp
@@ -5,8 +5,8 @@
#include <GLES/gl.h>
#include <GLES/glext.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
#include <stdio.h>
@@ -23,7 +23,7 @@ GLuint texture;
#define FIXED_ONE 0x10000
#define ITERATIONS 50
-int init_gl_surface(void);
+int init_gl_surface(const WindowSurface& windowSurface);
void free_gl_surface(void);
void init_scene(void);
void render();
@@ -194,7 +194,8 @@ int main(int argc, char **argv)
int q;
int start, end;
printf("Initializing EGL...\n");
- if(!init_gl_surface())
+ WindowSurface windowSurface;
+ if(!init_gl_surface(windowSurface))
{
printf("GL initialisation failed - exiting\n");
return 0;
@@ -209,7 +210,7 @@ int main(int argc, char **argv)
return 0;
}
-int init_gl_surface(void)
+int init_gl_surface(const WindowSurface& windowSurface)
{
EGLint numConfigs = 1;
EGLConfig myConfig = {0};
@@ -236,7 +237,7 @@ int init_gl_surface(void)
return 0;
}
- EGLNativeWindowType window = android_createDisplaySurface();
+ EGLNativeWindowType window = windowSurface.getSurface();
EGLUtils::selectConfigForNativeWindow(eglDisplay, attrib, window, &myConfig);
if ( (eglSurface = eglCreateWindowSurface(eglDisplay, myConfig,
diff --git a/opengl/tests/gl_perf/Android.mk b/opengl/tests/gl_perf/Android.mk
index b0f825c..9a93fab 100644
--- a/opengl/tests/gl_perf/Android.mk
+++ b/opengl/tests/gl_perf/Android.mk
@@ -10,7 +10,11 @@ LOCAL_SHARED_LIBRARIES := \
liblog \
libEGL \
libGLESv2 \
- libui
+ libui \
+ libgui \
+ libutils
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/gl_perf/gl2_perf.cpp b/opengl/tests/gl_perf/gl2_perf.cpp
index 224acaf..35df84f 100644
--- a/opengl/tests/gl_perf/gl2_perf.cpp
+++ b/opengl/tests/gl_perf/gl2_perf.cpp
@@ -26,8 +26,8 @@
#include <utils/Timers.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
using namespace android;
@@ -86,7 +86,8 @@ int main(int argc, char** argv) {
return 0;
}
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
returnValue = EGLUtils::selectConfigForNativeWindow(dpy, s_configAttribs, window, &myConfig);
if (returnValue) {
printf("EGLUtils::selectConfigForNativeWindow() returned %d", returnValue);
diff --git a/opengl/tests/gl_yuvtex/Android.mk b/opengl/tests/gl_yuvtex/Android.mk
index e0e2c16..7f2020a 100644
--- a/opengl/tests/gl_yuvtex/Android.mk
+++ b/opengl/tests/gl_yuvtex/Android.mk
@@ -9,7 +9,10 @@ LOCAL_SHARED_LIBRARIES := \
libEGL \
libGLESv1_CM \
libutils \
- libui
+ libui \
+ libgui
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/gl_yuvtex/gl_yuvtex.cpp b/opengl/tests/gl_yuvtex/gl_yuvtex.cpp
index 7a00f76..c923b07 100644
--- a/opengl/tests/gl_yuvtex/gl_yuvtex.cpp
+++ b/opengl/tests/gl_yuvtex/gl_yuvtex.cpp
@@ -27,9 +27,9 @@
#include <utils/Timers.h>
-#include <ui/FramebufferNativeWindow.h>
+#include <WindowSurface.h>
#include <ui/GraphicBuffer.h>
-#include "EGLUtils.h"
+#include <EGLUtils.h>
using namespace android;
@@ -254,7 +254,8 @@ int main(int argc, char** argv) {
return 0;
}
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
returnValue = EGLUtils::selectConfigForNativeWindow(dpy, s_configAttribs, window, &myConfig);
if (returnValue) {
printf("EGLUtils::selectConfigForNativeWindow() returned %d", returnValue);
diff --git a/opengl/tests/hwc/hwcColorEquiv.cpp b/opengl/tests/hwc/hwcColorEquiv.cpp
index 160906d..c4624d2 100644
--- a/opengl/tests/hwc/hwcColorEquiv.cpp
+++ b/opengl/tests/hwc/hwcColorEquiv.cpp
@@ -85,7 +85,6 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
-#include <ui/FramebufferNativeWindow.h>
#include <ui/GraphicBuffer.h>
#define LOG_TAG "hwcColorEquivTest"
diff --git a/opengl/tests/hwc/hwcCommit.cpp b/opengl/tests/hwc/hwcCommit.cpp
index 3681fbb..1bd5fdf 100644
--- a/opengl/tests/hwc/hwcCommit.cpp
+++ b/opengl/tests/hwc/hwcCommit.cpp
@@ -96,7 +96,6 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
-#include <ui/FramebufferNativeWindow.h>
#include <ui/GraphicBuffer.h>
#define LOG_TAG "hwcCommitTest"
diff --git a/opengl/tests/hwc/hwcRects.cpp b/opengl/tests/hwc/hwcRects.cpp
index ec0403f..9b57623 100644
--- a/opengl/tests/hwc/hwcRects.cpp
+++ b/opengl/tests/hwc/hwcRects.cpp
@@ -104,7 +104,6 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
-#include <ui/FramebufferNativeWindow.h>
#include <ui/GraphicBuffer.h>
#define LOG_TAG "hwcRectsTest"
diff --git a/opengl/tests/hwc/hwcStress.cpp b/opengl/tests/hwc/hwcStress.cpp
index dfaa6c1..b1d6c76 100644
--- a/opengl/tests/hwc/hwcStress.cpp
+++ b/opengl/tests/hwc/hwcStress.cpp
@@ -101,7 +101,6 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
-#include <ui/FramebufferNativeWindow.h>
#include <ui/GraphicBuffer.h>
#define LOG_TAG "hwcStressTest"
diff --git a/opengl/tests/hwc/hwcTestLib.cpp b/opengl/tests/hwc/hwcTestLib.cpp
index 9b224e2..7fae5e5 100644
--- a/opengl/tests/hwc/hwcTestLib.cpp
+++ b/opengl/tests/hwc/hwcTestLib.cpp
@@ -80,7 +80,11 @@ void hwcTestInitDisplay(bool verbose, EGLDisplay *dpy, EGLSurface *surface,
exit(71);
}
- EGLNativeWindowType window = android_createDisplaySurface();
+ // The tests want to stop the framework and play with the hardware
+ // composer, which means it doesn't make sense to use WindowSurface
+ // here. android_createDisplaySurface() is going away, so just
+ // politely fail here.
+ EGLNativeWindowType window = NULL; //android_createDisplaySurface();
if (window == NULL) {
testPrintE("android_createDisplaySurface failed");
exit(72);
diff --git a/opengl/tests/hwc/hwcTestLib.h b/opengl/tests/hwc/hwcTestLib.h
index d403308..a942c10 100644
--- a/opengl/tests/hwc/hwcTestLib.h
+++ b/opengl/tests/hwc/hwcTestLib.h
@@ -27,7 +27,6 @@
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
-#include <ui/FramebufferNativeWindow.h>
#include <ui/GraphicBuffer.h>
#include <utils/Log.h>
diff --git a/opengl/tests/include/WindowSurface.h b/opengl/tests/include/WindowSurface.h
new file mode 100644
index 0000000..0ec1404
--- /dev/null
+++ b/opengl/tests/include/WindowSurface.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 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 OPENGL_TESTS_WINDOWSURFACE_H
+#define OPENGL_TESTS_WINDOWSURFACE_H
+
+#include <gui/SurfaceControl.h>
+
+#include <EGL/egl.h>
+
+namespace android {
+
+/*
+ * A window that covers the entire display surface.
+ *
+ * The window is destroyed when this object is destroyed, so don't try
+ * to use the surface after that point.
+ */
+class WindowSurface {
+public:
+ // Creates the window.
+ WindowSurface();
+
+ // Retrieves a handle to the window.
+ EGLNativeWindowType getSurface() const;
+
+private:
+ WindowSurface(const WindowSurface&);
+ WindowSurface& operator=(const WindowSurface&);
+
+ sp<SurfaceControl> mSurfaceControl;
+};
+
+} // namespace android
+
+#endif /* OPENGL_TESTS_WINDOWSURFACE_H */
diff --git a/opengl/tests/lib/Android.mk b/opengl/tests/lib/Android.mk
index 0352a37..a2752cd 100644
--- a/opengl/tests/lib/Android.mk
+++ b/opengl/tests/lib/Android.mk
@@ -17,7 +17,7 @@ LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE:= libglTest
-LOCAL_SRC_FILES:= glTestLib.cpp
+LOCAL_SRC_FILES:= glTestLib.cpp WindowSurface.cpp
LOCAL_C_INCLUDES += system/extras/tests/include \
bionic \
bionic/libstdc++/include \
diff --git a/opengl/tests/lib/WindowSurface.cpp b/opengl/tests/lib/WindowSurface.cpp
new file mode 100644
index 0000000..1428945
--- /dev/null
+++ b/opengl/tests/lib/WindowSurface.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014 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 <WindowSurface.h>
+
+#include <gui/SurfaceComposerClient.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/Surface.h>
+#include <ui/DisplayInfo.h>
+
+using namespace android;
+
+WindowSurface::WindowSurface() {
+ status_t err;
+
+ sp<SurfaceComposerClient> surfaceComposerClient = new SurfaceComposerClient;
+ err = surfaceComposerClient->initCheck();
+ if (err != NO_ERROR) {
+ fprintf(stderr, "SurfaceComposerClient::initCheck error: %#x\n", err);
+ return;
+ }
+
+ // Get main display parameters.
+ sp<IBinder> mainDpy = SurfaceComposerClient::getBuiltInDisplay(
+ ISurfaceComposer::eDisplayIdMain);
+ DisplayInfo mainDpyInfo;
+ err = SurfaceComposerClient::getDisplayInfo(mainDpy, &mainDpyInfo);
+ if (err != NO_ERROR) {
+ fprintf(stderr, "ERROR: unable to get display characteristics\n");
+ return;
+ }
+
+ uint32_t width, height;
+ if (mainDpyInfo.orientation != DISPLAY_ORIENTATION_0 &&
+ mainDpyInfo.orientation != DISPLAY_ORIENTATION_180) {
+ // rotated
+ width = mainDpyInfo.h;
+ height = mainDpyInfo.w;
+ } else {
+ width = mainDpyInfo.w;
+ height = mainDpyInfo.h;
+ }
+
+ sp<SurfaceControl> sc = surfaceComposerClient->createSurface(
+ String8("Benchmark"), width, height,
+ PIXEL_FORMAT_RGBX_8888, ISurfaceComposerClient::eOpaque);
+ if (sc == NULL || !sc->isValid()) {
+ fprintf(stderr, "Failed to create SurfaceControl\n");
+ return;
+ }
+
+ SurfaceComposerClient::openGlobalTransaction();
+ err = sc->setLayer(0x7FFFFFFF); // always on top
+ if (err != NO_ERROR) {
+ fprintf(stderr, "SurfaceComposer::setLayer error: %#x\n", err);
+ return;
+ }
+
+ err = sc->show();
+ if (err != NO_ERROR) {
+ fprintf(stderr, "SurfaceComposer::show error: %#x\n", err);
+ return;
+ }
+ SurfaceComposerClient::closeGlobalTransaction();
+
+ mSurfaceControl = sc;
+}
+
+EGLNativeWindowType WindowSurface::getSurface() const {
+ sp<ANativeWindow> anw = mSurfaceControl->getSurface();
+ return (EGLNativeWindowType) anw.get();
+}
+
diff --git a/opengl/tests/linetex/Android.mk b/opengl/tests/linetex/Android.mk
index 5b6384e..968756a 100644
--- a/opengl/tests/linetex/Android.mk
+++ b/opengl/tests/linetex/Android.mk
@@ -8,7 +8,11 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libEGL \
libGLESv1_CM \
- libui
+ libui \
+ libgui \
+ libutils
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/linetex/linetex.cpp b/opengl/tests/linetex/linetex.cpp
index 8669492..7921f80 100644
--- a/opengl/tests/linetex/linetex.cpp
+++ b/opengl/tests/linetex/linetex.cpp
@@ -15,8 +15,6 @@
** limitations under the License.
*/
-#define LOG_TAG "fillrate"
-
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
@@ -26,8 +24,8 @@
#include <GLES/glext.h>
#include <utils/StopWatch.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
using namespace android;
@@ -46,7 +44,8 @@ int main(int argc, char** argv)
EGLint w, h;
EGLDisplay dpy;
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(dpy, &majorVersion, &minorVersion);
diff --git a/opengl/tests/swapinterval/Android.mk b/opengl/tests/swapinterval/Android.mk
index 5517f60..b0b15eb 100644
--- a/opengl/tests/swapinterval/Android.mk
+++ b/opengl/tests/swapinterval/Android.mk
@@ -9,7 +9,10 @@ LOCAL_SHARED_LIBRARIES := \
libutils \
libEGL \
libGLESv1_CM \
- libui
+ libui \
+ libgui
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/swapinterval/swapinterval.cpp b/opengl/tests/swapinterval/swapinterval.cpp
index a0f4bc4..3a8a8a1 100644
--- a/opengl/tests/swapinterval/swapinterval.cpp
+++ b/opengl/tests/swapinterval/swapinterval.cpp
@@ -23,8 +23,8 @@
#include <GLES/glext.h>
#include <utils/StopWatch.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
using namespace android;
@@ -45,7 +45,8 @@ int main(int argc, char** argv)
EGLDisplay dpy;
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(dpy, &majorVersion, &minorVersion);
diff --git a/opengl/tests/textures/Android.mk b/opengl/tests/textures/Android.mk
index 97697d7..bee61f9 100644
--- a/opengl/tests/textures/Android.mk
+++ b/opengl/tests/textures/Android.mk
@@ -8,7 +8,11 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libEGL \
libGLESv1_CM \
- libui
+ libui \
+ libgui \
+ libutils
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/textures/textures.cpp b/opengl/tests/textures/textures.cpp
index 5d3d94e..1e55db0 100644
--- a/opengl/tests/textures/textures.cpp
+++ b/opengl/tests/textures/textures.cpp
@@ -22,8 +22,8 @@
#include <GLES/gl.h>
#include <GLES/glext.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
using namespace android;
@@ -42,7 +42,8 @@ int main(int argc, char** argv)
EGLint w, h;
EGLDisplay dpy;
- EGLNativeWindowType window = android_createDisplaySurface();
+ WindowSurface windowSurface;
+ EGLNativeWindowType window = windowSurface.getSurface();
dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(dpy, &majorVersion, &minorVersion);
@@ -114,5 +115,7 @@ int main(int argc, char** argv)
glDrawTexiOES(dim/2, dim/2, 0, dim/2, dim/2);
eglSwapBuffers(dpy, surface);
+
+ sleep(2); // so you have a chance to admire it
return 0;
}
diff --git a/opengl/tests/tritex/Android.mk b/opengl/tests/tritex/Android.mk
index 89faa87..64382ed 100644
--- a/opengl/tests/tritex/Android.mk
+++ b/opengl/tests/tritex/Android.mk
@@ -8,7 +8,11 @@ LOCAL_SHARED_LIBRARIES := \
libcutils \
libEGL \
libGLESv1_CM \
- libui
+ libui \
+ libgui \
+ libutils
+
+LOCAL_STATIC_LIBRARIES += libglTest
LOCAL_C_INCLUDES += $(call include-path-for, opengl-tests-includes)
diff --git a/opengl/tests/tritex/tritex.cpp b/opengl/tests/tritex/tritex.cpp
index f183483..2db73ef 100644
--- a/opengl/tests/tritex/tritex.cpp
+++ b/opengl/tests/tritex/tritex.cpp
@@ -8,8 +8,8 @@
#include <GLES/gl.h>
#include <GLES/glext.h>
-#include <ui/FramebufferNativeWindow.h>
-#include "EGLUtils.h"
+#include <WindowSurface.h>
+#include <EGLUtils.h>
#include <stdio.h>
#include <stdlib.h>
@@ -25,7 +25,7 @@ GLuint texture;
#define FIXED_ONE 0x10000
#define ITERATIONS 50
-int init_gl_surface(void);
+int init_gl_surface(const WindowSurface&);
void free_gl_surface(void);
void init_scene(void);
void render(int quads);
@@ -98,7 +98,8 @@ int main(int argc, char **argv)
printf("Initializing EGL...\n");
- if(!init_gl_surface())
+ WindowSurface windowSurface;
+ if(!init_gl_surface(windowSurface))
{
printf("GL initialisation failed - exiting\n");
return 0;
@@ -117,7 +118,7 @@ int main(int argc, char **argv)
return 0;
}
-int init_gl_surface(void)
+int init_gl_surface(const WindowSurface& windowSurface)
{
EGLint numConfigs = 1;
EGLConfig myConfig = {0};
@@ -140,7 +141,7 @@ int init_gl_surface(void)
return 0;
}
- EGLNativeWindowType window = android_createDisplaySurface();
+ EGLNativeWindowType window = windowSurface.getSurface();
EGLUtils::selectConfigForNativeWindow(eglDisplay, attrib, window, &myConfig);
if ( (eglSurface = eglCreateWindowSurface(eglDisplay, myConfig,
diff --git a/opengl/tools/glgen/static/egl/EGLObjectHandle.java b/opengl/tools/glgen/static/egl/EGLObjectHandle.java
index e6e3976..f961eb7 100644
--- a/opengl/tools/glgen/static/egl/EGLObjectHandle.java
+++ b/opengl/tools/glgen/static/egl/EGLObjectHandle.java
@@ -24,36 +24,35 @@ package android.opengl;
public abstract class EGLObjectHandle {
private final long mHandle;
- // TODO Deprecate EGLObjectHandle(int) method
+ /**
+ * @deprecated Use {@link #EGLObjectHandle(long)} instead. Handles
+ * on 64 bit platforms will be wider than java ints.
+ */
+ @Deprecated
protected EGLObjectHandle(int handle) {
mHandle = handle;
}
- // TODO Unhide the EGLObjectHandle(long) method
- /**
- * {@hide}
- */
protected EGLObjectHandle(long handle) {
mHandle = handle;
}
- // TODO Deprecate getHandle() method in favor of getNativeHandle()
/**
- * Returns the native handle of the wrapped EGL object. This handle can be
- * cast to the corresponding native type on the native side.
- *
- * For example, EGLDisplay dpy = (EGLDisplay)handle;
- *
- * @return the native handle of the wrapped EGL object.
+ * @deprecated Use {@link #getNativeHandle()} instead. Handles on
+ * 64 bit platforms will be wider than java ints.
*/
+ @Deprecated
public int getHandle() {
if ((mHandle & 0xffffffffL) != mHandle) {
throw new UnsupportedOperationException();
}
return (int)mHandle;
}
-
- // TODO Unhide getNativeHandle() method
/**
- * {@hide}
+ * Returns the native handle of the wrapped EGL object. This handle can be
+ * cast to the corresponding native type on the native side.
+ *
+ * For example, EGLDisplay dpy = (EGLDisplay)handle;
+ *
+ * @return the native handle of the wrapped EGL object.
*/
public long getNativeHandle() {
return mHandle;
diff --git a/opengl/tools/glgen/stubs/gles11/glGetActiveAttrib.java b/opengl/tools/glgen/stubs/gles11/glGetActiveAttrib.java
index bad2137..d66200f 100644
--- a/opengl/tools/glgen/stubs/gles11/glGetActiveAttrib.java
+++ b/opengl/tools/glgen/stubs/gles11/glGetActiveAttrib.java
@@ -16,6 +16,7 @@
// C function void glGetActiveAttrib ( GLuint program, GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, char *name )
+ /** @hide Method is broken, but used to be public (b/6006380) */
public static native void glGetActiveAttrib(
int program,
int index,
diff --git a/opengl/tools/glgen/stubs/gles11/glGetActiveUniform.java b/opengl/tools/glgen/stubs/gles11/glGetActiveUniform.java
index 28aaa78..8c8d5a2 100644
--- a/opengl/tools/glgen/stubs/gles11/glGetActiveUniform.java
+++ b/opengl/tools/glgen/stubs/gles11/glGetActiveUniform.java
@@ -16,6 +16,7 @@
// C function void glGetActiveUniform ( GLuint program, GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, char *name )
+ /** @hide Method is broken, but used to be public (b/6006380) */
public static native void glGetActiveUniform(
int program,
int index,
diff --git a/opengl/tools/glgen/stubs/gles11/glGetShaderSource.java b/opengl/tools/glgen/stubs/gles11/glGetShaderSource.java
index 199d93a..afbaaca 100644
--- a/opengl/tools/glgen/stubs/gles11/glGetShaderSource.java
+++ b/opengl/tools/glgen/stubs/gles11/glGetShaderSource.java
@@ -11,6 +11,7 @@
// C function void glGetShaderSource ( GLuint shader, GLsizei bufsize, GLsizei *length, char *source )
+ /** @hide Method is broken, but used to be public (b/6006380) */
public static native void glGetShaderSource(
int shader,
int bufsize,
diff --git a/services/batteryservice/Android.mk b/services/batteryservice/Android.mk
index 0a29c36..9354b99 100644
--- a/services/batteryservice/Android.mk
+++ b/services/batteryservice/Android.mk
@@ -3,6 +3,7 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
BatteryProperties.cpp \
+ BatteryProperty.cpp \
IBatteryPropertiesListener.cpp \
IBatteryPropertiesRegistrar.cpp
diff --git a/services/batteryservice/BatteryProperties.cpp b/services/batteryservice/BatteryProperties.cpp
index e4a42ed..ab636a9 100644
--- a/services/batteryservice/BatteryProperties.cpp
+++ b/services/batteryservice/BatteryProperties.cpp
@@ -38,8 +38,6 @@ status_t BatteryProperties::readFromParcel(Parcel* p) {
batteryPresent = p->readInt32() == 1 ? true : false;
batteryLevel = p->readInt32();
batteryVoltage = p->readInt32();
- batteryCurrentNow = p->readInt32();
- batteryChargeCounter = p->readInt32();
batteryTemperature = p->readInt32();
batteryTechnology = String8((p->readString16()).string());
return OK;
@@ -54,8 +52,6 @@ status_t BatteryProperties::writeToParcel(Parcel* p) const {
p->writeInt32(batteryPresent ? 1 : 0);
p->writeInt32(batteryLevel);
p->writeInt32(batteryVoltage);
- p->writeInt32(batteryCurrentNow);
- p->writeInt32(batteryChargeCounter);
p->writeInt32(batteryTemperature);
p->writeString16(String16(batteryTechnology));
return OK;
diff --git a/services/surfaceflinger/SurfaceTextureLayer.h b/services/batteryservice/BatteryProperty.cpp
index 5f5e4ef..6cbc896 100644
--- a/services/surfaceflinger/SurfaceTextureLayer.h
+++ b/services/batteryservice/BatteryProperty.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,33 +14,27 @@
* limitations under the License.
*/
-#ifndef ANDROID_SURFACE_TEXTURE_LAYER_H
-#define ANDROID_SURFACE_TEXTURE_LAYER_H
-
-#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
-
+#include <batteryservice/BatteryService.h>
+#include <binder/Parcel.h>
#include <utils/Errors.h>
-#include <gui/BufferQueue.h>
namespace android {
-// ---------------------------------------------------------------------------
-
-class Layer;
-class SurfaceFlinger;
/*
- * This is a thin wrapper around BufferQueue, used by the Layer class.
+ * Parcel read/write code must be kept in sync with
+ * frameworks/base/core/java/android/os/BatteryProperty.java
*/
-class SurfaceTextureLayer : public BufferQueue {
- sp<SurfaceFlinger> flinger;
-public:
- SurfaceTextureLayer(const sp<SurfaceFlinger>& flinger);
- virtual ~SurfaceTextureLayer();
-};
-// ---------------------------------------------------------------------------
-}; // namespace android
+status_t BatteryProperty::readFromParcel(Parcel* p) {
+ valueInt = p->readInt32();
+ return OK;
+}
+
+status_t BatteryProperty::writeToParcel(Parcel* p) const {
+ p->writeInt32(valueInt);
+ return OK;
+}
-#endif // ANDROID_SURFACE_TEXTURE_LAYER_H
+}; // namespace android
diff --git a/services/batteryservice/IBatteryPropertiesRegistrar.cpp b/services/batteryservice/IBatteryPropertiesRegistrar.cpp
index 6c2d2a5..296bfab 100644
--- a/services/batteryservice/IBatteryPropertiesRegistrar.cpp
+++ b/services/batteryservice/IBatteryPropertiesRegistrar.cpp
@@ -44,6 +44,22 @@ public:
data.writeStrongBinder(listener->asBinder());
remote()->transact(UNREGISTER_LISTENER, data, NULL);
}
+
+ status_t getProperty(int id, struct BatteryProperty *val) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IBatteryPropertiesRegistrar::getInterfaceDescriptor());
+ data.writeInt32(id);
+ remote()->transact(GET_PROPERTY, data, &reply);
+ int32_t ret = reply.readExceptionCode();
+ if (ret != 0) {
+ return ret;
+ }
+ ret = reply.readInt32();
+ int parcelpresent = reply.readInt32();
+ if (parcelpresent)
+ val->readFromParcel(&reply);
+ return ret;
+ }
};
IMPLEMENT_META_INTERFACE(BatteryPropertiesRegistrar, "android.os.IBatteryPropertiesRegistrar");
@@ -69,6 +85,18 @@ status_t BnBatteryPropertiesRegistrar::onTransact(uint32_t code,
unregisterListener(listener);
return OK;
}
+
+ case GET_PROPERTY: {
+ CHECK_INTERFACE(IBatteryPropertiesRegistrar, data, reply);
+ int id = data.readInt32();
+ struct BatteryProperty val;
+ status_t result = getProperty(id, &val);
+ reply->writeNoException();
+ reply->writeInt32(result);
+ reply->writeInt32(1);
+ val.writeToParcel(reply);
+ return OK;
+ }
}
return BBinder::onTransact(code, data, reply, flags);
};
diff --git a/services/inputflinger/Android.mk b/services/inputflinger/Android.mk
new file mode 100644
index 0000000..574c14e
--- /dev/null
+++ b/services/inputflinger/Android.mk
@@ -0,0 +1,63 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ EventHub.cpp \
+ InputApplication.cpp \
+ InputDispatcher.cpp \
+ InputListener.cpp \
+ InputManager.cpp \
+ InputReader.cpp \
+ InputWindow.cpp \
+ InputFlinger.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libcutils \
+ libinput \
+ liblog \
+ libutils \
+ libui \
+ libhardware_legacy
+
+
+# TODO: Move inputflinger to its own process and mark it hidden
+#LOCAL_CFLAGS += -fvisibility=hidden
+
+LOCAL_CFLAGS += -Wno-unused-parameter
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+
+LOCAL_MODULE := libinputflinger
+
+include $(BUILD_SHARED_LIBRARY)
+
+########################################################################
+# build input flinger executable
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ main.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libinputflinger \
+ libutils
+
+LOCAL_MODULE := inputflinger
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/inputflinger/EventHub.cpp b/services/inputflinger/EventHub.cpp
new file mode 100644
index 0000000..ac73c1f
--- /dev/null
+++ b/services/inputflinger/EventHub.cpp
@@ -0,0 +1,1666 @@
+/*
+ * 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 <hardware_legacy/power.h>
+
+#include <cutils/properties.h>
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <utils/threads.h>
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <input/KeyLayoutMap.h>
+#include <input/KeyCharacterMap.h>
+#include <input/VirtualKeyMap.h>
+
+#include <string.h>
+#include <stdint.h>
+#include <dirent.h>
+
+#include <sys/inotify.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/limits.h>
+#include <sys/sha1.h>
+#include <sys/utsname.h>
+
+/* this macro is used to tell if "bit" is set in "array"
+ * it selects a byte from the array, and does a boolean AND
+ * operation with a byte that only has the relevant bit set.
+ * eg. to check for the 12th bit, we do (array[1] & 1<<4)
+ */
+#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
+
+/* this macro computes the number of bytes needed to represent a bit array of the specified size */
+#define sizeof_bit_array(bits) ((bits + 7) / 8)
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+
+namespace android {
+
+static const char *WAKE_LOCK_ID = "KeyEvents";
+static const char *DEVICE_PATH = "/dev/input";
+
+/* return the larger integer */
+static inline int max(int v1, int v2)
+{
+ return (v1 > v2) ? v1 : v2;
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static String8 sha1(const String8& in) {
+ SHA1_CTX ctx;
+ SHA1Init(&ctx);
+ SHA1Update(&ctx, reinterpret_cast<const u_char*>(in.string()), in.size());
+ u_char digest[SHA1_DIGEST_LENGTH];
+ SHA1Final(digest, &ctx);
+
+ String8 out;
+ for (size_t i = 0; i < SHA1_DIGEST_LENGTH; i++) {
+ out.appendFormat("%02x", digest[i]);
+ }
+ return out;
+}
+
+static void getLinuxRelease(int* major, int* minor) {
+ struct utsname info;
+ if (uname(&info) || sscanf(info.release, "%d.%d", major, minor) <= 0) {
+ *major = 0, *minor = 0;
+ ALOGE("Could not get linux version: %s", strerror(errno));
+ }
+}
+
+// --- Global Functions ---
+
+uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses) {
+ // Touch devices get dibs on touch-related axes.
+ if (deviceClasses & INPUT_DEVICE_CLASS_TOUCH) {
+ switch (axis) {
+ case ABS_X:
+ case ABS_Y:
+ case ABS_PRESSURE:
+ case ABS_TOOL_WIDTH:
+ case ABS_DISTANCE:
+ case ABS_TILT_X:
+ case ABS_TILT_Y:
+ case ABS_MT_SLOT:
+ case ABS_MT_TOUCH_MAJOR:
+ case ABS_MT_TOUCH_MINOR:
+ case ABS_MT_WIDTH_MAJOR:
+ case ABS_MT_WIDTH_MINOR:
+ case ABS_MT_ORIENTATION:
+ case ABS_MT_POSITION_X:
+ case ABS_MT_POSITION_Y:
+ case ABS_MT_TOOL_TYPE:
+ case ABS_MT_BLOB_ID:
+ case ABS_MT_TRACKING_ID:
+ case ABS_MT_PRESSURE:
+ case ABS_MT_DISTANCE:
+ return INPUT_DEVICE_CLASS_TOUCH;
+ }
+ }
+
+ // Joystick devices get the rest.
+ return deviceClasses & INPUT_DEVICE_CLASS_JOYSTICK;
+}
+
+// --- EventHub::Device ---
+
+EventHub::Device::Device(int fd, int32_t id, const String8& path,
+ const InputDeviceIdentifier& identifier) :
+ next(NULL),
+ fd(fd), id(id), path(path), identifier(identifier),
+ classes(0), configuration(NULL), virtualKeyMap(NULL),
+ ffEffectPlaying(false), ffEffectId(-1), controllerNumber(0),
+ timestampOverrideSec(0), timestampOverrideUsec(0) {
+ memset(keyBitmask, 0, sizeof(keyBitmask));
+ memset(absBitmask, 0, sizeof(absBitmask));
+ memset(relBitmask, 0, sizeof(relBitmask));
+ memset(swBitmask, 0, sizeof(swBitmask));
+ memset(ledBitmask, 0, sizeof(ledBitmask));
+ memset(ffBitmask, 0, sizeof(ffBitmask));
+ memset(propBitmask, 0, sizeof(propBitmask));
+}
+
+EventHub::Device::~Device() {
+ close();
+ delete configuration;
+ delete virtualKeyMap;
+}
+
+void EventHub::Device::close() {
+ if (fd >= 0) {
+ ::close(fd);
+ fd = -1;
+ }
+}
+
+
+// --- EventHub ---
+
+const uint32_t EventHub::EPOLL_ID_INOTIFY;
+const uint32_t EventHub::EPOLL_ID_WAKE;
+const int EventHub::EPOLL_SIZE_HINT;
+const int EventHub::EPOLL_MAX_EVENTS;
+
+EventHub::EventHub(void) :
+ mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
+ mOpeningDevices(0), mClosingDevices(0),
+ mNeedToSendFinishedDeviceScan(false),
+ mNeedToReopenDevices(false), mNeedToScanDevices(true),
+ mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+
+ mEpollFd = epoll_create(EPOLL_SIZE_HINT);
+ LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
+
+ mINotifyFd = inotify_init();
+ int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
+ LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s. errno=%d",
+ DEVICE_PATH, errno);
+
+ struct epoll_event eventItem;
+ memset(&eventItem, 0, sizeof(eventItem));
+ eventItem.events = EPOLLIN;
+ eventItem.data.u32 = EPOLL_ID_INOTIFY;
+ result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
+
+ int wakeFds[2];
+ result = pipe(wakeFds);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
+
+ mWakeReadPipeFd = wakeFds[0];
+ mWakeWritePipeFd = wakeFds[1];
+
+ result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
+ errno);
+
+ result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
+ errno);
+
+ eventItem.data.u32 = EPOLL_ID_WAKE;
+ result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
+ LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d",
+ errno);
+
+ int major, minor;
+ getLinuxRelease(&major, &minor);
+ // EPOLLWAKEUP was introduced in kernel 3.5
+ mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
+}
+
+EventHub::~EventHub(void) {
+ closeAllDevicesLocked();
+
+ while (mClosingDevices) {
+ Device* device = mClosingDevices;
+ mClosingDevices = device->next;
+ delete device;
+ }
+
+ ::close(mEpollFd);
+ ::close(mINotifyFd);
+ ::close(mWakeReadPipeFd);
+ ::close(mWakeWritePipeFd);
+
+ release_wake_lock(WAKE_LOCK_ID);
+}
+
+InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return InputDeviceIdentifier();
+ return device->identifier;
+}
+
+uint32_t EventHub::getDeviceClasses(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return 0;
+ return device->classes;
+}
+
+int32_t EventHub::getDeviceControllerNumber(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device == NULL) return 0;
+ return device->controllerNumber;
+}
+
+void EventHub::getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->configuration) {
+ *outConfiguration = *device->configuration;
+ } else {
+ outConfiguration->clear();
+ }
+}
+
+status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const {
+ outAxisInfo->clear();
+
+ if (axis >= 0 && axis <= ABS_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) {
+ struct input_absinfo info;
+ if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
+ ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
+ axis, device->identifier.name.string(), device->fd, errno);
+ return -errno;
+ }
+
+ if (info.minimum != info.maximum) {
+ outAxisInfo->valid = true;
+ outAxisInfo->minValue = info.minimum;
+ outAxisInfo->maxValue = info.maximum;
+ outAxisInfo->flat = info.flat;
+ outAxisInfo->fuzz = info.fuzz;
+ outAxisInfo->resolution = info.resolution;
+ }
+ return OK;
+ }
+ }
+ return -1;
+}
+
+bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
+ if (axis >= 0 && axis <= REL_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ return test_bit(axis, device->relBitmask);
+ }
+ }
+ return false;
+}
+
+bool EventHub::hasInputProperty(int32_t deviceId, int property) const {
+ if (property >= 0 && property <= INPUT_PROP_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ return test_bit(property, device->propBitmask);
+ }
+ }
+ return false;
+}
+
+int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+ if (scanCode >= 0 && scanCode <= KEY_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(scanCode, device->keyBitmask)) {
+ uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)];
+ memset(keyState, 0, sizeof(keyState));
+ if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) {
+ return test_bit(scanCode, keyState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && device->keyMap.haveKeyLayout()) {
+ Vector<int32_t> scanCodes;
+ device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode, &scanCodes);
+ if (scanCodes.size() != 0) {
+ uint8_t keyState[sizeof_bit_array(KEY_MAX + 1)];
+ memset(keyState, 0, sizeof(keyState));
+ if (ioctl(device->fd, EVIOCGKEY(sizeof(keyState)), keyState) >= 0) {
+ for (size_t i = 0; i < scanCodes.size(); i++) {
+ int32_t sc = scanCodes.itemAt(i);
+ if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, keyState)) {
+ return AKEY_STATE_DOWN;
+ }
+ }
+ return AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const {
+ if (sw >= 0 && sw <= SW_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(sw, device->swBitmask)) {
+ uint8_t swState[sizeof_bit_array(SW_MAX + 1)];
+ memset(swState, 0, sizeof(swState));
+ if (ioctl(device->fd, EVIOCGSW(sizeof(swState)), swState) >= 0) {
+ return test_bit(sw, swState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
+ }
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+}
+
+status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
+ *outValue = 0;
+
+ if (axis >= 0 && axis <= ABS_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual() && test_bit(axis, device->absBitmask)) {
+ struct input_absinfo info;
+ if(ioctl(device->fd, EVIOCGABS(axis), &info)) {
+ ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
+ axis, device->identifier.name.string(), device->fd, errno);
+ return -errno;
+ }
+
+ *outValue = info.value;
+ return OK;
+ }
+ }
+ return -1;
+}
+
+bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->keyMap.haveKeyLayout()) {
+ Vector<int32_t> scanCodes;
+ for (size_t codeIndex = 0; codeIndex < numCodes; codeIndex++) {
+ scanCodes.clear();
+
+ status_t err = device->keyMap.keyLayoutMap->findScanCodesForKey(
+ keyCodes[codeIndex], &scanCodes);
+ if (! err) {
+ // check the possible scan codes identified by the layout map against the
+ // map of codes actually emitted by the driver
+ for (size_t sc = 0; sc < scanCodes.size(); sc++) {
+ if (test_bit(scanCodes[sc], device->keyBitmask)) {
+ outFlags[codeIndex] = 1;
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+
+ if (device) {
+ // Check the key character map first.
+ sp<KeyCharacterMap> kcm = device->getKeyCharacterMap();
+ if (kcm != NULL) {
+ if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
+ *outFlags = 0;
+ return NO_ERROR;
+ }
+ }
+
+ // Check the key layout next.
+ if (device->keyMap.haveKeyLayout()) {
+ if (!device->keyMap.keyLayoutMap->mapKey(
+ scanCode, usageCode, outKeycode, outFlags)) {
+ return NO_ERROR;
+ }
+ }
+ }
+
+ *outKeycode = 0;
+ *outFlags = 0;
+ return NAME_NOT_FOUND;
+}
+
+status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+
+ if (device && device->keyMap.haveKeyLayout()) {
+ status_t err = device->keyMap.keyLayoutMap->mapAxis(scanCode, outAxisInfo);
+ if (err == NO_ERROR) {
+ return NO_ERROR;
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+void EventHub::setExcludedDevices(const Vector<String8>& devices) {
+ AutoMutex _l(mLock);
+
+ mExcludedDevices = devices;
+}
+
+bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && scanCode >= 0 && scanCode <= KEY_MAX) {
+ if (test_bit(scanCode, device->keyBitmask)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool EventHub::hasLed(int32_t deviceId, int32_t led) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ int32_t sc;
+ if (device && mapLed(device, led, &sc) == NO_ERROR) {
+ if (test_bit(sc, device->ledBitmask)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ setLedStateLocked(device, led, on);
+}
+
+void EventHub::setLedStateLocked(Device* device, int32_t led, bool on) {
+ int32_t sc;
+ if (device && !device->isVirtual() && mapLed(device, led, &sc) != NAME_NOT_FOUND) {
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_LED;
+ ev.code = sc;
+ ev.value = on ? 1 : 0;
+
+ ssize_t nWrite;
+ do {
+ nWrite = write(device->fd, &ev, sizeof(struct input_event));
+ } while (nWrite == -1 && errno == EINTR);
+ }
+}
+
+void EventHub::getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const {
+ outVirtualKeys.clear();
+
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->virtualKeyMap) {
+ outVirtualKeys.appendVector(device->virtualKeyMap->getVirtualKeys());
+ }
+}
+
+sp<KeyCharacterMap> EventHub::getKeyCharacterMap(int32_t deviceId) const {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ return device->getKeyCharacterMap();
+ }
+ return NULL;
+}
+
+bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId,
+ const sp<KeyCharacterMap>& map) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device) {
+ if (map != device->overlayKeyMap) {
+ device->overlayKeyMap = map;
+ device->combinedKeyMap = KeyCharacterMap::combine(
+ device->keyMap.keyCharacterMap, map);
+ return true;
+ }
+ }
+ return false;
+}
+
+static String8 generateDescriptor(InputDeviceIdentifier& identifier) {
+ String8 rawDescriptor;
+ rawDescriptor.appendFormat(":%04x:%04x:", identifier.vendor,
+ identifier.product);
+ // TODO add handling for USB devices to not uniqueify kbs that show up twice
+ if (!identifier.uniqueId.isEmpty()) {
+ rawDescriptor.append("uniqueId:");
+ rawDescriptor.append(identifier.uniqueId);
+ } else if (identifier.nonce != 0) {
+ rawDescriptor.appendFormat("nonce:%04x", identifier.nonce);
+ }
+
+ if (identifier.vendor == 0 && identifier.product == 0) {
+ // If we don't know the vendor and product id, then the device is probably
+ // built-in so we need to rely on other information to uniquely identify
+ // the input device. Usually we try to avoid relying on the device name or
+ // location but for built-in input device, they are unlikely to ever change.
+ if (!identifier.name.isEmpty()) {
+ rawDescriptor.append("name:");
+ rawDescriptor.append(identifier.name);
+ } else if (!identifier.location.isEmpty()) {
+ rawDescriptor.append("location:");
+ rawDescriptor.append(identifier.location);
+ }
+ }
+ identifier.descriptor = sha1(rawDescriptor);
+ return rawDescriptor;
+}
+
+void EventHub::assignDescriptorLocked(InputDeviceIdentifier& identifier) {
+ // Compute a device descriptor that uniquely identifies the device.
+ // The descriptor is assumed to be a stable identifier. Its value should not
+ // change between reboots, reconnections, firmware updates or new releases
+ // of Android. In practice we sometimes get devices that cannot be uniquely
+ // identified. In this case we enforce uniqueness between connected devices.
+ // Ideally, we also want the descriptor to be short and relatively opaque.
+
+ identifier.nonce = 0;
+ String8 rawDescriptor = generateDescriptor(identifier);
+ if (identifier.uniqueId.isEmpty()) {
+ // If it didn't have a unique id check for conflicts and enforce
+ // uniqueness if necessary.
+ while(getDeviceByDescriptorLocked(identifier.descriptor) != NULL) {
+ identifier.nonce++;
+ rawDescriptor = generateDescriptor(identifier);
+ }
+ }
+ ALOGV("Created descriptor: raw=%s, cooked=%s", rawDescriptor.string(),
+ identifier.descriptor.string());
+}
+
+void EventHub::vibrate(int32_t deviceId, nsecs_t duration) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ ff_effect effect;
+ memset(&effect, 0, sizeof(effect));
+ effect.type = FF_RUMBLE;
+ effect.id = device->ffEffectId;
+ effect.u.rumble.strong_magnitude = 0xc000;
+ effect.u.rumble.weak_magnitude = 0xc000;
+ effect.replay.length = (duration + 999999LL) / 1000000LL;
+ effect.replay.delay = 0;
+ if (ioctl(device->fd, EVIOCSFF, &effect)) {
+ ALOGW("Could not upload force feedback effect to device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectId = effect.id;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 1;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not start force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectPlaying = true;
+ }
+}
+
+void EventHub::cancelVibrate(int32_t deviceId) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ if (device->ffEffectPlaying) {
+ device->ffEffectPlaying = false;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 0;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not stop force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ }
+ }
+}
+
+EventHub::Device* EventHub::getDeviceByDescriptorLocked(String8& descriptor) const {
+ size_t size = mDevices.size();
+ for (size_t i = 0; i < size; i++) {
+ Device* device = mDevices.valueAt(i);
+ if (descriptor.compare(device->identifier.descriptor) == 0) {
+ return device;
+ }
+ }
+ return NULL;
+}
+
+EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
+ if (deviceId == BUILT_IN_KEYBOARD_ID) {
+ deviceId = mBuiltInKeyboardId;
+ }
+ ssize_t index = mDevices.indexOfKey(deviceId);
+ return index >= 0 ? mDevices.valueAt(index) : NULL;
+}
+
+EventHub::Device* EventHub::getDeviceByPathLocked(const char* devicePath) const {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ Device* device = mDevices.valueAt(i);
+ if (device->path == devicePath) {
+ return device;
+ }
+ }
+ return NULL;
+}
+
+size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
+ ALOG_ASSERT(bufferSize >= 1);
+
+ AutoMutex _l(mLock);
+
+ struct input_event readBuffer[bufferSize];
+
+ RawEvent* event = buffer;
+ size_t capacity = bufferSize;
+ bool awoken = false;
+ for (;;) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ // Reopen input devices if needed.
+ if (mNeedToReopenDevices) {
+ mNeedToReopenDevices = false;
+
+ ALOGI("Reopening all input devices due to a configuration change.");
+
+ closeAllDevicesLocked();
+ mNeedToScanDevices = true;
+ break; // return to the caller before we actually rescan
+ }
+
+ // Report any devices that had last been added/removed.
+ while (mClosingDevices) {
+ Device* device = mClosingDevices;
+ ALOGV("Reporting device closed: id=%d, name=%s\n",
+ device->id, device->path.string());
+ mClosingDevices = device->next;
+ event->when = now;
+ event->deviceId = device->id == mBuiltInKeyboardId ? BUILT_IN_KEYBOARD_ID : device->id;
+ event->type = DEVICE_REMOVED;
+ event += 1;
+ delete device;
+ mNeedToSendFinishedDeviceScan = true;
+ if (--capacity == 0) {
+ break;
+ }
+ }
+
+ if (mNeedToScanDevices) {
+ mNeedToScanDevices = false;
+ scanDevicesLocked();
+ mNeedToSendFinishedDeviceScan = true;
+ }
+
+ while (mOpeningDevices != NULL) {
+ Device* device = mOpeningDevices;
+ ALOGV("Reporting device opened: id=%d, name=%s\n",
+ device->id, device->path.string());
+ mOpeningDevices = device->next;
+ event->when = now;
+ event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
+ event->type = DEVICE_ADDED;
+ event += 1;
+ mNeedToSendFinishedDeviceScan = true;
+ if (--capacity == 0) {
+ break;
+ }
+ }
+
+ if (mNeedToSendFinishedDeviceScan) {
+ mNeedToSendFinishedDeviceScan = false;
+ event->when = now;
+ event->type = FINISHED_DEVICE_SCAN;
+ event += 1;
+ if (--capacity == 0) {
+ break;
+ }
+ }
+
+ // Grab the next input event.
+ bool deviceChanged = false;
+ while (mPendingEventIndex < mPendingEventCount) {
+ const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
+ if (eventItem.data.u32 == EPOLL_ID_INOTIFY) {
+ if (eventItem.events & EPOLLIN) {
+ mPendingINotify = true;
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
+ }
+ continue;
+ }
+
+ if (eventItem.data.u32 == EPOLL_ID_WAKE) {
+ if (eventItem.events & EPOLLIN) {
+ ALOGV("awoken after wake()");
+ awoken = true;
+ char buffer[16];
+ ssize_t nRead;
+ do {
+ nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
+ } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.",
+ eventItem.events);
+ }
+ continue;
+ }
+
+ ssize_t deviceIndex = mDevices.indexOfKey(eventItem.data.u32);
+ if (deviceIndex < 0) {
+ ALOGW("Received unexpected epoll event 0x%08x for unknown device id %d.",
+ eventItem.events, eventItem.data.u32);
+ continue;
+ }
+
+ Device* device = mDevices.valueAt(deviceIndex);
+ if (eventItem.events & EPOLLIN) {
+ int32_t readSize = read(device->fd, readBuffer,
+ sizeof(struct input_event) * capacity);
+ if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
+ // Device was removed before INotify noticed.
+ ALOGW("could not get event, removed? (fd: %d size: %d bufferSize: %d "
+ "capacity: %zu errno: %d)\n",
+ device->fd, readSize, bufferSize, capacity, errno);
+ deviceChanged = true;
+ closeDeviceLocked(device);
+ } else if (readSize < 0) {
+ if (errno != EAGAIN && errno != EINTR) {
+ ALOGW("could not get event (errno=%d)", errno);
+ }
+ } else if ((readSize % sizeof(struct input_event)) != 0) {
+ ALOGE("could not get event (wrong size: %d)", readSize);
+ } else {
+ int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
+
+ size_t count = size_t(readSize) / sizeof(struct input_event);
+ for (size_t i = 0; i < count; i++) {
+ struct input_event& iev = readBuffer[i];
+ ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d",
+ device->path.string(),
+ (int) iev.time.tv_sec, (int) iev.time.tv_usec,
+ iev.type, iev.code, iev.value);
+
+ // Some input devices may have a better concept of the time
+ // when an input event was actually generated than the kernel
+ // which simply timestamps all events on entry to evdev.
+ // This is a custom Android extension of the input protocol
+ // mainly intended for use with uinput based device drivers.
+ if (iev.type == EV_MSC) {
+ if (iev.code == MSC_ANDROID_TIME_SEC) {
+ device->timestampOverrideSec = iev.value;
+ continue;
+ } else if (iev.code == MSC_ANDROID_TIME_USEC) {
+ device->timestampOverrideUsec = iev.value;
+ continue;
+ }
+ }
+ if (device->timestampOverrideSec || device->timestampOverrideUsec) {
+ iev.time.tv_sec = device->timestampOverrideSec;
+ iev.time.tv_usec = device->timestampOverrideUsec;
+ if (iev.type == EV_SYN && iev.code == SYN_REPORT) {
+ device->timestampOverrideSec = 0;
+ device->timestampOverrideUsec = 0;
+ }
+ ALOGV("applied override time %d.%06d",
+ int(iev.time.tv_sec), int(iev.time.tv_usec));
+ }
+
+#ifdef HAVE_POSIX_CLOCKS
+ // Use the time specified in the event instead of the current time
+ // so that downstream code can get more accurate estimates of
+ // event dispatch latency from the time the event is enqueued onto
+ // the evdev client buffer.
+ //
+ // The event's timestamp fortuitously uses the same monotonic clock
+ // time base as the rest of Android. The kernel event device driver
+ // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts().
+ // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere
+ // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a
+ // system call that also queries ktime_get_ts().
+ event->when = nsecs_t(iev.time.tv_sec) * 1000000000LL
+ + nsecs_t(iev.time.tv_usec) * 1000LL;
+ ALOGV("event time %lld, now %lld", event->when, now);
+
+ // Bug 7291243: Add a guard in case the kernel generates timestamps
+ // that appear to be far into the future because they were generated
+ // using the wrong clock source.
+ //
+ // This can happen because when the input device is initially opened
+ // it has a default clock source of CLOCK_REALTIME. Any input events
+ // enqueued right after the device is opened will have timestamps
+ // generated using CLOCK_REALTIME. We later set the clock source
+ // to CLOCK_MONOTONIC but it is already too late.
+ //
+ // Invalid input event timestamps can result in ANRs, crashes and
+ // and other issues that are hard to track down. We must not let them
+ // propagate through the system.
+ //
+ // Log a warning so that we notice the problem and recover gracefully.
+ if (event->when >= now + 10 * 1000000000LL) {
+ // Double-check. Time may have moved on.
+ nsecs_t time = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (event->when > time) {
+ ALOGW("An input event from %s has a timestamp that appears to "
+ "have been generated using the wrong clock source "
+ "(expected CLOCK_MONOTONIC): "
+ "event time %lld, current time %lld, call time %lld. "
+ "Using current time instead.",
+ device->path.string(), event->when, time, now);
+ event->when = time;
+ } else {
+ ALOGV("Event time is ok but failed the fast path and required "
+ "an extra call to systemTime: "
+ "event time %lld, current time %lld, call time %lld.",
+ event->when, time, now);
+ }
+ }
+#else
+ event->when = now;
+#endif
+ event->deviceId = deviceId;
+ event->type = iev.type;
+ event->code = iev.code;
+ event->value = iev.value;
+ event += 1;
+ capacity -= 1;
+ }
+ if (capacity == 0) {
+ // The result buffer is full. Reset the pending event index
+ // so we will try to read the device again on the next iteration.
+ mPendingEventIndex -= 1;
+ break;
+ }
+ }
+ } else if (eventItem.events & EPOLLHUP) {
+ ALOGI("Removing device %s due to epoll hang-up event.",
+ device->identifier.name.string());
+ deviceChanged = true;
+ closeDeviceLocked(device);
+ } else {
+ ALOGW("Received unexpected epoll event 0x%08x for device %s.",
+ eventItem.events, device->identifier.name.string());
+ }
+ }
+
+ // readNotify() will modify the list of devices so this must be done after
+ // processing all other events to ensure that we read all remaining events
+ // before closing the devices.
+ if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) {
+ mPendingINotify = false;
+ readNotifyLocked();
+ deviceChanged = true;
+ }
+
+ // Report added or removed devices immediately.
+ if (deviceChanged) {
+ continue;
+ }
+
+ // Return now if we have collected any events or if we were explicitly awoken.
+ if (event != buffer || awoken) {
+ break;
+ }
+
+ // Poll for events. Mind the wake lock dance!
+ // We hold a wake lock at all times except during epoll_wait(). This works due to some
+ // subtle choreography. When a device driver has pending (unread) events, it acquires
+ // a kernel wake lock. However, once the last pending event has been read, the device
+ // driver will release the kernel wake lock. To prevent the system from going to sleep
+ // when this happens, the EventHub holds onto its own user wake lock while the client
+ // is processing events. Thus the system can only sleep if there are no events
+ // pending or currently being processed.
+ //
+ // The timeout is advisory only. If the device is asleep, it will not wake just to
+ // service the timeout.
+ mPendingEventIndex = 0;
+
+ mLock.unlock(); // release lock before poll, must be before release_wake_lock
+ release_wake_lock(WAKE_LOCK_ID);
+
+ int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
+
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
+ mLock.lock(); // reacquire lock after poll, must be after acquire_wake_lock
+
+ if (pollResult == 0) {
+ // Timed out.
+ mPendingEventCount = 0;
+ break;
+ }
+
+ if (pollResult < 0) {
+ // An error occurred.
+ mPendingEventCount = 0;
+
+ // Sleep after errors to avoid locking up the system.
+ // Hopefully the error is transient.
+ if (errno != EINTR) {
+ ALOGW("poll failed (errno=%d)\n", errno);
+ usleep(100000);
+ }
+ } else {
+ // Some events occurred.
+ mPendingEventCount = size_t(pollResult);
+ }
+ }
+
+ // All done, return the number of events we read.
+ return event - buffer;
+}
+
+void EventHub::wake() {
+ ALOGV("wake() called");
+
+ ssize_t nWrite;
+ do {
+ nWrite = write(mWakeWritePipeFd, "W", 1);
+ } while (nWrite == -1 && errno == EINTR);
+
+ if (nWrite != 1 && errno != EAGAIN) {
+ ALOGW("Could not write wake signal, errno=%d", errno);
+ }
+}
+
+void EventHub::scanDevicesLocked() {
+ status_t res = scanDirLocked(DEVICE_PATH);
+ if(res < 0) {
+ ALOGE("scan dir failed for %s\n", DEVICE_PATH);
+ }
+ if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
+ createVirtualKeyboardLocked();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+static bool containsNonZeroByte(const uint8_t* array, uint32_t startIndex, uint32_t endIndex) {
+ const uint8_t* end = array + endIndex;
+ array += startIndex;
+ while (array != end) {
+ if (*(array++) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static const int32_t GAMEPAD_KEYCODES[] = {
+ AKEYCODE_BUTTON_A, AKEYCODE_BUTTON_B, AKEYCODE_BUTTON_C,
+ AKEYCODE_BUTTON_X, AKEYCODE_BUTTON_Y, AKEYCODE_BUTTON_Z,
+ AKEYCODE_BUTTON_L1, AKEYCODE_BUTTON_R1,
+ AKEYCODE_BUTTON_L2, AKEYCODE_BUTTON_R2,
+ AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR,
+ AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE,
+};
+
+status_t EventHub::openDeviceLocked(const char *devicePath) {
+ char buffer[80];
+
+ ALOGV("Opening device: %s", devicePath);
+
+ int fd = open(devicePath, O_RDWR | O_CLOEXEC);
+ if(fd < 0) {
+ ALOGE("could not open %s, %s\n", devicePath, strerror(errno));
+ return -1;
+ }
+
+ InputDeviceIdentifier identifier;
+
+ // Get device name.
+ if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.name.setTo(buffer);
+ }
+
+ // Check to see if the device is on our excluded list
+ for (size_t i = 0; i < mExcludedDevices.size(); i++) {
+ const String8& item = mExcludedDevices.itemAt(i);
+ if (identifier.name == item) {
+ ALOGI("ignoring event id %s driver %s\n", devicePath, item.string());
+ close(fd);
+ return -1;
+ }
+ }
+
+ // Get device driver version.
+ int driverVersion;
+ if(ioctl(fd, EVIOCGVERSION, &driverVersion)) {
+ ALOGE("could not get driver version for %s, %s\n", devicePath, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ // Get device identifier.
+ struct input_id inputId;
+ if(ioctl(fd, EVIOCGID, &inputId)) {
+ ALOGE("could not get device input id for %s, %s\n", devicePath, strerror(errno));
+ close(fd);
+ return -1;
+ }
+ identifier.bus = inputId.bustype;
+ identifier.product = inputId.product;
+ identifier.vendor = inputId.vendor;
+ identifier.version = inputId.version;
+
+ // Get device physical location.
+ if(ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.location.setTo(buffer);
+ }
+
+ // Get device unique id.
+ if(ioctl(fd, EVIOCGUNIQ(sizeof(buffer) - 1), &buffer) < 1) {
+ //fprintf(stderr, "could not get idstring for %s, %s\n", devicePath, strerror(errno));
+ } else {
+ buffer[sizeof(buffer) - 1] = '\0';
+ identifier.uniqueId.setTo(buffer);
+ }
+
+ // Fill in the descriptor.
+ assignDescriptorLocked(identifier);
+
+ // Make file descriptor non-blocking for use with poll().
+ if (fcntl(fd, F_SETFL, O_NONBLOCK)) {
+ ALOGE("Error %d making device file descriptor non-blocking.", errno);
+ close(fd);
+ return -1;
+ }
+
+ // Allocate device. (The device object takes ownership of the fd at this point.)
+ int32_t deviceId = mNextDeviceId++;
+ Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
+
+ ALOGV("add device %d: %s\n", deviceId, devicePath);
+ ALOGV(" bus: %04x\n"
+ " vendor %04x\n"
+ " product %04x\n"
+ " version %04x\n",
+ identifier.bus, identifier.vendor, identifier.product, identifier.version);
+ ALOGV(" name: \"%s\"\n", identifier.name.string());
+ ALOGV(" location: \"%s\"\n", identifier.location.string());
+ ALOGV(" unique id: \"%s\"\n", identifier.uniqueId.string());
+ ALOGV(" descriptor: \"%s\"\n", identifier.descriptor.string());
+ ALOGV(" driver: v%d.%d.%d\n",
+ driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff);
+
+ // Load the configuration file for the device.
+ loadConfigurationLocked(device);
+
+ // Figure out the kinds of events the device reports.
+ ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
+ ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
+ ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
+ ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
+ ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
+ ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
+ ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
+
+ // See if this is a keyboard. Ignore everything in the button range except for
+ // joystick and gamepad buttons which are handled like keyboards for the most part.
+ bool haveKeyboardKeys = containsNonZeroByte(device->keyBitmask, 0, sizeof_bit_array(BTN_MISC))
+ || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(KEY_OK),
+ sizeof_bit_array(KEY_MAX + 1));
+ bool haveGamepadButtons = containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_MISC),
+ sizeof_bit_array(BTN_MOUSE))
+ || containsNonZeroByte(device->keyBitmask, sizeof_bit_array(BTN_JOYSTICK),
+ sizeof_bit_array(BTN_DIGI));
+ if (haveKeyboardKeys || haveGamepadButtons) {
+ device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+ }
+
+ // See if this is a cursor device such as a trackball or mouse.
+ if (test_bit(BTN_MOUSE, device->keyBitmask)
+ && test_bit(REL_X, device->relBitmask)
+ && test_bit(REL_Y, device->relBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_CURSOR;
+ }
+
+ // See if this is a touch pad.
+ // Is this a new modern multi-touch driver?
+ if (test_bit(ABS_MT_POSITION_X, device->absBitmask)
+ && test_bit(ABS_MT_POSITION_Y, device->absBitmask)) {
+ // Some joysticks such as the PS3 controller report axes that conflict
+ // with the ABS_MT range. Try to confirm that the device really is
+ // a touch screen.
+ if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) {
+ device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;
+ }
+ // Is this an old style single-touch driver?
+ } else if (test_bit(BTN_TOUCH, device->keyBitmask)
+ && test_bit(ABS_X, device->absBitmask)
+ && test_bit(ABS_Y, device->absBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_TOUCH;
+ }
+
+ // See if this device is a joystick.
+ // Assumes that joysticks always have gamepad buttons in order to distinguish them
+ // from other devices such as accelerometers that also have absolute axes.
+ if (haveGamepadButtons) {
+ uint32_t assumedClasses = device->classes | INPUT_DEVICE_CLASS_JOYSTICK;
+ for (int i = 0; i <= ABS_MAX; i++) {
+ if (test_bit(i, device->absBitmask)
+ && (getAbsAxisUsage(i, assumedClasses) & INPUT_DEVICE_CLASS_JOYSTICK)) {
+ device->classes = assumedClasses;
+ break;
+ }
+ }
+ }
+
+ // Check whether this device has switches.
+ for (int i = 0; i <= SW_MAX; i++) {
+ if (test_bit(i, device->swBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_SWITCH;
+ break;
+ }
+ }
+
+ // Check whether this device supports the vibrator.
+ if (test_bit(FF_RUMBLE, device->ffBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_VIBRATOR;
+ }
+
+ // Configure virtual keys.
+ if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) {
+ // Load the virtual keys for the touch screen, if any.
+ // We do this now so that we can make sure to load the keymap if necessary.
+ status_t status = loadVirtualKeyMapLocked(device);
+ if (!status) {
+ device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
+ }
+ }
+
+ // Load the key map.
+ // We need to do this for joysticks too because the key layout may specify axes.
+ status_t keyMapStatus = NAME_NOT_FOUND;
+ if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
+ // Load the keymap for the device.
+ keyMapStatus = loadKeyMapLocked(device);
+ }
+
+ // Configure the keyboard, gamepad or virtual keyboard.
+ if (device->classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+ // Register the keyboard as a built-in keyboard if it is eligible.
+ if (!keyMapStatus
+ && mBuiltInKeyboardId == NO_BUILT_IN_KEYBOARD
+ && isEligibleBuiltInKeyboard(device->identifier,
+ device->configuration, &device->keyMap)) {
+ mBuiltInKeyboardId = device->id;
+ }
+
+ // 'Q' key support = cheap test of whether this is an alpha-capable kbd
+ if (hasKeycodeLocked(device, AKEYCODE_Q)) {
+ device->classes |= INPUT_DEVICE_CLASS_ALPHAKEY;
+ }
+
+ // See if this device has a DPAD.
+ if (hasKeycodeLocked(device, AKEYCODE_DPAD_UP) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_DOWN) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_LEFT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_RIGHT) &&
+ hasKeycodeLocked(device, AKEYCODE_DPAD_CENTER)) {
+ device->classes |= INPUT_DEVICE_CLASS_DPAD;
+ }
+
+ // See if this device has a gamepad.
+ for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES)/sizeof(GAMEPAD_KEYCODES[0]); i++) {
+ if (hasKeycodeLocked(device, GAMEPAD_KEYCODES[i])) {
+ device->classes |= INPUT_DEVICE_CLASS_GAMEPAD;
+ break;
+ }
+ }
+
+ // Disable kernel key repeat since we handle it ourselves
+ unsigned int repeatRate[] = {0,0};
+ if (ioctl(fd, EVIOCSREP, repeatRate)) {
+ ALOGW("Unable to disable kernel key repeat for %s: %s", devicePath, strerror(errno));
+ }
+ }
+
+ // If the device isn't recognized as something we handle, don't monitor it.
+ if (device->classes == 0) {
+ ALOGV("Dropping device: id=%d, path='%s', name='%s'",
+ deviceId, devicePath, device->identifier.name.string());
+ delete device;
+ return -1;
+ }
+
+ // Determine whether the device is external or internal.
+ if (isExternalDeviceLocked(device)) {
+ device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;
+ }
+
+ if (device->classes & (INPUT_DEVICE_CLASS_JOYSTICK | INPUT_DEVICE_CLASS_DPAD)
+ && device->classes & INPUT_DEVICE_CLASS_GAMEPAD) {
+ device->controllerNumber = getNextControllerNumberLocked(device);
+ setLedForController(device);
+ }
+
+ // Register with epoll.
+ struct epoll_event eventItem;
+ memset(&eventItem, 0, sizeof(eventItem));
+ eventItem.events = mUsingEpollWakeup ? EPOLLIN : EPOLLIN | EPOLLWAKEUP;
+ 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;
+ }
+
+ String8 wakeMechanism("EPOLLWAKEUP");
+ if (!mUsingEpollWakeup) {
+#ifndef EVIOCSSUSPENDBLOCK
+ // uapi headers don't include EVIOCSSUSPENDBLOCK, and future kernels
+ // will use an epoll flag instead, so as long as we want to support
+ // this feature, we need to be prepared to define the ioctl ourselves.
+#define EVIOCSSUSPENDBLOCK _IOW('E', 0x91, int)
+#endif
+ if (ioctl(fd, EVIOCSSUSPENDBLOCK, 1)) {
+ wakeMechanism = "<none>";
+ } else {
+ wakeMechanism = "EVIOCSSUSPENDBLOCK";
+ }
+ }
+
+ // Tell the kernel that we want to use the monotonic clock for reporting timestamps
+ // associated with input events. This is important because the input system
+ // uses the timestamps extensively and assumes they were recorded using the monotonic
+ // clock.
+ //
+ // In older kernel, before Linux 3.4, there was no way to tell the kernel which
+ // clock to use to input event timestamps. The standard kernel behavior was to
+ // record a real time timestamp, which isn't what we want. Android kernels therefore
+ // contained a patch to the evdev_event() function in drivers/input/evdev.c to
+ // replace the call to do_gettimeofday() with ktime_get_ts() to cause the monotonic
+ // clock to be used instead of the real time clock.
+ //
+ // As of Linux 3.4, there is a new EVIOCSCLOCKID ioctl to set the desired clock.
+ // Therefore, we no longer require the Android-specific kernel patch described above
+ // as long as we make sure to set select the monotonic clock. We do that here.
+ int clockId = CLOCK_MONOTONIC;
+ bool usingClockIoctl = !ioctl(fd, EVIOCSCLOCKID, &clockId);
+
+ ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=0x%x, "
+ "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, "
+ "wakeMechanism=%s, usingClockIoctl=%s",
+ deviceId, fd, devicePath, device->identifier.name.string(),
+ device->classes,
+ device->configurationFile.string(),
+ device->keyMap.keyLayoutFile.string(),
+ device->keyMap.keyCharacterMapFile.string(),
+ toString(mBuiltInKeyboardId == deviceId),
+ wakeMechanism.string(), toString(usingClockIoctl));
+
+ addDeviceLocked(device);
+ return 0;
+}
+
+void EventHub::createVirtualKeyboardLocked() {
+ InputDeviceIdentifier identifier;
+ identifier.name = "Virtual";
+ identifier.uniqueId = "<virtual>";
+ assignDescriptorLocked(identifier);
+
+ Device* device = new Device(-1, VIRTUAL_KEYBOARD_ID, String8("<virtual>"), identifier);
+ device->classes = INPUT_DEVICE_CLASS_KEYBOARD
+ | INPUT_DEVICE_CLASS_ALPHAKEY
+ | INPUT_DEVICE_CLASS_DPAD
+ | INPUT_DEVICE_CLASS_VIRTUAL;
+ loadKeyMapLocked(device);
+ addDeviceLocked(device);
+}
+
+void EventHub::addDeviceLocked(Device* device) {
+ mDevices.add(device->id, device);
+ device->next = mOpeningDevices;
+ mOpeningDevices = device;
+}
+
+void EventHub::loadConfigurationLocked(Device* device) {
+ device->configurationFile = getInputDeviceConfigurationFilePathByDeviceIdentifier(
+ device->identifier, INPUT_DEVICE_CONFIGURATION_FILE_TYPE_CONFIGURATION);
+ if (device->configurationFile.isEmpty()) {
+ ALOGD("No input device configuration file found for device '%s'.",
+ device->identifier.name.string());
+ } else {
+ status_t status = PropertyMap::load(device->configurationFile,
+ &device->configuration);
+ if (status) {
+ ALOGE("Error loading input device configuration file for device '%s'. "
+ "Using default configuration.",
+ device->identifier.name.string());
+ }
+ }
+}
+
+status_t EventHub::loadVirtualKeyMapLocked(Device* device) {
+ // The virtual key map is supplied by the kernel as a system board property file.
+ String8 path;
+ path.append("/sys/board_properties/virtualkeys.");
+ path.append(device->identifier.name);
+ if (access(path.string(), R_OK)) {
+ return NAME_NOT_FOUND;
+ }
+ return VirtualKeyMap::load(path, &device->virtualKeyMap);
+}
+
+status_t EventHub::loadKeyMapLocked(Device* device) {
+ return device->keyMap.load(device->identifier, device->configuration);
+}
+
+bool EventHub::isExternalDeviceLocked(Device* device) {
+ if (device->configuration) {
+ bool value;
+ if (device->configuration->tryGetProperty(String8("device.internal"), value)) {
+ return !value;
+ }
+ }
+ return device->identifier.bus == BUS_USB || device->identifier.bus == BUS_BLUETOOTH;
+}
+
+int32_t EventHub::getNextControllerNumberLocked(Device* device) {
+ if (mControllerNumbers.isFull()) {
+ ALOGI("Maximum number of controllers reached, assigning controller number 0 to device %s",
+ device->identifier.name.string());
+ return 0;
+ }
+ // Since the controller number 0 is reserved for non-controllers, translate all numbers up by
+ // one
+ return static_cast<int32_t>(mControllerNumbers.markFirstUnmarkedBit() + 1);
+}
+
+void EventHub::releaseControllerNumberLocked(Device* device) {
+ int32_t num = device->controllerNumber;
+ device->controllerNumber= 0;
+ if (num == 0) {
+ return;
+ }
+ mControllerNumbers.clearBit(static_cast<uint32_t>(num - 1));
+}
+
+void EventHub::setLedForController(Device* device) {
+ for (int i = 0; i < MAX_CONTROLLER_LEDS; i++) {
+ setLedStateLocked(device, ALED_CONTROLLER_1 + i, device->controllerNumber == i + 1);
+ }
+}
+
+bool EventHub::hasKeycodeLocked(Device* device, int keycode) const {
+ if (!device->keyMap.haveKeyLayout() || !device->keyBitmask) {
+ return false;
+ }
+
+ Vector<int32_t> scanCodes;
+ device->keyMap.keyLayoutMap->findScanCodesForKey(keycode, &scanCodes);
+ const size_t N = scanCodes.size();
+ for (size_t i=0; i<N && i<=KEY_MAX; i++) {
+ int32_t sc = scanCodes.itemAt(i);
+ if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+status_t EventHub::mapLed(Device* device, int32_t led, int32_t* outScanCode) const {
+ if (!device->keyMap.haveKeyLayout() || !device->ledBitmask) {
+ return NAME_NOT_FOUND;
+ }
+
+ int32_t scanCode;
+ if(device->keyMap.keyLayoutMap->findScanCodeForLed(led, &scanCode) != NAME_NOT_FOUND) {
+ if(scanCode >= 0 && scanCode <= LED_MAX && test_bit(scanCode, device->ledBitmask)) {
+ *outScanCode = scanCode;
+ return NO_ERROR;
+ }
+ }
+ return NAME_NOT_FOUND;
+}
+
+status_t EventHub::closeDeviceByPathLocked(const char *devicePath) {
+ Device* device = getDeviceByPathLocked(devicePath);
+ if (device) {
+ closeDeviceLocked(device);
+ return 0;
+ }
+ ALOGV("Remove device: %s not found, device may already have been removed.", devicePath);
+ return -1;
+}
+
+void EventHub::closeAllDevicesLocked() {
+ while (mDevices.size() > 0) {
+ closeDeviceLocked(mDevices.valueAt(mDevices.size() - 1));
+ }
+}
+
+void EventHub::closeDeviceLocked(Device* device) {
+ ALOGI("Removed device: path=%s name=%s id=%d fd=%d classes=0x%x\n",
+ device->path.string(), device->identifier.name.string(), device->id,
+ device->fd, device->classes);
+
+ if (device->id == mBuiltInKeyboardId) {
+ ALOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this",
+ device->path.string(), mBuiltInKeyboardId);
+ mBuiltInKeyboardId = NO_BUILT_IN_KEYBOARD;
+ }
+
+ if (!device->isVirtual()) {
+ if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, device->fd, NULL)) {
+ ALOGW("Could not remove device fd from epoll instance. errno=%d", errno);
+ }
+ }
+
+ releaseControllerNumberLocked(device);
+
+ mDevices.removeItem(device->id);
+ device->close();
+
+ // Unlink for opening devices list if it is present.
+ Device* pred = NULL;
+ bool found = false;
+ for (Device* entry = mOpeningDevices; entry != NULL; ) {
+ if (entry == device) {
+ found = true;
+ break;
+ }
+ pred = entry;
+ entry = entry->next;
+ }
+ if (found) {
+ // Unlink the device from the opening devices list then delete it.
+ // We don't need to tell the client that the device was closed because
+ // it does not even know it was opened in the first place.
+ ALOGI("Device %s was immediately closed after opening.", device->path.string());
+ if (pred) {
+ pred->next = device->next;
+ } else {
+ mOpeningDevices = device->next;
+ }
+ delete device;
+ } else {
+ // Link into closing devices list.
+ // The device will be deleted later after we have informed the client.
+ device->next = mClosingDevices;
+ mClosingDevices = device;
+ }
+}
+
+status_t EventHub::readNotifyLocked() {
+ int res;
+ char devname[PATH_MAX];
+ char *filename;
+ char event_buf[512];
+ int event_size;
+ int event_pos = 0;
+ struct inotify_event *event;
+
+ ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd);
+ res = read(mINotifyFd, event_buf, sizeof(event_buf));
+ if(res < (int)sizeof(*event)) {
+ if(errno == EINTR)
+ return 0;
+ ALOGW("could not get event, %s\n", strerror(errno));
+ return -1;
+ }
+ //printf("got %d bytes of event information\n", res);
+
+ strcpy(devname, DEVICE_PATH);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+
+ while(res >= (int)sizeof(*event)) {
+ event = (struct inotify_event *)(event_buf + event_pos);
+ //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
+ if(event->len) {
+ strcpy(filename, event->name);
+ if(event->mask & IN_CREATE) {
+ openDeviceLocked(devname);
+ } else {
+ ALOGI("Removing device '%s' due to inotify event\n", devname);
+ closeDeviceByPathLocked(devname);
+ }
+ }
+ event_size = sizeof(*event) + event->len;
+ res -= event_size;
+ event_pos += event_size;
+ }
+ return 0;
+}
+
+status_t EventHub::scanDirLocked(const char *dirname)
+{
+ char devname[PATH_MAX];
+ char *filename;
+ DIR *dir;
+ struct dirent *de;
+ dir = opendir(dirname);
+ if(dir == NULL)
+ return -1;
+ strcpy(devname, dirname);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+ while((de = readdir(dir))) {
+ if(de->d_name[0] == '.' &&
+ (de->d_name[1] == '\0' ||
+ (de->d_name[1] == '.' && de->d_name[2] == '\0')))
+ continue;
+ strcpy(filename, de->d_name);
+ openDeviceLocked(devname);
+ }
+ closedir(dir);
+ return 0;
+}
+
+void EventHub::requestReopenDevices() {
+ ALOGV("requestReopenDevices() called");
+
+ AutoMutex _l(mLock);
+ mNeedToReopenDevices = true;
+}
+
+void EventHub::dump(String8& dump) {
+ dump.append("Event Hub State:\n");
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ dump.appendFormat(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId);
+
+ dump.append(INDENT "Devices:\n");
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ const Device* device = mDevices.valueAt(i);
+ if (mBuiltInKeyboardId == device->id) {
+ dump.appendFormat(INDENT2 "%d: %s (aka device 0 - built-in keyboard)\n",
+ device->id, device->identifier.name.string());
+ } else {
+ dump.appendFormat(INDENT2 "%d: %s\n", device->id,
+ device->identifier.name.string());
+ }
+ dump.appendFormat(INDENT3 "Classes: 0x%08x\n", device->classes);
+ dump.appendFormat(INDENT3 "Path: %s\n", device->path.string());
+ dump.appendFormat(INDENT3 "Descriptor: %s\n", device->identifier.descriptor.string());
+ dump.appendFormat(INDENT3 "Location: %s\n", device->identifier.location.string());
+ dump.appendFormat(INDENT3 "ControllerNumber: %d\n", device->controllerNumber);
+ dump.appendFormat(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.string());
+ dump.appendFormat(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, "
+ "product=0x%04x, version=0x%04x\n",
+ device->identifier.bus, device->identifier.vendor,
+ device->identifier.product, device->identifier.version);
+ dump.appendFormat(INDENT3 "KeyLayoutFile: %s\n",
+ device->keyMap.keyLayoutFile.string());
+ dump.appendFormat(INDENT3 "KeyCharacterMapFile: %s\n",
+ device->keyMap.keyCharacterMapFile.string());
+ dump.appendFormat(INDENT3 "ConfigurationFile: %s\n",
+ device->configurationFile.string());
+ dump.appendFormat(INDENT3 "HaveKeyboardLayoutOverlay: %s\n",
+ toString(device->overlayKeyMap != NULL));
+ }
+ } // release lock
+}
+
+void EventHub::monitor() {
+ // Acquire and release the lock to ensure that the event hub has not deadlocked.
+ mLock.lock();
+ mLock.unlock();
+}
+
+
+}; // namespace android
diff --git a/services/inputflinger/EventHub.h b/services/inputflinger/EventHub.h
new file mode 100644
index 0000000..20179ae
--- /dev/null
+++ b/services/inputflinger/EventHub.h
@@ -0,0 +1,457 @@
+/*
+ * 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/Input.h>
+#include <input/InputDevice.h>
+#include <input/Keyboard.h>
+#include <input/KeyLayoutMap.h>
+#include <input/KeyCharacterMap.h>
+#include <input/VirtualKeyMap.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <utils/Log.h>
+#include <utils/threads.h>
+#include <utils/List.h>
+#include <utils/Errors.h>
+#include <utils/PropertyMap.h>
+#include <utils/Vector.h>
+#include <utils/KeyedVector.h>
+#include <utils/BitSet.h>
+
+#include <linux/input.h>
+#include <sys/epoll.h>
+
+/* Convenience constants. */
+
+#define BTN_FIRST 0x100 // first button code
+#define BTN_LAST 0x15f // last button code
+
+/*
+ * These constants are used privately in Android to pass raw timestamps
+ * through evdev from uinput device drivers because there is currently no
+ * other way to transfer this information. The evdev driver automatically
+ * timestamps all input events with the time they were posted and clobbers
+ * whatever information was passed in.
+ *
+ * For the purposes of this hack, the timestamp is specified in the
+ * CLOCK_MONOTONIC timebase and is split into two EV_MSC events specifying
+ * seconds and microseconds.
+ */
+#define MSC_ANDROID_TIME_SEC 0x6
+#define MSC_ANDROID_TIME_USEC 0x7
+
+namespace android {
+
+enum {
+ // Device id of a special "virtual" keyboard that is always present.
+ VIRTUAL_KEYBOARD_ID = -1,
+ // Device id of the "built-in" keyboard if there is one.
+ BUILT_IN_KEYBOARD_ID = 0,
+};
+
+/*
+ * A raw event as retrieved from the EventHub.
+ */
+struct RawEvent {
+ nsecs_t when;
+ int32_t deviceId;
+ int32_t type;
+ int32_t code;
+ int32_t value;
+};
+
+/* Describes an absolute axis. */
+struct RawAbsoluteAxisInfo {
+ bool valid; // true if the information is valid, false otherwise
+
+ int32_t minValue; // minimum value
+ int32_t maxValue; // maximum value
+ int32_t flat; // center flat position, eg. flat == 8 means center is between -8 and 8
+ int32_t fuzz; // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
+ int32_t resolution; // resolution in units per mm or radians per mm
+
+ inline void clear() {
+ valid = false;
+ minValue = 0;
+ maxValue = 0;
+ flat = 0;
+ fuzz = 0;
+ resolution = 0;
+ }
+};
+
+/*
+ * Input device classes.
+ */
+enum {
+ /* The input device is a keyboard or has buttons. */
+ INPUT_DEVICE_CLASS_KEYBOARD = 0x00000001,
+
+ /* The input device is an alpha-numeric keyboard (not just a dial pad). */
+ INPUT_DEVICE_CLASS_ALPHAKEY = 0x00000002,
+
+ /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */
+ INPUT_DEVICE_CLASS_TOUCH = 0x00000004,
+
+ /* The input device is a cursor device such as a trackball or mouse. */
+ INPUT_DEVICE_CLASS_CURSOR = 0x00000008,
+
+ /* The input device is a multi-touch touchscreen. */
+ INPUT_DEVICE_CLASS_TOUCH_MT = 0x00000010,
+
+ /* The input device is a directional pad (implies keyboard, has DPAD keys). */
+ INPUT_DEVICE_CLASS_DPAD = 0x00000020,
+
+ /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
+ INPUT_DEVICE_CLASS_GAMEPAD = 0x00000040,
+
+ /* The input device has switches. */
+ INPUT_DEVICE_CLASS_SWITCH = 0x00000080,
+
+ /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
+ INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100,
+
+ /* The input device has a vibrator (supports FF_RUMBLE). */
+ INPUT_DEVICE_CLASS_VIBRATOR = 0x00000200,
+
+ /* The input device is virtual (not a real device, not part of UI configuration). */
+ INPUT_DEVICE_CLASS_VIRTUAL = 0x40000000,
+
+ /* The input device is external (not built-in). */
+ INPUT_DEVICE_CLASS_EXTERNAL = 0x80000000,
+};
+
+/*
+ * Gets the class that owns an axis, in cases where multiple classes might claim
+ * the same axis for different purposes.
+ */
+extern uint32_t getAbsAxisUsage(int32_t axis, uint32_t deviceClasses);
+
+/*
+ * Grand Central Station for events.
+ *
+ * The event hub aggregates input events received across all known input
+ * devices on the system, including devices that may be emulated by the simulator
+ * environment. In addition, the event hub generates fake input events to indicate
+ * when devices are added or removed.
+ *
+ * The event hub provides a stream of input events (via the getEvent function).
+ * It also supports querying the current actual state of input devices such as identifying
+ * which keys are currently down. Finally, the event hub keeps track of the capabilities of
+ * individual input devices, such as their class and the set of key codes that they support.
+ */
+class EventHubInterface : public virtual RefBase {
+protected:
+ EventHubInterface() { }
+ virtual ~EventHubInterface() { }
+
+public:
+ // Synthetic raw event type codes produced when devices are added or removed.
+ enum {
+ // Sent when a device is added.
+ DEVICE_ADDED = 0x10000000,
+ // Sent when a device is removed.
+ DEVICE_REMOVED = 0x20000000,
+ // Sent when all added/removed devices from the most recent scan have been reported.
+ // This event is always sent at least once.
+ FINISHED_DEVICE_SCAN = 0x30000000,
+
+ FIRST_SYNTHETIC_EVENT = DEVICE_ADDED,
+ };
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const = 0;
+
+ virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const = 0;
+
+ virtual int32_t getDeviceControllerNumber(int32_t deviceId) const = 0;
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const = 0;
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
+
+ virtual bool hasInputProperty(int32_t deviceId, int property) const = 0;
+
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const = 0;
+
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
+ AxisInfo* outAxisInfo) const = 0;
+
+ // Sets devices that are excluded from opening.
+ // This can be used to ignore input devices for sensors.
+ virtual void setExcludedDevices(const Vector<String8>& devices) = 0;
+
+ /*
+ * Wait for events to become available and returns them.
+ * After returning, the EventHub holds onto a wake lock until the next call to getEvent.
+ * This ensures that the device will not go to sleep while the event is being processed.
+ * If the device needs to remain awake longer than that, then the caller is responsible
+ * for taking care of it (say, by poking the power manager user activity timer).
+ *
+ * The timeout is advisory only. If the device is asleep, it will not wake just to
+ * service the timeout.
+ *
+ * Returns the number of events obtained, or 0 if the timeout expired.
+ */
+ virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) = 0;
+
+ /*
+ * Query current input state.
+ */
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
+ virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
+ int32_t* outValue) const = 0;
+
+ /*
+ * Examine key input devices for specific framework keycode support
+ */
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+ uint8_t* outFlags) const = 0;
+
+ virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0;
+
+ /* LED related functions expect Android LED constants, not scan codes or HID usages */
+ virtual bool hasLed(int32_t deviceId, int32_t led) const = 0;
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on) = 0;
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const = 0;
+
+ virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const = 0;
+ virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map) = 0;
+
+ /* Control the vibrator. */
+ virtual void vibrate(int32_t deviceId, nsecs_t duration) = 0;
+ virtual void cancelVibrate(int32_t deviceId) = 0;
+
+ /* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
+ virtual void requestReopenDevices() = 0;
+
+ /* Wakes up getEvents() if it is blocked on a read. */
+ virtual void wake() = 0;
+
+ /* Dump EventHub state to a string. */
+ virtual void dump(String8& dump) = 0;
+
+ /* Called by the heatbeat to ensures that the reader has not deadlocked. */
+ virtual void monitor() = 0;
+};
+
+class EventHub : public EventHubInterface
+{
+public:
+ EventHub();
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const;
+
+ virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const;
+
+ virtual int32_t getDeviceControllerNumber(int32_t deviceId) const;
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const;
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const;
+
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const;
+
+ virtual bool hasInputProperty(int32_t deviceId, int property) const;
+
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const;
+
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
+ AxisInfo* outAxisInfo) const;
+
+ virtual void setExcludedDevices(const Vector<String8>& devices);
+
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const;
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const;
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const;
+ virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const;
+
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) const;
+
+ virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize);
+
+ virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const;
+ virtual bool hasLed(int32_t deviceId, int32_t led) const;
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on);
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const;
+
+ virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const;
+ virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map);
+
+ virtual void vibrate(int32_t deviceId, nsecs_t duration);
+ virtual void cancelVibrate(int32_t deviceId);
+
+ virtual void requestReopenDevices();
+
+ virtual void wake();
+
+ virtual void dump(String8& dump);
+ virtual void monitor();
+
+protected:
+ virtual ~EventHub();
+
+private:
+ struct Device {
+ Device* next;
+
+ int fd; // may be -1 if device is virtual
+ const int32_t id;
+ const String8 path;
+ const InputDeviceIdentifier identifier;
+
+ uint32_t classes;
+
+ uint8_t keyBitmask[(KEY_MAX + 1) / 8];
+ uint8_t absBitmask[(ABS_MAX + 1) / 8];
+ uint8_t relBitmask[(REL_MAX + 1) / 8];
+ uint8_t swBitmask[(SW_MAX + 1) / 8];
+ uint8_t ledBitmask[(LED_MAX + 1) / 8];
+ uint8_t ffBitmask[(FF_MAX + 1) / 8];
+ uint8_t propBitmask[(INPUT_PROP_MAX + 1) / 8];
+
+ String8 configurationFile;
+ PropertyMap* configuration;
+ VirtualKeyMap* virtualKeyMap;
+ KeyMap keyMap;
+
+ sp<KeyCharacterMap> overlayKeyMap;
+ sp<KeyCharacterMap> combinedKeyMap;
+
+ bool ffEffectPlaying;
+ int16_t ffEffectId; // initially -1
+
+ int32_t controllerNumber;
+
+ int32_t timestampOverrideSec;
+ int32_t timestampOverrideUsec;
+
+ Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier);
+ ~Device();
+
+ void close();
+
+ inline bool isVirtual() const { return fd < 0; }
+
+ const sp<KeyCharacterMap>& getKeyCharacterMap() const {
+ if (combinedKeyMap != NULL) {
+ return combinedKeyMap;
+ }
+ return keyMap.keyCharacterMap;
+ }
+ };
+
+ status_t openDeviceLocked(const char *devicePath);
+ void createVirtualKeyboardLocked();
+ void addDeviceLocked(Device* device);
+ void assignDescriptorLocked(InputDeviceIdentifier& identifier);
+
+ status_t closeDeviceByPathLocked(const char *devicePath);
+ void closeDeviceLocked(Device* device);
+ void closeAllDevicesLocked();
+
+ status_t scanDirLocked(const char *dirname);
+ void scanDevicesLocked();
+ status_t readNotifyLocked();
+
+ Device* getDeviceByDescriptorLocked(String8& descriptor) const;
+ 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);
+
+ int32_t getNextControllerNumberLocked(Device* device);
+ void releaseControllerNumberLocked(Device* device);
+ void setLedForController(Device* device);
+
+ status_t mapLed(Device* device, int32_t led, int32_t* outScanCode) const;
+ void setLedStateLocked(Device* device, int32_t led, bool on);
+
+ // Protect all internal state.
+ mutable Mutex mLock;
+
+ // The actual id of the built-in keyboard, or NO_BUILT_IN_KEYBOARD if none.
+ // EventHub remaps the built-in keyboard to id 0 externally as required by the API.
+ enum {
+ // Must not conflict with any other assigned device ids, including
+ // the virtual keyboard id (-1).
+ NO_BUILT_IN_KEYBOARD = -2,
+ };
+ int32_t mBuiltInKeyboardId;
+
+ int32_t mNextDeviceId;
+
+ BitSet32 mControllerNumbers;
+
+ KeyedVector<int32_t, Device*> mDevices;
+
+ Device *mOpeningDevices;
+ Device *mClosingDevices;
+
+ bool mNeedToSendFinishedDeviceScan;
+ bool mNeedToReopenDevices;
+ bool mNeedToScanDevices;
+ Vector<String8> mExcludedDevices;
+
+ int mEpollFd;
+ int mINotifyFd;
+ int mWakeReadPipeFd;
+ int mWakeWritePipeFd;
+
+ // Ids used for epoll notifications not associated with devices.
+ static const uint32_t EPOLL_ID_INOTIFY = 0x80000001;
+ static const uint32_t EPOLL_ID_WAKE = 0x80000002;
+
+ // Epoll FD list size hint.
+ static const int EPOLL_SIZE_HINT = 8;
+
+ // Maximum number of signalled FDs to handle at a time.
+ static const int EPOLL_MAX_EVENTS = 16;
+
+ // The array of pending epoll events and the index of the next event to be handled.
+ struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];
+ size_t mPendingEventCount;
+ size_t mPendingEventIndex;
+ bool mPendingINotify;
+
+ bool mUsingEpollWakeup;
+};
+
+}; // namespace android
+
+#endif // _RUNTIME_EVENT_HUB_H
diff --git a/services/inputflinger/InputApplication.cpp b/services/inputflinger/InputApplication.cpp
new file mode 100644
index 0000000..a99e637
--- /dev/null
+++ b/services/inputflinger/InputApplication.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputApplication"
+
+#include "InputApplication.h"
+
+#include <cutils/log.h>
+
+namespace android {
+
+// --- InputApplicationHandle ---
+
+InputApplicationHandle::InputApplicationHandle() :
+ mInfo(NULL) {
+}
+
+InputApplicationHandle::~InputApplicationHandle() {
+ delete mInfo;
+}
+
+void InputApplicationHandle::releaseInfo() {
+ if (mInfo) {
+ delete mInfo;
+ mInfo = NULL;
+ }
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputApplication.h b/services/inputflinger/InputApplication.h
new file mode 100644
index 0000000..1f5504c
--- /dev/null
+++ b/services/inputflinger/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/Input.h>
+
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * Describes the properties of an application that can receive input.
+ */
+struct InputApplicationInfo {
+ String8 name;
+ nsecs_t dispatchingTimeout;
+};
+
+
+/*
+ * Handle for an application that can receive input.
+ *
+ * Used by the native input dispatcher as a handle for the window manager objects
+ * that describe an application.
+ */
+class InputApplicationHandle : public RefBase {
+public:
+ inline const InputApplicationInfo* getInfo() const {
+ return mInfo;
+ }
+
+ inline String8 getName() const {
+ return mInfo ? mInfo->name : String8("<invalid>");
+ }
+
+ inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const {
+ return mInfo ? mInfo->dispatchingTimeout : defaultValue;
+ }
+
+ /**
+ * Requests that the state of this object be updated to reflect
+ * the most current available information about the application.
+ *
+ * This method should only be called from within the input dispatcher's
+ * critical section.
+ *
+ * Returns true on success, or false if the handle is no longer valid.
+ */
+ virtual bool updateInfo() = 0;
+
+ /**
+ * Releases the storage used by the associated information when it is
+ * no longer needed.
+ */
+ void releaseInfo();
+
+protected:
+ InputApplicationHandle();
+ virtual ~InputApplicationHandle();
+
+ InputApplicationInfo* mInfo;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_APPLICATION_H
diff --git a/services/inputflinger/InputDispatcher.cpp b/services/inputflinger/InputDispatcher.cpp
new file mode 100644
index 0000000..0bae399
--- /dev/null
+++ b/services/inputflinger/InputDispatcher.cpp
@@ -0,0 +1,4487 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputDispatcher"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+//#define LOG_NDEBUG 0
+
+// Log detailed debug messages about each inbound event notification to the dispatcher.
+#define DEBUG_INBOUND_EVENT_DETAILS 0
+
+// Log detailed debug messages about each outbound event processed by the dispatcher.
+#define DEBUG_OUTBOUND_EVENT_DETAILS 0
+
+// Log debug messages about the dispatch cycle.
+#define DEBUG_DISPATCH_CYCLE 0
+
+// Log debug messages about registrations.
+#define DEBUG_REGISTRATION 0
+
+// Log debug messages about input event injection.
+#define DEBUG_INJECTION 0
+
+// Log debug messages about input focus tracking.
+#define DEBUG_FOCUS 0
+
+// Log debug messages about the app switch latency optimization.
+#define DEBUG_APP_SWITCH 0
+
+// Log debug messages about hover events.
+#define DEBUG_HOVER 0
+
+#include "InputDispatcher.h"
+
+#include <utils/Trace.h>
+#include <cutils/log.h>
+#include <powermanager/PowerManager.h>
+#include <ui/Region.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <time.h>
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+#define INDENT4 " "
+
+namespace android {
+
+// Default input dispatching timeout if there is no focused application or paused window
+// from which to determine an appropriate dispatching timeout.
+const nsecs_t DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5000 * 1000000LL; // 5 sec
+
+// Amount of time to allow for all pending events to be processed when an app switch
+// key is on the way. This is used to preempt input dispatch and drop input events
+// when an application takes too long to respond and the user has pressed an app switch key.
+const nsecs_t APP_SWITCH_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
+// Amount of time to allow for an event to be dispatched (measured since its eventTime)
+// before considering it stale and dropping it.
+const nsecs_t STALE_EVENT_TIMEOUT = 10000 * 1000000LL; // 10sec
+
+// Amount of time to allow touch events to be streamed out to a connection before requiring
+// that the first event be finished. This value extends the ANR timeout by the specified
+// amount. For example, if streaming is allowed to get ahead by one second relative to the
+// queue of waiting unfinished events, then ANRs will similarly be delayed by one second.
+const nsecs_t STREAM_AHEAD_EVENT_TIMEOUT = 500 * 1000000LL; // 0.5sec
+
+// Log a warning when an event takes longer than this to process, even if an ANR does not occur.
+const nsecs_t SLOW_EVENT_PROCESSING_WARNING_TIMEOUT = 2000 * 1000000LL; // 2sec
+
+// Number of recent events to keep for debugging purposes.
+const size_t RECENT_QUEUE_MAX_SIZE = 10;
+
+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 %zu; value must be between 1 and %d.",
+ pointerCount, MAX_POINTERS);
+ return false;
+ }
+ BitSet32 pointerIdBits;
+ for (size_t i = 0; i < pointerCount; i++) {
+ int32_t id = pointerProperties[i].id;
+ if (id < 0 || id > MAX_POINTER_ID) {
+ ALOGE("Motion event has invalid pointer id %d; value must be between 0 and %d",
+ id, MAX_POINTER_ID);
+ return false;
+ }
+ if (pointerIdBits.hasBit(id)) {
+ ALOGE("Motion event has duplicate pointer id %d", id);
+ return false;
+ }
+ pointerIdBits.markBit(id);
+ }
+ return true;
+}
+
+static bool isMainDisplay(int32_t displayId) {
+ return displayId == ADISPLAY_ID_DEFAULT || displayId == ADISPLAY_ID_NONE;
+}
+
+static void dumpRegion(String8& dump, const Region& region) {
+ if (region.isEmpty()) {
+ dump.append("<empty>");
+ return;
+ }
+
+ bool first = true;
+ Region::const_iterator cur = region.begin();
+ Region::const_iterator const tail = region.end();
+ while (cur != tail) {
+ if (first) {
+ first = false;
+ } else {
+ dump.append("|");
+ }
+ dump.appendFormat("[%d,%d][%d,%d]", cur->left, cur->top, cur->right, cur->bottom);
+ cur++;
+ }
+}
+
+
+// --- InputDispatcher ---
+
+InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
+ mPolicy(policy),
+ mPendingEvent(NULL), mAppSwitchSawKeyDown(false), mAppSwitchDueTime(LONG_LONG_MAX),
+ mNextUnblockedEvent(NULL),
+ mDispatchEnabled(false), mDispatchFrozen(false), mInputFilterEnabled(false),
+ mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
+ mLooper = new Looper(false);
+
+ mKeyRepeatState.lastKeyEntry = NULL;
+
+ policy->getDispatcherConfiguration(&mConfig);
+}
+
+InputDispatcher::~InputDispatcher() {
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ resetKeyRepeatLocked();
+ releasePendingEventLocked();
+ drainInboundQueueLocked();
+ }
+
+ while (mConnectionsByFd.size() != 0) {
+ unregisterInputChannel(mConnectionsByFd.valueAt(0)->inputChannel);
+ }
+}
+
+void InputDispatcher::dispatchOnce() {
+ nsecs_t nextWakeupTime = LONG_LONG_MAX;
+ { // acquire lock
+ AutoMutex _l(mLock);
+ mDispatcherIsAliveCondition.broadcast();
+
+ // Run a dispatch loop if there are no pending commands.
+ // The dispatch loop might enqueue commands to run afterwards.
+ if (!haveCommandsLocked()) {
+ dispatchOnceInnerLocked(&nextWakeupTime);
+ }
+
+ // Run all pending commands if there are any.
+ // If any commands were run then force the next poll to wake up immediately.
+ if (runCommandsLockedInterruptible()) {
+ nextWakeupTime = LONG_LONG_MIN;
+ }
+ } // release lock
+
+ // Wait for callback or timeout or wake. (make sure we round up, not down)
+ nsecs_t currentTime = now();
+ int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
+ mLooper->pollOnce(timeoutMillis);
+}
+
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
+ nsecs_t currentTime = now();
+
+ // Reset the key repeat timer whenever normal dispatch is suspended while the
+ // device is in a non-interactive state. This is to ensure that we abort a key
+ // repeat if the device is just coming out of sleep.
+ if (!mDispatchEnabled) {
+ resetKeyRepeatLocked();
+ }
+
+ // If dispatching is frozen, do not process timeouts or try to deliver any new events.
+ if (mDispatchFrozen) {
+#if DEBUG_FOCUS
+ ALOGD("Dispatch frozen. Waiting some more.");
+#endif
+ return;
+ }
+
+ // Optimize latency of app switches.
+ // Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
+ // been pressed. When it expires, we preempt dispatch and drop all other pending events.
+ bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
+ if (mAppSwitchDueTime < *nextWakeupTime) {
+ *nextWakeupTime = mAppSwitchDueTime;
+ }
+
+ // Ready to start a new event.
+ // If we don't already have a pending event, go grab one.
+ if (! mPendingEvent) {
+ if (mInboundQueue.isEmpty()) {
+ if (isAppSwitchDue) {
+ // The inbound queue is empty so the app switch key we were waiting
+ // for will never arrive. Stop waiting for it.
+ resetPendingAppSwitchLocked(false);
+ isAppSwitchDue = false;
+ }
+
+ // Synthesize a key repeat if appropriate.
+ if (mKeyRepeatState.lastKeyEntry) {
+ if (currentTime >= mKeyRepeatState.nextRepeatTime) {
+ mPendingEvent = synthesizeKeyRepeatLocked(currentTime);
+ } else {
+ if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
+ *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
+ }
+ }
+ }
+
+ // Nothing to do if there is no pending event.
+ if (!mPendingEvent) {
+ return;
+ }
+ } else {
+ // Inbound queue has at least one entry.
+ mPendingEvent = mInboundQueue.dequeueAtHead();
+ traceInboundQueueLengthLocked();
+ }
+
+ // Poke user activity for this event.
+ if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+ pokeUserActivityLocked(mPendingEvent);
+ }
+
+ // Get ready to dispatch the event.
+ resetANRTimeoutsLocked();
+ }
+
+ // Now we have an event to dispatch.
+ // All events are eventually dequeued and processed this way, even if we intend to drop them.
+ ALOG_ASSERT(mPendingEvent != NULL);
+ bool done = false;
+ DropReason dropReason = DROP_REASON_NOT_DROPPED;
+ if (!(mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+ dropReason = DROP_REASON_POLICY;
+ } else if (!mDispatchEnabled) {
+ dropReason = DROP_REASON_DISABLED;
+ }
+
+ if (mNextUnblockedEvent == mPendingEvent) {
+ mNextUnblockedEvent = NULL;
+ }
+
+ switch (mPendingEvent->type) {
+ case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+ ConfigurationChangedEntry* typedEntry =
+ static_cast<ConfigurationChangedEntry*>(mPendingEvent);
+ done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+ dropReason = DROP_REASON_NOT_DROPPED; // configuration changes are never dropped
+ break;
+ }
+
+ case EventEntry::TYPE_DEVICE_RESET: {
+ DeviceResetEntry* typedEntry =
+ static_cast<DeviceResetEntry*>(mPendingEvent);
+ done = dispatchDeviceResetLocked(currentTime, typedEntry);
+ dropReason = DROP_REASON_NOT_DROPPED; // device resets are never dropped
+ break;
+ }
+
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
+ if (isAppSwitchDue) {
+ if (isAppSwitchKeyEventLocked(typedEntry)) {
+ resetPendingAppSwitchLocked(true);
+ isAppSwitchDue = false;
+ } else if (dropReason == DROP_REASON_NOT_DROPPED) {
+ dropReason = DROP_REASON_APP_SWITCH;
+ }
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
+ done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
+ if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
+ dropReason = DROP_REASON_APP_SWITCH;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED
+ && isStaleEventLocked(currentTime, typedEntry)) {
+ dropReason = DROP_REASON_STALE;
+ }
+ if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
+ dropReason = DROP_REASON_BLOCKED;
+ }
+ done = dispatchMotionLocked(currentTime, typedEntry,
+ &dropReason, nextWakeupTime);
+ break;
+ }
+
+ default:
+ ALOG_ASSERT(false);
+ break;
+ }
+
+ if (done) {
+ if (dropReason != DROP_REASON_NOT_DROPPED) {
+ dropInboundEventLocked(mPendingEvent, dropReason);
+ }
+
+ releasePendingEventLocked();
+ *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
+ }
+}
+
+bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
+ bool needWake = mInboundQueue.isEmpty();
+ mInboundQueue.enqueueAtTail(entry);
+ traceInboundQueueLengthLocked();
+
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY: {
+ // Optimize app switch latency.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the app switch key.
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
+ if (isAppSwitchKeyEventLocked(keyEntry)) {
+ if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
+ mAppSwitchSawKeyDown = true;
+ } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
+ if (mAppSwitchSawKeyDown) {
+#if DEBUG_APP_SWITCH
+ ALOGD("App switch is pending!");
+#endif
+ mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
+ mAppSwitchSawKeyDown = false;
+ needWake = true;
+ }
+ }
+ }
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ // Optimize case where the current application is unresponsive and the user
+ // decides to touch a window in a different application.
+ // If the application takes too long to catch up then we drop all events preceding
+ // the touch into the other window.
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN
+ && (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
+ && mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
+ && mInputTargetWaitApplicationHandle != NULL) {
+ int32_t displayId = motionEntry->displayId;
+ int32_t x = int32_t(motionEntry->pointerCoords[0].
+ getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = int32_t(motionEntry->pointerCoords[0].
+ getAxisValue(AMOTION_EVENT_AXIS_Y));
+ sp<InputWindowHandle> touchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y);
+ if (touchedWindowHandle != NULL
+ && touchedWindowHandle->inputApplicationHandle
+ != mInputTargetWaitApplicationHandle) {
+ // User touched a different application than the one we are waiting on.
+ // Flag the event, and start pruning the input queue.
+ mNextUnblockedEvent = motionEntry;
+ needWake = true;
+ }
+ }
+ break;
+ }
+ }
+
+ return needWake;
+}
+
+void InputDispatcher::addRecentEventLocked(EventEntry* entry) {
+ entry->refCount += 1;
+ mRecentQueue.enqueueAtTail(entry);
+ if (mRecentQueue.count() > RECENT_QUEUE_MAX_SIZE) {
+ mRecentQueue.dequeueAtHead()->release();
+ }
+}
+
+sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId,
+ int32_t x, int32_t y) {
+ // Traverse windows from front to back to find touched window.
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ if (windowInfo->displayId == displayId) {
+ int32_t flags = windowInfo->layoutParamsFlags;
+ int32_t privateFlags = windowInfo->layoutParamsPrivateFlags;
+
+ 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 (privateFlags & InputWindowInfo::PRIVATE_FLAG_SYSTEM_ERROR) {
+ // Error window is on top but not visible, so touch is dropped.
+ return NULL;
+ }
+ }
+ }
+ return NULL;
+}
+
+void InputDispatcher::dropInboundEventLocked(EventEntry* entry, DropReason dropReason) {
+ const char* reason;
+ switch (dropReason) {
+ case DROP_REASON_POLICY:
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("Dropped event because policy consumed it.");
+#endif
+ reason = "inbound event was dropped because the policy consumed it";
+ break;
+ case DROP_REASON_DISABLED:
+ ALOGI("Dropped event because input dispatch is disabled.");
+ reason = "inbound event was dropped because input dispatch is disabled";
+ break;
+ case DROP_REASON_APP_SWITCH:
+ ALOGI("Dropped event because of pending overdue app switch.");
+ reason = "inbound event was dropped because of pending overdue app switch";
+ break;
+ case DROP_REASON_BLOCKED:
+ ALOGI("Dropped event because the current application is not responding and the user "
+ "has started interacting with a different application.");
+ reason = "inbound event was dropped because the current application is not responding "
+ "and the user has started interacting with a different application";
+ break;
+ case DROP_REASON_STALE:
+ ALOGI("Dropped event because it is stale.");
+ reason = "inbound event was dropped because it is stale";
+ break;
+ default:
+ ALOG_ASSERT(false);
+ return;
+ }
+
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY: {
+ CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ break;
+ }
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ if (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER) {
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ } else {
+ CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ }
+ break;
+ }
+ }
+}
+
+bool InputDispatcher::isAppSwitchKeyCode(int32_t keyCode) {
+ return keyCode == AKEYCODE_HOME
+ || keyCode == AKEYCODE_ENDCALL
+ || keyCode == AKEYCODE_APP_SWITCH;
+}
+
+bool InputDispatcher::isAppSwitchKeyEventLocked(KeyEntry* keyEntry) {
+ return ! (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
+ && isAppSwitchKeyCode(keyEntry->keyCode)
+ && (keyEntry->policyFlags & POLICY_FLAG_TRUSTED)
+ && (keyEntry->policyFlags & POLICY_FLAG_PASS_TO_USER);
+}
+
+bool InputDispatcher::isAppSwitchPendingLocked() {
+ return mAppSwitchDueTime != LONG_LONG_MAX;
+}
+
+void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
+ mAppSwitchDueTime = LONG_LONG_MAX;
+
+#if DEBUG_APP_SWITCH
+ if (handled) {
+ ALOGD("App switch has arrived.");
+ } else {
+ ALOGD("App switch was abandoned.");
+ }
+#endif
+}
+
+bool InputDispatcher::isStaleEventLocked(nsecs_t currentTime, EventEntry* entry) {
+ return currentTime - entry->eventTime >= STALE_EVENT_TIMEOUT;
+}
+
+bool InputDispatcher::haveCommandsLocked() const {
+ return !mCommandQueue.isEmpty();
+}
+
+bool InputDispatcher::runCommandsLockedInterruptible() {
+ if (mCommandQueue.isEmpty()) {
+ return false;
+ }
+
+ do {
+ CommandEntry* commandEntry = mCommandQueue.dequeueAtHead();
+
+ Command command = commandEntry->command;
+ (this->*command)(commandEntry); // commands are implicitly 'LockedInterruptible'
+
+ commandEntry->connection.clear();
+ delete commandEntry;
+ } while (! mCommandQueue.isEmpty());
+ return true;
+}
+
+InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command command) {
+ CommandEntry* commandEntry = new CommandEntry(command);
+ mCommandQueue.enqueueAtTail(commandEntry);
+ return commandEntry;
+}
+
+void InputDispatcher::drainInboundQueueLocked() {
+ while (! mInboundQueue.isEmpty()) {
+ EventEntry* entry = mInboundQueue.dequeueAtHead();
+ releaseInboundEventLocked(entry);
+ }
+ traceInboundQueueLengthLocked();
+}
+
+void InputDispatcher::releasePendingEventLocked() {
+ if (mPendingEvent) {
+ resetANRTimeoutsLocked();
+ releaseInboundEventLocked(mPendingEvent);
+ mPendingEvent = NULL;
+ }
+}
+
+void InputDispatcher::releaseInboundEventLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState && injectionState->injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("Injected inbound event was dropped.");
+#endif
+ setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
+ }
+ if (entry == mNextUnblockedEvent) {
+ mNextUnblockedEvent = NULL;
+ }
+ addRecentEventLocked(entry);
+ entry->release();
+}
+
+void InputDispatcher::resetKeyRepeatLocked() {
+ if (mKeyRepeatState.lastKeyEntry) {
+ mKeyRepeatState.lastKeyEntry->release();
+ mKeyRepeatState.lastKeyEntry = NULL;
+ }
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked(nsecs_t currentTime) {
+ KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
+
+ // Reuse the repeated key entry if it is otherwise unreferenced.
+ uint32_t policyFlags = (entry->policyFlags & POLICY_FLAG_RAW_MASK)
+ | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_TRUSTED;
+ if (entry->refCount == 1) {
+ entry->recycle();
+ entry->eventTime = currentTime;
+ entry->policyFlags = policyFlags;
+ entry->repeatCount += 1;
+ } else {
+ KeyEntry* newEntry = new KeyEntry(currentTime,
+ entry->deviceId, entry->source, policyFlags,
+ entry->action, entry->flags, entry->keyCode, entry->scanCode,
+ entry->metaState, entry->repeatCount + 1, entry->downTime);
+
+ mKeyRepeatState.lastKeyEntry = newEntry;
+ entry->release();
+
+ entry = newEntry;
+ }
+ entry->syntheticRepeat = true;
+
+ // Increment reference count since we keep a reference to the event in
+ // mKeyRepeatState.lastKeyEntry in addition to the one we return.
+ entry->refCount += 1;
+
+ mKeyRepeatState.nextRepeatTime = currentTime + mConfig.keyRepeatDelay;
+ return entry;
+}
+
+bool InputDispatcher::dispatchConfigurationChangedLocked(
+ nsecs_t currentTime, ConfigurationChangedEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("dispatchConfigurationChanged - eventTime=%lld", entry->eventTime);
+#endif
+
+ // Reset key repeating in case a keyboard device was added or removed or something.
+ resetKeyRepeatLocked();
+
+ // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyConfigurationChangedInterruptible);
+ commandEntry->eventTime = entry->eventTime;
+ return true;
+}
+
+bool InputDispatcher::dispatchDeviceResetLocked(
+ nsecs_t currentTime, DeviceResetEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("dispatchDeviceReset - eventTime=%lld, deviceId=%d", entry->eventTime, entry->deviceId);
+#endif
+
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
+ "device was reset");
+ options.deviceId = entry->deviceId;
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ return true;
+}
+
+bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, KeyEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ if (entry->repeatCount == 0
+ && entry->action == AKEY_EVENT_ACTION_DOWN
+ && (entry->policyFlags & POLICY_FLAG_TRUSTED)
+ && (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) {
+ if (mKeyRepeatState.lastKeyEntry
+ && mKeyRepeatState.lastKeyEntry->keyCode == entry->keyCode) {
+ // We have seen two identical key downs in a row which indicates that the device
+ // driver is automatically generating key repeats itself. We take note of the
+ // repeat here, but we disable our own next key repeat timer since it is clear that
+ // we will not need to synthesize key repeats ourselves.
+ entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
+ resetKeyRepeatLocked();
+ mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+ } else {
+ // Not a repeat. Save key down state in case we do see a repeat later.
+ resetKeyRepeatLocked();
+ mKeyRepeatState.nextRepeatTime = entry->eventTime + mConfig.keyRepeatTimeout;
+ }
+ mKeyRepeatState.lastKeyEntry = entry;
+ entry->refCount += 1;
+ } else if (! entry->syntheticRepeat) {
+ resetKeyRepeatLocked();
+ }
+
+ if (entry->repeatCount == 1) {
+ entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
+ } else {
+ entry->flags &= ~AKEY_EVENT_FLAG_LONG_PRESS;
+ }
+
+ entry->dispatchInProgress = true;
+
+ logOutboundKeyDetailsLocked("dispatchKey - ", entry);
+ }
+
+ // Handle case where the policy asked us to try again later last time.
+ if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
+ if (currentTime < entry->interceptKeyWakeupTime) {
+ if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
+ *nextWakeupTime = entry->interceptKeyWakeupTime;
+ }
+ return false; // wait until next wakeup
+ }
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+ entry->interceptKeyWakeupTime = 0;
+ }
+
+ // Give the policy a chance to intercept the key.
+ if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+ if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
+ if (mFocusedWindowHandle != NULL) {
+ commandEntry->inputWindowHandle = mFocusedWindowHandle;
+ }
+ commandEntry->keyEntry = entry;
+ entry->refCount += 1;
+ return false; // wait for the command to run
+ } else {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ }
+ } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+ if (*dropReason == DROP_REASON_NOT_DROPPED) {
+ *dropReason = DROP_REASON_POLICY;
+ }
+ }
+
+ // Clean up if dropping the event.
+ if (*dropReason != DROP_REASON_NOT_DROPPED) {
+ setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+ ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+ return true;
+ }
+
+ // Identify targets.
+ Vector<InputTarget> inputTargets;
+ int32_t injectionResult = findFocusedWindowTargetsLocked(currentTime,
+ entry, inputTargets, nextWakeupTime);
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
+
+ addMonitoringTargetsLocked(inputTargets);
+
+ // Dispatch the key.
+ dispatchEventLocked(currentTime, entry, inputTargets);
+ return true;
+}
+
+void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, "
+ "repeatCount=%d, downTime=%lld",
+ prefix,
+ entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+ entry->action, entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
+ entry->repeatCount, entry->downTime);
+#endif
+}
+
+bool InputDispatcher::dispatchMotionLocked(
+ nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ entry->dispatchInProgress = true;
+
+ logOutboundMotionDetailsLocked("dispatchMotion - ", entry);
+ }
+
+ // Clean up if dropping the event.
+ if (*dropReason != DROP_REASON_NOT_DROPPED) {
+ setInjectionResultLocked(entry, *dropReason == DROP_REASON_POLICY
+ ? INPUT_EVENT_INJECTION_SUCCEEDED : INPUT_EVENT_INJECTION_FAILED);
+ return true;
+ }
+
+ bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
+
+ // Identify targets.
+ Vector<InputTarget> inputTargets;
+
+ bool conflictingPointerActions = false;
+ int32_t injectionResult;
+ if (isPointerEvent) {
+ // Pointer event. (eg. touchscreen)
+ injectionResult = findTouchedWindowTargetsLocked(currentTime,
+ entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
+ } else {
+ // Non touch event. (eg. trackball)
+ injectionResult = findFocusedWindowTargetsLocked(currentTime,
+ entry, inputTargets, nextWakeupTime);
+ }
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
+
+ // TODO: support sending secondary display events to input monitors
+ if (isMainDisplay(entry->displayId)) {
+ addMonitoringTargetsLocked(inputTargets);
+ }
+
+ // Dispatch the motion.
+ if (conflictingPointerActions) {
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ "conflicting pointer actions");
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+ }
+ dispatchEventLocked(currentTime, entry, inputTargets);
+ return true;
+}
+
+
+void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("%seventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, "
+ "metaState=0x%x, buttonState=0x%x, "
+ "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%lld",
+ prefix,
+ entry->eventTime, entry->deviceId, entry->source, entry->policyFlags,
+ entry->action, entry->flags,
+ entry->metaState, entry->buttonState,
+ entry->edgeFlags, entry->xPrecision, entry->yPrecision,
+ entry->downTime);
+
+ for (uint32_t i = 0; i < entry->pointerCount; i++) {
+ ALOGD(" Pointer %d: id=%d, toolType=%d, "
+ "x=%f, y=%f, pressure=%f, size=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+ "orientation=%f",
+ i, entry->pointerProperties[i].id,
+ entry->pointerProperties[i].toolType,
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ entry->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+ }
+#endif
+}
+
+void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
+ EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("dispatchEventToCurrentInputTargets");
+#endif
+
+ ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
+
+ pokeUserActivityLocked(eventEntry);
+
+ for (size_t i = 0; i < inputTargets.size(); i++) {
+ const InputTarget& inputTarget = inputTargets.itemAt(i);
+
+ ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
+ } else {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event delivery to target with channel '%s' because it "
+ "is no longer registered with the input dispatcher.",
+ inputTarget.inputChannel->getName().string());
+#endif
+ }
+ }
+}
+
+int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
+ const EventEntry* entry,
+ const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t* nextWakeupTime, const char* reason) {
+ if (applicationHandle == NULL && windowHandle == NULL) {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
+#if DEBUG_FOCUS
+ ALOGD("Waiting for system to become ready for input. Reason: %s", reason);
+#endif
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
+ mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplicationHandle.clear();
+ }
+ } else {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+#if DEBUG_FOCUS
+ ALOGD("Waiting for application to become ready for input: %s. Reason: %s",
+ getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
+ reason);
+#endif
+ nsecs_t timeout;
+ if (windowHandle != NULL) {
+ timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
+ } else if (applicationHandle != NULL) {
+ timeout = applicationHandle->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT);
+ } else {
+ timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+ }
+
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = currentTime + timeout;
+ mInputTargetWaitTimeoutExpired = false;
+ mInputTargetWaitApplicationHandle.clear();
+
+ if (windowHandle != NULL) {
+ mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
+ }
+ if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
+ mInputTargetWaitApplicationHandle = applicationHandle;
+ }
+ }
+ }
+
+ if (mInputTargetWaitTimeoutExpired) {
+ return INPUT_EVENT_INJECTION_TIMED_OUT;
+ }
+
+ if (currentTime >= mInputTargetWaitTimeoutTime) {
+ onANRLocked(currentTime, applicationHandle, windowHandle,
+ entry->eventTime, mInputTargetWaitStartTime, reason);
+
+ // Force poll loop to wake up immediately on next iteration once we get the
+ // ANR response back from the policy.
+ *nextWakeupTime = LONG_LONG_MIN;
+ return INPUT_EVENT_INJECTION_PENDING;
+ } else {
+ // Force poll loop to wake up when timeout is due.
+ if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
+ *nextWakeupTime = mInputTargetWaitTimeoutTime;
+ }
+ return INPUT_EVENT_INJECTION_PENDING;
+ }
+}
+
+void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+ const sp<InputChannel>& inputChannel) {
+ if (newTimeout > 0) {
+ // Extend the timeout.
+ mInputTargetWaitTimeoutTime = now() + newTimeout;
+ } else {
+ // Give up.
+ mInputTargetWaitTimeoutExpired = true;
+
+ // Input state will not be realistic. Mark it out of sync.
+ if (inputChannel.get()) {
+ ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ sp<InputWindowHandle> windowHandle = connection->inputWindowHandle;
+
+ if (windowHandle != NULL) {
+ const InputWindowInfo* info = windowHandle->getInfo();
+ if (info) {
+ ssize_t stateIndex = mTouchStatesByDisplay.indexOfKey(info->displayId);
+ if (stateIndex >= 0) {
+ mTouchStatesByDisplay.editValueAt(stateIndex).removeWindow(
+ windowHandle);
+ }
+ }
+ }
+
+ if (connection->status == Connection::STATUS_NORMAL) {
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
+ "application not responding");
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
+ }
+ }
+ }
+ }
+}
+
+nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationLocked(
+ nsecs_t currentTime) {
+ if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+ return currentTime - mInputTargetWaitStartTime;
+ }
+ return 0;
+}
+
+void InputDispatcher::resetANRTimeoutsLocked() {
+#if DEBUG_FOCUS
+ ALOGD("Resetting ANR timeouts.");
+#endif
+
+ // Reset input target wait timeout.
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+ mInputTargetWaitApplicationHandle.clear();
+}
+
+int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
+ const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
+ int32_t injectionResult;
+
+ // If there is no currently focused window and no focused application
+ // then drop the event.
+ if (mFocusedWindowHandle == NULL) {
+ if (mFocusedApplicationHandle != NULL) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, NULL, nextWakeupTime,
+ "Waiting because no window has focus but there is a "
+ "focused application that may eventually add a window "
+ "when it finishes starting up.");
+ goto Unresponsive;
+ }
+
+ ALOGI("Dropping event because there is no focused window or focused application.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Check permissions.
+ if (! checkInjectionPermission(mFocusedWindowHandle, entry->injectionState)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+
+ // If the currently focused window is paused then keep waiting.
+ if (mFocusedWindowHandle->getInfo()->paused) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime,
+ "Waiting because the focused window is paused.");
+ goto Unresponsive;
+ }
+
+ // If the currently focused window is still working on previous events then keep waiting.
+ if (!isWindowReadyForMoreInputLocked(currentTime, mFocusedWindowHandle, entry)) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime,
+ "Waiting because the focused window has not finished "
+ "processing the input events that were previously delivered to it.");
+ goto Unresponsive;
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ addWindowTargetLocked(mFocusedWindowHandle,
+ InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS, BitSet32(0),
+ inputTargets);
+
+ // Done.
+Failed:
+Unresponsive:
+ nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+ updateDispatchStatisticsLocked(currentTime, entry,
+ injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+ ALOGD("findFocusedWindow finished: injectionResult=%d, "
+ "timeSpentWaitingForApplication=%0.1fms",
+ injectionResult, timeSpentWaitingForApplication / 1000000.0);
+#endif
+ return injectionResult;
+}
+
+int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
+ const MotionEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
+ bool* outConflictingPointerActions) {
+ enum InjectionPermission {
+ INJECTION_PERMISSION_UNKNOWN,
+ INJECTION_PERMISSION_GRANTED,
+ INJECTION_PERMISSION_DENIED
+ };
+
+ nsecs_t startTime = now();
+
+ // For security reasons, we defer updating the touch state until we are sure that
+ // event injection will be allowed.
+ int32_t displayId = entry->displayId;
+ int32_t action = entry->action;
+ int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+
+ // Update the touch state as needed based on the properties of the touch event.
+ int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING;
+ InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ sp<InputWindowHandle> newHoverWindowHandle;
+
+ // Copy current touch state into mTempTouchState.
+ // This state is always reset at the end of this function, so if we don't find state
+ // for the specified display then our initial state will be empty.
+ const TouchState* oldState = NULL;
+ ssize_t oldStateIndex = mTouchStatesByDisplay.indexOfKey(displayId);
+ if (oldStateIndex >= 0) {
+ oldState = &mTouchStatesByDisplay.valueAt(oldStateIndex);
+ mTempTouchState.copyFrom(*oldState);
+ }
+
+ bool isSplit = mTempTouchState.split;
+ bool switchedDevice = mTempTouchState.deviceId >= 0 && mTempTouchState.displayId >= 0
+ && (mTempTouchState.deviceId != entry->deviceId
+ || mTempTouchState.source != entry->source
+ || mTempTouchState.displayId != displayId);
+ bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
+ bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ || maskedAction == AMOTION_EVENT_ACTION_SCROLL
+ || isHoverAction);
+ bool wrongDevice = false;
+ if (newGesture) {
+ bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
+ if (switchedDevice && mTempTouchState.down && !down) {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event because a pointer for a different device is already down.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ switchedDevice = false;
+ wrongDevice = true;
+ goto Failed;
+ }
+ mTempTouchState.reset();
+ mTempTouchState.down = down;
+ mTempTouchState.deviceId = entry->deviceId;
+ mTempTouchState.source = entry->source;
+ mTempTouchState.displayId = displayId;
+ isSplit = false;
+ }
+
+ if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
+ /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
+
+ int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+ int32_t x = int32_t(entry->pointerCoords[pointerIndex].
+ getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = int32_t(entry->pointerCoords[pointerIndex].
+ getAxisValue(AMOTION_EVENT_AXIS_Y));
+ sp<InputWindowHandle> newTouchedWindowHandle;
+ sp<InputWindowHandle> topErrorWindowHandle;
+ bool isTouchModal = false;
+
+ // Traverse windows from front to back to find touched window and outside targets.
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ if (windowInfo->displayId != displayId) {
+ continue; // wrong display
+ }
+
+ int32_t privateFlags = windowInfo->layoutParamsPrivateFlags;
+ if (privateFlags & InputWindowInfo::PRIVATE_FLAG_SYSTEM_ERROR) {
+ if (topErrorWindowHandle == NULL) {
+ topErrorWindowHandle = windowHandle;
+ }
+ }
+
+ int32_t flags = windowInfo->layoutParamsFlags;
+ 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)) {
+ newTouchedWindowHandle = windowHandle;
+ break; // found touched window, exit window loop
+ }
+ }
+
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
+ int32_t outsideTargetFlags = InputTarget::FLAG_DISPATCH_AS_OUTSIDE;
+ if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
+ outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ mTempTouchState.addOrUpdateWindow(
+ windowHandle, outsideTargetFlags, BitSet32(0));
+ }
+ }
+ }
+
+ // If there is an error window but it is not taking focus (typically because
+ // it is invisible) then wait for it. Any other focused window may in
+ // fact be in ANR state.
+ if (topErrorWindowHandle != NULL && newTouchedWindowHandle != topErrorWindowHandle) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, NULL, nextWakeupTime,
+ "Waiting because a system error window is about to be displayed.");
+ injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ goto Unresponsive;
+ }
+
+ // Figure out whether splitting will be allowed for this window.
+ if (newTouchedWindowHandle != NULL
+ && newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
+ // New window supports splitting.
+ isSplit = true;
+ } else if (isSplit) {
+ // New window does not support splitting but we have already split events.
+ // Ignore the new window.
+ newTouchedWindowHandle = NULL;
+ }
+
+ // Handle the case where we did not find a window.
+ if (newTouchedWindowHandle == NULL) {
+ // Try to assign the pointer to the first foreground window we find, if there is one.
+ newTouchedWindowHandle = mTempTouchState.getFirstForegroundWindowHandle();
+ if (newTouchedWindowHandle == NULL) {
+ ALOGI("Dropping event because there is no touchable window at (%d, %d).", x, y);
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+ }
+
+ // Set target flags.
+ int32_t targetFlags = InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS;
+ if (isSplit) {
+ targetFlags |= InputTarget::FLAG_SPLIT;
+ }
+ if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ // Update hover state.
+ if (isHoverAction) {
+ newHoverWindowHandle = newTouchedWindowHandle;
+ } else if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
+ newHoverWindowHandle = mLastHoverWindowHandle;
+ }
+
+ // Update the temporary touch state.
+ BitSet32 pointerIds;
+ if (isSplit) {
+ uint32_t pointerId = entry->pointerProperties[pointerIndex].id;
+ pointerIds.markBit(pointerId);
+ }
+ mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+ } else {
+ /* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
+
+ // If the pointer is not currently down, then ignore the event.
+ if (! mTempTouchState.down) {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event because the pointer is not down or we previously "
+ "dropped the pointer down event.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Check whether touches should slip outside of the current foreground window.
+ if (maskedAction == AMOTION_EVENT_ACTION_MOVE
+ && entry->pointerCount == 1
+ && mTempTouchState.isSlippery()) {
+ int32_t x = int32_t(entry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
+ int32_t y = int32_t(entry->pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
+
+ sp<InputWindowHandle> oldTouchedWindowHandle =
+ mTempTouchState.getFirstForegroundWindowHandle();
+ sp<InputWindowHandle> newTouchedWindowHandle =
+ findTouchedWindowAtLocked(displayId, x, y);
+ if (oldTouchedWindowHandle != newTouchedWindowHandle
+ && newTouchedWindowHandle != NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Touch is slipping out of window %s into window %s.",
+ oldTouchedWindowHandle->getName().string(),
+ newTouchedWindowHandle->getName().string());
+#endif
+ // Make a slippery exit from the old window.
+ mTempTouchState.addOrUpdateWindow(oldTouchedWindowHandle,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT, BitSet32(0));
+
+ // Make a slippery entrance into the new window.
+ if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
+ isSplit = true;
+ }
+
+ int32_t targetFlags = InputTarget::FLAG_FOREGROUND
+ | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER;
+ if (isSplit) {
+ targetFlags |= InputTarget::FLAG_SPLIT;
+ }
+ if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ BitSet32 pointerIds;
+ if (isSplit) {
+ pointerIds.markBit(entry->pointerProperties[0].id);
+ }
+ mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+ }
+ }
+ }
+
+ if (newHoverWindowHandle != mLastHoverWindowHandle) {
+ // Let the previous window know that the hover sequence is over.
+ if (mLastHoverWindowHandle != NULL) {
+#if DEBUG_HOVER
+ ALOGD("Sending hover exit event to window %s.",
+ mLastHoverWindowHandle->getName().string());
+#endif
+ mTempTouchState.addOrUpdateWindow(mLastHoverWindowHandle,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0));
+ }
+
+ // Let the new window know that the hover sequence is starting.
+ if (newHoverWindowHandle != NULL) {
+#if DEBUG_HOVER
+ ALOGD("Sending hover enter event to window %s.",
+ newHoverWindowHandle->getName().string());
+#endif
+ mTempTouchState.addOrUpdateWindow(newHoverWindowHandle,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER, BitSet32(0));
+ }
+ }
+
+ // Check permission to inject into all touched foreground windows and ensure there
+ // is at least one touched foreground window.
+ {
+ bool haveForegroundWindow = false;
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ haveForegroundWindow = true;
+ if (! checkInjectionPermission(touchedWindow.windowHandle,
+ entry->injectionState)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+ }
+ }
+ if (! haveForegroundWindow) {
+#if DEBUG_FOCUS
+ ALOGD("Dropping event because there is no touched foreground window to receive it.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
+ }
+
+ // Permission granted to injection into all touched foreground windows.
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ }
+
+ // Check whether windows listening for outside touches are owned by the same UID. If it is
+ // set the policy flag that we will not reveal coordinate information to this window.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ sp<InputWindowHandle> foregroundWindowHandle =
+ mTempTouchState.getFirstForegroundWindowHandle();
+ const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+ sp<InputWindowHandle> inputWindowHandle = touchedWindow.windowHandle;
+ if (inputWindowHandle->getInfo()->ownerUid != foregroundWindowUid) {
+ mTempTouchState.addOrUpdateWindow(inputWindowHandle,
+ InputTarget::FLAG_ZERO_COORDS, BitSet32(0));
+ }
+ }
+ }
+ }
+
+ // Ensure all touched foreground windows are ready for new input.
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows[i];
+ if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ // If the touched window is paused then keep waiting.
+ if (touchedWindow.windowHandle->getInfo()->paused) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, touchedWindow.windowHandle, nextWakeupTime,
+ "Waiting because the touched window is paused.");
+ goto Unresponsive;
+ }
+
+ // If the touched window is still working on previous events then keep waiting.
+ if (!isWindowReadyForMoreInputLocked(currentTime, touchedWindow.windowHandle, entry)) {
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, touchedWindow.windowHandle, nextWakeupTime,
+ "Waiting because the touched window has not finished "
+ "processing the input events that were previously delivered to it.");
+ goto Unresponsive;
+ }
+ }
+ }
+
+ // If this is the first pointer going down and the touched window has a wallpaper
+ // then also add the touched wallpaper windows so they are locked in for the duration
+ // of the touch gesture.
+ // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
+ // engine only supports touch events. We would need to add a mechanism similar
+ // to View.onGenericMotionEvent to enable wallpapers to handle these events.
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ sp<InputWindowHandle> foregroundWindowHandle =
+ mTempTouchState.getFirstForegroundWindowHandle();
+ if (foregroundWindowHandle->getInfo()->hasWallpaper) {
+ for (size_t i = 0; i < mWindowHandles.size(); i++) {
+ sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* info = windowHandle->getInfo();
+ if (info->displayId == displayId
+ && windowHandle->getInfo()->layoutParamsType
+ == InputWindowInfo::TYPE_WALLPAPER) {
+ mTempTouchState.addOrUpdateWindow(windowHandle,
+ InputTarget::FLAG_WINDOW_IS_OBSCURED
+ | InputTarget::FLAG_DISPATCH_AS_IS,
+ BitSet32(0));
+ }
+ }
+ }
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+
+ for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
+ addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
+ touchedWindow.pointerIds, inputTargets);
+ }
+
+ // Drop the outside or hover touch windows since we will not care about them
+ // in the next iteration.
+ mTempTouchState.filterNonAsIsTouchWindows();
+
+Failed:
+ // Check injection permission once and for all.
+ if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
+ if (checkInjectionPermission(NULL, entry->injectionState)) {
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ } else {
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ }
+ }
+
+ // Update final pieces of touch state if the injector had permission.
+ if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
+ if (!wrongDevice) {
+ if (switchedDevice) {
+#if DEBUG_FOCUS
+ ALOGD("Conflicting pointer actions: Switched to a different device.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+
+ if (isHoverAction) {
+ // Started hovering, therefore no longer down.
+ if (oldState && oldState->down) {
+#if DEBUG_FOCUS
+ ALOGD("Conflicting pointer actions: Hover received while pointer was down.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+ mTempTouchState.reset();
+ if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+ mTempTouchState.deviceId = entry->deviceId;
+ mTempTouchState.source = entry->source;
+ mTempTouchState.displayId = displayId;
+ }
+ } else if (maskedAction == AMOTION_EVENT_ACTION_UP
+ || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ // All pointers up or canceled.
+ mTempTouchState.reset();
+ } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ // First pointer went down.
+ if (oldState && oldState->down) {
+#if DEBUG_FOCUS
+ ALOGD("Conflicting pointer actions: Down received while already down.");
+#endif
+ *outConflictingPointerActions = true;
+ }
+ } 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;
+ }
+ }
+ }
+
+ // Save changes unless the action was scroll in which case the temporary touch
+ // state was only valid for this one action.
+ if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
+ if (mTempTouchState.displayId >= 0) {
+ if (oldStateIndex >= 0) {
+ mTouchStatesByDisplay.editValueAt(oldStateIndex).copyFrom(mTempTouchState);
+ } else {
+ mTouchStatesByDisplay.add(displayId, mTempTouchState);
+ }
+ } else if (oldStateIndex >= 0) {
+ mTouchStatesByDisplay.removeItemsAt(oldStateIndex);
+ }
+ }
+
+ // Update hover state.
+ mLastHoverWindowHandle = newHoverWindowHandle;
+ }
+ } else {
+#if DEBUG_FOCUS
+ ALOGD("Not updating touch focus because injection was denied.");
+#endif
+ }
+
+Unresponsive:
+ // Reset temporary touch state to ensure we release unnecessary references to input channels.
+ mTempTouchState.reset();
+
+ nsecs_t timeSpentWaitingForApplication = getTimeSpentWaitingForApplicationLocked(currentTime);
+ updateDispatchStatisticsLocked(currentTime, entry,
+ injectionResult, timeSpentWaitingForApplication);
+#if DEBUG_FOCUS
+ ALOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d, "
+ "timeSpentWaitingForApplication=%0.1fms",
+ injectionResult, injectionPermission, timeSpentWaitingForApplication / 1000000.0);
+#endif
+ return injectionResult;
+}
+
+void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {
+ inputTargets.push();
+
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ InputTarget& target = inputTargets.editTop();
+ target.inputChannel = windowInfo->inputChannel;
+ target.flags = targetFlags;
+ target.xOffset = - windowInfo->frameLeft;
+ target.yOffset = - windowInfo->frameTop;
+ target.scaleFactor = windowInfo->scaleFactor;
+ target.pointerIds = pointerIds;
+}
+
+void InputDispatcher::addMonitoringTargetsLocked(Vector<InputTarget>& inputTargets) {
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ inputTargets.push();
+
+ InputTarget& target = inputTargets.editTop();
+ target.inputChannel = mMonitoringChannels[i];
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+ target.xOffset = 0;
+ target.yOffset = 0;
+ target.pointerIds.clear();
+ target.scaleFactor = 1.0f;
+ }
+}
+
+bool InputDispatcher::checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
+ const InjectionState* injectionState) {
+ if (injectionState
+ && (windowHandle == NULL
+ || windowHandle->getInfo()->ownerUid != injectionState->injectorUid)
+ && !hasInjectionPermission(injectionState->injectorPid, injectionState->injectorUid)) {
+ if (windowHandle != NULL) {
+ ALOGW("Permission denied: injecting event from pid %d uid %d to window %s "
+ "owned by uid %d",
+ injectionState->injectorPid, injectionState->injectorUid,
+ windowHandle->getName().string(),
+ windowHandle->getInfo()->ownerUid);
+ } else {
+ ALOGW("Permission denied: injecting event from pid %d uid %d",
+ injectionState->injectorPid, injectionState->injectorUid);
+ }
+ return false;
+ }
+ return true;
+}
+
+bool InputDispatcher::isWindowObscuredAtPointLocked(
+ const sp<InputWindowHandle>& windowHandle, int32_t x, int32_t y) const {
+ int32_t displayId = windowHandle->getInfo()->displayId;
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ sp<InputWindowHandle> otherHandle = mWindowHandles.itemAt(i);
+ if (otherHandle == windowHandle) {
+ break;
+ }
+
+ const InputWindowInfo* otherInfo = otherHandle->getInfo();
+ if (otherInfo->displayId == displayId
+ && otherInfo->visible && !otherInfo->isTrustedOverlay()
+ && otherInfo->frameContainsPoint(x, y)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::isWindowReadyForMoreInputLocked(nsecs_t currentTime,
+ const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry) {
+ ssize_t connectionIndex = getConnectionIndexLocked(windowHandle->getInputChannel());
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ if (connection->inputPublisherBlocked) {
+ return false;
+ }
+ if (eventEntry->type == EventEntry::TYPE_KEY) {
+ // If the event is a key event, then we must wait for all previous events to
+ // complete before delivering it because previous events may have the
+ // side-effect of transferring focus to a different window and we want to
+ // ensure that the following keys are sent to the new window.
+ //
+ // Suppose the user touches a button in a window then immediately presses "A".
+ // If the button causes a pop-up window to appear then we want to ensure that
+ // the "A" key is delivered to the new pop-up window. This is because users
+ // often anticipate pending UI changes when typing on a keyboard.
+ // To obtain this behavior, we must serialize key events with respect to all
+ // prior input events.
+ return connection->outboundQueue.isEmpty()
+ && connection->waitQueue.isEmpty();
+ }
+ // Touch events can always be sent to a window immediately because the user intended
+ // to touch whatever was visible at the time. Even if focus changes or a new
+ // window appears moments later, the touch event was meant to be delivered to
+ // whatever window happened to be on screen at the time.
+ //
+ // Generic motion events, such as trackball or joystick events are a little trickier.
+ // Like key events, generic motion events are delivered to the focused window.
+ // Unlike key events, generic motion events don't tend to transfer focus to other
+ // windows and it is not important for them to be serialized. So we prefer to deliver
+ // generic motion events as soon as possible to improve efficiency and reduce lag
+ // through batching.
+ //
+ // The one case where we pause input event delivery is when the wait queue is piling
+ // up with lots of events because the application is not responding.
+ // This condition ensures that ANRs are detected reliably.
+ if (!connection->waitQueue.isEmpty()
+ && currentTime >= connection->waitQueue.head->deliveryTime
+ + STREAM_AHEAD_EVENT_TIMEOUT) {
+ return false;
+ }
+ }
+ return true;
+}
+
+String8 InputDispatcher::getApplicationWindowLabelLocked(
+ const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle) {
+ if (applicationHandle != NULL) {
+ if (windowHandle != NULL) {
+ String8 label(applicationHandle->getName());
+ label.append(" - ");
+ label.append(windowHandle->getName());
+ return label;
+ } else {
+ return applicationHandle->getName();
+ }
+ } else if (windowHandle != NULL) {
+ return windowHandle->getName();
+ } else {
+ return String8("<unknown application or window>");
+ }
+}
+
+void InputDispatcher::pokeUserActivityLocked(const EventEntry* eventEntry) {
+ if (mFocusedWindowHandle != NULL) {
+ const InputWindowInfo* info = mFocusedWindowHandle->getInfo();
+ if (info->inputFeatures & InputWindowInfo::INPUT_FEATURE_DISABLE_USER_ACTIVITY) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("Not poking user activity: disabled by window '%s'.", info->name.string());
+#endif
+ return;
+ }
+ }
+
+ int32_t eventType = USER_ACTIVITY_EVENT_OTHER;
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_MOTION: {
+ const MotionEntry* motionEntry = static_cast<const MotionEntry*>(eventEntry);
+ if (motionEntry->action == AMOTION_EVENT_ACTION_CANCEL) {
+ return;
+ }
+
+ if (MotionEvent::isTouchEvent(motionEntry->source, motionEntry->action)) {
+ eventType = USER_ACTIVITY_EVENT_TOUCH;
+ }
+ break;
+ }
+ case EventEntry::TYPE_KEY: {
+ const KeyEntry* keyEntry = static_cast<const KeyEntry*>(eventEntry);
+ if (keyEntry->flags & AKEY_EVENT_FLAG_CANCELED) {
+ return;
+ }
+ eventType = USER_ACTIVITY_EVENT_BUTTON;
+ break;
+ }
+ }
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doPokeUserActivityLockedInterruptible);
+ commandEntry->eventTime = eventEntry->eventTime;
+ commandEntry->userActivityEventType = eventType;
+}
+
+void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, "
+ "xOffset=%f, yOffset=%f, scaleFactor=%f, "
+ "pointerIds=0x%x",
+ connection->getInputChannelName(), inputTarget->flags,
+ inputTarget->xOffset, inputTarget->yOffset,
+ inputTarget->scaleFactor, inputTarget->pointerIds.value);
+#endif
+
+ // Skip this event if the connection status is not normal.
+ // We don't want to enqueue additional outbound events if the connection is broken.
+ if (connection->status != Connection::STATUS_NORMAL) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Dropping event because the channel status is %s",
+ connection->getInputChannelName(), connection->getStatusLabel());
+#endif
+ return;
+ }
+
+ // Split a motion event if needed.
+ if (inputTarget->flags & InputTarget::FLAG_SPLIT) {
+ ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION);
+
+ MotionEntry* originalMotionEntry = static_cast<MotionEntry*>(eventEntry);
+ if (inputTarget->pointerIds.count() != originalMotionEntry->pointerCount) {
+ MotionEntry* splitMotionEntry = splitMotionEvent(
+ originalMotionEntry, inputTarget->pointerIds);
+ if (!splitMotionEntry) {
+ return; // split event was dropped
+ }
+#if DEBUG_FOCUS
+ ALOGD("channel '%s' ~ Split motion event.",
+ connection->getInputChannelName());
+ logOutboundMotionDetailsLocked(" ", splitMotionEntry);
+#endif
+ enqueueDispatchEntriesLocked(currentTime, connection,
+ splitMotionEntry, inputTarget);
+ splitMotionEntry->release();
+ return;
+ }
+ }
+
+ // Not splitting. Enqueue dispatch entries for the event as is.
+ enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
+}
+
+void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget) {
+ bool wasEmpty = connection->outboundQueue.isEmpty();
+
+ // Enqueue dispatch entries for the requested modes.
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_IS);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
+ InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
+
+ // If the outbound queue was previously empty, start the dispatch cycle going.
+ if (wasEmpty && !connection->outboundQueue.isEmpty()) {
+ startDispatchCycleLocked(currentTime, connection);
+ }
+}
+
+void InputDispatcher::enqueueDispatchEntryLocked(
+ const sp<Connection>& connection, EventEntry* eventEntry, const InputTarget* inputTarget,
+ int32_t dispatchMode) {
+ int32_t inputTargetFlags = inputTarget->flags;
+ if (!(inputTargetFlags & dispatchMode)) {
+ return;
+ }
+ inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
+
+ // This is a new event.
+ // Enqueue a new dispatch entry onto the outbound queue for this connection.
+ DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry, // increments ref
+ inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,
+ inputTarget->scaleFactor);
+
+ // Apply target flags and update the connection's input state.
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+ dispatchEntry->resolvedAction = keyEntry->action;
+ dispatchEntry->resolvedFlags = keyEntry->flags;
+
+ if (!connection->inputState.trackKey(keyEntry,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key event",
+ connection->getInputChannelName());
+#endif
+ delete dispatchEntry;
+ return; // skip the inconsistent event
+ }
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+ if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
+ } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
+ } else {
+ dispatchEntry->resolvedAction = motionEntry->action;
+ }
+ if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE
+ && !connection->inputState.isHovering(
+ motionEntry->deviceId, motionEntry->source, motionEntry->displayId)) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover enter event",
+ connection->getInputChannelName());
+#endif
+ dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ }
+
+ dispatchEntry->resolvedFlags = motionEntry->flags;
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
+ dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ }
+
+ if (!connection->inputState.trackMotion(motionEntry,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags)) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion event",
+ connection->getInputChannelName());
+#endif
+ delete dispatchEntry;
+ return; // skip the inconsistent event
+ }
+ break;
+ }
+ }
+
+ // Remember that we are waiting for this dispatch to complete.
+ if (dispatchEntry->hasForegroundTarget()) {
+ incrementPendingForegroundDispatchesLocked(eventEntry);
+ }
+
+ // Enqueue the dispatch entry.
+ connection->outboundQueue.enqueueAtTail(dispatchEntry);
+ traceOutboundQueueLengthLocked(connection);
+}
+
+void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ startDispatchCycle",
+ connection->getInputChannelName());
+#endif
+
+ while (connection->status == Connection::STATUS_NORMAL
+ && !connection->outboundQueue.isEmpty()) {
+ DispatchEntry* dispatchEntry = connection->outboundQueue.head;
+ dispatchEntry->deliveryTime = currentTime;
+
+ // Publish the event.
+ status_t status;
+ EventEntry* eventEntry = dispatchEntry->eventEntry;
+ switch (eventEntry->type) {
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(eventEntry);
+
+ // Publish the key event.
+ status = connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
+ keyEntry->deviceId, keyEntry->source,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+ keyEntry->keyCode, keyEntry->scanCode,
+ keyEntry->metaState, keyEntry->repeatCount, keyEntry->downTime,
+ keyEntry->eventTime);
+ break;
+ }
+
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(eventEntry);
+
+ PointerCoords scaledCoords[MAX_POINTERS];
+ const PointerCoords* usingCoords = motionEntry->pointerCoords;
+
+ // Set the X and Y offset depending on the input source.
+ float xOffset, yOffset, scaleFactor;
+ if ((motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
+ && !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
+ scaleFactor = dispatchEntry->scaleFactor;
+ xOffset = dispatchEntry->xOffset * scaleFactor;
+ yOffset = dispatchEntry->yOffset * scaleFactor;
+ if (scaleFactor != 1.0f) {
+ for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+ scaledCoords[i] = motionEntry->pointerCoords[i];
+ scaledCoords[i].scale(scaleFactor);
+ }
+ usingCoords = scaledCoords;
+ }
+ } else {
+ xOffset = 0.0f;
+ yOffset = 0.0f;
+ scaleFactor = 1.0f;
+
+ // We don't want the dispatch target to know.
+ if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) {
+ for (size_t i = 0; i < motionEntry->pointerCount; i++) {
+ scaledCoords[i].clear();
+ }
+ usingCoords = scaledCoords;
+ }
+ }
+
+ // Publish the motion event.
+ status = connection->inputPublisher.publishMotionEvent(dispatchEntry->seq,
+ motionEntry->deviceId, motionEntry->source,
+ dispatchEntry->resolvedAction, dispatchEntry->resolvedFlags,
+ motionEntry->edgeFlags, motionEntry->metaState, motionEntry->buttonState,
+ xOffset, yOffset,
+ motionEntry->xPrecision, motionEntry->yPrecision,
+ motionEntry->downTime, motionEntry->eventTime,
+ motionEntry->pointerCount, motionEntry->pointerProperties,
+ usingCoords);
+ break;
+ }
+
+ default:
+ ALOG_ASSERT(false);
+ return;
+ }
+
+ // Check the result.
+ if (status) {
+ if (status == WOULD_BLOCK) {
+ if (connection->waitQueue.isEmpty()) {
+ ALOGE("channel '%s' ~ Could not publish event because the pipe is full. "
+ "This is unexpected because the wait queue is empty, so the pipe "
+ "should be empty and we shouldn't have any problems writing an "
+ "event to it, status=%d", connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ } else {
+ // Pipe is full and we are waiting for the app to finish process some events
+ // before sending more events to it.
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ Could not publish event because the pipe is full, "
+ "waiting for the application to catch up",
+ connection->getInputChannelName());
+#endif
+ connection->inputPublisherBlocked = true;
+ }
+ } else {
+ ALOGE("channel '%s' ~ Could not publish event due to an unexpected error, "
+ "status=%d", connection->getInputChannelName(), status);
+ abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ }
+ return;
+ }
+
+ // Re-enqueue the event on the wait queue.
+ connection->outboundQueue.dequeue(dispatchEntry);
+ traceOutboundQueueLengthLocked(connection);
+ connection->waitQueue.enqueueAtTail(dispatchEntry);
+ traceWaitQueueLengthLocked(connection);
+ }
+}
+
+void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, uint32_t seq, bool handled) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s",
+ connection->getInputChannelName(), seq, toString(handled));
+#endif
+
+ connection->inputPublisherBlocked = false;
+
+ if (connection->status == Connection::STATUS_BROKEN
+ || connection->status == Connection::STATUS_ZOMBIE) {
+ return;
+ }
+
+ // Notify other system components and prepare to start the next dispatch cycle.
+ onDispatchCycleFinishedLocked(currentTime, connection, seq, handled);
+}
+
+void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection, bool notify) {
+#if DEBUG_DISPATCH_CYCLE
+ ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s",
+ connection->getInputChannelName(), toString(notify));
+#endif
+
+ // Clear the dispatch queues.
+ drainDispatchQueueLocked(&connection->outboundQueue);
+ traceOutboundQueueLengthLocked(connection);
+ drainDispatchQueueLocked(&connection->waitQueue);
+ traceWaitQueueLengthLocked(connection);
+
+ // The connection appears to be unrecoverably broken.
+ // Ignore already broken or zombie connections.
+ if (connection->status == Connection::STATUS_NORMAL) {
+ connection->status = Connection::STATUS_BROKEN;
+
+ if (notify) {
+ // Notify other system components.
+ onDispatchCycleBrokenLocked(currentTime, connection);
+ }
+ }
+}
+
+void InputDispatcher::drainDispatchQueueLocked(Queue<DispatchEntry>* queue) {
+ while (!queue->isEmpty()) {
+ DispatchEntry* dispatchEntry = queue->dequeueAtHead();
+ releaseDispatchEntryLocked(dispatchEntry);
+ }
+}
+
+void InputDispatcher::releaseDispatchEntryLocked(DispatchEntry* dispatchEntry) {
+ if (dispatchEntry->hasForegroundTarget()) {
+ decrementPendingForegroundDispatchesLocked(dispatchEntry->eventEntry);
+ }
+ delete dispatchEntry;
+}
+
+int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {
+ InputDispatcher* d = static_cast<InputDispatcher*>(data);
+
+ { // acquire lock
+ AutoMutex _l(d->mLock);
+
+ ssize_t connectionIndex = d->mConnectionsByFd.indexOfKey(fd);
+ if (connectionIndex < 0) {
+ ALOGE("Received spurious receive callback for unknown input channel. "
+ "fd=%d, events=0x%x", fd, events);
+ return 0; // remove the callback
+ }
+
+ bool notify;
+ sp<Connection> connection = d->mConnectionsByFd.valueAt(connectionIndex);
+ if (!(events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP))) {
+ if (!(events & ALOOPER_EVENT_INPUT)) {
+ ALOGW("channel '%s' ~ Received spurious callback for unhandled poll event. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ return 1;
+ }
+
+ nsecs_t currentTime = now();
+ bool gotOne = false;
+ status_t status;
+ for (;;) {
+ uint32_t seq;
+ bool handled;
+ status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);
+ if (status) {
+ break;
+ }
+ d->finishDispatchCycleLocked(currentTime, connection, seq, handled);
+ gotOne = true;
+ }
+ if (gotOne) {
+ d->runCommandsLockedInterruptible();
+ if (status == WOULD_BLOCK) {
+ return 1;
+ }
+ }
+
+ notify = status != DEAD_OBJECT || !connection->monitor;
+ if (notify) {
+ ALOGE("channel '%s' ~ Failed to receive finished signal. status=%d",
+ connection->getInputChannelName(), status);
+ }
+ } else {
+ // Monitor channels are never explicitly unregistered.
+ // We do it automatically when the remote endpoint is closed so don't warn
+ // about them.
+ notify = !connection->monitor;
+ if (notify) {
+ ALOGW("channel '%s' ~ Consumer closed input channel or an error occurred. "
+ "events=0x%x", connection->getInputChannelName(), events);
+ }
+ }
+
+ // Unregister the channel.
+ d->unregisterInputChannelLocked(connection->inputChannel, notify);
+ return 0; // remove the callback
+ } // release lock
+}
+
+void InputDispatcher::synthesizeCancelationEventsForAllConnectionsLocked(
+ const CancelationOptions& options) {
+ for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByFd.valueAt(i), options);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
+ const sp<InputChannel>& channel, const CancelationOptions& options) {
+ ssize_t index = getConnectionIndexLocked(channel);
+ if (index >= 0) {
+ synthesizeCancelationEventsForConnectionLocked(
+ mConnectionsByFd.valueAt(index), options);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
+ const sp<Connection>& connection, const CancelationOptions& options) {
+ if (connection->status == Connection::STATUS_BROKEN) {
+ return;
+ }
+
+ nsecs_t currentTime = now();
+
+ Vector<EventEntry*> cancelationEvents;
+ connection->inputState.synthesizeCancelationEvents(currentTime,
+ cancelationEvents, options);
+
+ if (!cancelationEvents.isEmpty()) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("channel '%s' ~ Synthesized %d cancelation events to bring channel back in sync "
+ "with reality: %s, mode=%d.",
+ connection->getInputChannelName(), cancelationEvents.size(),
+ options.reason, options.mode);
+#endif
+ for (size_t i = 0; i < cancelationEvents.size(); i++) {
+ EventEntry* cancelationEventEntry = cancelationEvents.itemAt(i);
+ switch (cancelationEventEntry->type) {
+ case EventEntry::TYPE_KEY:
+ logOutboundKeyDetailsLocked("cancel - ",
+ static_cast<KeyEntry*>(cancelationEventEntry));
+ break;
+ case EventEntry::TYPE_MOTION:
+ logOutboundMotionDetailsLocked("cancel - ",
+ static_cast<MotionEntry*>(cancelationEventEntry));
+ break;
+ }
+
+ InputTarget target;
+ sp<InputWindowHandle> windowHandle = getWindowHandleLocked(connection->inputChannel);
+ if (windowHandle != NULL) {
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+ target.xOffset = -windowInfo->frameLeft;
+ target.yOffset = -windowInfo->frameTop;
+ target.scaleFactor = windowInfo->scaleFactor;
+ } else {
+ target.xOffset = 0;
+ target.yOffset = 0;
+ target.scaleFactor = 1.0f;
+ }
+ target.inputChannel = connection->inputChannel;
+ target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+
+ enqueueDispatchEntryLocked(connection, cancelationEventEntry, // increments ref
+ &target, InputTarget::FLAG_DISPATCH_AS_IS);
+
+ cancelationEventEntry->release();
+ }
+
+ startDispatchCycleLocked(currentTime, connection);
+ }
+}
+
+InputDispatcher::MotionEntry*
+InputDispatcher::splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds) {
+ ALOG_ASSERT(pointerIds.value != 0);
+
+ 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->pointerCoords[originalPointerIndex]);
+ splitPointerCount += 1;
+ }
+ }
+
+ if (splitPointerCount != pointerIds.count()) {
+ // This is bad. We are missing some of the pointers that we expected to deliver.
+ // Most likely this indicates that we received an ACTION_MOVE events that has
+ // different pointer ids than we expected based on the previous ACTION_DOWN
+ // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
+ // in this way.
+ ALOGW("Dropping split motion event because the pointer count is %d but "
+ "we expected there to be %d pointers. This probably means we received "
+ "a broken sequence of pointer ids from the input device.",
+ splitPointerCount, pointerIds.count());
+ return NULL;
+ }
+
+ int32_t action = originalMotionEntry->action;
+ int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+ if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+ || maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+ int32_t originalPointerIndex = getMotionEventActionPointerIndex(action);
+ const PointerProperties& pointerProperties =
+ originalMotionEntry->pointerProperties[originalPointerIndex];
+ uint32_t pointerId = uint32_t(pointerProperties.id);
+ if (pointerIds.hasBit(pointerId)) {
+ if (pointerIds.count() == 1) {
+ // The first/last pointer went down/up.
+ action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
+ ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+ } else {
+ // A secondary pointer went down/up.
+ uint32_t splitPointerIndex = 0;
+ while (pointerId != uint32_t(splitPointerProperties[splitPointerIndex].id)) {
+ splitPointerIndex += 1;
+ }
+ action = maskedAction | (splitPointerIndex
+ << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ }
+ } else {
+ // An unrelated pointer changed.
+ action = AMOTION_EVENT_ACTION_MOVE;
+ }
+ }
+
+ MotionEntry* splitMotionEntry = new MotionEntry(
+ originalMotionEntry->eventTime,
+ originalMotionEntry->deviceId,
+ originalMotionEntry->source,
+ originalMotionEntry->policyFlags,
+ action,
+ originalMotionEntry->flags,
+ originalMotionEntry->metaState,
+ originalMotionEntry->buttonState,
+ originalMotionEntry->edgeFlags,
+ originalMotionEntry->xPrecision,
+ originalMotionEntry->yPrecision,
+ originalMotionEntry->downTime,
+ originalMotionEntry->displayId,
+ splitPointerCount, splitPointerProperties, splitPointerCoords, 0, 0);
+
+ 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_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);
+
+ bool needWake;
+ { // acquire lock
+ mLock.lock();
+
+ if (shouldSendKeyToInputFilterLocked(args)) {
+ mLock.unlock();
+
+ policyFlags |= POLICY_FLAG_FILTERED;
+ if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+ return; // event was consumed by the filter
+ }
+
+ mLock.lock();
+ }
+
+ int32_t repeatCount = 0;
+ KeyEntry* newEntry = new KeyEntry(args->eventTime,
+ args->deviceId, args->source, policyFlags,
+ args->action, flags, args->keyCode, args->scanCode,
+ metaState, repeatCount, args->downTime);
+
+ needWake = enqueueInboundEventLocked(newEntry);
+ mLock.unlock();
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) {
+ return mInputFilterEnabled;
+}
+
+void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyMotion - eventTime=%lld, deviceId=%d, source=0x%x, policyFlags=0x%x, "
+ "action=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, edgeFlags=0x%x, "
+ "xPrecision=%f, yPrecision=%f, downTime=%lld",
+ args->eventTime, args->deviceId, args->source, args->policyFlags,
+ args->action, args->flags, args->metaState, args->buttonState,
+ args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime);
+ for (uint32_t i = 0; i < args->pointerCount; i++) {
+ ALOGD(" Pointer %d: id=%d, toolType=%d, "
+ "x=%f, y=%f, pressure=%f, size=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
+ "orientation=%f",
+ i, args->pointerProperties[i].id,
+ args->pointerProperties[i].toolType,
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+ }
+#endif
+ if (!validateMotionEvent(args->action, args->pointerCount, args->pointerProperties)) {
+ return;
+ }
+
+ uint32_t policyFlags = args->policyFlags;
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags);
+
+ bool needWake;
+ { // acquire lock
+ mLock.lock();
+
+ if (shouldSendMotionToInputFilterLocked(args)) {
+ mLock.unlock();
+
+ MotionEvent event;
+ event.initialize(args->deviceId, args->source, args->action, args->flags,
+ args->edgeFlags, args->metaState, args->buttonState, 0, 0,
+ args->xPrecision, args->yPrecision,
+ args->downTime, args->eventTime,
+ args->pointerCount, args->pointerProperties, args->pointerCoords);
+
+ policyFlags |= POLICY_FLAG_FILTERED;
+ if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+ return; // event was consumed by the filter
+ }
+
+ mLock.lock();
+ }
+
+ // Just enqueue a new motion event.
+ MotionEntry* newEntry = new MotionEntry(args->eventTime,
+ args->deviceId, args->source, policyFlags,
+ args->action, args->flags, args->metaState, args->buttonState,
+ args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
+ args->displayId,
+ args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);
+
+ needWake = enqueueInboundEventLocked(newEntry);
+ mLock.unlock();
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) {
+ // TODO: support sending secondary display events to input filter
+ return mInputFilterEnabled && isMainDisplay(args->displayId);
+}
+
+void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifySwitch - eventTime=%lld, policyFlags=0x%x, switchValues=0x%08x, switchMask=0x%08x",
+ args->eventTime, args->policyFlags,
+ args->switchValues, args->switchMask);
+#endif
+
+ uint32_t policyFlags = args->policyFlags;
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ mPolicy->notifySwitch(args->eventTime,
+ args->switchValues, args->switchMask, policyFlags);
+}
+
+void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("notifyDeviceReset - eventTime=%lld, deviceId=%d",
+ args->eventTime, args->deviceId);
+#endif
+
+ bool needWake;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ DeviceResetEntry* newEntry = new DeviceResetEntry(args->eventTime, args->deviceId);
+ needWake = enqueueInboundEventLocked(newEntry);
+ } // release lock
+
+ if (needWake) {
+ mLooper->wake();
+ }
+}
+
+int32_t InputDispatcher::injectInputEvent(const InputEvent* event, int32_t displayId,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags) {
+#if DEBUG_INBOUND_EVENT_DETAILS
+ ALOGD("injectInputEvent - eventType=%d, injectorPid=%d, injectorUid=%d, "
+ "syncMode=%d, timeoutMillis=%d, policyFlags=0x%08x",
+ event->getType(), injectorPid, injectorUid, syncMode, timeoutMillis, policyFlags);
+#endif
+
+ nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
+
+ policyFlags |= POLICY_FLAG_INJECTED;
+ if (hasInjectionPermission(injectorPid, injectorUid)) {
+ policyFlags |= POLICY_FLAG_TRUSTED;
+ }
+
+ EventEntry* firstInjectedEntry;
+ EventEntry* lastInjectedEntry;
+ switch (event->getType()) {
+ case AINPUT_EVENT_TYPE_KEY: {
+ const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
+ int32_t action = keyEvent->getAction();
+ if (! validateKeyEvent(action)) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ int32_t flags = keyEvent->getFlags();
+ if (flags & AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY) {
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+ }
+
+ if (!(policyFlags & POLICY_FLAG_FILTERED)) {
+ mPolicy->interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
+ }
+
+ mLock.lock();
+ firstInjectedEntry = new KeyEntry(keyEvent->getEventTime(),
+ keyEvent->getDeviceId(), keyEvent->getSource(),
+ policyFlags, action, flags,
+ keyEvent->getKeyCode(), keyEvent->getScanCode(), keyEvent->getMetaState(),
+ keyEvent->getRepeatCount(), keyEvent->getDownTime());
+ lastInjectedEntry = firstInjectedEntry;
+ break;
+ }
+
+ case AINPUT_EVENT_TYPE_MOTION: {
+ const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
+ int32_t action = motionEvent->getAction();
+ size_t pointerCount = motionEvent->getPointerCount();
+ const PointerProperties* pointerProperties = motionEvent->getPointerProperties();
+ if (! validateMotionEvent(action, pointerCount, pointerProperties)) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ if (!(policyFlags & POLICY_FLAG_FILTERED)) {
+ nsecs_t eventTime = motionEvent->getEventTime();
+ mPolicy->interceptMotionBeforeQueueing(eventTime, /*byref*/ policyFlags);
+ }
+
+ mLock.lock();
+ const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
+ const PointerCoords* samplePointerCoords = motionEvent->getSamplePointerCoords();
+ firstInjectedEntry = new MotionEntry(*sampleEventTimes,
+ motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
+ action, motionEvent->getFlags(),
+ motionEvent->getMetaState(), motionEvent->getButtonState(),
+ motionEvent->getEdgeFlags(),
+ motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+ motionEvent->getDownTime(), displayId,
+ uint32_t(pointerCount), pointerProperties, samplePointerCoords,
+ motionEvent->getXOffset(), motionEvent->getYOffset());
+ lastInjectedEntry = firstInjectedEntry;
+ for (size_t i = motionEvent->getHistorySize(); i > 0; i--) {
+ sampleEventTimes += 1;
+ samplePointerCoords += pointerCount;
+ MotionEntry* nextInjectedEntry = new MotionEntry(*sampleEventTimes,
+ motionEvent->getDeviceId(), motionEvent->getSource(), policyFlags,
+ action, motionEvent->getFlags(),
+ motionEvent->getMetaState(), motionEvent->getButtonState(),
+ motionEvent->getEdgeFlags(),
+ motionEvent->getXPrecision(), motionEvent->getYPrecision(),
+ motionEvent->getDownTime(), displayId,
+ uint32_t(pointerCount), pointerProperties, samplePointerCoords,
+ motionEvent->getXOffset(), motionEvent->getYOffset());
+ lastInjectedEntry->next = nextInjectedEntry;
+ lastInjectedEntry = nextInjectedEntry;
+ }
+ break;
+ }
+
+ default:
+ ALOGW("Cannot inject event of type %d", event->getType());
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
+ InjectionState* injectionState = new InjectionState(injectorPid, injectorUid);
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionState->injectionIsAsync = true;
+ }
+
+ injectionState->refCount += 1;
+ lastInjectedEntry->injectionState = injectionState;
+
+ bool needWake = false;
+ for (EventEntry* entry = firstInjectedEntry; entry != NULL; ) {
+ EventEntry* nextEntry = entry->next;
+ needWake |= enqueueInboundEventLocked(entry);
+ entry = nextEntry;
+ }
+
+ mLock.unlock();
+
+ if (needWake) {
+ mLooper->wake();
+ }
+
+ int32_t injectionResult;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (syncMode == INPUT_EVENT_INJECTION_SYNC_NONE) {
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ } else {
+ for (;;) {
+ injectionResult = injectionState->injectionResult;
+ if (injectionResult != INPUT_EVENT_INJECTION_PENDING) {
+ break;
+ }
+
+ nsecs_t remainingTimeout = endTime - now();
+ if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Timed out waiting for injection result "
+ "to become available.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+ break;
+ }
+
+ mInjectionResultAvailableCondition.waitRelative(mLock, remainingTimeout);
+ }
+
+ if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED
+ && syncMode == INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED) {
+ while (injectionState->pendingForegroundDispatches != 0) {
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Waiting for %d pending foreground dispatches.",
+ injectionState->pendingForegroundDispatches);
+#endif
+ nsecs_t remainingTimeout = endTime - now();
+ if (remainingTimeout <= 0) {
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Timed out waiting for pending foreground "
+ "dispatches to finish.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_TIMED_OUT;
+ break;
+ }
+
+ mInjectionSyncFinishedCondition.waitRelative(mLock, remainingTimeout);
+ }
+ }
+ }
+
+ injectionState->release();
+ } // release lock
+
+#if DEBUG_INJECTION
+ ALOGD("injectInputEvent - Finished with result %d. "
+ "injectorPid=%d, injectorUid=%d",
+ injectionResult, injectorPid, injectorUid);
+#endif
+
+ return injectionResult;
+}
+
+bool InputDispatcher::hasInjectionPermission(int32_t injectorPid, int32_t injectorUid) {
+ return injectorUid == 0
+ || mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid);
+}
+
+void InputDispatcher::setInjectionResultLocked(EventEntry* entry, int32_t injectionResult) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+#if DEBUG_INJECTION
+ ALOGD("Setting input event injection result to %d. "
+ "injectorPid=%d, injectorUid=%d",
+ injectionResult, injectionState->injectorPid, injectionState->injectorUid);
+#endif
+
+ if (injectionState->injectionIsAsync
+ && !(entry->policyFlags & POLICY_FLAG_FILTERED)) {
+ // Log the outcome since the injector did not wait for the injection result.
+ switch (injectionResult) {
+ case INPUT_EVENT_INJECTION_SUCCEEDED:
+ ALOGV("Asynchronous input event injection succeeded.");
+ break;
+ case INPUT_EVENT_INJECTION_FAILED:
+ ALOGW("Asynchronous input event injection failed.");
+ break;
+ case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
+ ALOGW("Asynchronous input event injection permission denied.");
+ break;
+ case INPUT_EVENT_INJECTION_TIMED_OUT:
+ ALOGW("Asynchronous input event injection timed out.");
+ break;
+ }
+ }
+
+ injectionState->injectionResult = injectionResult;
+ mInjectionResultAvailableCondition.broadcast();
+ }
+}
+
+void InputDispatcher::incrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+ injectionState->pendingForegroundDispatches += 1;
+ }
+}
+
+void InputDispatcher::decrementPendingForegroundDispatchesLocked(EventEntry* entry) {
+ InjectionState* injectionState = entry->injectionState;
+ if (injectionState) {
+ injectionState->pendingForegroundDispatches -= 1;
+
+ if (injectionState->pendingForegroundDispatches == 0) {
+ mInjectionSyncFinishedCondition.broadcast();
+ }
+ }
+}
+
+sp<InputWindowHandle> InputDispatcher::getWindowHandleLocked(
+ const sp<InputChannel>& inputChannel) const {
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ if (windowHandle->getInputChannel() == inputChannel) {
+ return windowHandle;
+ }
+ }
+ return NULL;
+}
+
+bool InputDispatcher::hasWindowHandleLocked(
+ const sp<InputWindowHandle>& windowHandle) const {
+ size_t numWindows = mWindowHandles.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ if (mWindowHandles.itemAt(i) == windowHandle) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void InputDispatcher::setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) {
+#if DEBUG_FOCUS
+ ALOGD("setInputWindows");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ Vector<sp<InputWindowHandle> > oldWindowHandles = mWindowHandles;
+ mWindowHandles = inputWindowHandles;
+
+ sp<InputWindowHandle> newFocusedWindowHandle;
+ bool foundHoveredWindow = false;
+ for (size_t i = 0; i < mWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ if (!windowHandle->updateInfo() || windowHandle->getInputChannel() == NULL) {
+ mWindowHandles.removeAt(i--);
+ continue;
+ }
+ if (windowHandle->getInfo()->hasFocus) {
+ newFocusedWindowHandle = windowHandle;
+ }
+ if (windowHandle == mLastHoverWindowHandle) {
+ foundHoveredWindow = true;
+ }
+ }
+
+ if (!foundHoveredWindow) {
+ mLastHoverWindowHandle = NULL;
+ }
+
+ if (mFocusedWindowHandle != newFocusedWindowHandle) {
+ if (mFocusedWindowHandle != NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Focus left window: %s",
+ mFocusedWindowHandle->getName().string());
+#endif
+ sp<InputChannel> focusedInputChannel = mFocusedWindowHandle->getInputChannel();
+ if (focusedInputChannel != NULL) {
+ CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
+ "focus left window");
+ synthesizeCancelationEventsForInputChannelLocked(
+ focusedInputChannel, options);
+ }
+ }
+ if (newFocusedWindowHandle != NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Focus entered window: %s",
+ newFocusedWindowHandle->getName().string());
+#endif
+ }
+ mFocusedWindowHandle = newFocusedWindowHandle;
+ }
+
+ for (size_t d = 0; d < mTouchStatesByDisplay.size(); d++) {
+ TouchState& state = mTouchStatesByDisplay.editValueAt(d);
+ for (size_t i = 0; i < state.windows.size(); i++) {
+ TouchedWindow& touchedWindow = state.windows.editItemAt(i);
+ if (!hasWindowHandleLocked(touchedWindow.windowHandle)) {
+#if DEBUG_FOCUS
+ ALOGD("Touched window was removed: %s",
+ touchedWindow.windowHandle->getName().string());
+#endif
+ sp<InputChannel> touchedInputChannel =
+ touchedWindow.windowHandle->getInputChannel();
+ if (touchedInputChannel != NULL) {
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ "touched window was removed");
+ synthesizeCancelationEventsForInputChannelLocked(
+ touchedInputChannel, options);
+ }
+ state.windows.removeAt(i--);
+ }
+ }
+ }
+
+ // Release information for windows that are no longer present.
+ // This ensures that unused input channels are released promptly.
+ // Otherwise, they might stick around until the window handle is destroyed
+ // which might not happen until the next GC.
+ for (size_t i = 0; i < oldWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& oldWindowHandle = oldWindowHandles.itemAt(i);
+ if (!hasWindowHandleLocked(oldWindowHandle)) {
+#if DEBUG_FOCUS
+ ALOGD("Window went away: %s", oldWindowHandle->getName().string());
+#endif
+ oldWindowHandle->releaseInfo();
+ }
+ }
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+}
+
+void InputDispatcher::setFocusedApplication(
+ const sp<InputApplicationHandle>& inputApplicationHandle) {
+#if DEBUG_FOCUS
+ ALOGD("setFocusedApplication");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (inputApplicationHandle != NULL && inputApplicationHandle->updateInfo()) {
+ if (mFocusedApplicationHandle != inputApplicationHandle) {
+ if (mFocusedApplicationHandle != NULL) {
+ resetANRTimeoutsLocked();
+ mFocusedApplicationHandle->releaseInfo();
+ }
+ mFocusedApplicationHandle = inputApplicationHandle;
+ }
+ } else if (mFocusedApplicationHandle != NULL) {
+ resetANRTimeoutsLocked();
+ mFocusedApplicationHandle->releaseInfo();
+ mFocusedApplicationHandle.clear();
+ }
+
+#if DEBUG_FOCUS
+ //logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+}
+
+void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
+#if DEBUG_FOCUS
+ ALOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
+#endif
+
+ bool changed;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
+ if (mDispatchFrozen && !frozen) {
+ resetANRTimeoutsLocked();
+ }
+
+ if (mDispatchEnabled && !enabled) {
+ resetAndDropEverythingLocked("dispatcher is being disabled");
+ }
+
+ mDispatchEnabled = enabled;
+ mDispatchFrozen = frozen;
+ changed = true;
+ } else {
+ changed = false;
+ }
+
+#if DEBUG_FOCUS
+ //logDispatchStateLocked();
+#endif
+ } // release lock
+
+ if (changed) {
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+ }
+}
+
+void InputDispatcher::setInputFilterEnabled(bool enabled) {
+#if DEBUG_FOCUS
+ ALOGD("setInputFilterEnabled: enabled=%d", enabled);
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mInputFilterEnabled == enabled) {
+ return;
+ }
+
+ mInputFilterEnabled = enabled;
+ resetAndDropEverythingLocked("input filter is being enabled or disabled");
+ } // release lock
+
+ // Wake up poll loop since there might be work to do to drop everything.
+ mLooper->wake();
+}
+
+bool InputDispatcher::transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) {
+#if DEBUG_FOCUS
+ ALOGD("transferTouchFocus: fromChannel=%s, toChannel=%s",
+ fromChannel->getName().string(), toChannel->getName().string());
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ sp<InputWindowHandle> fromWindowHandle = getWindowHandleLocked(fromChannel);
+ sp<InputWindowHandle> toWindowHandle = getWindowHandleLocked(toChannel);
+ if (fromWindowHandle == NULL || toWindowHandle == NULL) {
+#if DEBUG_FOCUS
+ ALOGD("Cannot transfer focus because from or to window not found.");
+#endif
+ return false;
+ }
+ if (fromWindowHandle == toWindowHandle) {
+#if DEBUG_FOCUS
+ ALOGD("Trivial transfer to same window.");
+#endif
+ return true;
+ }
+ if (fromWindowHandle->getInfo()->displayId != toWindowHandle->getInfo()->displayId) {
+#if DEBUG_FOCUS
+ ALOGD("Cannot transfer focus because windows are on different displays.");
+#endif
+ return false;
+ }
+
+ bool found = false;
+ for (size_t d = 0; d < mTouchStatesByDisplay.size(); d++) {
+ TouchState& state = mTouchStatesByDisplay.editValueAt(d);
+ for (size_t i = 0; i < state.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = state.windows[i];
+ if (touchedWindow.windowHandle == fromWindowHandle) {
+ int32_t oldTargetFlags = touchedWindow.targetFlags;
+ BitSet32 pointerIds = touchedWindow.pointerIds;
+
+ state.windows.removeAt(i);
+
+ int32_t newTargetFlags = oldTargetFlags
+ & (InputTarget::FLAG_FOREGROUND
+ | InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
+ state.addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds);
+
+ found = true;
+ goto Found;
+ }
+ }
+ }
+Found:
+
+ if (! found) {
+#if DEBUG_FOCUS
+ ALOGD("Focus transfer failed because from window did not have focus.");
+#endif
+ return false;
+ }
+
+ ssize_t fromConnectionIndex = getConnectionIndexLocked(fromChannel);
+ ssize_t toConnectionIndex = getConnectionIndexLocked(toChannel);
+ if (fromConnectionIndex >= 0 && toConnectionIndex >= 0) {
+ sp<Connection> fromConnection = mConnectionsByFd.valueAt(fromConnectionIndex);
+ sp<Connection> toConnection = mConnectionsByFd.valueAt(toConnectionIndex);
+
+ fromConnection->inputState.copyPointerStateTo(toConnection->inputState);
+ CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ "transferring touch focus from this window to another window");
+ synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
+ }
+
+#if DEBUG_FOCUS
+ logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mLooper->wake();
+ return true;
+}
+
+void InputDispatcher::resetAndDropEverythingLocked(const char* reason) {
+#if DEBUG_FOCUS
+ ALOGD("Resetting and dropping all events (%s).", reason);
+#endif
+
+ CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, reason);
+ synthesizeCancelationEventsForAllConnectionsLocked(options);
+
+ resetKeyRepeatLocked();
+ releasePendingEventLocked();
+ drainInboundQueueLocked();
+ resetANRTimeoutsLocked();
+
+ mTouchStatesByDisplay.clear();
+ mLastHoverWindowHandle.clear();
+}
+
+void InputDispatcher::logDispatchStateLocked() {
+ String8 dump;
+ dumpDispatchStateLocked(dump);
+
+ char* text = dump.lockBuffer(dump.size());
+ char* start = text;
+ while (*start != '\0') {
+ char* end = strchr(start, '\n');
+ if (*end == '\n') {
+ *(end++) = '\0';
+ }
+ ALOGD("%s", start);
+ start = end;
+ }
+}
+
+void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
+ dump.appendFormat(INDENT "DispatchEnabled: %d\n", mDispatchEnabled);
+ dump.appendFormat(INDENT "DispatchFrozen: %d\n", mDispatchFrozen);
+
+ if (mFocusedApplicationHandle != NULL) {
+ dump.appendFormat(INDENT "FocusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
+ mFocusedApplicationHandle->getName().string(),
+ mFocusedApplicationHandle->getDispatchingTimeout(
+ DEFAULT_INPUT_DISPATCHING_TIMEOUT) / 1000000.0);
+ } else {
+ dump.append(INDENT "FocusedApplication: <null>\n");
+ }
+ dump.appendFormat(INDENT "FocusedWindow: name='%s'\n",
+ mFocusedWindowHandle != NULL ? mFocusedWindowHandle->getName().string() : "<null>");
+
+ if (!mTouchStatesByDisplay.isEmpty()) {
+ dump.appendFormat(INDENT "TouchStatesByDisplay:\n");
+ for (size_t i = 0; i < mTouchStatesByDisplay.size(); i++) {
+ const TouchState& state = mTouchStatesByDisplay.valueAt(i);
+ dump.appendFormat(INDENT2 "%d: down=%s, split=%s, deviceId=%d, source=0x%08x\n",
+ state.displayId, toString(state.down), toString(state.split),
+ state.deviceId, state.source);
+ if (!state.windows.isEmpty()) {
+ dump.append(INDENT3 "Windows:\n");
+ for (size_t i = 0; i < state.windows.size(); i++) {
+ const TouchedWindow& touchedWindow = state.windows[i];
+ dump.appendFormat(INDENT4 "%zu: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
+ i, touchedWindow.windowHandle->getName().string(),
+ touchedWindow.pointerIds.value,
+ touchedWindow.targetFlags);
+ }
+ } else {
+ dump.append(INDENT3 "Windows: <none>\n");
+ }
+ }
+ } else {
+ dump.append(INDENT "TouchStates: <no displays touched>\n");
+ }
+
+ if (!mWindowHandles.isEmpty()) {
+ dump.append(INDENT "Windows:\n");
+ for (size_t i = 0; i < mWindowHandles.size(); i++) {
+ const sp<InputWindowHandle>& windowHandle = mWindowHandles.itemAt(i);
+ const InputWindowInfo* windowInfo = windowHandle->getInfo();
+
+ dump.appendFormat(INDENT2 "%zu: name='%s', displayId=%d, "
+ "paused=%s, hasFocus=%s, hasWallpaper=%s, "
+ "visible=%s, canReceiveKeys=%s, flags=0x%08x, type=0x%08x, layer=%d, "
+ "frame=[%d,%d][%d,%d], scale=%f, "
+ "touchableRegion=",
+ i, windowInfo->name.string(), windowInfo->displayId,
+ toString(windowInfo->paused),
+ toString(windowInfo->hasFocus),
+ toString(windowInfo->hasWallpaper),
+ toString(windowInfo->visible),
+ toString(windowInfo->canReceiveKeys),
+ windowInfo->layoutParamsFlags, windowInfo->layoutParamsType,
+ windowInfo->layer,
+ windowInfo->frameLeft, windowInfo->frameTop,
+ windowInfo->frameRight, windowInfo->frameBottom,
+ windowInfo->scaleFactor);
+ dumpRegion(dump, windowInfo->touchableRegion);
+ dump.appendFormat(", inputFeatures=0x%08x", windowInfo->inputFeatures);
+ dump.appendFormat(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+ windowInfo->ownerPid, windowInfo->ownerUid,
+ windowInfo->dispatchingTimeout / 1000000.0);
+ }
+ } else {
+ dump.append(INDENT "Windows: <none>\n");
+ }
+
+ if (!mMonitoringChannels.isEmpty()) {
+ dump.append(INDENT "MonitoringChannels:\n");
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ const sp<InputChannel>& channel = mMonitoringChannels[i];
+ dump.appendFormat(INDENT2 "%zu: '%s'\n", i, channel->getName().string());
+ }
+ } else {
+ dump.append(INDENT "MonitoringChannels: <none>\n");
+ }
+
+ nsecs_t currentTime = now();
+
+ // Dump recently dispatched or dropped events from oldest to newest.
+ if (!mRecentQueue.isEmpty()) {
+ dump.appendFormat(INDENT "RecentQueue: length=%u\n", mRecentQueue.count());
+ for (EventEntry* entry = mRecentQueue.head; entry; entry = entry->next) {
+ dump.append(INDENT2);
+ entry->appendDescription(dump);
+ dump.appendFormat(", age=%0.1fms\n",
+ (currentTime - entry->eventTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT "RecentQueue: <empty>\n");
+ }
+
+ // Dump event currently being dispatched.
+ if (mPendingEvent) {
+ dump.append(INDENT "PendingEvent:\n");
+ dump.append(INDENT2);
+ mPendingEvent->appendDescription(dump);
+ dump.appendFormat(", age=%0.1fms\n",
+ (currentTime - mPendingEvent->eventTime) * 0.000001f);
+ } else {
+ dump.append(INDENT "PendingEvent: <none>\n");
+ }
+
+ // Dump inbound events from oldest to newest.
+ if (!mInboundQueue.isEmpty()) {
+ dump.appendFormat(INDENT "InboundQueue: length=%u\n", mInboundQueue.count());
+ for (EventEntry* entry = mInboundQueue.head; entry; entry = entry->next) {
+ dump.append(INDENT2);
+ entry->appendDescription(dump);
+ dump.appendFormat(", age=%0.1fms\n",
+ (currentTime - entry->eventTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT "InboundQueue: <empty>\n");
+ }
+
+ if (!mConnectionsByFd.isEmpty()) {
+ dump.append(INDENT "Connections:\n");
+ for (size_t i = 0; i < mConnectionsByFd.size(); i++) {
+ const sp<Connection>& connection = mConnectionsByFd.valueAt(i);
+ dump.appendFormat(INDENT2 "%zu: channelName='%s', windowName='%s', "
+ "status=%s, monitor=%s, inputPublisherBlocked=%s\n",
+ i, connection->getInputChannelName(), connection->getWindowName(),
+ connection->getStatusLabel(), toString(connection->monitor),
+ toString(connection->inputPublisherBlocked));
+
+ if (!connection->outboundQueue.isEmpty()) {
+ dump.appendFormat(INDENT3 "OutboundQueue: length=%u\n",
+ connection->outboundQueue.count());
+ for (DispatchEntry* entry = connection->outboundQueue.head; entry;
+ entry = entry->next) {
+ dump.append(INDENT4);
+ entry->eventEntry->appendDescription(dump);
+ dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, age=%0.1fms\n",
+ entry->targetFlags, entry->resolvedAction,
+ (currentTime - entry->eventEntry->eventTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT3 "OutboundQueue: <empty>\n");
+ }
+
+ if (!connection->waitQueue.isEmpty()) {
+ dump.appendFormat(INDENT3 "WaitQueue: length=%u\n",
+ connection->waitQueue.count());
+ for (DispatchEntry* entry = connection->waitQueue.head; entry;
+ entry = entry->next) {
+ dump.append(INDENT4);
+ entry->eventEntry->appendDescription(dump);
+ dump.appendFormat(", targetFlags=0x%08x, resolvedAction=%d, "
+ "age=%0.1fms, wait=%0.1fms\n",
+ entry->targetFlags, entry->resolvedAction,
+ (currentTime - entry->eventEntry->eventTime) * 0.000001f,
+ (currentTime - entry->deliveryTime) * 0.000001f);
+ }
+ } else {
+ dump.append(INDENT3 "WaitQueue: <empty>\n");
+ }
+ }
+ } else {
+ dump.append(INDENT "Connections: <none>\n");
+ }
+
+ if (isAppSwitchPendingLocked()) {
+ dump.appendFormat(INDENT "AppSwitch: pending, due in %0.1fms\n",
+ (mAppSwitchDueTime - now()) / 1000000.0);
+ } else {
+ dump.append(INDENT "AppSwitch: not pending\n");
+ }
+
+ dump.append(INDENT "Configuration:\n");
+ dump.appendFormat(INDENT2 "KeyRepeatDelay: %0.1fms\n",
+ mConfig.keyRepeatDelay * 0.000001f);
+ dump.appendFormat(INDENT2 "KeyRepeatTimeout: %0.1fms\n",
+ mConfig.keyRepeatTimeout * 0.000001f);
+}
+
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
+#if DEBUG_REGISTRATION
+ ALOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
+ toString(monitor));
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (getConnectionIndexLocked(inputChannel) >= 0) {
+ ALOGW("Attempted to register already registered input channel '%s'",
+ inputChannel->getName().string());
+ return BAD_VALUE;
+ }
+
+ sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);
+
+ int fd = inputChannel->getFd();
+ mConnectionsByFd.add(fd, connection);
+
+ if (monitor) {
+ mMonitoringChannels.push(inputChannel);
+ }
+
+ mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
+ } // release lock
+
+ // Wake the looper because some connections have changed.
+ mLooper->wake();
+ return OK;
+}
+
+status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
+#if DEBUG_REGISTRATION
+ ALOGD("channel '%s' ~ unregisterInputChannel", inputChannel->getName().string());
+#endif
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ status_t status = unregisterInputChannelLocked(inputChannel, false /*notify*/);
+ if (status) {
+ return status;
+ }
+ } // release lock
+
+ // Wake the poll loop because removing the connection may have changed the current
+ // synchronization state.
+ mLooper->wake();
+ return OK;
+}
+
+status_t InputDispatcher::unregisterInputChannelLocked(const sp<InputChannel>& inputChannel,
+ bool notify) {
+ ssize_t connectionIndex = getConnectionIndexLocked(inputChannel);
+ if (connectionIndex < 0) {
+ ALOGW("Attempted to unregister already unregistered input channel '%s'",
+ inputChannel->getName().string());
+ return BAD_VALUE;
+ }
+
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ mConnectionsByFd.removeItemsAt(connectionIndex);
+
+ if (connection->monitor) {
+ removeMonitorChannelLocked(inputChannel);
+ }
+
+ mLooper->removeFd(inputChannel->getFd());
+
+ nsecs_t currentTime = now();
+ abortBrokenDispatchCycleLocked(currentTime, connection, notify);
+
+ connection->status = Connection::STATUS_ZOMBIE;
+ return OK;
+}
+
+void InputDispatcher::removeMonitorChannelLocked(const sp<InputChannel>& inputChannel) {
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ if (mMonitoringChannels[i] == inputChannel) {
+ mMonitoringChannels.removeAt(i);
+ break;
+ }
+ }
+}
+
+ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
+ ssize_t connectionIndex = mConnectionsByFd.indexOfKey(inputChannel->getFd());
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
+ if (connection->inputChannel.get() == inputChannel.get()) {
+ return connectionIndex;
+ }
+ }
+
+ return -1;
+}
+
+void InputDispatcher::onDispatchCycleFinishedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doDispatchCycleFinishedLockedInterruptible);
+ commandEntry->connection = connection;
+ commandEntry->eventTime = currentTime;
+ commandEntry->seq = seq;
+ commandEntry->handled = handled;
+}
+
+void InputDispatcher::onDispatchCycleBrokenLocked(
+ nsecs_t currentTime, const sp<Connection>& connection) {
+ ALOGE("channel '%s' ~ Channel is unrecoverably broken and will be disposed!",
+ connection->getInputChannelName());
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible);
+ commandEntry->connection = connection;
+}
+
+void InputDispatcher::onANRLocked(
+ nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t eventTime, nsecs_t waitStartTime, const char* reason) {
+ float dispatchLatency = (currentTime - eventTime) * 0.000001f;
+ float waitDuration = (currentTime - waitStartTime) * 0.000001f;
+ ALOGI("Application is not responding: %s. "
+ "It has been %0.1fms since event, %0.1fms since wait started. Reason: %s",
+ getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
+ dispatchLatency, waitDuration, reason);
+
+ // Capture a record of the InputDispatcher state at the time of the ANR.
+ time_t t = time(NULL);
+ struct tm tm;
+ localtime_r(&t, &tm);
+ char timestr[64];
+ strftime(timestr, sizeof(timestr), "%F %T", &tm);
+ mLastANRState.clear();
+ mLastANRState.append(INDENT "ANR:\n");
+ mLastANRState.appendFormat(INDENT2 "Time: %s\n", timestr);
+ mLastANRState.appendFormat(INDENT2 "Window: %s\n",
+ getApplicationWindowLabelLocked(applicationHandle, windowHandle).string());
+ mLastANRState.appendFormat(INDENT2 "DispatchLatency: %0.1fms\n", dispatchLatency);
+ mLastANRState.appendFormat(INDENT2 "WaitDuration: %0.1fms\n", waitDuration);
+ mLastANRState.appendFormat(INDENT2 "Reason: %s\n", reason);
+ dumpDispatchStateLocked(mLastANRState);
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doNotifyANRLockedInterruptible);
+ commandEntry->inputApplicationHandle = applicationHandle;
+ commandEntry->inputWindowHandle = windowHandle;
+ commandEntry->reason = reason;
+}
+
+void InputDispatcher::doNotifyConfigurationChangedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
+
+ mLock.lock();
+}
+
+void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
+ CommandEntry* commandEntry) {
+ sp<Connection> connection = commandEntry->connection;
+
+ if (connection->status != Connection::STATUS_ZOMBIE) {
+ mLock.unlock();
+
+ mPolicy->notifyInputChannelBroken(connection->inputWindowHandle);
+
+ mLock.lock();
+ }
+}
+
+void InputDispatcher::doNotifyANRLockedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ nsecs_t newTimeout = mPolicy->notifyANR(
+ commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle,
+ commandEntry->reason);
+
+ mLock.lock();
+
+ resumeAfterTargetsNotReadyTimeoutLocked(newTimeout,
+ commandEntry->inputWindowHandle != NULL
+ ? commandEntry->inputWindowHandle->getInputChannel() : NULL);
+}
+
+void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
+ CommandEntry* commandEntry) {
+ KeyEntry* entry = commandEntry->keyEntry;
+
+ KeyEvent event;
+ initializeKeyEvent(&event, entry);
+
+ mLock.unlock();
+
+ nsecs_t delay = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputWindowHandle,
+ &event, entry->policyFlags);
+
+ mLock.lock();
+
+ if (delay < 0) {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
+ } else if (!delay) {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ } else {
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
+ entry->interceptKeyWakeupTime = now() + delay;
+ }
+ entry->release();
+}
+
+void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(
+ CommandEntry* commandEntry) {
+ sp<Connection> connection = commandEntry->connection;
+ nsecs_t finishTime = commandEntry->eventTime;
+ uint32_t seq = commandEntry->seq;
+ bool handled = commandEntry->handled;
+
+ // Handle post-event policy actions.
+ DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);
+ if (dispatchEntry) {
+ nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
+ if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
+ String8 msg;
+ msg.appendFormat("Window '%s' spent %0.1fms processing the last input event: ",
+ connection->getWindowName(), eventDuration * 0.000001f);
+ dispatchEntry->eventEntry->appendDescription(msg);
+ ALOGI("%s", msg.string());
+ }
+
+ bool restartEvent;
+ if (dispatchEntry->eventEntry->type == EventEntry::TYPE_KEY) {
+ KeyEntry* keyEntry = static_cast<KeyEntry*>(dispatchEntry->eventEntry);
+ restartEvent = afterKeyEventLockedInterruptible(connection,
+ dispatchEntry, keyEntry, handled);
+ } else if (dispatchEntry->eventEntry->type == EventEntry::TYPE_MOTION) {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(dispatchEntry->eventEntry);
+ restartEvent = afterMotionEventLockedInterruptible(connection,
+ dispatchEntry, motionEntry, handled);
+ } else {
+ restartEvent = false;
+ }
+
+ // Dequeue the event and start the next cycle.
+ // Note that because the lock might have been released, it is possible that the
+ // contents of the wait queue to have been drained, so we need to double-check
+ // a few things.
+ if (dispatchEntry == connection->findWaitQueueEntry(seq)) {
+ connection->waitQueue.dequeue(dispatchEntry);
+ traceWaitQueueLengthLocked(connection);
+ if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
+ connection->outboundQueue.enqueueAtHead(dispatchEntry);
+ traceOutboundQueueLengthLocked(connection);
+ } else {
+ releaseDispatchEntryLocked(dispatchEntry);
+ }
+ }
+
+ // Start the next dispatch cycle for this connection.
+ startDispatchCycleLocked(now(), connection);
+ }
+}
+
+bool InputDispatcher::afterKeyEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, KeyEntry* keyEntry, bool handled) {
+ if (!(keyEntry->flags & AKEY_EVENT_FLAG_FALLBACK)) {
+ // Get the fallback key state.
+ // Clear it out after dispatching the UP.
+ int32_t originalKeyCode = keyEntry->keyCode;
+ int32_t fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode);
+ if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
+ connection->inputState.removeFallbackKey(originalKeyCode);
+ }
+
+ if (handled || !dispatchEntry->hasForegroundTarget()) {
+ // If the application handles the original key for which we previously
+ // generated a fallback or if the window is not a foreground window,
+ // then cancel the associated fallback key, if any.
+ if (fallbackKeyCode != -1) {
+ // Dispatch the unhandled key to the policy with the cancel flag.
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Asking policy to cancel fallback action. "
+ "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+ keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount,
+ keyEntry->policyFlags);
+#endif
+ KeyEvent event;
+ initializeKeyEvent(&event, keyEntry);
+ event.setFlags(event.getFlags() | AKEY_EVENT_FLAG_CANCELED);
+
+ mLock.unlock();
+
+ mPolicy->dispatchUnhandledKey(connection->inputWindowHandle,
+ &event, keyEntry->policyFlags, &event);
+
+ mLock.lock();
+
+ // Cancel the fallback key.
+ if (fallbackKeyCode != AKEYCODE_UNKNOWN) {
+ CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+ "application handled the original non-fallback key "
+ "or is no longer a foreground target, "
+ "canceling previously dispatched fallback key");
+ options.keyCode = fallbackKeyCode;
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
+ }
+ connection->inputState.removeFallbackKey(originalKeyCode);
+ }
+ } else {
+ // If the application did not handle a non-fallback key, first check
+ // that we are in a good state to perform unhandled key event processing
+ // Then ask the policy what to do with it.
+ bool initialDown = keyEntry->action == AKEY_EVENT_ACTION_DOWN
+ && keyEntry->repeatCount == 0;
+ if (fallbackKeyCode == -1 && !initialDown) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Skipping unhandled key event processing "
+ "since this is not an initial down. "
+ "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+ originalKeyCode, keyEntry->action, keyEntry->repeatCount,
+ keyEntry->policyFlags);
+#endif
+ return false;
+ }
+
+ // Dispatch the unhandled key to the policy.
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Asking policy to perform fallback action. "
+ "keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
+ keyEntry->keyCode, keyEntry->action, keyEntry->repeatCount,
+ keyEntry->policyFlags);
+#endif
+ KeyEvent event;
+ initializeKeyEvent(&event, keyEntry);
+
+ mLock.unlock();
+
+ bool fallback = mPolicy->dispatchUnhandledKey(connection->inputWindowHandle,
+ &event, keyEntry->policyFlags, &event);
+
+ mLock.lock();
+
+ if (connection->status != Connection::STATUS_NORMAL) {
+ connection->inputState.removeFallbackKey(originalKeyCode);
+ return false;
+ }
+
+ // Latch the fallback keycode for this key on an initial down.
+ // The fallback keycode cannot change at any other point in the lifecycle.
+ if (initialDown) {
+ if (fallback) {
+ fallbackKeyCode = event.getKeyCode();
+ } else {
+ fallbackKeyCode = AKEYCODE_UNKNOWN;
+ }
+ connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode);
+ }
+
+ ALOG_ASSERT(fallbackKeyCode != -1);
+
+ // Cancel the fallback key if the policy decides not to send it anymore.
+ // We will continue to dispatch the key to the policy but we will no
+ // longer dispatch a fallback key to the application.
+ if (fallbackKeyCode != AKEYCODE_UNKNOWN
+ && (!fallback || fallbackKeyCode != event.getKeyCode())) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ if (fallback) {
+ ALOGD("Unhandled key event: Policy requested to send key %d"
+ "as a fallback for %d, but on the DOWN it had requested "
+ "to send %d instead. Fallback canceled.",
+ event.getKeyCode(), originalKeyCode, fallbackKeyCode);
+ } else {
+ ALOGD("Unhandled key event: Policy did not request fallback for %d, "
+ "but on the DOWN it had requested to send %d. "
+ "Fallback canceled.",
+ originalKeyCode, fallbackKeyCode);
+ }
+#endif
+
+ CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+ "canceling fallback, policy no longer desires it");
+ options.keyCode = fallbackKeyCode;
+ synthesizeCancelationEventsForConnectionLocked(connection, options);
+
+ fallback = false;
+ fallbackKeyCode = AKEYCODE_UNKNOWN;
+ if (keyEntry->action != AKEY_EVENT_ACTION_UP) {
+ connection->inputState.setFallbackKey(originalKeyCode,
+ fallbackKeyCode);
+ }
+ }
+
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ {
+ String8 msg;
+ const KeyedVector<int32_t, int32_t>& fallbackKeys =
+ connection->inputState.getFallbackKeys();
+ for (size_t i = 0; i < fallbackKeys.size(); i++) {
+ msg.appendFormat(", %d->%d", fallbackKeys.keyAt(i),
+ fallbackKeys.valueAt(i));
+ }
+ ALOGD("Unhandled key event: %d currently tracked fallback keys%s.",
+ fallbackKeys.size(), msg.string());
+ }
+#endif
+
+ if (fallback) {
+ // Restart the dispatch cycle using the fallback key.
+ keyEntry->eventTime = event.getEventTime();
+ keyEntry->deviceId = event.getDeviceId();
+ keyEntry->source = event.getSource();
+ keyEntry->flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK;
+ keyEntry->keyCode = fallbackKeyCode;
+ keyEntry->scanCode = event.getScanCode();
+ keyEntry->metaState = event.getMetaState();
+ keyEntry->repeatCount = event.getRepeatCount();
+ keyEntry->downTime = event.getDownTime();
+ keyEntry->syntheticRepeat = false;
+
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: Dispatching fallback key. "
+ "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
+ originalKeyCode, fallbackKeyCode, keyEntry->metaState);
+#endif
+ return true; // restart the event
+ } else {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Unhandled key event: No fallback key.");
+#endif
+ }
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::afterMotionEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, MotionEntry* motionEntry, bool handled) {
+ return false;
+}
+
+void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->userActivityEventType);
+
+ mLock.lock();
+}
+
+void InputDispatcher::initializeKeyEvent(KeyEvent* event, const KeyEntry* entry) {
+ event->initialize(entry->deviceId, entry->source, entry->action, entry->flags,
+ entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+ entry->downTime, entry->eventTime);
+}
+
+void InputDispatcher::updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+ int32_t injectionResult, nsecs_t timeSpentWaitingForApplication) {
+ // TODO Write some statistics about how long we spend waiting.
+}
+
+void InputDispatcher::traceInboundQueueLengthLocked() {
+ if (ATRACE_ENABLED()) {
+ ATRACE_INT("iq", mInboundQueue.count());
+ }
+}
+
+void InputDispatcher::traceOutboundQueueLengthLocked(const sp<Connection>& connection) {
+ if (ATRACE_ENABLED()) {
+ char counterName[40];
+ snprintf(counterName, sizeof(counterName), "oq:%s", connection->getWindowName());
+ ATRACE_INT(counterName, connection->outboundQueue.count());
+ }
+}
+
+void InputDispatcher::traceWaitQueueLengthLocked(const sp<Connection>& connection) {
+ if (ATRACE_ENABLED()) {
+ char counterName[40];
+ snprintf(counterName, sizeof(counterName), "wq:%s", connection->getWindowName());
+ ATRACE_INT(counterName, connection->waitQueue.count());
+ }
+}
+
+void InputDispatcher::dump(String8& dump) {
+ AutoMutex _l(mLock);
+
+ dump.append("Input Dispatcher State:\n");
+ dumpDispatchStateLocked(dump);
+
+ if (!mLastANRState.isEmpty()) {
+ dump.append("\nInput Dispatcher State at time of last ANR:\n");
+ dump.append(mLastANRState);
+ }
+}
+
+void InputDispatcher::monitor() {
+ // Acquire and release the lock to ensure that the dispatcher has not deadlocked.
+ mLock.lock();
+ mLooper->wake();
+ mDispatcherIsAliveCondition.wait(mLock);
+ mLock.unlock();
+}
+
+
+// --- InputDispatcher::Queue ---
+
+template <typename T>
+uint32_t InputDispatcher::Queue<T>::count() const {
+ uint32_t result = 0;
+ for (const T* entry = head; entry; entry = entry->next) {
+ result += 1;
+ }
+ return result;
+}
+
+
+// --- InputDispatcher::InjectionState ---
+
+InputDispatcher::InjectionState::InjectionState(int32_t injectorPid, int32_t injectorUid) :
+ refCount(1),
+ injectorPid(injectorPid), injectorUid(injectorUid),
+ injectionResult(INPUT_EVENT_INJECTION_PENDING), injectionIsAsync(false),
+ pendingForegroundDispatches(0) {
+}
+
+InputDispatcher::InjectionState::~InjectionState() {
+}
+
+void InputDispatcher::InjectionState::release() {
+ refCount -= 1;
+ if (refCount == 0) {
+ delete this;
+ } else {
+ ALOG_ASSERT(refCount > 0);
+ }
+}
+
+
+// --- InputDispatcher::EventEntry ---
+
+InputDispatcher::EventEntry::EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags) :
+ refCount(1), type(type), eventTime(eventTime), policyFlags(policyFlags),
+ injectionState(NULL), dispatchInProgress(false) {
+}
+
+InputDispatcher::EventEntry::~EventEntry() {
+ releaseInjectionState();
+}
+
+void InputDispatcher::EventEntry::release() {
+ refCount -= 1;
+ if (refCount == 0) {
+ delete this;
+ } else {
+ ALOG_ASSERT(refCount > 0);
+ }
+}
+
+void InputDispatcher::EventEntry::releaseInjectionState() {
+ if (injectionState) {
+ injectionState->release();
+ injectionState = NULL;
+ }
+}
+
+
+// --- InputDispatcher::ConfigurationChangedEntry ---
+
+InputDispatcher::ConfigurationChangedEntry::ConfigurationChangedEntry(nsecs_t eventTime) :
+ EventEntry(TYPE_CONFIGURATION_CHANGED, eventTime, 0) {
+}
+
+InputDispatcher::ConfigurationChangedEntry::~ConfigurationChangedEntry() {
+}
+
+void InputDispatcher::ConfigurationChangedEntry::appendDescription(String8& msg) const {
+ msg.append("ConfigurationChangedEvent(), policyFlags=0x%08x",
+ policyFlags);
+}
+
+
+// --- InputDispatcher::DeviceResetEntry ---
+
+InputDispatcher::DeviceResetEntry::DeviceResetEntry(nsecs_t eventTime, int32_t deviceId) :
+ EventEntry(TYPE_DEVICE_RESET, eventTime, 0),
+ deviceId(deviceId) {
+}
+
+InputDispatcher::DeviceResetEntry::~DeviceResetEntry() {
+}
+
+void InputDispatcher::DeviceResetEntry::appendDescription(String8& msg) const {
+ msg.appendFormat("DeviceResetEvent(deviceId=%d), policyFlags=0x%08x",
+ deviceId, policyFlags);
+}
+
+
+// --- InputDispatcher::KeyEntry ---
+
+InputDispatcher::KeyEntry::KeyEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+ int32_t repeatCount, nsecs_t downTime) :
+ EventEntry(TYPE_KEY, eventTime, policyFlags),
+ deviceId(deviceId), source(source), action(action), flags(flags),
+ keyCode(keyCode), scanCode(scanCode), metaState(metaState),
+ repeatCount(repeatCount), downTime(downTime),
+ syntheticRepeat(false), interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN),
+ interceptKeyWakeupTime(0) {
+}
+
+InputDispatcher::KeyEntry::~KeyEntry() {
+}
+
+void InputDispatcher::KeyEntry::appendDescription(String8& msg) const {
+ msg.appendFormat("KeyEvent(deviceId=%d, source=0x%08x, action=%d, "
+ "flags=0x%08x, keyCode=%d, scanCode=%d, metaState=0x%08x, "
+ "repeatCount=%d), policyFlags=0x%08x",
+ deviceId, source, action, flags, keyCode, scanCode, metaState,
+ repeatCount, policyFlags);
+}
+
+void InputDispatcher::KeyEntry::recycle() {
+ releaseInjectionState();
+
+ dispatchInProgress = false;
+ syntheticRepeat = false;
+ interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+ interceptKeyWakeupTime = 0;
+}
+
+
+// --- InputDispatcher::MotionEntry ---
+
+InputDispatcher::MotionEntry::MotionEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action, int32_t flags,
+ int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags, float xPrecision, float yPrecision,
+ nsecs_t downTime, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
+ float xOffset, float yOffset) :
+ EventEntry(TYPE_MOTION, eventTime, policyFlags),
+ eventTime(eventTime),
+ deviceId(deviceId), source(source), action(action), flags(flags),
+ metaState(metaState), buttonState(buttonState), edgeFlags(edgeFlags),
+ xPrecision(xPrecision), yPrecision(yPrecision),
+ downTime(downTime), displayId(displayId), pointerCount(pointerCount) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ this->pointerProperties[i].copyFrom(pointerProperties[i]);
+ this->pointerCoords[i].copyFrom(pointerCoords[i]);
+ if (xOffset || yOffset) {
+ this->pointerCoords[i].applyOffset(xOffset, yOffset);
+ }
+ }
+}
+
+InputDispatcher::MotionEntry::~MotionEntry() {
+}
+
+void InputDispatcher::MotionEntry::appendDescription(String8& msg) const {
+ msg.appendFormat("MotionEvent(deviceId=%d, source=0x%08x, action=%d, "
+ "flags=0x%08x, metaState=0x%08x, buttonState=0x%08x, edgeFlags=0x%08x, "
+ "xPrecision=%.1f, yPrecision=%.1f, displayId=%d, pointers=[",
+ deviceId, source, action, flags, metaState, buttonState, edgeFlags,
+ xPrecision, yPrecision, displayId);
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ if (i) {
+ msg.append(", ");
+ }
+ msg.appendFormat("%d: (%.1f, %.1f)", pointerProperties[i].id,
+ pointerCoords[i].getX(), pointerCoords[i].getY());
+ }
+ msg.appendFormat("]), policyFlags=0x%08x", policyFlags);
+}
+
+
+// --- InputDispatcher::DispatchEntry ---
+
+volatile int32_t InputDispatcher::DispatchEntry::sNextSeqAtomic;
+
+InputDispatcher::DispatchEntry::DispatchEntry(EventEntry* eventEntry,
+ int32_t targetFlags, float xOffset, float yOffset, float scaleFactor) :
+ seq(nextSeq()),
+ eventEntry(eventEntry), targetFlags(targetFlags),
+ xOffset(xOffset), yOffset(yOffset), scaleFactor(scaleFactor),
+ deliveryTime(0), resolvedAction(0), resolvedFlags(0) {
+ eventEntry->refCount += 1;
+}
+
+InputDispatcher::DispatchEntry::~DispatchEntry() {
+ eventEntry->release();
+}
+
+uint32_t InputDispatcher::DispatchEntry::nextSeq() {
+ // Sequence number 0 is reserved and will never be returned.
+ uint32_t seq;
+ do {
+ seq = android_atomic_inc(&sNextSeqAtomic);
+ } while (!seq);
+ return seq;
+}
+
+
+// --- InputDispatcher::InputState ---
+
+InputDispatcher::InputState::InputState() {
+}
+
+InputDispatcher::InputState::~InputState() {
+}
+
+bool InputDispatcher::InputState::isNeutral() const {
+ return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
+}
+
+bool InputDispatcher::InputState::isHovering(int32_t deviceId, uint32_t source,
+ int32_t displayId) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.deviceId == deviceId
+ && memento.source == source
+ && memento.displayId == displayId
+ && memento.hovering) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool InputDispatcher::InputState::trackKey(const KeyEntry* entry,
+ int32_t action, int32_t flags) {
+ switch (action) {
+ case AKEY_EVENT_ACTION_UP: {
+ if (entry->flags & AKEY_EVENT_FLAG_FALLBACK) {
+ for (size_t i = 0; i < mFallbackKeys.size(); ) {
+ if (mFallbackKeys.valueAt(i) == entry->keyCode) {
+ mFallbackKeys.removeItemsAt(i);
+ } else {
+ i += 1;
+ }
+ }
+ }
+ ssize_t index = findKeyMemento(entry);
+ if (index >= 0) {
+ mKeyMementos.removeAt(index);
+ return true;
+ }
+ /* FIXME: We can't just drop the key up event because that prevents creating
+ * popup windows that are automatically shown when a key is held and then
+ * dismissed when the key is released. The problem is that the popup will
+ * not have received the original key down, so the key up will be considered
+ * to be inconsistent with its observed state. We could perhaps handle this
+ * by synthesizing a key down but that will cause other problems.
+ *
+ * So for now, allow inconsistent key up events to be dispatched.
+ *
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, "
+ "keyCode=%d, scanCode=%d",
+ entry->deviceId, entry->source, entry->keyCode, entry->scanCode);
+#endif
+ return false;
+ */
+ return true;
+ }
+
+ case AKEY_EVENT_ACTION_DOWN: {
+ ssize_t index = findKeyMemento(entry);
+ if (index >= 0) {
+ mKeyMementos.removeAt(index);
+ }
+ addKeyMemento(entry, flags);
+ return true;
+ }
+
+ default:
+ return true;
+ }
+}
+
+bool InputDispatcher::InputState::trackMotion(const MotionEntry* entry,
+ int32_t action, int32_t flags) {
+ int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+ switch (actionMasked) {
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent motion up or cancel event: deviceId=%d, source=%08x, "
+ "actionMasked=%d",
+ entry->deviceId, entry->source, actionMasked);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_DOWN: {
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ }
+ addMotionMemento(entry, flags, false /*hovering*/);
+ return true;
+ }
+
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_MOVE: {
+ if (entry->source & AINPUT_SOURCE_CLASS_NAVIGATION) {
+ // Trackballs can send MOVE events with a corresponding DOWN or UP. There's no need to
+ // generate cancellation events for these since they're based in relative rather than
+ // absolute units.
+ return true;
+ }
+
+ ssize_t index = findMotionMemento(entry, false /*hovering*/);
+
+ if (entry->source & AINPUT_SOURCE_CLASS_JOYSTICK) {
+ // Joysticks can send MOVE events without a corresponding DOWN or UP. Since all
+ // joystick axes are normalized to [-1, 1] we can trust that 0 means it's neutral. Any
+ // other value and we need to track the motion so we can send cancellation events for
+ // anything generating fallback events (e.g. DPad keys for joystick movements).
+ if (index >= 0) {
+ if (entry->pointerCoords[0].isEmpty()) {
+ mMotionMementos.removeAt(index);
+ } else {
+ MotionMemento& memento = mMotionMementos.editItemAt(index);
+ memento.setPointers(entry);
+ }
+ } else if (!entry->pointerCoords[0].isEmpty()) {
+ addMotionMemento(entry, flags, false /*hovering*/);
+ }
+
+ // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP.
+ return true;
+ }
+ if (index >= 0) {
+ MotionMemento& memento = mMotionMementos.editItemAt(index);
+ memento.setPointers(entry);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent motion pointer up/down or move event: "
+ "deviceId=%d, source=%08x, actionMasked=%d",
+ entry->deviceId, entry->source, actionMasked);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+ ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ return true;
+ }
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ ALOGD("Dropping inconsistent motion hover exit event: deviceId=%d, source=%08x",
+ entry->deviceId, entry->source);
+#endif
+ return false;
+ }
+
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE: {
+ ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ if (index >= 0) {
+ mMotionMementos.removeAt(index);
+ }
+ addMotionMemento(entry, flags, true /*hovering*/);
+ return true;
+ }
+
+ default:
+ return true;
+ }
+}
+
+ssize_t InputDispatcher::InputState::findKeyMemento(const KeyEntry* entry) const {
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
+ const KeyMemento& memento = mKeyMementos.itemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source
+ && memento.keyCode == entry->keyCode
+ && memento.scanCode == entry->scanCode) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+ssize_t InputDispatcher::InputState::findMotionMemento(const MotionEntry* entry,
+ bool hovering) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source
+ && memento.displayId == entry->displayId
+ && memento.hovering == hovering) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+void InputDispatcher::InputState::addKeyMemento(const KeyEntry* entry, int32_t flags) {
+ mKeyMementos.push();
+ KeyMemento& memento = mKeyMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.keyCode = entry->keyCode;
+ memento.scanCode = entry->scanCode;
+ memento.metaState = entry->metaState;
+ memento.flags = flags;
+ memento.downTime = entry->downTime;
+ memento.policyFlags = entry->policyFlags;
+}
+
+void InputDispatcher::InputState::addMotionMemento(const MotionEntry* entry,
+ int32_t flags, bool hovering) {
+ mMotionMementos.push();
+ MotionMemento& memento = mMotionMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.flags = flags;
+ memento.xPrecision = entry->xPrecision;
+ memento.yPrecision = entry->yPrecision;
+ memento.downTime = entry->downTime;
+ memento.displayId = entry->displayId;
+ memento.setPointers(entry);
+ memento.hovering = hovering;
+ memento.policyFlags = entry->policyFlags;
+}
+
+void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
+ pointerCount = entry->pointerCount;
+ for (uint32_t i = 0; i < entry->pointerCount; i++) {
+ pointerProperties[i].copyFrom(entry->pointerProperties[i]);
+ pointerCoords[i].copyFrom(entry->pointerCoords[i]);
+ }
+}
+
+void InputDispatcher::InputState::synthesizeCancelationEvents(nsecs_t currentTime,
+ Vector<EventEntry*>& outEvents, const CancelationOptions& options) {
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
+ const KeyMemento& memento = mKeyMementos.itemAt(i);
+ if (shouldCancelKey(memento, options)) {
+ outEvents.push(new KeyEntry(currentTime,
+ memento.deviceId, memento.source, memento.policyFlags,
+ AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED,
+ memento.keyCode, memento.scanCode, memento.metaState, 0, memento.downTime));
+ }
+ }
+
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (shouldCancelMotion(memento, options)) {
+ outEvents.push(new MotionEntry(currentTime,
+ memento.deviceId, memento.source, memento.policyFlags,
+ memento.hovering
+ ? AMOTION_EVENT_ACTION_HOVER_EXIT
+ : AMOTION_EVENT_ACTION_CANCEL,
+ memento.flags, 0, 0, 0,
+ memento.xPrecision, memento.yPrecision, memento.downTime,
+ memento.displayId,
+ memento.pointerCount, memento.pointerProperties, memento.pointerCoords,
+ 0, 0));
+ }
+ }
+}
+
+void InputDispatcher::InputState::clear() {
+ mKeyMementos.clear();
+ mMotionMementos.clear();
+ mFallbackKeys.clear();
+}
+
+void InputDispatcher::InputState::copyPointerStateTo(InputState& other) const {
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
+ for (size_t j = 0; j < other.mMotionMementos.size(); ) {
+ const MotionMemento& otherMemento = other.mMotionMementos.itemAt(j);
+ if (memento.deviceId == otherMemento.deviceId
+ && memento.source == otherMemento.source
+ && memento.displayId == otherMemento.displayId) {
+ other.mMotionMementos.removeAt(j);
+ } else {
+ j += 1;
+ }
+ }
+ other.mMotionMementos.push(memento);
+ }
+ }
+}
+
+int32_t InputDispatcher::InputState::getFallbackKey(int32_t originalKeyCode) {
+ ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
+ return index >= 0 ? mFallbackKeys.valueAt(index) : -1;
+}
+
+void InputDispatcher::InputState::setFallbackKey(int32_t originalKeyCode,
+ int32_t fallbackKeyCode) {
+ ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
+ if (index >= 0) {
+ mFallbackKeys.replaceValueAt(index, fallbackKeyCode);
+ } else {
+ mFallbackKeys.add(originalKeyCode, fallbackKeyCode);
+ }
+}
+
+void InputDispatcher::InputState::removeFallbackKey(int32_t originalKeyCode) {
+ mFallbackKeys.removeItem(originalKeyCode);
+}
+
+bool InputDispatcher::InputState::shouldCancelKey(const KeyMemento& memento,
+ const CancelationOptions& options) {
+ if (options.keyCode != -1 && memento.keyCode != options.keyCode) {
+ return false;
+ }
+
+ if (options.deviceId != -1 && memento.deviceId != options.deviceId) {
+ return false;
+ }
+
+ switch (options.mode) {
+ case CancelationOptions::CANCEL_ALL_EVENTS:
+ case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+ return true;
+ case CancelationOptions::CANCEL_FALLBACK_EVENTS:
+ return memento.flags & AKEY_EVENT_FLAG_FALLBACK;
+ default:
+ return false;
+ }
+}
+
+bool InputDispatcher::InputState::shouldCancelMotion(const MotionMemento& memento,
+ const CancelationOptions& options) {
+ if (options.deviceId != -1 && memento.deviceId != options.deviceId) {
+ return false;
+ }
+
+ switch (options.mode) {
+ case CancelationOptions::CANCEL_ALL_EVENTS:
+ return true;
+ case CancelationOptions::CANCEL_POINTER_EVENTS:
+ return memento.source & AINPUT_SOURCE_CLASS_POINTER;
+ case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+ return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
+ default:
+ return false;
+ }
+}
+
+
+// --- InputDispatcher::Connection ---
+
+InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) :
+ status(STATUS_NORMAL), inputChannel(inputChannel), inputWindowHandle(inputWindowHandle),
+ monitor(monitor),
+ inputPublisher(inputChannel), inputPublisherBlocked(false) {
+}
+
+InputDispatcher::Connection::~Connection() {
+}
+
+const char* InputDispatcher::Connection::getWindowName() const {
+ if (inputWindowHandle != NULL) {
+ return inputWindowHandle->getName().string();
+ }
+ if (monitor) {
+ return "monitor";
+ }
+ return "?";
+}
+
+const char* InputDispatcher::Connection::getStatusLabel() const {
+ switch (status) {
+ case STATUS_NORMAL:
+ return "NORMAL";
+
+ case STATUS_BROKEN:
+ return "BROKEN";
+
+ case STATUS_ZOMBIE:
+ return "ZOMBIE";
+
+ default:
+ return "UNKNOWN";
+ }
+}
+
+InputDispatcher::DispatchEntry* InputDispatcher::Connection::findWaitQueueEntry(uint32_t seq) {
+ for (DispatchEntry* entry = waitQueue.head; entry != NULL; entry = entry->next) {
+ if (entry->seq == seq) {
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+
+// --- InputDispatcher::CommandEntry ---
+
+InputDispatcher::CommandEntry::CommandEntry(Command command) :
+ command(command), eventTime(0), keyEntry(NULL), userActivityEventType(0),
+ seq(0), handled(false) {
+}
+
+InputDispatcher::CommandEntry::~CommandEntry() {
+}
+
+
+// --- InputDispatcher::TouchState ---
+
+InputDispatcher::TouchState::TouchState() :
+ down(false), split(false), deviceId(-1), source(0), displayId(-1) {
+}
+
+InputDispatcher::TouchState::~TouchState() {
+}
+
+void InputDispatcher::TouchState::reset() {
+ down = false;
+ split = false;
+ deviceId = -1;
+ source = 0;
+ displayId = -1;
+ windows.clear();
+}
+
+void InputDispatcher::TouchState::copyFrom(const TouchState& other) {
+ down = other.down;
+ split = other.split;
+ deviceId = other.deviceId;
+ source = other.source;
+ displayId = other.displayId;
+ windows = other.windows;
+}
+
+void InputDispatcher::TouchState::addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds) {
+ if (targetFlags & InputTarget::FLAG_SPLIT) {
+ split = true;
+ }
+
+ for (size_t i = 0; i < windows.size(); i++) {
+ TouchedWindow& touchedWindow = windows.editItemAt(i);
+ if (touchedWindow.windowHandle == windowHandle) {
+ touchedWindow.targetFlags |= targetFlags;
+ if (targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
+ touchedWindow.targetFlags &= ~InputTarget::FLAG_DISPATCH_AS_IS;
+ }
+ touchedWindow.pointerIds.value |= pointerIds.value;
+ return;
+ }
+ }
+
+ windows.push();
+
+ TouchedWindow& touchedWindow = windows.editTop();
+ touchedWindow.windowHandle = windowHandle;
+ touchedWindow.targetFlags = targetFlags;
+ touchedWindow.pointerIds = pointerIds;
+}
+
+void InputDispatcher::TouchState::removeWindow(const sp<InputWindowHandle>& windowHandle) {
+ for (size_t i = 0; i < windows.size(); i++) {
+ if (windows.itemAt(i).windowHandle == windowHandle) {
+ windows.removeAt(i);
+ return;
+ }
+ }
+}
+
+void InputDispatcher::TouchState::filterNonAsIsTouchWindows() {
+ for (size_t i = 0 ; i < windows.size(); ) {
+ TouchedWindow& window = windows.editItemAt(i);
+ if (window.targetFlags & (InputTarget::FLAG_DISPATCH_AS_IS
+ | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER)) {
+ window.targetFlags &= ~InputTarget::FLAG_DISPATCH_MASK;
+ window.targetFlags |= InputTarget::FLAG_DISPATCH_AS_IS;
+ i += 1;
+ } else {
+ windows.removeAt(i);
+ }
+ }
+}
+
+sp<InputWindowHandle> InputDispatcher::TouchState::getFirstForegroundWindowHandle() const {
+ for (size_t i = 0; i < windows.size(); i++) {
+ const TouchedWindow& window = windows.itemAt(i);
+ if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ return window.windowHandle;
+ }
+ }
+ return NULL;
+}
+
+bool InputDispatcher::TouchState::isSlippery() const {
+ // Must have exactly one foreground window.
+ bool haveSlipperyForegroundWindow = false;
+ for (size_t i = 0; i < windows.size(); i++) {
+ const TouchedWindow& window = windows.itemAt(i);
+ if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ if (haveSlipperyForegroundWindow
+ || !(window.windowHandle->getInfo()->layoutParamsFlags
+ & InputWindowInfo::FLAG_SLIPPERY)) {
+ return false;
+ }
+ haveSlipperyForegroundWindow = true;
+ }
+ }
+ return haveSlipperyForegroundWindow;
+}
+
+
+// --- InputDispatcherThread ---
+
+InputDispatcherThread::InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher) :
+ Thread(/*canCallJava*/ true), mDispatcher(dispatcher) {
+}
+
+InputDispatcherThread::~InputDispatcherThread() {
+}
+
+bool InputDispatcherThread::threadLoop() {
+ mDispatcher->dispatchOnce();
+ return true;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputDispatcher.h b/services/inputflinger/InputDispatcher.h
new file mode 100644
index 0000000..9439124
--- /dev/null
+++ b/services/inputflinger/InputDispatcher.h
@@ -0,0 +1,1121 @@
+/*
+ * 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/Input.h>
+#include <input/InputTransport.h>
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Looper.h>
+#include <utils/BitSet.h>
+#include <cutils/atomic.h>
+
+#include <stddef.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include "InputWindow.h"
+#include "InputApplication.h"
+#include "InputListener.h"
+
+
+namespace android {
+
+/*
+ * Constants used to report the outcome of input event injection.
+ */
+enum {
+ /* (INTERNAL USE ONLY) Specifies that injection is pending and its outcome is unknown. */
+ INPUT_EVENT_INJECTION_PENDING = -1,
+
+ /* Injection succeeded. */
+ INPUT_EVENT_INJECTION_SUCCEEDED = 0,
+
+ /* Injection failed because the injector did not have permission to inject
+ * into the application with input focus. */
+ INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1,
+
+ /* Injection failed because there were no available input targets. */
+ INPUT_EVENT_INJECTION_FAILED = 2,
+
+ /* Injection failed due to a timeout. */
+ INPUT_EVENT_INJECTION_TIMED_OUT = 3
+};
+
+/*
+ * Constants used to determine the input event injection synchronization mode.
+ */
+enum {
+ /* Injection is asynchronous and is assumed always to be successful. */
+ INPUT_EVENT_INJECTION_SYNC_NONE = 0,
+
+ /* Waits for previous events to be dispatched so that the input dispatcher can determine
+ * whether input event injection willbe permitted based on the current input focus.
+ * Does not wait for the input event to finish processing. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_RESULT = 1,
+
+ /* Waits for the input event to be completely processed. */
+ INPUT_EVENT_INJECTION_SYNC_WAIT_FOR_FINISHED = 2,
+};
+
+
+/*
+ * An input target specifies how an input event is to be dispatched to a particular window
+ * including the window's input channel, control flags, a timeout, and an X / Y offset to
+ * be added to input event coordinates to compensate for the absolute position of the
+ * window area.
+ */
+struct InputTarget {
+ enum {
+ /* This flag indicates that the event is being delivered to a foreground application. */
+ FLAG_FOREGROUND = 1 << 0,
+
+ /* This flag indicates that the target of a MotionEvent is partly or wholly
+ * obscured by another visible window above it. The motion event should be
+ * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
+ FLAG_WINDOW_IS_OBSCURED = 1 << 1,
+
+ /* This flag indicates that a motion event is being split across multiple windows. */
+ FLAG_SPLIT = 1 << 2,
+
+ /* This flag indicates that the pointer coordinates dispatched to the application
+ * will be zeroed out to avoid revealing information to an application. This is
+ * used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
+ * the same UID from watching all touches. */
+ FLAG_ZERO_COORDS = 1 << 3,
+
+ /* This flag indicates that the event should be sent as is.
+ * Should always be set unless the event is to be transmuted. */
+ FLAG_DISPATCH_AS_IS = 1 << 8,
+
+ /* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
+ * of the area of this target and so should instead be delivered as an
+ * AMOTION_EVENT_ACTION_OUTSIDE to this target. */
+ FLAG_DISPATCH_AS_OUTSIDE = 1 << 9,
+
+ /* This flag indicates that a hover sequence is starting in the given window.
+ * The event is transmuted into ACTION_HOVER_ENTER. */
+ FLAG_DISPATCH_AS_HOVER_ENTER = 1 << 10,
+
+ /* This flag indicates that a hover event happened outside of a window which handled
+ * previous hover events, signifying the end of the current hover sequence for that
+ * window.
+ * The event is transmuted into ACTION_HOVER_ENTER. */
+ FLAG_DISPATCH_AS_HOVER_EXIT = 1 << 11,
+
+ /* This flag indicates that the event should be canceled.
+ * It is used to transmute ACTION_MOVE into ACTION_CANCEL when a touch slips
+ * outside of a window. */
+ FLAG_DISPATCH_AS_SLIPPERY_EXIT = 1 << 12,
+
+ /* This flag indicates that the event should be dispatched as an initial down.
+ * It is used to transmute ACTION_MOVE into ACTION_DOWN when a touch slips
+ * into a new window. */
+ FLAG_DISPATCH_AS_SLIPPERY_ENTER = 1 << 13,
+
+ /* Mask for all dispatch modes. */
+ FLAG_DISPATCH_MASK = FLAG_DISPATCH_AS_IS
+ | FLAG_DISPATCH_AS_OUTSIDE
+ | FLAG_DISPATCH_AS_HOVER_ENTER
+ | FLAG_DISPATCH_AS_HOVER_EXIT
+ | FLAG_DISPATCH_AS_SLIPPERY_EXIT
+ | FLAG_DISPATCH_AS_SLIPPERY_ENTER,
+ };
+
+ // The input channel to be targeted.
+ sp<InputChannel> inputChannel;
+
+ // Flags for the input target.
+ int32_t flags;
+
+ // The x and y offset to add to a MotionEvent as it is delivered.
+ // (ignored for KeyEvents)
+ float xOffset, yOffset;
+
+ // Scaling factor to apply to MotionEvent as it is delivered.
+ // (ignored for KeyEvents)
+ float scaleFactor;
+
+ // The subset of pointer ids to include in motion events dispatched to this input target
+ // if FLAG_SPLIT is set.
+ BitSet32 pointerIds;
+};
+
+
+/*
+ * Input dispatcher configuration.
+ *
+ * Specifies various options that modify the behavior of the input dispatcher.
+ * The values provided here are merely defaults. The actual values will come from ViewConfiguration
+ * and are passed into the dispatcher during initialization.
+ */
+struct InputDispatcherConfiguration {
+ // The key repeat initial timeout.
+ nsecs_t keyRepeatTimeout;
+
+ // The key repeat inter-key delay.
+ nsecs_t keyRepeatDelay;
+
+ InputDispatcherConfiguration() :
+ keyRepeatTimeout(500 * 1000000LL),
+ keyRepeatDelay(50 * 1000000LL) { }
+};
+
+
+/*
+ * Input dispatcher policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class InputDispatcherPolicyInterface : public virtual RefBase {
+protected:
+ InputDispatcherPolicyInterface() { }
+ virtual ~InputDispatcherPolicyInterface() { }
+
+public:
+ /* Notifies the system that a configuration change has occurred. */
+ virtual void notifyConfigurationChanged(nsecs_t when) = 0;
+
+ /* Notifies the system that an application is not responding.
+ * Returns a new timeout to continue waiting, or 0 to abort dispatch. */
+ virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+ const sp<InputWindowHandle>& inputWindowHandle,
+ const String8& reason) = 0;
+
+ /* Notifies the system that an input channel is unrecoverably broken. */
+ virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) = 0;
+
+ /* Gets the input dispatcher configuration. */
+ virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) = 0;
+
+ /* Filters an input event.
+ * Return true to dispatch the event unmodified, false to consume the event.
+ * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED
+ * to injectInputEvent.
+ */
+ virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) = 0;
+
+ /* Intercepts a key event immediately before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) = 0;
+
+ /* Intercepts a touch, trackball or other motion event before queueing it.
+ * The policy can use this method as an opportunity to perform power management functions
+ * and early event preprocessing such as updating policy flags.
+ *
+ * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
+ * should be dispatched to applications.
+ */
+ virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) = 0;
+
+ /* Allows the policy a chance to intercept a key before dispatching. */
+ virtual nsecs_t interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags) = 0;
+
+ /* Allows the policy a chance to perform default processing for an unhandled key.
+ * Returns an alternate keycode to redispatch as a fallback, or 0 to give up. */
+ virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) = 0;
+
+ /* Notifies the policy about switch events.
+ */
+ virtual void notifySwitch(nsecs_t when,
+ uint32_t switchValues, uint32_t switchMask, uint32_t policyFlags) = 0;
+
+ /* Poke user activity for an event dispatched to a window. */
+ virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) = 0;
+
+ /* Checks whether a given application pid/uid has permission to inject input events
+ * into other applications.
+ *
+ * This method is special in that its implementation promises to be non-reentrant and
+ * is safe to call while holding other locks. (Most other methods make no such guarantees!)
+ */
+ virtual bool checkInjectEventsPermissionNonReentrant(
+ int32_t injectorPid, int32_t injectorUid) = 0;
+};
+
+
+/* Notifies the system about input events generated by the input reader.
+ * The dispatcher is expected to be mostly asynchronous. */
+class InputDispatcherInterface : public virtual RefBase, public InputListenerInterface {
+protected:
+ InputDispatcherInterface() { }
+ virtual ~InputDispatcherInterface() { }
+
+public:
+ /* Dumps the state of the input dispatcher.
+ *
+ * This method may be called on any thread (usually by the input manager). */
+ virtual void dump(String8& dump) = 0;
+
+ /* Called by the heatbeat to ensures that the dispatcher has not deadlocked. */
+ virtual void monitor() = 0;
+
+ /* Runs a single iteration of the dispatch loop.
+ * Nominally processes one queued event, a timeout, or a response from an input consumer.
+ *
+ * This method should only be called on the input dispatcher thread.
+ */
+ virtual void dispatchOnce() = 0;
+
+ /* Injects an input event and optionally waits for sync.
+ * The synchronization mode determines whether the method blocks while waiting for
+ * input injection to proceed.
+ * Returns one of the INPUT_EVENT_INJECTION_XXX constants.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual int32_t injectInputEvent(const InputEvent* event, int32_t displayId,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags) = 0;
+
+ /* Sets the list of input windows.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles) = 0;
+
+ /* Sets the focused application.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setFocusedApplication(
+ const sp<InputApplicationHandle>& inputApplicationHandle) = 0;
+
+ /* Sets the input dispatching mode.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void setInputDispatchMode(bool enabled, bool frozen) = 0;
+
+ /* Sets whether input event filtering is enabled.
+ * When enabled, incoming input events are sent to the policy's filterInputEvent
+ * method instead of being dispatched. The filter is expected to use
+ * injectInputEvent to inject the events it would like to have dispatched.
+ * It should include POLICY_FLAG_FILTERED in the policy flags during injection.
+ */
+ virtual void setInputFilterEnabled(bool enabled) = 0;
+
+ /* Transfers touch focus from the window associated with one channel to the
+ * window associated with the other channel.
+ *
+ * Returns true on success. False if the window did not actually have touch focus.
+ */
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel) = 0;
+
+ /* Registers or unregister input channels that may be used as targets for input events.
+ * If monitor is true, the channel will receive a copy of all input events.
+ *
+ * These methods may be called on any thread (usually by the input manager).
+ */
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor) = 0;
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel) = 0;
+};
+
+/* Dispatches events to input targets. Some functions of the input dispatcher, such as
+ * identifying input targets, are controlled by a separate policy object.
+ *
+ * IMPORTANT INVARIANT:
+ * Because the policy can potentially block or cause re-entrance into the input dispatcher,
+ * the input dispatcher never calls into the policy while holding its internal locks.
+ * The implementation is also carefully designed to recover from scenarios such as an
+ * input channel becoming unregistered while identifying input targets or processing timeouts.
+ *
+ * Methods marked 'Locked' must be called with the lock acquired.
+ *
+ * Methods marked 'LockedInterruptible' must be called with the lock acquired but
+ * may during the course of their execution release the lock, call into the policy, and
+ * then reacquire the lock. The caller is responsible for recovering gracefully.
+ *
+ * A 'LockedInterruptible' method may called a 'Locked' method, but NOT vice-versa.
+ */
+class InputDispatcher : public InputDispatcherInterface {
+protected:
+ virtual ~InputDispatcher();
+
+public:
+ explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
+
+ virtual void dump(String8& dump);
+ virtual void monitor();
+
+ virtual void dispatchOnce();
+
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
+ virtual void notifyKey(const NotifyKeyArgs* args);
+ virtual void notifyMotion(const NotifyMotionArgs* args);
+ virtual void notifySwitch(const NotifySwitchArgs* args);
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
+
+ virtual int32_t injectInputEvent(const InputEvent* event, int32_t displayId,
+ int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis,
+ uint32_t policyFlags);
+
+ virtual void setInputWindows(const Vector<sp<InputWindowHandle> >& inputWindowHandles);
+ virtual void setFocusedApplication(const sp<InputApplicationHandle>& inputApplicationHandle);
+ virtual void setInputDispatchMode(bool enabled, bool frozen);
+ virtual void setInputFilterEnabled(bool enabled);
+
+ virtual bool transferTouchFocus(const sp<InputChannel>& fromChannel,
+ const sp<InputChannel>& toChannel);
+
+ virtual status_t registerInputChannel(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+ virtual status_t unregisterInputChannel(const sp<InputChannel>& inputChannel);
+
+private:
+ template <typename T>
+ struct Link {
+ T* next;
+ T* prev;
+
+ protected:
+ inline Link() : next(NULL), prev(NULL) { }
+ };
+
+ struct InjectionState {
+ mutable int32_t refCount;
+
+ int32_t injectorPid;
+ int32_t injectorUid;
+ int32_t injectionResult; // initially INPUT_EVENT_INJECTION_PENDING
+ bool injectionIsAsync; // set to true if injection is not waiting for the result
+ int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
+
+ InjectionState(int32_t injectorPid, int32_t injectorUid);
+ void release();
+
+ private:
+ ~InjectionState();
+ };
+
+ struct EventEntry : Link<EventEntry> {
+ enum {
+ TYPE_CONFIGURATION_CHANGED,
+ TYPE_DEVICE_RESET,
+ TYPE_KEY,
+ TYPE_MOTION
+ };
+
+ mutable int32_t refCount;
+ int32_t type;
+ nsecs_t eventTime;
+ uint32_t policyFlags;
+ InjectionState* injectionState;
+
+ bool dispatchInProgress; // initially false, set to true while dispatching
+
+ inline bool isInjected() const { return injectionState != NULL; }
+
+ void release();
+
+ virtual void appendDescription(String8& msg) const = 0;
+
+ protected:
+ EventEntry(int32_t type, nsecs_t eventTime, uint32_t policyFlags);
+ virtual ~EventEntry();
+ void releaseInjectionState();
+ };
+
+ struct ConfigurationChangedEntry : EventEntry {
+ ConfigurationChangedEntry(nsecs_t eventTime);
+ virtual void appendDescription(String8& msg) const;
+
+ protected:
+ virtual ~ConfigurationChangedEntry();
+ };
+
+ struct DeviceResetEntry : EventEntry {
+ int32_t deviceId;
+
+ DeviceResetEntry(nsecs_t eventTime, int32_t deviceId);
+ virtual void appendDescription(String8& msg) const;
+
+ protected:
+ virtual ~DeviceResetEntry();
+ };
+
+ struct KeyEntry : EventEntry {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ int32_t repeatCount;
+ nsecs_t downTime;
+
+ bool syntheticRepeat; // set to true for synthetic key repeats
+
+ enum InterceptKeyResult {
+ INTERCEPT_KEY_RESULT_UNKNOWN,
+ INTERCEPT_KEY_RESULT_SKIP,
+ INTERCEPT_KEY_RESULT_CONTINUE,
+ INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,
+ };
+ InterceptKeyResult interceptKeyResult; // set based on the interception result
+ nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
+
+ KeyEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags, int32_t action,
+ int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+ int32_t repeatCount, nsecs_t downTime);
+ virtual void appendDescription(String8& msg) const;
+ void recycle();
+
+ protected:
+ virtual ~KeyEntry();
+ };
+
+ struct MotionEntry : EventEntry {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t buttonState;
+ int32_t edgeFlags;
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+ int32_t displayId;
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+
+ MotionEntry(nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, uint32_t policyFlags,
+ int32_t action, int32_t flags,
+ int32_t metaState, int32_t buttonState, int32_t edgeFlags,
+ float xPrecision, float yPrecision,
+ nsecs_t downTime, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
+ float xOffset, float yOffset);
+ virtual void appendDescription(String8& msg) const;
+
+ protected:
+ virtual ~MotionEntry();
+ };
+
+ // Tracks the progress of dispatching a particular event to a particular connection.
+ struct DispatchEntry : Link<DispatchEntry> {
+ const uint32_t seq; // unique sequence number, never 0
+
+ EventEntry* eventEntry; // the event to dispatch
+ int32_t targetFlags;
+ float xOffset;
+ float yOffset;
+ float scaleFactor;
+ nsecs_t deliveryTime; // time when the event was actually delivered
+
+ // Set to the resolved action and flags when the event is enqueued.
+ int32_t resolvedAction;
+ int32_t resolvedFlags;
+
+ DispatchEntry(EventEntry* eventEntry,
+ int32_t targetFlags, float xOffset, float yOffset, float scaleFactor);
+ ~DispatchEntry();
+
+ inline bool hasForegroundTarget() const {
+ return targetFlags & InputTarget::FLAG_FOREGROUND;
+ }
+
+ inline bool isSplit() const {
+ return targetFlags & InputTarget::FLAG_SPLIT;
+ }
+
+ private:
+ static volatile int32_t sNextSeqAtomic;
+
+ static uint32_t nextSeq();
+ };
+
+ // A command entry captures state and behavior for an action to be performed in the
+ // dispatch loop after the initial processing has taken place. It is essentially
+ // a kind of continuation used to postpone sensitive policy interactions to a point
+ // in the dispatch loop where it is safe to release the lock (generally after finishing
+ // the critical parts of the dispatch cycle).
+ //
+ // The special thing about commands is that they can voluntarily release and reacquire
+ // the dispatcher lock at will. Initially when the command starts running, the
+ // dispatcher lock is held. However, if the command needs to call into the policy to
+ // do some work, it can release the lock, do the work, then reacquire the lock again
+ // before returning.
+ //
+ // This mechanism is a bit clunky but it helps to preserve the invariant that the dispatch
+ // never calls into the policy while holding its lock.
+ //
+ // Commands are implicitly 'LockedInterruptible'.
+ struct CommandEntry;
+ typedef void (InputDispatcher::*Command)(CommandEntry* commandEntry);
+
+ class Connection;
+ struct CommandEntry : Link<CommandEntry> {
+ CommandEntry(Command command);
+ ~CommandEntry();
+
+ Command command;
+
+ // parameters for the command (usage varies by command)
+ sp<Connection> connection;
+ nsecs_t eventTime;
+ KeyEntry* keyEntry;
+ sp<InputApplicationHandle> inputApplicationHandle;
+ sp<InputWindowHandle> inputWindowHandle;
+ String8 reason;
+ int32_t userActivityEventType;
+ uint32_t seq;
+ bool handled;
+ };
+
+ // Generic queue implementation.
+ template <typename T>
+ struct Queue {
+ T* head;
+ T* tail;
+
+ inline Queue() : head(NULL), tail(NULL) {
+ }
+
+ inline bool isEmpty() const {
+ return !head;
+ }
+
+ inline void enqueueAtTail(T* entry) {
+ entry->prev = tail;
+ if (tail) {
+ tail->next = entry;
+ } else {
+ head = entry;
+ }
+ entry->next = NULL;
+ tail = entry;
+ }
+
+ inline void enqueueAtHead(T* entry) {
+ entry->next = head;
+ if (head) {
+ head->prev = entry;
+ } else {
+ tail = entry;
+ }
+ entry->prev = NULL;
+ head = entry;
+ }
+
+ inline void dequeue(T* entry) {
+ if (entry->prev) {
+ entry->prev->next = entry->next;
+ } else {
+ head = entry->next;
+ }
+ if (entry->next) {
+ entry->next->prev = entry->prev;
+ } else {
+ tail = entry->prev;
+ }
+ }
+
+ inline T* dequeueAtHead() {
+ T* entry = head;
+ head = entry->next;
+ if (head) {
+ head->prev = NULL;
+ } else {
+ tail = NULL;
+ }
+ return entry;
+ }
+
+ uint32_t count() const;
+ };
+
+ /* Specifies which events are to be canceled and why. */
+ struct CancelationOptions {
+ enum Mode {
+ CANCEL_ALL_EVENTS = 0,
+ CANCEL_POINTER_EVENTS = 1,
+ CANCEL_NON_POINTER_EVENTS = 2,
+ CANCEL_FALLBACK_EVENTS = 3,
+ };
+
+ // The criterion to use to determine which events should be canceled.
+ Mode mode;
+
+ // Descriptive reason for the cancelation.
+ const char* reason;
+
+ // The specific keycode of the key event to cancel, or -1 to cancel any key event.
+ int32_t keyCode;
+
+ // The specific device id of events to cancel, or -1 to cancel events from any device.
+ int32_t deviceId;
+
+ CancelationOptions(Mode mode, const char* reason) :
+ mode(mode), reason(reason), keyCode(-1), deviceId(-1) { }
+ };
+
+ /* Tracks dispatched key and motion event state so that cancelation events can be
+ * synthesized when events are dropped. */
+ class InputState {
+ public:
+ InputState();
+ ~InputState();
+
+ // Returns true if there is no state to be canceled.
+ bool isNeutral() const;
+
+ // Returns true if the specified source is known to have received a hover enter
+ // motion event.
+ bool isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const;
+
+ // Records tracking information for a key event that has just been published.
+ // Returns true if the event should be delivered, false if it is inconsistent
+ // and should be skipped.
+ bool trackKey(const KeyEntry* entry, int32_t action, int32_t flags);
+
+ // Records tracking information for a motion event that has just been published.
+ // Returns true if the event should be delivered, false if it is inconsistent
+ // and should be skipped.
+ bool trackMotion(const MotionEntry* entry, int32_t action, int32_t flags);
+
+ // Synthesizes cancelation events for the current state and resets the tracked state.
+ void synthesizeCancelationEvents(nsecs_t currentTime,
+ Vector<EventEntry*>& outEvents, const CancelationOptions& options);
+
+ // Clears the current state.
+ void clear();
+
+ // Copies pointer-related parts of the input state to another instance.
+ void copyPointerStateTo(InputState& other) const;
+
+ // Gets the fallback key associated with a keycode.
+ // Returns -1 if none.
+ // Returns AKEYCODE_UNKNOWN if we are only dispatching the unhandled key to the policy.
+ int32_t getFallbackKey(int32_t originalKeyCode);
+
+ // Sets the fallback key for a particular keycode.
+ void setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode);
+
+ // Removes the fallback key for a particular keycode.
+ void removeFallbackKey(int32_t originalKeyCode);
+
+ inline const KeyedVector<int32_t, int32_t>& getFallbackKeys() const {
+ return mFallbackKeys;
+ }
+
+ private:
+ struct KeyMemento {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ int32_t flags;
+ nsecs_t downTime;
+ uint32_t policyFlags;
+ };
+
+ struct MotionMemento {
+ int32_t deviceId;
+ uint32_t source;
+ int32_t flags;
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+ int32_t displayId;
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ bool hovering;
+ uint32_t policyFlags;
+
+ void setPointers(const MotionEntry* entry);
+ };
+
+ Vector<KeyMemento> mKeyMementos;
+ Vector<MotionMemento> mMotionMementos;
+ KeyedVector<int32_t, int32_t> mFallbackKeys;
+
+ ssize_t findKeyMemento(const KeyEntry* entry) const;
+ ssize_t findMotionMemento(const MotionEntry* entry, bool hovering) const;
+
+ void addKeyMemento(const KeyEntry* entry, int32_t flags);
+ void addMotionMemento(const MotionEntry* entry, int32_t flags, bool hovering);
+
+ static bool shouldCancelKey(const KeyMemento& memento,
+ const CancelationOptions& options);
+ static bool shouldCancelMotion(const MotionMemento& memento,
+ const CancelationOptions& options);
+ };
+
+ /* Manages the dispatch state associated with a single input channel. */
+ class Connection : public RefBase {
+ protected:
+ virtual ~Connection();
+
+ public:
+ enum Status {
+ // Everything is peachy.
+ STATUS_NORMAL,
+ // An unrecoverable communication error has occurred.
+ STATUS_BROKEN,
+ // The input channel has been unregistered.
+ STATUS_ZOMBIE
+ };
+
+ Status status;
+ sp<InputChannel> inputChannel; // never null
+ sp<InputWindowHandle> inputWindowHandle; // may be null
+ bool monitor;
+ InputPublisher inputPublisher;
+ InputState inputState;
+
+ // True if the socket is full and no further events can be published until
+ // the application consumes some of the input.
+ bool inputPublisherBlocked;
+
+ // Queue of events that need to be published to the connection.
+ Queue<DispatchEntry> outboundQueue;
+
+ // Queue of events that have been published to the connection but that have not
+ // yet received a "finished" response from the application.
+ Queue<DispatchEntry> waitQueue;
+
+ explicit Connection(const sp<InputChannel>& inputChannel,
+ const sp<InputWindowHandle>& inputWindowHandle, bool monitor);
+
+ inline const char* getInputChannelName() const { return inputChannel->getName().string(); }
+
+ const char* getWindowName() const;
+ const char* getStatusLabel() const;
+
+ DispatchEntry* findWaitQueueEntry(uint32_t seq);
+ };
+
+ enum DropReason {
+ DROP_REASON_NOT_DROPPED = 0,
+ DROP_REASON_POLICY = 1,
+ DROP_REASON_APP_SWITCH = 2,
+ DROP_REASON_DISABLED = 3,
+ DROP_REASON_BLOCKED = 4,
+ DROP_REASON_STALE = 5,
+ };
+
+ sp<InputDispatcherPolicyInterface> mPolicy;
+ InputDispatcherConfiguration mConfig;
+
+ Mutex mLock;
+
+ Condition mDispatcherIsAliveCondition;
+
+ sp<Looper> mLooper;
+
+ EventEntry* mPendingEvent;
+ Queue<EventEntry> mInboundQueue;
+ Queue<EventEntry> mRecentQueue;
+ Queue<CommandEntry> mCommandQueue;
+
+ void dispatchOnceInnerLocked(nsecs_t* nextWakeupTime);
+
+ // Enqueues an inbound event. Returns true if mLooper->wake() should be called.
+ bool enqueueInboundEventLocked(EventEntry* entry);
+
+ // Cleans up input state when dropping an inbound event.
+ void dropInboundEventLocked(EventEntry* entry, DropReason dropReason);
+
+ // Adds an event to a queue of recent events for debugging purposes.
+ void addRecentEventLocked(EventEntry* entry);
+
+ // App switch latency optimization.
+ bool mAppSwitchSawKeyDown;
+ nsecs_t mAppSwitchDueTime;
+
+ static bool isAppSwitchKeyCode(int32_t keyCode);
+ bool isAppSwitchKeyEventLocked(KeyEntry* keyEntry);
+ bool isAppSwitchPendingLocked();
+ void resetPendingAppSwitchLocked(bool handled);
+
+ // Stale event latency optimization.
+ static bool isStaleEventLocked(nsecs_t currentTime, EventEntry* entry);
+
+ // Blocked event latency optimization. Drops old events when the user intends
+ // to transfer focus to a new application.
+ EventEntry* mNextUnblockedEvent;
+
+ sp<InputWindowHandle> findTouchedWindowAtLocked(int32_t displayId, int32_t x, int32_t y);
+
+ // All registered connections mapped by channel file descriptor.
+ KeyedVector<int, sp<Connection> > mConnectionsByFd;
+
+ ssize_t getConnectionIndexLocked(const sp<InputChannel>& inputChannel);
+
+ // Input channels that will receive a copy of all input events.
+ Vector<sp<InputChannel> > mMonitoringChannels;
+
+ // Event injection and synchronization.
+ Condition mInjectionResultAvailableCondition;
+ bool hasInjectionPermission(int32_t injectorPid, int32_t injectorUid);
+ void setInjectionResultLocked(EventEntry* entry, int32_t injectionResult);
+
+ Condition mInjectionSyncFinishedCondition;
+ void incrementPendingForegroundDispatchesLocked(EventEntry* entry);
+ void decrementPendingForegroundDispatchesLocked(EventEntry* entry);
+
+ // Key repeat tracking.
+ struct KeyRepeatState {
+ KeyEntry* lastKeyEntry; // or null if no repeat
+ nsecs_t nextRepeatTime;
+ } mKeyRepeatState;
+
+ void resetKeyRepeatLocked();
+ KeyEntry* synthesizeKeyRepeatLocked(nsecs_t currentTime);
+
+ // Deferred command processing.
+ bool haveCommandsLocked() const;
+ bool runCommandsLockedInterruptible();
+ CommandEntry* postCommandLocked(Command command);
+
+ // Input filter processing.
+ bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args);
+ bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args);
+
+ // Inbound event processing.
+ void drainInboundQueueLocked();
+ void releasePendingEventLocked();
+ void releaseInboundEventLocked(EventEntry* entry);
+
+ // Dispatch state.
+ bool mDispatchEnabled;
+ bool mDispatchFrozen;
+ bool mInputFilterEnabled;
+
+ Vector<sp<InputWindowHandle> > mWindowHandles;
+
+ sp<InputWindowHandle> getWindowHandleLocked(const sp<InputChannel>& inputChannel) const;
+ bool hasWindowHandleLocked(const sp<InputWindowHandle>& windowHandle) const;
+
+ // Focus tracking for keys, trackball, etc.
+ sp<InputWindowHandle> mFocusedWindowHandle;
+
+ // Focus tracking for touch.
+ struct TouchedWindow {
+ sp<InputWindowHandle> windowHandle;
+ int32_t targetFlags;
+ BitSet32 pointerIds; // zero unless target flag FLAG_SPLIT is set
+ };
+ struct TouchState {
+ bool down;
+ bool split;
+ int32_t deviceId; // id of the device that is currently down, others are rejected
+ uint32_t source; // source of the device that is current down, others are rejected
+ int32_t displayId; // id to the display that currently has a touch, others are rejected
+ Vector<TouchedWindow> windows;
+
+ TouchState();
+ ~TouchState();
+ void reset();
+ void copyFrom(const TouchState& other);
+ void addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds);
+ void removeWindow(const sp<InputWindowHandle>& windowHandle);
+ void filterNonAsIsTouchWindows();
+ sp<InputWindowHandle> getFirstForegroundWindowHandle() const;
+ bool isSlippery() const;
+ };
+
+ KeyedVector<int32_t, TouchState> mTouchStatesByDisplay;
+ TouchState mTempTouchState;
+
+ // Focused application.
+ sp<InputApplicationHandle> mFocusedApplicationHandle;
+
+ // Dispatcher state at time of last ANR.
+ String8 mLastANRState;
+
+ // Dispatch inbound events.
+ bool dispatchConfigurationChangedLocked(
+ nsecs_t currentTime, ConfigurationChangedEntry* entry);
+ bool dispatchDeviceResetLocked(
+ nsecs_t currentTime, DeviceResetEntry* entry);
+ bool dispatchKeyLocked(
+ nsecs_t currentTime, KeyEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime);
+ bool dispatchMotionLocked(
+ nsecs_t currentTime, MotionEntry* entry,
+ DropReason* dropReason, nsecs_t* nextWakeupTime);
+ void dispatchEventLocked(nsecs_t currentTime, EventEntry* entry,
+ const Vector<InputTarget>& inputTargets);
+
+ void logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry);
+ void logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry);
+
+ // Keeping track of ANR timeouts.
+ enum InputTargetWaitCause {
+ INPUT_TARGET_WAIT_CAUSE_NONE,
+ INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY,
+ INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY,
+ };
+
+ InputTargetWaitCause mInputTargetWaitCause;
+ nsecs_t mInputTargetWaitStartTime;
+ nsecs_t mInputTargetWaitTimeoutTime;
+ bool mInputTargetWaitTimeoutExpired;
+ sp<InputApplicationHandle> mInputTargetWaitApplicationHandle;
+
+ // Contains the last window which received a hover event.
+ sp<InputWindowHandle> mLastHoverWindowHandle;
+
+ // Finding targets for input events.
+ int32_t handleTargetsNotReadyLocked(nsecs_t currentTime, const EventEntry* entry,
+ const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t* nextWakeupTime, const char* reason);
+ void resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout,
+ const sp<InputChannel>& inputChannel);
+ nsecs_t getTimeSpentWaitingForApplicationLocked(nsecs_t currentTime);
+ void resetANRTimeoutsLocked();
+
+ int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry,
+ Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime);
+ int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry,
+ Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime,
+ bool* outConflictingPointerActions);
+
+ void addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets);
+ void addMonitoringTargetsLocked(Vector<InputTarget>& inputTargets);
+
+ void pokeUserActivityLocked(const EventEntry* eventEntry);
+ bool checkInjectionPermission(const sp<InputWindowHandle>& windowHandle,
+ const InjectionState* injectionState);
+ bool isWindowObscuredAtPointLocked(const sp<InputWindowHandle>& windowHandle,
+ int32_t x, int32_t y) const;
+ bool isWindowReadyForMoreInputLocked(nsecs_t currentTime,
+ const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry);
+ String8 getApplicationWindowLabelLocked(const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle);
+
+ // Manage the dispatch cycle for a single connection.
+ // These methods are deliberately not Interruptible because doing all of the work
+ // with the mutex held makes it easier to ensure that connection invariants are maintained.
+ // If needed, the methods post commands to run later once the critical bits are done.
+ void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget);
+ void enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget);
+ void enqueueDispatchEntryLocked(const sp<Connection>& connection,
+ EventEntry* eventEntry, const InputTarget* inputTarget, int32_t dispatchMode);
+ void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection);
+ void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ uint32_t seq, bool handled);
+ void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
+ bool notify);
+ void drainDispatchQueueLocked(Queue<DispatchEntry>* queue);
+ void releaseDispatchEntryLocked(DispatchEntry* dispatchEntry);
+ static int handleReceiveCallback(int fd, int events, void* data);
+
+ void synthesizeCancelationEventsForAllConnectionsLocked(
+ const CancelationOptions& options);
+ void synthesizeCancelationEventsForInputChannelLocked(const sp<InputChannel>& channel,
+ const CancelationOptions& options);
+ void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection,
+ const CancelationOptions& options);
+
+ // Splitting motion events across windows.
+ MotionEntry* splitMotionEvent(const MotionEntry* originalMotionEntry, BitSet32 pointerIds);
+
+ // Reset and drop everything the dispatcher is doing.
+ void resetAndDropEverythingLocked(const char* reason);
+
+ // Dump state.
+ void dumpDispatchStateLocked(String8& dump);
+ void logDispatchStateLocked();
+
+ // Registration.
+ void removeMonitorChannelLocked(const sp<InputChannel>& inputChannel);
+ status_t unregisterInputChannelLocked(const sp<InputChannel>& inputChannel, bool notify);
+
+ // Add or remove a connection to the mActiveConnections vector.
+ void activateConnectionLocked(Connection* connection);
+ void deactivateConnectionLocked(Connection* connection);
+
+ // Interesting events that we might like to log or tell the framework about.
+ void onDispatchCycleFinishedLocked(
+ nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled);
+ void onDispatchCycleBrokenLocked(
+ nsecs_t currentTime, const sp<Connection>& connection);
+ void onANRLocked(
+ nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
+ const sp<InputWindowHandle>& windowHandle,
+ nsecs_t eventTime, nsecs_t waitStartTime, const char* reason);
+
+ // Outbound policy interactions.
+ void doNotifyConfigurationChangedInterruptible(CommandEntry* commandEntry);
+ void doNotifyInputChannelBrokenLockedInterruptible(CommandEntry* commandEntry);
+ void doNotifyANRLockedInterruptible(CommandEntry* commandEntry);
+ void doInterceptKeyBeforeDispatchingLockedInterruptible(CommandEntry* commandEntry);
+ void doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry);
+ bool afterKeyEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, KeyEntry* keyEntry, bool handled);
+ bool afterMotionEventLockedInterruptible(const sp<Connection>& connection,
+ DispatchEntry* dispatchEntry, MotionEntry* motionEntry, bool handled);
+ void doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry);
+ void initializeKeyEvent(KeyEvent* event, const KeyEntry* entry);
+
+ // Statistics gathering.
+ void updateDispatchStatisticsLocked(nsecs_t currentTime, const EventEntry* entry,
+ int32_t injectionResult, nsecs_t timeSpentWaitingForApplication);
+ void traceInboundQueueLengthLocked();
+ void traceOutboundQueueLengthLocked(const sp<Connection>& connection);
+ void traceWaitQueueLengthLocked(const sp<Connection>& connection);
+};
+
+/* Enqueues and dispatches input events, endlessly. */
+class InputDispatcherThread : public Thread {
+public:
+ explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher);
+ ~InputDispatcherThread();
+
+private:
+ virtual bool threadLoop();
+
+ sp<InputDispatcherInterface> mDispatcher;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_DISPATCHER_H
diff --git a/services/inputflinger/InputFlinger.cpp b/services/inputflinger/InputFlinger.cpp
new file mode 100644
index 0000000..9ea6ce5
--- /dev/null
+++ b/services/inputflinger/InputFlinger.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputFlinger"
+
+#include "InputFlinger.h"
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/PermissionCache.h>
+#include <cutils/log.h>
+#include <private/android_filesystem_config.h>
+
+namespace android {
+
+const String16 sAccessInputFlingerPermission("android.permission.ACCESS_INPUT_FLINGER");
+const String16 sDumpPermission("android.permission.DUMP");
+
+
+InputFlinger::InputFlinger() :
+ BnInputFlinger() {
+ ALOGI("InputFlinger is starting");
+}
+
+InputFlinger::~InputFlinger() {
+}
+
+status_t InputFlinger::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ switch (code) {
+ case DO_SOMETHING_TRANSACTION:
+ const IPCThreadState* ipc = IPCThreadState::self();
+ const int pid = ipc->getCallingPid();
+ const int uid = ipc->getCallingUid();
+ if (!PermissionCache::checkPermission(sAccessInputFlingerPermission, pid, uid)) {
+ ALOGE("Permission Denial: "
+ "can't access InputFlinger from pid=%d, uid=%d", pid, uid);
+ return PERMISSION_DENIED;
+ }
+ break;
+ }
+
+ return BnInputFlinger::onTransact(code, data, reply, flags);
+}
+
+status_t InputFlinger::dump(int fd, const Vector<String16>& args) {
+ String8 result;
+ const IPCThreadState* ipc = IPCThreadState::self();
+ const int pid = ipc->getCallingPid();
+ const int uid = ipc->getCallingUid();
+ if ((uid != AID_SHELL)
+ && !PermissionCache::checkPermission(sDumpPermission, pid, uid)) {
+ result.appendFormat("Permission Denial: "
+ "can't dump SurfaceFlinger from pid=%d, uid=%d\n", pid, uid);
+ } else {
+ dumpInternal(result);
+ }
+ write(fd, result.string(), result.size());
+ return OK;
+}
+
+void InputFlinger::dumpInternal(String8& result) {
+ result.append("INPUT FLINGER (dumpsys inputflinger)\n");
+ result.append("... nothing here yet...\n");
+}
+
+status_t InputFlinger::doSomething() {
+ ALOGI("Did something...");
+ return OK;
+}
+
+}; // namespace android
diff --git a/services/inputflinger/InputFlinger.h b/services/inputflinger/InputFlinger.h
new file mode 100644
index 0000000..731ab17
--- /dev/null
+++ b/services/inputflinger/InputFlinger.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_INPUT_FLINGER_H
+#define ANDROID_INPUT_FLINGER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <cutils/compiler.h>
+#include <input/IInputFlinger.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+namespace android {
+
+class InputFlinger : public BnInputFlinger {
+public:
+ static char const* getServiceName() ANDROID_API {
+ return "inputflinger";
+ }
+
+ InputFlinger() ANDROID_API;
+
+ // IBinder interface
+ virtual status_t onTransact(uint32_t code,
+ const Parcel& data, Parcel* reply, uint32_t flags);
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ // IInputFlinger interface
+ virtual status_t doSomething();
+
+private:
+ virtual ~InputFlinger();
+
+ void dumpInternal(String8& result);
+};
+
+} // namespace android
+
+#endif // ANDROID_INPUT_FLINGER_H
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
new file mode 100644
index 0000000..85bb0ed
--- /dev/null
+++ b/services/inputflinger/InputListener.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputListener"
+
+//#define LOG_NDEBUG 0
+
+#include "InputListener.h"
+
+#include <cutils/log.h>
+
+namespace android {
+
+// --- NotifyConfigurationChangedArgs ---
+
+NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(nsecs_t eventTime) :
+ eventTime(eventTime) {
+}
+
+NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(
+ const NotifyConfigurationChangedArgs& other) :
+ eventTime(other.eventTime) {
+}
+
+void NotifyConfigurationChangedArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyConfigurationChanged(this);
+}
+
+
+// --- NotifyKeyArgs ---
+
+NotifyKeyArgs::NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, nsecs_t downTime) :
+ eventTime(eventTime), deviceId(deviceId), source(source), policyFlags(policyFlags),
+ action(action), flags(flags), keyCode(keyCode), scanCode(scanCode),
+ metaState(metaState), downTime(downTime) {
+}
+
+NotifyKeyArgs::NotifyKeyArgs(const NotifyKeyArgs& other) :
+ eventTime(other.eventTime), deviceId(other.deviceId), source(other.source),
+ policyFlags(other.policyFlags),
+ action(other.action), flags(other.flags),
+ keyCode(other.keyCode), scanCode(other.scanCode),
+ metaState(other.metaState), downTime(other.downTime) {
+}
+
+void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyKey(this);
+}
+
+
+// --- NotifyMotionArgs ---
+
+NotifyMotionArgs::NotifyMotionArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime) :
+ eventTime(eventTime), deviceId(deviceId), source(source), policyFlags(policyFlags),
+ action(action), flags(flags), metaState(metaState), buttonState(buttonState),
+ edgeFlags(edgeFlags), displayId(displayId), pointerCount(pointerCount),
+ xPrecision(xPrecision), yPrecision(yPrecision), downTime(downTime) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ this->pointerProperties[i].copyFrom(pointerProperties[i]);
+ this->pointerCoords[i].copyFrom(pointerCoords[i]);
+ }
+}
+
+NotifyMotionArgs::NotifyMotionArgs(const NotifyMotionArgs& other) :
+ eventTime(other.eventTime), deviceId(other.deviceId), source(other.source),
+ policyFlags(other.policyFlags),
+ action(other.action), flags(other.flags),
+ metaState(other.metaState), buttonState(other.buttonState),
+ edgeFlags(other.edgeFlags), displayId(other.displayId),
+ pointerCount(other.pointerCount),
+ xPrecision(other.xPrecision), yPrecision(other.yPrecision), downTime(other.downTime) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].copyFrom(other.pointerProperties[i]);
+ pointerCoords[i].copyFrom(other.pointerCoords[i]);
+ }
+}
+
+void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyMotion(this);
+}
+
+
+// --- NotifySwitchArgs ---
+
+NotifySwitchArgs::NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags,
+ uint32_t switchValues, uint32_t switchMask) :
+ eventTime(eventTime), policyFlags(policyFlags),
+ switchValues(switchValues), switchMask(switchMask) {
+}
+
+NotifySwitchArgs::NotifySwitchArgs(const NotifySwitchArgs& other) :
+ eventTime(other.eventTime), policyFlags(other.policyFlags),
+ switchValues(other.switchValues), switchMask(other.switchMask) {
+}
+
+void NotifySwitchArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifySwitch(this);
+}
+
+
+// --- NotifyDeviceResetArgs ---
+
+NotifyDeviceResetArgs::NotifyDeviceResetArgs(nsecs_t eventTime, int32_t deviceId) :
+ eventTime(eventTime), deviceId(deviceId) {
+}
+
+NotifyDeviceResetArgs::NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) :
+ eventTime(other.eventTime), deviceId(other.deviceId) {
+}
+
+void NotifyDeviceResetArgs::notify(const sp<InputListenerInterface>& listener) const {
+ listener->notifyDeviceReset(this);
+}
+
+
+// --- QueuedInputListener ---
+
+QueuedInputListener::QueuedInputListener(const sp<InputListenerInterface>& innerListener) :
+ mInnerListener(innerListener) {
+}
+
+QueuedInputListener::~QueuedInputListener() {
+ size_t count = mArgsQueue.size();
+ for (size_t i = 0; i < count; i++) {
+ delete mArgsQueue[i];
+ }
+}
+
+void QueuedInputListener::notifyConfigurationChanged(
+ const NotifyConfigurationChangedArgs* args) {
+ mArgsQueue.push(new NotifyConfigurationChangedArgs(*args));
+}
+
+void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
+ mArgsQueue.push(new NotifyKeyArgs(*args));
+}
+
+void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
+ mArgsQueue.push(new NotifyMotionArgs(*args));
+}
+
+void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) {
+ mArgsQueue.push(new NotifySwitchArgs(*args));
+}
+
+void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+ mArgsQueue.push(new NotifyDeviceResetArgs(*args));
+}
+
+void QueuedInputListener::flush() {
+ size_t count = mArgsQueue.size();
+ for (size_t i = 0; i < count; i++) {
+ NotifyArgs* args = mArgsQueue[i];
+ args->notify(mInnerListener);
+ delete args;
+ }
+ mArgsQueue.clear();
+}
+
+
+} // namespace android
diff --git a/services/inputflinger/InputListener.h b/services/inputflinger/InputListener.h
new file mode 100644
index 0000000..78ae10f
--- /dev/null
+++ b/services/inputflinger/InputListener.h
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_LISTENER_H
+#define _UI_INPUT_LISTENER_H
+
+#include <input/Input.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+
+namespace android {
+
+class InputListenerInterface;
+
+
+/* Superclass of all input event argument objects */
+struct NotifyArgs {
+ virtual ~NotifyArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const = 0;
+};
+
+
+/* Describes a configuration change event. */
+struct NotifyConfigurationChangedArgs : public NotifyArgs {
+ nsecs_t eventTime;
+
+ inline NotifyConfigurationChangedArgs() { }
+
+ NotifyConfigurationChangedArgs(nsecs_t eventTime);
+
+ NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other);
+
+ virtual ~NotifyConfigurationChangedArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a key event. */
+struct NotifyKeyArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ uint32_t policyFlags;
+ int32_t action;
+ int32_t flags;
+ int32_t keyCode;
+ int32_t scanCode;
+ int32_t metaState;
+ nsecs_t downTime;
+
+ inline NotifyKeyArgs() { }
+
+ NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, nsecs_t downTime);
+
+ NotifyKeyArgs(const NotifyKeyArgs& other);
+
+ virtual ~NotifyKeyArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a motion event. */
+struct NotifyMotionArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+ uint32_t source;
+ uint32_t policyFlags;
+ int32_t action;
+ int32_t flags;
+ int32_t metaState;
+ int32_t buttonState;
+ int32_t edgeFlags;
+ int32_t displayId;
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ float xPrecision;
+ float yPrecision;
+ nsecs_t downTime;
+
+ inline NotifyMotionArgs() { }
+
+ NotifyMotionArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags, int32_t displayId, uint32_t pointerCount,
+ const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
+ float xPrecision, float yPrecision, nsecs_t downTime);
+
+ NotifyMotionArgs(const NotifyMotionArgs& other);
+
+ virtual ~NotifyMotionArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a switch event. */
+struct NotifySwitchArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ uint32_t policyFlags;
+ uint32_t switchValues;
+ uint32_t switchMask;
+
+ inline NotifySwitchArgs() { }
+
+ NotifySwitchArgs(nsecs_t eventTime, uint32_t policyFlags,
+ uint32_t switchValues, uint32_t switchMask);
+
+ NotifySwitchArgs(const NotifySwitchArgs& other);
+
+ virtual ~NotifySwitchArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/* Describes a device reset event, such as when a device is added,
+ * reconfigured, or removed. */
+struct NotifyDeviceResetArgs : public NotifyArgs {
+ nsecs_t eventTime;
+ int32_t deviceId;
+
+ inline NotifyDeviceResetArgs() { }
+
+ NotifyDeviceResetArgs(nsecs_t eventTime, int32_t deviceId);
+
+ NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other);
+
+ virtual ~NotifyDeviceResetArgs() { }
+
+ virtual void notify(const sp<InputListenerInterface>& listener) const;
+};
+
+
+/*
+ * The interface used by the InputReader to notify the InputListener about input events.
+ */
+class InputListenerInterface : public virtual RefBase {
+protected:
+ InputListenerInterface() { }
+ virtual ~InputListenerInterface() { }
+
+public:
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0;
+ virtual void notifyKey(const NotifyKeyArgs* args) = 0;
+ virtual void notifyMotion(const NotifyMotionArgs* args) = 0;
+ virtual void notifySwitch(const NotifySwitchArgs* args) = 0;
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0;
+};
+
+
+/*
+ * An implementation of the listener interface that queues up and defers dispatch
+ * of decoded events until flushed.
+ */
+class QueuedInputListener : public InputListenerInterface {
+protected:
+ virtual ~QueuedInputListener();
+
+public:
+ QueuedInputListener(const sp<InputListenerInterface>& innerListener);
+
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
+ virtual void notifyKey(const NotifyKeyArgs* args);
+ virtual void notifyMotion(const NotifyMotionArgs* args);
+ virtual void notifySwitch(const NotifySwitchArgs* args);
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
+
+ void flush();
+
+private:
+ sp<InputListenerInterface> mInnerListener;
+ Vector<NotifyArgs*> mArgsQueue;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_LISTENER_H
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
new file mode 100644
index 0000000..6a6547b
--- /dev/null
+++ b/services/inputflinger/InputManager.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputManager"
+
+//#define LOG_NDEBUG 0
+
+#include "InputManager.h"
+
+#include <cutils/log.h>
+
+namespace android {
+
+InputManager::InputManager(
+ const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& readerPolicy,
+ const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
+ mDispatcher = new InputDispatcher(dispatcherPolicy);
+ mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
+ initialize();
+}
+
+InputManager::InputManager(
+ const sp<InputReaderInterface>& reader,
+ const sp<InputDispatcherInterface>& dispatcher) :
+ mReader(reader),
+ mDispatcher(dispatcher) {
+ initialize();
+}
+
+InputManager::~InputManager() {
+ stop();
+}
+
+void InputManager::initialize() {
+ mReaderThread = new InputReaderThread(mReader);
+ mDispatcherThread = new InputDispatcherThread(mDispatcher);
+}
+
+status_t InputManager::start() {
+ status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ ALOGE("Could not start InputDispatcher thread due to error %d.", result);
+ return result;
+ }
+
+ result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
+ if (result) {
+ ALOGE("Could not start InputReader thread due to error %d.", result);
+
+ mDispatcherThread->requestExit();
+ return result;
+ }
+
+ return OK;
+}
+
+status_t InputManager::stop() {
+ status_t result = mReaderThread->requestExitAndWait();
+ if (result) {
+ ALOGW("Could not stop InputReader thread due to error %d.", result);
+ }
+
+ result = mDispatcherThread->requestExitAndWait();
+ if (result) {
+ ALOGW("Could not stop InputDispatcher thread due to error %d.", result);
+ }
+
+ return OK;
+}
+
+sp<InputReaderInterface> InputManager::getReader() {
+ return mReader;
+}
+
+sp<InputDispatcherInterface> InputManager::getDispatcher() {
+ return mDispatcher;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
new file mode 100644
index 0000000..a213b2d
--- /dev/null
+++ b/services/inputflinger/InputManager.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_MANAGER_H
+#define _UI_INPUT_MANAGER_H
+
+/**
+ * Native input manager.
+ */
+
+#include "EventHub.h"
+#include "InputReader.h"
+#include "InputDispatcher.h"
+
+#include <input/Input.h>
+#include <input/InputTransport.h>
+#include <utils/Errors.h>
+#include <utils/Vector.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * The input manager is the core of the system event processing.
+ *
+ * The input manager uses two threads.
+ *
+ * 1. The InputReaderThread (called "InputReader") reads and preprocesses raw input events,
+ * applies policy, and posts messages to a queue managed by the DispatcherThread.
+ * 2. The InputDispatcherThread (called "InputDispatcher") thread waits for new events on the
+ * queue and asynchronously dispatches them to applications.
+ *
+ * By design, the InputReaderThread class and InputDispatcherThread class do not share any
+ * internal state. Moreover, all communication is done one way from the InputReaderThread
+ * into the InputDispatcherThread and never the reverse. Both classes may interact with the
+ * InputDispatchPolicy, however.
+ *
+ * The InputManager class never makes any calls into Java itself. Instead, the
+ * InputDispatchPolicy is responsible for performing all external interactions with the
+ * system, including calling DVM services.
+ */
+class InputManagerInterface : public virtual RefBase {
+protected:
+ InputManagerInterface() { }
+ virtual ~InputManagerInterface() { }
+
+public:
+ /* Starts the input manager threads. */
+ virtual status_t start() = 0;
+
+ /* Stops the input manager threads and waits for them to exit. */
+ virtual status_t stop() = 0;
+
+ /* Gets the input reader. */
+ virtual sp<InputReaderInterface> getReader() = 0;
+
+ /* Gets the input dispatcher. */
+ virtual sp<InputDispatcherInterface> getDispatcher() = 0;
+};
+
+class InputManager : public InputManagerInterface {
+protected:
+ virtual ~InputManager();
+
+public:
+ InputManager(
+ const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& readerPolicy,
+ const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);
+
+ // (used for testing purposes)
+ InputManager(
+ const sp<InputReaderInterface>& reader,
+ const sp<InputDispatcherInterface>& dispatcher);
+
+ virtual status_t start();
+ virtual status_t stop();
+
+ virtual sp<InputReaderInterface> getReader();
+ virtual sp<InputDispatcherInterface> getDispatcher();
+
+private:
+ sp<InputReaderInterface> mReader;
+ sp<InputReaderThread> mReaderThread;
+
+ sp<InputDispatcherInterface> mDispatcher;
+ sp<InputDispatcherThread> mDispatcherThread;
+
+ void initialize();
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_MANAGER_H
diff --git a/services/inputflinger/InputReader.cpp b/services/inputflinger/InputReader.cpp
new file mode 100644
index 0000000..8295c4c
--- /dev/null
+++ b/services/inputflinger/InputReader.cpp
@@ -0,0 +1,6584 @@
+/*
+ * 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
+
+// Log debug messages about the vibrator.
+#define DEBUG_VIBRATOR 0
+
+#include "InputReader.h"
+
+#include <cutils/log.h>
+#include <input/Keyboard.h>
+#include <input/VirtualKeyMap.h>
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+#include <math.h>
+
+#define INDENT " "
+#define INDENT2 " "
+#define INDENT3 " "
+#define INDENT4 " "
+#define INDENT5 " "
+
+namespace android {
+
+// --- Constants ---
+
+// Maximum number of slots supported when using the slot-based Multitouch Protocol B.
+static const size_t MAX_SLOTS = 32;
+
+// --- Static Functions ---
+
+template<typename T>
+inline static T abs(const T& value) {
+ return value < 0 ? - value : value;
+}
+
+template<typename T>
+inline static T min(const T& a, const T& b) {
+ return a < b ? a : b;
+}
+
+template<typename T>
+inline static void swap(T& a, T& b) {
+ T temp = a;
+ a = b;
+ b = temp;
+}
+
+inline static float avg(float x, float y) {
+ return (x + y) / 2;
+}
+
+inline static float distance(float x1, float y1, float x2, float y2) {
+ return hypotf(x1 - x2, y1 - y2);
+}
+
+inline static int32_t signExtendNybble(int32_t value) {
+ return value >= 8 ? value - 16 : value;
+}
+
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+static int32_t rotateValueUsingRotationMap(int32_t value, int32_t orientation,
+ const int32_t map[][4], size_t mapSize) {
+ if (orientation != DISPLAY_ORIENTATION_0) {
+ for (size_t i = 0; i < mapSize; i++) {
+ if (value == map[i][0]) {
+ return map[i][orientation];
+ }
+ }
+ }
+ return value;
+}
+
+static const int32_t keyCodeRotationMap[][4] = {
+ // key codes enumerated counter-clockwise with the original (unrotated) key first
+ // no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation
+ { AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT },
+ { AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN },
+ { AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT },
+ { AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP },
+};
+static const size_t keyCodeRotationMapSize =
+ sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
+
+static int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) {
+ return rotateValueUsingRotationMap(keyCode, orientation,
+ keyCodeRotationMap, keyCodeRotationMapSize);
+}
+
+static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) {
+ float temp;
+ switch (orientation) {
+ case DISPLAY_ORIENTATION_90:
+ temp = *deltaX;
+ *deltaX = *deltaY;
+ *deltaY = -temp;
+ break;
+
+ case DISPLAY_ORIENTATION_180:
+ *deltaX = -*deltaX;
+ *deltaY = -*deltaY;
+ break;
+
+ case DISPLAY_ORIENTATION_270:
+ temp = *deltaX;
+ *deltaX = -*deltaY;
+ *deltaY = temp;
+ break;
+ }
+}
+
+static inline bool sourcesMatchMask(uint32_t sources, uint32_t sourceMask) {
+ return (sources & sourceMask & ~ AINPUT_SOURCE_CLASS_MASK) != 0;
+}
+
+// Returns true if the pointer should be reported as being down given the specified
+// button states. This determines whether the event is reported as a touch event.
+static bool isPointerDown(int32_t buttonState) {
+ return buttonState &
+ (AMOTION_EVENT_BUTTON_PRIMARY | AMOTION_EVENT_BUTTON_SECONDARY
+ | AMOTION_EVENT_BUTTON_TERTIARY);
+}
+
+static float calculateCommonVector(float a, float b) {
+ if (a > 0 && b > 0) {
+ return a < b ? a : b;
+ } else if (a < 0 && b < 0) {
+ return a > b ? a : b;
+ } else {
+ return 0;
+ }
+}
+
+static void synthesizeButtonKey(InputReaderContext* context, int32_t action,
+ nsecs_t when, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState,
+ int32_t buttonState, int32_t keyCode) {
+ if (
+ (action == AKEY_EVENT_ACTION_DOWN
+ && !(lastButtonState & buttonState)
+ && (currentButtonState & buttonState))
+ || (action == AKEY_EVENT_ACTION_UP
+ && (lastButtonState & buttonState)
+ && !(currentButtonState & buttonState))) {
+ NotifyKeyArgs args(when, deviceId, source, policyFlags,
+ action, 0, keyCode, 0, context->getGlobalMetaState(), when);
+ context->getListener()->notifyKey(&args);
+ }
+}
+
+static void synthesizeButtonKeys(InputReaderContext* context, int32_t action,
+ nsecs_t when, int32_t deviceId, uint32_t source,
+ uint32_t policyFlags, int32_t lastButtonState, int32_t currentButtonState) {
+ synthesizeButtonKey(context, action, when, deviceId, source, policyFlags,
+ lastButtonState, currentButtonState,
+ AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK);
+ synthesizeButtonKey(context, action, when, deviceId, source, policyFlags,
+ lastButtonState, currentButtonState,
+ AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD);
+}
+
+
+// --- InputReaderConfiguration ---
+
+bool InputReaderConfiguration::getDisplayInfo(bool external, DisplayViewport* outViewport) const {
+ const DisplayViewport& viewport = external ? mExternalDisplay : mInternalDisplay;
+ if (viewport.displayId >= 0) {
+ *outViewport = viewport;
+ return true;
+ }
+ return false;
+}
+
+void InputReaderConfiguration::setDisplayInfo(bool external, const DisplayViewport& viewport) {
+ DisplayViewport& v = external ? mExternalDisplay : mInternalDisplay;
+ v = viewport;
+}
+
+
+// -- TouchAffineTransformation --
+void TouchAffineTransformation::applyTo(float& x, float& y) const {
+ float newX, newY;
+ newX = x * x_scale + y * x_ymix + x_offset;
+ newY = x * y_xmix + y * y_scale + y_offset;
+
+ x = newX;
+ y = newY;
+}
+
+
+// --- InputReader ---
+
+InputReader::InputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputListenerInterface>& listener) :
+ mContext(this), mEventHub(eventHub), mPolicy(policy),
+ mGlobalMetaState(0), mGeneration(1),
+ mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
+ mConfigurationChangesToRefresh(0) {
+ mQueuedListener = new QueuedInputListener(listener);
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ refreshConfigurationLocked(0);
+ updateGlobalMetaStateLocked();
+ } // release lock
+}
+
+InputReader::~InputReader() {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ delete mDevices.valueAt(i);
+ }
+}
+
+void InputReader::loopOnce() {
+ int32_t oldGeneration;
+ int32_t timeoutMillis;
+ bool inputDevicesChanged = false;
+ Vector<InputDeviceInfo> inputDevices;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ oldGeneration = mGeneration;
+ timeoutMillis = -1;
+
+ uint32_t changes = mConfigurationChangesToRefresh;
+ if (changes) {
+ mConfigurationChangesToRefresh = 0;
+ timeoutMillis = 0;
+ refreshConfigurationLocked(changes);
+ } else if (mNextTimeout != LLONG_MAX) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
+ }
+ } // release lock
+
+ size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
+
+ { // acquire lock
+ AutoMutex _l(mLock);
+ mReaderIsAliveCondition.broadcast();
+
+ if (count) {
+ processEventsLocked(mEventBuffer, count);
+ }
+
+ if (mNextTimeout != LLONG_MAX) {
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ if (now >= mNextTimeout) {
+#if DEBUG_RAW_EVENTS
+ ALOGD("Timeout expired, latency=%0.3fms", (now - mNextTimeout) * 0.000001f);
+#endif
+ mNextTimeout = LLONG_MAX;
+ timeoutExpiredLocked(now);
+ }
+ }
+
+ if (oldGeneration != mGeneration) {
+ inputDevicesChanged = true;
+ getInputDevicesLocked(inputDevices);
+ }
+ } // release lock
+
+ // Send out a message that the describes the changed input devices.
+ if (inputDevicesChanged) {
+ mPolicy->notifyInputDevicesChanged(inputDevices);
+ }
+
+ // Flush queued events out to the listener.
+ // This must happen outside of the lock because the listener could potentially call
+ // back into the InputReader's methods, such as getScanCodeState, or become blocked
+ // on another thread similarly waiting to acquire the InputReader lock thereby
+ // resulting in a deadlock. This situation is actually quite plausible because the
+ // listener is actually the input dispatcher, which calls into the window manager,
+ // which occasionally calls into the input reader.
+ mQueuedListener->flush();
+}
+
+void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
+ for (const RawEvent* rawEvent = rawEvents; count;) {
+ int32_t type = rawEvent->type;
+ size_t batchSize = 1;
+ if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
+ int32_t deviceId = rawEvent->deviceId;
+ while (batchSize < count) {
+ if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
+ || rawEvent[batchSize].deviceId != deviceId) {
+ break;
+ }
+ batchSize += 1;
+ }
+#if DEBUG_RAW_EVENTS
+ ALOGD("BatchSize: %d Count: %d", batchSize, count);
+#endif
+ processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
+ } else {
+ switch (rawEvent->type) {
+ case EventHubInterface::DEVICE_ADDED:
+ addDeviceLocked(rawEvent->when, rawEvent->deviceId);
+ break;
+ case EventHubInterface::DEVICE_REMOVED:
+ removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
+ break;
+ case EventHubInterface::FINISHED_DEVICE_SCAN:
+ handleConfigurationChangedLocked(rawEvent->when);
+ break;
+ default:
+ ALOG_ASSERT(false); // can't happen
+ break;
+ }
+ }
+ count -= batchSize;
+ rawEvent += batchSize;
+ }
+}
+
+void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+ return;
+ }
+
+ InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
+ uint32_t classes = mEventHub->getDeviceClasses(deviceId);
+ int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId);
+
+ InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
+ device->configure(when, &mConfig, 0);
+ device->reset(when);
+
+ if (device->isIgnored()) {
+ ALOGI("Device added: id=%d, name='%s' (ignored non-input device)", deviceId,
+ identifier.name.string());
+ } else {
+ ALOGI("Device added: id=%d, name='%s', sources=0x%08x", deviceId,
+ identifier.name.string(), device->getSources());
+ }
+
+ mDevices.add(deviceId, device);
+ bumpGenerationLocked();
+}
+
+void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) {
+ InputDevice* device = NULL;
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex < 0) {
+ ALOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
+ return;
+ }
+
+ device = mDevices.valueAt(deviceIndex);
+ mDevices.removeItemsAt(deviceIndex, 1);
+ bumpGenerationLocked();
+
+ if (device->isIgnored()) {
+ ALOGI("Device removed: id=%d, name='%s' (ignored non-input device)",
+ device->getId(), device->getName().string());
+ } else {
+ ALOGI("Device removed: id=%d, name='%s', sources=0x%08x",
+ device->getId(), device->getName().string(), device->getSources());
+ }
+
+ device->reset(when);
+ delete device;
+}
+
+InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
+ const InputDeviceIdentifier& identifier, uint32_t classes) {
+ InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
+ controllerNumber, identifier, classes);
+
+ // External devices.
+ if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
+ device->setExternal(true);
+ }
+
+ // Switch-like devices.
+ if (classes & INPUT_DEVICE_CLASS_SWITCH) {
+ device->addMapper(new SwitchInputMapper(device));
+ }
+
+ // Vibrator-like devices.
+ if (classes & INPUT_DEVICE_CLASS_VIBRATOR) {
+ device->addMapper(new VibratorInputMapper(device));
+ }
+
+ // Keyboard-like devices.
+ uint32_t keyboardSource = 0;
+ int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
+ if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
+ keyboardSource |= AINPUT_SOURCE_KEYBOARD;
+ }
+ if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
+ keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
+ }
+ if (classes & INPUT_DEVICE_CLASS_DPAD) {
+ keyboardSource |= AINPUT_SOURCE_DPAD;
+ }
+ if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
+ keyboardSource |= AINPUT_SOURCE_GAMEPAD;
+ }
+
+ if (keyboardSource != 0) {
+ device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
+ }
+
+ // Cursor-like devices.
+ if (classes & INPUT_DEVICE_CLASS_CURSOR) {
+ device->addMapper(new CursorInputMapper(device));
+ }
+
+ // Touchscreens and touchpad devices.
+ if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
+ device->addMapper(new MultiTouchInputMapper(device));
+ } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
+ device->addMapper(new SingleTouchInputMapper(device));
+ }
+
+ // Joystick-like devices.
+ if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
+ device->addMapper(new JoystickInputMapper(device));
+ }
+
+ return device;
+}
+
+void InputReader::processEventsForDeviceLocked(int32_t deviceId,
+ const RawEvent* rawEvents, size_t count) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex < 0) {
+ ALOGW("Discarding event for unknown deviceId %d.", deviceId);
+ return;
+ }
+
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (device->isIgnored()) {
+ //ALOGD("Discarding event for ignored deviceId %d.", deviceId);
+ return;
+ }
+
+ device->process(rawEvents, count);
+}
+
+void InputReader::timeoutExpiredLocked(nsecs_t when) {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (!device->isIgnored()) {
+ device->timeoutExpired(when);
+ }
+ }
+}
+
+void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
+ // Reset global meta state because it depends on the list of all configured devices.
+ updateGlobalMetaStateLocked();
+
+ // Enqueue configuration changed.
+ NotifyConfigurationChangedArgs args(when);
+ mQueuedListener->notifyConfigurationChanged(&args);
+}
+
+void InputReader::refreshConfigurationLocked(uint32_t changes) {
+ mPolicy->getReaderConfiguration(&mConfig);
+ mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
+
+ if (changes) {
+ ALOGI("Reconfiguring input devices. changes=0x%08x", changes);
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
+ mEventHub->requestReopenDevices();
+ } else {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ device->configure(now, &mConfig, changes);
+ }
+ }
+ }
+}
+
+void InputReader::updateGlobalMetaStateLocked() {
+ mGlobalMetaState = 0;
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ mGlobalMetaState |= device->getMetaState();
+ }
+}
+
+int32_t InputReader::getGlobalMetaStateLocked() {
+ return mGlobalMetaState;
+}
+
+void InputReader::disableVirtualKeysUntilLocked(nsecs_t time) {
+ mDisableVirtualKeysTimeout = time;
+}
+
+bool InputReader::shouldDropVirtualKeyLocked(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) {
+ if (now < mDisableVirtualKeysTimeout) {
+ ALOGI("Dropping virtual key from device %s because virtual keys are "
+ "temporarily disabled for the next %0.3fms. keyCode=%d, scanCode=%d",
+ device->getName().string(),
+ (mDisableVirtualKeysTimeout - now) * 0.000001,
+ keyCode, scanCode);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void InputReader::fadePointerLocked() {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ device->fadePointer();
+ }
+}
+
+void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) {
+ if (when < mNextTimeout) {
+ mNextTimeout = when;
+ mEventHub->wake();
+ }
+}
+
+int32_t InputReader::bumpGenerationLocked() {
+ return ++mGeneration;
+}
+
+void InputReader::getInputDevices(Vector<InputDeviceInfo>& outInputDevices) {
+ AutoMutex _l(mLock);
+ getInputDevicesLocked(outInputDevices);
+}
+
+void InputReader::getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices) {
+ outInputDevices.clear();
+
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (!device->isIgnored()) {
+ outInputDevices.push();
+ device->getDeviceInfo(&outInputDevices.editTop());
+ }
+ }
+}
+
+int32_t InputReader::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode) {
+ AutoMutex _l(mLock);
+
+ return getStateLocked(deviceId, sourceMask, keyCode, &InputDevice::getKeyCodeState);
+}
+
+int32_t InputReader::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode) {
+ AutoMutex _l(mLock);
+
+ return getStateLocked(deviceId, sourceMask, scanCode, &InputDevice::getScanCodeState);
+}
+
+int32_t InputReader::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t switchCode) {
+ AutoMutex _l(mLock);
+
+ return getStateLocked(deviceId, sourceMask, switchCode, &InputDevice::getSwitchState);
+}
+
+int32_t InputReader::getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
+ GetStateFunc getStateFunc) {
+ int32_t result = AKEY_STATE_UNKNOWN;
+ if (deviceId >= 0) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result = (device->*getStateFunc)(sourceMask, code);
+ }
+ }
+ } else {
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ // If any device reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that
+ // value. Otherwise, return AKEY_STATE_UP as long as one device reports it.
+ int32_t currentResult = (device->*getStateFunc)(sourceMask, code);
+ if (currentResult >= AKEY_STATE_DOWN) {
+ return currentResult;
+ } else if (currentResult == AKEY_STATE_UP) {
+ result = currentResult;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+bool InputReader::hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+ AutoMutex _l(mLock);
+
+ memset(outFlags, 0, numCodes);
+ return markSupportedKeyCodesLocked(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+}
+
+bool InputReader::markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
+ bool result = false;
+ if (deviceId >= 0) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result = device->markSupportedKeyCodes(sourceMask,
+ numCodes, keyCodes, outFlags);
+ }
+ }
+ } else {
+ size_t numDevices = mDevices.size();
+ for (size_t i = 0; i < numDevices; i++) {
+ InputDevice* device = mDevices.valueAt(i);
+ if (! device->isIgnored() && sourcesMatchMask(device->getSources(), sourceMask)) {
+ result |= device->markSupportedKeyCodes(sourceMask,
+ numCodes, keyCodes, outFlags);
+ }
+ }
+ }
+ return result;
+}
+
+void InputReader::requestRefreshConfiguration(uint32_t changes) {
+ AutoMutex _l(mLock);
+
+ if (changes) {
+ bool needWake = !mConfigurationChangesToRefresh;
+ mConfigurationChangesToRefresh |= changes;
+
+ if (needWake) {
+ mEventHub->wake();
+ }
+ }
+}
+
+void InputReader::vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->cancelVibrate(token);
+ }
+}
+
+void InputReader::dump(String8& dump) {
+ AutoMutex _l(mLock);
+
+ mEventHub->dump(dump);
+ dump.append("\n");
+
+ dump.append("Input Reader State:\n");
+
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ mDevices.valueAt(i)->dump(dump);
+ }
+
+ dump.append(INDENT "Configuration:\n");
+ dump.append(INDENT2 "ExcludedDeviceNames: [");
+ for (size_t i = 0; i < mConfig.excludedDeviceNames.size(); i++) {
+ if (i != 0) {
+ dump.append(", ");
+ }
+ dump.append(mConfig.excludedDeviceNames.itemAt(i).string());
+ }
+ dump.append("]\n");
+ dump.appendFormat(INDENT2 "VirtualKeyQuietTime: %0.1fms\n",
+ mConfig.virtualKeyQuietTime * 0.000001f);
+
+ dump.appendFormat(INDENT2 "PointerVelocityControlParameters: "
+ "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n",
+ mConfig.pointerVelocityControlParameters.scale,
+ mConfig.pointerVelocityControlParameters.lowThreshold,
+ mConfig.pointerVelocityControlParameters.highThreshold,
+ mConfig.pointerVelocityControlParameters.acceleration);
+
+ dump.appendFormat(INDENT2 "WheelVelocityControlParameters: "
+ "scale=%0.3f, lowThreshold=%0.3f, highThreshold=%0.3f, acceleration=%0.3f\n",
+ mConfig.wheelVelocityControlParameters.scale,
+ mConfig.wheelVelocityControlParameters.lowThreshold,
+ mConfig.wheelVelocityControlParameters.highThreshold,
+ mConfig.wheelVelocityControlParameters.acceleration);
+
+ dump.appendFormat(INDENT2 "PointerGesture:\n");
+ dump.appendFormat(INDENT3 "Enabled: %s\n",
+ toString(mConfig.pointerGesturesEnabled));
+ dump.appendFormat(INDENT3 "QuietInterval: %0.1fms\n",
+ mConfig.pointerGestureQuietInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "DragMinSwitchSpeed: %0.1fpx/s\n",
+ mConfig.pointerGestureDragMinSwitchSpeed);
+ dump.appendFormat(INDENT3 "TapInterval: %0.1fms\n",
+ mConfig.pointerGestureTapInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "TapDragInterval: %0.1fms\n",
+ mConfig.pointerGestureTapDragInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "TapSlop: %0.1fpx\n",
+ mConfig.pointerGestureTapSlop);
+ dump.appendFormat(INDENT3 "MultitouchSettleInterval: %0.1fms\n",
+ mConfig.pointerGestureMultitouchSettleInterval * 0.000001f);
+ dump.appendFormat(INDENT3 "MultitouchMinDistance: %0.1fpx\n",
+ mConfig.pointerGestureMultitouchMinDistance);
+ dump.appendFormat(INDENT3 "SwipeTransitionAngleCosine: %0.1f\n",
+ mConfig.pointerGestureSwipeTransitionAngleCosine);
+ dump.appendFormat(INDENT3 "SwipeMaxWidthRatio: %0.1f\n",
+ mConfig.pointerGestureSwipeMaxWidthRatio);
+ dump.appendFormat(INDENT3 "MovementSpeedRatio: %0.1f\n",
+ mConfig.pointerGestureMovementSpeedRatio);
+ dump.appendFormat(INDENT3 "ZoomSpeedRatio: %0.1f\n",
+ mConfig.pointerGestureZoomSpeedRatio);
+}
+
+void InputReader::monitor() {
+ // Acquire and release the lock to ensure that the reader has not deadlocked.
+ mLock.lock();
+ mEventHub->wake();
+ mReaderIsAliveCondition.wait(mLock);
+ mLock.unlock();
+
+ // Check the EventHub
+ mEventHub->monitor();
+}
+
+
+// --- InputReader::ContextImpl ---
+
+InputReader::ContextImpl::ContextImpl(InputReader* reader) :
+ mReader(reader) {
+}
+
+void InputReader::ContextImpl::updateGlobalMetaState() {
+ // lock is already held by the input loop
+ mReader->updateGlobalMetaStateLocked();
+}
+
+int32_t InputReader::ContextImpl::getGlobalMetaState() {
+ // lock is already held by the input loop
+ return mReader->getGlobalMetaStateLocked();
+}
+
+void InputReader::ContextImpl::disableVirtualKeysUntil(nsecs_t time) {
+ // lock is already held by the input loop
+ mReader->disableVirtualKeysUntilLocked(time);
+}
+
+bool InputReader::ContextImpl::shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) {
+ // lock is already held by the input loop
+ return mReader->shouldDropVirtualKeyLocked(now, device, keyCode, scanCode);
+}
+
+void InputReader::ContextImpl::fadePointer() {
+ // lock is already held by the input loop
+ mReader->fadePointerLocked();
+}
+
+void InputReader::ContextImpl::requestTimeoutAtTime(nsecs_t when) {
+ // lock is already held by the input loop
+ mReader->requestTimeoutAtTimeLocked(when);
+}
+
+int32_t InputReader::ContextImpl::bumpGeneration() {
+ // lock is already held by the input loop
+ return mReader->bumpGenerationLocked();
+}
+
+InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() {
+ return mReader->mPolicy.get();
+}
+
+InputListenerInterface* InputReader::ContextImpl::getListener() {
+ return mReader->mQueuedListener.get();
+}
+
+EventHubInterface* InputReader::ContextImpl::getEventHub() {
+ return mReader->mEventHub.get();
+}
+
+
+// --- InputReaderThread ---
+
+InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
+ Thread(/*canCallJava*/ true), mReader(reader) {
+}
+
+InputReaderThread::~InputReaderThread() {
+}
+
+bool InputReaderThread::threadLoop() {
+ mReader->loopOnce();
+ return true;
+}
+
+
+// --- InputDevice ---
+
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
+ int32_t controllerNumber, const InputDeviceIdentifier& identifier, uint32_t classes) :
+ mContext(context), mId(id), mGeneration(generation), mControllerNumber(controllerNumber),
+ mIdentifier(identifier), mClasses(classes),
+ mSources(0), mIsExternal(false), mDropUntilNextSync(false) {
+}
+
+InputDevice::~InputDevice() {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ delete mMappers[i];
+ }
+ mMappers.clear();
+}
+
+void InputDevice::dump(String8& dump) {
+ InputDeviceInfo deviceInfo;
+ getDeviceInfo(& deviceInfo);
+
+ dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(),
+ deviceInfo.getDisplayName().string());
+ dump.appendFormat(INDENT2 "Generation: %d\n", mGeneration);
+ dump.appendFormat(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
+ dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
+ dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+
+ const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
+ if (!ranges.isEmpty()) {
+ dump.append(INDENT2 "Motion Ranges:\n");
+ for (size_t i = 0; i < ranges.size(); i++) {
+ const InputDeviceInfo::MotionRange& range = ranges.itemAt(i);
+ const char* label = getAxisLabel(range.axis);
+ char name[32];
+ if (label) {
+ strncpy(name, label, sizeof(name));
+ name[sizeof(name) - 1] = '\0';
+ } else {
+ snprintf(name, sizeof(name), "%d", range.axis);
+ }
+ dump.appendFormat(INDENT3 "%s: source=0x%08x, "
+ "min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f, resolution=%0.3f\n",
+ name, range.source, range.min, range.max, range.flat, range.fuzz,
+ range.resolution);
+ }
+ }
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->dump(dump);
+ }
+}
+
+void InputDevice::addMapper(InputMapper* mapper) {
+ mMappers.add(mapper);
+}
+
+void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) {
+ mSources = 0;
+
+ if (!isIgnored()) {
+ if (!changes) { // first time only
+ mContext->getEventHub()->getConfiguration(mId, &mConfiguration);
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
+ if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) {
+ sp<KeyCharacterMap> keyboardLayout =
+ mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier);
+ if (mContext->getEventHub()->setKeyboardLayoutOverlay(mId, keyboardLayout)) {
+ bumpGeneration();
+ }
+ }
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DEVICE_ALIAS)) {
+ if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) {
+ String8 alias = mContext->getPolicy()->getDeviceAlias(mIdentifier);
+ if (mAlias != alias) {
+ mAlias = alias;
+ bumpGeneration();
+ }
+ }
+ }
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->configure(when, config, changes);
+ mSources |= mapper->getSources();
+ }
+ }
+}
+
+void InputDevice::reset(nsecs_t when) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->reset(when);
+ }
+
+ mContext->updateGlobalMetaState();
+
+ notifyReset(when);
+}
+
+void InputDevice::process(const RawEvent* rawEvents, size_t count) {
+ // Process all of the events in order for each mapper.
+ // We cannot simply ask each mapper to process them in bulk because mappers may
+ // have side-effects that must be interleaved. For example, joystick movement events and
+ // gamepad button presses are handled by different mappers but they should be dispatched
+ // in the order received.
+ size_t numMappers = mMappers.size();
+ for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
+#if DEBUG_RAW_EVENTS
+ ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%lld",
+ rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value,
+ rawEvent->when);
+#endif
+
+ if (mDropUntilNextSync) {
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ mDropUntilNextSync = false;
+#if DEBUG_RAW_EVENTS
+ ALOGD("Recovered from input event buffer overrun.");
+#endif
+ } else {
+#if DEBUG_RAW_EVENTS
+ ALOGD("Dropped input event while waiting for next input sync.");
+#endif
+ }
+ } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
+ ALOGI("Detected input event buffer overrun for device %s.", getName().string());
+ mDropUntilNextSync = true;
+ reset(rawEvent->when);
+ } else {
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->process(rawEvent);
+ }
+ }
+ }
+}
+
+void InputDevice::timeoutExpired(nsecs_t when) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->timeoutExpired(when);
+ }
+}
+
+void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
+ outDeviceInfo->initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias,
+ mIsExternal);
+
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->populateDeviceInfo(outDeviceInfo);
+ }
+}
+
+int32_t InputDevice::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return getState(sourceMask, keyCode, & InputMapper::getKeyCodeState);
+}
+
+int32_t InputDevice::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return getState(sourceMask, scanCode, & InputMapper::getScanCodeState);
+}
+
+int32_t InputDevice::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return getState(sourceMask, switchCode, & InputMapper::getSwitchState);
+}
+
+int32_t InputDevice::getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc) {
+ int32_t result = AKEY_STATE_UNKNOWN;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+ // If any mapper reports AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL, return that
+ // value. Otherwise, return AKEY_STATE_UP as long as one mapper reports it.
+ int32_t currentResult = (mapper->*getStateFunc)(sourceMask, code);
+ if (currentResult >= AKEY_STATE_DOWN) {
+ return currentResult;
+ } else if (currentResult == AKEY_STATE_UP) {
+ result = currentResult;
+ }
+ }
+ }
+ return result;
+}
+
+bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ bool result = false;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ if (sourcesMatchMask(mapper->getSources(), sourceMask)) {
+ result |= mapper->markSupportedKeyCodes(sourceMask, numCodes, keyCodes, outFlags);
+ }
+ }
+ return result;
+}
+
+void InputDevice::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputDevice::cancelVibrate(int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->cancelVibrate(token);
+ }
+}
+
+int32_t InputDevice::getMetaState() {
+ int32_t result = 0;
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ result |= mapper->getMetaState();
+ }
+ return result;
+}
+
+void InputDevice::fadePointer() {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->fadePointer();
+ }
+}
+
+void InputDevice::bumpGeneration() {
+ mGeneration = mContext->bumpGeneration();
+}
+
+void InputDevice::notifyReset(nsecs_t when) {
+ NotifyDeviceResetArgs args(when, mId);
+ mContext->getListener()->notifyDeviceReset(&args);
+}
+
+
+// --- CursorButtonAccumulator ---
+
+CursorButtonAccumulator::CursorButtonAccumulator() {
+ clearButtons();
+}
+
+void CursorButtonAccumulator::reset(InputDevice* device) {
+ mBtnLeft = device->isKeyPressed(BTN_LEFT);
+ mBtnRight = device->isKeyPressed(BTN_RIGHT);
+ mBtnMiddle = device->isKeyPressed(BTN_MIDDLE);
+ mBtnBack = device->isKeyPressed(BTN_BACK);
+ mBtnSide = device->isKeyPressed(BTN_SIDE);
+ mBtnForward = device->isKeyPressed(BTN_FORWARD);
+ mBtnExtra = device->isKeyPressed(BTN_EXTRA);
+ mBtnTask = device->isKeyPressed(BTN_TASK);
+}
+
+void CursorButtonAccumulator::clearButtons() {
+ mBtnLeft = 0;
+ mBtnRight = 0;
+ mBtnMiddle = 0;
+ mBtnBack = 0;
+ mBtnSide = 0;
+ mBtnForward = 0;
+ mBtnExtra = 0;
+ mBtnTask = 0;
+}
+
+void CursorButtonAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_KEY) {
+ switch (rawEvent->code) {
+ case BTN_LEFT:
+ mBtnLeft = rawEvent->value;
+ break;
+ case BTN_RIGHT:
+ mBtnRight = rawEvent->value;
+ break;
+ case BTN_MIDDLE:
+ mBtnMiddle = rawEvent->value;
+ break;
+ case BTN_BACK:
+ mBtnBack = rawEvent->value;
+ break;
+ case BTN_SIDE:
+ mBtnSide = rawEvent->value;
+ break;
+ case BTN_FORWARD:
+ mBtnForward = rawEvent->value;
+ break;
+ case BTN_EXTRA:
+ mBtnExtra = rawEvent->value;
+ break;
+ case BTN_TASK:
+ mBtnTask = rawEvent->value;
+ break;
+ }
+ }
+}
+
+uint32_t CursorButtonAccumulator::getButtonState() const {
+ uint32_t result = 0;
+ if (mBtnLeft) {
+ result |= AMOTION_EVENT_BUTTON_PRIMARY;
+ }
+ if (mBtnRight) {
+ result |= AMOTION_EVENT_BUTTON_SECONDARY;
+ }
+ if (mBtnMiddle) {
+ result |= AMOTION_EVENT_BUTTON_TERTIARY;
+ }
+ if (mBtnBack || mBtnSide) {
+ result |= AMOTION_EVENT_BUTTON_BACK;
+ }
+ if (mBtnForward || mBtnExtra) {
+ result |= AMOTION_EVENT_BUTTON_FORWARD;
+ }
+ return result;
+}
+
+
+// --- CursorMotionAccumulator ---
+
+CursorMotionAccumulator::CursorMotionAccumulator() {
+ clearRelativeAxes();
+}
+
+void CursorMotionAccumulator::reset(InputDevice* device) {
+ clearRelativeAxes();
+}
+
+void CursorMotionAccumulator::clearRelativeAxes() {
+ mRelX = 0;
+ mRelY = 0;
+}
+
+void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_REL) {
+ switch (rawEvent->code) {
+ case REL_X:
+ mRelX = rawEvent->value;
+ break;
+ case REL_Y:
+ mRelY = rawEvent->value;
+ break;
+ }
+ }
+}
+
+void CursorMotionAccumulator::finishSync() {
+ clearRelativeAxes();
+}
+
+
+// --- CursorScrollAccumulator ---
+
+CursorScrollAccumulator::CursorScrollAccumulator() :
+ mHaveRelWheel(false), mHaveRelHWheel(false) {
+ clearRelativeAxes();
+}
+
+void CursorScrollAccumulator::configure(InputDevice* device) {
+ mHaveRelWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_WHEEL);
+ mHaveRelHWheel = device->getEventHub()->hasRelativeAxis(device->getId(), REL_HWHEEL);
+}
+
+void CursorScrollAccumulator::reset(InputDevice* device) {
+ clearRelativeAxes();
+}
+
+void CursorScrollAccumulator::clearRelativeAxes() {
+ mRelWheel = 0;
+ mRelHWheel = 0;
+}
+
+void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_REL) {
+ switch (rawEvent->code) {
+ case REL_WHEEL:
+ mRelWheel = rawEvent->value;
+ break;
+ case REL_HWHEEL:
+ mRelHWheel = rawEvent->value;
+ break;
+ }
+ }
+}
+
+void CursorScrollAccumulator::finishSync() {
+ clearRelativeAxes();
+}
+
+
+// --- TouchButtonAccumulator ---
+
+TouchButtonAccumulator::TouchButtonAccumulator() :
+ mHaveBtnTouch(false), mHaveStylus(false) {
+ clearButtons();
+}
+
+void TouchButtonAccumulator::configure(InputDevice* device) {
+ mHaveBtnTouch = device->hasKey(BTN_TOUCH);
+ mHaveStylus = device->hasKey(BTN_TOOL_PEN)
+ || device->hasKey(BTN_TOOL_RUBBER)
+ || device->hasKey(BTN_TOOL_BRUSH)
+ || device->hasKey(BTN_TOOL_PENCIL)
+ || device->hasKey(BTN_TOOL_AIRBRUSH);
+}
+
+void TouchButtonAccumulator::reset(InputDevice* device) {
+ mBtnTouch = device->isKeyPressed(BTN_TOUCH);
+ mBtnStylus = device->isKeyPressed(BTN_STYLUS);
+ mBtnStylus2 = device->isKeyPressed(BTN_STYLUS);
+ mBtnToolFinger = device->isKeyPressed(BTN_TOOL_FINGER);
+ mBtnToolPen = device->isKeyPressed(BTN_TOOL_PEN);
+ mBtnToolRubber = device->isKeyPressed(BTN_TOOL_RUBBER);
+ mBtnToolBrush = device->isKeyPressed(BTN_TOOL_BRUSH);
+ mBtnToolPencil = device->isKeyPressed(BTN_TOOL_PENCIL);
+ mBtnToolAirbrush = device->isKeyPressed(BTN_TOOL_AIRBRUSH);
+ mBtnToolMouse = device->isKeyPressed(BTN_TOOL_MOUSE);
+ mBtnToolLens = device->isKeyPressed(BTN_TOOL_LENS);
+ mBtnToolDoubleTap = device->isKeyPressed(BTN_TOOL_DOUBLETAP);
+ mBtnToolTripleTap = device->isKeyPressed(BTN_TOOL_TRIPLETAP);
+ mBtnToolQuadTap = device->isKeyPressed(BTN_TOOL_QUADTAP);
+}
+
+void TouchButtonAccumulator::clearButtons() {
+ mBtnTouch = 0;
+ mBtnStylus = 0;
+ mBtnStylus2 = 0;
+ mBtnToolFinger = 0;
+ mBtnToolPen = 0;
+ mBtnToolRubber = 0;
+ mBtnToolBrush = 0;
+ mBtnToolPencil = 0;
+ mBtnToolAirbrush = 0;
+ mBtnToolMouse = 0;
+ mBtnToolLens = 0;
+ mBtnToolDoubleTap = 0;
+ mBtnToolTripleTap = 0;
+ mBtnToolQuadTap = 0;
+}
+
+void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_KEY) {
+ switch (rawEvent->code) {
+ case BTN_TOUCH:
+ mBtnTouch = rawEvent->value;
+ break;
+ case BTN_STYLUS:
+ mBtnStylus = rawEvent->value;
+ break;
+ case BTN_STYLUS2:
+ mBtnStylus2 = rawEvent->value;
+ break;
+ case BTN_TOOL_FINGER:
+ mBtnToolFinger = rawEvent->value;
+ break;
+ case BTN_TOOL_PEN:
+ mBtnToolPen = rawEvent->value;
+ break;
+ case BTN_TOOL_RUBBER:
+ mBtnToolRubber = rawEvent->value;
+ break;
+ case BTN_TOOL_BRUSH:
+ mBtnToolBrush = rawEvent->value;
+ break;
+ case BTN_TOOL_PENCIL:
+ mBtnToolPencil = rawEvent->value;
+ break;
+ case BTN_TOOL_AIRBRUSH:
+ mBtnToolAirbrush = rawEvent->value;
+ break;
+ case BTN_TOOL_MOUSE:
+ mBtnToolMouse = rawEvent->value;
+ break;
+ case BTN_TOOL_LENS:
+ mBtnToolLens = rawEvent->value;
+ break;
+ case BTN_TOOL_DOUBLETAP:
+ mBtnToolDoubleTap = rawEvent->value;
+ break;
+ case BTN_TOOL_TRIPLETAP:
+ mBtnToolTripleTap = rawEvent->value;
+ break;
+ case BTN_TOOL_QUADTAP:
+ mBtnToolQuadTap = rawEvent->value;
+ break;
+ }
+ }
+}
+
+uint32_t TouchButtonAccumulator::getButtonState() const {
+ uint32_t result = 0;
+ if (mBtnStylus) {
+ result |= AMOTION_EVENT_BUTTON_SECONDARY;
+ }
+ if (mBtnStylus2) {
+ result |= AMOTION_EVENT_BUTTON_TERTIARY;
+ }
+ return result;
+}
+
+int32_t TouchButtonAccumulator::getToolType() const {
+ if (mBtnToolMouse || mBtnToolLens) {
+ return AMOTION_EVENT_TOOL_TYPE_MOUSE;
+ }
+ if (mBtnToolRubber) {
+ return AMOTION_EVENT_TOOL_TYPE_ERASER;
+ }
+ if (mBtnToolPen || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush) {
+ return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+ }
+ if (mBtnToolFinger || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap) {
+ return AMOTION_EVENT_TOOL_TYPE_FINGER;
+ }
+ return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+bool TouchButtonAccumulator::isToolActive() const {
+ return mBtnTouch || mBtnToolFinger || mBtnToolPen || mBtnToolRubber
+ || mBtnToolBrush || mBtnToolPencil || mBtnToolAirbrush
+ || mBtnToolMouse || mBtnToolLens
+ || mBtnToolDoubleTap || mBtnToolTripleTap || mBtnToolQuadTap;
+}
+
+bool TouchButtonAccumulator::isHovering() const {
+ return mHaveBtnTouch && !mBtnTouch;
+}
+
+bool TouchButtonAccumulator::hasStylus() const {
+ return mHaveStylus;
+}
+
+
+// --- RawPointerAxes ---
+
+RawPointerAxes::RawPointerAxes() {
+ clear();
+}
+
+void RawPointerAxes::clear() {
+ x.clear();
+ y.clear();
+ pressure.clear();
+ touchMajor.clear();
+ touchMinor.clear();
+ toolMajor.clear();
+ toolMinor.clear();
+ orientation.clear();
+ distance.clear();
+ tiltX.clear();
+ tiltY.clear();
+ trackingId.clear();
+ slot.clear();
+}
+
+
+// --- RawPointerData ---
+
+RawPointerData::RawPointerData() {
+ clear();
+}
+
+void RawPointerData::clear() {
+ pointerCount = 0;
+ clearIdBits();
+}
+
+void RawPointerData::copyFrom(const RawPointerData& other) {
+ pointerCount = other.pointerCount;
+ hoveringIdBits = other.hoveringIdBits;
+ touchingIdBits = other.touchingIdBits;
+
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointers[i] = other.pointers[i];
+
+ int id = pointers[i].id;
+ idToIndex[id] = other.idToIndex[id];
+ }
+}
+
+void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const {
+ float x = 0, y = 0;
+ uint32_t count = touchingIdBits.count();
+ if (count) {
+ for (BitSet32 idBits(touchingIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const Pointer& pointer = pointerForId(id);
+ x += pointer.x;
+ y += pointer.y;
+ }
+ x /= count;
+ y /= count;
+ }
+ *outX = x;
+ *outY = y;
+}
+
+
+// --- CookedPointerData ---
+
+CookedPointerData::CookedPointerData() {
+ clear();
+}
+
+void CookedPointerData::clear() {
+ pointerCount = 0;
+ hoveringIdBits.clear();
+ touchingIdBits.clear();
+}
+
+void CookedPointerData::copyFrom(const CookedPointerData& other) {
+ pointerCount = other.pointerCount;
+ hoveringIdBits = other.hoveringIdBits;
+ touchingIdBits = other.touchingIdBits;
+
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ pointerProperties[i].copyFrom(other.pointerProperties[i]);
+ pointerCoords[i].copyFrom(other.pointerCoords[i]);
+
+ int id = pointerProperties[i].id;
+ idToIndex[id] = other.idToIndex[id];
+ }
+}
+
+
+// --- SingleTouchMotionAccumulator ---
+
+SingleTouchMotionAccumulator::SingleTouchMotionAccumulator() {
+ clearAbsoluteAxes();
+}
+
+void SingleTouchMotionAccumulator::reset(InputDevice* device) {
+ mAbsX = device->getAbsoluteAxisValue(ABS_X);
+ mAbsY = device->getAbsoluteAxisValue(ABS_Y);
+ mAbsPressure = device->getAbsoluteAxisValue(ABS_PRESSURE);
+ mAbsToolWidth = device->getAbsoluteAxisValue(ABS_TOOL_WIDTH);
+ mAbsDistance = device->getAbsoluteAxisValue(ABS_DISTANCE);
+ mAbsTiltX = device->getAbsoluteAxisValue(ABS_TILT_X);
+ mAbsTiltY = device->getAbsoluteAxisValue(ABS_TILT_Y);
+}
+
+void SingleTouchMotionAccumulator::clearAbsoluteAxes() {
+ mAbsX = 0;
+ mAbsY = 0;
+ mAbsPressure = 0;
+ mAbsToolWidth = 0;
+ mAbsDistance = 0;
+ mAbsTiltX = 0;
+ mAbsTiltY = 0;
+}
+
+void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_ABS) {
+ switch (rawEvent->code) {
+ case ABS_X:
+ mAbsX = rawEvent->value;
+ break;
+ case ABS_Y:
+ mAbsY = rawEvent->value;
+ break;
+ case ABS_PRESSURE:
+ mAbsPressure = rawEvent->value;
+ break;
+ case ABS_TOOL_WIDTH:
+ mAbsToolWidth = rawEvent->value;
+ break;
+ case ABS_DISTANCE:
+ mAbsDistance = rawEvent->value;
+ break;
+ case ABS_TILT_X:
+ mAbsTiltX = rawEvent->value;
+ break;
+ case ABS_TILT_Y:
+ mAbsTiltY = rawEvent->value;
+ break;
+ }
+ }
+}
+
+
+// --- MultiTouchMotionAccumulator ---
+
+MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() :
+ mCurrentSlot(-1), mSlots(NULL), mSlotCount(0), mUsingSlotsProtocol(false),
+ mHaveStylus(false) {
+}
+
+MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() {
+ delete[] mSlots;
+}
+
+void MultiTouchMotionAccumulator::configure(InputDevice* device,
+ size_t slotCount, bool usingSlotsProtocol) {
+ mSlotCount = slotCount;
+ mUsingSlotsProtocol = usingSlotsProtocol;
+ mHaveStylus = device->hasAbsoluteAxis(ABS_MT_TOOL_TYPE);
+
+ delete[] mSlots;
+ mSlots = new Slot[slotCount];
+}
+
+void MultiTouchMotionAccumulator::reset(InputDevice* device) {
+ // Unfortunately there is no way to read the initial contents of the slots.
+ // So when we reset the accumulator, we must assume they are all zeroes.
+ if (mUsingSlotsProtocol) {
+ // Query the driver for the current slot index and use it as the initial slot
+ // before we start reading events from the device. It is possible that the
+ // current slot index will not be the same as it was when the first event was
+ // written into the evdev buffer, which means the input mapper could start
+ // out of sync with the initial state of the events in the evdev buffer.
+ // In the extremely unlikely case that this happens, the data from
+ // two slots will be confused until the next ABS_MT_SLOT event is received.
+ // This can cause the touch point to "jump", but at least there will be
+ // no stuck touches.
+ int32_t initialSlot;
+ status_t status = device->getEventHub()->getAbsoluteAxisValue(device->getId(),
+ ABS_MT_SLOT, &initialSlot);
+ if (status) {
+ ALOGD("Could not retrieve current multitouch slot index. status=%d", status);
+ initialSlot = -1;
+ }
+ clearSlots(initialSlot);
+ } else {
+ clearSlots(-1);
+ }
+}
+
+void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) {
+ if (mSlots) {
+ for (size_t i = 0; i < mSlotCount; i++) {
+ mSlots[i].clear();
+ }
+ }
+ mCurrentSlot = initialSlot;
+}
+
+void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
+ if (rawEvent->type == EV_ABS) {
+ bool newSlot = false;
+ if (mUsingSlotsProtocol) {
+ if (rawEvent->code == 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->code) {
+ case ABS_MT_POSITION_X:
+ slot->mInUse = true;
+ slot->mAbsMTPositionX = rawEvent->value;
+ break;
+ case ABS_MT_POSITION_Y:
+ slot->mInUse = true;
+ slot->mAbsMTPositionY = rawEvent->value;
+ break;
+ case ABS_MT_TOUCH_MAJOR:
+ slot->mInUse = true;
+ slot->mAbsMTTouchMajor = rawEvent->value;
+ break;
+ case ABS_MT_TOUCH_MINOR:
+ slot->mInUse = true;
+ slot->mAbsMTTouchMinor = rawEvent->value;
+ slot->mHaveAbsMTTouchMinor = true;
+ break;
+ case ABS_MT_WIDTH_MAJOR:
+ slot->mInUse = true;
+ slot->mAbsMTWidthMajor = rawEvent->value;
+ break;
+ case ABS_MT_WIDTH_MINOR:
+ slot->mInUse = true;
+ slot->mAbsMTWidthMinor = rawEvent->value;
+ slot->mHaveAbsMTWidthMinor = true;
+ break;
+ case ABS_MT_ORIENTATION:
+ slot->mInUse = true;
+ slot->mAbsMTOrientation = rawEvent->value;
+ break;
+ case ABS_MT_TRACKING_ID:
+ if (mUsingSlotsProtocol && rawEvent->value < 0) {
+ // The slot is no longer in use but it retains its previous contents,
+ // which may be reused for subsequent touches.
+ slot->mInUse = false;
+ } else {
+ slot->mInUse = true;
+ slot->mAbsMTTrackingId = rawEvent->value;
+ }
+ break;
+ case ABS_MT_PRESSURE:
+ slot->mInUse = true;
+ slot->mAbsMTPressure = rawEvent->value;
+ break;
+ case ABS_MT_DISTANCE:
+ slot->mInUse = true;
+ slot->mAbsMTDistance = rawEvent->value;
+ break;
+ case ABS_MT_TOOL_TYPE:
+ slot->mInUse = true;
+ slot->mAbsMTToolType = rawEvent->value;
+ slot->mHaveAbsMTToolType = true;
+ break;
+ }
+ }
+ } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
+ // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
+ mCurrentSlot += 1;
+ }
+}
+
+void MultiTouchMotionAccumulator::finishSync() {
+ if (!mUsingSlotsProtocol) {
+ clearSlots(-1);
+ }
+}
+
+bool MultiTouchMotionAccumulator::hasStylus() const {
+ return mHaveStylus;
+}
+
+
+// --- MultiTouchMotionAccumulator::Slot ---
+
+MultiTouchMotionAccumulator::Slot::Slot() {
+ clear();
+}
+
+void MultiTouchMotionAccumulator::Slot::clear() {
+ mInUse = false;
+ mHaveAbsMTTouchMinor = false;
+ mHaveAbsMTWidthMinor = false;
+ mHaveAbsMTToolType = false;
+ mAbsMTPositionX = 0;
+ mAbsMTPositionY = 0;
+ mAbsMTTouchMajor = 0;
+ mAbsMTTouchMinor = 0;
+ mAbsMTWidthMajor = 0;
+ mAbsMTWidthMinor = 0;
+ mAbsMTOrientation = 0;
+ mAbsMTTrackingId = -1;
+ mAbsMTPressure = 0;
+ mAbsMTDistance = 0;
+ mAbsMTToolType = 0;
+}
+
+int32_t MultiTouchMotionAccumulator::Slot::getToolType() const {
+ if (mHaveAbsMTToolType) {
+ switch (mAbsMTToolType) {
+ case MT_TOOL_FINGER:
+ return AMOTION_EVENT_TOOL_TYPE_FINGER;
+ case MT_TOOL_PEN:
+ return AMOTION_EVENT_TOOL_TYPE_STYLUS;
+ }
+ }
+ return AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+}
+
+
+// --- InputMapper ---
+
+InputMapper::InputMapper(InputDevice* device) :
+ mDevice(device), mContext(device->getContext()) {
+}
+
+InputMapper::~InputMapper() {
+}
+
+void InputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ info->addSource(getSources());
+}
+
+void InputMapper::dump(String8& dump) {
+}
+
+void InputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+}
+
+void InputMapper::reset(nsecs_t when) {
+}
+
+void InputMapper::timeoutExpired(nsecs_t when) {
+}
+
+int32_t InputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t InputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return AKEY_STATE_UNKNOWN;
+}
+
+bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ return false;
+}
+
+void InputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+}
+
+void InputMapper::cancelVibrate(int32_t token) {
+}
+
+int32_t InputMapper::getMetaState() {
+ return 0;
+}
+
+void InputMapper::fadePointer() {
+}
+
+status_t InputMapper::getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo) {
+ return getEventHub()->getAbsoluteAxisInfo(getDeviceId(), axis, axisInfo);
+}
+
+void InputMapper::bumpGeneration() {
+ mDevice->bumpGeneration();
+}
+
+void InputMapper::dumpRawAbsoluteAxisInfo(String8& dump,
+ const RawAbsoluteAxisInfo& axis, const char* name) {
+ if (axis.valid) {
+ dump.appendFormat(INDENT4 "%s: min=%d, max=%d, flat=%d, fuzz=%d, resolution=%d\n",
+ name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz, axis.resolution);
+ } else {
+ dump.appendFormat(INDENT4 "%s: unknown range\n", name);
+ }
+}
+
+
+// --- SwitchInputMapper ---
+
+SwitchInputMapper::SwitchInputMapper(InputDevice* device) :
+ InputMapper(device), mUpdatedSwitchValues(0), mUpdatedSwitchMask(0) {
+}
+
+SwitchInputMapper::~SwitchInputMapper() {
+}
+
+uint32_t SwitchInputMapper::getSources() {
+ return AINPUT_SOURCE_SWITCH;
+}
+
+void SwitchInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_SW:
+ processSwitch(rawEvent->code, rawEvent->value);
+ break;
+
+ case EV_SYN:
+ if (rawEvent->code == SYN_REPORT) {
+ sync(rawEvent->when);
+ }
+ }
+}
+
+void SwitchInputMapper::processSwitch(int32_t switchCode, int32_t switchValue) {
+ if (switchCode >= 0 && switchCode < 32) {
+ if (switchValue) {
+ mUpdatedSwitchValues |= 1 << switchCode;
+ }
+ mUpdatedSwitchMask |= 1 << switchCode;
+ }
+}
+
+void SwitchInputMapper::sync(nsecs_t when) {
+ if (mUpdatedSwitchMask) {
+ NotifySwitchArgs args(when, 0, mUpdatedSwitchValues, mUpdatedSwitchMask);
+ getListener()->notifySwitch(&args);
+
+ mUpdatedSwitchValues = 0;
+ mUpdatedSwitchMask = 0;
+ }
+}
+
+int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ return getEventHub()->getSwitchState(getDeviceId(), switchCode);
+}
+
+
+// --- VibratorInputMapper ---
+
+VibratorInputMapper::VibratorInputMapper(InputDevice* device) :
+ InputMapper(device), mVibrating(false) {
+}
+
+VibratorInputMapper::~VibratorInputMapper() {
+}
+
+uint32_t VibratorInputMapper::getSources() {
+ return 0;
+}
+
+void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ info->setVibrator(true);
+}
+
+void VibratorInputMapper::process(const RawEvent* rawEvent) {
+ // TODO: Handle FF_STATUS, although it does not seem to be widely supported.
+}
+
+void VibratorInputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+#if DEBUG_VIBRATOR
+ String8 patternStr;
+ for (size_t i = 0; i < patternSize; i++) {
+ if (i != 0) {
+ patternStr.append(", ");
+ }
+ patternStr.appendFormat("%lld", pattern[i]);
+ }
+ ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%ld, token=%d",
+ getDeviceId(), patternStr.string(), repeat, token);
+#endif
+
+ mVibrating = true;
+ memcpy(mPattern, pattern, patternSize * sizeof(nsecs_t));
+ mPatternSize = patternSize;
+ mRepeat = repeat;
+ mToken = token;
+ mIndex = -1;
+
+ nextStep();
+}
+
+void VibratorInputMapper::cancelVibrate(int32_t token) {
+#if DEBUG_VIBRATOR
+ ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
+#endif
+
+ if (mVibrating && mToken == token) {
+ stopVibrating();
+ }
+}
+
+void VibratorInputMapper::timeoutExpired(nsecs_t when) {
+ if (mVibrating) {
+ if (when >= mNextStepTime) {
+ nextStep();
+ } else {
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+ }
+ }
+}
+
+void VibratorInputMapper::nextStep() {
+ mIndex += 1;
+ if (size_t(mIndex) >= mPatternSize) {
+ if (mRepeat < 0) {
+ // We are done.
+ stopVibrating();
+ return;
+ }
+ mIndex = mRepeat;
+ }
+
+ bool vibratorOn = mIndex & 1;
+ nsecs_t duration = mPattern[mIndex];
+ if (vibratorOn) {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending vibrate deviceId=%d, duration=%lld",
+ getDeviceId(), duration);
+#endif
+ getEventHub()->vibrate(getDeviceId(), duration);
+ } else {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+ }
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ mNextStepTime = now + duration;
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: scheduled timeout in %0.3fms", duration * 0.000001f);
+#endif
+}
+
+void VibratorInputMapper::stopVibrating() {
+ mVibrating = false;
+#if DEBUG_VIBRATOR
+ ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+}
+
+void VibratorInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Vibrator Input Mapper:\n");
+ dump.appendFormat(INDENT3 "Vibrating: %s\n", toString(mVibrating));
+}
+
+
+// --- KeyboardInputMapper ---
+
+KeyboardInputMapper::KeyboardInputMapper(InputDevice* device,
+ uint32_t source, int32_t keyboardType) :
+ InputMapper(device), mSource(source),
+ mKeyboardType(keyboardType) {
+}
+
+KeyboardInputMapper::~KeyboardInputMapper() {
+}
+
+uint32_t KeyboardInputMapper::getSources() {
+ return mSource;
+}
+
+void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ info->setKeyboardType(mKeyboardType);
+ info->setKeyCharacterMap(getEventHub()->getKeyCharacterMap(getDeviceId()));
+}
+
+void KeyboardInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Keyboard Input Mapper:\n");
+ dumpParameters(dump);
+ dump.appendFormat(INDENT3 "KeyboardType: %d\n", mKeyboardType);
+ dump.appendFormat(INDENT3 "Orientation: %d\n", mOrientation);
+ dump.appendFormat(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size());
+ dump.appendFormat(INDENT3 "MetaState: 0x%0x\n", mMetaState);
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", (long long)mDownTime);
+}
+
+
+void KeyboardInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ // Configure basic parameters.
+ configureParameters();
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+ DisplayViewport v;
+ if (config->getDisplayInfo(false /*external*/, &v)) {
+ mOrientation = v.orientation;
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ }
+}
+
+void KeyboardInputMapper::configureParameters() {
+ mParameters.orientationAware = false;
+ getDevice()->getConfiguration().tryGetProperty(String8("keyboard.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.hasAssociatedDisplay = false;
+ if (mParameters.orientationAware) {
+ mParameters.hasAssociatedDisplay = true;
+ }
+
+ mParameters.handlesKeyRepeat = false;
+ getDevice()->getConfiguration().tryGetProperty(String8("keyboard.handlesKeyRepeat"),
+ mParameters.handlesKeyRepeat);
+}
+
+void KeyboardInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+ dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n",
+ toString(mParameters.hasAssociatedDisplay));
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+ dump.appendFormat(INDENT4 "HandlesKeyRepeat: %s\n",
+ toString(mParameters.handlesKeyRepeat));
+}
+
+void KeyboardInputMapper::reset(nsecs_t when) {
+ mMetaState = AMETA_NONE;
+ mDownTime = 0;
+ mKeyDowns.clear();
+ mCurrentHidUsage = 0;
+
+ resetLedState();
+
+ InputMapper::reset(when);
+}
+
+void KeyboardInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_KEY: {
+ int32_t scanCode = rawEvent->code;
+ int32_t usageCode = mCurrentHidUsage;
+ mCurrentHidUsage = 0;
+
+ if (isKeyboardOrGamepadKey(scanCode)) {
+ int32_t keyCode;
+ uint32_t flags;
+ if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
+ keyCode = AKEYCODE_UNKNOWN;
+ flags = 0;
+ }
+ processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
+ }
+ break;
+ }
+ case EV_MSC: {
+ if (rawEvent->code == MSC_SCAN) {
+ mCurrentHidUsage = rawEvent->value;
+ }
+ break;
+ }
+ case EV_SYN: {
+ if (rawEvent->code == SYN_REPORT) {
+ mCurrentHidUsage = 0;
+ }
+ }
+ }
+}
+
+bool KeyboardInputMapper::isKeyboardOrGamepadKey(int32_t scanCode) {
+ return scanCode < BTN_MOUSE
+ || scanCode >= KEY_OK
+ || (scanCode >= BTN_MISC && scanCode < BTN_MOUSE)
+ || (scanCode >= BTN_JOYSTICK && scanCode < BTN_DIGI);
+}
+
+void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
+ int32_t scanCode, uint32_t policyFlags) {
+
+ if (down) {
+ // Rotate key codes according to orientation if needed.
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+ keyCode = rotateKeyCode(keyCode, mOrientation);
+ }
+
+ // Add key down.
+ ssize_t keyDownIndex = findKeyDown(scanCode);
+ if (keyDownIndex >= 0) {
+ // key repeat, be sure to use same keycode as before in case of rotation
+ keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
+ } else {
+ // key down
+ if ((policyFlags & POLICY_FLAG_VIRTUAL)
+ && mContext->shouldDropVirtualKey(when,
+ getDevice(), keyCode, scanCode)) {
+ return;
+ }
+
+ mKeyDowns.push();
+ KeyDown& keyDown = mKeyDowns.editTop();
+ keyDown.keyCode = keyCode;
+ keyDown.scanCode = scanCode;
+ }
+
+ mDownTime = when;
+ } else {
+ // Remove key down.
+ ssize_t keyDownIndex = findKeyDown(scanCode);
+ if (keyDownIndex >= 0) {
+ // key up, be sure to use same keycode as before in case of rotation
+ keyCode = mKeyDowns.itemAt(keyDownIndex).keyCode;
+ mKeyDowns.removeAt(size_t(keyDownIndex));
+ } else {
+ // key was not actually down
+ ALOGI("Dropping key up from device %s because the key was not down. "
+ "keyCode=%d, scanCode=%d",
+ getDeviceName().string(), keyCode, scanCode);
+ return;
+ }
+ }
+
+ int32_t oldMetaState = mMetaState;
+ int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState);
+ bool metaStateChanged = oldMetaState != newMetaState;
+ if (metaStateChanged) {
+ mMetaState = newMetaState;
+ 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;
+ }
+
+ if (mParameters.handlesKeyRepeat) {
+ policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
+ }
+
+ 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, ALED_CAPS_LOCK);
+ initializeLedState(mNumLockLedState, ALED_NUM_LOCK);
+ initializeLedState(mScrollLockLedState, ALED_SCROLL_LOCK);
+
+ 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, ALED_CAPS_LOCK,
+ AMETA_CAPS_LOCK_ON, reset);
+ updateLedStateForModifier(mNumLockLedState, ALED_NUM_LOCK,
+ AMETA_NUM_LOCK_ON, reset);
+ updateLedStateForModifier(mScrollLockLedState, ALED_SCROLL_LOCK,
+ AMETA_SCROLL_LOCK_ON, reset);
+}
+
+void KeyboardInputMapper::updateLedStateForModifier(LedState& ledState,
+ int32_t led, int32_t modifier, bool reset) {
+ if (ledState.avail) {
+ bool desiredState = (mMetaState & modifier) != 0;
+ if (reset || ledState.on != desiredState) {
+ getEventHub()->setLedState(getDeviceId(), led, desiredState);
+ ledState.on = desiredState;
+ }
+ }
+}
+
+
+// --- CursorInputMapper ---
+
+CursorInputMapper::CursorInputMapper(InputDevice* device) :
+ InputMapper(device) {
+}
+
+CursorInputMapper::~CursorInputMapper() {
+}
+
+uint32_t CursorInputMapper::getSources() {
+ return mSource;
+}
+
+void CursorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ if (mParameters.mode == Parameters::MODE_POINTER) {
+ float minX, minY, maxX, maxY;
+ if (mPointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, minX, maxX, 0.0f, 0.0f, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, minY, maxY, 0.0f, 0.0f, 0.0f);
+ }
+ } else {
+ info->addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
+ info->addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, -1.0f, 1.0f, 0.0f, mYScale, 0.0f);
+ }
+ info->addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, mSource, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+
+ if (mCursorScrollAccumulator.haveRelativeVWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+ }
+ if (mCursorScrollAccumulator.haveRelativeHWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f);
+ }
+}
+
+void CursorInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Cursor Input Mapper:\n");
+ dumpParameters(dump);
+ dump.appendFormat(INDENT3 "XScale: %0.3f\n", mXScale);
+ dump.appendFormat(INDENT3 "YScale: %0.3f\n", mYScale);
+ dump.appendFormat(INDENT3 "XPrecision: %0.3f\n", mXPrecision);
+ dump.appendFormat(INDENT3 "YPrecision: %0.3f\n", mYPrecision);
+ dump.appendFormat(INDENT3 "HaveVWheel: %s\n",
+ toString(mCursorScrollAccumulator.haveRelativeVWheel()));
+ dump.appendFormat(INDENT3 "HaveHWheel: %s\n",
+ toString(mCursorScrollAccumulator.haveRelativeHWheel()));
+ dump.appendFormat(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
+ dump.appendFormat(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
+ dump.appendFormat(INDENT3 "Orientation: %d\n", mOrientation);
+ dump.appendFormat(INDENT3 "ButtonState: 0x%08x\n", mButtonState);
+ dump.appendFormat(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState)));
+ dump.appendFormat(INDENT3 "DownTime: %lld\n", (long long)mDownTime);
+}
+
+void CursorInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ mCursorScrollAccumulator.configure(getDevice());
+
+ // Configure basic parameters.
+ configureParameters();
+
+ // Configure device mode.
+ switch (mParameters.mode) {
+ case Parameters::MODE_POINTER:
+ mSource = AINPUT_SOURCE_MOUSE;
+ mXPrecision = 1.0f;
+ mYPrecision = 1.0f;
+ mXScale = 1.0f;
+ mYScale = 1.0f;
+ mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+ break;
+ case Parameters::MODE_NAVIGATION:
+ mSource = AINPUT_SOURCE_TRACKBALL;
+ mXPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+ mYPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
+ mXScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+ mYScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
+ break;
+ }
+
+ mVWheelScale = 1.0f;
+ mHWheelScale = 1.0f;
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_POINTER_SPEED)) {
+ mPointerVelocityControl.setParameters(config->pointerVelocityControlParameters);
+ mWheelXVelocityControl.setParameters(config->wheelVelocityControlParameters);
+ mWheelYVelocityControl.setParameters(config->wheelVelocityControlParameters);
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay) {
+ DisplayViewport v;
+ if (config->getDisplayInfo(false /*external*/, &v)) {
+ mOrientation = v.orientation;
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ } else {
+ mOrientation = DISPLAY_ORIENTATION_0;
+ }
+ bumpGeneration();
+ }
+}
+
+void CursorInputMapper::configureParameters() {
+ mParameters.mode = Parameters::MODE_POINTER;
+ String8 cursorModeString;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("cursor.mode"), cursorModeString)) {
+ if (cursorModeString == "navigation") {
+ mParameters.mode = Parameters::MODE_NAVIGATION;
+ } else if (cursorModeString != "pointer" && cursorModeString != "default") {
+ ALOGW("Invalid value for cursor.mode: '%s'", cursorModeString.string());
+ }
+ }
+
+ mParameters.orientationAware = false;
+ getDevice()->getConfiguration().tryGetProperty(String8("cursor.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.hasAssociatedDisplay = false;
+ if (mParameters.mode == Parameters::MODE_POINTER || mParameters.orientationAware) {
+ mParameters.hasAssociatedDisplay = true;
+ }
+}
+
+void CursorInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+ dump.appendFormat(INDENT4 "HasAssociatedDisplay: %s\n",
+ toString(mParameters.hasAssociatedDisplay));
+
+ switch (mParameters.mode) {
+ case Parameters::MODE_POINTER:
+ dump.append(INDENT4 "Mode: pointer\n");
+ break;
+ case Parameters::MODE_NAVIGATION:
+ dump.append(INDENT4 "Mode: navigation\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void CursorInputMapper::reset(nsecs_t when) {
+ mButtonState = 0;
+ mDownTime = 0;
+
+ mPointerVelocityControl.reset();
+ mWheelXVelocityControl.reset();
+ mWheelYVelocityControl.reset();
+
+ mCursorButtonAccumulator.reset(getDevice());
+ mCursorMotionAccumulator.reset(getDevice());
+ mCursorScrollAccumulator.reset(getDevice());
+
+ InputMapper::reset(when);
+}
+
+void CursorInputMapper::process(const RawEvent* rawEvent) {
+ mCursorButtonAccumulator.process(rawEvent);
+ mCursorMotionAccumulator.process(rawEvent);
+ mCursorScrollAccumulator.process(rawEvent);
+
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ sync(rawEvent->when);
+ }
+}
+
+void CursorInputMapper::sync(nsecs_t when) {
+ int32_t lastButtonState = mButtonState;
+ int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
+ mButtonState = currentButtonState;
+
+ bool wasDown = isPointerDown(lastButtonState);
+ bool down = isPointerDown(currentButtonState);
+ bool downChanged;
+ if (!wasDown && down) {
+ mDownTime = when;
+ downChanged = true;
+ } else if (wasDown && !down) {
+ downChanged = true;
+ } else {
+ downChanged = false;
+ }
+ nsecs_t downTime = mDownTime;
+ bool buttonsChanged = currentButtonState != lastButtonState;
+ bool buttonsPressed = currentButtonState & ~lastButtonState;
+
+ float deltaX = mCursorMotionAccumulator.getRelativeX() * mXScale;
+ float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale;
+ bool moved = deltaX != 0 || deltaY != 0;
+
+ // Rotate delta according to orientation if needed.
+ if (mParameters.orientationAware && mParameters.hasAssociatedDisplay
+ && (deltaX != 0.0f || deltaY != 0.0f)) {
+ rotateDelta(mOrientation, &deltaX, &deltaY);
+ }
+
+ // Move the pointer.
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_MOUSE;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+
+ float vscroll = mCursorScrollAccumulator.getRelativeVWheel();
+ float hscroll = mCursorScrollAccumulator.getRelativeHWheel();
+ bool scrolled = vscroll != 0 || hscroll != 0;
+
+ mWheelYVelocityControl.move(when, NULL, &vscroll);
+ mWheelXVelocityControl.move(when, &hscroll, NULL);
+
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ int32_t displayId;
+ if (mPointerController != NULL) {
+ if (moved || scrolled || buttonsChanged) {
+ mPointerController->setPresentation(
+ PointerControllerInterface::PRESENTATION_POINTER);
+
+ if (moved) {
+ mPointerController->move(deltaX, deltaY);
+ }
+
+ if (buttonsChanged) {
+ mPointerController->setButtonState(currentButtonState);
+ }
+
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ }
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ displayId = ADISPLAY_ID_DEFAULT;
+ } else {
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, deltaX);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, deltaY);
+ displayId = ADISPLAY_ID_NONE;
+ }
+
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
+
+ // Moving an external trackball or mouse should wake the device.
+ // We don't do this for internal cursor devices to prevent them from waking up
+ // the device in your pocket.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ uint32_t policyFlags = 0;
+ if ((buttonsPressed || moved || scrolled) && getDevice()->isExternal()) {
+ policyFlags |= POLICY_FLAG_WAKE;
+ }
+
+ // Synthesize key down from buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
+ policyFlags, lastButtonState, currentButtonState);
+
+ // Send motion event.
+ if (downChanged || moved || scrolled || buttonsChanged) {
+ int32_t metaState = mContext->getGlobalMetaState();
+ int32_t motionEventAction;
+ if (downChanged) {
+ motionEventAction = down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
+ } else if (down || mPointerController == NULL) {
+ motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+ } else {
+ motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE;
+ }
+
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ motionEventAction, 0, metaState, currentButtonState, 0,
+ displayId, 1, &pointerProperties, &pointerCoords,
+ mXPrecision, mYPrecision, downTime);
+ getListener()->notifyMotion(&args);
+
+ // Send hover move after UP to tell the application that the mouse is hovering now.
+ if (motionEventAction == AMOTION_EVENT_ACTION_UP
+ && mPointerController != NULL) {
+ NotifyMotionArgs hoverArgs(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, currentButtonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ displayId, 1, &pointerProperties, &pointerCoords,
+ mXPrecision, mYPrecision, downTime);
+ getListener()->notifyMotion(&hoverArgs);
+ }
+
+ // Send scroll events.
+ if (scrolled) {
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
+
+ NotifyMotionArgs scrollArgs(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_SCROLL, 0, metaState, currentButtonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ displayId, 1, &pointerProperties, &pointerCoords,
+ mXPrecision, mYPrecision, downTime);
+ getListener()->notifyMotion(&scrollArgs);
+ }
+ }
+
+ // Synthesize key up from buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+ policyFlags, lastButtonState, currentButtonState);
+
+ mCursorMotionAccumulator.finishSync();
+ mCursorScrollAccumulator.finishSync();
+}
+
+int32_t CursorInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ if (scanCode >= BTN_MOUSE && scanCode < BTN_JOYSTICK) {
+ return getEventHub()->getScanCodeState(getDeviceId(), scanCode);
+ } else {
+ return AKEY_STATE_UNKNOWN;
+ }
+}
+
+void CursorInputMapper::fadePointer() {
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+}
+
+
+// --- TouchInputMapper ---
+
+TouchInputMapper::TouchInputMapper(InputDevice* device) :
+ InputMapper(device),
+ mSource(0), mDeviceMode(DEVICE_MODE_DISABLED),
+ mSurfaceWidth(-1), mSurfaceHeight(-1), mSurfaceLeft(0), mSurfaceTop(0),
+ mSurfaceOrientation(DISPLAY_ORIENTATION_0) {
+}
+
+TouchInputMapper::~TouchInputMapper() {
+}
+
+uint32_t TouchInputMapper::getSources() {
+ return mSource;
+}
+
+void TouchInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ if (mDeviceMode != DEVICE_MODE_DISABLED) {
+ info->addMotionRange(mOrientedRanges.x);
+ info->addMotionRange(mOrientedRanges.y);
+ info->addMotionRange(mOrientedRanges.pressure);
+
+ if (mOrientedRanges.haveSize) {
+ info->addMotionRange(mOrientedRanges.size);
+ }
+
+ if (mOrientedRanges.haveTouchSize) {
+ info->addMotionRange(mOrientedRanges.touchMajor);
+ info->addMotionRange(mOrientedRanges.touchMinor);
+ }
+
+ if (mOrientedRanges.haveToolSize) {
+ info->addMotionRange(mOrientedRanges.toolMajor);
+ info->addMotionRange(mOrientedRanges.toolMinor);
+ }
+
+ if (mOrientedRanges.haveOrientation) {
+ info->addMotionRange(mOrientedRanges.orientation);
+ }
+
+ if (mOrientedRanges.haveDistance) {
+ info->addMotionRange(mOrientedRanges.distance);
+ }
+
+ if (mOrientedRanges.haveTilt) {
+ info->addMotionRange(mOrientedRanges.tilt);
+ }
+
+ if (mCursorScrollAccumulator.haveRelativeVWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_VSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f);
+ }
+ if (mCursorScrollAccumulator.haveRelativeHWheel()) {
+ info->addMotionRange(AMOTION_EVENT_AXIS_HSCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
+ 0.0f);
+ }
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+ const InputDeviceInfo::MotionRange& x = mOrientedRanges.x;
+ const InputDeviceInfo::MotionRange& y = mOrientedRanges.y;
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_1, mSource, x.min, x.max, x.flat,
+ x.fuzz, x.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_2, mSource, y.min, y.max, y.flat,
+ y.fuzz, y.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_3, mSource, x.min, x.max, x.flat,
+ x.fuzz, x.resolution);
+ info->addMotionRange(AMOTION_EVENT_AXIS_GENERIC_4, mSource, y.min, y.max, y.flat,
+ y.fuzz, y.resolution);
+ }
+ info->setButtonUnderPad(mParameters.hasButtonUnderPad);
+ }
+}
+
+void TouchInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Touch Input Mapper:\n");
+ dumpParameters(dump);
+ dumpVirtualKeys(dump);
+ dumpRawPointerAxes(dump);
+ dumpCalibration(dump);
+ dumpAffineTransformation(dump);
+ dumpSurface(dump);
+
+ dump.appendFormat(INDENT3 "Translation and Scaling Factors:\n");
+ dump.appendFormat(INDENT4 "XTranslate: %0.3f\n", mXTranslate);
+ dump.appendFormat(INDENT4 "YTranslate: %0.3f\n", mYTranslate);
+ dump.appendFormat(INDENT4 "XScale: %0.3f\n", mXScale);
+ dump.appendFormat(INDENT4 "YScale: %0.3f\n", mYScale);
+ dump.appendFormat(INDENT4 "XPrecision: %0.3f\n", mXPrecision);
+ dump.appendFormat(INDENT4 "YPrecision: %0.3f\n", mYPrecision);
+ dump.appendFormat(INDENT4 "GeometricScale: %0.3f\n", mGeometricScale);
+ dump.appendFormat(INDENT4 "PressureScale: %0.3f\n", mPressureScale);
+ dump.appendFormat(INDENT4 "SizeScale: %0.3f\n", mSizeScale);
+ dump.appendFormat(INDENT4 "OrientationScale: %0.3f\n", mOrientationScale);
+ dump.appendFormat(INDENT4 "DistanceScale: %0.3f\n", mDistanceScale);
+ dump.appendFormat(INDENT4 "HaveTilt: %s\n", toString(mHaveTilt));
+ dump.appendFormat(INDENT4 "TiltXCenter: %0.3f\n", mTiltXCenter);
+ dump.appendFormat(INDENT4 "TiltXScale: %0.3f\n", mTiltXScale);
+ dump.appendFormat(INDENT4 "TiltYCenter: %0.3f\n", mTiltYCenter);
+ dump.appendFormat(INDENT4 "TiltYScale: %0.3f\n", mTiltYScale);
+
+ dump.appendFormat(INDENT3 "Last Button State: 0x%08x\n", mLastButtonState);
+
+ dump.appendFormat(INDENT3 "Last Raw Touch: pointerCount=%d\n",
+ mLastRawPointerData.pointerCount);
+ for (uint32_t i = 0; i < mLastRawPointerData.pointerCount; i++) {
+ const RawPointerData::Pointer& pointer = mLastRawPointerData.pointers[i];
+ dump.appendFormat(INDENT4 "[%d]: id=%d, x=%d, y=%d, pressure=%d, "
+ "touchMajor=%d, touchMinor=%d, toolMajor=%d, toolMinor=%d, "
+ "orientation=%d, tiltX=%d, tiltY=%d, distance=%d, "
+ "toolType=%d, isHovering=%s\n", i,
+ pointer.id, pointer.x, pointer.y, pointer.pressure,
+ pointer.touchMajor, pointer.touchMinor,
+ pointer.toolMajor, pointer.toolMinor,
+ pointer.orientation, pointer.tiltX, pointer.tiltY, pointer.distance,
+ pointer.toolType, toString(pointer.isHovering));
+ }
+
+ dump.appendFormat(INDENT3 "Last Cooked Touch: pointerCount=%d\n",
+ mLastCookedPointerData.pointerCount);
+ for (uint32_t i = 0; i < mLastCookedPointerData.pointerCount; i++) {
+ const PointerProperties& pointerProperties = mLastCookedPointerData.pointerProperties[i];
+ const PointerCoords& pointerCoords = mLastCookedPointerData.pointerCoords[i];
+ dump.appendFormat(INDENT4 "[%d]: id=%d, x=%0.3f, y=%0.3f, pressure=%0.3f, "
+ "touchMajor=%0.3f, touchMinor=%0.3f, toolMajor=%0.3f, toolMinor=%0.3f, "
+ "orientation=%0.3f, tilt=%0.3f, distance=%0.3f, "
+ "toolType=%d, isHovering=%s\n", i,
+ pointerProperties.id,
+ pointerCoords.getX(),
+ pointerCoords.getY(),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_TILT),
+ pointerCoords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE),
+ pointerProperties.toolType,
+ toString(mLastCookedPointerData.isHovering(i)));
+ }
+
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ dump.appendFormat(INDENT3 "Pointer Gesture Detector:\n");
+ dump.appendFormat(INDENT4 "XMovementScale: %0.3f\n",
+ mPointerXMovementScale);
+ dump.appendFormat(INDENT4 "YMovementScale: %0.3f\n",
+ mPointerYMovementScale);
+ dump.appendFormat(INDENT4 "XZoomScale: %0.3f\n",
+ mPointerXZoomScale);
+ dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n",
+ mPointerYZoomScale);
+ dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n",
+ mPointerGestureMaxSwipeWidth);
+ }
+}
+
+void TouchInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ mConfig = *config;
+
+ if (!changes) { // first time only
+ // Configure basic parameters.
+ configureParameters();
+
+ // Configure common accumulators.
+ mCursorScrollAccumulator.configure(getDevice());
+ mTouchButtonAccumulator.configure(getDevice());
+
+ // Configure absolute axis information.
+ configureRawPointerAxes();
+
+ // Prepare input device calibration.
+ parseCalibration();
+ resolveCalibration();
+ }
+
+ if (!changes || (changes & InputReaderConfiguration::TOUCH_AFFINE_TRANSFORMATION)) {
+ // Update location calibration to reflect current settings
+ updateAffineTransformation();
+ }
+
+ 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;
+ }
+
+ mParameters.hasButtonUnderPad=
+ getEventHub()->hasInputProperty(getDeviceId(), INPUT_PROP_BUTTONPAD);
+
+ String8 deviceTypeString;
+ if (getDevice()->getConfiguration().tryGetProperty(String8("touch.deviceType"),
+ deviceTypeString)) {
+ if (deviceTypeString == "touchScreen") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ } else if (deviceTypeString == "touchPad") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_PAD;
+ } else if (deviceTypeString == "touchNavigation") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_TOUCH_NAVIGATION;
+ } else if (deviceTypeString == "pointer") {
+ mParameters.deviceType = Parameters::DEVICE_TYPE_POINTER;
+ } else if (deviceTypeString != "default") {
+ ALOGW("Invalid value for touch.deviceType: '%s'", deviceTypeString.string());
+ }
+ }
+
+ mParameters.orientationAware = mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN;
+ getDevice()->getConfiguration().tryGetProperty(String8("touch.orientationAware"),
+ mParameters.orientationAware);
+
+ mParameters.hasAssociatedDisplay = false;
+ mParameters.associatedDisplayIsExternal = false;
+ if (mParameters.orientationAware
+ || mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
+ || mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
+ mParameters.hasAssociatedDisplay = true;
+ mParameters.associatedDisplayIsExternal =
+ mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
+ && getDevice()->isExternal();
+ }
+
+ // Initial downs on external touch devices should wake the device.
+ // Normally we don't do this for internal touch screens to prevent them from waking
+ // up in your pocket but you can enable it using the input device configuration.
+ mParameters.wake = getDevice()->isExternal();
+ getDevice()->getConfiguration().tryGetProperty(String8("touch.wake"),
+ mParameters.wake);
+}
+
+void TouchInputMapper::dumpParameters(String8& dump) {
+ dump.append(INDENT3 "Parameters:\n");
+
+ switch (mParameters.gestureMode) {
+ case Parameters::GESTURE_MODE_POINTER:
+ dump.append(INDENT4 "GestureMode: pointer\n");
+ break;
+ case Parameters::GESTURE_MODE_SPOTS:
+ dump.append(INDENT4 "GestureMode: spots\n");
+ break;
+ default:
+ assert(false);
+ }
+
+ switch (mParameters.deviceType) {
+ case Parameters::DEVICE_TYPE_TOUCH_SCREEN:
+ dump.append(INDENT4 "DeviceType: touchScreen\n");
+ break;
+ case Parameters::DEVICE_TYPE_TOUCH_PAD:
+ dump.append(INDENT4 "DeviceType: touchPad\n");
+ break;
+ case Parameters::DEVICE_TYPE_TOUCH_NAVIGATION:
+ dump.append(INDENT4 "DeviceType: touchNavigation\n");
+ break;
+ case Parameters::DEVICE_TYPE_POINTER:
+ dump.append(INDENT4 "DeviceType: pointer\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ dump.appendFormat(INDENT4 "AssociatedDisplay: hasAssociatedDisplay=%s, isExternal=%s\n",
+ toString(mParameters.hasAssociatedDisplay),
+ toString(mParameters.associatedDisplayIsExternal));
+ dump.appendFormat(INDENT4 "OrientationAware: %s\n",
+ toString(mParameters.orientationAware));
+}
+
+void TouchInputMapper::configureRawPointerAxes() {
+ mRawPointerAxes.clear();
+}
+
+void TouchInputMapper::dumpRawPointerAxes(String8& dump) {
+ dump.append(INDENT3 "Raw Touch Axes:\n");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.x, "X");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.y, "Y");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.pressure, "Pressure");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMajor, "TouchMajor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.touchMinor, "TouchMinor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMajor, "ToolMajor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.toolMinor, "ToolMinor");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.orientation, "Orientation");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.distance, "Distance");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltX, "TiltX");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.tiltY, "TiltY");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.trackingId, "TrackingId");
+ dumpRawAbsoluteAxisInfo(dump, mRawPointerAxes.slot, "Slot");
+}
+
+void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) {
+ int32_t oldDeviceMode = mDeviceMode;
+
+ // Determine device mode.
+ if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER
+ && mConfig.pointerGesturesEnabled) {
+ mSource = AINPUT_SOURCE_MOUSE;
+ mDeviceMode = DEVICE_MODE_POINTER;
+ if (hasStylus()) {
+ mSource |= AINPUT_SOURCE_STYLUS;
+ }
+ } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_SCREEN
+ && mParameters.hasAssociatedDisplay) {
+ mSource = AINPUT_SOURCE_TOUCHSCREEN;
+ mDeviceMode = DEVICE_MODE_DIRECT;
+ if (hasStylus()) {
+ mSource |= AINPUT_SOURCE_STYLUS;
+ }
+ } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_NAVIGATION) {
+ mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
+ mDeviceMode = DEVICE_MODE_NAVIGATION;
+ } else {
+ mSource = AINPUT_SOURCE_TOUCHPAD;
+ mDeviceMode = DEVICE_MODE_UNSCALED;
+ }
+
+ // Ensure we have valid X and Y axes.
+ if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
+ ALOGW(INDENT "Touch device '%s' did not report support for X or Y axis! "
+ "The device will be inoperable.", getDeviceName().string());
+ mDeviceMode = DEVICE_MODE_DISABLED;
+ return;
+ }
+
+ // Raw width and height in the natural orientation.
+ int32_t rawWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1;
+ int32_t rawHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1;
+
+ // Get associated display dimensions.
+ DisplayViewport newViewport;
+ if (mParameters.hasAssociatedDisplay) {
+ if (!mConfig.getDisplayInfo(mParameters.associatedDisplayIsExternal, &newViewport)) {
+ ALOGI(INDENT "Touch device '%s' could not query the properties of its associated "
+ "display. The device will be inoperable until the display size "
+ "becomes available.",
+ getDeviceName().string());
+ mDeviceMode = DEVICE_MODE_DISABLED;
+ return;
+ }
+ } else {
+ newViewport.setNonDisplayViewport(rawWidth, rawHeight);
+ }
+ bool viewportChanged = mViewport != newViewport;
+ if (viewportChanged) {
+ mViewport = newViewport;
+
+ if (mDeviceMode == DEVICE_MODE_DIRECT || mDeviceMode == DEVICE_MODE_POINTER) {
+ // Convert rotated viewport to natural surface coordinates.
+ int32_t naturalLogicalWidth, naturalLogicalHeight;
+ int32_t naturalPhysicalWidth, naturalPhysicalHeight;
+ int32_t naturalPhysicalLeft, naturalPhysicalTop;
+ int32_t naturalDeviceWidth, naturalDeviceHeight;
+ switch (mViewport.orientation) {
+ case DISPLAY_ORIENTATION_90:
+ naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalLeft = mViewport.deviceHeight - mViewport.physicalBottom;
+ naturalPhysicalTop = mViewport.physicalLeft;
+ naturalDeviceWidth = mViewport.deviceHeight;
+ naturalDeviceHeight = mViewport.deviceWidth;
+ break;
+ case DISPLAY_ORIENTATION_180:
+ naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalLeft = mViewport.deviceWidth - mViewport.physicalRight;
+ naturalPhysicalTop = mViewport.deviceHeight - mViewport.physicalBottom;
+ naturalDeviceWidth = mViewport.deviceWidth;
+ naturalDeviceHeight = mViewport.deviceHeight;
+ break;
+ case DISPLAY_ORIENTATION_270:
+ naturalLogicalWidth = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalLogicalHeight = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalPhysicalWidth = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalHeight = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalLeft = mViewport.physicalTop;
+ naturalPhysicalTop = mViewport.deviceWidth - mViewport.physicalRight;
+ naturalDeviceWidth = mViewport.deviceHeight;
+ naturalDeviceHeight = mViewport.deviceWidth;
+ break;
+ case DISPLAY_ORIENTATION_0:
+ default:
+ naturalLogicalWidth = mViewport.logicalRight - mViewport.logicalLeft;
+ naturalLogicalHeight = mViewport.logicalBottom - mViewport.logicalTop;
+ naturalPhysicalWidth = mViewport.physicalRight - mViewport.physicalLeft;
+ naturalPhysicalHeight = mViewport.physicalBottom - mViewport.physicalTop;
+ naturalPhysicalLeft = mViewport.physicalLeft;
+ naturalPhysicalTop = mViewport.physicalTop;
+ naturalDeviceWidth = mViewport.deviceWidth;
+ naturalDeviceHeight = mViewport.deviceHeight;
+ break;
+ }
+
+ mSurfaceWidth = naturalLogicalWidth * naturalDeviceWidth / naturalPhysicalWidth;
+ mSurfaceHeight = naturalLogicalHeight * naturalDeviceHeight / naturalPhysicalHeight;
+ mSurfaceLeft = naturalPhysicalLeft * naturalLogicalWidth / naturalPhysicalWidth;
+ mSurfaceTop = naturalPhysicalTop * naturalLogicalHeight / naturalPhysicalHeight;
+
+ mSurfaceOrientation = mParameters.orientationAware ?
+ mViewport.orientation : DISPLAY_ORIENTATION_0;
+ } else {
+ mSurfaceWidth = rawWidth;
+ mSurfaceHeight = rawHeight;
+ mSurfaceLeft = 0;
+ mSurfaceTop = 0;
+ mSurfaceOrientation = DISPLAY_ORIENTATION_0;
+ }
+ }
+
+ // If moving between pointer modes, need to reset some state.
+ bool deviceModeChanged = mDeviceMode != oldDeviceMode;
+ if (deviceModeChanged) {
+ mOrientedRanges.clear();
+ }
+
+ // Create pointer controller if needed.
+ if (mDeviceMode == DEVICE_MODE_POINTER ||
+ (mDeviceMode == DEVICE_MODE_DIRECT && mConfig.showTouches)) {
+ if (mPointerController == NULL) {
+ mPointerController = getPolicy()->obtainPointerController(getDeviceId());
+ }
+ } else {
+ mPointerController.clear();
+ }
+
+ if (viewportChanged || deviceModeChanged) {
+ ALOGI("Device reconfigured: id=%d, name='%s', size %dx%d, orientation %d, mode %d, "
+ "display id %d",
+ getDeviceId(), getDeviceName().string(), mSurfaceWidth, mSurfaceHeight,
+ mSurfaceOrientation, mDeviceMode, mViewport.displayId);
+
+ // Configure X and Y factors.
+ mXScale = float(mSurfaceWidth) / rawWidth;
+ mYScale = float(mSurfaceHeight) / rawHeight;
+ mXTranslate = -mSurfaceLeft;
+ mYTranslate = -mSurfaceTop;
+ mXPrecision = 1.0f / mXScale;
+ mYPrecision = 1.0f / mYScale;
+
+ mOrientedRanges.x.axis = AMOTION_EVENT_AXIS_X;
+ mOrientedRanges.x.source = mSource;
+ mOrientedRanges.y.axis = AMOTION_EVENT_AXIS_Y;
+ mOrientedRanges.y.source = mSource;
+
+ configureVirtualKeys();
+
+ // Scale factor for terms that are not oriented in a particular axis.
+ // If the pixels are square then xScale == yScale otherwise we fake it
+ // by choosing an average.
+ mGeometricScale = avg(mXScale, mYScale);
+
+ // Size of diagonal axis.
+ float diagonalSize = hypotf(mSurfaceWidth, mSurfaceHeight);
+
+ // Size factors.
+ if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) {
+ if (mRawPointerAxes.touchMajor.valid
+ && mRawPointerAxes.touchMajor.maxValue != 0) {
+ mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue;
+ } else if (mRawPointerAxes.toolMajor.valid
+ && mRawPointerAxes.toolMajor.maxValue != 0) {
+ mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue;
+ } else {
+ mSizeScale = 0.0f;
+ }
+
+ mOrientedRanges.haveTouchSize = true;
+ mOrientedRanges.haveToolSize = true;
+ mOrientedRanges.haveSize = true;
+
+ mOrientedRanges.touchMajor.axis = AMOTION_EVENT_AXIS_TOUCH_MAJOR;
+ mOrientedRanges.touchMajor.source = mSource;
+ mOrientedRanges.touchMajor.min = 0;
+ mOrientedRanges.touchMajor.max = diagonalSize;
+ mOrientedRanges.touchMajor.flat = 0;
+ mOrientedRanges.touchMajor.fuzz = 0;
+ mOrientedRanges.touchMajor.resolution = 0;
+
+ mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
+ mOrientedRanges.touchMinor.axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
+
+ mOrientedRanges.toolMajor.axis = AMOTION_EVENT_AXIS_TOOL_MAJOR;
+ mOrientedRanges.toolMajor.source = mSource;
+ mOrientedRanges.toolMajor.min = 0;
+ mOrientedRanges.toolMajor.max = diagonalSize;
+ mOrientedRanges.toolMajor.flat = 0;
+ mOrientedRanges.toolMajor.fuzz = 0;
+ mOrientedRanges.toolMajor.resolution = 0;
+
+ mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
+ mOrientedRanges.toolMinor.axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
+
+ mOrientedRanges.size.axis = AMOTION_EVENT_AXIS_SIZE;
+ mOrientedRanges.size.source = mSource;
+ mOrientedRanges.size.min = 0;
+ mOrientedRanges.size.max = 1.0;
+ mOrientedRanges.size.flat = 0;
+ mOrientedRanges.size.fuzz = 0;
+ mOrientedRanges.size.resolution = 0;
+ } else {
+ mSizeScale = 0.0f;
+ }
+
+ // Pressure factors.
+ mPressureScale = 0;
+ if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL
+ || mCalibration.pressureCalibration
+ == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) {
+ if (mCalibration.havePressureScale) {
+ mPressureScale = mCalibration.pressureScale;
+ } else if (mRawPointerAxes.pressure.valid
+ && mRawPointerAxes.pressure.maxValue != 0) {
+ mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue;
+ }
+ }
+
+ mOrientedRanges.pressure.axis = AMOTION_EVENT_AXIS_PRESSURE;
+ mOrientedRanges.pressure.source = mSource;
+ mOrientedRanges.pressure.min = 0;
+ mOrientedRanges.pressure.max = 1.0;
+ mOrientedRanges.pressure.flat = 0;
+ mOrientedRanges.pressure.fuzz = 0;
+ mOrientedRanges.pressure.resolution = 0;
+
+ // Tilt
+ mTiltXCenter = 0;
+ mTiltXScale = 0;
+ mTiltYCenter = 0;
+ mTiltYScale = 0;
+ mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid;
+ if (mHaveTilt) {
+ mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue,
+ mRawPointerAxes.tiltX.maxValue);
+ mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue,
+ mRawPointerAxes.tiltY.maxValue);
+ mTiltXScale = M_PI / 180;
+ mTiltYScale = M_PI / 180;
+
+ mOrientedRanges.haveTilt = true;
+
+ mOrientedRanges.tilt.axis = AMOTION_EVENT_AXIS_TILT;
+ mOrientedRanges.tilt.source = mSource;
+ mOrientedRanges.tilt.min = 0;
+ mOrientedRanges.tilt.max = M_PI_2;
+ mOrientedRanges.tilt.flat = 0;
+ mOrientedRanges.tilt.fuzz = 0;
+ mOrientedRanges.tilt.resolution = 0;
+ }
+
+ // Orientation
+ mOrientationScale = 0;
+ if (mHaveTilt) {
+ mOrientedRanges.haveOrientation = true;
+
+ mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+ mOrientedRanges.orientation.source = mSource;
+ mOrientedRanges.orientation.min = -M_PI;
+ mOrientedRanges.orientation.max = M_PI;
+ mOrientedRanges.orientation.flat = 0;
+ mOrientedRanges.orientation.fuzz = 0;
+ mOrientedRanges.orientation.resolution = 0;
+ } else if (mCalibration.orientationCalibration !=
+ Calibration::ORIENTATION_CALIBRATION_NONE) {
+ if (mCalibration.orientationCalibration
+ == Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) {
+ if (mRawPointerAxes.orientation.valid) {
+ if (mRawPointerAxes.orientation.maxValue > 0) {
+ mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue;
+ } else if (mRawPointerAxes.orientation.minValue < 0) {
+ mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue;
+ } else {
+ mOrientationScale = 0;
+ }
+ }
+ }
+
+ mOrientedRanges.haveOrientation = true;
+
+ mOrientedRanges.orientation.axis = AMOTION_EVENT_AXIS_ORIENTATION;
+ mOrientedRanges.orientation.source = mSource;
+ mOrientedRanges.orientation.min = -M_PI_2;
+ mOrientedRanges.orientation.max = M_PI_2;
+ mOrientedRanges.orientation.flat = 0;
+ mOrientedRanges.orientation.fuzz = 0;
+ mOrientedRanges.orientation.resolution = 0;
+ }
+
+ // Distance
+ mDistanceScale = 0;
+ if (mCalibration.distanceCalibration != Calibration::DISTANCE_CALIBRATION_NONE) {
+ if (mCalibration.distanceCalibration
+ == Calibration::DISTANCE_CALIBRATION_SCALED) {
+ if (mCalibration.haveDistanceScale) {
+ mDistanceScale = mCalibration.distanceScale;
+ } else {
+ mDistanceScale = 1.0f;
+ }
+ }
+
+ mOrientedRanges.haveDistance = true;
+
+ mOrientedRanges.distance.axis = AMOTION_EVENT_AXIS_DISTANCE;
+ mOrientedRanges.distance.source = mSource;
+ mOrientedRanges.distance.min =
+ mRawPointerAxes.distance.minValue * mDistanceScale;
+ mOrientedRanges.distance.max =
+ mRawPointerAxes.distance.maxValue * mDistanceScale;
+ mOrientedRanges.distance.flat = 0;
+ mOrientedRanges.distance.fuzz =
+ mRawPointerAxes.distance.fuzz * mDistanceScale;
+ mOrientedRanges.distance.resolution = 0;
+ }
+
+ // Compute oriented precision, scales and ranges.
+ // Note that the maximum value reported is an inclusive maximum value so it is one
+ // unit less than the total width or height of surface.
+ switch (mSurfaceOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ case DISPLAY_ORIENTATION_270:
+ mOrientedXPrecision = mYPrecision;
+ mOrientedYPrecision = mXPrecision;
+
+ mOrientedRanges.x.min = mYTranslate;
+ mOrientedRanges.x.max = mSurfaceHeight + mYTranslate - 1;
+ mOrientedRanges.x.flat = 0;
+ mOrientedRanges.x.fuzz = 0;
+ mOrientedRanges.x.resolution = mRawPointerAxes.y.resolution * mYScale;
+
+ mOrientedRanges.y.min = mXTranslate;
+ mOrientedRanges.y.max = mSurfaceWidth + mXTranslate - 1;
+ mOrientedRanges.y.flat = 0;
+ mOrientedRanges.y.fuzz = 0;
+ mOrientedRanges.y.resolution = mRawPointerAxes.x.resolution * mXScale;
+ break;
+
+ default:
+ mOrientedXPrecision = mXPrecision;
+ mOrientedYPrecision = mYPrecision;
+
+ mOrientedRanges.x.min = mXTranslate;
+ mOrientedRanges.x.max = mSurfaceWidth + mXTranslate - 1;
+ mOrientedRanges.x.flat = 0;
+ mOrientedRanges.x.fuzz = 0;
+ mOrientedRanges.x.resolution = mRawPointerAxes.x.resolution * mXScale;
+
+ mOrientedRanges.y.min = mYTranslate;
+ mOrientedRanges.y.max = mSurfaceHeight + mYTranslate - 1;
+ mOrientedRanges.y.flat = 0;
+ mOrientedRanges.y.fuzz = 0;
+ mOrientedRanges.y.resolution = mRawPointerAxes.y.resolution * mYScale;
+ break;
+ }
+
+ // Location
+ updateAffineTransformation();
+
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ // Compute pointer gesture detection parameters.
+ float rawDiagonal = hypotf(rawWidth, rawHeight);
+ float displayDiagonal = hypotf(mSurfaceWidth, mSurfaceHeight);
+
+ // Scale movements such that one whole swipe of the touch pad covers a
+ // given area relative to the diagonal size of the display when no acceleration
+ // is applied.
+ // Assume that the touch pad has a square aspect ratio such that movements in
+ // X and Y of the same number of raw units cover the same physical distance.
+ mPointerXMovementScale = mConfig.pointerGestureMovementSpeedRatio
+ * displayDiagonal / rawDiagonal;
+ mPointerYMovementScale = mPointerXMovementScale;
+
+ // Scale zooms to cover a smaller range of the display than movements do.
+ // This value determines the area around the pointer that is affected by freeform
+ // pointer gestures.
+ mPointerXZoomScale = mConfig.pointerGestureZoomSpeedRatio
+ * displayDiagonal / rawDiagonal;
+ mPointerYZoomScale = mPointerXZoomScale;
+
+ // Max width between pointers to detect a swipe gesture is more than some fraction
+ // of the diagonal axis of the touch pad. Touches that are wider than this are
+ // translated into freeform gestures.
+ mPointerGestureMaxSwipeWidth =
+ mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal;
+
+ // Abort current pointer usages because the state has changed.
+ abortPointerUsage(when, 0 /*policyFlags*/);
+ }
+
+ // Inform the dispatcher about the changes.
+ *outResetNeeded = true;
+ bumpGeneration();
+ }
+}
+
+void TouchInputMapper::dumpSurface(String8& dump) {
+ dump.appendFormat(INDENT3 "Viewport: displayId=%d, orientation=%d, "
+ "logicalFrame=[%d, %d, %d, %d], "
+ "physicalFrame=[%d, %d, %d, %d], "
+ "deviceSize=[%d, %d]\n",
+ mViewport.displayId, mViewport.orientation,
+ mViewport.logicalLeft, mViewport.logicalTop,
+ mViewport.logicalRight, mViewport.logicalBottom,
+ mViewport.physicalLeft, mViewport.physicalTop,
+ mViewport.physicalRight, mViewport.physicalBottom,
+ mViewport.deviceWidth, mViewport.deviceHeight);
+
+ dump.appendFormat(INDENT3 "SurfaceWidth: %dpx\n", mSurfaceWidth);
+ dump.appendFormat(INDENT3 "SurfaceHeight: %dpx\n", mSurfaceHeight);
+ dump.appendFormat(INDENT3 "SurfaceLeft: %d\n", mSurfaceLeft);
+ dump.appendFormat(INDENT3 "SurfaceTop: %d\n", mSurfaceTop);
+ dump.appendFormat(INDENT3 "SurfaceOrientation: %d\n", mSurfaceOrientation);
+}
+
+void TouchInputMapper::configureVirtualKeys() {
+ Vector<VirtualKeyDefinition> virtualKeyDefinitions;
+ getEventHub()->getVirtualKeyDefinitions(getDeviceId(), virtualKeyDefinitions);
+
+ mVirtualKeys.clear();
+
+ if (virtualKeyDefinitions.size() == 0) {
+ return;
+ }
+
+ mVirtualKeys.setCapacity(virtualKeyDefinitions.size());
+
+ int32_t touchScreenLeft = mRawPointerAxes.x.minValue;
+ int32_t touchScreenTop = mRawPointerAxes.y.minValue;
+ int32_t touchScreenWidth = mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue + 1;
+ int32_t touchScreenHeight = mRawPointerAxes.y.maxValue - mRawPointerAxes.y.minValue + 1;
+
+ for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
+ const VirtualKeyDefinition& virtualKeyDefinition =
+ virtualKeyDefinitions[i];
+
+ mVirtualKeys.add();
+ VirtualKey& virtualKey = mVirtualKeys.editTop();
+
+ virtualKey.scanCode = virtualKeyDefinition.scanCode;
+ int32_t keyCode;
+ uint32_t flags;
+ if (getEventHub()->mapKey(getDeviceId(), virtualKey.scanCode, 0, &keyCode, &flags)) {
+ ALOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring",
+ virtualKey.scanCode);
+ mVirtualKeys.pop(); // drop the key
+ continue;
+ }
+
+ virtualKey.keyCode = keyCode;
+ virtualKey.flags = flags;
+
+ // convert the key definition's display coordinates into touch coordinates for a hit box
+ int32_t halfWidth = virtualKeyDefinition.width / 2;
+ int32_t halfHeight = virtualKeyDefinition.height / 2;
+
+ virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth)
+ * touchScreenWidth / mSurfaceWidth + touchScreenLeft;
+ virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth)
+ * touchScreenWidth / mSurfaceWidth + touchScreenLeft;
+ virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight)
+ * touchScreenHeight / mSurfaceHeight + touchScreenTop;
+ virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
+ * touchScreenHeight / mSurfaceHeight + touchScreenTop;
+ }
+}
+
+void TouchInputMapper::dumpVirtualKeys(String8& dump) {
+ if (!mVirtualKeys.isEmpty()) {
+ dump.append(INDENT3 "Virtual Keys:\n");
+
+ for (size_t i = 0; i < mVirtualKeys.size(); i++) {
+ const VirtualKey& virtualKey = mVirtualKeys.itemAt(i);
+ dump.appendFormat(INDENT4 "%zu: scanCode=%d, keyCode=%d, "
+ "hitLeft=%d, hitRight=%d, hitTop=%d, hitBottom=%d\n",
+ i, virtualKey.scanCode, virtualKey.keyCode,
+ virtualKey.hitLeft, virtualKey.hitRight,
+ virtualKey.hitTop, virtualKey.hitBottom);
+ }
+ }
+}
+
+void TouchInputMapper::parseCalibration() {
+ const PropertyMap& in = getDevice()->getConfiguration();
+ Calibration& out = mCalibration;
+
+ // Size
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_DEFAULT;
+ String8 sizeCalibrationString;
+ if (in.tryGetProperty(String8("touch.size.calibration"), sizeCalibrationString)) {
+ if (sizeCalibrationString == "none") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+ } else if (sizeCalibrationString == "geometric") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
+ } else if (sizeCalibrationString == "diameter") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_DIAMETER;
+ } else if (sizeCalibrationString == "box") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_BOX;
+ } else if (sizeCalibrationString == "area") {
+ out.sizeCalibration = Calibration::SIZE_CALIBRATION_AREA;
+ } else if (sizeCalibrationString != "default") {
+ ALOGW("Invalid value for touch.size.calibration: '%s'",
+ sizeCalibrationString.string());
+ }
+ }
+
+ out.haveSizeScale = in.tryGetProperty(String8("touch.size.scale"),
+ out.sizeScale);
+ out.haveSizeBias = in.tryGetProperty(String8("touch.size.bias"),
+ out.sizeBias);
+ out.haveSizeIsSummed = in.tryGetProperty(String8("touch.size.isSummed"),
+ out.sizeIsSummed);
+
+ // Pressure
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_DEFAULT;
+ String8 pressureCalibrationString;
+ if (in.tryGetProperty(String8("touch.pressure.calibration"), pressureCalibrationString)) {
+ if (pressureCalibrationString == "none") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+ } else if (pressureCalibrationString == "physical") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+ } else if (pressureCalibrationString == "amplitude") {
+ out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
+ } else if (pressureCalibrationString != "default") {
+ ALOGW("Invalid value for touch.pressure.calibration: '%s'",
+ pressureCalibrationString.string());
+ }
+ }
+
+ out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"),
+ out.pressureScale);
+
+ // Orientation
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_DEFAULT;
+ String8 orientationCalibrationString;
+ if (in.tryGetProperty(String8("touch.orientation.calibration"), orientationCalibrationString)) {
+ if (orientationCalibrationString == "none") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+ } else if (orientationCalibrationString == "interpolated") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+ } else if (orientationCalibrationString == "vector") {
+ out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_VECTOR;
+ } else if (orientationCalibrationString != "default") {
+ ALOGW("Invalid value for touch.orientation.calibration: '%s'",
+ orientationCalibrationString.string());
+ }
+ }
+
+ // Distance
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_DEFAULT;
+ String8 distanceCalibrationString;
+ if (in.tryGetProperty(String8("touch.distance.calibration"), distanceCalibrationString)) {
+ if (distanceCalibrationString == "none") {
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+ } else if (distanceCalibrationString == "scaled") {
+ out.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+ } else if (distanceCalibrationString != "default") {
+ ALOGW("Invalid value for touch.distance.calibration: '%s'",
+ distanceCalibrationString.string());
+ }
+ }
+
+ out.haveDistanceScale = in.tryGetProperty(String8("touch.distance.scale"),
+ out.distanceScale);
+
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_DEFAULT;
+ String8 coverageCalibrationString;
+ if (in.tryGetProperty(String8("touch.coverage.calibration"), coverageCalibrationString)) {
+ if (coverageCalibrationString == "none") {
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+ } else if (coverageCalibrationString == "box") {
+ out.coverageCalibration = Calibration::COVERAGE_CALIBRATION_BOX;
+ } else if (coverageCalibrationString != "default") {
+ ALOGW("Invalid value for touch.coverage.calibration: '%s'",
+ coverageCalibrationString.string());
+ }
+ }
+}
+
+void TouchInputMapper::resolveCalibration() {
+ // Size
+ if (mRawPointerAxes.touchMajor.valid || mRawPointerAxes.toolMajor.valid) {
+ if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DEFAULT) {
+ mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_GEOMETRIC;
+ }
+ } else {
+ mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+ }
+
+ // Pressure
+ if (mRawPointerAxes.pressure.valid) {
+ if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_DEFAULT) {
+ mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+ }
+ } else {
+ mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+ }
+
+ // Orientation
+ if (mRawPointerAxes.orientation.valid) {
+ if (mCalibration.orientationCalibration == Calibration::ORIENTATION_CALIBRATION_DEFAULT) {
+ mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+ }
+ } else {
+ mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+ }
+
+ // Distance
+ if (mRawPointerAxes.distance.valid) {
+ if (mCalibration.distanceCalibration == Calibration::DISTANCE_CALIBRATION_DEFAULT) {
+ mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_SCALED;
+ }
+ } else {
+ mCalibration.distanceCalibration = Calibration::DISTANCE_CALIBRATION_NONE;
+ }
+
+ // Coverage
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_DEFAULT) {
+ mCalibration.coverageCalibration = Calibration::COVERAGE_CALIBRATION_NONE;
+ }
+}
+
+void TouchInputMapper::dumpCalibration(String8& dump) {
+ dump.append(INDENT3 "Calibration:\n");
+
+ // Size
+ switch (mCalibration.sizeCalibration) {
+ case Calibration::SIZE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.size.calibration: none\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_GEOMETRIC:
+ dump.append(INDENT4 "touch.size.calibration: geometric\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_DIAMETER:
+ dump.append(INDENT4 "touch.size.calibration: diameter\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_BOX:
+ dump.append(INDENT4 "touch.size.calibration: box\n");
+ break;
+ case Calibration::SIZE_CALIBRATION_AREA:
+ dump.append(INDENT4 "touch.size.calibration: area\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ if (mCalibration.haveSizeScale) {
+ dump.appendFormat(INDENT4 "touch.size.scale: %0.3f\n",
+ mCalibration.sizeScale);
+ }
+
+ if (mCalibration.haveSizeBias) {
+ dump.appendFormat(INDENT4 "touch.size.bias: %0.3f\n",
+ mCalibration.sizeBias);
+ }
+
+ if (mCalibration.haveSizeIsSummed) {
+ dump.appendFormat(INDENT4 "touch.size.isSummed: %s\n",
+ toString(mCalibration.sizeIsSummed));
+ }
+
+ // Pressure
+ switch (mCalibration.pressureCalibration) {
+ case Calibration::PRESSURE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.pressure.calibration: none\n");
+ break;
+ case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+ dump.append(INDENT4 "touch.pressure.calibration: physical\n");
+ break;
+ case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+ dump.append(INDENT4 "touch.pressure.calibration: amplitude\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ if (mCalibration.havePressureScale) {
+ dump.appendFormat(INDENT4 "touch.pressure.scale: %0.3f\n",
+ mCalibration.pressureScale);
+ }
+
+ // Orientation
+ switch (mCalibration.orientationCalibration) {
+ case Calibration::ORIENTATION_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.orientation.calibration: none\n");
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+ dump.append(INDENT4 "touch.orientation.calibration: interpolated\n");
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_VECTOR:
+ dump.append(INDENT4 "touch.orientation.calibration: vector\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ // Distance
+ switch (mCalibration.distanceCalibration) {
+ case Calibration::DISTANCE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.distance.calibration: none\n");
+ break;
+ case Calibration::DISTANCE_CALIBRATION_SCALED:
+ dump.append(INDENT4 "touch.distance.calibration: scaled\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+
+ if (mCalibration.haveDistanceScale) {
+ dump.appendFormat(INDENT4 "touch.distance.scale: %0.3f\n",
+ mCalibration.distanceScale);
+ }
+
+ switch (mCalibration.coverageCalibration) {
+ case Calibration::COVERAGE_CALIBRATION_NONE:
+ dump.append(INDENT4 "touch.coverage.calibration: none\n");
+ break;
+ case Calibration::COVERAGE_CALIBRATION_BOX:
+ dump.append(INDENT4 "touch.coverage.calibration: box\n");
+ break;
+ default:
+ ALOG_ASSERT(false);
+ }
+}
+
+void TouchInputMapper::dumpAffineTransformation(String8& dump) {
+ dump.append(INDENT3 "Affine Transformation:\n");
+
+ dump.appendFormat(INDENT4 "X scale: %0.3f\n", mAffineTransform.x_scale);
+ dump.appendFormat(INDENT4 "X ymix: %0.3f\n", mAffineTransform.x_ymix);
+ dump.appendFormat(INDENT4 "X offset: %0.3f\n", mAffineTransform.x_offset);
+ dump.appendFormat(INDENT4 "Y xmix: %0.3f\n", mAffineTransform.y_xmix);
+ dump.appendFormat(INDENT4 "Y scale: %0.3f\n", mAffineTransform.y_scale);
+ dump.appendFormat(INDENT4 "Y offset: %0.3f\n", mAffineTransform.y_offset);
+}
+
+void TouchInputMapper::updateAffineTransformation() {
+ mAffineTransform = getPolicy()->getTouchAffineTransformation(mDevice->getDescriptor(),
+ mSurfaceOrientation);
+}
+
+void TouchInputMapper::reset(nsecs_t when) {
+ mCursorButtonAccumulator.reset(getDevice());
+ mCursorScrollAccumulator.reset(getDevice());
+ mTouchButtonAccumulator.reset(getDevice());
+
+ mPointerVelocityControl.reset();
+ mWheelXVelocityControl.reset();
+ mWheelYVelocityControl.reset();
+
+ mCurrentRawPointerData.clear();
+ mLastRawPointerData.clear();
+ mCurrentCookedPointerData.clear();
+ mLastCookedPointerData.clear();
+ mCurrentButtonState = 0;
+ mLastButtonState = 0;
+ mCurrentRawVScroll = 0;
+ mCurrentRawHScroll = 0;
+ mCurrentFingerIdBits.clear();
+ mLastFingerIdBits.clear();
+ mCurrentStylusIdBits.clear();
+ mLastStylusIdBits.clear();
+ mCurrentMouseIdBits.clear();
+ mLastMouseIdBits.clear();
+ mPointerUsage = POINTER_USAGE_NONE;
+ mSentHoverEnter = false;
+ mDownTime = 0;
+
+ mCurrentVirtualKey.down = false;
+
+ mPointerGesture.reset();
+ mPointerSimple.reset();
+
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ mPointerController->clearSpots();
+ }
+
+ InputMapper::reset(when);
+}
+
+void TouchInputMapper::process(const RawEvent* rawEvent) {
+ mCursorButtonAccumulator.process(rawEvent);
+ mCursorScrollAccumulator.process(rawEvent);
+ mTouchButtonAccumulator.process(rawEvent);
+
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+ sync(rawEvent->when);
+ }
+}
+
+void TouchInputMapper::sync(nsecs_t when) {
+ // Sync button state.
+ mCurrentButtonState = mTouchButtonAccumulator.getButtonState()
+ | mCursorButtonAccumulator.getButtonState();
+
+ // Sync scroll state.
+ mCurrentRawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
+ mCurrentRawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
+ mCursorScrollAccumulator.finishSync();
+
+ // Sync touch state.
+ bool havePointerIds = true;
+ mCurrentRawPointerData.clear();
+ syncTouch(when, &havePointerIds);
+
+#if DEBUG_RAW_EVENTS
+ if (!havePointerIds) {
+ ALOGD("syncTouch: pointerCount %d -> %d, no pointer ids",
+ mLastRawPointerData.pointerCount,
+ mCurrentRawPointerData.pointerCount);
+ } else {
+ ALOGD("syncTouch: pointerCount %d -> %d, touching ids 0x%08x -> 0x%08x, "
+ "hovering ids 0x%08x -> 0x%08x",
+ mLastRawPointerData.pointerCount,
+ mCurrentRawPointerData.pointerCount,
+ mLastRawPointerData.touchingIdBits.value,
+ mCurrentRawPointerData.touchingIdBits.value,
+ mLastRawPointerData.hoveringIdBits.value,
+ mCurrentRawPointerData.hoveringIdBits.value);
+ }
+#endif
+
+ // Reset state that we will compute below.
+ mCurrentFingerIdBits.clear();
+ mCurrentStylusIdBits.clear();
+ mCurrentMouseIdBits.clear();
+ mCurrentCookedPointerData.clear();
+
+ if (mDeviceMode == DEVICE_MODE_DISABLED) {
+ // Drop all input if the device is disabled.
+ mCurrentRawPointerData.clear();
+ mCurrentButtonState = 0;
+ } else {
+ // Preprocess pointer data.
+ if (!havePointerIds) {
+ assignPointerIds();
+ }
+
+ // Handle policy on initial down or hover events.
+ uint32_t policyFlags = 0;
+ bool initialDown = mLastRawPointerData.pointerCount == 0
+ && mCurrentRawPointerData.pointerCount != 0;
+ bool buttonsPressed = mCurrentButtonState & ~mLastButtonState;
+ if (initialDown || buttonsPressed) {
+ // If this is a touch screen, hide the pointer on an initial down.
+ if (mDeviceMode == DEVICE_MODE_DIRECT) {
+ getContext()->fadePointer();
+ }
+
+ if (mParameters.wake) {
+ policyFlags |= POLICY_FLAG_WAKE;
+ }
+ }
+
+ // Synthesize key down from raw buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
+ policyFlags, mLastButtonState, mCurrentButtonState);
+
+ // Consume raw off-screen touches before cooking pointer data.
+ // If touches are consumed, subsequent code will not receive any pointer data.
+ if (consumeRawTouches(when, policyFlags)) {
+ mCurrentRawPointerData.clear();
+ }
+
+ // Cook pointer data. This call populates the mCurrentCookedPointerData structure
+ // with cooked pointer data that has the same ids and indices as the raw data.
+ // The following code can use either the raw or cooked data, as needed.
+ cookPointerData();
+
+ // Dispatch the touches either directly or by translation through a pointer on screen.
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ for (BitSet32 idBits(mCurrentRawPointerData.touchingIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS
+ || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+ mCurrentStylusIdBits.markBit(id);
+ } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
+ || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ mCurrentFingerIdBits.markBit(id);
+ } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) {
+ mCurrentMouseIdBits.markBit(id);
+ }
+ }
+ for (BitSet32 idBits(mCurrentRawPointerData.hoveringIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS
+ || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+ mCurrentStylusIdBits.markBit(id);
+ }
+ }
+
+ // Stylus takes precedence over all tools, then mouse, then finger.
+ PointerUsage pointerUsage = mPointerUsage;
+ if (!mCurrentStylusIdBits.isEmpty()) {
+ mCurrentMouseIdBits.clear();
+ mCurrentFingerIdBits.clear();
+ pointerUsage = POINTER_USAGE_STYLUS;
+ } else if (!mCurrentMouseIdBits.isEmpty()) {
+ mCurrentFingerIdBits.clear();
+ pointerUsage = POINTER_USAGE_MOUSE;
+ } else if (!mCurrentFingerIdBits.isEmpty() || isPointerDown(mCurrentButtonState)) {
+ pointerUsage = POINTER_USAGE_GESTURES;
+ }
+
+ dispatchPointerUsage(when, policyFlags, pointerUsage);
+ } else {
+ if (mDeviceMode == DEVICE_MODE_DIRECT
+ && mConfig.showTouches && mPointerController != NULL) {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+
+ mPointerController->setButtonState(mCurrentButtonState);
+ mPointerController->setSpots(mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mCurrentCookedPointerData.touchingIdBits);
+ }
+
+ dispatchHoverExit(when, policyFlags);
+ dispatchTouches(when, policyFlags);
+ dispatchHoverEnterAndMove(when, policyFlags);
+ }
+
+ // Synthesize key up from raw buttons if needed.
+ synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
+ policyFlags, mLastButtonState, mCurrentButtonState);
+ }
+
+ // Copy current touch to last touch in preparation for the next cycle.
+ mLastRawPointerData.copyFrom(mCurrentRawPointerData);
+ mLastCookedPointerData.copyFrom(mCurrentCookedPointerData);
+ mLastButtonState = mCurrentButtonState;
+ mLastFingerIdBits = mCurrentFingerIdBits;
+ mLastStylusIdBits = mCurrentStylusIdBits;
+ mLastMouseIdBits = mCurrentMouseIdBits;
+
+ // Clear some transient state.
+ mCurrentRawVScroll = 0;
+ mCurrentRawHScroll = 0;
+}
+
+void TouchInputMapper::timeoutExpired(nsecs_t when) {
+ if (mDeviceMode == DEVICE_MODE_POINTER) {
+ if (mPointerUsage == POINTER_USAGE_GESTURES) {
+ dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/);
+ }
+ }
+}
+
+bool TouchInputMapper::consumeRawTouches(nsecs_t when, uint32_t policyFlags) {
+ // Check for release of a virtual key.
+ if (mCurrentVirtualKey.down) {
+ if (mCurrentRawPointerData.touchingIdBits.isEmpty()) {
+ // Pointer went up while virtual key was down.
+ mCurrentVirtualKey.down = false;
+ if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
+ mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+ dispatchVirtualKey(when, policyFlags,
+ AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+ }
+ return true;
+ }
+
+ if (mCurrentRawPointerData.touchingIdBits.count() == 1) {
+ uint32_t id = mCurrentRawPointerData.touchingIdBits.firstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
+ if (virtualKey && virtualKey->keyCode == mCurrentVirtualKey.keyCode) {
+ // Pointer is still within the space of the virtual key.
+ return true;
+ }
+ }
+
+ // Pointer left virtual key area or another pointer also went down.
+ // Send key cancellation but do not consume the touch yet.
+ // This is useful when the user swipes through from the virtual key area
+ // into the main display surface.
+ mCurrentVirtualKey.down = false;
+ if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
+ mCurrentVirtualKey.keyCode, mCurrentVirtualKey.scanCode);
+#endif
+ dispatchVirtualKey(when, policyFlags,
+ AKEY_EVENT_ACTION_UP,
+ AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+ | AKEY_EVENT_FLAG_CANCELED);
+ }
+ }
+
+ if (mLastRawPointerData.touchingIdBits.isEmpty()
+ && !mCurrentRawPointerData.touchingIdBits.isEmpty()) {
+ // Pointer just went down. Check for virtual key press or off-screen touches.
+ uint32_t id = mCurrentRawPointerData.touchingIdBits.firstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ if (!isPointInsideSurface(pointer.x, pointer.y)) {
+ // If exactly one pointer went down, check for virtual key hit.
+ // Otherwise we will drop the entire stroke.
+ if (mCurrentRawPointerData.touchingIdBits.count() == 1) {
+ const VirtualKey* virtualKey = findVirtualKeyHit(pointer.x, pointer.y);
+ if (virtualKey) {
+ mCurrentVirtualKey.down = true;
+ mCurrentVirtualKey.downTime = when;
+ mCurrentVirtualKey.keyCode = virtualKey->keyCode;
+ mCurrentVirtualKey.scanCode = virtualKey->scanCode;
+ mCurrentVirtualKey.ignored = mContext->shouldDropVirtualKey(
+ when, getDevice(), virtualKey->keyCode, virtualKey->scanCode);
+
+ if (!mCurrentVirtualKey.ignored) {
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
+ mCurrentVirtualKey.keyCode,
+ mCurrentVirtualKey.scanCode);
+#endif
+ dispatchVirtualKey(when, policyFlags,
+ AKEY_EVENT_ACTION_DOWN,
+ AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ // Disable all virtual key touches that happen within a short time interval of the
+ // most recent touch within the screen area. The idea is to filter out stray
+ // virtual key presses when interacting with the touch screen.
+ //
+ // Problems we're trying to solve:
+ //
+ // 1. While scrolling a list or dragging the window shade, the user swipes down into a
+ // virtual key area that is implemented by a separate touch panel and accidentally
+ // triggers a virtual key.
+ //
+ // 2. While typing in the on screen keyboard, the user taps slightly outside the screen
+ // area and accidentally triggers a virtual key. This often happens when virtual keys
+ // are layed out below the screen near to where the on screen keyboard's space bar
+ // is displayed.
+ if (mConfig.virtualKeyQuietTime > 0 && !mCurrentRawPointerData.touchingIdBits.isEmpty()) {
+ mContext->disableVirtualKeysUntil(when + mConfig.virtualKeyQuietTime);
+ }
+ return false;
+}
+
+void TouchInputMapper::dispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
+ int32_t keyEventAction, int32_t keyEventFlags) {
+ int32_t keyCode = mCurrentVirtualKey.keyCode;
+ int32_t scanCode = mCurrentVirtualKey.scanCode;
+ nsecs_t downTime = mCurrentVirtualKey.downTime;
+ int32_t metaState = mContext->getGlobalMetaState();
+ policyFlags |= POLICY_FLAG_VIRTUAL;
+
+ NotifyKeyArgs args(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, policyFlags,
+ keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
+ getListener()->notifyKey(&args);
+}
+
+void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
+ BitSet32 currentIdBits = mCurrentCookedPointerData.touchingIdBits;
+ BitSet32 lastIdBits = mLastCookedPointerData.touchingIdBits;
+ int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mCurrentButtonState;
+
+ if (currentIdBits == lastIdBits) {
+ if (!currentIdBits.isEmpty()) {
+ // No pointer id changes so this is a move event.
+ // The listener takes care of batching moves so we don't have to deal with that here.
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ currentIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+ } else {
+ // There may be pointers going up and pointers going down and pointers moving
+ // all at the same time.
+ BitSet32 upIdBits(lastIdBits.value & ~currentIdBits.value);
+ BitSet32 downIdBits(currentIdBits.value & ~lastIdBits.value);
+ BitSet32 moveIdBits(lastIdBits.value & currentIdBits.value);
+ BitSet32 dispatchedIdBits(lastIdBits.value);
+
+ // Update last coordinates of pointers that have moved so that we observe the new
+ // pointer positions at the same time as other pointers that have just gone up.
+ bool moveNeeded = updateMovedPointers(
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mLastCookedPointerData.pointerProperties,
+ mLastCookedPointerData.pointerCoords,
+ mLastCookedPointerData.idToIndex,
+ moveIdBits);
+ if (buttonState != mLastButtonState) {
+ moveNeeded = true;
+ }
+
+ // Dispatch pointer up events.
+ while (!upIdBits.isEmpty()) {
+ uint32_t upId = upIdBits.clearFirstMarkedBit();
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_UP, 0, metaState, buttonState, 0,
+ mLastCookedPointerData.pointerProperties,
+ mLastCookedPointerData.pointerCoords,
+ mLastCookedPointerData.idToIndex,
+ dispatchedIdBits, upId,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ dispatchedIdBits.clearBit(upId);
+ }
+
+ // Dispatch move events if any of the remaining pointers moved from their old locations.
+ // Although applications receive new locations as part of individual pointer up
+ // events, they do not generally handle them except when presented in a move event.
+ if (moveNeeded) {
+ ALOG_ASSERT(moveIdBits.value == dispatchedIdBits.value);
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ dispatchedIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+
+ // Dispatch pointer down events using the new pointer locations.
+ while (!downIdBits.isEmpty()) {
+ uint32_t downId = downIdBits.clearFirstMarkedBit();
+ dispatchedIdBits.markBit(downId);
+
+ if (dispatchedIdBits.count() == 1) {
+ // First pointer is going down. Set down time.
+ mDownTime = when;
+ }
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, buttonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ dispatchedIdBits, downId,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+ }
+}
+
+void TouchInputMapper::dispatchHoverExit(nsecs_t when, uint32_t policyFlags) {
+ if (mSentHoverEnter &&
+ (mCurrentCookedPointerData.hoveringIdBits.isEmpty()
+ || !mCurrentCookedPointerData.touchingIdBits.isEmpty())) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_EXIT, 0, metaState, mLastButtonState, 0,
+ mLastCookedPointerData.pointerProperties,
+ mLastCookedPointerData.pointerCoords,
+ mLastCookedPointerData.idToIndex,
+ mLastCookedPointerData.hoveringIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ mSentHoverEnter = false;
+ }
+}
+
+void TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags) {
+ if (mCurrentCookedPointerData.touchingIdBits.isEmpty()
+ && !mCurrentCookedPointerData.hoveringIdBits.isEmpty()) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ if (!mSentHoverEnter) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_ENTER, 0, metaState, mCurrentButtonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mCurrentCookedPointerData.hoveringIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ mSentHoverEnter = true;
+ }
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, mCurrentButtonState, 0,
+ mCurrentCookedPointerData.pointerProperties,
+ mCurrentCookedPointerData.pointerCoords,
+ mCurrentCookedPointerData.idToIndex,
+ mCurrentCookedPointerData.hoveringIdBits, -1,
+ mOrientedXPrecision, mOrientedYPrecision, mDownTime);
+ }
+}
+
+void TouchInputMapper::cookPointerData() {
+ uint32_t currentPointerCount = mCurrentRawPointerData.pointerCount;
+
+ mCurrentCookedPointerData.clear();
+ mCurrentCookedPointerData.pointerCount = currentPointerCount;
+ mCurrentCookedPointerData.hoveringIdBits = mCurrentRawPointerData.hoveringIdBits;
+ mCurrentCookedPointerData.touchingIdBits = mCurrentRawPointerData.touchingIdBits;
+
+ // Walk through the the active pointers and map device coordinates onto
+ // surface coordinates and adjust for display orientation.
+ for (uint32_t i = 0; i < currentPointerCount; i++) {
+ const RawPointerData::Pointer& in = mCurrentRawPointerData.pointers[i];
+
+ // Size
+ float touchMajor, touchMinor, toolMajor, toolMinor, size;
+ switch (mCalibration.sizeCalibration) {
+ case Calibration::SIZE_CALIBRATION_GEOMETRIC:
+ case Calibration::SIZE_CALIBRATION_DIAMETER:
+ case Calibration::SIZE_CALIBRATION_BOX:
+ case Calibration::SIZE_CALIBRATION_AREA:
+ if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.toolMajor.valid) {
+ touchMajor = in.touchMajor;
+ touchMinor = mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
+ toolMajor = in.toolMajor;
+ toolMinor = mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
+ size = mRawPointerAxes.touchMinor.valid
+ ? avg(in.touchMajor, in.touchMinor) : in.touchMajor;
+ } else if (mRawPointerAxes.touchMajor.valid) {
+ toolMajor = touchMajor = in.touchMajor;
+ toolMinor = touchMinor = mRawPointerAxes.touchMinor.valid
+ ? in.touchMinor : in.touchMajor;
+ size = mRawPointerAxes.touchMinor.valid
+ ? avg(in.touchMajor, in.touchMinor) : in.touchMajor;
+ } else if (mRawPointerAxes.toolMajor.valid) {
+ touchMajor = toolMajor = in.toolMajor;
+ touchMinor = toolMinor = mRawPointerAxes.toolMinor.valid
+ ? in.toolMinor : in.toolMajor;
+ size = mRawPointerAxes.toolMinor.valid
+ ? avg(in.toolMajor, in.toolMinor) : in.toolMajor;
+ } else {
+ ALOG_ASSERT(false, "No touch or tool axes. "
+ "Size calibration should have been resolved to NONE.");
+ touchMajor = 0;
+ touchMinor = 0;
+ toolMajor = 0;
+ toolMinor = 0;
+ size = 0;
+ }
+
+ if (mCalibration.haveSizeIsSummed && mCalibration.sizeIsSummed) {
+ uint32_t touchingCount = mCurrentRawPointerData.touchingIdBits.count();
+ if (touchingCount > 1) {
+ touchMajor /= touchingCount;
+ touchMinor /= touchingCount;
+ toolMajor /= touchingCount;
+ toolMinor /= touchingCount;
+ size /= touchingCount;
+ }
+ }
+
+ if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_GEOMETRIC) {
+ touchMajor *= mGeometricScale;
+ touchMinor *= mGeometricScale;
+ toolMajor *= mGeometricScale;
+ toolMinor *= mGeometricScale;
+ } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_AREA) {
+ touchMajor = touchMajor > 0 ? sqrtf(touchMajor) : 0;
+ touchMinor = touchMajor;
+ toolMajor = toolMajor > 0 ? sqrtf(toolMajor) : 0;
+ toolMinor = toolMajor;
+ } else if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_DIAMETER) {
+ touchMinor = touchMajor;
+ toolMinor = toolMajor;
+ }
+
+ mCalibration.applySizeScaleAndBias(&touchMajor);
+ mCalibration.applySizeScaleAndBias(&touchMinor);
+ mCalibration.applySizeScaleAndBias(&toolMajor);
+ mCalibration.applySizeScaleAndBias(&toolMinor);
+ size *= mSizeScale;
+ break;
+ default:
+ touchMajor = 0;
+ touchMinor = 0;
+ toolMajor = 0;
+ toolMinor = 0;
+ size = 0;
+ break;
+ }
+
+ // Pressure
+ float pressure;
+ switch (mCalibration.pressureCalibration) {
+ case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+ case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+ pressure = in.pressure * mPressureScale;
+ break;
+ default:
+ pressure = in.isHovering ? 0 : 1;
+ break;
+ }
+
+ // Tilt and Orientation
+ float tilt;
+ float orientation;
+ if (mHaveTilt) {
+ float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale;
+ float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale;
+ orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle));
+ tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
+ } else {
+ tilt = 0;
+
+ switch (mCalibration.orientationCalibration) {
+ case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+ orientation = in.orientation * mOrientationScale;
+ break;
+ case Calibration::ORIENTATION_CALIBRATION_VECTOR: {
+ int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
+ int32_t c2 = signExtendNybble(in.orientation & 0x0f);
+ if (c1 != 0 || c2 != 0) {
+ orientation = atan2f(c1, c2) * 0.5f;
+ float confidence = hypotf(c1, c2);
+ float scale = 1.0f + confidence / 16.0f;
+ touchMajor *= scale;
+ touchMinor /= scale;
+ toolMajor *= scale;
+ toolMinor /= scale;
+ } else {
+ orientation = 0;
+ }
+ break;
+ }
+ default:
+ orientation = 0;
+ }
+ }
+
+ // Distance
+ float distance;
+ switch (mCalibration.distanceCalibration) {
+ case Calibration::DISTANCE_CALIBRATION_SCALED:
+ distance = in.distance * mDistanceScale;
+ break;
+ default:
+ distance = 0;
+ }
+
+ // Coverage
+ int32_t rawLeft, rawTop, rawRight, rawBottom;
+ switch (mCalibration.coverageCalibration) {
+ case Calibration::COVERAGE_CALIBRATION_BOX:
+ rawLeft = (in.toolMinor & 0xffff0000) >> 16;
+ rawRight = in.toolMinor & 0x0000ffff;
+ rawBottom = in.toolMajor & 0x0000ffff;
+ rawTop = (in.toolMajor & 0xffff0000) >> 16;
+ break;
+ default:
+ rawLeft = rawTop = rawRight = rawBottom = 0;
+ break;
+ }
+
+ // Adjust X,Y coords for device calibration
+ // TODO: Adjust coverage coords?
+ float xTransformed = in.x, yTransformed = in.y;
+ mAffineTransform.applyTo(xTransformed, yTransformed);
+
+ // Adjust X, Y, and coverage coords for surface orientation.
+ float x, y;
+ float left, top, right, bottom;
+
+ switch (mSurfaceOrientation) {
+ case DISPLAY_ORIENTATION_90:
+ x = float(yTransformed - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ y = float(mRawPointerAxes.x.maxValue - xTransformed) * mXScale + mXTranslate;
+ left = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ right = float(rawBottom- mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ bottom = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
+ top = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
+ orientation -= M_PI_2;
+ if (orientation < mOrientedRanges.orientation.min) {
+ orientation += (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
+ }
+ break;
+ case DISPLAY_ORIENTATION_180:
+ x = float(mRawPointerAxes.x.maxValue - xTransformed) * mXScale + mXTranslate;
+ y = float(mRawPointerAxes.y.maxValue - yTransformed) * mYScale + mYTranslate;
+ left = float(mRawPointerAxes.x.maxValue - rawRight) * mXScale + mXTranslate;
+ right = float(mRawPointerAxes.x.maxValue - rawLeft) * mXScale + mXTranslate;
+ bottom = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
+ top = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
+ orientation -= M_PI;
+ if (orientation < mOrientedRanges.orientation.min) {
+ orientation += (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
+ }
+ break;
+ case DISPLAY_ORIENTATION_270:
+ x = float(mRawPointerAxes.y.maxValue - yTransformed) * mYScale + mYTranslate;
+ y = float(xTransformed - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ left = float(mRawPointerAxes.y.maxValue - rawBottom) * mYScale + mYTranslate;
+ right = float(mRawPointerAxes.y.maxValue - rawTop) * mYScale + mYTranslate;
+ bottom = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ top = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ orientation += M_PI_2;
+ if (orientation > mOrientedRanges.orientation.max) {
+ orientation -= (mOrientedRanges.orientation.max - mOrientedRanges.orientation.min);
+ }
+ break;
+ default:
+ x = float(xTransformed - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ y = float(yTransformed - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ left = float(rawLeft - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ right = float(rawRight - mRawPointerAxes.x.minValue) * mXScale + mXTranslate;
+ bottom = float(rawBottom - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ top = float(rawTop - mRawPointerAxes.y.minValue) * mYScale + mYTranslate;
+ break;
+ }
+
+ // Write output coords.
+ PointerCoords& out = mCurrentCookedPointerData.pointerCoords[i];
+ out.clear();
+ out.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ out.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ out.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, pressure);
+ out.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, touchMajor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, touchMinor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, orientation);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TILT, tilt);
+ out.setAxisValue(AMOTION_EVENT_AXIS_DISTANCE, distance);
+ if (mCalibration.coverageCalibration == Calibration::COVERAGE_CALIBRATION_BOX) {
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_1, left);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_2, top);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_3, right);
+ out.setAxisValue(AMOTION_EVENT_AXIS_GENERIC_4, bottom);
+ } else {
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, toolMajor);
+ out.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, toolMinor);
+ }
+
+ // Write output properties.
+ PointerProperties& properties = mCurrentCookedPointerData.pointerProperties[i];
+ uint32_t id = in.id;
+ properties.clear();
+ properties.id = id;
+ properties.toolType = in.toolType;
+
+ // Write id index.
+ mCurrentCookedPointerData.idToIndex[id] = i;
+ }
+}
+
+void TouchInputMapper::dispatchPointerUsage(nsecs_t when, uint32_t policyFlags,
+ PointerUsage pointerUsage) {
+ if (pointerUsage != mPointerUsage) {
+ abortPointerUsage(when, policyFlags);
+ mPointerUsage = pointerUsage;
+ }
+
+ switch (mPointerUsage) {
+ case POINTER_USAGE_GESTURES:
+ dispatchPointerGestures(when, policyFlags, false /*isTimeout*/);
+ break;
+ case POINTER_USAGE_STYLUS:
+ dispatchPointerStylus(when, policyFlags);
+ break;
+ case POINTER_USAGE_MOUSE:
+ dispatchPointerMouse(when, policyFlags);
+ break;
+ default:
+ break;
+ }
+}
+
+void TouchInputMapper::abortPointerUsage(nsecs_t when, uint32_t policyFlags) {
+ switch (mPointerUsage) {
+ case POINTER_USAGE_GESTURES:
+ abortPointerGestures(when, policyFlags);
+ break;
+ case POINTER_USAGE_STYLUS:
+ abortPointerStylus(when, policyFlags);
+ break;
+ case POINTER_USAGE_MOUSE:
+ abortPointerMouse(when, policyFlags);
+ break;
+ default:
+ break;
+ }
+
+ mPointerUsage = POINTER_USAGE_NONE;
+}
+
+void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags,
+ bool isTimeout) {
+ // Update current gesture coordinates.
+ bool cancelPreviousGesture, finishPreviousGesture;
+ bool sendEvents = preparePointerGestures(when,
+ &cancelPreviousGesture, &finishPreviousGesture, isTimeout);
+ if (!sendEvents) {
+ return;
+ }
+ if (finishPreviousGesture) {
+ cancelPreviousGesture = false;
+ }
+
+ // Update the pointer presentation and spots.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);
+ if (finishPreviousGesture || cancelPreviousGesture) {
+ mPointerController->clearSpots();
+ }
+ mPointerController->setSpots(mPointerGesture.currentGestureCoords,
+ mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.currentGestureIdBits);
+ } else {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+ }
+
+ // Show or hide the pointer if needed.
+ switch (mPointerGesture.currentGestureMode) {
+ case PointerGesture::NEUTRAL:
+ case PointerGesture::QUIET:
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+ && (mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)) {
+ // Remind the user of where the pointer is after finishing a gesture with spots.
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+ break;
+ case PointerGesture::TAP:
+ case PointerGesture::TAP_DRAG:
+ case PointerGesture::BUTTON_CLICK_OR_DRAG:
+ case PointerGesture::HOVER:
+ case PointerGesture::PRESS:
+ // Unfade the pointer when the current gesture manipulates the
+ // area directly under the pointer.
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ break;
+ case PointerGesture::SWIPE:
+ case PointerGesture::FREEFORM:
+ // Fade the pointer when the current gesture manipulates a different
+ // area and there are spots to guide the user experience.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ } else {
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ }
+ break;
+ }
+
+ // Send events!
+ int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mCurrentButtonState;
+
+ // Update last coordinates of pointers that have moved so that we observe the new
+ // pointer positions at the same time as other pointers that have just gone up.
+ bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP
+ || mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG
+ || mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG
+ || mPointerGesture.currentGestureMode == PointerGesture::PRESS
+ || mPointerGesture.currentGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.currentGestureMode == PointerGesture::FREEFORM;
+ bool moveNeeded = false;
+ if (down && !cancelPreviousGesture && !finishPreviousGesture
+ && !mPointerGesture.lastGestureIdBits.isEmpty()
+ && !mPointerGesture.currentGestureIdBits.isEmpty()) {
+ BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value
+ & mPointerGesture.lastGestureIdBits.value);
+ moveNeeded = updateMovedPointers(mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ movedGestureIdBits);
+ if (buttonState != mLastButtonState) {
+ moveNeeded = true;
+ }
+ }
+
+ // Send motion events for all pointers that went up or were canceled.
+ BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
+ if (!dispatchedGestureIdBits.isEmpty()) {
+ if (cancelPreviousGesture) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_CANCEL, 0, metaState, buttonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ dispatchedGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+
+ dispatchedGestureIdBits.clear();
+ } else {
+ BitSet32 upGestureIdBits;
+ if (finishPreviousGesture) {
+ upGestureIdBits = dispatchedGestureIdBits;
+ } else {
+ upGestureIdBits.value = dispatchedGestureIdBits.value
+ & ~mPointerGesture.currentGestureIdBits.value;
+ }
+ while (!upGestureIdBits.isEmpty()) {
+ uint32_t id = upGestureIdBits.clearFirstMarkedBit();
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_UP, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ dispatchedGestureIdBits, id,
+ 0, 0, mPointerGesture.downTime);
+
+ dispatchedGestureIdBits.clearBit(id);
+ }
+ }
+ }
+
+ // Send motion events for all pointers that moved.
+ if (moveNeeded) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ dispatchedGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+ }
+
+ // Send motion events for all pointers that went down.
+ if (down) {
+ BitSet32 downGestureIdBits(mPointerGesture.currentGestureIdBits.value
+ & ~dispatchedGestureIdBits.value);
+ while (!downGestureIdBits.isEmpty()) {
+ uint32_t id = downGestureIdBits.clearFirstMarkedBit();
+ dispatchedGestureIdBits.markBit(id);
+
+ if (dispatchedGestureIdBits.count() == 1) {
+ mPointerGesture.downTime = when;
+ }
+
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_POINTER_DOWN, 0, metaState, buttonState, 0,
+ mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ dispatchedGestureIdBits, id,
+ 0, 0, mPointerGesture.downTime);
+ }
+ }
+
+ // Send motion events for hover.
+ if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) {
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.currentGestureProperties,
+ mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
+ mPointerGesture.currentGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+ } else if (dispatchedGestureIdBits.isEmpty()
+ && !mPointerGesture.lastGestureIdBits.isEmpty()) {
+ // Synthesize a hover move event after all pointers go up to indicate that
+ // the pointer is hovering again even if the user is not currently touching
+ // the touch pad. This ensures that a view will receive a fresh hover enter
+ // event after a tap.
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+ metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ mViewport.displayId, 1, &pointerProperties, &pointerCoords,
+ 0, 0, mPointerGesture.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Update state.
+ mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode;
+ if (!down) {
+ mPointerGesture.lastGestureIdBits.clear();
+ } else {
+ mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits;
+ for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
+ mPointerGesture.lastGestureProperties[index].copyFrom(
+ mPointerGesture.currentGestureProperties[index]);
+ mPointerGesture.lastGestureCoords[index].copyFrom(
+ mPointerGesture.currentGestureCoords[index]);
+ mPointerGesture.lastGestureIdToIndex[id] = index;
+ }
+ }
+}
+
+void TouchInputMapper::abortPointerGestures(nsecs_t when, uint32_t policyFlags) {
+ // Cancel previously dispatches pointers.
+ if (!mPointerGesture.lastGestureIdBits.isEmpty()) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ int32_t buttonState = mCurrentButtonState;
+ dispatchMotion(when, policyFlags, mSource,
+ AMOTION_EVENT_ACTION_CANCEL, 0, metaState, buttonState,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ mPointerGesture.lastGestureProperties,
+ mPointerGesture.lastGestureCoords, mPointerGesture.lastGestureIdToIndex,
+ mPointerGesture.lastGestureIdBits, -1,
+ 0, 0, mPointerGesture.downTime);
+ }
+
+ // Reset the current pointer gesture.
+ mPointerGesture.reset();
+ mPointerVelocityControl.reset();
+
+ // Remove any current spots.
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ mPointerController->clearSpots();
+ }
+}
+
+bool TouchInputMapper::preparePointerGestures(nsecs_t when,
+ bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout) {
+ *outCancelPreviousGesture = false;
+ *outFinishPreviousGesture = false;
+
+ // Handle TAP timeout.
+ if (isTimeout) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Processing timeout");
+#endif
+
+ if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+ if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
+ // The tap/drag timeout has not yet expired.
+ getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime
+ + mConfig.pointerGestureTapDragInterval);
+ } else {
+ // The tap is finished.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: TAP finished");
+#endif
+ *outFinishPreviousGesture = true;
+
+ mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+ mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerVelocityControl.reset();
+ return true;
+ }
+ }
+
+ // We did not handle this timeout.
+ return false;
+ }
+
+ const uint32_t currentFingerCount = mCurrentFingerIdBits.count();
+ const uint32_t lastFingerCount = mLastFingerIdBits.count();
+
+ // Update the velocity tracker.
+ {
+ VelocityTracker::Position positions[MAX_POINTERS];
+ uint32_t count = 0;
+ for (BitSet32 idBits(mCurrentFingerIdBits); !idBits.isEmpty(); count++) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& pointer = mCurrentRawPointerData.pointerForId(id);
+ positions[count].x = pointer.x * mPointerXMovementScale;
+ positions[count].y = pointer.y * mPointerYMovementScale;
+ }
+ mPointerGesture.velocityTracker.addMovement(when,
+ mCurrentFingerIdBits, positions);
+ }
+
+ // If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning
+ // to NEUTRAL, then we should not generate tap event.
+ if (mPointerGesture.lastGestureMode != PointerGesture::HOVER
+ && mPointerGesture.lastGestureMode != PointerGesture::TAP
+ && mPointerGesture.lastGestureMode != PointerGesture::TAP_DRAG) {
+ mPointerGesture.resetTap();
+ }
+
+ // 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
+ if (mPointerGesture.tapDownTime != LLONG_MIN) {
+ ALOGD("Gestures: Not a TAP, %0.3fms since down",
+ (when - mPointerGesture.tapDownTime) * 0.000001f);
+ } else {
+ ALOGD("Gestures: Not a TAP, incompatible mode transitions");
+ }
+#endif
+ }
+ }
+
+ mPointerVelocityControl.reset();
+
+ if (!tapped) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: NEUTRAL");
+#endif
+ mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+ mPointerGesture.currentGestureIdBits.clear();
+ }
+ } else if (currentFingerCount == 1) {
+ // Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG)
+ // The pointer follows the active touch point.
+ // When in HOVER, emit HOVER_MOVE events at the pointer location.
+ // When in TAP_DRAG, emit MOVE events at the pointer location.
+ ALOG_ASSERT(activeTouchId >= 0);
+
+ mPointerGesture.currentGestureMode = PointerGesture::HOVER;
+ if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+ if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop
+ && fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+ mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
+ x - mPointerGesture.tapX,
+ y - mPointerGesture.tapY);
+#endif
+ }
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up",
+ (when - mPointerGesture.tapUpTime) * 0.000001f);
+#endif
+ }
+ } else if (mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) {
+ mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+ }
+
+ if (mLastFingerIdBits.hasBit(activeTouchId)) {
+ const RawPointerData::Pointer& currentPointer =
+ mCurrentRawPointerData.pointerForId(activeTouchId);
+ const RawPointerData::Pointer& lastPointer =
+ mLastRawPointerData.pointerForId(activeTouchId);
+ float deltaX = (currentPointer.x - lastPointer.x)
+ * mPointerXMovementScale;
+ float deltaY = (currentPointer.y - lastPointer.y)
+ * mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ // Move the pointer using a relative motion.
+ // When using spots, the hover or drag will occur at the position of the anchor spot.
+ mPointerController->move(deltaX, deltaY);
+ } else {
+ mPointerVelocityControl.reset();
+ }
+
+ bool down;
+ if (mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: TAP_DRAG");
+#endif
+ down = true;
+ } else {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: HOVER");
+#endif
+ if (mPointerGesture.lastGestureMode != PointerGesture::HOVER) {
+ *outFinishPreviousGesture = true;
+ }
+ mPointerGesture.activeGestureId = 0;
+ down = false;
+ }
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+ down ? 1.0f : 0.0f);
+
+ if (lastFingerCount == 0 && currentFingerCount != 0) {
+ mPointerGesture.resetTap();
+ mPointerGesture.tapDownTime = when;
+ mPointerGesture.tapX = x;
+ mPointerGesture.tapY = y;
+ }
+ } else {
+ // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
+ // We need to provide feedback for each finger that goes down so we cannot wait
+ // for the fingers to move before deciding what to do.
+ //
+ // The ambiguous case is deciding what to do when there are two fingers down but they
+ // have not moved enough to determine whether they are part of a drag or part of a
+ // freeform gesture, or just a press or long-press at the pointer location.
+ //
+ // When there are two fingers we start with the PRESS hypothesis and we generate a
+ // down at the pointer location.
+ //
+ // When the two fingers move enough or when additional fingers are added, we make
+ // a decision to transition into SWIPE or FREEFORM mode accordingly.
+ ALOG_ASSERT(activeTouchId >= 0);
+
+ bool settled = when >= mPointerGesture.firstTouchTime
+ + mConfig.pointerGestureMultitouchSettleInterval;
+ if (mPointerGesture.lastGestureMode != PointerGesture::PRESS
+ && mPointerGesture.lastGestureMode != PointerGesture::SWIPE
+ && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
+ *outFinishPreviousGesture = true;
+ } else if (!settled && currentFingerCount > lastFingerCount) {
+ // Additional pointers have gone down but not yet settled.
+ // Reset the gesture.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, "
+ "settle time remaining %0.3fms", (mPointerGesture.firstTouchTime
+ + mConfig.pointerGestureMultitouchSettleInterval - when)
+ * 0.000001f);
+#endif
+ *outCancelPreviousGesture = true;
+ } else {
+ // Continue previous gesture.
+ mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
+ }
+
+ if (*outFinishPreviousGesture || *outCancelPreviousGesture) {
+ mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+ mPointerGesture.activeGestureId = 0;
+ mPointerGesture.referenceIdBits.clear();
+ mPointerVelocityControl.reset();
+
+ // Use the centroid and pointer location as the reference points for the gesture.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: Using centroid as reference for MULTITOUCH, "
+ "settle time remaining %0.3fms", (mPointerGesture.firstTouchTime
+ + mConfig.pointerGestureMultitouchSettleInterval - when)
+ * 0.000001f);
+#endif
+ mCurrentRawPointerData.getCentroidOfTouchingPointers(
+ &mPointerGesture.referenceTouchX,
+ &mPointerGesture.referenceTouchY);
+ mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+ &mPointerGesture.referenceGestureY);
+ }
+
+ // Clear the reference deltas for fingers not yet included in the reference calculation.
+ for (BitSet32 idBits(mCurrentFingerIdBits.value
+ & ~mPointerGesture.referenceIdBits.value); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ mPointerGesture.referenceDeltas[id].dx = 0;
+ mPointerGesture.referenceDeltas[id].dy = 0;
+ }
+ mPointerGesture.referenceIdBits = mCurrentFingerIdBits;
+
+ // Add delta for all fingers and calculate a common movement delta.
+ float commonDeltaX = 0, commonDeltaY = 0;
+ BitSet32 commonIdBits(mLastFingerIdBits.value
+ & mCurrentFingerIdBits.value);
+ for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) {
+ bool first = (idBits == commonIdBits);
+ uint32_t id = idBits.clearFirstMarkedBit();
+ const RawPointerData::Pointer& cpd = mCurrentRawPointerData.pointerForId(id);
+ const RawPointerData::Pointer& lpd = mLastRawPointerData.pointerForId(id);
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ delta.dx += cpd.x - lpd.x;
+ delta.dy += cpd.y - lpd.y;
+
+ if (first) {
+ commonDeltaX = delta.dx;
+ commonDeltaY = delta.dy;
+ } else {
+ commonDeltaX = calculateCommonVector(commonDeltaX, delta.dx);
+ commonDeltaY = calculateCommonVector(commonDeltaY, delta.dy);
+ }
+ }
+
+ // Consider transitions from PRESS to SWIPE or MULTITOUCH.
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+ float dist[MAX_POINTER_ID + 1];
+ int32_t distOverThreshold = 0;
+ for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ dist[id] = hypotf(delta.dx * mPointerXZoomScale,
+ delta.dy * mPointerYZoomScale);
+ if (dist[id] > mConfig.pointerGestureMultitouchMinDistance) {
+ distOverThreshold += 1;
+ }
+ }
+
+ // Only transition when at least two pointers have moved further than
+ // the minimum distance threshold.
+ if (distOverThreshold >= 2) {
+ if (currentFingerCount > 2) {
+ // There are more than two pointers, switch to FREEFORM.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+ currentFingerCount);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ } else {
+ // There are exactly two pointers.
+ BitSet32 idBits(mCurrentFingerIdBits);
+ uint32_t id1 = idBits.clearFirstMarkedBit();
+ uint32_t id2 = idBits.firstMarkedBit();
+ const RawPointerData::Pointer& p1 = mCurrentRawPointerData.pointerForId(id1);
+ const RawPointerData::Pointer& p2 = mCurrentRawPointerData.pointerForId(id2);
+ float mutualDistance = distance(p1.x, p1.y, p2.x, p2.y);
+ if (mutualDistance > mPointerGestureMaxSwipeWidth) {
+ // There are two pointers but they are too far apart for a SWIPE,
+ // switch to FREEFORM.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+ mutualDistance, mPointerGestureMaxSwipeWidth);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ } else {
+ // There are two pointers. Wait for both pointers to start moving
+ // before deciding whether this is a SWIPE or FREEFORM gesture.
+ float dist1 = dist[id1];
+ float dist2 = dist[id2];
+ if (dist1 >= mConfig.pointerGestureMultitouchMinDistance
+ && dist2 >= mConfig.pointerGestureMultitouchMinDistance) {
+ // Calculate the dot product of the displacement vectors.
+ // When the vectors are oriented in approximately the same direction,
+ // the angle betweeen them is near zero and the cosine of the angle
+ // approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2).
+ PointerGesture::Delta& delta1 = mPointerGesture.referenceDeltas[id1];
+ PointerGesture::Delta& delta2 = mPointerGesture.referenceDeltas[id2];
+ float dx1 = delta1.dx * mPointerXZoomScale;
+ float dy1 = delta1.dy * mPointerYZoomScale;
+ float dx2 = delta2.dx * mPointerXZoomScale;
+ float dy2 = delta2.dy * mPointerYZoomScale;
+ float dot = dx1 * dx2 + dy1 * dy2;
+ float cosine = dot / (dist1 * dist2); // denominator always > 0
+ if (cosine >= mConfig.pointerGestureSwipeTransitionAngleCosine) {
+ // Pointers are moving in the same direction. Switch to SWIPE.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to SWIPE, "
+ "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+ "cosine %0.3f >= %0.3f",
+ dist1, mConfig.pointerGestureMultitouchMinDistance,
+ dist2, mConfig.pointerGestureMultitouchMinDistance,
+ cosine, mConfig.pointerGestureSwipeTransitionAngleCosine);
+#endif
+ mPointerGesture.currentGestureMode = PointerGesture::SWIPE;
+ } else {
+ // Pointers are moving in different directions. Switch to FREEFORM.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS transitioned to FREEFORM, "
+ "dist1 %0.3f >= %0.3f, dist2 %0.3f >= %0.3f, "
+ "cosine %0.3f < %0.3f",
+ dist1, mConfig.pointerGestureMultitouchMinDistance,
+ dist2, mConfig.pointerGestureMultitouchMinDistance,
+ cosine, mConfig.pointerGestureSwipeTransitionAngleCosine);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ }
+ }
+ }
+ }
+ }
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+ // Switch from SWIPE to FREEFORM if additional pointers go down.
+ // Cancel previous gesture.
+ if (currentFingerCount > 2) {
+#if DEBUG_GESTURES
+ ALOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+ currentFingerCount);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ }
+ }
+
+ // Move the reference points based on the overall group motion of the fingers
+ // except in PRESS mode while waiting for a transition to occur.
+ if (mPointerGesture.currentGestureMode != PointerGesture::PRESS
+ && (commonDeltaX || commonDeltaY)) {
+ for (BitSet32 idBits(mPointerGesture.referenceIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ PointerGesture::Delta& delta = mPointerGesture.referenceDeltas[id];
+ delta.dx = 0;
+ delta.dy = 0;
+ }
+
+ mPointerGesture.referenceTouchX += commonDeltaX;
+ mPointerGesture.referenceTouchY += commonDeltaY;
+
+ commonDeltaX *= mPointerXMovementScale;
+ commonDeltaY *= mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &commonDeltaX, &commonDeltaY);
+ mPointerVelocityControl.move(when, &commonDeltaX, &commonDeltaY);
+
+ mPointerGesture.referenceGestureX += commonDeltaX;
+ mPointerGesture.referenceGestureY += commonDeltaY;
+ }
+
+ // Report gestures.
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS
+ || mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+ // PRESS or SWIPE mode.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: PRESS or SWIPE activeTouchId=%d,"
+ "activeGestureId=%d, currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+#endif
+ ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureProperties[0].clear();
+ mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
+ mPointerGesture.currentGestureProperties[0].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ mPointerGesture.referenceGestureX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ mPointerGesture.referenceGestureY);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
+ // FREEFORM mode.
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM activeTouchId=%d,"
+ "activeGestureId=%d, currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, currentFingerCount);
+#endif
+ ALOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+
+ BitSet32 mappedTouchIdBits;
+ BitSet32 usedGestureIdBits;
+ if (mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
+ // Initially, assign the active gesture id to the active touch point
+ // if there is one. No other touch id bits are mapped yet.
+ if (!*outCancelPreviousGesture) {
+ mappedTouchIdBits.markBit(activeTouchId);
+ usedGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.freeformTouchToGestureIdMap[activeTouchId] =
+ mPointerGesture.activeGestureId;
+ } else {
+ mPointerGesture.activeGestureId = -1;
+ }
+ } else {
+ // Otherwise, assume we mapped all touches from the previous frame.
+ // Reuse all mappings that are still applicable.
+ mappedTouchIdBits.value = mLastFingerIdBits.value
+ & mCurrentFingerIdBits.value;
+ usedGestureIdBits = mPointerGesture.lastGestureIdBits;
+
+ // Check whether we need to choose a new active gesture id because the
+ // current went went up.
+ for (BitSet32 upTouchIdBits(mLastFingerIdBits.value
+ & ~mCurrentFingerIdBits.value);
+ !upTouchIdBits.isEmpty(); ) {
+ uint32_t upTouchId = upTouchIdBits.clearFirstMarkedBit();
+ uint32_t upGestureId = mPointerGesture.freeformTouchToGestureIdMap[upTouchId];
+ if (upGestureId == uint32_t(mPointerGesture.activeGestureId)) {
+ mPointerGesture.activeGestureId = -1;
+ break;
+ }
+ }
+ }
+
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM follow up "
+ "mappedTouchIdBits=0x%08x, usedGestureIdBits=0x%08x, "
+ "activeGestureId=%d",
+ mappedTouchIdBits.value, usedGestureIdBits.value,
+ mPointerGesture.activeGestureId);
+#endif
+
+ BitSet32 idBits(mCurrentFingerIdBits);
+ for (uint32_t i = 0; i < currentFingerCount; i++) {
+ uint32_t touchId = idBits.clearFirstMarkedBit();
+ uint32_t gestureId;
+ if (!mappedTouchIdBits.hasBit(touchId)) {
+ gestureId = usedGestureIdBits.markFirstUnmarkedBit();
+ mPointerGesture.freeformTouchToGestureIdMap[touchId] = gestureId;
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM "
+ "new mapping for touch id %d -> gesture id %d",
+ touchId, gestureId);
+#endif
+ } else {
+ gestureId = mPointerGesture.freeformTouchToGestureIdMap[touchId];
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM "
+ "existing mapping for touch id %d -> gesture id %d",
+ touchId, gestureId);
+#endif
+ }
+ mPointerGesture.currentGestureIdBits.markBit(gestureId);
+ mPointerGesture.currentGestureIdToIndex[gestureId] = i;
+
+ const RawPointerData::Pointer& pointer =
+ mCurrentRawPointerData.pointerForId(touchId);
+ float deltaX = (pointer.x - mPointerGesture.referenceTouchX)
+ * mPointerXZoomScale;
+ float deltaY = (pointer.y - mPointerGesture.referenceTouchY)
+ * mPointerYZoomScale;
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+
+ mPointerGesture.currentGestureProperties[i].clear();
+ mPointerGesture.currentGestureProperties[i].id = gestureId;
+ mPointerGesture.currentGestureProperties[i].toolType =
+ AMOTION_EVENT_TOOL_TYPE_FINGER;
+ mPointerGesture.currentGestureCoords[i].clear();
+ mPointerGesture.currentGestureCoords[i].setAxisValue(
+ AMOTION_EVENT_AXIS_X, mPointerGesture.referenceGestureX + deltaX);
+ mPointerGesture.currentGestureCoords[i].setAxisValue(
+ AMOTION_EVENT_AXIS_Y, mPointerGesture.referenceGestureY + deltaY);
+ mPointerGesture.currentGestureCoords[i].setAxisValue(
+ AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ }
+
+ if (mPointerGesture.activeGestureId < 0) {
+ mPointerGesture.activeGestureId =
+ mPointerGesture.currentGestureIdBits.firstMarkedBit();
+#if DEBUG_GESTURES
+ ALOGD("Gestures: FREEFORM new "
+ "activeGestureId=%d", mPointerGesture.activeGestureId);
+#endif
+ }
+ }
+ }
+
+ mPointerController->setButtonState(mCurrentButtonState);
+
+#if DEBUG_GESTURES
+ ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
+ "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
+ "lastGestureMode=%d, lastGestureIdBits=0x%08x",
+ toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
+ mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
+ mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
+ for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
+ const PointerProperties& properties = mPointerGesture.currentGestureProperties[index];
+ const PointerCoords& coords = mPointerGesture.currentGestureCoords[index];
+ ALOGD(" currentGesture[%d]: index=%d, toolType=%d, "
+ "x=%0.3f, y=%0.3f, pressure=%0.3f",
+ id, index, properties.toolType,
+ coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+ for (BitSet32 idBits = mPointerGesture.lastGestureIdBits; !idBits.isEmpty(); ) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = mPointerGesture.lastGestureIdToIndex[id];
+ const PointerProperties& properties = mPointerGesture.lastGestureProperties[index];
+ const PointerCoords& coords = mPointerGesture.lastGestureCoords[index];
+ ALOGD(" lastGesture[%d]: index=%d, toolType=%d, "
+ "x=%0.3f, y=%0.3f, pressure=%0.3f",
+ id, index, properties.toolType,
+ coords.getAxisValue(AMOTION_EVENT_AXIS_X),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+#endif
+ return true;
+}
+
+void TouchInputMapper::dispatchPointerStylus(nsecs_t when, uint32_t policyFlags) {
+ mPointerSimple.currentCoords.clear();
+ mPointerSimple.currentProperties.clear();
+
+ bool down, hovering;
+ if (!mCurrentStylusIdBits.isEmpty()) {
+ uint32_t id = mCurrentStylusIdBits.firstMarkedBit();
+ uint32_t index = mCurrentCookedPointerData.idToIndex[id];
+ float x = mCurrentCookedPointerData.pointerCoords[index].getX();
+ float y = mCurrentCookedPointerData.pointerCoords[index].getY();
+ mPointerController->setPosition(x, y);
+
+ hovering = mCurrentCookedPointerData.hoveringIdBits.hasBit(id);
+ down = !hovering;
+
+ mPointerController->getPosition(&x, &y);
+ mPointerSimple.currentCoords.copyFrom(mCurrentCookedPointerData.pointerCoords[index]);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerSimple.currentProperties.id = 0;
+ mPointerSimple.currentProperties.toolType =
+ mCurrentCookedPointerData.pointerProperties[index].toolType;
+ } else {
+ down = false;
+ hovering = false;
+ }
+
+ dispatchPointerSimple(when, policyFlags, down, hovering);
+}
+
+void TouchInputMapper::abortPointerStylus(nsecs_t when, uint32_t policyFlags) {
+ abortPointerSimple(when, policyFlags);
+}
+
+void TouchInputMapper::dispatchPointerMouse(nsecs_t when, uint32_t policyFlags) {
+ mPointerSimple.currentCoords.clear();
+ mPointerSimple.currentProperties.clear();
+
+ bool down, hovering;
+ if (!mCurrentMouseIdBits.isEmpty()) {
+ uint32_t id = mCurrentMouseIdBits.firstMarkedBit();
+ uint32_t currentIndex = mCurrentRawPointerData.idToIndex[id];
+ if (mLastMouseIdBits.hasBit(id)) {
+ uint32_t lastIndex = mCurrentRawPointerData.idToIndex[id];
+ float deltaX = (mCurrentRawPointerData.pointers[currentIndex].x
+ - mLastRawPointerData.pointers[lastIndex].x)
+ * mPointerXMovementScale;
+ float deltaY = (mCurrentRawPointerData.pointers[currentIndex].y
+ - mLastRawPointerData.pointers[lastIndex].y)
+ * mPointerYMovementScale;
+
+ rotateDelta(mSurfaceOrientation, &deltaX, &deltaY);
+ mPointerVelocityControl.move(when, &deltaX, &deltaY);
+
+ mPointerController->move(deltaX, deltaY);
+ } else {
+ mPointerVelocityControl.reset();
+ }
+
+ down = isPointerDown(mCurrentButtonState);
+ hovering = !down;
+
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ mPointerSimple.currentCoords.copyFrom(
+ mCurrentCookedPointerData.pointerCoords[currentIndex]);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+ hovering ? 0.0f : 1.0f);
+ mPointerSimple.currentProperties.id = 0;
+ mPointerSimple.currentProperties.toolType =
+ mCurrentCookedPointerData.pointerProperties[currentIndex].toolType;
+ } else {
+ mPointerVelocityControl.reset();
+
+ down = false;
+ hovering = false;
+ }
+
+ dispatchPointerSimple(when, policyFlags, down, hovering);
+}
+
+void TouchInputMapper::abortPointerMouse(nsecs_t when, uint32_t policyFlags) {
+ abortPointerSimple(when, policyFlags);
+
+ mPointerVelocityControl.reset();
+}
+
+void TouchInputMapper::dispatchPointerSimple(nsecs_t when, uint32_t policyFlags,
+ bool down, bool hovering) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+
+ if (mPointerController != NULL) {
+ if (down || hovering) {
+ mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_POINTER);
+ mPointerController->clearSpots();
+ mPointerController->setButtonState(mCurrentButtonState);
+ mPointerController->unfade(PointerControllerInterface::TRANSITION_IMMEDIATE);
+ } else if (!down && !hovering && (mPointerSimple.down || mPointerSimple.hovering)) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+ }
+
+ if (mPointerSimple.down && !down) {
+ mPointerSimple.down = false;
+
+ // Send up.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_UP, 0, metaState, mLastButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (mPointerSimple.hovering && !hovering) {
+ mPointerSimple.hovering = false;
+
+ // Send hover exit.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_EXIT, 0, metaState, mLastButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.lastProperties, &mPointerSimple.lastCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (down) {
+ if (!mPointerSimple.down) {
+ mPointerSimple.down = true;
+ mPointerSimple.downTime = when;
+
+ // Send down.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_DOWN, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Send move.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (hovering) {
+ if (!mPointerSimple.hovering) {
+ mPointerSimple.hovering = true;
+
+ // Send hover enter.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_ENTER, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Send hover move.
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_HOVER_MOVE, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &mPointerSimple.currentCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ if (mCurrentRawVScroll || mCurrentRawHScroll) {
+ float vscroll = mCurrentRawVScroll;
+ float hscroll = mCurrentRawHScroll;
+ mWheelYVelocityControl.move(when, NULL, &vscroll);
+ mWheelXVelocityControl.move(when, &hscroll, NULL);
+
+ // Send scroll.
+ PointerCoords pointerCoords;
+ pointerCoords.copyFrom(mPointerSimple.currentCoords);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
+
+ NotifyMotionArgs args(when, getDeviceId(), mSource, policyFlags,
+ AMOTION_EVENT_ACTION_SCROLL, 0, metaState, mCurrentButtonState, 0,
+ mViewport.displayId,
+ 1, &mPointerSimple.currentProperties, &pointerCoords,
+ mOrientedXPrecision, mOrientedYPrecision,
+ mPointerSimple.downTime);
+ getListener()->notifyMotion(&args);
+ }
+
+ // Save state.
+ if (down || hovering) {
+ mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
+ mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+ } else {
+ mPointerSimple.reset();
+ }
+}
+
+void TouchInputMapper::abortPointerSimple(nsecs_t when, uint32_t policyFlags) {
+ mPointerSimple.currentCoords.clear();
+ mPointerSimple.currentProperties.clear();
+
+ dispatchPointerSimple(when, policyFlags, false, false);
+}
+
+void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags,
+ const PointerProperties* properties, const PointerCoords* coords,
+ const uint32_t* idToIndex, BitSet32 idBits,
+ int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) {
+ PointerCoords pointerCoords[MAX_POINTERS];
+ PointerProperties pointerProperties[MAX_POINTERS];
+ uint32_t pointerCount = 0;
+ while (!idBits.isEmpty()) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t index = idToIndex[id];
+ pointerProperties[pointerCount].copyFrom(properties[index]);
+ pointerCoords[pointerCount].copyFrom(coords[index]);
+
+ if (changedId >= 0 && id == uint32_t(changedId)) {
+ action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ }
+
+ pointerCount += 1;
+ }
+
+ ALOG_ASSERT(pointerCount != 0);
+
+ if (changedId >= 0 && pointerCount == 1) {
+ // Replace initial down and final up action.
+ // We can compare the action without masking off the changed pointer index
+ // because we know the index is 0.
+ if (action == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ action = AMOTION_EVENT_ACTION_DOWN;
+ } else if (action == AMOTION_EVENT_ACTION_POINTER_UP) {
+ action = AMOTION_EVENT_ACTION_UP;
+ } else {
+ // Can't happen.
+ ALOG_ASSERT(false);
+ }
+ }
+
+ NotifyMotionArgs args(when, getDeviceId(), source, policyFlags,
+ action, flags, metaState, buttonState, edgeFlags,
+ mViewport.displayId, pointerCount, pointerProperties, pointerCoords,
+ xPrecision, yPrecision, downTime);
+ getListener()->notifyMotion(&args);
+}
+
+bool TouchInputMapper::updateMovedPointers(const PointerProperties* inProperties,
+ const PointerCoords* inCoords, const uint32_t* inIdToIndex,
+ PointerProperties* outProperties, PointerCoords* outCoords, const uint32_t* outIdToIndex,
+ BitSet32 idBits) const {
+ bool changed = false;
+ while (!idBits.isEmpty()) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ uint32_t inIndex = inIdToIndex[id];
+ uint32_t outIndex = outIdToIndex[id];
+
+ const PointerProperties& curInProperties = inProperties[inIndex];
+ const PointerCoords& curInCoords = inCoords[inIndex];
+ PointerProperties& curOutProperties = outProperties[outIndex];
+ PointerCoords& curOutCoords = outCoords[outIndex];
+
+ if (curInProperties != curOutProperties) {
+ curOutProperties.copyFrom(curInProperties);
+ changed = true;
+ }
+
+ if (curInCoords != curOutCoords) {
+ curOutCoords.copyFrom(curInCoords);
+ changed = true;
+ }
+ }
+ return changed;
+}
+
+void TouchInputMapper::fadePointer() {
+ if (mPointerController != NULL) {
+ mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);
+ }
+}
+
+bool TouchInputMapper::isPointInsideSurface(int32_t x, int32_t y) {
+ return x >= mRawPointerAxes.x.minValue && x <= mRawPointerAxes.x.maxValue
+ && y >= mRawPointerAxes.y.minValue && y <= mRawPointerAxes.y.maxValue;
+}
+
+const TouchInputMapper::VirtualKey* TouchInputMapper::findVirtualKeyHit(
+ int32_t x, int32_t y) {
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+
+#if DEBUG_VIRTUAL_KEYS
+ ALOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
+ "left=%d, top=%d, right=%d, bottom=%d",
+ x, y,
+ virtualKey.keyCode, virtualKey.scanCode,
+ virtualKey.hitLeft, virtualKey.hitTop,
+ virtualKey.hitRight, virtualKey.hitBottom);
+#endif
+
+ if (virtualKey.isHit(x, y)) {
+ return & virtualKey;
+ }
+ }
+
+ return NULL;
+}
+
+void TouchInputMapper::assignPointerIds() {
+ uint32_t currentPointerCount = mCurrentRawPointerData.pointerCount;
+ uint32_t lastPointerCount = mLastRawPointerData.pointerCount;
+
+ mCurrentRawPointerData.clearIdBits();
+
+ if (currentPointerCount == 0) {
+ // No pointers to assign.
+ return;
+ }
+
+ if (lastPointerCount == 0) {
+ // All pointers are new.
+ for (uint32_t i = 0; i < currentPointerCount; i++) {
+ uint32_t id = i;
+ mCurrentRawPointerData.pointers[i].id = id;
+ mCurrentRawPointerData.idToIndex[id] = i;
+ mCurrentRawPointerData.markIdBit(id, mCurrentRawPointerData.isHovering(i));
+ }
+ return;
+ }
+
+ if (currentPointerCount == 1 && lastPointerCount == 1
+ && mCurrentRawPointerData.pointers[0].toolType
+ == mLastRawPointerData.pointers[0].toolType) {
+ // Only one pointer and no change in count so it must have the same id as before.
+ uint32_t id = mLastRawPointerData.pointers[0].id;
+ mCurrentRawPointerData.pointers[0].id = id;
+ mCurrentRawPointerData.idToIndex[id] = 0;
+ mCurrentRawPointerData.markIdBit(id, mCurrentRawPointerData.isHovering(0));
+ return;
+ }
+
+ // General case.
+ // We build a heap of squared euclidean distances between current and last pointers
+ // associated with the current and last pointer indices. Then, we find the best
+ // match (by distance) for each current pointer.
+ // The pointers must have the same tool type but it is possible for them to
+ // transition from hovering to touching or vice-versa while retaining the same id.
+ PointerDistanceHeapElement heap[MAX_POINTERS * MAX_POINTERS];
+
+ uint32_t heapSize = 0;
+ for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
+ currentPointerIndex++) {
+ for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
+ lastPointerIndex++) {
+ const RawPointerData::Pointer& currentPointer =
+ mCurrentRawPointerData.pointers[currentPointerIndex];
+ const RawPointerData::Pointer& lastPointer =
+ mLastRawPointerData.pointers[lastPointerIndex];
+ if (currentPointer.toolType == lastPointer.toolType) {
+ int64_t deltaX = currentPointer.x - lastPointer.x;
+ int64_t deltaY = currentPointer.y - lastPointer.y;
+
+ uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
+
+ // Insert new element into the heap (sift up).
+ heap[heapSize].currentPointerIndex = currentPointerIndex;
+ heap[heapSize].lastPointerIndex = lastPointerIndex;
+ heap[heapSize].distance = distance;
+ heapSize += 1;
+ }
+ }
+ }
+
+ // Heapify
+ for (uint32_t startIndex = heapSize / 2; startIndex != 0; ) {
+ startIndex -= 1;
+ for (uint32_t parentIndex = startIndex; ;) {
+ uint32_t childIndex = parentIndex * 2 + 1;
+ if (childIndex >= heapSize) {
+ break;
+ }
+
+ if (childIndex + 1 < heapSize
+ && heap[childIndex + 1].distance < heap[childIndex].distance) {
+ childIndex += 1;
+ }
+
+ if (heap[parentIndex].distance <= heap[childIndex].distance) {
+ break;
+ }
+
+ swap(heap[parentIndex], heap[childIndex]);
+ parentIndex = childIndex;
+ }
+ }
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - initial distance min-heap: size=%d", heapSize);
+ for (size_t i = 0; i < heapSize; i++) {
+ ALOGD(" heap[%d]: cur=%d, last=%d, distance=%lld",
+ i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+ heap[i].distance);
+ }
+#endif
+
+ // Pull matches out by increasing order of distance.
+ // To avoid reassigning pointers that have already been matched, the loop keeps track
+ // of which last and current pointers have been matched using the matchedXXXBits variables.
+ // It also tracks the used pointer id bits.
+ BitSet32 matchedLastBits(0);
+ BitSet32 matchedCurrentBits(0);
+ BitSet32 usedIdBits(0);
+ bool first = true;
+ for (uint32_t i = min(currentPointerCount, lastPointerCount); heapSize > 0 && i > 0; i--) {
+ while (heapSize > 0) {
+ if (first) {
+ // The first time through the loop, we just consume the root element of
+ // the heap (the one with smallest distance).
+ first = false;
+ } else {
+ // Previous iterations consumed the root element of the heap.
+ // Pop root element off of the heap (sift down).
+ heap[0] = heap[heapSize];
+ for (uint32_t parentIndex = 0; ;) {
+ uint32_t childIndex = parentIndex * 2 + 1;
+ if (childIndex >= heapSize) {
+ break;
+ }
+
+ if (childIndex + 1 < heapSize
+ && heap[childIndex + 1].distance < heap[childIndex].distance) {
+ childIndex += 1;
+ }
+
+ if (heap[parentIndex].distance <= heap[childIndex].distance) {
+ break;
+ }
+
+ swap(heap[parentIndex], heap[childIndex]);
+ parentIndex = childIndex;
+ }
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - reduced distance min-heap: size=%d", heapSize);
+ for (size_t i = 0; i < heapSize; i++) {
+ ALOGD(" heap[%d]: cur=%d, last=%d, distance=%lld",
+ i, heap[i].currentPointerIndex, heap[i].lastPointerIndex,
+ heap[i].distance);
+ }
+#endif
+ }
+
+ heapSize -= 1;
+
+ uint32_t currentPointerIndex = heap[0].currentPointerIndex;
+ if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched
+
+ uint32_t lastPointerIndex = heap[0].lastPointerIndex;
+ if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched
+
+ matchedCurrentBits.markBit(currentPointerIndex);
+ matchedLastBits.markBit(lastPointerIndex);
+
+ uint32_t id = mLastRawPointerData.pointers[lastPointerIndex].id;
+ mCurrentRawPointerData.pointers[currentPointerIndex].id = id;
+ mCurrentRawPointerData.idToIndex[id] = currentPointerIndex;
+ mCurrentRawPointerData.markIdBit(id,
+ mCurrentRawPointerData.isHovering(currentPointerIndex));
+ usedIdBits.markBit(id);
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - matched: cur=%d, last=%d, id=%d, distance=%lld",
+ lastPointerIndex, currentPointerIndex, id, heap[0].distance);
+#endif
+ break;
+ }
+ }
+
+ // Assign fresh ids to pointers that were not matched in the process.
+ for (uint32_t i = currentPointerCount - matchedCurrentBits.count(); i != 0; i--) {
+ uint32_t currentPointerIndex = matchedCurrentBits.markFirstUnmarkedBit();
+ uint32_t id = usedIdBits.markFirstUnmarkedBit();
+
+ mCurrentRawPointerData.pointers[currentPointerIndex].id = id;
+ mCurrentRawPointerData.idToIndex[id] = currentPointerIndex;
+ mCurrentRawPointerData.markIdBit(id,
+ mCurrentRawPointerData.isHovering(currentPointerIndex));
+
+#if DEBUG_POINTER_ASSIGNMENT
+ ALOGD("assignPointerIds - assigned: cur=%d, id=%d",
+ currentPointerIndex, id);
+#endif
+ }
+}
+
+int32_t TouchInputMapper::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ if (mCurrentVirtualKey.down && mCurrentVirtualKey.keyCode == keyCode) {
+ return AKEY_STATE_VIRTUAL;
+ }
+
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+ if (virtualKey.keyCode == keyCode) {
+ return AKEY_STATE_UP;
+ }
+ }
+
+ return AKEY_STATE_UNKNOWN;
+}
+
+int32_t TouchInputMapper::getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ if (mCurrentVirtualKey.down && mCurrentVirtualKey.scanCode == scanCode) {
+ return AKEY_STATE_VIRTUAL;
+ }
+
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+ if (virtualKey.scanCode == scanCode) {
+ return AKEY_STATE_UP;
+ }
+ }
+
+ return AKEY_STATE_UNKNOWN;
+}
+
+bool TouchInputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ size_t numVirtualKeys = mVirtualKeys.size();
+ for (size_t i = 0; i < numVirtualKeys; i++) {
+ const VirtualKey& virtualKey = mVirtualKeys[i];
+
+ for (size_t i = 0; i < numCodes; i++) {
+ if (virtualKey.keyCode == keyCodes[i]) {
+ outFlags[i] = 1;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+// --- SingleTouchInputMapper ---
+
+SingleTouchInputMapper::SingleTouchInputMapper(InputDevice* device) :
+ TouchInputMapper(device) {
+}
+
+SingleTouchInputMapper::~SingleTouchInputMapper() {
+}
+
+void SingleTouchInputMapper::reset(nsecs_t when) {
+ mSingleTouchMotionAccumulator.reset(getDevice());
+
+ TouchInputMapper::reset(when);
+}
+
+void SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+ TouchInputMapper::process(rawEvent);
+
+ mSingleTouchMotionAccumulator.process(rawEvent);
+}
+
+void SingleTouchInputMapper::syncTouch(nsecs_t when, bool* outHavePointerIds) {
+ if (mTouchButtonAccumulator.isToolActive()) {
+ mCurrentRawPointerData.pointerCount = 1;
+ mCurrentRawPointerData.idToIndex[0] = 0;
+
+ bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE
+ && (mTouchButtonAccumulator.isHovering()
+ || (mRawPointerAxes.pressure.valid
+ && mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0));
+ mCurrentRawPointerData.markIdBit(0, isHovering);
+
+ RawPointerData::Pointer& outPointer = mCurrentRawPointerData.pointers[0];
+ outPointer.id = 0;
+ outPointer.x = mSingleTouchMotionAccumulator.getAbsoluteX();
+ outPointer.y = mSingleTouchMotionAccumulator.getAbsoluteY();
+ outPointer.pressure = mSingleTouchMotionAccumulator.getAbsolutePressure();
+ outPointer.touchMajor = 0;
+ outPointer.touchMinor = 0;
+ outPointer.toolMajor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
+ outPointer.toolMinor = mSingleTouchMotionAccumulator.getAbsoluteToolWidth();
+ outPointer.orientation = 0;
+ outPointer.distance = mSingleTouchMotionAccumulator.getAbsoluteDistance();
+ outPointer.tiltX = mSingleTouchMotionAccumulator.getAbsoluteTiltX();
+ outPointer.tiltY = mSingleTouchMotionAccumulator.getAbsoluteTiltY();
+ outPointer.toolType = mTouchButtonAccumulator.getToolType();
+ if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ }
+ outPointer.isHovering = isHovering;
+ }
+}
+
+void SingleTouchInputMapper::configureRawPointerAxes() {
+ TouchInputMapper::configureRawPointerAxes();
+
+ getAbsoluteAxisInfo(ABS_X, &mRawPointerAxes.x);
+ getAbsoluteAxisInfo(ABS_Y, &mRawPointerAxes.y);
+ getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPointerAxes.pressure);
+ getAbsoluteAxisInfo(ABS_TOOL_WIDTH, &mRawPointerAxes.toolMajor);
+ getAbsoluteAxisInfo(ABS_DISTANCE, &mRawPointerAxes.distance);
+ getAbsoluteAxisInfo(ABS_TILT_X, &mRawPointerAxes.tiltX);
+ getAbsoluteAxisInfo(ABS_TILT_Y, &mRawPointerAxes.tiltY);
+}
+
+bool SingleTouchInputMapper::hasStylus() const {
+ return mTouchButtonAccumulator.hasStylus();
+}
+
+
+// --- MultiTouchInputMapper ---
+
+MultiTouchInputMapper::MultiTouchInputMapper(InputDevice* device) :
+ TouchInputMapper(device) {
+}
+
+MultiTouchInputMapper::~MultiTouchInputMapper() {
+}
+
+void MultiTouchInputMapper::reset(nsecs_t when) {
+ mMultiTouchMotionAccumulator.reset(getDevice());
+
+ mPointerIdBits.clear();
+
+ TouchInputMapper::reset(when);
+}
+
+void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+ TouchInputMapper::process(rawEvent);
+
+ mMultiTouchMotionAccumulator.process(rawEvent);
+}
+
+void MultiTouchInputMapper::syncTouch(nsecs_t when, bool* outHavePointerIds) {
+ size_t inCount = mMultiTouchMotionAccumulator.getSlotCount();
+ size_t outCount = 0;
+ BitSet32 newPointerIdBits;
+
+ for (size_t inIndex = 0; inIndex < inCount; inIndex++) {
+ const MultiTouchMotionAccumulator::Slot* inSlot =
+ mMultiTouchMotionAccumulator.getSlot(inIndex);
+ if (!inSlot->isInUse()) {
+ continue;
+ }
+
+ if (outCount >= MAX_POINTERS) {
+#if DEBUG_POINTERS
+ ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
+ "ignoring the rest.",
+ getDeviceName().string(), MAX_POINTERS);
+#endif
+ break; // too many fingers!
+ }
+
+ RawPointerData::Pointer& outPointer = mCurrentRawPointerData.pointers[outCount];
+ outPointer.x = inSlot->getX();
+ outPointer.y = inSlot->getY();
+ outPointer.pressure = inSlot->getPressure();
+ outPointer.touchMajor = inSlot->getTouchMajor();
+ outPointer.touchMinor = inSlot->getTouchMinor();
+ outPointer.toolMajor = inSlot->getToolMajor();
+ outPointer.toolMinor = inSlot->getToolMinor();
+ outPointer.orientation = inSlot->getOrientation();
+ outPointer.distance = inSlot->getDistance();
+ outPointer.tiltX = 0;
+ outPointer.tiltY = 0;
+
+ outPointer.toolType = inSlot->getToolType();
+ if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ outPointer.toolType = mTouchButtonAccumulator.getToolType();
+ if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
+ outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ }
+ }
+
+ bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE
+ && (mTouchButtonAccumulator.isHovering()
+ || (mRawPointerAxes.pressure.valid && inSlot->getPressure() <= 0));
+ outPointer.isHovering = isHovering;
+
+ // Assign pointer id using tracking id if available.
+ if (*outHavePointerIds) {
+ int32_t trackingId = inSlot->getTrackingId();
+ int32_t id = -1;
+ if (trackingId >= 0) {
+ for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty(); ) {
+ uint32_t n = idBits.clearFirstMarkedBit();
+ if (mPointerTrackingIdMap[n] == trackingId) {
+ id = n;
+ }
+ }
+
+ if (id < 0 && !mPointerIdBits.isFull()) {
+ id = mPointerIdBits.markFirstUnmarkedBit();
+ mPointerTrackingIdMap[id] = trackingId;
+ }
+ }
+ if (id < 0) {
+ *outHavePointerIds = false;
+ mCurrentRawPointerData.clearIdBits();
+ newPointerIdBits.clear();
+ } else {
+ outPointer.id = id;
+ mCurrentRawPointerData.idToIndex[id] = outCount;
+ mCurrentRawPointerData.markIdBit(id, isHovering);
+ newPointerIdBits.markBit(id);
+ }
+ }
+
+ outCount += 1;
+ }
+
+ mCurrentRawPointerData.pointerCount = outCount;
+ mPointerIdBits = newPointerIdBits;
+
+ mMultiTouchMotionAccumulator.finishSync();
+}
+
+void MultiTouchInputMapper::configureRawPointerAxes() {
+ TouchInputMapper::configureRawPointerAxes();
+
+ getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x);
+ getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y);
+ getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor);
+ getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor);
+ getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor);
+ getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor);
+ getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation);
+ getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure);
+ getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance);
+ getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId);
+ getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot);
+
+ if (mRawPointerAxes.trackingId.valid
+ && mRawPointerAxes.slot.valid
+ && mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) {
+ size_t slotCount = mRawPointerAxes.slot.maxValue + 1;
+ if (slotCount > MAX_SLOTS) {
+ ALOGW("MultiTouch Device %s reported %zu slots but the framework "
+ "only supports a maximum of %zu slots at this time.",
+ getDeviceName().string(), slotCount, MAX_SLOTS);
+ slotCount = MAX_SLOTS;
+ }
+ mMultiTouchMotionAccumulator.configure(getDevice(),
+ slotCount, true /*usingSlotsProtocol*/);
+ } else {
+ mMultiTouchMotionAccumulator.configure(getDevice(),
+ MAX_POINTERS, false /*usingSlotsProtocol*/);
+ }
+}
+
+bool MultiTouchInputMapper::hasStylus() const {
+ return mMultiTouchMotionAccumulator.hasStylus()
+ || mTouchButtonAccumulator.hasStylus();
+}
+
+
+// --- JoystickInputMapper ---
+
+JoystickInputMapper::JoystickInputMapper(InputDevice* device) :
+ InputMapper(device) {
+}
+
+JoystickInputMapper::~JoystickInputMapper() {
+}
+
+uint32_t JoystickInputMapper::getSources() {
+ return AINPUT_SOURCE_JOYSTICK;
+}
+
+void JoystickInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ for (size_t i = 0; i < mAxes.size(); i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ addMotionRange(axis.axisInfo.axis, axis, info);
+
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ addMotionRange(axis.axisInfo.highAxis, axis, info);
+
+ }
+ }
+}
+
+void JoystickInputMapper::addMotionRange(int32_t axisId, const Axis& axis,
+ InputDeviceInfo* info) {
+ info->addMotionRange(axisId, AINPUT_SOURCE_JOYSTICK,
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ /* In order to ease the transition for developers from using the old axes
+ * to the newer, more semantically correct axes, we'll continue to register
+ * the old axes as duplicates of their corresponding new ones. */
+ int32_t compatAxis = getCompatAxis(axisId);
+ if (compatAxis >= 0) {
+ info->addMotionRange(compatAxis, AINPUT_SOURCE_JOYSTICK,
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ }
+}
+
+/* A mapping from axes the joystick actually has to the axes that should be
+ * artificially created for compatibility purposes.
+ * Returns -1 if no compatibility axis is needed. */
+int32_t JoystickInputMapper::getCompatAxis(int32_t axis) {
+ switch(axis) {
+ case AMOTION_EVENT_AXIS_LTRIGGER:
+ return AMOTION_EVENT_AXIS_BRAKE;
+ case AMOTION_EVENT_AXIS_RTRIGGER:
+ return AMOTION_EVENT_AXIS_GAS;
+ }
+ return -1;
+}
+
+void JoystickInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Joystick Input Mapper:\n");
+
+ dump.append(INDENT3 "Axes:\n");
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ const char* label = getAxisLabel(axis.axisInfo.axis);
+ if (label) {
+ dump.appendFormat(INDENT4 "%s", label);
+ } else {
+ dump.appendFormat(INDENT4 "%d", axis.axisInfo.axis);
+ }
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ label = getAxisLabel(axis.axisInfo.highAxis);
+ if (label) {
+ dump.appendFormat(" / %s (split at %d)", label, axis.axisInfo.splitValue);
+ } else {
+ dump.appendFormat(" / %d (split at %d)", axis.axisInfo.highAxis,
+ axis.axisInfo.splitValue);
+ }
+ } else if (axis.axisInfo.mode == AxisInfo::MODE_INVERT) {
+ dump.append(" (invert)");
+ }
+
+ dump.appendFormat(": min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f, resolution=%0.5f\n",
+ axis.min, axis.max, axis.flat, axis.fuzz, axis.resolution);
+ dump.appendFormat(INDENT4 " scale=%0.5f, offset=%0.5f, "
+ "highScale=%0.5f, highOffset=%0.5f\n",
+ axis.scale, axis.offset, axis.highScale, axis.highOffset);
+ dump.appendFormat(INDENT4 " rawAxis=%d, rawMin=%d, rawMax=%d, "
+ "rawFlat=%d, rawFuzz=%d, rawResolution=%d\n",
+ mAxes.keyAt(i), axis.rawAxisInfo.minValue, axis.rawAxisInfo.maxValue,
+ axis.rawAxisInfo.flat, axis.rawAxisInfo.fuzz, axis.rawAxisInfo.resolution);
+ }
+}
+
+void JoystickInputMapper::configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ // Collect all axes.
+ for (int32_t abs = 0; abs <= ABS_MAX; abs++) {
+ if (!(getAbsAxisUsage(abs, getDevice()->getClasses())
+ & INPUT_DEVICE_CLASS_JOYSTICK)) {
+ continue; // axis must be claimed by a different device
+ }
+
+ RawAbsoluteAxisInfo rawAxisInfo;
+ getAbsoluteAxisInfo(abs, &rawAxisInfo);
+ if (rawAxisInfo.valid) {
+ // Map axis.
+ AxisInfo axisInfo;
+ bool explicitlyMapped = !getEventHub()->mapAxis(getDeviceId(), abs, &axisInfo);
+ if (!explicitlyMapped) {
+ // Axis is not explicitly mapped, will choose a generic axis later.
+ axisInfo.mode = AxisInfo::MODE_NORMAL;
+ axisInfo.axis = -1;
+ }
+
+ // Apply flat override.
+ int32_t rawFlat = axisInfo.flatOverride < 0
+ ? rawAxisInfo.flat : axisInfo.flatOverride;
+
+ // Calculate scaling factors and limits.
+ Axis axis;
+ if (axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ float scale = 1.0f / (axisInfo.splitValue - rawAxisInfo.minValue);
+ float highScale = 1.0f / (rawAxisInfo.maxValue - axisInfo.splitValue);
+ axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+ scale, 0.0f, highScale, 0.0f,
+ 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
+ } else if (isCenteredAxis(axisInfo.axis)) {
+ float scale = 2.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
+ float offset = avg(rawAxisInfo.minValue, rawAxisInfo.maxValue) * -scale;
+ axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+ scale, offset, scale, offset,
+ -1.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
+ } else {
+ float scale = 1.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
+ axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+ scale, 0.0f, scale, 0.0f,
+ 0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale,
+ rawAxisInfo.resolution * scale);
+ }
+
+ // To eliminate noise while the joystick is at rest, filter out small variations
+ // in axis values up front.
+ axis.filter = axis.fuzz ? axis.fuzz : 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 %zu axes but the framework only supports a maximum of %d.",
+ getDeviceName().string(), mAxes.size(), PointerCoords::MAX_AXES);
+ pruneAxes(true);
+ pruneAxes(false);
+ }
+
+ // Assign generic axis ids to remaining axes.
+ int32_t nextGenericAxisId = AMOTION_EVENT_AXIS_GENERIC_1;
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ Axis& axis = mAxes.editValueAt(i);
+ if (axis.axisInfo.axis < 0) {
+ while (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16
+ && haveAxis(nextGenericAxisId)) {
+ nextGenericAxisId += 1;
+ }
+
+ if (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16) {
+ axis.axisInfo.axis = nextGenericAxisId;
+ nextGenericAxisId += 1;
+ } else {
+ ALOGI("Ignoring joystick '%s' axis %d because all of the generic axis ids "
+ "have already been assigned to other axes.",
+ getDeviceName().string(), mAxes.keyAt(i));
+ mAxes.removeItemsAt(i--);
+ numAxes -= 1;
+ }
+ }
+ }
+ }
+}
+
+bool JoystickInputMapper::haveAxis(int32_t axisId) {
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ if (axis.axisInfo.axis == axisId
+ || (axis.axisInfo.mode == AxisInfo::MODE_SPLIT
+ && axis.axisInfo.highAxis == axisId)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void JoystickInputMapper::pruneAxes(bool ignoreExplicitlyMappedAxes) {
+ size_t i = mAxes.size();
+ while (mAxes.size() > PointerCoords::MAX_AXES && i-- > 0) {
+ if (ignoreExplicitlyMappedAxes && mAxes.valueAt(i).explicitlyMapped) {
+ continue;
+ }
+ ALOGI("Discarding joystick '%s' axis %d because there are too many axes.",
+ getDeviceName().string(), mAxes.keyAt(i));
+ mAxes.removeItemsAt(i);
+ }
+}
+
+bool JoystickInputMapper::isCenteredAxis(int32_t axis) {
+ switch (axis) {
+ case AMOTION_EVENT_AXIS_X:
+ case AMOTION_EVENT_AXIS_Y:
+ case AMOTION_EVENT_AXIS_Z:
+ case AMOTION_EVENT_AXIS_RX:
+ case AMOTION_EVENT_AXIS_RY:
+ case AMOTION_EVENT_AXIS_RZ:
+ case AMOTION_EVENT_AXIS_HAT_X:
+ case AMOTION_EVENT_AXIS_HAT_Y:
+ case AMOTION_EVENT_AXIS_ORIENTATION:
+ case AMOTION_EVENT_AXIS_RUDDER:
+ case AMOTION_EVENT_AXIS_WHEEL:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void JoystickInputMapper::reset(nsecs_t when) {
+ // Recenter all axes.
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ Axis& axis = mAxes.editValueAt(i);
+ axis.resetValue();
+ }
+
+ InputMapper::reset(when);
+}
+
+void JoystickInputMapper::process(const RawEvent* rawEvent) {
+ switch (rawEvent->type) {
+ case EV_ABS: {
+ ssize_t index = mAxes.indexOfKey(rawEvent->code);
+ if (index >= 0) {
+ Axis& axis = mAxes.editValueAt(index);
+ float newValue, highNewValue;
+ switch (axis.axisInfo.mode) {
+ case AxisInfo::MODE_INVERT:
+ newValue = (axis.rawAxisInfo.maxValue - rawEvent->value)
+ * axis.scale + axis.offset;
+ highNewValue = 0.0f;
+ break;
+ case AxisInfo::MODE_SPLIT:
+ if (rawEvent->value < axis.axisInfo.splitValue) {
+ newValue = (axis.axisInfo.splitValue - rawEvent->value)
+ * axis.scale + axis.offset;
+ highNewValue = 0.0f;
+ } else if (rawEvent->value > axis.axisInfo.splitValue) {
+ newValue = 0.0f;
+ highNewValue = (rawEvent->value - axis.axisInfo.splitValue)
+ * axis.highScale + axis.highOffset;
+ } else {
+ newValue = 0.0f;
+ highNewValue = 0.0f;
+ }
+ break;
+ default:
+ newValue = rawEvent->value * axis.scale + axis.offset;
+ highNewValue = 0.0f;
+ break;
+ }
+ axis.newValue = newValue;
+ axis.highNewValue = highNewValue;
+ }
+ break;
+ }
+
+ case EV_SYN:
+ switch (rawEvent->code) {
+ case SYN_REPORT:
+ sync(rawEvent->when, false /*force*/);
+ break;
+ }
+ break;
+ }
+}
+
+void JoystickInputMapper::sync(nsecs_t when, bool force) {
+ if (!filterAxes(force)) {
+ return;
+ }
+
+ int32_t metaState = mContext->getGlobalMetaState();
+ int32_t buttonState = 0;
+
+ PointerProperties pointerProperties;
+ pointerProperties.clear();
+ pointerProperties.id = 0;
+ pointerProperties.toolType = AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
+
+ PointerCoords pointerCoords;
+ pointerCoords.clear();
+
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ const Axis& axis = mAxes.valueAt(i);
+ setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.axis, axis.currentValue);
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ setPointerCoordsAxisValue(&pointerCoords, axis.axisInfo.highAxis,
+ axis.highCurrentValue);
+ }
+ }
+
+ // Moving a joystick axis should not wake the device because joysticks can
+ // be fairly noisy even when not in use. On the other hand, pushing a gamepad
+ // button will likely wake the device.
+ // TODO: Use the input device configuration to control this behavior more finely.
+ uint32_t policyFlags = 0;
+
+ NotifyMotionArgs args(when, getDeviceId(), AINPUT_SOURCE_JOYSTICK, policyFlags,
+ AMOTION_EVENT_ACTION_MOVE, 0, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+ ADISPLAY_ID_NONE, 1, &pointerProperties, &pointerCoords, 0, 0, 0);
+ getListener()->notifyMotion(&args);
+}
+
+void JoystickInputMapper::setPointerCoordsAxisValue(PointerCoords* pointerCoords,
+ int32_t axis, float value) {
+ pointerCoords->setAxisValue(axis, value);
+ /* In order to ease the transition for developers from using the old axes
+ * to the newer, more semantically correct axes, we'll continue to produce
+ * values for the old axes as mirrors of the value of their corresponding
+ * new axes. */
+ int32_t compatAxis = getCompatAxis(axis);
+ if (compatAxis >= 0) {
+ pointerCoords->setAxisValue(compatAxis, value);
+ }
+}
+
+bool JoystickInputMapper::filterAxes(bool force) {
+ bool atLeastOneSignificantChange = force;
+ size_t numAxes = mAxes.size();
+ for (size_t i = 0; i < numAxes; i++) {
+ Axis& axis = mAxes.editValueAt(i);
+ if (force || hasValueChangedSignificantly(axis.filter,
+ axis.newValue, axis.currentValue, axis.min, axis.max)) {
+ axis.currentValue = axis.newValue;
+ atLeastOneSignificantChange = true;
+ }
+ if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+ if (force || hasValueChangedSignificantly(axis.filter,
+ axis.highNewValue, axis.highCurrentValue, axis.min, axis.max)) {
+ axis.highCurrentValue = axis.highNewValue;
+ atLeastOneSignificantChange = true;
+ }
+ }
+ }
+ return atLeastOneSignificantChange;
+}
+
+bool JoystickInputMapper::hasValueChangedSignificantly(
+ float filter, float newValue, float currentValue, float min, float max) {
+ if (newValue != currentValue) {
+ // Filter out small changes in value unless the value is converging on the axis
+ // bounds or center point. This is intended to reduce the amount of information
+ // sent to applications by particularly noisy joysticks (such as PS3).
+ if (fabs(newValue - currentValue) > filter
+ || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, min)
+ || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, max)
+ || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, 0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool JoystickInputMapper::hasMovedNearerToValueWithinFilteredRange(
+ float filter, float newValue, float currentValue, float thresholdValue) {
+ float newDistance = fabs(newValue - thresholdValue);
+ if (newDistance < filter) {
+ float oldDistance = fabs(currentValue - thresholdValue);
+ if (newDistance < oldDistance) {
+ return true;
+ }
+ }
+ return false;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputReader.h b/services/inputflinger/InputReader.h
new file mode 100644
index 0000000..9e36e35
--- /dev/null
+++ b/services/inputflinger/InputReader.h
@@ -0,0 +1,1857 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_INPUT_READER_H
+#define _UI_INPUT_READER_H
+
+#include "EventHub.h"
+#include "PointerControllerInterface.h"
+#include "InputListener.h"
+
+#include <input/Input.h>
+#include <input/VelocityControl.h>
+#include <input/VelocityTracker.h>
+#include <ui/DisplayInfo.h>
+#include <utils/KeyedVector.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/BitSet.h>
+
+#include <stddef.h>
+#include <unistd.h>
+
+// Maximum supported size of a vibration pattern.
+// Must be at least 2.
+#define MAX_VIBRATE_PATTERN_SIZE 100
+
+// Maximum allowable delay value in a vibration pattern before
+// which the delay will be truncated.
+#define MAX_VIBRATE_PATTERN_DELAY_NSECS (1000000 * 1000000000LL)
+
+namespace android {
+
+class InputDevice;
+class InputMapper;
+
+/*
+ * Describes how coordinates are mapped on a physical display.
+ * See com.android.server.display.DisplayViewport.
+ */
+struct DisplayViewport {
+ int32_t displayId; // -1 if invalid
+ int32_t orientation;
+ int32_t logicalLeft;
+ int32_t logicalTop;
+ int32_t logicalRight;
+ int32_t logicalBottom;
+ int32_t physicalLeft;
+ int32_t physicalTop;
+ int32_t physicalRight;
+ int32_t physicalBottom;
+ int32_t deviceWidth;
+ int32_t deviceHeight;
+
+ DisplayViewport() :
+ displayId(ADISPLAY_ID_NONE), orientation(DISPLAY_ORIENTATION_0),
+ logicalLeft(0), logicalTop(0), logicalRight(0), logicalBottom(0),
+ physicalLeft(0), physicalTop(0), physicalRight(0), physicalBottom(0),
+ deviceWidth(0), deviceHeight(0) {
+ }
+
+ bool operator==(const DisplayViewport& other) const {
+ return displayId == other.displayId
+ && orientation == other.orientation
+ && logicalLeft == other.logicalLeft
+ && logicalTop == other.logicalTop
+ && logicalRight == other.logicalRight
+ && logicalBottom == other.logicalBottom
+ && physicalLeft == other.physicalLeft
+ && physicalTop == other.physicalTop
+ && physicalRight == other.physicalRight
+ && physicalBottom == other.physicalBottom
+ && deviceWidth == other.deviceWidth
+ && deviceHeight == other.deviceHeight;
+ }
+
+ bool operator!=(const DisplayViewport& other) const {
+ return !(*this == other);
+ }
+
+ inline bool isValid() const {
+ return displayId >= 0;
+ }
+
+ void setNonDisplayViewport(int32_t width, int32_t height) {
+ displayId = ADISPLAY_ID_NONE;
+ orientation = DISPLAY_ORIENTATION_0;
+ logicalLeft = 0;
+ logicalTop = 0;
+ logicalRight = width;
+ logicalBottom = height;
+ physicalLeft = 0;
+ physicalTop = 0;
+ physicalRight = width;
+ physicalBottom = height;
+ deviceWidth = width;
+ deviceHeight = height;
+ }
+};
+
+/*
+ * Input reader configuration.
+ *
+ * Specifies various options that modify the behavior of the input reader.
+ */
+struct InputReaderConfiguration {
+ // Describes changes that have occurred.
+ enum {
+ // The pointer speed changed.
+ CHANGE_POINTER_SPEED = 1 << 0,
+
+ // The pointer gesture control changed.
+ CHANGE_POINTER_GESTURE_ENABLEMENT = 1 << 1,
+
+ // The display size or orientation changed.
+ CHANGE_DISPLAY_INFO = 1 << 2,
+
+ // The visible touches option changed.
+ CHANGE_SHOW_TOUCHES = 1 << 3,
+
+ // The keyboard layouts must be reloaded.
+ CHANGE_KEYBOARD_LAYOUTS = 1 << 4,
+
+ // The device name alias supplied by the may have changed for some devices.
+ CHANGE_DEVICE_ALIAS = 1 << 5,
+
+ // The location calibration matrix changed.
+ TOUCH_AFFINE_TRANSFORMATION = 1 << 6,
+
+ // All devices must be reopened.
+ CHANGE_MUST_REOPEN = 1 << 31,
+ };
+
+ // Gets the amount of time to disable virtual keys after the screen is touched
+ // in order to filter out accidental virtual key presses due to swiping gestures
+ // or taps near the edge of the display. May be 0 to disable the feature.
+ nsecs_t virtualKeyQuietTime;
+
+ // The excluded device names for the platform.
+ // Devices with these names will be ignored.
+ Vector<String8> excludedDeviceNames;
+
+ // Velocity control parameters for mouse pointer movements.
+ VelocityControlParameters pointerVelocityControlParameters;
+
+ // Velocity control parameters for mouse wheel movements.
+ VelocityControlParameters wheelVelocityControlParameters;
+
+ // True if pointer gestures are enabled.
+ bool pointerGesturesEnabled;
+
+ // Quiet time between certain pointer gesture transitions.
+ // Time to allow for all fingers or buttons to settle into a stable state before
+ // starting a new gesture.
+ nsecs_t pointerGestureQuietInterval;
+
+ // The minimum speed that a pointer must travel for us to consider switching the active
+ // touch pointer to it during a drag. This threshold is set to avoid switching due
+ // to noise from a finger resting on the touch pad (perhaps just pressing it down).
+ float pointerGestureDragMinSwitchSpeed; // in pixels per second
+
+ // Tap gesture delay time.
+ // The time between down and up must be less than this to be considered a tap.
+ nsecs_t pointerGestureTapInterval;
+
+ // Tap drag gesture delay time.
+ // The time between the previous tap's up and the next down must be less than
+ // this to be considered a drag. Otherwise, the previous tap is finished and a
+ // new tap begins.
+ //
+ // Note that the previous tap will be held down for this entire duration so this
+ // interval must be shorter than the long press timeout.
+ nsecs_t pointerGestureTapDragInterval;
+
+ // The distance in pixels that the pointer is allowed to move from initial down
+ // to up and still be called a tap.
+ float pointerGestureTapSlop; // in pixels
+
+ // Time after the first touch points go down to settle on an initial centroid.
+ // This is intended to be enough time to handle cases where the user puts down two
+ // fingers at almost but not quite exactly the same time.
+ nsecs_t pointerGestureMultitouchSettleInterval;
+
+ // The transition from PRESS to SWIPE or FREEFORM gesture mode is made when
+ // at least two pointers have moved at least this far from their starting place.
+ float pointerGestureMultitouchMinDistance; // in pixels
+
+ // The transition from PRESS to SWIPE gesture mode can only occur when the
+ // cosine of the angle between the two vectors is greater than or equal to than this value
+ // which indicates that the vectors are oriented in the same direction.
+ // When the vectors are oriented in the exactly same direction, the cosine is 1.0.
+ // (In exactly opposite directions, the cosine is -1.0.)
+ float pointerGestureSwipeTransitionAngleCosine;
+
+ // The transition from PRESS to SWIPE gesture mode can only occur when the
+ // fingers are no more than this far apart relative to the diagonal size of
+ // the touch pad. For example, a ratio of 0.5 means that the fingers must be
+ // no more than half the diagonal size of the touch pad apart.
+ float pointerGestureSwipeMaxWidthRatio;
+
+ // The gesture movement speed factor relative to the size of the display.
+ // Movement speed applies when the fingers are moving in the same direction.
+ // Without acceleration, a full swipe of the touch pad diagonal in movement mode
+ // will cover this portion of the display diagonal.
+ float pointerGestureMovementSpeedRatio;
+
+ // The gesture zoom speed factor relative to the size of the display.
+ // Zoom speed applies when the fingers are mostly moving relative to each other
+ // to execute a scale gesture or similar.
+ // Without acceleration, a full swipe of the touch pad diagonal in zoom mode
+ // will cover this portion of the display diagonal.
+ float pointerGestureZoomSpeedRatio;
+
+ // True to show the location of touches on the touch screen as spots.
+ bool showTouches;
+
+ InputReaderConfiguration() :
+ virtualKeyQuietTime(0),
+ pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f, 3.0f),
+ wheelVelocityControlParameters(1.0f, 15.0f, 50.0f, 4.0f),
+ pointerGesturesEnabled(true),
+ pointerGestureQuietInterval(100 * 1000000LL), // 100 ms
+ pointerGestureDragMinSwitchSpeed(50), // 50 pixels per second
+ pointerGestureTapInterval(150 * 1000000LL), // 150 ms
+ pointerGestureTapDragInterval(150 * 1000000LL), // 150 ms
+ pointerGestureTapSlop(10.0f), // 10 pixels
+ pointerGestureMultitouchSettleInterval(100 * 1000000LL), // 100 ms
+ pointerGestureMultitouchMinDistance(15), // 15 pixels
+ pointerGestureSwipeTransitionAngleCosine(0.2588f), // cosine of 75 degrees
+ pointerGestureSwipeMaxWidthRatio(0.25f),
+ pointerGestureMovementSpeedRatio(0.8f),
+ pointerGestureZoomSpeedRatio(0.3f),
+ showTouches(false) { }
+
+ bool getDisplayInfo(bool external, DisplayViewport* outViewport) const;
+ void setDisplayInfo(bool external, const DisplayViewport& viewport);
+
+private:
+ DisplayViewport mInternalDisplay;
+ DisplayViewport mExternalDisplay;
+};
+
+
+struct TouchAffineTransformation {
+ float x_scale;
+ float x_ymix;
+ float x_offset;
+ float y_xmix;
+ float y_scale;
+ float y_offset;
+
+ TouchAffineTransformation() :
+ x_scale(1.0f), x_ymix(0.0f), x_offset(0.0f),
+ y_xmix(0.0f), y_scale(1.0f), y_offset(0.0f) {
+ }
+
+ TouchAffineTransformation(float xscale, float xymix, float xoffset,
+ float yxmix, float yscale, float yoffset) :
+ x_scale(xscale), x_ymix(xymix), x_offset(xoffset),
+ y_xmix(yxmix), y_scale(yscale), y_offset(yoffset) {
+ }
+
+ void applyTo(float& x, float& y) const;
+};
+
+
+/*
+ * Input reader policy interface.
+ *
+ * The input reader policy is used by the input reader to interact with the Window Manager
+ * and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ *
+ * These methods must NOT re-enter the input reader since they may be called while
+ * holding the input reader lock.
+ */
+class InputReaderPolicyInterface : public virtual RefBase {
+protected:
+ InputReaderPolicyInterface() { }
+ virtual ~InputReaderPolicyInterface() { }
+
+public:
+ /* Gets the input reader configuration. */
+ virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) = 0;
+
+ /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
+ virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+
+ /* Notifies the input reader policy that some input devices have changed
+ * and provides information about all current input devices.
+ */
+ virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) = 0;
+
+ /* Gets the keyboard layout for a particular input device. */
+ virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(
+ const InputDeviceIdentifier& identifier) = 0;
+
+ /* Gets a user-supplied alias for a particular input device, or an empty string if none. */
+ virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier) = 0;
+
+ /* Gets the affine calibration associated with the specified device. */
+ virtual TouchAffineTransformation getTouchAffineTransformation(
+ const String8& inputDeviceDescriptor, int32_t surfaceRotation) = 0;
+};
+
+
+/* Processes raw input events and sends cooked event data to an input listener. */
+class InputReaderInterface : public virtual RefBase {
+protected:
+ InputReaderInterface() { }
+ virtual ~InputReaderInterface() { }
+
+public:
+ /* Dumps the state of the input reader.
+ *
+ * This method may be called on any thread (usually by the input manager). */
+ virtual void dump(String8& dump) = 0;
+
+ /* Called by the heatbeat to ensures that the reader has not deadlocked. */
+ virtual void monitor() = 0;
+
+ /* Runs a single iteration of the processing loop.
+ * Nominally reads and processes one incoming message from the EventHub.
+ *
+ * This method should be called on the input reader thread.
+ */
+ virtual void loopOnce() = 0;
+
+ /* Gets information about all input devices.
+ *
+ * This method may be called on any thread (usually by the input manager).
+ */
+ virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices) = 0;
+
+ /* Query current input state. */
+ virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode) = 0;
+ virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode) = 0;
+ virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+ int32_t sw) = 0;
+
+ /* Determine whether physical keys exist for the given framework-domain key codes. */
+ virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) = 0;
+
+ /* Requests that a reconfiguration of all input devices.
+ * The changes flag is a bitfield that indicates what has changed and whether
+ * the input devices must all be reopened. */
+ virtual void requestRefreshConfiguration(uint32_t changes) = 0;
+
+ /* Controls the vibrator of a particular input device. */
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) = 0;
+ virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0;
+};
+
+
+/* Internal interface used by individual input devices to access global input device state
+ * and parameters maintained by the input reader.
+ */
+class InputReaderContext {
+public:
+ InputReaderContext() { }
+ virtual ~InputReaderContext() { }
+
+ virtual void updateGlobalMetaState() = 0;
+ virtual int32_t getGlobalMetaState() = 0;
+
+ virtual void disableVirtualKeysUntil(nsecs_t time) = 0;
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) = 0;
+
+ virtual void fadePointer() = 0;
+
+ virtual void requestTimeoutAtTime(nsecs_t when) = 0;
+ virtual int32_t bumpGeneration() = 0;
+
+ virtual InputReaderPolicyInterface* getPolicy() = 0;
+ virtual InputListenerInterface* getListener() = 0;
+ virtual EventHubInterface* getEventHub() = 0;
+};
+
+
+/* The input reader reads raw event data from the event hub and processes it into input events
+ * that it sends to the input listener. Some functions of the input reader, such as early
+ * event filtering in low power states, are controlled by a separate policy object.
+ *
+ * The InputReader owns a collection of InputMappers. Most of the work it does happens
+ * on the input reader thread but the InputReader can receive queries from other system
+ * components running on arbitrary threads. To keep things manageable, the InputReader
+ * uses a single Mutex to guard its state. The Mutex may be held while calling into the
+ * EventHub or the InputReaderPolicy but it is never held while calling into the
+ * InputListener.
+ */
+class InputReader : public InputReaderInterface {
+public:
+ InputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputListenerInterface>& listener);
+ virtual ~InputReader();
+
+ virtual void dump(String8& dump);
+ virtual void monitor();
+
+ virtual void loopOnce();
+
+ virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices);
+
+ virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t scanCode);
+ virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
+ int32_t keyCode);
+ virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask,
+ int32_t sw);
+
+ virtual bool hasKeys(int32_t deviceId, uint32_t sourceMask,
+ size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual void requestRefreshConfiguration(uint32_t changes);
+
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token);
+ virtual void cancelVibrate(int32_t deviceId, int32_t token);
+
+protected:
+ // These members are protected so they can be instrumented by test cases.
+ virtual InputDevice* createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
+ const InputDeviceIdentifier& identifier, uint32_t classes);
+
+ class ContextImpl : public InputReaderContext {
+ InputReader* mReader;
+
+ public:
+ ContextImpl(InputReader* reader);
+
+ virtual void updateGlobalMetaState();
+ virtual int32_t getGlobalMetaState();
+ virtual void disableVirtualKeysUntil(nsecs_t time);
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode);
+ virtual void fadePointer();
+ virtual void requestTimeoutAtTime(nsecs_t when);
+ virtual int32_t bumpGeneration();
+ virtual InputReaderPolicyInterface* getPolicy();
+ virtual InputListenerInterface* getListener();
+ virtual EventHubInterface* getEventHub();
+ } mContext;
+
+ friend class ContextImpl;
+
+private:
+ Mutex mLock;
+
+ Condition mReaderIsAliveCondition;
+
+ sp<EventHubInterface> mEventHub;
+ sp<InputReaderPolicyInterface> mPolicy;
+ sp<QueuedInputListener> mQueuedListener;
+
+ InputReaderConfiguration mConfig;
+
+ // The event queue.
+ static const int EVENT_BUFFER_SIZE = 256;
+ RawEvent mEventBuffer[EVENT_BUFFER_SIZE];
+
+ KeyedVector<int32_t, InputDevice*> mDevices;
+
+ // low-level input event decoding and device management
+ void processEventsLocked(const RawEvent* rawEvents, size_t count);
+
+ void addDeviceLocked(nsecs_t when, int32_t deviceId);
+ void removeDeviceLocked(nsecs_t when, int32_t deviceId);
+ void processEventsForDeviceLocked(int32_t deviceId, const RawEvent* rawEvents, size_t count);
+ void timeoutExpiredLocked(nsecs_t when);
+
+ void handleConfigurationChangedLocked(nsecs_t when);
+
+ int32_t mGlobalMetaState;
+ void updateGlobalMetaStateLocked();
+ int32_t getGlobalMetaStateLocked();
+
+ void fadePointerLocked();
+
+ int32_t mGeneration;
+ int32_t bumpGenerationLocked();
+
+ void getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices);
+
+ nsecs_t mDisableVirtualKeysTimeout;
+ void disableVirtualKeysUntilLocked(nsecs_t time);
+ bool shouldDropVirtualKeyLocked(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode);
+
+ nsecs_t mNextTimeout;
+ void requestTimeoutAtTimeLocked(nsecs_t when);
+
+ uint32_t mConfigurationChangesToRefresh;
+ void refreshConfigurationLocked(uint32_t changes);
+
+ // state queries
+ typedef int32_t (InputDevice::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+ int32_t getStateLocked(int32_t deviceId, uint32_t sourceMask, int32_t code,
+ GetStateFunc getStateFunc);
+ bool markSupportedKeyCodesLocked(int32_t deviceId, uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+};
+
+
+/* Reads raw events from the event hub and processes them, endlessly. */
+class InputReaderThread : public Thread {
+public:
+ InputReaderThread(const sp<InputReaderInterface>& reader);
+ virtual ~InputReaderThread();
+
+private:
+ sp<InputReaderInterface> mReader;
+
+ virtual bool threadLoop();
+};
+
+
+/* Represents the state of a single input device. */
+class InputDevice {
+public:
+ InputDevice(InputReaderContext* context, int32_t id, int32_t generation, int32_t
+ controllerNumber, const InputDeviceIdentifier& identifier, uint32_t classes);
+ ~InputDevice();
+
+ inline InputReaderContext* getContext() { return mContext; }
+ inline int32_t getId() const { return mId; }
+ inline int32_t getControllerNumber() const { return mControllerNumber; }
+ inline int32_t getGeneration() const { return mGeneration; }
+ inline const String8& getName() const { return mIdentifier.name; }
+ inline const String8& getDescriptor() { return mIdentifier.descriptor; }
+ inline uint32_t getClasses() const { return mClasses; }
+ inline uint32_t getSources() const { return mSources; }
+
+ inline bool isExternal() { return mIsExternal; }
+ inline void setExternal(bool external) { mIsExternal = external; }
+
+ inline bool isIgnored() { return mMappers.isEmpty(); }
+
+ void dump(String8& dump);
+ void addMapper(InputMapper* mapper);
+ void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ void reset(nsecs_t when);
+ void process(const RawEvent* rawEvents, size_t count);
+ void timeoutExpired(nsecs_t when);
+
+ void getDeviceInfo(InputDeviceInfo* outDeviceInfo);
+ int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+ bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+ void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, int32_t token);
+ void cancelVibrate(int32_t token);
+
+ int32_t getMetaState();
+
+ void fadePointer();
+
+ void bumpGeneration();
+
+ void notifyReset(nsecs_t when);
+
+ inline const PropertyMap& getConfiguration() { return mConfiguration; }
+ inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+ bool hasKey(int32_t code) {
+ return getEventHub()->hasScanCode(mId, code);
+ }
+
+ bool hasAbsoluteAxis(int32_t code) {
+ RawAbsoluteAxisInfo info;
+ getEventHub()->getAbsoluteAxisInfo(mId, code, &info);
+ return info.valid;
+ }
+
+ bool isKeyPressed(int32_t code) {
+ return getEventHub()->getScanCodeState(mId, code) == AKEY_STATE_DOWN;
+ }
+
+ int32_t getAbsoluteAxisValue(int32_t code) {
+ int32_t value;
+ getEventHub()->getAbsoluteAxisValue(mId, code, &value);
+ return value;
+ }
+
+private:
+ InputReaderContext* mContext;
+ int32_t mId;
+ int32_t mGeneration;
+ int32_t mControllerNumber;
+ InputDeviceIdentifier mIdentifier;
+ String8 mAlias;
+ uint32_t mClasses;
+
+ Vector<InputMapper*> mMappers;
+
+ uint32_t mSources;
+ bool mIsExternal;
+ bool mDropUntilNextSync;
+
+ typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
+ int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
+
+ PropertyMap mConfiguration;
+};
+
+
+/* Keeps track of the state of mouse or touch pad buttons. */
+class CursorButtonAccumulator {
+public:
+ CursorButtonAccumulator();
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+
+ uint32_t getButtonState() const;
+
+private:
+ bool mBtnLeft;
+ bool mBtnRight;
+ bool mBtnMiddle;
+ bool mBtnBack;
+ bool mBtnSide;
+ bool mBtnForward;
+ bool mBtnExtra;
+ bool mBtnTask;
+
+ void clearButtons();
+};
+
+
+/* Keeps track of cursor movements. */
+
+class CursorMotionAccumulator {
+public:
+ CursorMotionAccumulator();
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+
+ inline int32_t getRelativeX() const { return mRelX; }
+ inline int32_t getRelativeY() const { return mRelY; }
+
+private:
+ int32_t mRelX;
+ int32_t mRelY;
+
+ void clearRelativeAxes();
+};
+
+
+/* Keeps track of cursor scrolling motions. */
+
+class CursorScrollAccumulator {
+public:
+ CursorScrollAccumulator();
+ void configure(InputDevice* device);
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+
+ inline bool haveRelativeVWheel() const { return mHaveRelWheel; }
+ inline bool haveRelativeHWheel() const { return mHaveRelHWheel; }
+
+ inline int32_t getRelativeX() const { return mRelX; }
+ inline int32_t getRelativeY() const { return mRelY; }
+ inline int32_t getRelativeVWheel() const { return mRelWheel; }
+ inline int32_t getRelativeHWheel() const { return mRelHWheel; }
+
+private:
+ bool mHaveRelWheel;
+ bool mHaveRelHWheel;
+
+ int32_t mRelX;
+ int32_t mRelY;
+ int32_t mRelWheel;
+ int32_t mRelHWheel;
+
+ void clearRelativeAxes();
+};
+
+
+/* Keeps track of the state of touch, stylus and tool buttons. */
+class TouchButtonAccumulator {
+public:
+ TouchButtonAccumulator();
+ void configure(InputDevice* device);
+ void reset(InputDevice* device);
+
+ void process(const RawEvent* rawEvent);
+
+ uint32_t getButtonState() const;
+ int32_t getToolType() const;
+ bool isToolActive() const;
+ bool isHovering() const;
+ bool hasStylus() const;
+
+private:
+ bool mHaveBtnTouch;
+ bool mHaveStylus;
+
+ bool mBtnTouch;
+ bool mBtnStylus;
+ bool mBtnStylus2;
+ bool mBtnToolFinger;
+ bool mBtnToolPen;
+ bool mBtnToolRubber;
+ bool mBtnToolBrush;
+ bool mBtnToolPencil;
+ bool mBtnToolAirbrush;
+ bool mBtnToolMouse;
+ bool mBtnToolLens;
+ bool mBtnToolDoubleTap;
+ bool mBtnToolTripleTap;
+ bool mBtnToolQuadTap;
+
+ void clearButtons();
+};
+
+
+/* Raw axis information from the driver. */
+struct RawPointerAxes {
+ RawAbsoluteAxisInfo x;
+ RawAbsoluteAxisInfo y;
+ RawAbsoluteAxisInfo pressure;
+ RawAbsoluteAxisInfo touchMajor;
+ RawAbsoluteAxisInfo touchMinor;
+ RawAbsoluteAxisInfo toolMajor;
+ RawAbsoluteAxisInfo toolMinor;
+ RawAbsoluteAxisInfo orientation;
+ RawAbsoluteAxisInfo distance;
+ RawAbsoluteAxisInfo tiltX;
+ RawAbsoluteAxisInfo tiltY;
+ RawAbsoluteAxisInfo trackingId;
+ RawAbsoluteAxisInfo slot;
+
+ RawPointerAxes();
+ void clear();
+};
+
+
+/* Raw data for a collection of pointers including a pointer id mapping table. */
+struct RawPointerData {
+ struct Pointer {
+ uint32_t id;
+ int32_t x;
+ int32_t y;
+ int32_t pressure;
+ int32_t touchMajor;
+ int32_t touchMinor;
+ int32_t toolMajor;
+ int32_t toolMinor;
+ int32_t orientation;
+ int32_t distance;
+ int32_t tiltX;
+ int32_t tiltY;
+ int32_t toolType; // a fully decoded AMOTION_EVENT_TOOL_TYPE constant
+ bool isHovering;
+ };
+
+ uint32_t pointerCount;
+ Pointer pointers[MAX_POINTERS];
+ BitSet32 hoveringIdBits, touchingIdBits;
+ uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+ RawPointerData();
+ void clear();
+ void copyFrom(const RawPointerData& other);
+ void getCentroidOfTouchingPointers(float* outX, float* outY) const;
+
+ inline void markIdBit(uint32_t id, bool isHovering) {
+ if (isHovering) {
+ hoveringIdBits.markBit(id);
+ } else {
+ touchingIdBits.markBit(id);
+ }
+ }
+
+ inline void clearIdBits() {
+ hoveringIdBits.clear();
+ touchingIdBits.clear();
+ }
+
+ inline const Pointer& pointerForId(uint32_t id) const {
+ return pointers[idToIndex[id]];
+ }
+
+ inline bool isHovering(uint32_t pointerIndex) {
+ return pointers[pointerIndex].isHovering;
+ }
+};
+
+
+/* Cooked data for a collection of pointers including a pointer id mapping table. */
+struct CookedPointerData {
+ uint32_t pointerCount;
+ PointerProperties pointerProperties[MAX_POINTERS];
+ PointerCoords pointerCoords[MAX_POINTERS];
+ BitSet32 hoveringIdBits, touchingIdBits;
+ uint32_t idToIndex[MAX_POINTER_ID + 1];
+
+ CookedPointerData();
+ void clear();
+ void copyFrom(const CookedPointerData& other);
+
+ inline const PointerCoords& pointerCoordsForId(uint32_t id) const {
+ return pointerCoords[idToIndex[id]];
+ }
+
+ inline bool isHovering(uint32_t pointerIndex) {
+ return hoveringIdBits.hasBit(pointerProperties[pointerIndex].id);
+ }
+};
+
+
+/* Keeps track of the state of single-touch protocol. */
+class SingleTouchMotionAccumulator {
+public:
+ SingleTouchMotionAccumulator();
+
+ void process(const RawEvent* rawEvent);
+ void reset(InputDevice* device);
+
+ inline int32_t getAbsoluteX() const { return mAbsX; }
+ inline int32_t getAbsoluteY() const { return mAbsY; }
+ inline int32_t getAbsolutePressure() const { return mAbsPressure; }
+ inline int32_t getAbsoluteToolWidth() const { return mAbsToolWidth; }
+ inline int32_t getAbsoluteDistance() const { return mAbsDistance; }
+ inline int32_t getAbsoluteTiltX() const { return mAbsTiltX; }
+ inline int32_t getAbsoluteTiltY() const { return mAbsTiltY; }
+
+private:
+ int32_t mAbsX;
+ int32_t mAbsY;
+ int32_t mAbsPressure;
+ int32_t mAbsToolWidth;
+ int32_t mAbsDistance;
+ int32_t mAbsTiltX;
+ int32_t mAbsTiltY;
+
+ void clearAbsoluteAxes();
+};
+
+
+/* Keeps track of the state of multi-touch protocol. */
+class MultiTouchMotionAccumulator {
+public:
+ class Slot {
+ public:
+ inline bool isInUse() const { return mInUse; }
+ inline int32_t getX() const { return mAbsMTPositionX; }
+ inline int32_t getY() const { return mAbsMTPositionY; }
+ inline int32_t getTouchMajor() const { return mAbsMTTouchMajor; }
+ inline int32_t getTouchMinor() const {
+ return mHaveAbsMTTouchMinor ? mAbsMTTouchMinor : mAbsMTTouchMajor; }
+ inline int32_t getToolMajor() const { return mAbsMTWidthMajor; }
+ inline int32_t getToolMinor() const {
+ return mHaveAbsMTWidthMinor ? mAbsMTWidthMinor : mAbsMTWidthMajor; }
+ inline int32_t getOrientation() const { return mAbsMTOrientation; }
+ inline int32_t getTrackingId() const { return mAbsMTTrackingId; }
+ inline int32_t getPressure() const { return mAbsMTPressure; }
+ inline int32_t getDistance() const { return mAbsMTDistance; }
+ inline int32_t getToolType() const;
+
+ private:
+ friend class MultiTouchMotionAccumulator;
+
+ bool mInUse;
+ bool mHaveAbsMTTouchMinor;
+ bool mHaveAbsMTWidthMinor;
+ bool mHaveAbsMTToolType;
+
+ int32_t mAbsMTPositionX;
+ int32_t mAbsMTPositionY;
+ int32_t mAbsMTTouchMajor;
+ int32_t mAbsMTTouchMinor;
+ int32_t mAbsMTWidthMajor;
+ int32_t mAbsMTWidthMinor;
+ int32_t mAbsMTOrientation;
+ int32_t mAbsMTTrackingId;
+ int32_t mAbsMTPressure;
+ int32_t mAbsMTDistance;
+ int32_t mAbsMTToolType;
+
+ Slot();
+ void clear();
+ };
+
+ MultiTouchMotionAccumulator();
+ ~MultiTouchMotionAccumulator();
+
+ void configure(InputDevice* device, size_t slotCount, bool usingSlotsProtocol);
+ void reset(InputDevice* device);
+ void process(const RawEvent* rawEvent);
+ void finishSync();
+ bool hasStylus() const;
+
+ inline size_t getSlotCount() const { return mSlotCount; }
+ inline const Slot* getSlot(size_t index) const { return &mSlots[index]; }
+
+private:
+ int32_t mCurrentSlot;
+ Slot* mSlots;
+ size_t mSlotCount;
+ bool mUsingSlotsProtocol;
+ bool mHaveStylus;
+
+ void clearSlots(int32_t initialSlot);
+};
+
+
+/* An input mapper transforms raw input events into cooked event data.
+ * A single input device can have multiple associated input mappers in order to interpret
+ * different classes of events.
+ *
+ * InputMapper lifecycle:
+ * - create
+ * - configure with 0 changes
+ * - reset
+ * - process, process, process (may occasionally reconfigure with non-zero changes or reset)
+ * - reset
+ * - destroy
+ */
+class InputMapper {
+public:
+ InputMapper(InputDevice* device);
+ virtual ~InputMapper();
+
+ inline InputDevice* getDevice() { return mDevice; }
+ inline int32_t getDeviceId() { return mDevice->getId(); }
+ inline const String8 getDeviceName() { return mDevice->getName(); }
+ inline InputReaderContext* getContext() { return mContext; }
+ inline InputReaderPolicyInterface* getPolicy() { return mContext->getPolicy(); }
+ inline InputListenerInterface* getListener() { return mContext->getListener(); }
+ inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
+
+ virtual uint32_t getSources() = 0;
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent) = 0;
+ virtual void timeoutExpired(nsecs_t when);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
+
+ virtual int32_t getMetaState();
+
+ virtual void fadePointer();
+
+protected:
+ InputDevice* mDevice;
+ InputReaderContext* mContext;
+
+ status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
+ void bumpGeneration();
+
+ static void dumpRawAbsoluteAxisInfo(String8& dump,
+ const RawAbsoluteAxisInfo& axis, const char* name);
+};
+
+
+class SwitchInputMapper : public InputMapper {
+public:
+ SwitchInputMapper(InputDevice* device);
+ virtual ~SwitchInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
+
+private:
+ uint32_t mUpdatedSwitchValues;
+ uint32_t mUpdatedSwitchMask;
+
+ void processSwitch(int32_t switchCode, int32_t switchValue);
+ void sync(nsecs_t when);
+};
+
+
+class VibratorInputMapper : public InputMapper {
+public:
+ VibratorInputMapper(InputDevice* device);
+ virtual ~VibratorInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
+ virtual void timeoutExpired(nsecs_t when);
+ virtual void dump(String8& dump);
+
+private:
+ bool mVibrating;
+ nsecs_t mPattern[MAX_VIBRATE_PATTERN_SIZE];
+ size_t mPatternSize;
+ ssize_t mRepeat;
+ int32_t mToken;
+ ssize_t mIndex;
+ nsecs_t mNextStepTime;
+
+ void nextStep();
+ void stopVibrating();
+};
+
+
+class KeyboardInputMapper : public InputMapper {
+public:
+ KeyboardInputMapper(InputDevice* device, uint32_t source, int32_t keyboardType);
+ virtual ~KeyboardInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual int32_t getMetaState();
+
+private:
+ struct KeyDown {
+ int32_t keyCode;
+ int32_t scanCode;
+ };
+
+ uint32_t mSource;
+ int32_t mKeyboardType;
+
+ int32_t mOrientation; // orientation for dpad keys
+
+ Vector<KeyDown> mKeyDowns; // keys that are down
+ int32_t mMetaState;
+ nsecs_t mDownTime; // time of most recent key down
+
+ int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none
+
+ struct LedState {
+ bool avail; // led is available
+ bool on; // we think the led is currently on
+ };
+ LedState mCapsLockLedState;
+ LedState mNumLockLedState;
+ LedState mScrollLockLedState;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ bool hasAssociatedDisplay;
+ bool orientationAware;
+ bool handlesKeyRepeat;
+ } mParameters;
+
+ void configureParameters();
+ void dumpParameters(String8& dump);
+
+ bool isKeyboardOrGamepadKey(int32_t scanCode);
+
+ void processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode,
+ uint32_t policyFlags);
+
+ ssize_t findKeyDown(int32_t scanCode);
+
+ void resetLedState();
+ void initializeLedState(LedState& ledState, int32_t led);
+ void updateLedState(bool reset);
+ void updateLedStateForModifier(LedState& ledState, int32_t led,
+ int32_t modifier, bool reset);
+};
+
+
+class CursorInputMapper : public InputMapper {
+public:
+ CursorInputMapper(InputDevice* device);
+ virtual ~CursorInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+
+ virtual void fadePointer();
+
+private:
+ // Amount that trackball needs to move in order to generate a key event.
+ static const int32_t TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ enum Mode {
+ MODE_POINTER,
+ MODE_NAVIGATION,
+ };
+
+ Mode mode;
+ bool hasAssociatedDisplay;
+ bool orientationAware;
+ } mParameters;
+
+ CursorButtonAccumulator mCursorButtonAccumulator;
+ CursorMotionAccumulator mCursorMotionAccumulator;
+ CursorScrollAccumulator mCursorScrollAccumulator;
+
+ int32_t mSource;
+ float mXScale;
+ float mYScale;
+ float mXPrecision;
+ float mYPrecision;
+
+ float mVWheelScale;
+ float mHWheelScale;
+
+ // Velocity controls for mouse pointer and wheel movements.
+ // The controls for X and Y wheel movements are separate to keep them decoupled.
+ VelocityControl mPointerVelocityControl;
+ VelocityControl mWheelXVelocityControl;
+ VelocityControl mWheelYVelocityControl;
+
+ int32_t mOrientation;
+
+ sp<PointerControllerInterface> mPointerController;
+
+ int32_t mButtonState;
+ nsecs_t mDownTime;
+
+ void configureParameters();
+ void dumpParameters(String8& dump);
+
+ void sync(nsecs_t when);
+};
+
+
+class TouchInputMapper : public InputMapper {
+public:
+ TouchInputMapper(InputDevice* device);
+ virtual ~TouchInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode);
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags);
+
+ virtual void fadePointer();
+ virtual void timeoutExpired(nsecs_t when);
+
+protected:
+ CursorButtonAccumulator mCursorButtonAccumulator;
+ CursorScrollAccumulator mCursorScrollAccumulator;
+ TouchButtonAccumulator mTouchButtonAccumulator;
+
+ struct VirtualKey {
+ int32_t keyCode;
+ int32_t scanCode;
+ uint32_t flags;
+
+ // computed hit box, specified in touch screen coords based on known display size
+ int32_t hitLeft;
+ int32_t hitTop;
+ int32_t hitRight;
+ int32_t hitBottom;
+
+ inline bool isHit(int32_t x, int32_t y) const {
+ return x >= hitLeft && x <= hitRight && y >= hitTop && y <= hitBottom;
+ }
+ };
+
+ // Input sources and device mode.
+ uint32_t mSource;
+
+ enum DeviceMode {
+ DEVICE_MODE_DISABLED, // input is disabled
+ DEVICE_MODE_DIRECT, // direct mapping (touchscreen)
+ DEVICE_MODE_UNSCALED, // unscaled mapping (touchpad)
+ DEVICE_MODE_NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
+ DEVICE_MODE_POINTER, // pointer mapping (pointer)
+ };
+ DeviceMode mDeviceMode;
+
+ // The reader's configuration.
+ InputReaderConfiguration mConfig;
+
+ // Immutable configuration parameters.
+ struct Parameters {
+ enum DeviceType {
+ DEVICE_TYPE_TOUCH_SCREEN,
+ DEVICE_TYPE_TOUCH_PAD,
+ DEVICE_TYPE_TOUCH_NAVIGATION,
+ DEVICE_TYPE_POINTER,
+ };
+
+ DeviceType deviceType;
+ bool hasAssociatedDisplay;
+ bool associatedDisplayIsExternal;
+ bool orientationAware;
+ bool hasButtonUnderPad;
+
+ enum GestureMode {
+ GESTURE_MODE_POINTER,
+ GESTURE_MODE_SPOTS,
+ };
+ GestureMode gestureMode;
+
+ bool wake;
+ } mParameters;
+
+ // Immutable calibration parameters in parsed form.
+ struct Calibration {
+ // Size
+ enum SizeCalibration {
+ SIZE_CALIBRATION_DEFAULT,
+ SIZE_CALIBRATION_NONE,
+ SIZE_CALIBRATION_GEOMETRIC,
+ SIZE_CALIBRATION_DIAMETER,
+ SIZE_CALIBRATION_BOX,
+ SIZE_CALIBRATION_AREA,
+ };
+
+ SizeCalibration sizeCalibration;
+
+ bool haveSizeScale;
+ float sizeScale;
+ bool haveSizeBias;
+ float sizeBias;
+ bool haveSizeIsSummed;
+ bool sizeIsSummed;
+
+ // Pressure
+ enum PressureCalibration {
+ PRESSURE_CALIBRATION_DEFAULT,
+ PRESSURE_CALIBRATION_NONE,
+ PRESSURE_CALIBRATION_PHYSICAL,
+ PRESSURE_CALIBRATION_AMPLITUDE,
+ };
+
+ PressureCalibration pressureCalibration;
+ bool havePressureScale;
+ float pressureScale;
+
+ // Orientation
+ enum OrientationCalibration {
+ ORIENTATION_CALIBRATION_DEFAULT,
+ ORIENTATION_CALIBRATION_NONE,
+ ORIENTATION_CALIBRATION_INTERPOLATED,
+ ORIENTATION_CALIBRATION_VECTOR,
+ };
+
+ OrientationCalibration orientationCalibration;
+
+ // Distance
+ enum DistanceCalibration {
+ DISTANCE_CALIBRATION_DEFAULT,
+ DISTANCE_CALIBRATION_NONE,
+ DISTANCE_CALIBRATION_SCALED,
+ };
+
+ DistanceCalibration distanceCalibration;
+ bool haveDistanceScale;
+ float distanceScale;
+
+ enum CoverageCalibration {
+ COVERAGE_CALIBRATION_DEFAULT,
+ COVERAGE_CALIBRATION_NONE,
+ COVERAGE_CALIBRATION_BOX,
+ };
+
+ CoverageCalibration coverageCalibration;
+
+ inline void applySizeScaleAndBias(float* outSize) const {
+ if (haveSizeScale) {
+ *outSize *= sizeScale;
+ }
+ if (haveSizeBias) {
+ *outSize += sizeBias;
+ }
+ if (*outSize < 0) {
+ *outSize = 0;
+ }
+ }
+ } mCalibration;
+
+ // Affine location transformation/calibration
+ struct TouchAffineTransformation mAffineTransform;
+
+ // Raw pointer axis information from the driver.
+ RawPointerAxes mRawPointerAxes;
+
+ // Raw pointer sample data.
+ RawPointerData mCurrentRawPointerData;
+ RawPointerData mLastRawPointerData;
+
+ // Cooked pointer sample data.
+ CookedPointerData mCurrentCookedPointerData;
+ CookedPointerData mLastCookedPointerData;
+
+ // Button state.
+ int32_t mCurrentButtonState;
+ int32_t mLastButtonState;
+
+ // Scroll state.
+ int32_t mCurrentRawVScroll;
+ int32_t mCurrentRawHScroll;
+
+ // Id bits used to differentiate fingers, stylus and mouse tools.
+ BitSet32 mCurrentFingerIdBits; // finger or unknown
+ BitSet32 mLastFingerIdBits;
+ BitSet32 mCurrentStylusIdBits; // stylus or eraser
+ BitSet32 mLastStylusIdBits;
+ BitSet32 mCurrentMouseIdBits; // mouse or lens
+ BitSet32 mLastMouseIdBits;
+
+ // True if we sent a HOVER_ENTER event.
+ bool mSentHoverEnter;
+
+ // The time the primary pointer last went down.
+ nsecs_t mDownTime;
+
+ // The pointer controller, or null if the device is not a pointer.
+ sp<PointerControllerInterface> mPointerController;
+
+ Vector<VirtualKey> mVirtualKeys;
+
+ virtual void configureParameters();
+ virtual void dumpParameters(String8& dump);
+ virtual void configureRawPointerAxes();
+ virtual void dumpRawPointerAxes(String8& dump);
+ virtual void configureSurface(nsecs_t when, bool* outResetNeeded);
+ virtual void dumpSurface(String8& dump);
+ virtual void configureVirtualKeys();
+ virtual void dumpVirtualKeys(String8& dump);
+ virtual void parseCalibration();
+ virtual void resolveCalibration();
+ virtual void dumpCalibration(String8& dump);
+ virtual void dumpAffineTransformation(String8& dump);
+ virtual bool hasStylus() const = 0;
+ virtual void updateAffineTransformation();
+
+ virtual void syncTouch(nsecs_t when, bool* outHavePointerIds) = 0;
+
+private:
+ // The current viewport.
+ // The components of the viewport are specified in the display's rotated orientation.
+ DisplayViewport mViewport;
+
+ // The surface orientation, width and height set by configureSurface().
+ // The width and height are derived from the viewport but are specified
+ // in the natural orientation.
+ // The surface origin specifies how the surface coordinates should be translated
+ // to align with the logical display coordinate space.
+ // The orientation may be different from the viewport orientation as it specifies
+ // the rotation of the surface coordinates required to produce the viewport's
+ // requested orientation, so it will depend on whether the device is orientation aware.
+ int32_t mSurfaceWidth;
+ int32_t mSurfaceHeight;
+ int32_t mSurfaceLeft;
+ int32_t mSurfaceTop;
+ int32_t mSurfaceOrientation;
+
+ // Translation and scaling factors, orientation-independent.
+ float mXTranslate;
+ float mXScale;
+ float mXPrecision;
+
+ float mYTranslate;
+ float mYScale;
+ float mYPrecision;
+
+ float mGeometricScale;
+
+ float mPressureScale;
+
+ float mSizeScale;
+
+ float mOrientationScale;
+
+ float mDistanceScale;
+
+ bool mHaveTilt;
+ float mTiltXCenter;
+ float mTiltXScale;
+ float mTiltYCenter;
+ float mTiltYScale;
+
+ // Oriented motion ranges for input device info.
+ struct OrientedRanges {
+ InputDeviceInfo::MotionRange x;
+ InputDeviceInfo::MotionRange y;
+ InputDeviceInfo::MotionRange pressure;
+
+ bool haveSize;
+ InputDeviceInfo::MotionRange size;
+
+ bool haveTouchSize;
+ InputDeviceInfo::MotionRange touchMajor;
+ InputDeviceInfo::MotionRange touchMinor;
+
+ bool haveToolSize;
+ InputDeviceInfo::MotionRange toolMajor;
+ InputDeviceInfo::MotionRange toolMinor;
+
+ bool haveOrientation;
+ InputDeviceInfo::MotionRange orientation;
+
+ bool haveDistance;
+ InputDeviceInfo::MotionRange distance;
+
+ bool haveTilt;
+ InputDeviceInfo::MotionRange tilt;
+
+ OrientedRanges() {
+ clear();
+ }
+
+ void clear() {
+ haveSize = false;
+ haveTouchSize = false;
+ haveToolSize = false;
+ haveOrientation = false;
+ haveDistance = false;
+ haveTilt = false;
+ }
+ } mOrientedRanges;
+
+ // Oriented dimensions and precision.
+ float mOrientedXPrecision;
+ float mOrientedYPrecision;
+
+ struct CurrentVirtualKeyState {
+ bool down;
+ bool ignored;
+ nsecs_t downTime;
+ int32_t keyCode;
+ int32_t scanCode;
+ } mCurrentVirtualKey;
+
+ // Scale factor for gesture or mouse based pointer movements.
+ float mPointerXMovementScale;
+ float mPointerYMovementScale;
+
+ // Scale factor for gesture based zooming and other freeform motions.
+ float mPointerXZoomScale;
+ float mPointerYZoomScale;
+
+ // The maximum swipe width.
+ float mPointerGestureMaxSwipeWidth;
+
+ struct PointerDistanceHeapElement {
+ uint32_t currentPointerIndex : 8;
+ uint32_t lastPointerIndex : 8;
+ uint64_t distance : 48; // squared distance
+ };
+
+ enum PointerUsage {
+ POINTER_USAGE_NONE,
+ POINTER_USAGE_GESTURES,
+ POINTER_USAGE_STYLUS,
+ POINTER_USAGE_MOUSE,
+ };
+ PointerUsage mPointerUsage;
+
+ struct PointerGesture {
+ enum Mode {
+ // No fingers, button is not pressed.
+ // Nothing happening.
+ NEUTRAL,
+
+ // No fingers, button is not pressed.
+ // Tap detected.
+ // Emits DOWN and UP events at the pointer location.
+ TAP,
+
+ // Exactly one finger dragging following a tap.
+ // Pointer follows the active finger.
+ // Emits DOWN, MOVE and UP events at the pointer location.
+ //
+ // Detect double-taps when the finger goes up while in TAP_DRAG mode.
+ TAP_DRAG,
+
+ // Button is pressed.
+ // Pointer follows the active finger if there is one. Other fingers are ignored.
+ // Emits DOWN, MOVE and UP events at the pointer location.
+ BUTTON_CLICK_OR_DRAG,
+
+ // Exactly one finger, button is not pressed.
+ // Pointer follows the active finger.
+ // Emits HOVER_MOVE events at the pointer location.
+ //
+ // Detect taps when the finger goes up while in HOVER mode.
+ HOVER,
+
+ // Exactly two fingers but neither have moved enough to clearly indicate
+ // whether a swipe or freeform gesture was intended. We consider the
+ // pointer to be pressed so this enables clicking or long-pressing on buttons.
+ // Pointer does not move.
+ // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate.
+ PRESS,
+
+ // Exactly two fingers moving in the same direction, button is not pressed.
+ // Pointer does not move.
+ // Emits DOWN, MOVE and UP events with a single pointer coordinate that
+ // follows the midpoint between both fingers.
+ SWIPE,
+
+ // Two or more fingers moving in arbitrary directions, button is not pressed.
+ // Pointer does not move.
+ // Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow
+ // each finger individually relative to the initial centroid of the finger.
+ FREEFORM,
+
+ // Waiting for quiet time to end before starting the next gesture.
+ QUIET,
+ };
+
+ // Time the first finger went down.
+ nsecs_t firstTouchTime;
+
+ // The active pointer id from the raw touch data.
+ int32_t activeTouchId; // -1 if none
+
+ // The active pointer id from the gesture last delivered to the application.
+ int32_t activeGestureId; // -1 if none
+
+ // Pointer coords and ids for the current and previous pointer gesture.
+ Mode currentGestureMode;
+ BitSet32 currentGestureIdBits;
+ uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
+ PointerProperties currentGestureProperties[MAX_POINTERS];
+ PointerCoords currentGestureCoords[MAX_POINTERS];
+
+ Mode lastGestureMode;
+ BitSet32 lastGestureIdBits;
+ uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
+ PointerProperties lastGestureProperties[MAX_POINTERS];
+ PointerCoords lastGestureCoords[MAX_POINTERS];
+
+ // Time the pointer gesture last went down.
+ nsecs_t downTime;
+
+ // Time when the pointer went down for a TAP.
+ nsecs_t tapDownTime;
+
+ // Time when the pointer went up for a TAP.
+ nsecs_t tapUpTime;
+
+ // Location of initial tap.
+ float tapX, tapY;
+
+ // Time we started waiting for quiescence.
+ nsecs_t quietTime;
+
+ // Reference points for multitouch gestures.
+ float referenceTouchX; // reference touch X/Y coordinates in surface units
+ float referenceTouchY;
+ float referenceGestureX; // reference gesture X/Y coordinates in pixels
+ float referenceGestureY;
+
+ // Distance that each pointer has traveled which has not yet been
+ // subsumed into the reference gesture position.
+ BitSet32 referenceIdBits;
+ struct Delta {
+ float dx, dy;
+ };
+ Delta referenceDeltas[MAX_POINTER_ID + 1];
+
+ // Describes how touch ids are mapped to gesture ids for freeform gestures.
+ uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
+
+ // A velocity tracker for determining whether to switch active pointers during drags.
+ VelocityTracker velocityTracker;
+
+ void reset() {
+ firstTouchTime = LLONG_MIN;
+ activeTouchId = -1;
+ activeGestureId = -1;
+ currentGestureMode = NEUTRAL;
+ currentGestureIdBits.clear();
+ lastGestureMode = NEUTRAL;
+ lastGestureIdBits.clear();
+ downTime = 0;
+ velocityTracker.clear();
+ resetTap();
+ resetQuietTime();
+ }
+
+ void resetTap() {
+ tapDownTime = LLONG_MIN;
+ tapUpTime = LLONG_MIN;
+ }
+
+ void resetQuietTime() {
+ quietTime = LLONG_MIN;
+ }
+ } mPointerGesture;
+
+ struct PointerSimple {
+ PointerCoords currentCoords;
+ PointerProperties currentProperties;
+ PointerCoords lastCoords;
+ PointerProperties lastProperties;
+
+ // True if the pointer is down.
+ bool down;
+
+ // True if the pointer is hovering.
+ bool hovering;
+
+ // Time the pointer last went down.
+ nsecs_t downTime;
+
+ void reset() {
+ currentCoords.clear();
+ currentProperties.clear();
+ lastCoords.clear();
+ lastProperties.clear();
+ down = false;
+ hovering = false;
+ downTime = 0;
+ }
+ } mPointerSimple;
+
+ // The pointer and scroll velocity controls.
+ VelocityControl mPointerVelocityControl;
+ VelocityControl mWheelXVelocityControl;
+ VelocityControl mWheelYVelocityControl;
+
+ void sync(nsecs_t when);
+
+ bool consumeRawTouches(nsecs_t when, uint32_t policyFlags);
+ void dispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
+ int32_t keyEventAction, int32_t keyEventFlags);
+
+ void dispatchTouches(nsecs_t when, uint32_t policyFlags);
+ void dispatchHoverExit(nsecs_t when, uint32_t policyFlags);
+ void dispatchHoverEnterAndMove(nsecs_t when, uint32_t policyFlags);
+ void cookPointerData();
+
+ void dispatchPointerUsage(nsecs_t when, uint32_t policyFlags, PointerUsage pointerUsage);
+ void abortPointerUsage(nsecs_t when, uint32_t policyFlags);
+
+ void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout);
+ void abortPointerGestures(nsecs_t when, uint32_t policyFlags);
+ bool preparePointerGestures(nsecs_t when,
+ bool* outCancelPreviousGesture, bool* outFinishPreviousGesture,
+ bool isTimeout);
+
+ void dispatchPointerStylus(nsecs_t when, uint32_t policyFlags);
+ void abortPointerStylus(nsecs_t when, uint32_t policyFlags);
+
+ void dispatchPointerMouse(nsecs_t when, uint32_t policyFlags);
+ void abortPointerMouse(nsecs_t when, uint32_t policyFlags);
+
+ void dispatchPointerSimple(nsecs_t when, uint32_t policyFlags,
+ bool down, bool hovering);
+ void abortPointerSimple(nsecs_t when, uint32_t policyFlags);
+
+ // Dispatches a motion event.
+ // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
+ // method will take care of setting the index and transmuting the action to DOWN or UP
+ // it is the first / last pointer to go down / up.
+ void dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
+ int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,
+ int32_t edgeFlags,
+ const PointerProperties* properties, const PointerCoords* coords,
+ const uint32_t* idToIndex, BitSet32 idBits,
+ int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime);
+
+ // Updates pointer coords and properties for pointers with specified ids that have moved.
+ // Returns true if any of them changed.
+ bool updateMovedPointers(const PointerProperties* inProperties,
+ const PointerCoords* inCoords, const uint32_t* inIdToIndex,
+ PointerProperties* outProperties, PointerCoords* outCoords,
+ const uint32_t* outIdToIndex, BitSet32 idBits) const;
+
+ bool isPointInsideSurface(int32_t x, int32_t y);
+ const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y);
+
+ void assignPointerIds();
+};
+
+
+class SingleTouchInputMapper : public TouchInputMapper {
+public:
+ SingleTouchInputMapper(InputDevice* device);
+ virtual ~SingleTouchInputMapper();
+
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+protected:
+ virtual void syncTouch(nsecs_t when, bool* outHavePointerIds);
+ virtual void configureRawPointerAxes();
+ virtual bool hasStylus() const;
+
+private:
+ SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
+};
+
+
+class MultiTouchInputMapper : public TouchInputMapper {
+public:
+ MultiTouchInputMapper(InputDevice* device);
+ virtual ~MultiTouchInputMapper();
+
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+protected:
+ virtual void syncTouch(nsecs_t when, bool* outHavePointerIds);
+ virtual void configureRawPointerAxes();
+ virtual bool hasStylus() const;
+
+private:
+ MultiTouchMotionAccumulator mMultiTouchMotionAccumulator;
+
+ // Specifies the pointer id bits that are in use, and their associated tracking id.
+ BitSet32 mPointerIdBits;
+ int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
+};
+
+
+class JoystickInputMapper : public InputMapper {
+public:
+ JoystickInputMapper(InputDevice* device);
+ virtual ~JoystickInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void dump(String8& dump);
+ virtual void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
+ virtual void reset(nsecs_t when);
+ virtual void process(const RawEvent* rawEvent);
+
+private:
+ struct Axis {
+ RawAbsoluteAxisInfo rawAxisInfo;
+ AxisInfo axisInfo;
+
+ bool explicitlyMapped; // true if the axis was explicitly assigned an axis id
+
+ float scale; // scale factor from raw to normalized values
+ float offset; // offset to add after scaling for normalization
+ float highScale; // scale factor from raw to normalized values of high split
+ float highOffset; // offset to add after scaling for normalization of high split
+
+ float min; // normalized inclusive minimum
+ float max; // normalized inclusive maximum
+ float flat; // normalized flat region size
+ float fuzz; // normalized error tolerance
+ float resolution; // normalized resolution in units/mm
+
+ float filter; // filter out small variations of this size
+ float currentValue; // current value
+ float newValue; // most recent value
+ float highCurrentValue; // current value of high split
+ float highNewValue; // most recent value of high split
+
+ void initialize(const RawAbsoluteAxisInfo& rawAxisInfo, const AxisInfo& axisInfo,
+ bool explicitlyMapped, float scale, float offset,
+ float highScale, float highOffset,
+ float min, float max, float flat, float fuzz, float resolution) {
+ this->rawAxisInfo = rawAxisInfo;
+ this->axisInfo = axisInfo;
+ this->explicitlyMapped = explicitlyMapped;
+ this->scale = scale;
+ this->offset = offset;
+ this->highScale = highScale;
+ this->highOffset = highOffset;
+ this->min = min;
+ this->max = max;
+ this->flat = flat;
+ this->fuzz = fuzz;
+ this->resolution = resolution;
+ this->filter = 0;
+ resetValue();
+ }
+
+ void resetValue() {
+ this->currentValue = 0;
+ this->newValue = 0;
+ this->highCurrentValue = 0;
+ this->highNewValue = 0;
+ }
+ };
+
+ // Axes indexed by raw ABS_* axis index.
+ KeyedVector<int32_t, Axis> mAxes;
+
+ void sync(nsecs_t when, bool force);
+
+ bool haveAxis(int32_t axisId);
+ void pruneAxes(bool ignoreExplicitlyMappedAxes);
+ bool filterAxes(bool force);
+
+ static bool hasValueChangedSignificantly(float filter,
+ float newValue, float currentValue, float min, float max);
+ static bool hasMovedNearerToValueWithinFilteredRange(float filter,
+ float newValue, float currentValue, float thresholdValue);
+
+ static bool isCenteredAxis(int32_t axis);
+ static int32_t getCompatAxis(int32_t axis);
+
+ static void addMotionRange(int32_t axisId, const Axis& axis, InputDeviceInfo* info);
+ static void setPointerCoordsAxisValue(PointerCoords* pointerCoords, int32_t axis,
+ float value);
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_READER_H
diff --git a/services/inputflinger/InputWindow.cpp b/services/inputflinger/InputWindow.cpp
new file mode 100644
index 0000000..fda3ffa
--- /dev/null
+++ b/services/inputflinger/InputWindow.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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"
+#define LOG_NDEBUG 0
+
+#include "InputWindow.h"
+
+#include <cutils/log.h>
+
+#include <ui/Rect.h>
+#include <ui/Region.h>
+
+namespace android {
+
+// --- InputWindowInfo ---
+void InputWindowInfo::addTouchableRegion(const Rect& region) {
+ touchableRegion.orSelf(region);
+}
+
+bool InputWindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
+ return touchableRegion.contains(x,y);
+}
+
+bool InputWindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
+ return x >= frameLeft && x <= frameRight
+ && y >= frameTop && y <= frameBottom;
+}
+
+bool InputWindowInfo::isTrustedOverlay() const {
+ return layoutParamsType == TYPE_INPUT_METHOD
+ || layoutParamsType == TYPE_INPUT_METHOD_DIALOG
+ || layoutParamsType == TYPE_MAGNIFICATION_OVERLAY
+ || layoutParamsType == TYPE_SECURE_SYSTEM_OVERLAY;
+}
+
+bool InputWindowInfo::supportsSplitTouch() const {
+ return layoutParamsFlags & FLAG_SPLIT_TOUCH;
+}
+
+
+// --- InputWindowHandle ---
+
+InputWindowHandle::InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle) :
+ inputApplicationHandle(inputApplicationHandle), mInfo(NULL) {
+}
+
+InputWindowHandle::~InputWindowHandle() {
+ delete mInfo;
+}
+
+void InputWindowHandle::releaseInfo() {
+ if (mInfo) {
+ delete mInfo;
+ mInfo = NULL;
+ }
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputWindow.h b/services/inputflinger/InputWindow.h
new file mode 100644
index 0000000..5879c84
--- /dev/null
+++ b/services/inputflinger/InputWindow.h
@@ -0,0 +1,210 @@
+/*
+ * 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/Input.h>
+#include <input/InputTransport.h>
+#include <ui/Rect.h>
+#include <ui/Region.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+#include <utils/String8.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_SLIPPERY = 0x20000000,
+ FLAG_NEEDS_MENU_KEY = 0x40000000,
+ };
+
+ // Private Window flags from WindowManager.LayoutParams
+ enum {
+ PRIVATE_FLAG_SYSTEM_ERROR = 0x00000100,
+ };
+
+ // 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,
+ TYPE_MAGNIFICATION_OVERLAY = FIRST_SYSTEM_WINDOW+22,
+ LAST_SYSTEM_WINDOW = 2999,
+ };
+
+ enum {
+ INPUT_FEATURE_DISABLE_TOUCH_PAD_GESTURES = 0x00000001,
+ INPUT_FEATURE_NO_INPUT_CHANNEL = 0x00000002,
+ INPUT_FEATURE_DISABLE_USER_ACTIVITY = 0x00000004,
+ };
+
+ sp<InputChannel> inputChannel;
+ String8 name;
+ int32_t layoutParamsFlags;
+ int32_t layoutParamsPrivateFlags;
+ int32_t layoutParamsType;
+ nsecs_t dispatchingTimeout;
+ int32_t frameLeft;
+ int32_t frameTop;
+ int32_t frameRight;
+ int32_t frameBottom;
+ float scaleFactor;
+ Region touchableRegion;
+ bool visible;
+ bool canReceiveKeys;
+ bool hasFocus;
+ bool hasWallpaper;
+ bool paused;
+ int32_t layer;
+ int32_t ownerPid;
+ int32_t ownerUid;
+ int32_t inputFeatures;
+ int32_t displayId;
+
+ void addTouchableRegion(const Rect& region);
+
+ bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
+ bool frameContainsPoint(int32_t x, int32_t y) const;
+
+ /* Returns true if the window is of a trusted type that is allowed to silently
+ * overlay other windows for the purpose of implementing the secure views feature.
+ * Trusted overlays, such as IME windows, can partly obscure other windows without causing
+ * motion events to be delivered to them with AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED.
+ */
+ bool isTrustedOverlay() const;
+
+ bool supportsSplitTouch() const;
+};
+
+
+/*
+ * Handle for a window that can receive input.
+ *
+ * Used by the native input dispatcher to indirectly refer to the window manager objects
+ * that describe a window.
+ */
+class InputWindowHandle : public RefBase {
+public:
+ const sp<InputApplicationHandle> inputApplicationHandle;
+
+ inline const InputWindowInfo* getInfo() const {
+ return mInfo;
+ }
+
+ inline sp<InputChannel> getInputChannel() const {
+ return mInfo ? mInfo->inputChannel : NULL;
+ }
+
+ inline String8 getName() const {
+ return mInfo ? mInfo->name : String8("<invalid>");
+ }
+
+ inline nsecs_t getDispatchingTimeout(nsecs_t defaultValue) const {
+ return mInfo ? mInfo->dispatchingTimeout : defaultValue;
+ }
+
+ /**
+ * Requests that the state of this object be updated to reflect
+ * the most current available information about the application.
+ *
+ * This method should only be called from within the input dispatcher's
+ * critical section.
+ *
+ * Returns true on success, or false if the handle is no longer valid.
+ */
+ virtual bool updateInfo() = 0;
+
+ /**
+ * Releases the storage used by the associated information when it is
+ * no longer needed.
+ */
+ void releaseInfo();
+
+protected:
+ InputWindowHandle(const sp<InputApplicationHandle>& inputApplicationHandle);
+ virtual ~InputWindowHandle();
+
+ InputWindowInfo* mInfo;
+};
+
+} // namespace android
+
+#endif // _UI_INPUT_WINDOW_H
diff --git a/services/inputflinger/PointerControllerInterface.h b/services/inputflinger/PointerControllerInterface.h
new file mode 100644
index 0000000..e94dd94
--- /dev/null
+++ b/services/inputflinger/PointerControllerInterface.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2014 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 _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H
+#define _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H
+
+#include <input/Input.h>
+#include <utils/BitSet.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+/**
+ * Interface for tracking a mouse / touch pad pointer and touch pad spots.
+ *
+ * The spots are sprites on screen that visually represent the positions of
+ * fingers
+ *
+ * The pointer controller is responsible for providing synchronization and for tracking
+ * display orientation changes if needed.
+ */
+class PointerControllerInterface : public virtual RefBase {
+protected:
+ PointerControllerInterface() { }
+ virtual ~PointerControllerInterface() { }
+
+public:
+ /* Gets the bounds of the region that the pointer can traverse.
+ * Returns true if the bounds are available. */
+ virtual bool getBounds(float* outMinX, float* outMinY,
+ float* outMaxX, float* outMaxY) const = 0;
+
+ /* Move the pointer. */
+ virtual void move(float deltaX, float deltaY) = 0;
+
+ /* Sets a mask that indicates which buttons are pressed. */
+ virtual void setButtonState(int32_t buttonState) = 0;
+
+ /* Gets a mask that indicates which buttons are pressed. */
+ virtual int32_t getButtonState() const = 0;
+
+ /* Sets the absolute location of the pointer. */
+ virtual void setPosition(float x, float y) = 0;
+
+ /* Gets the absolute location of the pointer. */
+ virtual void getPosition(float* outX, float* outY) const = 0;
+
+ enum Transition {
+ // Fade/unfade immediately.
+ TRANSITION_IMMEDIATE,
+ // Fade/unfade gradually.
+ TRANSITION_GRADUAL,
+ };
+
+ /* Fades the pointer out now. */
+ virtual void fade(Transition transition) = 0;
+
+ /* Makes the pointer visible if it has faded out.
+ * The pointer never unfades itself automatically. This method must be called
+ * by the client whenever the pointer is moved or a button is pressed and it
+ * wants to ensure that the pointer becomes visible again. */
+ virtual void unfade(Transition transition) = 0;
+
+ enum Presentation {
+ // Show the mouse pointer.
+ PRESENTATION_POINTER,
+ // Show spots and a spot anchor in place of the mouse pointer.
+ PRESENTATION_SPOT,
+ };
+
+ /* Sets the mode of the pointer controller. */
+ virtual void setPresentation(Presentation presentation) = 0;
+
+ /* Sets the spots for the current gesture.
+ * The spots are not subject to the inactivity timeout like the pointer
+ * itself it since they are expected to remain visible for so long as
+ * the fingers are on the touch pad.
+ *
+ * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant.
+ * For spotCoords, pressure != 0 indicates that the spot's location is being
+ * pressed (not hovering).
+ */
+ virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits) = 0;
+
+ /* Removes all spots. */
+ virtual void clearSpots() = 0;
+};
+
+} // namespace android
+
+#endif // _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H
diff --git a/services/inputflinger/main.cpp b/services/inputflinger/main.cpp
new file mode 100644
index 0000000..0a517cc
--- /dev/null
+++ b/services/inputflinger/main.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/BinderService.h>
+#include "InputFlinger.h"
+
+using namespace android;
+
+int main(int, char**) {
+ ProcessState::self()->setThreadPoolMaxThreadCount(4);
+ BinderService<InputFlinger>::publishAndJoinThreadPool(true);
+ return 0;
+}
diff --git a/services/inputflinger/tests/Android.mk b/services/inputflinger/tests/Android.mk
new file mode 100644
index 0000000..6dae82f
--- /dev/null
+++ b/services/inputflinger/tests/Android.mk
@@ -0,0 +1,51 @@
+# Build the unit tests.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Build the unit tests.
+test_src_files := \
+ InputReader_test.cpp \
+ InputDispatcher_test.cpp
+
+shared_libraries := \
+ libcutils \
+ liblog \
+ libandroidfw \
+ libutils \
+ libhardware \
+ libhardware_legacy \
+ libui \
+ libskia \
+ libstlport \
+ libinput \
+ libinputflinger \
+ libinputservice
+
+static_libraries := \
+ libgtest \
+ libgtest_main
+
+c_includes := \
+ bionic \
+ bionic/libstdc++/include \
+ external/gtest/include \
+ external/stlport/stlport \
+ external/skia/include/core
+
+
+module_tags := eng tests
+
+$(foreach file,$(test_src_files), \
+ $(eval include $(CLEAR_VARS)) \
+ $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+ $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+ $(eval LOCAL_C_INCLUDES := $(c_includes)) \
+ $(eval LOCAL_CFLAGS += -Wno-unused-parameter) \
+ $(eval LOCAL_SRC_FILES := $(file)) \
+ $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+ $(eval LOCAL_MODULE_TAGS := $(module_tags)) \
+ $(eval include $(BUILD_NATIVE_TEST)) \
+)
+
+# Build the manual test programs.
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
new file mode 100644
index 0000000..7aac6ed
--- /dev/null
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -0,0 +1,258 @@
+/*
+ * 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.
+ */
+
+#include "../InputDispatcher.h"
+
+#include <gtest/gtest.h>
+#include <linux/input.h>
+
+namespace android {
+
+// An arbitrary time value.
+static const nsecs_t ARBITRARY_TIME = 1234;
+
+// An arbitrary device id.
+static const int32_t DEVICE_ID = 1;
+
+// An arbitrary display id.
+static const int32_t DISPLAY_ID = 0;
+
+// An arbitrary injector pid / uid pair that has permission to inject events.
+static const int32_t INJECTOR_PID = 999;
+static const int32_t INJECTOR_UID = 1001;
+
+
+// --- FakeInputDispatcherPolicy ---
+
+class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
+ InputDispatcherConfiguration mConfig;
+
+protected:
+ virtual ~FakeInputDispatcherPolicy() {
+ }
+
+public:
+ FakeInputDispatcherPolicy() {
+ }
+
+private:
+ virtual void notifyConfigurationChanged(nsecs_t when) {
+ }
+
+ virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
+ const sp<InputWindowHandle>& inputWindowHandle,
+ const String8& reason) {
+ return 0;
+ }
+
+ virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) {
+ }
+
+ virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) {
+ *outConfig = mConfig;
+ }
+
+ virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
+ return true;
+ }
+
+ virtual void interceptKeyBeforeQueueing(const KeyEvent* keyEvent, uint32_t& policyFlags) {
+ }
+
+ virtual void interceptMotionBeforeQueueing(nsecs_t when, uint32_t& policyFlags) {
+ }
+
+ virtual nsecs_t interceptKeyBeforeDispatching(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags) {
+ return 0;
+ }
+
+ virtual bool dispatchUnhandledKey(const sp<InputWindowHandle>& inputWindowHandle,
+ const KeyEvent* keyEvent, uint32_t policyFlags, KeyEvent* outFallbackKeyEvent) {
+ return false;
+ }
+
+ virtual void notifySwitch(nsecs_t when,
+ uint32_t switchValues, uint32_t switchMask, uint32_t policyFlags) {
+ }
+
+ virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
+ }
+
+ virtual bool checkInjectEventsPermissionNonReentrant(
+ int32_t injectorPid, int32_t injectorUid) {
+ return false;
+ }
+};
+
+
+// --- InputDispatcherTest ---
+
+class InputDispatcherTest : public testing::Test {
+protected:
+ sp<FakeInputDispatcherPolicy> mFakePolicy;
+ sp<InputDispatcher> mDispatcher;
+
+ virtual void SetUp() {
+ mFakePolicy = new FakeInputDispatcherPolicy();
+ mDispatcher = new InputDispatcher(mFakePolicy);
+ }
+
+ virtual void TearDown() {
+ mFakePolicy.clear();
+ mDispatcher.clear();
+ }
+};
+
+
+TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) {
+ KeyEvent event;
+
+ // Rejects undefined key actions.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+ /*action*/ -1, 0,
+ AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject key events with undefined action.";
+
+ // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+ AKEY_EVENT_ACTION_MULTIPLE, 0,
+ AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject key events with ACTION_MULTIPLE.";
+}
+
+TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
+ MotionEvent event;
+ PointerProperties pointerProperties[MAX_POINTERS + 1];
+ PointerCoords pointerCoords[MAX_POINTERS + 1];
+ for (int i = 0; i <= MAX_POINTERS; i++) {
+ pointerProperties[i].clear();
+ pointerProperties[i].id = i;
+ pointerCoords[i].clear();
+ }
+
+ // Rejects undefined motion actions.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ /*action*/ -1, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with undefined action.";
+
+ // Rejects pointer down with invalid index.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with pointer down index too large.";
+
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with pointer down index too small.";
+
+ // Rejects pointer up with invalid index.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with pointer up index too large.";
+
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_POINTER_UP | (-1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with pointer up index too small.";
+
+ // Rejects motion events with invalid number of pointers.
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 0, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with 0 pointers.";
+
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with more than MAX_POINTERS pointers.";
+
+ // Rejects motion events with invalid pointer ids.
+ pointerProperties[0].id = -1;
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with pointer ids less than 0.";
+
+ pointerProperties[0].id = MAX_POINTER_ID + 1;
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with pointer ids greater than MAX_POINTER_ID.";
+
+ // Rejects motion events with duplicate pointer ids.
+ pointerProperties[0].id = 1;
+ pointerProperties[1].id = 1;
+ event.initialize(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ AMOTION_EVENT_ACTION_DOWN, 0, 0, AMETA_NONE, 0, 0, 0, 0, 0,
+ ARBITRARY_TIME, ARBITRARY_TIME,
+ /*pointerCount*/ 2, pointerProperties, pointerCoords);
+ ASSERT_EQ(INPUT_EVENT_INJECTION_FAILED, mDispatcher->injectInputEvent(
+ &event, DISPLAY_ID,
+ INJECTOR_PID, INJECTOR_UID, INPUT_EVENT_INJECTION_SYNC_NONE, 0, 0))
+ << "Should reject motion events with duplicate pointer ids.";
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
new file mode 100644
index 0000000..c6eb1fd
--- /dev/null
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -0,0 +1,5153 @@
+/*
+ * 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.
+ */
+
+#include "../InputReader.h"
+
+#include <utils/List.h>
+#include <gtest/gtest.h>
+#include <math.h>
+
+namespace android {
+
+// An arbitrary time value.
+static const nsecs_t ARBITRARY_TIME = 1234;
+
+// Arbitrary display properties.
+static const int32_t DISPLAY_ID = 0;
+static const int32_t DISPLAY_WIDTH = 480;
+static const int32_t DISPLAY_HEIGHT = 800;
+
+// Error tolerance for floating point assertions.
+static const float EPSILON = 0.001f;
+
+template<typename T>
+static inline T min(T a, T b) {
+ return a < b ? a : b;
+}
+
+static inline float avg(float x, float y) {
+ return (x + y) / 2;
+}
+
+
+// --- FakePointerController ---
+
+class FakePointerController : public PointerControllerInterface {
+ bool mHaveBounds;
+ float mMinX, mMinY, mMaxX, mMaxY;
+ float mX, mY;
+ int32_t mButtonState;
+
+protected:
+ virtual ~FakePointerController() { }
+
+public:
+ FakePointerController() :
+ mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mX(0), mY(0),
+ mButtonState(0) {
+ }
+
+ void setBounds(float minX, float minY, float maxX, float maxY) {
+ mHaveBounds = true;
+ mMinX = minX;
+ mMinY = minY;
+ mMaxX = maxX;
+ mMaxY = maxY;
+ }
+
+ virtual void setPosition(float x, float y) {
+ mX = x;
+ mY = y;
+ }
+
+ virtual void setButtonState(int32_t buttonState) {
+ mButtonState = buttonState;
+ }
+
+ virtual int32_t getButtonState() const {
+ return mButtonState;
+ }
+
+ virtual void getPosition(float* outX, float* outY) const {
+ *outX = mX;
+ *outY = mY;
+ }
+
+private:
+ virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const {
+ *outMinX = mMinX;
+ *outMinY = mMinY;
+ *outMaxX = mMaxX;
+ *outMaxY = mMaxY;
+ return mHaveBounds;
+ }
+
+ virtual void move(float deltaX, float deltaY) {
+ mX += deltaX;
+ if (mX < mMinX) mX = mMinX;
+ if (mX > mMaxX) mX = mMaxX;
+ mY += deltaY;
+ if (mY < mMinY) mY = mMinY;
+ if (mY > mMaxY) mY = mMaxY;
+ }
+
+ 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() {
+ }
+};
+
+
+// --- FakeInputReaderPolicy ---
+
+class FakeInputReaderPolicy : public InputReaderPolicyInterface {
+ InputReaderConfiguration mConfig;
+ KeyedVector<int32_t, sp<FakePointerController> > mPointerControllers;
+ Vector<InputDeviceInfo> mInputDevices;
+ TouchAffineTransformation transform;
+
+protected:
+ virtual ~FakeInputReaderPolicy() { }
+
+public:
+ FakeInputReaderPolicy() {
+ }
+
+ void setDisplayInfo(int32_t displayId, int32_t width, int32_t height, int32_t orientation) {
+ // Set the size of both the internal and external display at the same time.
+ bool isRotated = (orientation == DISPLAY_ORIENTATION_90
+ || orientation == DISPLAY_ORIENTATION_270);
+ DisplayViewport v;
+ v.displayId = displayId;
+ v.orientation = orientation;
+ v.logicalLeft = 0;
+ v.logicalTop = 0;
+ v.logicalRight = isRotated ? height : width;
+ v.logicalBottom = isRotated ? width : height;
+ v.physicalLeft = 0;
+ v.physicalTop = 0;
+ v.physicalRight = isRotated ? height : width;
+ v.physicalBottom = isRotated ? width : height;
+ v.deviceWidth = isRotated ? height : width;
+ v.deviceHeight = isRotated ? width : height;
+ mConfig.setDisplayInfo(false /*external*/, v);
+ mConfig.setDisplayInfo(true /*external*/, v);
+ }
+
+ void addExcludedDeviceName(const String8& deviceName) {
+ mConfig.excludedDeviceNames.push(deviceName);
+ }
+
+ void setPointerController(int32_t deviceId, const sp<FakePointerController>& controller) {
+ mPointerControllers.add(deviceId, controller);
+ }
+
+ const InputReaderConfiguration* getReaderConfiguration() const {
+ return &mConfig;
+ }
+
+ const Vector<InputDeviceInfo>& getInputDevices() const {
+ return mInputDevices;
+ }
+
+ TouchAffineTransformation getTouchAffineTransformation(const String8& inputDeviceDescriptor,
+ int32_t surfaceRotation) {
+ return transform;
+ }
+
+ void setTouchAffineTransformation(const TouchAffineTransformation t) {
+ transform = t;
+ }
+
+private:
+ virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) {
+ *outConfig = mConfig;
+ }
+
+ virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
+ return mPointerControllers.valueFor(deviceId);
+ }
+
+ virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) {
+ mInputDevices = inputDevices;
+ }
+
+ virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(const InputDeviceIdentifier& identifier) {
+ return NULL;
+ }
+
+ virtual String8 getDeviceAlias(const InputDeviceIdentifier& identifier) {
+ return String8::empty();
+ }
+};
+
+
+// --- FakeInputListener ---
+
+class FakeInputListener : public InputListenerInterface {
+private:
+ List<NotifyConfigurationChangedArgs> mNotifyConfigurationChangedArgsQueue;
+ List<NotifyDeviceResetArgs> mNotifyDeviceResetArgsQueue;
+ List<NotifyKeyArgs> mNotifyKeyArgsQueue;
+ List<NotifyMotionArgs> mNotifyMotionArgsQueue;
+ List<NotifySwitchArgs> mNotifySwitchArgsQueue;
+
+protected:
+ virtual ~FakeInputListener() { }
+
+public:
+ FakeInputListener() {
+ }
+
+ void assertNotifyConfigurationChangedWasCalled(
+ NotifyConfigurationChangedArgs* outEventArgs = NULL) {
+ ASSERT_FALSE(mNotifyConfigurationChangedArgsQueue.empty())
+ << "Expected notifyConfigurationChanged() to have been called.";
+ if (outEventArgs) {
+ *outEventArgs = *mNotifyConfigurationChangedArgsQueue.begin();
+ }
+ mNotifyConfigurationChangedArgsQueue.erase(mNotifyConfigurationChangedArgsQueue.begin());
+ }
+
+ void assertNotifyDeviceResetWasCalled(
+ NotifyDeviceResetArgs* outEventArgs = NULL) {
+ ASSERT_FALSE(mNotifyDeviceResetArgsQueue.empty())
+ << "Expected notifyDeviceReset() to have been called.";
+ if (outEventArgs) {
+ *outEventArgs = *mNotifyDeviceResetArgsQueue.begin();
+ }
+ mNotifyDeviceResetArgsQueue.erase(mNotifyDeviceResetArgsQueue.begin());
+ }
+
+ void assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs = NULL) {
+ ASSERT_FALSE(mNotifyKeyArgsQueue.empty())
+ << "Expected notifyKey() to have been called.";
+ if (outEventArgs) {
+ *outEventArgs = *mNotifyKeyArgsQueue.begin();
+ }
+ mNotifyKeyArgsQueue.erase(mNotifyKeyArgsQueue.begin());
+ }
+
+ void assertNotifyKeyWasNotCalled() {
+ ASSERT_TRUE(mNotifyKeyArgsQueue.empty())
+ << "Expected notifyKey() to not have been called.";
+ }
+
+ void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = NULL) {
+ ASSERT_FALSE(mNotifyMotionArgsQueue.empty())
+ << "Expected notifyMotion() to have been called.";
+ if (outEventArgs) {
+ *outEventArgs = *mNotifyMotionArgsQueue.begin();
+ }
+ mNotifyMotionArgsQueue.erase(mNotifyMotionArgsQueue.begin());
+ }
+
+ void assertNotifyMotionWasNotCalled() {
+ ASSERT_TRUE(mNotifyMotionArgsQueue.empty())
+ << "Expected notifyMotion() to not have been called.";
+ }
+
+ void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = NULL) {
+ ASSERT_FALSE(mNotifySwitchArgsQueue.empty())
+ << "Expected notifySwitch() to have been called.";
+ if (outEventArgs) {
+ *outEventArgs = *mNotifySwitchArgsQueue.begin();
+ }
+ mNotifySwitchArgsQueue.erase(mNotifySwitchArgsQueue.begin());
+ }
+
+private:
+ virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
+ mNotifyConfigurationChangedArgsQueue.push_back(*args);
+ }
+
+ virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+ mNotifyDeviceResetArgsQueue.push_back(*args);
+ }
+
+ virtual void notifyKey(const NotifyKeyArgs* args) {
+ mNotifyKeyArgsQueue.push_back(*args);
+ }
+
+ virtual void notifyMotion(const NotifyMotionArgs* args) {
+ mNotifyMotionArgsQueue.push_back(*args);
+ }
+
+ virtual void notifySwitch(const NotifySwitchArgs* args) {
+ mNotifySwitchArgsQueue.push_back(*args);
+ }
+};
+
+
+// --- FakeEventHub ---
+
+class FakeEventHub : public EventHubInterface {
+ struct KeyInfo {
+ int32_t keyCode;
+ uint32_t flags;
+ };
+
+ struct Device {
+ InputDeviceIdentifier identifier;
+ uint32_t classes;
+ PropertyMap configuration;
+ KeyedVector<int, RawAbsoluteAxisInfo> absoluteAxes;
+ KeyedVector<int, bool> relativeAxes;
+ KeyedVector<int32_t, int32_t> keyCodeStates;
+ KeyedVector<int32_t, int32_t> scanCodeStates;
+ KeyedVector<int32_t, int32_t> switchStates;
+ KeyedVector<int32_t, int32_t> absoluteAxisValue;
+ KeyedVector<int32_t, KeyInfo> keysByScanCode;
+ KeyedVector<int32_t, KeyInfo> keysByUsageCode;
+ KeyedVector<int32_t, bool> leds;
+ Vector<VirtualKeyDefinition> virtualKeys;
+
+ Device(uint32_t classes) :
+ classes(classes) {
+ }
+ };
+
+ KeyedVector<int32_t, Device*> mDevices;
+ Vector<String8> mExcludedDevices;
+ List<RawEvent> mEvents;
+
+protected:
+ virtual ~FakeEventHub() {
+ for (size_t i = 0; i < mDevices.size(); i++) {
+ delete mDevices.valueAt(i);
+ }
+ }
+
+public:
+ FakeEventHub() { }
+
+ void addDevice(int32_t deviceId, const String8& name, uint32_t classes) {
+ Device* device = new Device(classes);
+ device->identifier.name = name;
+ mDevices.add(deviceId, device);
+
+ enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0);
+ }
+
+ void removeDevice(int32_t deviceId) {
+ delete mDevices.valueFor(deviceId);
+ mDevices.removeItem(deviceId);
+
+ enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0);
+ }
+
+ void finishDeviceScan() {
+ enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
+ }
+
+ void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) {
+ Device* device = getDevice(deviceId);
+ device->configuration.addProperty(key, value);
+ }
+
+ void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) {
+ Device* device = getDevice(deviceId);
+ device->configuration.addAll(configuration);
+ }
+
+ void addAbsoluteAxis(int32_t deviceId, int axis,
+ int32_t minValue, int32_t maxValue, int flat, int fuzz, int resolution = 0) {
+ Device* device = getDevice(deviceId);
+
+ RawAbsoluteAxisInfo info;
+ info.valid = true;
+ info.minValue = minValue;
+ info.maxValue = maxValue;
+ info.flat = flat;
+ info.fuzz = fuzz;
+ info.resolution = resolution;
+ device->absoluteAxes.add(axis, info);
+ }
+
+ void addRelativeAxis(int32_t deviceId, int32_t axis) {
+ Device* device = getDevice(deviceId);
+ device->relativeAxes.add(axis, true);
+ }
+
+ void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) {
+ Device* device = getDevice(deviceId);
+ device->keyCodeStates.replaceValueFor(keyCode, state);
+ }
+
+ void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
+ Device* device = getDevice(deviceId);
+ device->scanCodeStates.replaceValueFor(scanCode, state);
+ }
+
+ void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) {
+ Device* device = getDevice(deviceId);
+ device->switchStates.replaceValueFor(switchCode, state);
+ }
+
+ void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) {
+ Device* device = getDevice(deviceId);
+ device->absoluteAxisValue.replaceValueFor(axis, value);
+ }
+
+ void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t keyCode, uint32_t flags) {
+ Device* device = getDevice(deviceId);
+ KeyInfo info;
+ info.keyCode = keyCode;
+ info.flags = flags;
+ if (scanCode) {
+ device->keysByScanCode.add(scanCode, info);
+ }
+ if (usageCode) {
+ device->keysByUsageCode.add(usageCode, info);
+ }
+ }
+
+ void addLed(int32_t deviceId, int32_t led, bool initialState) {
+ Device* device = getDevice(deviceId);
+ device->leds.add(led, initialState);
+ }
+
+ bool getLedState(int32_t deviceId, int32_t led) {
+ Device* device = getDevice(deviceId);
+ return device->leds.valueFor(led);
+ }
+
+ Vector<String8>& getExcludedDevices() {
+ return mExcludedDevices;
+ }
+
+ void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) {
+ Device* device = getDevice(deviceId);
+ device->virtualKeys.push(definition);
+ }
+
+ void enqueueEvent(nsecs_t when, int32_t deviceId, int32_t type,
+ int32_t code, int32_t value) {
+ RawEvent event;
+ event.when = when;
+ event.deviceId = deviceId;
+ event.type = type;
+ event.code = code;
+ event.value = value;
+ mEvents.push_back(event);
+
+ if (type == EV_ABS) {
+ setAbsoluteAxisValue(deviceId, code, value);
+ }
+ }
+
+ void assertQueueIsEmpty() {
+ ASSERT_EQ(size_t(0), mEvents.size())
+ << "Expected the event queue to be empty (fully consumed).";
+ }
+
+private:
+ Device* getDevice(int32_t deviceId) const {
+ ssize_t index = mDevices.indexOfKey(deviceId);
+ return index >= 0 ? mDevices.valueAt(index) : NULL;
+ }
+
+ virtual uint32_t getDeviceClasses(int32_t deviceId) const {
+ Device* device = getDevice(deviceId);
+ return device ? device->classes : 0;
+ }
+
+ virtual InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const {
+ Device* device = getDevice(deviceId);
+ return device ? device->identifier : InputDeviceIdentifier();
+ }
+
+ virtual int32_t getDeviceControllerNumber(int32_t deviceId) const {
+ return 0;
+ }
+
+ virtual void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ *outConfiguration = device->configuration;
+ }
+ }
+
+ virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->absoluteAxes.indexOfKey(axis);
+ if (index >= 0) {
+ *outAxisInfo = device->absoluteAxes.valueAt(index);
+ return OK;
+ }
+ }
+ outAxisInfo->clear();
+ return -1;
+ }
+
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ return device->relativeAxes.indexOfKey(axis) >= 0;
+ }
+ return false;
+ }
+
+ virtual bool hasInputProperty(int32_t deviceId, int property) const {
+ return false;
+ }
+
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ const KeyInfo* key = getKey(device, scanCode, usageCode);
+ if (key) {
+ if (outKeycode) {
+ *outKeycode = key->keyCode;
+ }
+ if (outFlags) {
+ *outFlags = key->flags;
+ }
+ return OK;
+ }
+ }
+ return NAME_NOT_FOUND;
+ }
+
+ const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const {
+ if (usageCode) {
+ ssize_t index = device->keysByUsageCode.indexOfKey(usageCode);
+ if (index >= 0) {
+ return &device->keysByUsageCode.valueAt(index);
+ }
+ }
+ if (scanCode) {
+ ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
+ if (index >= 0) {
+ return &device->keysByScanCode.valueAt(index);
+ }
+ }
+ return NULL;
+ }
+
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
+ AxisInfo* outAxisInfo) const {
+ return NAME_NOT_FOUND;
+ }
+
+ virtual void setExcludedDevices(const Vector<String8>& devices) {
+ mExcludedDevices = devices;
+ }
+
+ virtual size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
+ if (mEvents.empty()) {
+ return 0;
+ }
+
+ *buffer = *mEvents.begin();
+ mEvents.erase(mEvents.begin());
+ return 1;
+ }
+
+ virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->scanCodeStates.indexOfKey(scanCode);
+ if (index >= 0) {
+ return device->scanCodeStates.valueAt(index);
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+ }
+
+ virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->keyCodeStates.indexOfKey(keyCode);
+ if (index >= 0) {
+ return device->keyCodeStates.valueAt(index);
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+ }
+
+ virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->switchStates.indexOfKey(sw);
+ if (index >= 0) {
+ return device->switchStates.valueAt(index);
+ }
+ }
+ return AKEY_STATE_UNKNOWN;
+ }
+
+ virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
+ int32_t* outValue) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->absoluteAxisValue.indexOfKey(axis);
+ if (index >= 0) {
+ *outValue = device->absoluteAxisValue.valueAt(index);
+ return OK;
+ }
+ }
+ *outValue = 0;
+ return -1;
+ }
+
+ virtual bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+ uint8_t* outFlags) const {
+ bool result = false;
+ Device* device = getDevice(deviceId);
+ if (device) {
+ for (size_t i = 0; i < numCodes; i++) {
+ for (size_t j = 0; j < device->keysByScanCode.size(); j++) {
+ if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) {
+ outFlags[i] = 1;
+ result = true;
+ }
+ }
+ for (size_t j = 0; j < device->keysByUsageCode.size(); j++) {
+ if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) {
+ outFlags[i] = 1;
+ result = true;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
+ return index >= 0;
+ }
+ return false;
+ }
+
+ virtual bool hasLed(int32_t deviceId, int32_t led) const {
+ Device* device = getDevice(deviceId);
+ return device && device->leds.indexOfKey(led) >= 0;
+ }
+
+ virtual void setLedState(int32_t deviceId, int32_t led, bool on) {
+ Device* device = getDevice(deviceId);
+ if (device) {
+ ssize_t index = device->leds.indexOfKey(led);
+ if (index >= 0) {
+ device->leds.replaceValueAt(led, on);
+ } else {
+ ADD_FAILURE()
+ << "Attempted to set the state of an LED that the EventHub declared "
+ "was not present. led=" << led;
+ }
+ }
+ }
+
+ virtual void getVirtualKeyDefinitions(int32_t deviceId,
+ Vector<VirtualKeyDefinition>& outVirtualKeys) const {
+ outVirtualKeys.clear();
+
+ Device* device = getDevice(deviceId);
+ if (device) {
+ outVirtualKeys.appendVector(device->virtualKeys);
+ }
+ }
+
+ virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const {
+ return NULL;
+ }
+
+ virtual bool setKeyboardLayoutOverlay(int32_t deviceId, const sp<KeyCharacterMap>& map) {
+ return false;
+ }
+
+ virtual void vibrate(int32_t deviceId, nsecs_t duration) {
+ }
+
+ virtual void cancelVibrate(int32_t deviceId) {
+ }
+
+ virtual bool isExternal(int32_t deviceId) const {
+ return false;
+ }
+
+ virtual void dump(String8& dump) {
+ }
+
+ virtual void monitor() {
+ }
+
+ virtual void requestReopenDevices() {
+ }
+
+ virtual void wake() {
+ }
+};
+
+
+// --- FakeInputReaderContext ---
+
+class FakeInputReaderContext : public InputReaderContext {
+ sp<EventHubInterface> mEventHub;
+ sp<InputReaderPolicyInterface> mPolicy;
+ sp<InputListenerInterface> mListener;
+ int32_t mGlobalMetaState;
+ bool mUpdateGlobalMetaStateWasCalled;
+ int32_t mGeneration;
+
+public:
+ FakeInputReaderContext(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputListenerInterface>& listener) :
+ mEventHub(eventHub), mPolicy(policy), mListener(listener),
+ mGlobalMetaState(0) {
+ }
+
+ virtual ~FakeInputReaderContext() { }
+
+ void assertUpdateGlobalMetaStateWasCalled() {
+ ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled)
+ << "Expected updateGlobalMetaState() to have been called.";
+ mUpdateGlobalMetaStateWasCalled = false;
+ }
+
+ void setGlobalMetaState(int32_t state) {
+ mGlobalMetaState = state;
+ }
+
+private:
+ virtual void updateGlobalMetaState() {
+ mUpdateGlobalMetaStateWasCalled = true;
+ }
+
+ virtual int32_t getGlobalMetaState() {
+ return mGlobalMetaState;
+ }
+
+ virtual EventHubInterface* getEventHub() {
+ return mEventHub.get();
+ }
+
+ virtual InputReaderPolicyInterface* getPolicy() {
+ return mPolicy.get();
+ }
+
+ virtual InputListenerInterface* getListener() {
+ return mListener.get();
+ }
+
+ virtual void disableVirtualKeysUntil(nsecs_t time) {
+ }
+
+ virtual bool shouldDropVirtualKey(nsecs_t now,
+ InputDevice* device, int32_t keyCode, int32_t scanCode) {
+ return false;
+ }
+
+ virtual void fadePointer() {
+ }
+
+ virtual void requestTimeoutAtTime(nsecs_t when) {
+ }
+
+ virtual int32_t bumpGeneration() {
+ return ++mGeneration;
+ }
+};
+
+
+// --- FakeInputMapper ---
+
+class FakeInputMapper : public InputMapper {
+ uint32_t mSources;
+ int32_t mKeyboardType;
+ int32_t mMetaState;
+ KeyedVector<int32_t, int32_t> mKeyCodeStates;
+ KeyedVector<int32_t, int32_t> mScanCodeStates;
+ KeyedVector<int32_t, int32_t> mSwitchStates;
+ Vector<int32_t> mSupportedKeyCodes;
+ RawEvent mLastEvent;
+
+ bool mConfigureWasCalled;
+ bool mResetWasCalled;
+ bool mProcessWasCalled;
+
+public:
+ FakeInputMapper(InputDevice* device, uint32_t sources) :
+ InputMapper(device),
+ mSources(sources), mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE),
+ mMetaState(0),
+ mConfigureWasCalled(false), mResetWasCalled(false), mProcessWasCalled(false) {
+ }
+
+ virtual ~FakeInputMapper() { }
+
+ void setKeyboardType(int32_t keyboardType) {
+ mKeyboardType = keyboardType;
+ }
+
+ void setMetaState(int32_t metaState) {
+ mMetaState = metaState;
+ }
+
+ void assertConfigureWasCalled() {
+ ASSERT_TRUE(mConfigureWasCalled)
+ << "Expected configure() to have been called.";
+ mConfigureWasCalled = false;
+ }
+
+ void assertResetWasCalled() {
+ ASSERT_TRUE(mResetWasCalled)
+ << "Expected reset() to have been called.";
+ mResetWasCalled = false;
+ }
+
+ void assertProcessWasCalled(RawEvent* outLastEvent = NULL) {
+ ASSERT_TRUE(mProcessWasCalled)
+ << "Expected process() to have been called.";
+ if (outLastEvent) {
+ *outLastEvent = mLastEvent;
+ }
+ mProcessWasCalled = false;
+ }
+
+ void setKeyCodeState(int32_t keyCode, int32_t state) {
+ mKeyCodeStates.replaceValueFor(keyCode, state);
+ }
+
+ void setScanCodeState(int32_t scanCode, int32_t state) {
+ mScanCodeStates.replaceValueFor(scanCode, state);
+ }
+
+ void setSwitchState(int32_t switchCode, int32_t state) {
+ mSwitchStates.replaceValueFor(switchCode, state);
+ }
+
+ void addSupportedKeyCode(int32_t keyCode) {
+ mSupportedKeyCodes.add(keyCode);
+ }
+
+private:
+ virtual uint32_t getSources() {
+ return mSources;
+ }
+
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) {
+ InputMapper::populateDeviceInfo(deviceInfo);
+
+ if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) {
+ deviceInfo->setKeyboardType(mKeyboardType);
+ }
+ }
+
+ virtual void configure(nsecs_t when,
+ const InputReaderConfiguration* config, uint32_t changes) {
+ mConfigureWasCalled = true;
+ }
+
+ virtual void reset(nsecs_t when) {
+ mResetWasCalled = true;
+ }
+
+ virtual void process(const RawEvent* rawEvent) {
+ mLastEvent = *rawEvent;
+ mProcessWasCalled = true;
+ }
+
+ virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
+ ssize_t index = mKeyCodeStates.indexOfKey(keyCode);
+ return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+ }
+
+ virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) {
+ ssize_t index = mScanCodeStates.indexOfKey(scanCode);
+ return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+ }
+
+ virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) {
+ ssize_t index = mSwitchStates.indexOfKey(switchCode);
+ return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN;
+ }
+
+ virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
+ const int32_t* keyCodes, uint8_t* outFlags) {
+ bool result = false;
+ for (size_t i = 0; i < numCodes; i++) {
+ for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) {
+ if (keyCodes[i] == mSupportedKeyCodes[j]) {
+ outFlags[i] = 1;
+ result = true;
+ }
+ }
+ }
+ return result;
+ }
+
+ virtual int32_t getMetaState() {
+ return mMetaState;
+ }
+
+ virtual void fadePointer() {
+ }
+};
+
+
+// --- InstrumentedInputReader ---
+
+class InstrumentedInputReader : public InputReader {
+ InputDevice* mNextDevice;
+
+public:
+ InstrumentedInputReader(const sp<EventHubInterface>& eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ const sp<InputListenerInterface>& listener) :
+ InputReader(eventHub, policy, listener),
+ mNextDevice(NULL) {
+ }
+
+ virtual ~InstrumentedInputReader() {
+ if (mNextDevice) {
+ delete mNextDevice;
+ }
+ }
+
+ void setNextDevice(InputDevice* device) {
+ mNextDevice = device;
+ }
+
+ InputDevice* newDevice(int32_t deviceId, int32_t controllerNumber, const String8& name,
+ uint32_t classes) {
+ InputDeviceIdentifier identifier;
+ identifier.name = name;
+ int32_t generation = deviceId + 1;
+ return new InputDevice(&mContext, deviceId, generation, controllerNumber, identifier,
+ classes);
+ }
+
+protected:
+ virtual InputDevice* createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
+ const InputDeviceIdentifier& identifier, uint32_t classes) {
+ if (mNextDevice) {
+ InputDevice* device = mNextDevice;
+ mNextDevice = NULL;
+ return device;
+ }
+ return InputReader::createDeviceLocked(deviceId, controllerNumber, identifier, classes);
+ }
+
+ friend class InputReaderTest;
+};
+
+
+// --- InputReaderTest ---
+
+class InputReaderTest : public testing::Test {
+protected:
+ sp<FakeInputListener> mFakeListener;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ sp<FakeEventHub> mFakeEventHub;
+ sp<InstrumentedInputReader> mReader;
+
+ virtual void SetUp() {
+ mFakeEventHub = new FakeEventHub();
+ mFakePolicy = new FakeInputReaderPolicy();
+ mFakeListener = new FakeInputListener();
+
+ mReader = new InstrumentedInputReader(mFakeEventHub, mFakePolicy, mFakeListener);
+ }
+
+ virtual void TearDown() {
+ mReader.clear();
+
+ mFakeListener.clear();
+ mFakePolicy.clear();
+ mFakeEventHub.clear();
+ }
+
+ void addDevice(int32_t deviceId, const String8& name, uint32_t classes,
+ const PropertyMap* configuration) {
+ mFakeEventHub->addDevice(deviceId, name, classes);
+
+ if (configuration) {
+ mFakeEventHub->addConfigurationMap(deviceId, configuration);
+ }
+ mFakeEventHub->finishDeviceScan();
+ mReader->loopOnce();
+ mReader->loopOnce();
+ mFakeEventHub->assertQueueIsEmpty();
+ }
+
+ FakeInputMapper* addDeviceWithFakeInputMapper(int32_t deviceId, int32_t controllerNumber,
+ const String8& name, uint32_t classes, uint32_t sources,
+ const PropertyMap* configuration) {
+ InputDevice* device = mReader->newDevice(deviceId, controllerNumber, name, classes);
+ FakeInputMapper* mapper = new FakeInputMapper(device, sources);
+ device->addMapper(mapper);
+ mReader->setNextDevice(device);
+ addDevice(deviceId, name, classes, configuration);
+ return mapper;
+ }
+};
+
+TEST_F(InputReaderTest, GetInputDevices) {
+ ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
+ INPUT_DEVICE_CLASS_KEYBOARD, NULL));
+ ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("ignored"),
+ 0, NULL)); // no classes so device will be ignored
+
+ Vector<InputDeviceInfo> inputDevices;
+ mReader->getInputDevices(inputDevices);
+
+ ASSERT_EQ(1U, inputDevices.size());
+ ASSERT_EQ(1, inputDevices[0].getId());
+ ASSERT_STREQ("keyboard", inputDevices[0].getIdentifier().name.string());
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType());
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources());
+ ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size());
+
+ // Should also have received a notification describing the new input devices.
+ inputDevices = mFakePolicy->getInputDevices();
+ ASSERT_EQ(1U, inputDevices.size());
+ ASSERT_EQ(1, inputDevices[0].getId());
+ ASSERT_STREQ("keyboard", inputDevices[0].getIdentifier().name.string());
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType());
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources());
+ ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size());
+}
+
+TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, 0, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+ mapper->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(0,
+ AINPUT_SOURCE_ANY, AKEYCODE_A))
+ << "Should return unknown when the device id is >= 0 but unknown.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(1,
+ AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(-1,
+ AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(-1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, 0, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+ mapper->setScanCodeState(KEY_A, AKEY_STATE_DOWN);
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(0,
+ AINPUT_SOURCE_ANY, KEY_A))
+ << "Should return unknown when the device id is >= 0 but unknown.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(1,
+ AINPUT_SOURCE_TRACKBALL, KEY_A))
+ << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A))
+ << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(-1,
+ AINPUT_SOURCE_TRACKBALL, KEY_A))
+ << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(-1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A))
+ << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, GetSwitchState_ForwardsRequestsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, 0, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+ mapper->setSwitchState(SW_LID, AKEY_STATE_DOWN);
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(0,
+ AINPUT_SOURCE_ANY, SW_LID))
+ << "Should return unknown when the device id is >= 0 but unknown.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(1,
+ AINPUT_SOURCE_TRACKBALL, SW_LID))
+ << "Should return unknown when the device id is valid but the sources are not supported by the device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID))
+ << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(-1,
+ AINPUT_SOURCE_TRACKBALL, SW_LID))
+ << "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(-1,
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID))
+ << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+}
+
+TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, 0, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+ mapper->addSupportedKeyCode(AKEYCODE_A);
+ mapper->addSupportedKeyCode(AKEYCODE_B);
+
+ const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+ uint8_t flags[4] = { 0, 0, 0, 1 };
+
+ ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, 4, keyCodes, flags))
+ << "Should return false when device id is >= 0 but unknown.";
+ ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+ flags[3] = 1;
+ ASSERT_FALSE(mReader->hasKeys(1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should return false when device id is valid but the sources are not supported by the device.";
+ ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+ flags[3] = 1;
+ ASSERT_TRUE(mReader->hasKeys(1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should return value provided by mapper when device id is valid and the device supports some of the sources.";
+ ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
+
+ flags[3] = 1;
+ ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should return false when the device id is < 0 but the sources are not supported by any device.";
+ ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
+
+ flags[3] = 1;
+ ASSERT_TRUE(mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
+ ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
+}
+
+TEST_F(InputReaderTest, LoopOnce_WhenDeviceScanFinished_SendsConfigurationChanged) {
+ addDevice(1, String8("ignored"), INPUT_DEVICE_CLASS_KEYBOARD, NULL);
+
+ NotifyConfigurationChangedArgs args;
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+}
+
+TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) {
+ FakeInputMapper* mapper = NULL;
+ ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, 0, String8("fake"),
+ INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
+
+ mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, 1);
+ mReader->loopOnce();
+ ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
+
+ RawEvent event;
+ ASSERT_NO_FATAL_FAILURE(mapper->assertProcessWasCalled(&event));
+ ASSERT_EQ(0, event.when);
+ ASSERT_EQ(1, event.deviceId);
+ ASSERT_EQ(EV_KEY, event.type);
+ ASSERT_EQ(KEY_A, event.code);
+ ASSERT_EQ(1, event.value);
+}
+
+
+// --- InputDeviceTest ---
+
+class InputDeviceTest : public testing::Test {
+protected:
+ static const char* DEVICE_NAME;
+ static const int32_t DEVICE_ID;
+ static const int32_t DEVICE_GENERATION;
+ static const int32_t DEVICE_CONTROLLER_NUMBER;
+ static const uint32_t DEVICE_CLASSES;
+
+ sp<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ sp<FakeInputListener> mFakeListener;
+ FakeInputReaderContext* mFakeContext;
+
+ InputDevice* mDevice;
+
+ virtual void SetUp() {
+ mFakeEventHub = new FakeEventHub();
+ mFakePolicy = new FakeInputReaderPolicy();
+ mFakeListener = new FakeInputListener();
+ mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener);
+
+ mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
+ InputDeviceIdentifier identifier;
+ identifier.name = DEVICE_NAME;
+ mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION,
+ DEVICE_CONTROLLER_NUMBER, identifier, DEVICE_CLASSES);
+ }
+
+ virtual void TearDown() {
+ delete mDevice;
+
+ delete mFakeContext;
+ mFakeListener.clear();
+ mFakePolicy.clear();
+ mFakeEventHub.clear();
+ }
+};
+
+const char* InputDeviceTest::DEVICE_NAME = "device";
+const int32_t InputDeviceTest::DEVICE_ID = 1;
+const int32_t InputDeviceTest::DEVICE_GENERATION = 2;
+const int32_t InputDeviceTest::DEVICE_CONTROLLER_NUMBER = 0;
+const uint32_t InputDeviceTest::DEVICE_CLASSES = INPUT_DEVICE_CLASS_KEYBOARD
+ | INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_JOYSTICK;
+
+TEST_F(InputDeviceTest, ImmutableProperties) {
+ ASSERT_EQ(DEVICE_ID, mDevice->getId());
+ ASSERT_STREQ(DEVICE_NAME, mDevice->getName());
+ ASSERT_EQ(DEVICE_CLASSES, mDevice->getClasses());
+}
+
+TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
+ // Configuration.
+ InputReaderConfiguration config;
+ mDevice->configure(ARBITRARY_TIME, &config, 0);
+
+ // Reset.
+ mDevice->reset(ARBITRARY_TIME);
+
+ NotifyDeviceResetArgs resetArgs;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
+ ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
+
+ // Metadata.
+ ASSERT_TRUE(mDevice->isIgnored());
+ ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mDevice->getSources());
+
+ InputDeviceInfo info;
+ mDevice->getDeviceInfo(&info);
+ ASSERT_EQ(DEVICE_ID, info.getId());
+ ASSERT_STREQ(DEVICE_NAME, info.getIdentifier().name.string());
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NONE, info.getKeyboardType());
+ ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, info.getSources());
+
+ // State queries.
+ ASSERT_EQ(0, mDevice->getMetaState());
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, 0))
+ << "Ignored device should return unknown key code state.";
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 0))
+ << "Ignored device should return unknown scan code state.";
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 0))
+ << "Ignored device should return unknown switch state.";
+
+ const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
+ uint8_t flags[2] = { 0, 1 };
+ ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 2, keyCodes, flags))
+ << "Ignored device should never mark any key codes.";
+ ASSERT_EQ(0, flags[0]) << "Flag for unsupported key should be unchanged.";
+ ASSERT_EQ(1, flags[1]) << "Flag for unsupported key should be unchanged.";
+}
+
+TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRequestsToMappers) {
+ // Configuration.
+ mFakeEventHub->addConfigurationProperty(DEVICE_ID, String8("key"), String8("value"));
+
+ FakeInputMapper* mapper1 = new FakeInputMapper(mDevice, AINPUT_SOURCE_KEYBOARD);
+ mapper1->setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ mapper1->setMetaState(AMETA_ALT_ON);
+ mapper1->addSupportedKeyCode(AKEYCODE_A);
+ mapper1->addSupportedKeyCode(AKEYCODE_B);
+ mapper1->setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
+ mapper1->setKeyCodeState(AKEYCODE_B, AKEY_STATE_UP);
+ mapper1->setScanCodeState(2, AKEY_STATE_DOWN);
+ mapper1->setScanCodeState(3, AKEY_STATE_UP);
+ mapper1->setSwitchState(4, AKEY_STATE_DOWN);
+ mDevice->addMapper(mapper1);
+
+ FakeInputMapper* mapper2 = new FakeInputMapper(mDevice, AINPUT_SOURCE_TOUCHSCREEN);
+ mapper2->setMetaState(AMETA_SHIFT_ON);
+ mDevice->addMapper(mapper2);
+
+ InputReaderConfiguration config;
+ mDevice->configure(ARBITRARY_TIME, &config, 0);
+
+ String8 propertyValue;
+ ASSERT_TRUE(mDevice->getConfiguration().tryGetProperty(String8("key"), propertyValue))
+ << "Device should have read configuration during configuration phase.";
+ ASSERT_STREQ("value", propertyValue.string());
+
+ ASSERT_NO_FATAL_FAILURE(mapper1->assertConfigureWasCalled());
+ ASSERT_NO_FATAL_FAILURE(mapper2->assertConfigureWasCalled());
+
+ // Reset
+ mDevice->reset(ARBITRARY_TIME);
+ ASSERT_NO_FATAL_FAILURE(mapper1->assertResetWasCalled());
+ ASSERT_NO_FATAL_FAILURE(mapper2->assertResetWasCalled());
+
+ NotifyDeviceResetArgs resetArgs;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
+ ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
+
+ // Metadata.
+ ASSERT_FALSE(mDevice->isIgnored());
+ ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), mDevice->getSources());
+
+ InputDeviceInfo info;
+ mDevice->getDeviceInfo(&info);
+ ASSERT_EQ(DEVICE_ID, info.getId());
+ ASSERT_STREQ(DEVICE_NAME, info.getIdentifier().name.string());
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_ALPHABETIC, info.getKeyboardType());
+ ASSERT_EQ(uint32_t(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TOUCHSCREEN), info.getSources());
+
+ // State queries.
+ ASSERT_EQ(AMETA_ALT_ON | AMETA_SHIFT_ON, mDevice->getMetaState())
+ << "Should query mappers and combine meta states.";
+
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown key code state when source not supported.";
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getScanCodeState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown scan code state when source not supported.";
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
+ << "Should return unknown switch state when source not supported.";
+
+ ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getKeyCodeState(AINPUT_SOURCE_KEYBOARD, AKEYCODE_A))
+ << "Should query mapper when source is supported.";
+ ASSERT_EQ(AKEY_STATE_UP, mDevice->getScanCodeState(AINPUT_SOURCE_KEYBOARD, 3))
+ << "Should query mapper when source is supported.";
+ ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 4))
+ << "Should query mapper when source is supported.";
+
+ const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+ uint8_t flags[4] = { 0, 0, 0, 1 };
+ ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ << "Should do nothing when source is unsupported.";
+ ASSERT_EQ(0, flags[0]) << "Flag should be unchanged when source is unsupported.";
+ ASSERT_EQ(0, flags[1]) << "Flag should be unchanged when source is unsupported.";
+ ASSERT_EQ(0, flags[2]) << "Flag should be unchanged when source is unsupported.";
+ ASSERT_EQ(1, flags[3]) << "Flag should be unchanged when source is unsupported.";
+
+ ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 4, keyCodes, flags))
+ << "Should query mapper when source is supported.";
+ ASSERT_EQ(1, flags[0]) << "Flag for supported key should be set.";
+ ASSERT_EQ(1, flags[1]) << "Flag for supported key should be set.";
+ ASSERT_EQ(0, flags[2]) << "Flag for unsupported key should be unchanged.";
+ ASSERT_EQ(1, flags[3]) << "Flag for unsupported key should be unchanged.";
+
+ // Event handling.
+ RawEvent event;
+ mDevice->process(&event, 1);
+
+ ASSERT_NO_FATAL_FAILURE(mapper1->assertProcessWasCalled());
+ ASSERT_NO_FATAL_FAILURE(mapper2->assertProcessWasCalled());
+}
+
+
+// --- InputMapperTest ---
+
+class InputMapperTest : public testing::Test {
+protected:
+ static const char* DEVICE_NAME;
+ static const int32_t DEVICE_ID;
+ static const int32_t DEVICE_GENERATION;
+ static const int32_t DEVICE_CONTROLLER_NUMBER;
+ static const uint32_t DEVICE_CLASSES;
+
+ sp<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ sp<FakeInputListener> mFakeListener;
+ FakeInputReaderContext* mFakeContext;
+ InputDevice* mDevice;
+
+ virtual void SetUp() {
+ mFakeEventHub = new FakeEventHub();
+ mFakePolicy = new FakeInputReaderPolicy();
+ mFakeListener = new FakeInputListener();
+ mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener);
+ InputDeviceIdentifier identifier;
+ identifier.name = DEVICE_NAME;
+ mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION,
+ DEVICE_CONTROLLER_NUMBER, identifier, DEVICE_CLASSES);
+
+ mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
+ }
+
+ virtual void TearDown() {
+ delete mDevice;
+ delete mFakeContext;
+ mFakeListener.clear();
+ mFakePolicy.clear();
+ mFakeEventHub.clear();
+ }
+
+ void addConfigurationProperty(const char* key, const char* value) {
+ mFakeEventHub->addConfigurationProperty(DEVICE_ID, String8(key), String8(value));
+ }
+
+ void addMapperAndConfigure(InputMapper* mapper) {
+ mDevice->addMapper(mapper);
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+ mDevice->reset(ARBITRARY_TIME);
+ }
+
+ void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
+ int32_t orientation) {
+ mFakePolicy->setDisplayInfo(displayId, width, height, orientation);
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ }
+
+ static void process(InputMapper* mapper, nsecs_t when, int32_t deviceId, int32_t type,
+ int32_t code, int32_t value) {
+ RawEvent event;
+ event.when = when;
+ event.deviceId = deviceId;
+ event.type = type;
+ event.code = code;
+ event.value = value;
+ mapper->process(&event);
+ }
+
+ static void assertMotionRange(const InputDeviceInfo& info,
+ int32_t axis, uint32_t source, float min, float max, float flat, float fuzz) {
+ const InputDeviceInfo::MotionRange* range = info.getMotionRange(axis, source);
+ ASSERT_TRUE(range != NULL) << "Axis: " << axis << " Source: " << source;
+ ASSERT_EQ(axis, range->axis) << "Axis: " << axis << " Source: " << source;
+ ASSERT_EQ(source, range->source) << "Axis: " << axis << " Source: " << source;
+ ASSERT_NEAR(min, range->min, EPSILON) << "Axis: " << axis << " Source: " << source;
+ ASSERT_NEAR(max, range->max, EPSILON) << "Axis: " << axis << " Source: " << source;
+ ASSERT_NEAR(flat, range->flat, EPSILON) << "Axis: " << axis << " Source: " << source;
+ ASSERT_NEAR(fuzz, range->fuzz, EPSILON) << "Axis: " << axis << " Source: " << source;
+ }
+
+ static void assertPointerCoords(const PointerCoords& coords,
+ float x, float y, float pressure, float size,
+ float touchMajor, float touchMinor, float toolMajor, float toolMinor,
+ float orientation, float distance) {
+ ASSERT_NEAR(x, coords.getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+ ASSERT_NEAR(y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+ ASSERT_NEAR(pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), EPSILON);
+ ASSERT_NEAR(size, coords.getAxisValue(AMOTION_EVENT_AXIS_SIZE), EPSILON);
+ ASSERT_NEAR(touchMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), 1);
+ ASSERT_NEAR(touchMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), 1);
+ ASSERT_NEAR(toolMajor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), 1);
+ ASSERT_NEAR(toolMinor, coords.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), 1);
+ ASSERT_NEAR(orientation, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), EPSILON);
+ ASSERT_NEAR(distance, coords.getAxisValue(AMOTION_EVENT_AXIS_DISTANCE), EPSILON);
+ }
+
+ static void assertPosition(const sp<FakePointerController>& controller, float x, float y) {
+ float actualX, actualY;
+ controller->getPosition(&actualX, &actualY);
+ ASSERT_NEAR(x, actualX, 1);
+ ASSERT_NEAR(y, actualY, 1);
+ }
+};
+
+const char* InputMapperTest::DEVICE_NAME = "device";
+const int32_t InputMapperTest::DEVICE_ID = 1;
+const int32_t InputMapperTest::DEVICE_GENERATION = 2;
+const int32_t InputMapperTest::DEVICE_CONTROLLER_NUMBER = 0;
+const uint32_t InputMapperTest::DEVICE_CLASSES = 0; // not needed for current tests
+
+
+// --- SwitchInputMapperTest ---
+
+class SwitchInputMapperTest : public InputMapperTest {
+protected:
+};
+
+TEST_F(SwitchInputMapperTest, GetSources) {
+ SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper->getSources());
+}
+
+TEST_F(SwitchInputMapperTest, GetSwitchState) {
+ SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+ addMapperAndConfigure(mapper);
+
+ mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 1);
+ ASSERT_EQ(1, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+
+ mFakeEventHub->setSwitchState(DEVICE_ID, SW_LID, 0);
+ ASSERT_EQ(0, mapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+}
+
+TEST_F(SwitchInputMapperTest, Process) {
+ SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
+ addMapperAndConfigure(mapper);
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_LID, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_JACK_PHYSICAL_INSERT, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_HEADPHONE_INSERT, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+
+ NotifySwitchArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySwitchWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ((1 << SW_LID) | (1 << SW_JACK_PHYSICAL_INSERT), args.switchValues);
+ ASSERT_EQ((1 << SW_LID) | (1 << SW_JACK_PHYSICAL_INSERT) | (1 << SW_HEADPHONE_INSERT),
+ args.switchMask);
+ ASSERT_EQ(uint32_t(0), args.policyFlags);
+}
+
+
+// --- KeyboardInputMapperTest ---
+
+class KeyboardInputMapperTest : public InputMapperTest {
+protected:
+ void testDPadKeyRotation(KeyboardInputMapper* mapper,
+ int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode);
+};
+
+void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper* mapper,
+ int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode) {
+ NotifyKeyArgs args;
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, 1);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(originalScanCode, args.scanCode);
+ ASSERT_EQ(rotatedKeyCode, args.keyCode);
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(originalScanCode, args.scanCode);
+ ASSERT_EQ(rotatedKeyCode, args.keyCode);
+}
+
+
+TEST_F(KeyboardInputMapperTest, GetSources) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper->getSources());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) {
+ const int32_t USAGE_A = 0x070004;
+ const int32_t USAGE_UNKNOWN = 0x07ffff;
+ mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+ mFakeEventHub->addKey(DEVICE_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
+
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ // Key down by scan code.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_HOME, 1);
+ NotifyKeyArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key up by scan code.
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+ EV_KEY, KEY_HOME, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key down by usage code.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_MSC, MSC_SCAN, USAGE_A);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, 0, 1);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(AKEYCODE_A, args.keyCode);
+ ASSERT_EQ(0, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key up by usage code.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_MSC, MSC_SCAN, USAGE_A);
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+ EV_KEY, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEYCODE_A, args.keyCode);
+ ASSERT_EQ(0, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key down with unknown scan code or usage code.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_UNKNOWN, 1);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(0, args.keyCode);
+ ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(0U, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key up with unknown scan code or usage code.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+ EV_KEY, KEY_UNKNOWN, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(0, args.keyCode);
+ ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(0U, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) {
+ mFakeEventHub->addKey(DEVICE_ID, KEY_LEFTSHIFT, 0, AKEYCODE_SHIFT_LEFT, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_A, 0, AKEYCODE_A, 0);
+
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ // Initial metastate.
+ ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+
+ // Metakey down.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_LEFTSHIFT, 1);
+ NotifyKeyArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+ ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled());
+
+ // Key down.
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+ EV_KEY, KEY_A, 1);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+
+ // Key up.
+ process(mapper, ARBITRARY_TIME + 2, DEVICE_ID,
+ EV_KEY, KEY_A, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
+
+ // Metakey up.
+ process(mapper, ARBITRARY_TIME + 3, DEVICE_ID,
+ EV_KEY, KEY_LEFTSHIFT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+ ASSERT_NO_FATAL_FAILURE(mFakeContext->assertUpdateGlobalMetaStateWasCalled());
+}
+
+TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) {
+ mFakeEventHub->addKey(DEVICE_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
+
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_90);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+}
+
+TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
+ mFakeEventHub->addKey(DEVICE_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
+
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addConfigurationProperty("keyboard.orientationAware", "1");
+ addMapperAndConfigure(mapper);
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_0);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_RIGHT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_DOWN));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_LEFT));
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_90);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_LEFT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_UP));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_RIGHT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_DOWN));
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_180);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_LEFT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_UP));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_RIGHT));
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_270);
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_RIGHT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_RIGHT, AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_DOWN));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_DOWN, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_LEFT));
+ ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
+ KEY_LEFT, AKEYCODE_DPAD_LEFT, AKEYCODE_DPAD_UP));
+
+ // Special case: if orientation changes while key is down, we still emit the same keycode
+ // in the key up as we did in the key down.
+ NotifyKeyArgs args;
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_270);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, 1);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(KEY_UP, args.scanCode);
+ ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_180);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(KEY_UP, args.scanCode);
+ ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
+}
+
+TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 1);
+ ASSERT_EQ(1, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+
+ mFakeEventHub->setKeyCodeState(DEVICE_ID, AKEYCODE_A, 0);
+ ASSERT_EQ(0, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+}
+
+TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 1);
+ ASSERT_EQ(1, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+
+ mFakeEventHub->setScanCodeState(DEVICE_ID, KEY_A, 0);
+ ASSERT_EQ(0, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+}
+
+TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) {
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ mFakeEventHub->addKey(DEVICE_ID, KEY_A, 0, AKEYCODE_A, 0);
+
+ const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
+ uint8_t flags[2] = { 0, 0 };
+ ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 1, keyCodes, flags));
+ ASSERT_TRUE(flags[0]);
+ ASSERT_FALSE(flags[1]);
+}
+
+TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds) {
+ mFakeEventHub->addLed(DEVICE_ID, LED_CAPSL, true /*initially on*/);
+ mFakeEventHub->addLed(DEVICE_ID, LED_NUML, false /*initially off*/);
+ mFakeEventHub->addLed(DEVICE_ID, LED_SCROLLL, false /*initially off*/);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
+
+ KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
+ AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ addMapperAndConfigure(mapper);
+
+ // Initialization should have turned all of the lights off.
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+
+ // Toggle caps lock on.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_CAPSLOCK, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_CAPSLOCK, 0);
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_CAPS_LOCK_ON, mapper->getMetaState());
+
+ // Toggle num lock on.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_NUMLOCK, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_NUMLOCK, 0);
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON, mapper->getMetaState());
+
+ // Toggle caps lock off.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_CAPSLOCK, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_CAPSLOCK, 0);
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_NUM_LOCK_ON, mapper->getMetaState());
+
+ // Toggle scroll lock on.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_SCROLLLOCK, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_SCROLLLOCK, 0);
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_NUM_LOCK_ON | AMETA_SCROLL_LOCK_ON, mapper->getMetaState());
+
+ // Toggle num lock off.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_NUMLOCK, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_NUMLOCK, 0);
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_SCROLL_LOCK_ON, mapper->getMetaState());
+
+ // Toggle scroll lock off.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_SCROLLLOCK, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_SCROLLLOCK, 0);
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
+ ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
+ ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
+}
+
+
+// --- CursorInputMapperTest ---
+
+class CursorInputMapperTest : public InputMapperTest {
+protected:
+ static const int32_t TRACKBALL_MOVEMENT_THRESHOLD;
+
+ sp<FakePointerController> mFakePointerController;
+
+ virtual void SetUp() {
+ InputMapperTest::SetUp();
+
+ mFakePointerController = new FakePointerController();
+ mFakePolicy->setPointerController(DEVICE_ID, mFakePointerController);
+ }
+
+ void testMotionRotation(CursorInputMapper* mapper,
+ int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY);
+};
+
+const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6;
+
+void CursorInputMapperTest::testMotionRotation(CursorInputMapper* mapper,
+ int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY) {
+ NotifyMotionArgs args;
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, originalX);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, originalY);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ float(rotatedX) / TRACKBALL_MOVEMENT_THRESHOLD,
+ float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD,
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "pointer");
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper->getSources());
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper->getSources());
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "pointer");
+ addMapperAndConfigure(mapper);
+
+ InputDeviceInfo info;
+ mapper->populateDeviceInfo(&info);
+
+ // Initially there may not be a valid motion range.
+ ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
+ ASSERT_EQ(NULL, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
+ AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+
+ // When the bounds are set, then there should be a valid motion range.
+ mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
+
+ InputDeviceInfo info2;
+ mapper->populateDeviceInfo(&info2);
+
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
+ AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE,
+ 1, 800 - 1, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
+ AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE,
+ 2, 480 - 1, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
+ AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_MOUSE,
+ 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ InputDeviceInfo info;
+ mapper->populateDeviceInfo(&info);
+
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
+ AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
+ -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
+ AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_TRACKBALL,
+ -1.0f, 1.0f, 0.0f, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
+ AINPUT_MOTION_RANGE_PRESSURE, AINPUT_SOURCE_TRACKBALL,
+ 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ NotifyMotionArgs args;
+
+ // Button press.
+ // Mostly testing non x/y behavior here so we don't need to check again elsewhere.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
+ ASSERT_EQ(uint32_t(0), args.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(0, args.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, args.buttonState);
+ ASSERT_EQ(0, args.edgeFlags);
+ ASSERT_EQ(uint32_t(1), args.pointerCount);
+ ASSERT_EQ(0, args.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
+ ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Button release. Should have same down time.
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0);
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, args.source);
+ ASSERT_EQ(uint32_t(0), args.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(0, args.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(0, args.buttonState);
+ ASSERT_EQ(0, args.edgeFlags);
+ ASSERT_EQ(uint32_t(1), args.pointerCount);
+ ASSERT_EQ(0, args.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.xPrecision);
+ ASSERT_EQ(TRACKBALL_MOVEMENT_THRESHOLD, args.yPrecision);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs args;
+
+ // Motion in X but not Y.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // Motion in Y but not X.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, -2);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs args;
+
+ // Button press.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // Button release.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs args;
+
+ // Combined X, Y and Button.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, -2);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 1.0f / TRACKBALL_MOVEMENT_THRESHOLD, -2.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // Move X, Y a bit while pressed.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 2);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 2.0f / TRACKBALL_MOVEMENT_THRESHOLD, 1.0f / TRACKBALL_MOVEMENT_THRESHOLD,
+ 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // Release Button.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateMotions) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addMapperAndConfigure(mapper);
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_ORIENTATION_90);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1));
+}
+
+TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldRotateMotions) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "navigation");
+ addConfigurationProperty("cursor.orientationAware", "1");
+ addMapperAndConfigure(mapper);
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1));
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_90);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, 1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, -1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, 1));
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_180);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, -1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, -1));
+
+ setDisplayInfoAndReconfigure(DISPLAY_ID,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_270);
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, 1, -1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 1, -1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, 0, 0, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 1, -1, 1, 1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, 0, -1, 1, 0));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, -1, 1, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 0, 0, -1));
+ ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, -1));
+}
+
+TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "pointer");
+ addMapperAndConfigure(mapper);
+
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(100, 200);
+ mFakePointerController->setButtonState(0);
+
+ NotifyMotionArgs motionArgs;
+ NotifyKeyArgs keyArgs;
+
+ // press BTN_LEFT, release BTN_LEFT
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, mFakePointerController->getButtonState());
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
+ motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
+ mFakePointerController->getButtonState());
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ // press BTN_BACK, release BTN_BACK
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ // press BTN_SIDE, release BTN_SIDE
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ // press BTN_FORWARD, release BTN_FORWARD
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+
+ // press BTN_EXTRA, release BTN_EXTRA
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, mFakePointerController->getButtonState());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ 100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+}
+
+TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) {
+ CursorInputMapper* mapper = new CursorInputMapper(mDevice);
+ addConfigurationProperty("cursor.mode", "pointer");
+ addMapperAndConfigure(mapper);
+
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(100, 200);
+ mFakePointerController->setButtonState(0);
+
+ NotifyMotionArgs args;
+
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 10);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 20);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ 110.0f, 220.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(assertPosition(mFakePointerController, 110.0f, 220.0f));
+}
+
+
+// --- TouchInputMapperTest ---
+
+class TouchInputMapperTest : public InputMapperTest {
+protected:
+ static const int32_t RAW_X_MIN;
+ static const int32_t RAW_X_MAX;
+ static const int32_t RAW_Y_MIN;
+ static const int32_t RAW_Y_MAX;
+ static const int32_t RAW_TOUCH_MIN;
+ static const int32_t RAW_TOUCH_MAX;
+ static const int32_t RAW_TOOL_MIN;
+ static const int32_t RAW_TOOL_MAX;
+ static const int32_t RAW_PRESSURE_MIN;
+ static const int32_t RAW_PRESSURE_MAX;
+ static const int32_t RAW_ORIENTATION_MIN;
+ static const int32_t RAW_ORIENTATION_MAX;
+ static const int32_t RAW_DISTANCE_MIN;
+ static const int32_t RAW_DISTANCE_MAX;
+ static const int32_t RAW_TILT_MIN;
+ static const int32_t RAW_TILT_MAX;
+ static const int32_t RAW_ID_MIN;
+ static const int32_t RAW_ID_MAX;
+ static const int32_t RAW_SLOT_MIN;
+ static const int32_t RAW_SLOT_MAX;
+ static const float X_PRECISION;
+ static const float Y_PRECISION;
+
+ static const float GEOMETRIC_SCALE;
+ static const TouchAffineTransformation AFFINE_TRANSFORM;
+
+ static const VirtualKeyDefinition VIRTUAL_KEYS[2];
+
+ enum Axes {
+ POSITION = 1 << 0,
+ TOUCH = 1 << 1,
+ TOOL = 1 << 2,
+ PRESSURE = 1 << 3,
+ ORIENTATION = 1 << 4,
+ MINOR = 1 << 5,
+ ID = 1 << 6,
+ DISTANCE = 1 << 7,
+ TILT = 1 << 8,
+ SLOT = 1 << 9,
+ TOOL_TYPE = 1 << 10,
+ };
+
+ void prepareDisplay(int32_t orientation);
+ void prepareVirtualKeys();
+ void prepareLocationCalibration();
+ int32_t toRawX(float displayX);
+ int32_t toRawY(float displayY);
+ float toCookedX(float rawX, float rawY);
+ float toCookedY(float rawX, float rawY);
+ float toDisplayX(int32_t rawX);
+ float toDisplayY(int32_t rawY);
+};
+
+const int32_t TouchInputMapperTest::RAW_X_MIN = 25;
+const int32_t TouchInputMapperTest::RAW_X_MAX = 1019;
+const int32_t TouchInputMapperTest::RAW_Y_MIN = 30;
+const int32_t TouchInputMapperTest::RAW_Y_MAX = 1009;
+const int32_t TouchInputMapperTest::RAW_TOUCH_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_TOUCH_MAX = 31;
+const int32_t TouchInputMapperTest::RAW_TOOL_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_TOOL_MAX = 15;
+const int32_t TouchInputMapperTest::RAW_PRESSURE_MIN = RAW_TOUCH_MIN;
+const int32_t TouchInputMapperTest::RAW_PRESSURE_MAX = RAW_TOUCH_MAX;
+const int32_t TouchInputMapperTest::RAW_ORIENTATION_MIN = -7;
+const int32_t TouchInputMapperTest::RAW_ORIENTATION_MAX = 7;
+const int32_t TouchInputMapperTest::RAW_DISTANCE_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_DISTANCE_MAX = 7;
+const int32_t TouchInputMapperTest::RAW_TILT_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_TILT_MAX = 150;
+const int32_t TouchInputMapperTest::RAW_ID_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_ID_MAX = 9;
+const int32_t TouchInputMapperTest::RAW_SLOT_MIN = 0;
+const int32_t TouchInputMapperTest::RAW_SLOT_MAX = 9;
+const float TouchInputMapperTest::X_PRECISION = float(RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_WIDTH;
+const float TouchInputMapperTest::Y_PRECISION = float(RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_HEIGHT;
+const TouchAffineTransformation TouchInputMapperTest::AFFINE_TRANSFORM =
+ TouchAffineTransformation(1, -2, 3, -4, 5, -6);
+
+const float TouchInputMapperTest::GEOMETRIC_SCALE =
+ avg(float(DISPLAY_WIDTH) / (RAW_X_MAX - RAW_X_MIN + 1),
+ float(DISPLAY_HEIGHT) / (RAW_Y_MAX - RAW_Y_MIN + 1));
+
+const VirtualKeyDefinition TouchInputMapperTest::VIRTUAL_KEYS[2] = {
+ { KEY_HOME, 60, DISPLAY_HEIGHT + 15, 20, 20 },
+ { KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 },
+};
+
+void TouchInputMapperTest::prepareDisplay(int32_t orientation) {
+ setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation);
+}
+
+void TouchInputMapperTest::prepareVirtualKeys() {
+ mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[0]);
+ mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[1]);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, 0, AKEYCODE_MENU, POLICY_FLAG_WAKE);
+}
+
+void TouchInputMapperTest::prepareLocationCalibration() {
+ mFakePolicy->setTouchAffineTransformation(AFFINE_TRANSFORM);
+}
+
+int32_t TouchInputMapperTest::toRawX(float displayX) {
+ return int32_t(displayX * (RAW_X_MAX - RAW_X_MIN + 1) / DISPLAY_WIDTH + RAW_X_MIN);
+}
+
+int32_t TouchInputMapperTest::toRawY(float displayY) {
+ return int32_t(displayY * (RAW_Y_MAX - RAW_Y_MIN + 1) / DISPLAY_HEIGHT + RAW_Y_MIN);
+}
+
+float TouchInputMapperTest::toCookedX(float rawX, float rawY) {
+ AFFINE_TRANSFORM.applyTo(rawX, rawY);
+ return rawX;
+}
+
+float TouchInputMapperTest::toCookedY(float rawX, float rawY) {
+ AFFINE_TRANSFORM.applyTo(rawX, rawY);
+ return rawY;
+}
+
+float TouchInputMapperTest::toDisplayX(int32_t rawX) {
+ return float(rawX - RAW_X_MIN) * DISPLAY_WIDTH / (RAW_X_MAX - RAW_X_MIN + 1);
+}
+
+float TouchInputMapperTest::toDisplayY(int32_t rawY) {
+ return float(rawY - RAW_Y_MIN) * DISPLAY_HEIGHT / (RAW_Y_MAX - RAW_Y_MIN + 1);
+}
+
+
+// --- SingleTouchInputMapperTest ---
+
+class SingleTouchInputMapperTest : public TouchInputMapperTest {
+protected:
+ void prepareButtons();
+ void prepareAxes(int axes);
+
+ void processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y);
+ void processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y);
+ void processUp(SingleTouchInputMapper* mappery);
+ void processPressure(SingleTouchInputMapper* mapper, int32_t pressure);
+ void processToolMajor(SingleTouchInputMapper* mapper, int32_t toolMajor);
+ void processDistance(SingleTouchInputMapper* mapper, int32_t distance);
+ void processTilt(SingleTouchInputMapper* mapper, int32_t tiltX, int32_t tiltY);
+ void processKey(SingleTouchInputMapper* mapper, int32_t code, int32_t value);
+ void processSync(SingleTouchInputMapper* mapper);
+};
+
+void SingleTouchInputMapperTest::prepareButtons() {
+ mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
+}
+
+void SingleTouchInputMapperTest::prepareAxes(int axes) {
+ if (axes & POSITION) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_X,
+ RAW_X_MIN, RAW_X_MAX, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_Y,
+ RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+ }
+ if (axes & PRESSURE) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_PRESSURE,
+ RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0);
+ }
+ if (axes & TOOL) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_TOOL_WIDTH,
+ RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0);
+ }
+ if (axes & DISTANCE) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_DISTANCE,
+ RAW_DISTANCE_MIN, RAW_DISTANCE_MAX, 0, 0);
+ }
+ if (axes & TILT) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_TILT_X,
+ RAW_TILT_MIN, RAW_TILT_MAX, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_TILT_Y,
+ RAW_TILT_MIN, RAW_TILT_MAX, 0, 0);
+ }
+}
+
+void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, x);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, y);
+}
+
+void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, x);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, y);
+}
+
+void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper* mapper) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0);
+}
+
+void SingleTouchInputMapperTest::processPressure(
+ SingleTouchInputMapper* mapper, int32_t pressure) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, pressure);
+}
+
+void SingleTouchInputMapperTest::processToolMajor(
+ SingleTouchInputMapper* mapper, int32_t toolMajor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, toolMajor);
+}
+
+void SingleTouchInputMapperTest::processDistance(
+ SingleTouchInputMapper* mapper, int32_t distance) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_DISTANCE, distance);
+}
+
+void SingleTouchInputMapperTest::processTilt(
+ SingleTouchInputMapper* mapper, int32_t tiltX, int32_t tiltY) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_X, tiltX);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_Y, tiltY);
+}
+
+void SingleTouchInputMapperTest::processKey(
+ SingleTouchInputMapper* mapper, int32_t code, int32_t value) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, value);
+}
+
+void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+}
+
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ prepareButtons();
+ prepareAxes(POSITION);
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndIsACursor_ReturnsTouchPad) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_X);
+ mFakeEventHub->addRelativeAxis(DEVICE_ID, REL_Y);
+ prepareButtons();
+ prepareAxes(POSITION);
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchPad_ReturnsTouchPad) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ prepareButtons();
+ prepareAxes(POSITION);
+ addConfigurationProperty("touch.deviceType", "touchPad");
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ prepareButtons();
+ prepareAxes(POSITION);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ addMapperAndConfigure(mapper);
+
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper->getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ // Unknown key.
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
+
+ // Virtual key is down.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled());
+
+ ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME));
+
+ // Virtual key is up.
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled());
+
+ ASSERT_EQ(AKEY_STATE_UP, mapper->getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_HOME));
+}
+
+TEST_F(SingleTouchInputMapperTest, GetScanCodeState) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ // Unknown key.
+ ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
+
+ // Virtual key is down.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled());
+
+ ASSERT_EQ(AKEY_STATE_VIRTUAL, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME));
+
+ // Virtual key is up.
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled());
+
+ ASSERT_EQ(AKEY_STATE_UP, mapper->getScanCodeState(AINPUT_SOURCE_ANY, KEY_HOME));
+}
+
+TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ const int32_t keys[2] = { AKEYCODE_HOME, AKEYCODE_A };
+ uint8_t flags[2] = { 0, 0 };
+ ASSERT_TRUE(mapper->markSupportedKeyCodes(AINPUT_SOURCE_ANY, 2, keys, flags));
+ ASSERT_TRUE(flags[0]);
+ ASSERT_FALSE(flags[1]);
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ NotifyKeyArgs args;
+
+ // Press virtual key.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Release virtual key.
+ processUp(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(POLICY_FLAG_VIRTUAL, args.policyFlags);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, args.flags);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Should not have sent any motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ NotifyKeyArgs keyArgs;
+
+ // Press virtual key.
+ int32_t x = toRawX(VIRTUAL_KEYS[0].centerX);
+ int32_t y = toRawY(VIRTUAL_KEYS[0].centerY);
+ processDown(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, keyArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source);
+ ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY, keyArgs.flags);
+ ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode);
+ ASSERT_EQ(KEY_HOME, keyArgs.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState);
+ ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime);
+
+ // Move out of bounds. This should generate a cancel and a pointer down since we moved
+ // into the display area.
+ y -= 100;
+ processMove(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(ARBITRARY_TIME, keyArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, keyArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, keyArgs.source);
+ ASSERT_EQ(POLICY_FLAG_VIRTUAL, keyArgs.policyFlags);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY
+ | AKEY_EVENT_FLAG_CANCELED, keyArgs.flags);
+ ASSERT_EQ(AKEYCODE_HOME, keyArgs.keyCode);
+ ASSERT_EQ(KEY_HOME, keyArgs.scanCode);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, keyArgs.metaState);
+ ASSERT_EQ(ARBITRARY_TIME, keyArgs.downTime);
+
+ NotifyMotionArgs motionArgs;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Keep moving out of bounds. Should generate a pointer move.
+ y -= 50;
+ processMove(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Release out of bounds. Should generate a pointer up.
+ processUp(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ NotifyMotionArgs motionArgs;
+
+ // Initially go down out of bounds.
+ int32_t x = -10;
+ int32_t y = -10;
+ processDown(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+ // Move into the display area. Should generate a pointer down.
+ x = 50;
+ y = 75;
+ processMove(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Release. Should generate a pointer up.
+ processUp(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ NotifyMotionArgs motionArgs;
+
+ // Down.
+ int32_t x = 100;
+ int32_t y = 125;
+ processDown(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Move.
+ x += 50;
+ y += 75;
+ processMove(mapper, x, y);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Up.
+ processUp(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_DoesNotRotateMotions) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareButtons();
+ prepareAxes(POSITION);
+ addConfigurationProperty("touch.orientationAware", "0");
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs args;
+
+ // Rotation 90.
+ prepareDisplay(DISPLAY_ORIENTATION_90);
+ processDown(mapper, toRawX(50), toRawY(75));
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+ ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_RotatesMotions) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareButtons();
+ prepareAxes(POSITION);
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs args;
+
+ // Rotation 0.
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ processDown(mapper, toRawX(50), toRawY(75));
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+ ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+
+ // Rotation 90.
+ prepareDisplay(DISPLAY_ORIENTATION_90);
+ processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50));
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+ ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+
+ // Rotation 180.
+ prepareDisplay(DISPLAY_ORIENTATION_180);
+ processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+ ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+
+ // Rotation 270.
+ prepareDisplay(DISPLAY_ORIENTATION_270);
+ processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NEAR(50, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X), 1);
+ ASSERT_NEAR(75, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y), 1);
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION | PRESSURE | TOOL | DISTANCE | TILT);
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawPressure = 10;
+ int32_t rawToolMajor = 12;
+ int32_t rawDistance = 2;
+ int32_t rawTiltX = 30;
+ int32_t rawTiltY = 110;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float pressure = float(rawPressure) / RAW_PRESSURE_MAX;
+ float size = float(rawToolMajor) / RAW_TOOL_MAX;
+ float tool = float(rawToolMajor) * GEOMETRIC_SCALE;
+ float distance = float(rawDistance);
+
+ float tiltCenter = (RAW_TILT_MAX + RAW_TILT_MIN) * 0.5f;
+ float tiltScale = M_PI / 180;
+ float tiltXAngle = (rawTiltX - tiltCenter) * tiltScale;
+ float tiltYAngle = (rawTiltY - tiltCenter) * tiltScale;
+ float orientation = atan2f(-sinf(tiltXAngle), sinf(tiltYAngle));
+ float tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
+
+ processDown(mapper, rawX, rawY);
+ processPressure(mapper, rawPressure);
+ processToolMajor(mapper, rawToolMajor);
+ processDistance(mapper, rawDistance);
+ processTilt(mapper, rawTiltX, rawTiltY);
+ processSync(mapper);
+
+ NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, pressure, size, tool, tool, tool, tool, orientation, distance));
+ ASSERT_EQ(tilt, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TILT));
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareLocationCalibration();
+ prepareButtons();
+ prepareAxes(POSITION);
+ addMapperAndConfigure(mapper);
+
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+
+ float x = toDisplayX(toCookedX(rawX, rawY));
+ float y = toDisplayY(toCookedY(rawX, rawY));
+
+ processDown(mapper, rawX, rawY);
+ processSync(mapper);
+
+ NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, 1, 0, 0, 0, 0, 0, 0, 0));
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs motionArgs;
+ NotifyKeyArgs keyArgs;
+
+ processDown(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+
+ // press BTN_LEFT, release BTN_LEFT
+ processKey(mapper, BTN_LEFT, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
+
+ processKey(mapper, BTN_LEFT, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
+ processKey(mapper, BTN_RIGHT, 1);
+ processKey(mapper, BTN_MIDDLE, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
+ motionArgs.buttonState);
+
+ processKey(mapper, BTN_RIGHT, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_MIDDLE, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ // press BTN_BACK, release BTN_BACK
+ processKey(mapper, BTN_BACK, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_BACK, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ // press BTN_SIDE, release BTN_SIDE
+ processKey(mapper, BTN_SIDE, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_SIDE, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ // press BTN_FORWARD, release BTN_FORWARD
+ processKey(mapper, BTN_FORWARD, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_FORWARD, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+
+ // press BTN_EXTRA, release BTN_EXTRA
+ processKey(mapper, BTN_EXTRA, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_EXTRA, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+
+ // press BTN_STYLUS, release BTN_STYLUS
+ processKey(mapper, BTN_STYLUS, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY, motionArgs.buttonState);
+
+ processKey(mapper, BTN_STYLUS, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ // press BTN_STYLUS2, release BTN_STYLUS2
+ processKey(mapper, BTN_STYLUS2, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
+
+ processKey(mapper, BTN_STYLUS2, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ // release touch
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs motionArgs;
+
+ // default tool type is finger
+ processDown(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // eraser
+ processKey(mapper, BTN_TOOL_RUBBER, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType);
+
+ // stylus
+ processKey(mapper, BTN_TOOL_RUBBER, 0);
+ processKey(mapper, BTN_TOOL_PEN, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // brush
+ processKey(mapper, BTN_TOOL_PEN, 0);
+ processKey(mapper, BTN_TOOL_BRUSH, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // pencil
+ processKey(mapper, BTN_TOOL_BRUSH, 0);
+ processKey(mapper, BTN_TOOL_PENCIL, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // airbrush
+ processKey(mapper, BTN_TOOL_PENCIL, 0);
+ processKey(mapper, BTN_TOOL_AIRBRUSH, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // mouse
+ processKey(mapper, BTN_TOOL_AIRBRUSH, 0);
+ processKey(mapper, BTN_TOOL_MOUSE, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+
+ // lens
+ processKey(mapper, BTN_TOOL_MOUSE, 0);
+ processKey(mapper, BTN_TOOL_LENS, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+
+ // double-tap
+ processKey(mapper, BTN_TOOL_LENS, 0);
+ processKey(mapper, BTN_TOOL_DOUBLETAP, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // triple-tap
+ processKey(mapper, BTN_TOOL_DOUBLETAP, 0);
+ processKey(mapper, BTN_TOOL_TRIPLETAP, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // quad-tap
+ processKey(mapper, BTN_TOOL_TRIPLETAP, 0);
+ processKey(mapper, BTN_TOOL_QUADTAP, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // finger
+ processKey(mapper, BTN_TOOL_QUADTAP, 0);
+ processKey(mapper, BTN_TOOL_FINGER, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // stylus trumps finger
+ processKey(mapper, BTN_TOOL_PEN, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // eraser trumps stylus
+ processKey(mapper, BTN_TOOL_RUBBER, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType);
+
+ // mouse trumps eraser
+ processKey(mapper, BTN_TOOL_MOUSE, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+
+ // back to default tool type
+ processKey(mapper, BTN_TOOL_MOUSE, 0);
+ processKey(mapper, BTN_TOOL_RUBBER, 0);
+ processKey(mapper, BTN_TOOL_PEN, 0);
+ processKey(mapper, BTN_TOOL_FINGER, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ mFakeEventHub->addKey(DEVICE_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0);
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs motionArgs;
+
+ // initially hovering because BTN_TOUCH not sent yet, pressure defaults to 0
+ processKey(mapper, BTN_TOOL_FINGER, 1);
+ processMove(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // move a little
+ processMove(mapper, 150, 250);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // down when BTN_TOUCH is pressed, pressure defaults to 1
+ processKey(mapper, BTN_TOUCH, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // up when BTN_TOUCH is released, hover restored
+ processKey(mapper, BTN_TOUCH, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // exit hover when pointer goes away
+ processKey(mapper, BTN_TOOL_FINGER, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsValueIsZero) {
+ SingleTouchInputMapper* mapper = new SingleTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION | PRESSURE);
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs motionArgs;
+
+ // initially hovering because pressure is 0
+ processDown(mapper, 100, 200);
+ processPressure(mapper, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // move a little
+ processMove(mapper, 150, 250);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // down when pressure is non-zero
+ processPressure(mapper, RAW_PRESSURE_MAX);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // up when pressure becomes 0, hover restored
+ processPressure(mapper, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // exit hover when pointer goes away
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+
+// --- MultiTouchInputMapperTest ---
+
+class MultiTouchInputMapperTest : public TouchInputMapperTest {
+protected:
+ void prepareAxes(int axes);
+
+ void processPosition(MultiTouchInputMapper* mapper, int32_t x, int32_t y);
+ void processTouchMajor(MultiTouchInputMapper* mapper, int32_t touchMajor);
+ void processTouchMinor(MultiTouchInputMapper* mapper, int32_t touchMinor);
+ void processToolMajor(MultiTouchInputMapper* mapper, int32_t toolMajor);
+ void processToolMinor(MultiTouchInputMapper* mapper, int32_t toolMinor);
+ void processOrientation(MultiTouchInputMapper* mapper, int32_t orientation);
+ void processPressure(MultiTouchInputMapper* mapper, int32_t pressure);
+ void processDistance(MultiTouchInputMapper* mapper, int32_t distance);
+ void processId(MultiTouchInputMapper* mapper, int32_t id);
+ void processSlot(MultiTouchInputMapper* mapper, int32_t slot);
+ void processToolType(MultiTouchInputMapper* mapper, int32_t toolType);
+ void processKey(MultiTouchInputMapper* mapper, int32_t code, int32_t value);
+ void processMTSync(MultiTouchInputMapper* mapper);
+ void processSync(MultiTouchInputMapper* mapper);
+};
+
+void MultiTouchInputMapperTest::prepareAxes(int axes) {
+ if (axes & POSITION) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_POSITION_X,
+ RAW_X_MIN, RAW_X_MAX, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_POSITION_Y,
+ RAW_Y_MIN, RAW_Y_MAX, 0, 0);
+ }
+ if (axes & TOUCH) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TOUCH_MAJOR,
+ RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0);
+ if (axes & MINOR) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TOUCH_MINOR,
+ RAW_TOUCH_MIN, RAW_TOUCH_MAX, 0, 0);
+ }
+ }
+ if (axes & TOOL) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_WIDTH_MAJOR,
+ RAW_TOOL_MIN, RAW_TOOL_MAX, 0, 0);
+ if (axes & MINOR) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_WIDTH_MINOR,
+ RAW_TOOL_MAX, RAW_TOOL_MAX, 0, 0);
+ }
+ }
+ if (axes & ORIENTATION) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_ORIENTATION,
+ RAW_ORIENTATION_MIN, RAW_ORIENTATION_MAX, 0, 0);
+ }
+ if (axes & PRESSURE) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_PRESSURE,
+ RAW_PRESSURE_MIN, RAW_PRESSURE_MAX, 0, 0);
+ }
+ if (axes & DISTANCE) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_DISTANCE,
+ RAW_DISTANCE_MIN, RAW_DISTANCE_MAX, 0, 0);
+ }
+ if (axes & ID) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TRACKING_ID,
+ RAW_ID_MIN, RAW_ID_MAX, 0, 0);
+ }
+ if (axes & SLOT) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_SLOT,
+ RAW_SLOT_MIN, RAW_SLOT_MAX, 0, 0);
+ mFakeEventHub->setAbsoluteAxisValue(DEVICE_ID, ABS_MT_SLOT, 0);
+ }
+ if (axes & TOOL_TYPE) {
+ mFakeEventHub->addAbsoluteAxis(DEVICE_ID, ABS_MT_TOOL_TYPE,
+ 0, MT_TOOL_MAX, 0, 0);
+ }
+}
+
+void MultiTouchInputMapperTest::processPosition(
+ MultiTouchInputMapper* mapper, int32_t x, int32_t y) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_X, x);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, y);
+}
+
+void MultiTouchInputMapperTest::processTouchMajor(
+ MultiTouchInputMapper* mapper, int32_t touchMajor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, touchMajor);
+}
+
+void MultiTouchInputMapperTest::processTouchMinor(
+ MultiTouchInputMapper* mapper, int32_t touchMinor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, touchMinor);
+}
+
+void MultiTouchInputMapperTest::processToolMajor(
+ MultiTouchInputMapper* mapper, int32_t toolMajor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, toolMajor);
+}
+
+void MultiTouchInputMapperTest::processToolMinor(
+ MultiTouchInputMapper* mapper, int32_t toolMinor) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, toolMinor);
+}
+
+void MultiTouchInputMapperTest::processOrientation(
+ MultiTouchInputMapper* mapper, int32_t orientation) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, orientation);
+}
+
+void MultiTouchInputMapperTest::processPressure(
+ MultiTouchInputMapper* mapper, int32_t pressure) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, pressure);
+}
+
+void MultiTouchInputMapperTest::processDistance(
+ MultiTouchInputMapper* mapper, int32_t distance) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_DISTANCE, distance);
+}
+
+void MultiTouchInputMapperTest::processId(
+ MultiTouchInputMapper* mapper, int32_t id) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, id);
+}
+
+void MultiTouchInputMapperTest::processSlot(
+ MultiTouchInputMapper* mapper, int32_t slot) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_SLOT, slot);
+}
+
+void MultiTouchInputMapperTest::processToolType(
+ MultiTouchInputMapper* mapper, int32_t toolType) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOOL_TYPE, toolType);
+}
+
+void MultiTouchInputMapperTest::processKey(
+ MultiTouchInputMapper* mapper, int32_t code, int32_t value) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, value);
+}
+
+void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper* mapper) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0);
+}
+
+void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper* mapper) {
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
+}
+
+
+TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackingIds) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ NotifyMotionArgs motionArgs;
+
+ // Two fingers down at once.
+ int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
+ processPosition(mapper, x1, y1);
+ processMTSync(mapper);
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Move.
+ x1 += 10; y1 += 15; x2 += 5; y2 -= 10;
+ processPosition(mapper, x1, y1);
+ processMTSync(mapper);
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // First finger up.
+ x2 += 15; y2 -= 20;
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Move.
+ x2 += 20; y2 -= 25;
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // New finger down.
+ int32_t x3 = 700, y3 = 300;
+ processPosition(mapper, x2, y2);
+ processMTSync(mapper);
+ processPosition(mapper, x3, y3);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Second finger up.
+ x3 += 30; y3 -= 20;
+ processPosition(mapper, x3, y3);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Last finger up.
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, motionArgs.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, motionArgs.source);
+ ASSERT_EQ(uint32_t(0), motionArgs.policyFlags);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.flags);
+ ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, motionArgs.metaState);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(0, motionArgs.edgeFlags);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
+ ASSERT_NEAR(Y_PRECISION, motionArgs.yPrecision, EPSILON);
+ ASSERT_EQ(ARBITRARY_TIME, motionArgs.downTime);
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingIds) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | ID);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ NotifyMotionArgs motionArgs;
+
+ // Two fingers down at once.
+ int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
+ processPosition(mapper, x1, y1);
+ processId(mapper, 1);
+ processMTSync(mapper);
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Move.
+ x1 += 10; y1 += 15; x2 += 5; y2 -= 10;
+ processPosition(mapper, x1, y1);
+ processId(mapper, 1);
+ processMTSync(mapper);
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // First finger up.
+ x2 += 15; y2 -= 20;
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Move.
+ x2 += 20; y2 -= 25;
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // New finger down.
+ int32_t x3 = 700, y3 = 300;
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processMTSync(mapper);
+ processPosition(mapper, x3, y3);
+ processId(mapper, 3);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Second finger up.
+ x3 += 30; y3 -= 20;
+ processPosition(mapper, x3, y3);
+ processId(mapper, 3);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Last finger up.
+ processMTSync(mapper);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | ID | SLOT);
+ prepareVirtualKeys();
+ addMapperAndConfigure(mapper);
+
+ mFakeContext->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
+
+ NotifyMotionArgs motionArgs;
+
+ // Two fingers down at once.
+ int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
+ processPosition(mapper, x1, y1);
+ processId(mapper, 1);
+ processSlot(mapper, 1);
+ processPosition(mapper, x2, y2);
+ processId(mapper, 2);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Move.
+ x1 += 10; y1 += 15; x2 += 5; y2 -= 10;
+ processSlot(mapper, 0);
+ processPosition(mapper, x1, y1);
+ processSlot(mapper, 1);
+ processPosition(mapper, x2, y2);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // First finger up.
+ x2 += 15; y2 -= 20;
+ processSlot(mapper, 0);
+ processId(mapper, -1);
+ processSlot(mapper, 1);
+ processPosition(mapper, x2, y2);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x1), toDisplayY(y1), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Move.
+ x2 += 20; y2 -= 25;
+ processPosition(mapper, x2, y2);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(1, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // New finger down.
+ int32_t x3 = 700, y3 = 300;
+ processPosition(mapper, x2, y2);
+ processSlot(mapper, 0);
+ processId(mapper, 3);
+ processPosition(mapper, x3, y3);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Second finger up.
+ x3 += 30; y3 -= 20;
+ processSlot(mapper, 1);
+ processId(mapper, -1);
+ processSlot(mapper, 0);
+ processPosition(mapper, x3, y3);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ motionArgs.action);
+ ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(1, motionArgs.pointerProperties[1].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[1].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[1],
+ toDisplayX(x2), toDisplayY(y2), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Last finger up.
+ processId(mapper, -1);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(0, motionArgs.pointerProperties[0].id);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(x3), toDisplayY(y3), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // Should not have sent any more keys or motions.
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_AllAxes_WithDefaultCalibration) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | TOUCH | TOOL | PRESSURE | ORIENTATION | ID | MINOR | DISTANCE);
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawTouchMajor = 7;
+ int32_t rawTouchMinor = 6;
+ int32_t rawToolMajor = 9;
+ int32_t rawToolMinor = 8;
+ int32_t rawPressure = 11;
+ int32_t rawDistance = 0;
+ int32_t rawOrientation = 3;
+ int32_t id = 5;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float pressure = float(rawPressure) / RAW_PRESSURE_MAX;
+ float size = avg(rawTouchMajor, rawTouchMinor) / RAW_TOUCH_MAX;
+ float toolMajor = float(rawToolMajor) * GEOMETRIC_SCALE;
+ float toolMinor = float(rawToolMinor) * GEOMETRIC_SCALE;
+ float touchMajor = float(rawTouchMajor) * GEOMETRIC_SCALE;
+ float touchMinor = float(rawTouchMinor) * GEOMETRIC_SCALE;
+ float orientation = float(rawOrientation) / RAW_ORIENTATION_MAX * M_PI_2;
+ float distance = float(rawDistance);
+
+ processPosition(mapper, rawX, rawY);
+ processTouchMajor(mapper, rawTouchMajor);
+ processTouchMinor(mapper, rawTouchMinor);
+ processToolMajor(mapper, rawToolMajor);
+ processToolMinor(mapper, rawToolMinor);
+ processPressure(mapper, rawPressure);
+ processOrientation(mapper, rawOrientation);
+ processDistance(mapper, rawDistance);
+ processId(mapper, id);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(0, args.pointerProperties[0].id);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor,
+ orientation, distance));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | TOUCH | TOOL | MINOR);
+ addConfigurationProperty("touch.size.calibration", "geometric");
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawTouchMajor = 140;
+ int32_t rawTouchMinor = 120;
+ int32_t rawToolMajor = 180;
+ int32_t rawToolMinor = 160;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float size = avg(rawTouchMajor, rawTouchMinor) / RAW_TOUCH_MAX;
+ float toolMajor = float(rawToolMajor) * GEOMETRIC_SCALE;
+ float toolMinor = float(rawToolMinor) * GEOMETRIC_SCALE;
+ float touchMajor = float(rawTouchMajor) * GEOMETRIC_SCALE;
+ float touchMinor = float(rawTouchMinor) * GEOMETRIC_SCALE;
+
+ processPosition(mapper, rawX, rawY);
+ processTouchMajor(mapper, rawTouchMajor);
+ processTouchMinor(mapper, rawTouchMinor);
+ processToolMajor(mapper, rawToolMajor);
+ processToolMinor(mapper, rawToolMinor);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, 1.0f, size, touchMajor, touchMinor, toolMajor, toolMinor, 0, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_SummedLinearCalibration) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | TOUCH | TOOL);
+ addConfigurationProperty("touch.size.calibration", "diameter");
+ addConfigurationProperty("touch.size.scale", "10");
+ addConfigurationProperty("touch.size.bias", "160");
+ addConfigurationProperty("touch.size.isSummed", "1");
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ // Note: We only provide a single common touch/tool value because the device is assumed
+ // not to emit separate values for each pointer (isSummed = 1).
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawX2 = 150;
+ int32_t rawY2 = 250;
+ int32_t rawTouchMajor = 5;
+ int32_t rawToolMajor = 8;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float x2 = toDisplayX(rawX2);
+ float y2 = toDisplayY(rawY2);
+ float size = float(rawTouchMajor) / 2 / RAW_TOUCH_MAX;
+ float touch = float(rawTouchMajor) / 2 * 10.0f + 160.0f;
+ float tool = float(rawToolMajor) / 2 * 10.0f + 160.0f;
+
+ processPosition(mapper, rawX, rawY);
+ processTouchMajor(mapper, rawTouchMajor);
+ processToolMajor(mapper, rawToolMajor);
+ processMTSync(mapper);
+ processPosition(mapper, rawX2, rawY2);
+ processTouchMajor(mapper, rawTouchMajor);
+ processToolMajor(mapper, rawToolMajor);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ args.action);
+ ASSERT_EQ(size_t(2), args.pointerCount);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, 1.0f, size, touch, touch, tool, tool, 0, 0));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[1],
+ x2, y2, 1.0f, size, touch, touch, tool, tool, 0, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_AreaCalibration) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | TOUCH | TOOL);
+ addConfigurationProperty("touch.size.calibration", "area");
+ addConfigurationProperty("touch.size.scale", "43");
+ addConfigurationProperty("touch.size.bias", "3");
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawTouchMajor = 5;
+ int32_t rawToolMajor = 8;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float size = float(rawTouchMajor) / RAW_TOUCH_MAX;
+ float touch = sqrtf(rawTouchMajor) * 43.0f + 3.0f;
+ float tool = sqrtf(rawToolMajor) * 43.0f + 3.0f;
+
+ processPosition(mapper, rawX, rawY);
+ processTouchMajor(mapper, rawTouchMajor);
+ processToolMajor(mapper, rawToolMajor);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, 1.0f, size, touch, touch, tool, tool, 0, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_PressureAxis_AmplitudeCalibration) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | PRESSURE);
+ addConfigurationProperty("touch.pressure.calibration", "amplitude");
+ addConfigurationProperty("touch.pressure.scale", "0.01");
+ addMapperAndConfigure(mapper);
+
+ // These calculations are based on the input device calibration documentation.
+ int32_t rawX = 100;
+ int32_t rawY = 200;
+ int32_t rawPressure = 60;
+
+ float x = toDisplayX(rawX);
+ float y = toDisplayY(rawY);
+ float pressure = float(rawPressure) * 0.01f;
+
+ processPosition(mapper, rawX, rawY);
+ processPressure(mapper, rawPressure);
+ processMTSync(mapper);
+ processSync(mapper);
+
+ NotifyMotionArgs args;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
+ x, y, pressure, 0, 0, 0, 0, 0, 0, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllButtons) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | ID | SLOT);
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs motionArgs;
+ NotifyKeyArgs keyArgs;
+
+ processId(mapper, 1);
+ processPosition(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+
+ // press BTN_LEFT, release BTN_LEFT
+ processKey(mapper, BTN_LEFT, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
+
+ processKey(mapper, BTN_LEFT, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
+ processKey(mapper, BTN_RIGHT, 1);
+ processKey(mapper, BTN_MIDDLE, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
+ motionArgs.buttonState);
+
+ processKey(mapper, BTN_RIGHT, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_MIDDLE, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ // press BTN_BACK, release BTN_BACK
+ processKey(mapper, BTN_BACK, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_BACK, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ // press BTN_SIDE, release BTN_SIDE
+ processKey(mapper, BTN_SIDE, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_SIDE, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ // press BTN_FORWARD, release BTN_FORWARD
+ processKey(mapper, BTN_FORWARD, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_FORWARD, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+
+ // press BTN_EXTRA, release BTN_EXTRA
+ processKey(mapper, BTN_EXTRA, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ processKey(mapper, BTN_EXTRA, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+
+ // press BTN_STYLUS, release BTN_STYLUS
+ processKey(mapper, BTN_STYLUS, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY, motionArgs.buttonState);
+
+ processKey(mapper, BTN_STYLUS, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ // press BTN_STYLUS2, release BTN_STYLUS2
+ processKey(mapper, BTN_STYLUS2, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
+
+ processKey(mapper, BTN_STYLUS2, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+
+ // release touch
+ processId(mapper, -1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_ShouldHandleAllToolTypes) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | ID | SLOT | TOOL_TYPE);
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs motionArgs;
+
+ // default tool type is finger
+ processId(mapper, 1);
+ processPosition(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // eraser
+ processKey(mapper, BTN_TOOL_RUBBER, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType);
+
+ // stylus
+ processKey(mapper, BTN_TOOL_RUBBER, 0);
+ processKey(mapper, BTN_TOOL_PEN, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // brush
+ processKey(mapper, BTN_TOOL_PEN, 0);
+ processKey(mapper, BTN_TOOL_BRUSH, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // pencil
+ processKey(mapper, BTN_TOOL_BRUSH, 0);
+ processKey(mapper, BTN_TOOL_PENCIL, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // airbrush
+ processKey(mapper, BTN_TOOL_PENCIL, 0);
+ processKey(mapper, BTN_TOOL_AIRBRUSH, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // mouse
+ processKey(mapper, BTN_TOOL_AIRBRUSH, 0);
+ processKey(mapper, BTN_TOOL_MOUSE, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+
+ // lens
+ processKey(mapper, BTN_TOOL_MOUSE, 0);
+ processKey(mapper, BTN_TOOL_LENS, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+
+ // double-tap
+ processKey(mapper, BTN_TOOL_LENS, 0);
+ processKey(mapper, BTN_TOOL_DOUBLETAP, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // triple-tap
+ processKey(mapper, BTN_TOOL_DOUBLETAP, 0);
+ processKey(mapper, BTN_TOOL_TRIPLETAP, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // quad-tap
+ processKey(mapper, BTN_TOOL_TRIPLETAP, 0);
+ processKey(mapper, BTN_TOOL_QUADTAP, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // finger
+ processKey(mapper, BTN_TOOL_QUADTAP, 0);
+ processKey(mapper, BTN_TOOL_FINGER, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // stylus trumps finger
+ processKey(mapper, BTN_TOOL_PEN, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // eraser trumps stylus
+ processKey(mapper, BTN_TOOL_RUBBER, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_ERASER, motionArgs.pointerProperties[0].toolType);
+
+ // mouse trumps eraser
+ processKey(mapper, BTN_TOOL_MOUSE, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, motionArgs.pointerProperties[0].toolType);
+
+ // MT tool type trumps BTN tool types: MT_TOOL_FINGER
+ processToolType(mapper, MT_TOOL_FINGER); // this is the first time we send MT_TOOL_TYPE
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+
+ // MT tool type trumps BTN tool types: MT_TOOL_PEN
+ processToolType(mapper, MT_TOOL_PEN);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_STYLUS, motionArgs.pointerProperties[0].toolType);
+
+ // back to default tool type
+ processToolType(mapper, -1); // use a deliberately undefined tool type, for testing
+ processKey(mapper, BTN_TOOL_MOUSE, 0);
+ processKey(mapper, BTN_TOOL_RUBBER, 0);
+ processKey(mapper, BTN_TOOL_PEN, 0);
+ processKey(mapper, BTN_TOOL_FINGER, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_FINGER, motionArgs.pointerProperties[0].toolType);
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | ID | SLOT);
+ mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs motionArgs;
+
+ // initially hovering because BTN_TOUCH not sent yet, pressure defaults to 0
+ processId(mapper, 1);
+ processPosition(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // move a little
+ processPosition(mapper, 150, 250);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // down when BTN_TOUCH is pressed, pressure defaults to 1
+ processKey(mapper, BTN_TOUCH, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // up when BTN_TOUCH is released, hover restored
+ processKey(mapper, BTN_TOUCH, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // exit hover when pointer goes away
+ processId(mapper, -1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_WhenAbsMTPressureIsPresent_HoversIfItsValueIsZero) {
+ MultiTouchInputMapper* mapper = new MultiTouchInputMapper(mDevice);
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareAxes(POSITION | ID | SLOT | PRESSURE);
+ addMapperAndConfigure(mapper);
+
+ NotifyMotionArgs motionArgs;
+
+ // initially hovering because pressure is 0
+ processId(mapper, 1);
+ processPosition(mapper, 100, 200);
+ processPressure(mapper, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(100), toDisplayY(200), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // move a little
+ processPosition(mapper, 150, 250);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // down when pressure becomes non-zero
+ processPressure(mapper, RAW_PRESSURE_MAX);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ // up when pressure becomes 0, hover restored
+ processPressure(mapper, 0);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 1, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_ENTER, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+
+ // exit hover when pointer goes away
+ processId(mapper, -1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_EXIT, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
+ toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+
+} // namespace android
diff --git a/services/powermanager/IPowerManager.cpp b/services/powermanager/IPowerManager.cpp
index 5ecd299..ee730d6 100644
--- a/services/powermanager/IPowerManager.cpp
+++ b/services/powermanager/IPowerManager.cpp
@@ -33,6 +33,7 @@ enum {
ACQUIRE_WAKE_LOCK_UID = IBinder::FIRST_CALL_TRANSACTION + 1,
RELEASE_WAKE_LOCK = IBinder::FIRST_CALL_TRANSACTION + 2,
UPDATE_WAKE_LOCK_UIDS = IBinder::FIRST_CALL_TRANSACTION + 3,
+ POWER_HINT = IBinder::FIRST_CALL_TRANSACTION + 4,
};
class BpPowerManager : public BpInterface<IPowerManager>
@@ -54,6 +55,7 @@ public:
data.writeString16(tag);
data.writeString16(packageName);
data.writeInt32(0); // no WorkSource
+ data.writeString16(NULL, 0); // no history tag
return remote()->transact(ACQUIRE_WAKE_LOCK, data, &reply);
}
@@ -89,6 +91,15 @@ public:
// but it should return ASAP
return remote()->transact(UPDATE_WAKE_LOCK_UIDS, data, &reply, IBinder::FLAG_ONEWAY);
}
+
+ virtual status_t powerHint(int hintId, int param)
+ {
+ Parcel data, reply;
+ data.writeInterfaceToken(IPowerManager::getInterfaceDescriptor());
+ data.writeInt32(hintId);
+ data.writeInt32(param);
+ return remote()->transact(POWER_HINT, data, &reply, IBinder::FLAG_ONEWAY);
+ }
};
IMPLEMENT_META_INTERFACE(PowerManager, "android.os.IPowerManager");
diff --git a/services/sensorservice/BatteryService.cpp b/services/sensorservice/BatteryService.cpp
index 38dc749..cb962a6 100644
--- a/services/sensorservice/BatteryService.cpp
+++ b/services/sensorservice/BatteryService.cpp
@@ -34,32 +34,10 @@ BatteryService::BatteryService() {
const sp<IServiceManager> sm(defaultServiceManager());
if (sm != NULL) {
const String16 name("batterystats");
- mBatteryStatService = sm->getService(name);
+ mBatteryStatService = interface_cast<IBatteryStats>(sm->getService(name));
}
}
-status_t BatteryService::noteStartSensor(int uid, int handle) {
- Parcel data, reply;
- data.writeInterfaceToken(DESCRIPTOR);
- data.writeInt32(uid);
- data.writeInt32(handle);
- status_t err = mBatteryStatService->transact(
- TRANSACTION_noteStartSensor, data, &reply, 0);
- err = reply.readExceptionCode();
- return err;
-}
-
-status_t BatteryService::noteStopSensor(int uid, int handle) {
- Parcel data, reply;
- data.writeInterfaceToken(DESCRIPTOR);
- data.writeInt32(uid);
- data.writeInt32(handle);
- status_t err = mBatteryStatService->transact(
- TRANSACTION_noteStopSensor, data, &reply, 0);
- err = reply.readExceptionCode();
- return err;
-}
-
bool BatteryService::addSensor(uid_t uid, int handle) {
Mutex::Autolock _l(mActivationsLock);
Info key(uid, handle);
@@ -86,7 +64,7 @@ void BatteryService::enableSensorImpl(uid_t uid, int handle) {
if (mBatteryStatService != 0) {
if (addSensor(uid, handle)) {
int64_t identity = IPCThreadState::self()->clearCallingIdentity();
- noteStartSensor(uid, handle);
+ mBatteryStatService->noteStartSensor(uid, handle);
IPCThreadState::self()->restoreCallingIdentity(identity);
}
}
@@ -95,7 +73,7 @@ void BatteryService::disableSensorImpl(uid_t uid, int handle) {
if (mBatteryStatService != 0) {
if (removeSensor(uid, handle)) {
int64_t identity = IPCThreadState::self()->clearCallingIdentity();
- noteStopSensor(uid, handle);
+ mBatteryStatService->noteStopSensor(uid, handle);
IPCThreadState::self()->restoreCallingIdentity(identity);
}
}
@@ -108,7 +86,7 @@ void BatteryService::cleanupImpl(uid_t uid) {
for (ssize_t i=0 ; i<mActivations.size() ; i++) {
const Info& info(mActivations[i]);
if (info.uid == uid) {
- noteStopSensor(info.uid, info.handle);
+ mBatteryStatService->noteStopSensor(info.uid, info.handle);
mActivations.removeAt(i);
i--;
}
@@ -117,8 +95,6 @@ void BatteryService::cleanupImpl(uid_t uid) {
}
}
-const String16 BatteryService::DESCRIPTOR("com.android.internal.app.IBatteryStats");
-
ANDROID_SINGLETON_STATIC_INSTANCE(BatteryService)
// ---------------------------------------------------------------------------
diff --git a/services/sensorservice/BatteryService.h b/services/sensorservice/BatteryService.h
index 86cc884..08ba857 100644
--- a/services/sensorservice/BatteryService.h
+++ b/services/sensorservice/BatteryService.h
@@ -17,22 +17,18 @@
#include <stdint.h>
#include <sys/types.h>
+#include <binder/IBatteryStats.h>
#include <utils/Singleton.h>
namespace android {
// ---------------------------------------------------------------------------
class BatteryService : public Singleton<BatteryService> {
- static const int TRANSACTION_noteStartSensor = IBinder::FIRST_CALL_TRANSACTION + 3;
- static const int TRANSACTION_noteStopSensor = IBinder::FIRST_CALL_TRANSACTION + 4;
- static const String16 DESCRIPTOR;
friend class Singleton<BatteryService>;
- sp<IBinder> mBatteryStatService;
+ sp<IBatteryStats> mBatteryStatService;
BatteryService();
- status_t noteStartSensor(int uid, int handle);
- status_t noteStopSensor(int uid, int handle);
void enableSensorImpl(uid_t uid, int handle);
void disableSensorImpl(uid_t uid, int handle);
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index 3b64f0a..01dbdfb 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -309,7 +309,7 @@ int SensorDevice::getHalDeviceVersion() const {
return mSensorDevice->common.version;
}
-status_t SensorDevice::flush(void* /*ident*/, int handle) {
+status_t SensorDevice::flush(void* ident, int handle) {
if (getHalDeviceVersion() < SENSORS_DEVICE_API_VERSION_1_1) {
return INVALID_OPERATION;
}
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 148f404..aff4e9a 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -164,6 +164,7 @@ void SensorService::onFirstRef()
fclose(fp);
}
+ mWakeLockAcquired = false;
run("SensorService", PRIORITY_URGENT_DISPLAY);
mInitCheck = NO_ERROR;
}
@@ -284,6 +285,7 @@ status_t SensorService::dump(int fd, const Vector<String16>& /*args*/)
}
result.appendFormat("%zu Max Socket Buffer size\n", mSocketBufferSize);
+ result.appendFormat("WakeLock Status: %s \n", mWakeLockAcquired ? "acquired" : "not held");
result.appendFormat("%zd active connections\n", mActiveConnections.size());
for (size_t i=0 ; i < mActiveConnections.size() ; i++) {
@@ -298,7 +300,7 @@ status_t SensorService::dump(int fd, const Vector<String16>& /*args*/)
return NO_ERROR;
}
-void SensorService::cleanupAutoDisabledSensor(const sp<SensorEventConnection>& connection,
+void SensorService::cleanupAutoDisabledSensorLocked(const sp<SensorEventConnection>& connection,
sensors_event_t const* buffer, const int count) {
SensorInterface* sensor;
status_t err = NO_ERROR;
@@ -311,7 +313,7 @@ void SensorService::cleanupAutoDisabledSensor(const sp<SensorEventConnection>& c
if (sensor != NULL) {
sensor->autoDisable(connection.get(), handle);
}
- cleanupWithoutDisable(connection, handle);
+ cleanupWithoutDisableLocked(connection, handle);
}
}
}
@@ -333,7 +335,6 @@ bool SensorService::threadLoop()
const size_t vcount = mVirtualSensorList.size();
ssize_t count;
- bool wakeLockAcquired = false;
const int halVersion = device.getHalDeviceVersion();
do {
count = device.poll(buffer, numEventMax);
@@ -341,26 +342,31 @@ bool SensorService::threadLoop()
ALOGE("sensor poll failed (%s)", strerror(-count));
break;
}
-
- // Poll has returned. Hold a wakelock.
- // Todo(): add a flag to the sensors definitions to indicate
- // the sensors which can wake up the AP
+ Mutex::Autolock _l(mLock);
+ // Poll has returned. Hold a wakelock if one of the events is from a wake up sensor. The
+ // rest of this loop is under a critical section protected by mLock. Acquiring a wakeLock,
+ // sending events to clients (incrementing SensorEventConnection::mWakeLockRefCount) should
+ // not be interleaved with decrementing SensorEventConnection::mWakeLockRefCount and
+ // releasing the wakelock.
+ bool bufferHasWakeUpEvent = false;
for (int i = 0; i < count; i++) {
- if (buffer[i].type == SENSOR_TYPE_SIGNIFICANT_MOTION) {
- acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
- wakeLockAcquired = true;
- break;
+ if (isWakeUpSensorEvent(buffer[i])) {
+ bufferHasWakeUpEvent = true;
+ break;
}
}
- recordLastValue(buffer, count);
+ if (bufferHasWakeUpEvent && !mWakeLockAcquired) {
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
+ mWakeLockAcquired = true;
+ ALOGD_IF(DEBUG_CONNECTIONS, "acquired wakelock %s", WAKE_LOCK_NAME);
+ }
+ recordLastValueLocked(buffer, count);
// handle virtual sensors
if (count && vcount) {
sensors_event_t const * const event = buffer;
- const DefaultKeyedVector<int, SensorInterface*> virtualSensors(
- getActiveVirtualSensors());
- const size_t activeVirtualSensorCount = virtualSensors.size();
+ const size_t activeVirtualSensorCount = mActiveVirtualSensors.size();
if (activeVirtualSensorCount) {
size_t k = 0;
SensorFusion& fusion(SensorFusion::getInstance());
@@ -378,7 +384,7 @@ bool SensorService::threadLoop()
break;
}
sensors_event_t out;
- SensorInterface* si = virtualSensors.valueAt(j);
+ SensorInterface* si = mActiveVirtualSensors.valueAt(j);
if (si->process(&out, event[i])) {
buffer[count + k] = out;
k++;
@@ -387,7 +393,7 @@ bool SensorService::threadLoop()
}
if (k) {
// record the last synthesized values
- recordLastValue(&buffer[count], k);
+ recordLastValueLocked(&buffer[count], k);
count += k;
// sort the buffer by time-stamps
sortEventBuffer(buffer, count);
@@ -406,22 +412,24 @@ bool SensorService::threadLoop()
}
}
- // send our events to clients...
- const SortedVector< wp<SensorEventConnection> > activeConnections(
- getActiveConnections());
- size_t numConnections = activeConnections.size();
- for (size_t i=0 ; i<numConnections ; i++) {
- sp<SensorEventConnection> connection(
- activeConnections[i].promote());
+ // Send our events to clients. Check the state of wake lock for each client and release the
+ // lock if none of the clients need it.
+ bool needsWakeLock = false;
+ for (size_t i=0 ; i < mActiveConnections.size(); i++) {
+ sp<SensorEventConnection> connection(mActiveConnections[i].promote());
if (connection != 0) {
connection->sendEvents(buffer, count, scratch);
+ needsWakeLock |= connection->needsWakeLock();
// Some sensors need to be auto disabled after the trigger
- cleanupAutoDisabledSensor(connection, buffer, count);
+ cleanupAutoDisabledSensorLocked(connection, buffer, count);
}
}
- // We have read the data, upper layers should hold the wakelock.
- if (wakeLockAcquired) release_wake_lock(WAKE_LOCK_NAME);
+ if (mWakeLockAcquired && !needsWakeLock) {
+ release_wake_lock(WAKE_LOCK_NAME);
+ mWakeLockAcquired = false;
+ ALOGD_IF(DEBUG_CONNECTIONS, "released wakelock %s", WAKE_LOCK_NAME);
+ }
} while (count >= 0 || Thread::exitPending());
ALOGW("Exiting SensorService::threadLoop => aborting...");
@@ -429,9 +437,8 @@ bool SensorService::threadLoop()
return false;
}
-void SensorService::recordLastValue(
+void SensorService::recordLastValueLocked(
const sensors_event_t* buffer, size_t count) {
- Mutex::Autolock _l(mLock);
const sensors_event_t* last = NULL;
for (size_t i = 0; i < count; i++) {
const sensors_event_t* event = &buffer[i];
@@ -459,20 +466,6 @@ void SensorService::sortEventBuffer(sensors_event_t* buffer, size_t count)
qsort(buffer, count, sizeof(sensors_event_t), compar::cmp);
}
-SortedVector< wp<SensorService::SensorEventConnection> >
-SensorService::getActiveConnections() const
-{
- Mutex::Autolock _l(mLock);
- return mActiveConnections;
-}
-
-DefaultKeyedVector<int, SensorInterface*>
-SensorService::getActiveVirtualSensors() const
-{
- Mutex::Autolock _l(mLock);
- return mActiveVirtualSensors;
-}
-
String8 SensorService::getSensorName(int handle) const {
size_t count = mUserSensorList.size();
for (size_t i=0 ; i<count ; i++) {
@@ -490,6 +483,11 @@ bool SensorService::isVirtualSensor(int handle) const {
return sensor->isVirtual();
}
+bool SensorService::isWakeUpSensorEvent(const sensors_event_t& event) const {
+ SensorInterface* sensor = mSensorMap.valueFor(event.sensor);
+ return sensor->getSensor().isWakeUpSensor();
+}
+
Vector<Sensor> SensorService::getSensorList()
{
char value[PROPERTY_VALUE_MAX];
@@ -554,6 +552,9 @@ void SensorService::cleanupConnection(SensorEventConnection* c)
}
mActiveConnections.remove(connection);
BatteryService::cleanup(c->getUid());
+ if (c->needsWakeLock()) {
+ checkWakeLockStateLocked();
+ }
}
Sensor SensorService::getSensorFromHandle(int handle) const {
@@ -585,15 +586,24 @@ status_t SensorService::enable(const sp<SensorEventConnection>& connection,
}
} else {
if (rec->addConnection(connection)) {
- // this sensor is already activated, but we are adding a
- // connection that uses it. Immediately send down the last
- // known value of the requested sensor if it's not a
+ // this sensor is already activated, but we are adding a connection that uses it.
+ // Immediately send down the last known value of the requested sensor if it's not a
// "continuous" sensor.
if (sensor->getSensor().getMinDelay() == 0) {
- sensors_event_t scratch;
+ // NOTE: The wake_up flag of this event may get set to
+ // WAKE_UP_SENSOR_EVENT_NEEDS_ACK if this is a wake_up event.
sensors_event_t& event(mLastEventSeen.editValueFor(handle));
if (event.version == sizeof(sensors_event_t)) {
- connection->sendEvents(&event, 1);
+ if (isWakeUpSensorEvent(event) && !mWakeLockAcquired) {
+ acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_NAME);
+ mWakeLockAcquired = true;
+ ALOGD_IF(DEBUG_CONNECTIONS, "acquired wakelock for on_change sensor %s",
+ WAKE_LOCK_NAME);
+ }
+ connection->sendEvents(&event, 1, NULL);
+ if (!connection->needsWakeLock() && mWakeLockAcquired) {
+ checkWakeLockStateLocked();
+ }
}
}
}
@@ -751,6 +761,31 @@ bool SensorService::verifyCanAccessSensor(const Sensor& sensor, const char* oper
}
}
+void SensorService::checkWakeLockState() {
+ Mutex::Autolock _l(mLock);
+ checkWakeLockStateLocked();
+}
+
+void SensorService::checkWakeLockStateLocked() {
+ if (!mWakeLockAcquired) {
+ return;
+ }
+ bool releaseLock = true;
+ for (size_t i=0 ; i<mActiveConnections.size() ; i++) {
+ sp<SensorEventConnection> connection(mActiveConnections[i].promote());
+ if (connection != 0) {
+ if (connection->needsWakeLock()) {
+ releaseLock = false;
+ break;
+ }
+ }
+ }
+ if (releaseLock) {
+ ALOGD_IF(DEBUG_CONNECTIONS, "releasing wakelock %s", WAKE_LOCK_NAME);
+ release_wake_lock(WAKE_LOCK_NAME);
+ mWakeLockAcquired = false;
+ }
+}
// ---------------------------------------------------------------------------
SensorService::SensorRecord::SensorRecord(
@@ -783,7 +818,7 @@ bool SensorService::SensorRecord::removeConnection(
SensorService::SensorEventConnection::SensorEventConnection(
const sp<SensorService>& service, uid_t uid)
- : mService(service), mUid(uid)
+ : mService(service), mUid(uid), mWakeLockRefCount(0)
{
const SensorDevice& device(SensorDevice::getInstance());
if (device.getHalDeviceVersion() >= SENSORS_DEVICE_API_VERSION_1_1) {
@@ -804,15 +839,22 @@ void SensorService::SensorEventConnection::onFirstRef()
{
}
+bool SensorService::SensorEventConnection::needsWakeLock() {
+ Mutex::Autolock _l(mConnectionLock);
+ return mWakeLockRefCount > 0;
+}
+
void SensorService::SensorEventConnection::dump(String8& result) {
Mutex::Autolock _l(mConnectionLock);
+ result.appendFormat("%d WakeLockRefCount\n", mWakeLockRefCount);
for (size_t i = 0; i < mSensorInfo.size(); ++i) {
const FlushInfo& flushInfo = mSensorInfo.valueAt(i);
- result.appendFormat("\t %s | status: %s | pending flush events %d\n",
+ result.appendFormat("\t %s | status: %s | pending flush events %d | uid %d\n",
mService->getSensorName(mSensorInfo.keyAt(i)).string(),
flushInfo.mFirstFlushPending ? "First flush pending" :
"active",
- flushInfo.mPendingFlushEventsToSend);
+ flushInfo.mPendingFlushEventsToSend,
+ mUid);
}
}
@@ -862,9 +904,8 @@ status_t SensorService::SensorEventConnection::sendEvents(
{
// filter out events not for this connection
size_t count = 0;
-
+ Mutex::Autolock _l(mConnectionLock);
if (scratch) {
- Mutex::Autolock _l(mConnectionLock);
size_t i=0;
while (i<numEvents) {
int32_t curr = buffer[i].sensor;
@@ -904,7 +945,6 @@ status_t SensorService::SensorEventConnection::sendEvents(
ASensorEvent flushCompleteEvent;
flushCompleteEvent.type = SENSOR_TYPE_META_DATA;
flushCompleteEvent.sensor = 0;
- Mutex::Autolock _l(mConnectionLock);
// Loop through all the sensors for this connection and check if there are any pending
// flush complete events to be sent.
for (size_t i = 0; i < mSensorInfo.size(); ++i) {
@@ -929,6 +969,7 @@ status_t SensorService::SensorEventConnection::sendEvents(
return status_t(NO_ERROR);
}
+ int numWakeUpSensorEvents = countWakeUpSensorEventsLocked(scratch, count);
// NOTE: ASensorEvent and sensors_event_t are the same type
ssize_t size = SensorEventQueue::write(mChannel,
reinterpret_cast<ASensorEvent const*>(scratch), count);
@@ -936,11 +977,10 @@ status_t SensorService::SensorEventConnection::sendEvents(
// the destination doesn't accept events anymore, it's probably
// full. For now, we just drop the events on the floor.
// ALOGW("dropping %d events on the floor", count);
- Mutex::Autolock _l(mConnectionLock);
countFlushCompleteEventsLocked(scratch, count);
+ mWakeLockRefCount -= numWakeUpSensorEvents;
return size;
}
-
return size < 0 ? status_t(size) : status_t(NO_ERROR);
}
@@ -960,6 +1000,18 @@ void SensorService::SensorEventConnection::countFlushCompleteEventsLocked(
return;
}
+int SensorService::SensorEventConnection::countWakeUpSensorEventsLocked(
+ sensors_event_t* scratch, const int count) {
+ for (int i = 0; i < count; ++i) {
+ if (mService->isWakeUpSensorEvent(scratch[i])) {
+ scratch[i].flags |= WAKE_UP_SENSOR_EVENT_NEEDS_ACK;
+ ++mWakeLockRefCount;
+ return 1;
+ }
+ }
+ return 0;
+}
+
sp<BitTube> SensorService::SensorEventConnection::getSensorChannel() const
{
return mChannel;
@@ -1009,6 +1061,17 @@ status_t SensorService::SensorEventConnection::flush() {
return err;
}
+void SensorService::SensorEventConnection::decreaseWakeLockRefCount() {
+ {
+ Mutex::Autolock _l(mConnectionLock);
+ --mWakeLockRefCount;
+ }
+ // Release the lock before calling checkWakeLockState which also needs the same connectionLock.
+ if (mWakeLockRefCount == 0) {
+ mService->checkWakeLockState();
+ }
+}
+
// ---------------------------------------------------------------------------
}; // namespace android
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index e88ffc8..5fd56b8 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -41,6 +41,7 @@
// Max size is 1 MB which is enough to accept a batch of about 10k events.
#define MAX_SOCKET_BUFFER_SIZE_BATCHED 1024 * 1024
#define SOCKET_BUFFER_SIZE_NON_BATCHED 4 * 1024
+#define WAKE_UP_SENSOR_EVENT_NEEDS_ACK (1 << 31)
struct sensors_poll_device_t;
struct sensors_module_t;
@@ -71,7 +72,6 @@ class SensorService :
virtual sp<ISensorEventConnection> createSensorEventConnection();
virtual status_t dump(int fd, const Vector<String16>& args);
-
class SensorEventConnection : public BnSensorEventConnection {
virtual ~SensorEventConnection();
virtual void onFirstRef();
@@ -80,15 +80,26 @@ class SensorService :
nsecs_t maxBatchReportLatencyNs, int reservedFlags);
virtual status_t setEventRate(int handle, nsecs_t samplingPeriodNs);
virtual status_t flush();
+ void decreaseWakeLockRefCount();
// Count the number of flush complete events which are about to be dropped in the buffer.
// Increment mPendingFlushEventsToSend in mSensorInfo. These flush complete events will be
// sent separately before the next batch of events.
void countFlushCompleteEventsLocked(sensors_event_t* scratch, int numEventsDropped);
+ // Check if there are any wake up events in the buffer. If yes, increment the ref count.
+ // Increment it by exactly one unit for each packet sent on the socket. SOCK_SEQPACKET for
+ // the socket ensures that either the entire packet is read or dropped.
+ // Return 1 if mWakeLockRefCount has been incremented, zero if not.
+ int countWakeUpSensorEventsLocked(sensors_event_t* scratch, const int count);
+
sp<SensorService> const mService;
sp<BitTube> mChannel;
uid_t mUid;
mutable Mutex mConnectionLock;
+ // Number of events from wake up sensors which are still pending and haven't been delivered
+ // to the corresponding application. It is incremented by one unit for each write to the
+ // socket.
+ int mWakeLockRefCount;
struct FlushInfo {
// The number of flush complete events dropped for this sensor is stored here.
@@ -106,13 +117,14 @@ class SensorService :
SensorEventConnection(const sp<SensorService>& service, uid_t uid);
status_t sendEvents(sensors_event_t const* buffer, size_t count,
- sensors_event_t* scratch = NULL);
+ sensors_event_t* scratch);
bool hasSensor(int32_t handle) const;
bool hasAnySensor() const;
bool addSensor(int32_t handle);
bool removeSensor(int32_t handle);
void setFirstFlushPending(int32_t handle, bool value);
void dump(String8& result);
+ bool needsWakeLock();
uid_t getUid() const { return mUid; }
};
@@ -126,13 +138,11 @@ class SensorService :
size_t getNumConnections() const { return mConnections.size(); }
};
- SortedVector< wp<SensorEventConnection> > getActiveConnections() const;
- DefaultKeyedVector<int, SensorInterface*> getActiveVirtualSensors() const;
-
String8 getSensorName(int handle) const;
bool isVirtualSensor(int handle) const;
Sensor getSensorFromHandle(int handle) const;
- void recordLastValue(const sensors_event_t* buffer, size_t count);
+ bool isWakeUpSensor(int type) const;
+ void recordLastValueLocked(const sensors_event_t* buffer, size_t count);
static void sortEventBuffer(sensors_event_t* buffer, size_t count);
Sensor registerSensor(SensorInterface* sensor);
Sensor registerVirtualSensor(SensorInterface* sensor);
@@ -140,10 +150,16 @@ class SensorService :
const sp<SensorEventConnection>& connection, int handle);
status_t cleanupWithoutDisableLocked(
const sp<SensorEventConnection>& connection, int handle);
- void cleanupAutoDisabledSensor(const sp<SensorEventConnection>& connection,
+ void cleanupAutoDisabledSensorLocked(const sp<SensorEventConnection>& connection,
sensors_event_t const* buffer, const int count);
static bool canAccessSensor(const Sensor& sensor);
static bool verifyCanAccessSensor(const Sensor& sensor, const char* operation);
+ // SensorService acquires a partial wakelock for delivering events from wake up sensors. This
+ // method checks whether all the events from these wake up sensors have been delivered to the
+ // corresponding applications, if yes the wakelock is released.
+ void checkWakeLockState();
+ void checkWakeLockStateLocked();
+ bool isWakeUpSensorEvent(const sensors_event_t& event) const;
// constants
Vector<Sensor> mSensorList;
Vector<Sensor> mUserSensorListDebug;
@@ -158,6 +174,7 @@ class SensorService :
DefaultKeyedVector<int, SensorRecord*> mActiveSensors;
DefaultKeyedVector<int, SensorInterface*> mActiveVirtualSensors;
SortedVector< wp<SensorEventConnection> > mActiveConnections;
+ bool mWakeLockAcquired;
// The size of this vector is constant, only the items are mutable
KeyedVector<int32_t, sensors_event_t> mLastEventSeen;
diff --git a/services/surfaceflinger/Android.mk b/services/surfaceflinger/Android.mk
index 49a017f..0834c80 100644
--- a/services/surfaceflinger/Android.mk
+++ b/services/surfaceflinger/Android.mk
@@ -11,9 +11,9 @@ LOCAL_SRC_FILES:= \
Layer.cpp \
LayerDim.cpp \
MessageQueue.cpp \
+ MonitoredProducer.cpp \
SurfaceFlinger.cpp \
SurfaceFlingerConsumer.cpp \
- SurfaceTextureLayer.cpp \
Transform.cpp \
DisplayHardware/FramebufferSurface.cpp \
DisplayHardware/HWComposer.cpp \
diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp
index 975631c..f7d32d0 100644
--- a/services/surfaceflinger/Client.cpp
+++ b/services/surfaceflinger/Client.cpp
@@ -155,5 +155,23 @@ status_t Client::destroySurface(const sp<IBinder>& handle) {
return mFlinger->onLayerRemoved(this, handle);
}
+status_t Client::clearLayerFrameStats(const sp<IBinder>& handle) const {
+ sp<Layer> layer = getLayerUser(handle);
+ if (layer == NULL) {
+ return NAME_NOT_FOUND;
+ }
+ layer->clearFrameStats();
+ return NO_ERROR;
+}
+
+status_t Client::getLayerFrameStats(const sp<IBinder>& handle, FrameStats* outStats) const {
+ sp<Layer> layer = getLayerUser(handle);
+ if (layer == NULL) {
+ return NAME_NOT_FOUND;
+ }
+ layer->getFrameStats(outStats);
+ return NO_ERROR;
+}
+
// ---------------------------------------------------------------------------
}; // namespace android
diff --git a/services/surfaceflinger/Client.h b/services/surfaceflinger/Client.h
index 84e649f..b6d7381 100644
--- a/services/surfaceflinger/Client.h
+++ b/services/surfaceflinger/Client.h
@@ -60,6 +60,10 @@ private:
virtual status_t destroySurface(const sp<IBinder>& handle);
+ virtual status_t clearLayerFrameStats(const sp<IBinder>& handle) const;
+
+ virtual status_t getLayerFrameStats(const sp<IBinder>& handle, FrameStats* outStats) const;
+
virtual status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
diff --git a/services/surfaceflinger/DispSync.cpp b/services/surfaceflinger/DispSync.cpp
index 602f20a..a00ccdb 100644
--- a/services/surfaceflinger/DispSync.cpp
+++ b/services/surfaceflinger/DispSync.cpp
@@ -61,7 +61,10 @@ class DispSyncThread: public Thread {
public:
DispSyncThread():
+ mLowPowerMode(false),
mStop(false),
+ mLastVsyncSent(false),
+ mLastBufferFull(false),
mPeriod(0),
mPhase(0),
mWakeupLatency(0) {
@@ -145,7 +148,18 @@ public:
}
if (callbackInvocations.size() > 0) {
- fireCallbackInvocations(callbackInvocations);
+ if (mLowPowerMode) {
+ if (!mLastVsyncSent || !mLastBufferFull) {
+ fireCallbackInvocations(callbackInvocations);
+ mLastVsyncSent = true;
+ } else
+ mLastVsyncSent = false;
+ } else {
+ fireCallbackInvocations(callbackInvocations);
+ }
+ mLastBufferFull = true;
+ } else {
+ mLastBufferFull = false;
}
}
@@ -200,6 +214,7 @@ public:
return !mEventListeners.empty();
}
+ bool mLowPowerMode;
private:
struct EventListener {
@@ -272,6 +287,8 @@ private:
}
bool mStop;
+ bool mLastVsyncSent;
+ bool mLastBufferFull;
nsecs_t mPeriod;
nsecs_t mPhase;
@@ -395,6 +412,10 @@ status_t DispSync::addEventListener(nsecs_t phase,
return mThread->addEventListener(phase, callback);
}
+void DispSync::setLowPowerMode(bool enabled) {
+ mThread->mLowPowerMode = enabled;
+}
+
status_t DispSync::removeEventListener(const sp<Callback>& callback) {
Mutex::Autolock lock(mMutex);
return mThread->removeEventListener(callback);
diff --git a/services/surfaceflinger/DispSync.h b/services/surfaceflinger/DispSync.h
index c4280aa..c2ae135 100644
--- a/services/surfaceflinger/DispSync.h
+++ b/services/surfaceflinger/DispSync.h
@@ -88,6 +88,9 @@ public:
// turned on. It should NOT be used after that.
void setPeriod(nsecs_t period);
+ // Setting the low power mode reduces the frame rate to half of the default
+ void setLowPowerMode(bool enabled);
+
// addEventListener registers a callback to be called repeatedly at the
// given phase offset from the hardware vsync events. The callback is
// called from a separate thread and it should return reasonably quickly
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index f9034a6..42993b9 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -194,6 +194,8 @@ void DisplayDevice::flip(const Region& dirty) const
eglSetSwapRectangleANDROID(dpy, surface,
b.left, b.top, b.width(), b.height());
}
+#else
+ (void) dirty; // Eliminate unused parameter warning
#endif
mPageFlipCount++;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index a48582e..c4ea8cc 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -24,9 +24,9 @@
#include <string.h>
#include <sys/types.h>
-#include <utils/CallStack.h>
#include <utils/Errors.h>
#include <utils/misc.h>
+#include <utils/NativeHandle.h>
#include <utils/String8.h>
#include <utils/Thread.h>
#include <utils/Trace.h>
@@ -944,12 +944,22 @@ public:
SharedBuffer const* sb = reg.getSharedBuffer(&visibleRegion.numRects);
visibleRegion.rects = reinterpret_cast<hwc_rect_t const *>(sb->data());
}
+ virtual void setSidebandStream(const sp<NativeHandle>& stream) {
+ ALOG_ASSERT(stream->handle() != NULL);
+ getLayer()->compositionType = HWC_SIDEBAND;
+ getLayer()->sidebandStream = stream->handle();
+ }
virtual void setBuffer(const sp<GraphicBuffer>& buffer) {
if (buffer == 0 || buffer->handle == 0) {
getLayer()->compositionType = HWC_FRAMEBUFFER;
getLayer()->flags |= HWC_SKIP_LAYER;
getLayer()->handle = 0;
} else {
+ if (getLayer()->compositionType == HWC_SIDEBAND) {
+ // If this was a sideband layer but the stream was removed, reset
+ // it to FRAMEBUFFER. The HWC can change it to OVERLAY in prepare.
+ getLayer()->compositionType = HWC_FRAMEBUFFER;
+ }
getLayer()->handle = buffer->handle;
}
}
@@ -1010,9 +1020,27 @@ HWComposer::LayerListIterator HWComposer::end(int32_t id) {
return getLayerIterator(id, numLayers);
}
+// Converts a PixelFormat to a human-readable string. Max 11 chars.
+// (Could use a table of prefab String8 objects.)
+static String8 getFormatStr(PixelFormat format) {
+ switch (format) {
+ case PIXEL_FORMAT_RGBA_8888: return String8("RGBA_8888");
+ case PIXEL_FORMAT_RGBX_8888: return String8("RGBx_8888");
+ case PIXEL_FORMAT_RGB_888: return String8("RGB_888");
+ case PIXEL_FORMAT_RGB_565: return String8("RGB_565");
+ case PIXEL_FORMAT_BGRA_8888: return String8("BGRA_8888");
+ case PIXEL_FORMAT_sRGB_A_8888: return String8("sRGB_A_8888");
+ case PIXEL_FORMAT_sRGB_X_8888: return String8("sRGB_x_8888");
+ default:
+ String8 result;
+ result.appendFormat("? %08x", format);
+ return result;
+ }
+}
+
void HWComposer::dump(String8& result) const {
if (mHwc) {
- result.appendFormat("Hardware Composer state (version %8x):\n", hwcApiVersion(mHwc));
+ result.appendFormat("Hardware Composer state (version %08x):\n", hwcApiVersion(mHwc));
result.appendFormat(" mDebugForceFakeVSync=%d\n", mDebugForceFakeVSync);
for (size_t i=0 ; i<mNumDisplays ; i++) {
const DisplayData& disp(mDisplayData[i]);
@@ -1032,9 +1060,9 @@ void HWComposer::dump(String8& result) const {
disp.list->numHwLayers, disp.list->flags);
result.append(
- " type | handle | hints | flags | tr | blend | format | source crop | frame name \n"
- "------------+----------+----------+----------+----+-------+----------+---------------------------------+--------------------------------\n");
- // " __________ | ________ | ________ | ________ | __ | _____ | ________ | [_____._,_____._,_____._,_____._] | [_____,_____,_____,_____]
+ " type | handle | hint | flag | tr | blnd | format | source crop (l,t,r,b) | frame | name \n"
+ "-----------+----------+------+------+----+------+-------------+--------------------------------+------------------------+------\n");
+ // " _________ | ________ | ____ | ____ | __ | ____ | ___________ |_____._,_____._,_____._,_____._ |_____,_____,_____,_____ | ___...
for (size_t i=0 ; i<disp.list->numHwLayers ; i++) {
const hwc_layer_1_t&l = disp.list->hwLayers[i];
int32_t format = -1;
@@ -1059,25 +1087,26 @@ void HWComposer::dump(String8& result) const {
static char const* compositionTypeName[] = {
"GLES",
"HWC",
- "BACKGROUND",
+ "BKGND",
"FB TARGET",
"UNKNOWN"};
if (type >= NELEM(compositionTypeName))
type = NELEM(compositionTypeName) - 1;
+ String8 formatStr = getFormatStr(format);
if (hwcHasApiVersion(mHwc, HWC_DEVICE_API_VERSION_1_3)) {
result.appendFormat(
- " %10s | %08" PRIxPTR " | %08x | %08x | %02x | %05x | %08x | [%7.1f,%7.1f,%7.1f,%7.1f] | [%5d,%5d,%5d,%5d] %s\n",
+ " %9s | %08" PRIxPTR " | %04x | %04x | %02x | %04x | %-11s |%7.1f,%7.1f,%7.1f,%7.1f |%5d,%5d,%5d,%5d | %s\n",
compositionTypeName[type],
- intptr_t(l.handle), l.hints, l.flags, l.transform, l.blending, format,
+ intptr_t(l.handle), l.hints, l.flags, l.transform, l.blending, formatStr.string(),
l.sourceCropf.left, l.sourceCropf.top, l.sourceCropf.right, l.sourceCropf.bottom,
l.displayFrame.left, l.displayFrame.top, l.displayFrame.right, l.displayFrame.bottom,
name.string());
} else {
result.appendFormat(
- " %10s | %08" PRIxPTR " | %08x | %08x | %02x | %05x | %08x | [%7d,%7d,%7d,%7d] | [%5d,%5d,%5d,%5d] %s\n",
+ " %9s | %08" PRIxPTR " | %04x | %04x | %02x | %04x | %-11s |%7d,%7d,%7d,%7d |%5d,%5d,%5d,%5d | %s\n",
compositionTypeName[type],
- intptr_t(l.handle), l.hints, l.flags, l.transform, l.blending, format,
+ intptr_t(l.handle), l.hints, l.flags, l.transform, l.blending, formatStr.string(),
l.sourceCrop.left, l.sourceCrop.top, l.sourceCrop.right, l.sourceCrop.bottom,
l.displayFrame.left, l.displayFrame.top, l.displayFrame.right, l.displayFrame.bottom,
name.string());
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 9f96113..9218bf6 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -45,9 +45,10 @@ struct framebuffer_device_t;
namespace android {
// ---------------------------------------------------------------------------
-class GraphicBuffer;
class Fence;
class FloatRect;
+class GraphicBuffer;
+class NativeHandle;
class Region;
class String8;
class SurfaceFlinger;
@@ -164,6 +165,7 @@ public:
virtual void setFrame(const Rect& frame) = 0;
virtual void setCrop(const FloatRect& crop) = 0;
virtual void setVisibleRegionScreen(const Region& reg) = 0;
+ virtual void setSidebandStream(const sp<NativeHandle>& stream) = 0;
virtual void setBuffer(const sp<GraphicBuffer>& buffer) = 0;
virtual void setAcquireFenceFd(int fenceFd) = 0;
virtual void setPlaneAlpha(uint8_t alpha) = 0;
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index a1820ab..1362ae8 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -47,9 +47,10 @@ static const char* dbgCompositionTypeStr(DisplaySurface::CompositionType type) {
VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc, int32_t dispId,
const sp<IGraphicBufferProducer>& sink,
- const sp<BufferQueue>& bq,
+ const sp<IGraphicBufferProducer>& bqProducer,
+ const sp<IGraphicBufferConsumer>& bqConsumer,
const String8& name)
-: ConsumerBase(bq),
+: ConsumerBase(bqConsumer),
mHwc(hwc),
mDisplayId(dispId),
mDisplayName(name),
@@ -60,7 +61,7 @@ VirtualDisplaySurface::VirtualDisplaySurface(HWComposer& hwc, int32_t dispId,
mMustRecompose(false)
{
mSource[SOURCE_SINK] = sink;
- mSource[SOURCE_SCRATCH] = bq;
+ mSource[SOURCE_SCRATCH] = bqProducer;
resetPerFrameState();
@@ -255,7 +256,7 @@ void VirtualDisplaySurface::onFrameCommitted() {
resetPerFrameState();
}
-void VirtualDisplaySurface::dump(String8& result) const {
+void VirtualDisplaySurface::dump(String8& /* result */) const {
}
status_t VirtualDisplaySurface::requestBuffer(int pslot,
@@ -284,19 +285,19 @@ status_t VirtualDisplaySurface::dequeueBuffer(Source source,
int pslot = mapSource2ProducerSlot(source, *sslot);
VDS_LOGV("dequeueBuffer(%s): sslot=%d pslot=%d result=%d",
dbgSourceStr(source), *sslot, pslot, result);
- uint32_t sourceBit = static_cast<uint32_t>(source) << pslot;
+ uint64_t sourceBit = static_cast<uint64_t>(source) << pslot;
- if ((mProducerSlotSource & (1u << pslot)) != sourceBit) {
+ if ((mProducerSlotSource & (1ULL << pslot)) != sourceBit) {
// This slot was previously dequeued from the other source; must
// re-request the buffer.
result |= BUFFER_NEEDS_REALLOCATION;
- mProducerSlotSource &= ~(1u << pslot);
+ mProducerSlotSource &= ~(1ULL << pslot);
mProducerSlotSource |= sourceBit;
}
if (result & RELEASE_ALL_BUFFERS) {
for (uint32_t i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
- if ((mProducerSlotSource & (1u << i)) == sourceBit)
+ if ((mProducerSlotSource & (1ULL << i)) == sourceBit)
mProducerBuffers[i].clear();
}
}
@@ -374,6 +375,23 @@ status_t VirtualDisplaySurface::dequeueBuffer(int* pslot, sp<Fence>* fence, bool
return result;
}
+status_t VirtualDisplaySurface::detachBuffer(int /* slot */) {
+ VDS_LOGE("detachBuffer is not available for VirtualDisplaySurface");
+ return INVALID_OPERATION;
+}
+
+status_t VirtualDisplaySurface::detachNextBuffer(
+ sp<GraphicBuffer>* /* outBuffer */, sp<Fence>* /* outFence */) {
+ VDS_LOGE("detachNextBuffer is not available for VirtualDisplaySurface");
+ return INVALID_OPERATION;
+}
+
+status_t VirtualDisplaySurface::attachBuffer(int* /* outSlot */,
+ const sp<GraphicBuffer>& /* buffer */) {
+ VDS_LOGE("attachBuffer is not available for VirtualDisplaySurface");
+ return INVALID_OPERATION;
+}
+
status_t VirtualDisplaySurface::queueBuffer(int pslot,
const QueueBufferInput& input, QueueBufferOutput* output) {
VDS_LOGW_IF(mDbgState != DBG_STATE_GLES,
@@ -442,11 +460,12 @@ int VirtualDisplaySurface::query(int what, int* value) {
return mSource[SOURCE_SINK]->query(what, value);
}
-status_t VirtualDisplaySurface::connect(const sp<IBinder>& token,
+status_t VirtualDisplaySurface::connect(const sp<IProducerListener>& listener,
int api, bool producerControlledByApp,
QueueBufferOutput* output) {
QueueBufferOutput qbo;
- status_t result = mSource[SOURCE_SINK]->connect(token, api, producerControlledByApp, &qbo);
+ status_t result = mSource[SOURCE_SINK]->connect(listener, api,
+ producerControlledByApp, &qbo);
if (result == NO_ERROR) {
updateQueueBufferOutput(qbo);
*output = mQueueBufferOutput;
@@ -458,6 +477,10 @@ status_t VirtualDisplaySurface::disconnect(int api) {
return mSource[SOURCE_SINK]->disconnect(api);
}
+status_t VirtualDisplaySurface::setSidebandStream(const sp<NativeHandle>& /*stream*/) {
+ return INVALID_OPERATION;
+}
+
void VirtualDisplaySurface::updateQueueBufferOutput(
const QueueBufferOutput& qbo) {
uint32_t w, h, transformHint, numPendingBuffers;
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index 6899904..0ae9804 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -27,6 +27,7 @@ namespace android {
// ---------------------------------------------------------------------------
class HWComposer;
+class IProducerListener;
/* This DisplaySurface implementation supports virtual displays, where GLES
* and/or HWC compose into a buffer that is then passed to an arbitrary
@@ -73,7 +74,8 @@ class VirtualDisplaySurface : public DisplaySurface,
public:
VirtualDisplaySurface(HWComposer& hwc, int32_t dispId,
const sp<IGraphicBufferProducer>& sink,
- const sp<BufferQueue>& bq,
+ const sp<IGraphicBufferProducer>& bqProducer,
+ const sp<IGraphicBufferConsumer>& bqConsumer,
const String8& name);
//
@@ -98,13 +100,18 @@ private:
virtual status_t setBufferCount(int bufferCount);
virtual status_t dequeueBuffer(int* pslot, sp<Fence>* fence, bool async,
uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
+ virtual status_t detachBuffer(int slot);
+ virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence);
+ virtual status_t attachBuffer(int* slot, const sp<GraphicBuffer>& buffer);
virtual status_t queueBuffer(int pslot,
const QueueBufferInput& input, QueueBufferOutput* output);
virtual void cancelBuffer(int pslot, const sp<Fence>& fence);
virtual int query(int what, int* value);
- virtual status_t connect(const sp<IBinder>& token,
+ virtual status_t connect(const sp<IProducerListener>& listener,
int api, bool producerControlledByApp, QueueBufferOutput* output);
virtual status_t disconnect(int api);
+ virtual status_t setSidebandStream(const sp<NativeHandle>& stream);
//
// Utility methods
@@ -148,10 +155,10 @@ private:
// Since we present a single producer interface to the GLES driver, but
// are internally muxing between the sink and scratch producers, we have
// to keep track of which source last returned each producer slot from
- // dequeueBuffer. Each bit in mLastSlotSource corresponds to a producer
+ // dequeueBuffer. Each bit in mProducerSlotSource corresponds to a producer
// slot. Both mProducerSlotSource and mProducerBuffers are indexed by a
// "producer slot"; see the mapSlot*() functions.
- uint32_t mProducerSlotSource;
+ uint64_t mProducerSlotSource;
sp<GraphicBuffer> mProducerBuffers[BufferQueue::NUM_BUFFER_SLOTS];
// The QueueBufferOutput with the latest info from the sink, and with the
diff --git a/services/surfaceflinger/Effects/Daltonizer.cpp b/services/surfaceflinger/Effects/Daltonizer.cpp
index f384ba4..feb8936 100644
--- a/services/surfaceflinger/Effects/Daltonizer.cpp
+++ b/services/surfaceflinger/Effects/Daltonizer.cpp
@@ -148,9 +148,6 @@ void Daltonizer::update() {
// set to identity, errp, errd, errt ([0] for simulation only)
mat4 correction(0);
- // control: simulation post-correction (used for debugging):
- // set to identity or lms2lmsp, lms2lmsd, lms2lmst
- mat4 control;
switch (mType) {
case protanopia:
case protanomaly:
@@ -172,12 +169,8 @@ void Daltonizer::update() {
break;
}
- if (true) {
- control = simulation;
- }
-
- mColorTransform = lms2rgb * control *
- (simulation * rgb2lms + correction * (rgb2lms - simulation * rgb2lms));
+ mColorTransform = lms2rgb *
+ (simulation * rgb2lms + correction * (rgb2lms - simulation * rgb2lms));
}
} /* namespace android */
diff --git a/services/surfaceflinger/FrameTracker.cpp b/services/surfaceflinger/FrameTracker.cpp
index 2fb665e..c09bbe4 100644
--- a/services/surfaceflinger/FrameTracker.cpp
+++ b/services/surfaceflinger/FrameTracker.cpp
@@ -22,6 +22,7 @@
#include <cutils/log.h>
#include <ui/Fence.h>
+#include <ui/FrameStats.h>
#include <utils/String8.h>
@@ -100,7 +101,7 @@ void FrameTracker::advanceFrame() {
processFencesLocked();
}
-void FrameTracker::clear() {
+void FrameTracker::clearStats() {
Mutex::Autolock lock(mMutex);
for (size_t i = 0; i < NUM_FRAME_RECORDS; i++) {
mFrameRecords[i].desiredPresentTime = 0;
@@ -115,6 +116,32 @@ void FrameTracker::clear() {
mFrameRecords[mOffset].actualPresentTime = INT64_MAX;
}
+void FrameTracker::getStats(FrameStats* outStats) const {
+ Mutex::Autolock lock(mMutex);
+ processFencesLocked();
+
+ outStats->refreshPeriodNano = mDisplayPeriod;
+
+ const size_t offset = mOffset;
+ for (size_t i = 1; i < NUM_FRAME_RECORDS; i++) {
+ const size_t index = (offset + i) % NUM_FRAME_RECORDS;
+
+ // Skip frame records with no data (if buffer not yet full).
+ if (mFrameRecords[index].desiredPresentTime == 0) {
+ continue;
+ }
+
+ nsecs_t desiredPresentTimeNano = mFrameRecords[index].desiredPresentTime;
+ outStats->desiredPresentTimesNano.push_back(desiredPresentTimeNano);
+
+ nsecs_t actualPresentTimeNano = mFrameRecords[index].actualPresentTime;
+ outStats->actualPresentTimesNano.push_back(actualPresentTimeNano);
+
+ nsecs_t frameReadyTimeNano = mFrameRecords[index].frameReadyTime;
+ outStats->frameReadyTimesNano.push_back(frameReadyTimeNano);
+ }
+}
+
void FrameTracker::logAndResetStats(const String8& name) {
Mutex::Autolock lock(mMutex);
logStatsLocked(name);
@@ -206,7 +233,7 @@ bool FrameTracker::isFrameValidLocked(size_t idx) const {
mFrameRecords[idx].actualPresentTime < INT64_MAX;
}
-void FrameTracker::dump(String8& result) const {
+void FrameTracker::dumpStats(String8& result) const {
Mutex::Autolock lock(mMutex);
processFencesLocked();
diff --git a/services/surfaceflinger/FrameTracker.h b/services/surfaceflinger/FrameTracker.h
index 9233247..cd5e3f3 100644
--- a/services/surfaceflinger/FrameTracker.h
+++ b/services/surfaceflinger/FrameTracker.h
@@ -78,15 +78,18 @@ public:
// advanceFrame advances the frame tracker to the next frame.
void advanceFrame();
- // clear resets all the tracked frame data to zero.
- void clear();
+ // clearStats clears the tracked frame stats.
+ void clearStats();
+
+ // getStats gets the tracked frame stats.
+ void getStats(FrameStats* outStats) const;
// logAndResetStats dumps the current statistics to the binary event log
// and then resets the accumulated statistics to their initial values.
void logAndResetStats(const String8& name);
- // dump appends the current frame display time history to the result string.
- void dump(String8& result) const;
+ // dumpStats dump appends the current frame display time history to the result string.
+ void dumpStats(String8& result) const;
private:
struct FrameRecord {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index fcc9d78..1b86204 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -27,6 +27,7 @@
#include <utils/Errors.h>
#include <utils/Log.h>
+#include <utils/NativeHandle.h>
#include <utils/StopWatch.h>
#include <utils/Trace.h>
@@ -39,8 +40,8 @@
#include "Colorizer.h"
#include "DisplayDevice.h"
#include "Layer.h"
+#include "MonitoredProducer.h"
#include "SurfaceFlinger.h"
-#include "SurfaceTextureLayer.h"
#include "DisplayHardware/HWComposer.h"
@@ -66,6 +67,7 @@ Layer::Layer(SurfaceFlinger* flinger, const sp<Client>& client,
mFormat(PIXEL_FORMAT_NONE),
mTransactionFlags(0),
mQueuedFrames(0),
+ mSidebandStreamChanged(false),
mCurrentTransform(0),
mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
mCurrentOpacity(true),
@@ -115,10 +117,13 @@ Layer::Layer(SurfaceFlinger* flinger, const sp<Client>& client,
void Layer::onFirstRef() {
// Creates a custom BufferQueue for SurfaceFlingerConsumer to use
- mBufferQueue = new SurfaceTextureLayer(mFlinger);
- mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(mBufferQueue, mTextureName);
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ mProducer = new MonitoredProducer(producer, mFlinger);
+ mSurfaceFlingerConsumer = new SurfaceFlingerConsumer(consumer, mTextureName);
mSurfaceFlingerConsumer->setConsumerUsageBits(getEffectiveUsage(0));
- mSurfaceFlingerConsumer->setFrameAvailableListener(this);
+ mSurfaceFlingerConsumer->setContentsChangedListener(this);
mSurfaceFlingerConsumer->setName(mName);
#ifdef TARGET_DISABLE_TRIPLE_BUFFERING
@@ -145,7 +150,7 @@ Layer::~Layer() {
// callbacks
// ---------------------------------------------------------------------------
-void Layer::onLayerDisplayed(const sp<const DisplayDevice>& hw,
+void Layer::onLayerDisplayed(const sp<const DisplayDevice>& /* hw */,
HWComposer::HWCLayerInterface* layer) {
if (layer) {
layer->onDisplayed();
@@ -158,6 +163,13 @@ void Layer::onFrameAvailable() {
mFlinger->signalLayerUpdate();
}
+void Layer::onSidebandStreamChanged() {
+ if (android_atomic_release_cas(false, true, &mSidebandStreamChanged) == 0) {
+ // mSidebandStreamChanged was false
+ mFlinger->signalLayerUpdate();
+ }
+}
+
// called with SurfaceFlinger::mStateLock from the drawing thread after
// the layer has been remove from the current state list (and just before
// it's removed from the drawing state list)
@@ -227,8 +239,8 @@ sp<IBinder> Layer::getHandle() {
return new Handle(mFlinger, this);
}
-sp<IGraphicBufferProducer> Layer::getBufferQueue() const {
- return mBufferQueue;
+sp<IGraphicBufferProducer> Layer::getProducer() const {
+ return mProducer;
}
// ---------------------------------------------------------------------------
@@ -413,12 +425,16 @@ void Layer::setPerFrameData(const sp<const DisplayDevice>& hw,
Region visible = tr.transform(visibleRegion.intersect(hw->getViewport()));
layer.setVisibleRegionScreen(visible);
- // NOTE: buffer can be NULL if the client never drew into this
- // layer yet, or if we ran out of memory
- layer.setBuffer(mActiveBuffer);
+ if (mSidebandStream.get()) {
+ layer.setSidebandStream(mSidebandStream);
+ } else {
+ // NOTE: buffer can be NULL if the client never drew into this
+ // layer yet, or if we ran out of memory
+ layer.setBuffer(mActiveBuffer);
+ }
}
-void Layer::setAcquireFence(const sp<const DisplayDevice>& hw,
+void Layer::setAcquireFence(const sp<const DisplayDevice>& /* hw */,
HWComposer::HWCLayerInterface& layer) {
int fenceFd = -1;
@@ -442,14 +458,20 @@ void Layer::setAcquireFence(const sp<const DisplayDevice>& hw,
// ---------------------------------------------------------------------------
void Layer::draw(const sp<const DisplayDevice>& hw, const Region& clip) const {
- onDraw(hw, clip);
+ onDraw(hw, clip, false);
+}
+
+void Layer::draw(const sp<const DisplayDevice>& hw,
+ bool useIdentityTransform) const {
+ onDraw(hw, Region(hw->bounds()), useIdentityTransform);
}
-void Layer::draw(const sp<const DisplayDevice>& hw) {
- onDraw( hw, Region(hw->bounds()) );
+void Layer::draw(const sp<const DisplayDevice>& hw) const {
+ onDraw(hw, Region(hw->bounds()), false);
}
-void Layer::onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const
+void Layer::onDraw(const sp<const DisplayDevice>& hw, const Region& clip,
+ bool useIdentityTransform) const
{
ATRACE_CALL();
@@ -540,16 +562,17 @@ void Layer::onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const
} else {
engine.setupLayerBlackedOut();
}
- drawWithOpenGL(hw, clip);
+ drawWithOpenGL(hw, clip, useIdentityTransform);
engine.disableTexturing();
}
-void Layer::clearWithOpenGL(const sp<const DisplayDevice>& hw, const Region& clip,
- float red, float green, float blue, float alpha) const
+void Layer::clearWithOpenGL(const sp<const DisplayDevice>& hw,
+ const Region& /* clip */, float red, float green, float blue,
+ float alpha) const
{
RenderEngine& engine(mFlinger->getRenderEngine());
- computeGeometry(hw, mMesh);
+ computeGeometry(hw, mMesh, false);
engine.setupFillWithColor(red, green, blue, alpha);
engine.drawMesh(mMesh);
}
@@ -559,12 +582,12 @@ void Layer::clearWithOpenGL(
clearWithOpenGL(hw, clip, 0,0,0,0);
}
-void Layer::drawWithOpenGL(
- const sp<const DisplayDevice>& hw, const Region& clip) const {
+void Layer::drawWithOpenGL(const sp<const DisplayDevice>& hw,
+ const Region& /* clip */, bool useIdentityTransform) const {
const uint32_t fbHeight = hw->getHeight();
const State& s(getDrawingState());
- computeGeometry(hw, mMesh);
+ computeGeometry(hw, mMesh, useIdentityTransform);
/*
* NOTE: the way we compute the texture coordinates here produces
@@ -634,10 +657,12 @@ bool Layer::getOpacityForFormat(uint32_t format) {
// local state
// ----------------------------------------------------------------------------
-void Layer::computeGeometry(const sp<const DisplayDevice>& hw, Mesh& mesh) const
+void Layer::computeGeometry(const sp<const DisplayDevice>& hw, Mesh& mesh,
+ bool useIdentityTransform) const
{
const Layer::State& s(getDrawingState());
- const Transform tr(hw->getTransform() * s.transform);
+ const Transform tr(useIdentityTransform ?
+ hw->getTransform() : hw->getTransform() * s.transform);
const uint32_t hw_h = hw->getHeight();
Rect win(s.active.w, s.active.h);
if (!s.active.crop.isEmpty()) {
@@ -898,7 +923,7 @@ bool Layer::setLayerStack(uint32_t layerStack) {
bool Layer::onPreComposition() {
mRefreshPending = false;
- return mQueuedFrames > 0;
+ return mQueuedFrames > 0 || mSidebandStreamChanged;
}
void Layer::onPostComposition() {
@@ -934,13 +959,18 @@ void Layer::onPostComposition() {
bool Layer::isVisible() const {
const Layer::State& s(mDrawingState);
return !(s.flags & layer_state_t::eLayerHidden) && s.alpha
- && (mActiveBuffer != NULL);
+ && (mActiveBuffer != NULL || mSidebandStream != NULL);
}
Region Layer::latchBuffer(bool& recomputeVisibleRegions)
{
ATRACE_CALL();
+ if (android_atomic_acquire_cas(true, false, &mSidebandStreamChanged) == 0) {
+ // mSidebandStreamChanged was true
+ mSidebandStream = mSurfaceFlingerConsumer->getSidebandStream();
+ }
+
Region outDirtyRegion;
if (mQueuedFrames > 0) {
@@ -1214,18 +1244,22 @@ void Layer::dump(String8& result, Colorizer& colorizer) const
}
}
-void Layer::dumpStats(String8& result) const {
- mFrameTracker.dump(result);
+void Layer::dumpFrameStats(String8& result) const {
+ mFrameTracker.dumpStats(result);
}
-void Layer::clearStats() {
- mFrameTracker.clear();
+void Layer::clearFrameStats() {
+ mFrameTracker.clearStats();
}
void Layer::logFrameStats() {
mFrameTracker.logAndResetStats(mName);
}
+void Layer::getFrameStats(FrameStats* outStats) const {
+ mFrameTracker.getStats(outStats);
+}
+
// ---------------------------------------------------------------------------
Layer::LayerCleaner::LayerCleaner(const sp<SurfaceFlinger>& flinger,
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index ea65ded..ee9f8a0 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -27,6 +27,7 @@
#include <utils/String8.h>
#include <utils/Timers.h>
+#include <ui/FrameStats.h>
#include <ui/GraphicBuffer.h>
#include <ui/PixelFormat.h>
#include <ui/Region.h>
@@ -37,9 +38,9 @@
#include "FrameTracker.h"
#include "Client.h"
+#include "MonitoredProducer.h"
#include "SurfaceFlinger.h"
#include "SurfaceFlingerConsumer.h"
-#include "SurfaceTextureLayer.h"
#include "Transform.h"
#include "DisplayHardware/HWComposer.h"
@@ -66,7 +67,7 @@ class SurfaceFlinger;
* This also implements onFrameAvailable(), which notifies SurfaceFlinger
* that new data has arrived.
*/
-class Layer : public SurfaceFlingerConsumer::FrameAvailableListener {
+class Layer : public SurfaceFlingerConsumer::ContentsChangedListener {
static int32_t sSequence;
public:
@@ -75,6 +76,10 @@ public:
Region visibleRegion;
Region coveredRegion;
Region visibleNonTransparentRegion;
+
+ // Layer serial number. This gives layers an explicit ordering, so we
+ // have a stable sort order when their layer stack and Z-order are
+ // the same.
int32_t sequence;
enum { // flags for doTransaction()
@@ -135,11 +140,12 @@ public:
uint32_t getTransactionFlags(uint32_t flags);
uint32_t setTransactionFlags(uint32_t flags);
- void computeGeometry(const sp<const DisplayDevice>& hw, Mesh& mesh) const;
+ void computeGeometry(const sp<const DisplayDevice>& hw, Mesh& mesh,
+ bool useIdentityTransform) const;
Rect computeBounds() const;
sp<IBinder> getHandle();
- sp<IGraphicBufferProducer> getBufferQueue() const;
+ sp<IGraphicBufferProducer> getProducer() const;
const String8& getName() const;
// -----------------------------------------------------------------------
@@ -182,7 +188,8 @@ protected:
/*
* onDraw - draws the surface.
*/
- virtual void onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const;
+ virtual void onDraw(const sp<const DisplayDevice>& hw, const Region& clip,
+ bool useIdentityTransform) const;
public:
// -----------------------------------------------------------------------
@@ -216,7 +223,8 @@ public:
* and calls onDraw().
*/
void draw(const sp<const DisplayDevice>& hw, const Region& clip) const;
- void draw(const sp<const DisplayDevice>& hw);
+ void draw(const sp<const DisplayDevice>& hw, bool useIdentityTransform) const;
+ void draw(const sp<const DisplayDevice>& hw) const;
/*
* doTransaction - process the transaction. This is a good place to figure
@@ -285,9 +293,10 @@ public:
/* always call base class first */
void dump(String8& result, Colorizer& colorizer) const;
- void dumpStats(String8& result) const;
- void clearStats();
+ void dumpFrameStats(String8& result) const;
+ void clearFrameStats();
void logFrameStats();
+ void getFrameStats(FrameStats* outStats) const;
protected:
// constant
@@ -310,8 +319,9 @@ protected:
private:
- // Interface implementation for SurfaceFlingerConsumer::FrameAvailableListener
+ // Interface implementation for SurfaceFlingerConsumer::ContentsChangedListener
virtual void onFrameAvailable();
+ virtual void onSidebandStreamChanged();
void commitTransaction();
@@ -326,15 +336,16 @@ private:
// drawing
void clearWithOpenGL(const sp<const DisplayDevice>& hw, const Region& clip,
float r, float g, float b, float alpha) const;
- void drawWithOpenGL(const sp<const DisplayDevice>& hw, const Region& clip) const;
+ void drawWithOpenGL(const sp<const DisplayDevice>& hw, const Region& clip,
+ bool useIdentityTransform) const;
// -----------------------------------------------------------------------
// constants
sp<SurfaceFlingerConsumer> mSurfaceFlingerConsumer;
- sp<BufferQueue> mBufferQueue;
- uint32_t mTextureName;
+ sp<IGraphicBufferProducer> mProducer;
+ uint32_t mTextureName; // from GLES
bool mPremultipliedAlpha;
String8 mName;
mutable bool mDebug;
@@ -347,10 +358,12 @@ private:
// thread-safe
volatile int32_t mQueuedFrames;
+ volatile int32_t mSidebandStreamChanged; // used like an atomic boolean
FrameTracker mFrameTracker;
// main thread
sp<GraphicBuffer> mActiveBuffer;
+ sp<NativeHandle> mSidebandStream;
Rect mCurrentCrop;
uint32_t mCurrentTransform;
uint32_t mCurrentScalingMode;
@@ -363,7 +376,7 @@ private:
bool mNeedsFiltering;
// The mesh used to draw the layer in GLES composition mode
mutable Mesh mMesh;
- // The mesh used to draw the layer in GLES composition mode
+ // The texture used to draw the layer in GLES composition mode
mutable Texture mTexture;
// page-flip thread (currently main thread)
diff --git a/services/surfaceflinger/LayerDim.cpp b/services/surfaceflinger/LayerDim.cpp
index 4e82bab..14aa328 100644
--- a/services/surfaceflinger/LayerDim.cpp
+++ b/services/surfaceflinger/LayerDim.cpp
@@ -39,12 +39,13 @@ LayerDim::LayerDim(SurfaceFlinger* flinger, const sp<Client>& client,
LayerDim::~LayerDim() {
}
-void LayerDim::onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const
+void LayerDim::onDraw(const sp<const DisplayDevice>& hw,
+ const Region& /* clip */, bool useIdentityTransform) const
{
const State& s(getDrawingState());
if (s.alpha>0) {
Mesh mesh(Mesh::TRIANGLE_FAN, 4, 2);
- computeGeometry(hw, mesh);
+ computeGeometry(hw, mesh, useIdentityTransform);
RenderEngine& engine(mFlinger->getRenderEngine());
engine.setupDimLayerBlending(s.alpha);
engine.drawMesh(mesh);
diff --git a/services/surfaceflinger/LayerDim.h b/services/surfaceflinger/LayerDim.h
index 6561d7f..4de0ddc 100644
--- a/services/surfaceflinger/LayerDim.h
+++ b/services/surfaceflinger/LayerDim.h
@@ -34,7 +34,8 @@ public:
virtual ~LayerDim();
virtual const char* getTypeId() const { return "LayerDim"; }
- virtual void onDraw(const sp<const DisplayDevice>& hw, const Region& clip) const;
+ virtual void onDraw(const sp<const DisplayDevice>& hw, const Region& clip,
+ bool useIdentityTransform) const;
virtual bool isOpaque() const { return false; }
virtual bool isSecure() const { return false; }
virtual bool isFixedSize() const { return true; }
diff --git a/services/surfaceflinger/MonitoredProducer.cpp b/services/surfaceflinger/MonitoredProducer.cpp
new file mode 100644
index 0000000..d0e81bc
--- /dev/null
+++ b/services/surfaceflinger/MonitoredProducer.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014 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 "MessageQueue.h"
+#include "MonitoredProducer.h"
+#include "SurfaceFlinger.h"
+
+namespace android {
+
+MonitoredProducer::MonitoredProducer(const sp<IGraphicBufferProducer>& producer,
+ const sp<SurfaceFlinger>& flinger) :
+ mProducer(producer),
+ mFlinger(flinger) {}
+
+MonitoredProducer::~MonitoredProducer() {
+ // Remove ourselves from SurfaceFlinger's list. We do this asynchronously
+ // because we don't know where this destructor is called from. It could be
+ // called with the mStateLock held, leading to a dead-lock (it actually
+ // happens).
+ class MessageCleanUpList : public MessageBase {
+ public:
+ MessageCleanUpList(const sp<SurfaceFlinger>& flinger,
+ const wp<IBinder>& producer)
+ : mFlinger(flinger), mProducer(producer) {}
+
+ virtual ~MessageCleanUpList() {}
+
+ virtual bool handler() {
+ Mutex::Autolock _l(mFlinger->mStateLock);
+ mFlinger->mGraphicBufferProducerList.remove(mProducer);
+ return true;
+ }
+
+ private:
+ sp<SurfaceFlinger> mFlinger;
+ wp<IBinder> mProducer;
+ };
+
+ mFlinger->postMessageAsync(new MessageCleanUpList(mFlinger, asBinder()));
+}
+
+status_t MonitoredProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
+ return mProducer->requestBuffer(slot, buf);
+}
+
+status_t MonitoredProducer::setBufferCount(int bufferCount) {
+ return mProducer->setBufferCount(bufferCount);
+}
+
+status_t MonitoredProducer::dequeueBuffer(int* slot, sp<Fence>* fence,
+ bool async, uint32_t w, uint32_t h, uint32_t format, uint32_t usage) {
+ return mProducer->dequeueBuffer(slot, fence, async, w, h, format, usage);
+}
+
+status_t MonitoredProducer::detachBuffer(int slot) {
+ return mProducer->detachBuffer(slot);
+}
+
+status_t MonitoredProducer::detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence) {
+ return mProducer->detachNextBuffer(outBuffer, outFence);
+}
+
+status_t MonitoredProducer::attachBuffer(int* outSlot,
+ const sp<GraphicBuffer>& buffer) {
+ return mProducer->attachBuffer(outSlot, buffer);
+}
+
+status_t MonitoredProducer::queueBuffer(int slot, const QueueBufferInput& input,
+ QueueBufferOutput* output) {
+ return mProducer->queueBuffer(slot, input, output);
+}
+
+void MonitoredProducer::cancelBuffer(int slot, const sp<Fence>& fence) {
+ mProducer->cancelBuffer(slot, fence);
+}
+
+int MonitoredProducer::query(int what, int* value) {
+ return mProducer->query(what, value);
+}
+
+status_t MonitoredProducer::connect(const sp<IProducerListener>& listener,
+ int api, bool producerControlledByApp, QueueBufferOutput* output) {
+ return mProducer->connect(listener, api, producerControlledByApp, output);
+}
+
+status_t MonitoredProducer::disconnect(int api) {
+ return mProducer->disconnect(api);
+}
+
+status_t MonitoredProducer::setSidebandStream(const sp<NativeHandle>& stream) {
+ return mProducer->setSidebandStream(stream);
+}
+
+IBinder* MonitoredProducer::onAsBinder() {
+ return mProducer->asBinder().get();
+}
+
+// ---------------------------------------------------------------------------
+}; // namespace android
diff --git a/services/surfaceflinger/MonitoredProducer.h b/services/surfaceflinger/MonitoredProducer.h
new file mode 100644
index 0000000..f034e39
--- /dev/null
+++ b/services/surfaceflinger/MonitoredProducer.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2014 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_MONITORED_PRODUCER_H
+#define ANDROID_MONITORED_PRODUCER_H
+
+#include <gui/IGraphicBufferProducer.h>
+
+namespace android {
+
+class IProducerListener;
+class NativeHandle;
+class SurfaceFlinger;
+
+// MonitoredProducer wraps an IGraphicBufferProducer so that SurfaceFlinger will
+// be notified upon its destruction
+class MonitoredProducer : public IGraphicBufferProducer {
+public:
+ MonitoredProducer(const sp<IGraphicBufferProducer>& producer,
+ const sp<SurfaceFlinger>& flinger);
+ virtual ~MonitoredProducer();
+
+ // From IGraphicBufferProducer
+ virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
+ virtual status_t setBufferCount(int bufferCount);
+ virtual status_t dequeueBuffer(int* slot, sp<Fence>* fence, bool async,
+ uint32_t w, uint32_t h, uint32_t format, uint32_t usage);
+ virtual status_t detachBuffer(int slot);
+ virtual status_t detachNextBuffer(sp<GraphicBuffer>* outBuffer,
+ sp<Fence>* outFence);
+ virtual status_t attachBuffer(int* outSlot,
+ const sp<GraphicBuffer>& buffer);
+ virtual status_t queueBuffer(int slot, const QueueBufferInput& input,
+ QueueBufferOutput* output);
+ virtual void cancelBuffer(int slot, const sp<Fence>& fence);
+ virtual int query(int what, int* value);
+ virtual status_t connect(const sp<IProducerListener>& token, int api,
+ bool producerControlledByApp, QueueBufferOutput* output);
+ virtual status_t disconnect(int api);
+ virtual status_t setSidebandStream(const sp<NativeHandle>& stream);
+ virtual IBinder* onAsBinder();
+
+private:
+ sp<IGraphicBufferProducer> mProducer;
+ sp<SurfaceFlinger> mFlinger;
+};
+
+}; // namespace android
+
+#endif // ANDROID_MONITORED_PRODUCER_H
diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.cpp b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
index 09b0ddc..d130506 100644
--- a/services/surfaceflinger/RenderEngine/ProgramCache.cpp
+++ b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
@@ -169,7 +169,8 @@ String8 ProgramCache::generateFragmentShader(const Key& needs) {
fs << "gl_FragColor.rgb = gl_FragColor.rgb/gl_FragColor.a;";
}
fs << "gl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(2.2));";
- fs << "gl_FragColor = colorMatrix*gl_FragColor;";
+ fs << "vec4 transformed = colorMatrix * vec4(gl_FragColor.rgb, 1);";
+ fs << "gl_FragColor.rgb = transformed.rgb/transformed.a;";
fs << "gl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(1.0 / 2.2));";
if (!needs.isOpaque() && needs.isPremultiplied()) {
// and re-premultiply if needed after gamma correction
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 0c2e8cb..0142992 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -153,7 +153,8 @@ SurfaceFlinger::SurfaceFlinger()
mBootFinished(false),
mPrimaryHWVsyncEnabled(false),
mHWVsyncAvailable(false),
- mDaltonize(false)
+ mDaltonize(false),
+ mHasColorMatrix(false)
{
ALOGI("SurfaceFlinger is starting");
@@ -190,7 +191,7 @@ SurfaceFlinger::~SurfaceFlinger()
eglTerminate(display);
}
-void SurfaceFlinger::binderDied(const wp<IBinder>& who)
+void SurfaceFlinger::binderDied(const wp<IBinder>& /* who */)
{
// the window manager died on us. prepare its eulogy.
@@ -419,12 +420,17 @@ void SurfaceFlinger::init() {
createBuiltinDisplayLocked(type);
wp<IBinder> token = mBuiltinDisplays[i];
- sp<BufferQueue> bq = new BufferQueue(new GraphicBufferAlloc());
- sp<FramebufferSurface> fbs = new FramebufferSurface(*mHwc, i, bq);
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer,
+ new GraphicBufferAlloc());
+
+ sp<FramebufferSurface> fbs = new FramebufferSurface(*mHwc, i,
+ consumer);
int32_t hwcId = allocateHwcDisplayId(type);
sp<DisplayDevice> hw = new DisplayDevice(this,
type, hwcId, mHwc->getFormat(hwcId), isSecure, token,
- fbs, bq,
+ fbs, producer,
mRenderEngine->getEGLConfig());
if (i > DisplayDevice::DISPLAY_PRIMARY) {
// FIXME: currently we don't get blank/unblank requests
@@ -568,6 +574,18 @@ status_t SurfaceFlinger::getDisplayInfo(const sp<IBinder>& display, DisplayInfo*
return NO_ERROR;
}
+status_t SurfaceFlinger::clearAnimationFrameStats() {
+ Mutex::Autolock _l(mStateLock);
+ mAnimFrameTracker.clearStats();
+ return NO_ERROR;
+}
+
+status_t SurfaceFlinger::getAnimationFrameStats(FrameStats* outStats) const {
+ Mutex::Autolock _l(mStateLock);
+ mAnimFrameTracker.getStats(outStats);
+ return NO_ERROR;
+}
+
// ----------------------------------------------------------------------------
sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection() {
@@ -593,12 +611,12 @@ void SurfaceFlinger::signalRefresh() {
}
status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg,
- nsecs_t reltime, uint32_t flags) {
+ nsecs_t reltime, uint32_t /* flags */) {
return mEventQueue.postMessage(msg, reltime);
}
status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg,
- nsecs_t reltime, uint32_t flags) {
+ nsecs_t reltime, uint32_t /* flags */) {
status_t res = mEventQueue.postMessage(msg, reltime);
if (res == NO_ERROR) {
msg->wait();
@@ -905,7 +923,7 @@ void SurfaceFlinger::setUpHWComposer() {
for (size_t i=0 ; cur!=end && i<count ; ++i, ++cur) {
const sp<Layer>& layer(currentLayers[i]);
layer->setGeometry(hw, *cur);
- if (mDebugDisableHWC || mDebugRegion || mDaltonize) {
+ if (mDebugDisableHWC || mDebugRegion || mDaltonize || mHasColorMatrix) {
cur->setSkip(true);
}
}
@@ -1151,7 +1169,10 @@ void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags)
sp<DisplaySurface> dispSurface;
sp<IGraphicBufferProducer> producer;
- sp<BufferQueue> bq = new BufferQueue(new GraphicBufferAlloc());
+ sp<IGraphicBufferProducer> bqProducer;
+ sp<IGraphicBufferConsumer> bqConsumer;
+ BufferQueue::createBufferQueue(&bqProducer, &bqConsumer,
+ new GraphicBufferAlloc());
int32_t hwcDisplayId = -1;
if (state.isVirtualDisplay()) {
@@ -1162,8 +1183,8 @@ void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags)
hwcDisplayId = allocateHwcDisplayId(state.type);
sp<VirtualDisplaySurface> vds = new VirtualDisplaySurface(
- *mHwc, hwcDisplayId, state.surface, bq,
- state.displayName);
+ *mHwc, hwcDisplayId, state.surface,
+ bqProducer, bqConsumer, state.displayName);
dispSurface = vds;
if (hwcDisplayId >= 0) {
@@ -1182,8 +1203,9 @@ void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags)
hwcDisplayId = allocateHwcDisplayId(state.type);
// for supported (by hwc) displays we provide our
// own rendering surface
- dispSurface = new FramebufferSurface(*mHwc, state.type, bq);
- producer = bq;
+ dispSurface = new FramebufferSurface(*mHwc, state.type,
+ bqConsumer);
+ producer = bqProducer;
}
const wp<IBinder>& display(curr.keyAt(i));
@@ -1536,11 +1558,15 @@ void SurfaceFlinger::doDisplayComposition(const sp<const DisplayDevice>& hw,
}
}
- if (CC_LIKELY(!mDaltonize)) {
+ if (CC_LIKELY(!mDaltonize && !mHasColorMatrix)) {
doComposeSurfaces(hw, dirtyRegion);
} else {
RenderEngine& engine(getRenderEngine());
- engine.beginGroup(mDaltonizer());
+ mat4 colorMatrix = mColorMatrix;
+ if (mDaltonize) {
+ colorMatrix = colorMatrix * mDaltonizer();
+ }
+ engine.beginGroup(colorMatrix);
doComposeSurfaces(hw, dirtyRegion);
engine.endGroup();
}
@@ -1706,7 +1732,7 @@ status_t SurfaceFlinger::removeLayer(const sp<Layer>& layer) {
return status_t(index);
}
-uint32_t SurfaceFlinger::peekTransactionFlags(uint32_t flags) {
+uint32_t SurfaceFlinger::peekTransactionFlags(uint32_t /* flags */) {
return android_atomic_release_load(&mTransactionFlags);
}
@@ -1973,7 +1999,7 @@ status_t SurfaceFlinger::createNormalLayer(const sp<Client>& client,
status_t err = (*outLayer)->setBuffers(w, h, format, flags);
if (err == NO_ERROR) {
*handle = (*outLayer)->getHandle();
- *gbp = (*outLayer)->getBufferQueue();
+ *gbp = (*outLayer)->getProducer();
}
ALOGE_IF(err, "createNormalLayer() failed (%s)", strerror(-err));
@@ -1986,7 +2012,7 @@ status_t SurfaceFlinger::createDimLayer(const sp<Client>& client,
{
*outLayer = new LayerDim(this, client, name, w, h, flags);
*handle = (*outLayer)->getHandle();
- *gbp = (*outLayer)->getBufferQueue();
+ *gbp = (*outLayer)->getProducer();
return NO_ERROR;
}
@@ -2216,8 +2242,8 @@ status_t SurfaceFlinger::dump(int fd, const Vector<String16>& args)
return NO_ERROR;
}
-void SurfaceFlinger::listLayersLocked(const Vector<String16>& args, size_t& index,
- String8& result) const
+void SurfaceFlinger::listLayersLocked(const Vector<String16>& /* args */,
+ size_t& /* index */, String8& result) const
{
const LayerVector& currentLayers = mCurrentState.layersSortedByZ;
const size_t count = currentLayers.size();
@@ -2241,21 +2267,21 @@ void SurfaceFlinger::dumpStatsLocked(const Vector<String16>& args, size_t& index
result.appendFormat("%" PRId64 "\n", period);
if (name.isEmpty()) {
- mAnimFrameTracker.dump(result);
+ mAnimFrameTracker.dumpStats(result);
} else {
const LayerVector& currentLayers = mCurrentState.layersSortedByZ;
const size_t count = currentLayers.size();
for (size_t i=0 ; i<count ; i++) {
const sp<Layer>& layer(currentLayers[i]);
if (name == layer->getName()) {
- layer->dumpStats(result);
+ layer->dumpFrameStats(result);
}
}
}
}
void SurfaceFlinger::clearStatsLocked(const Vector<String16>& args, size_t& index,
- String8& result)
+ String8& /* result */)
{
String8 name;
if (index < args.size()) {
@@ -2268,11 +2294,11 @@ void SurfaceFlinger::clearStatsLocked(const Vector<String16>& args, size_t& inde
for (size_t i=0 ; i<count ; i++) {
const sp<Layer>& layer(currentLayers[i]);
if (name.isEmpty() || (name == layer->getName())) {
- layer->clearStats();
+ layer->clearFrameStats();
}
}
- mAnimFrameTracker.clear();
+ mAnimFrameTracker.clearStats();
}
// This should only be called from the main thread. Otherwise it would need
@@ -2429,7 +2455,8 @@ void SurfaceFlinger::dumpAllLocked(const Vector<String16>& args, size_t& index,
colorizer.reset(result);
result.appendFormat(" h/w composer %s and %s\n",
hwc.initCheck()==NO_ERROR ? "present" : "not present",
- (mDebugDisableHWC || mDebugRegion || mDaltonize) ? "disabled" : "enabled");
+ (mDebugDisableHWC || mDebugRegion || mDaltonize
+ || mHasColorMatrix) ? "disabled" : "enabled");
hwc.dump(result);
/*
@@ -2485,6 +2512,8 @@ status_t SurfaceFlinger::onTransact(
case BOOT_FINISHED:
case BLANK:
case UNBLANK:
+ case CLEAR_ANIMATION_FRAME_STATS:
+ case GET_ANIMATION_FRAME_STATS:
{
// codes that require permission check
IPCThreadState* ipc = IPCThreadState::self();
@@ -2592,8 +2621,38 @@ status_t SurfaceFlinger::onTransact(
mDaltonize = n > 0;
invalidateHwcGeometry();
repaintEverything();
+ return NO_ERROR;
+ }
+ case 1015: {
+ // apply a color matrix
+ n = data.readInt32();
+ mHasColorMatrix = n ? 1 : 0;
+ if (n) {
+ // color matrix is sent as mat3 matrix followed by vec3
+ // offset, then packed into a mat4 where the last row is
+ // the offset and extra values are 0
+ for (size_t i = 0 ; i < 4; i++) {
+ for (size_t j = 0; j < 4; j++) {
+ mColorMatrix[i][j] = data.readFloat();
+ }
+ }
+ } else {
+ mColorMatrix = mat4();
+ }
+ invalidateHwcGeometry();
+ repaintEverything();
+ return NO_ERROR;
+ }
+ // This is an experimental interface
+ // Needs to be shifted to proper binder interface when we productize
+ case 1016: {
+ mPrimaryDispSync.setLowPowerMode(true);
+ return NO_ERROR;
+ }
+ case 1017: {
+ mPrimaryDispSync.setLowPowerMode(false);
+ return NO_ERROR;
}
- return NO_ERROR;
}
}
return err;
@@ -2639,7 +2698,7 @@ class GraphicProducerWrapper : public BBinder, public MessageHandler {
* data and reply Parcel and forward them to the calling thread.
*/
virtual status_t transact(uint32_t code,
- const Parcel& data, Parcel* reply, uint32_t flags) {
+ const Parcel& data, Parcel* reply, uint32_t /* flags */) {
this->code = code;
this->data = &data;
this->reply = reply;
@@ -2693,7 +2752,8 @@ public:
status_t SurfaceFlinger::captureScreen(const sp<IBinder>& display,
const sp<IGraphicBufferProducer>& producer,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ) {
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform) {
if (CC_UNLIKELY(display == 0))
return BAD_VALUE;
@@ -2719,16 +2779,19 @@ status_t SurfaceFlinger::captureScreen(const sp<IBinder>& display,
sp<IGraphicBufferProducer> producer;
uint32_t reqWidth, reqHeight;
uint32_t minLayerZ,maxLayerZ;
+ bool useIdentityTransform;
status_t result;
public:
MessageCaptureScreen(SurfaceFlinger* flinger,
const sp<IBinder>& display,
const sp<IGraphicBufferProducer>& producer,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ)
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform)
: flinger(flinger), display(display), producer(producer),
reqWidth(reqWidth), reqHeight(reqHeight),
minLayerZ(minLayerZ), maxLayerZ(maxLayerZ),
+ useIdentityTransform(useIdentityTransform),
result(PERMISSION_DENIED)
{
}
@@ -2738,8 +2801,9 @@ status_t SurfaceFlinger::captureScreen(const sp<IBinder>& display,
virtual bool handler() {
Mutex::Autolock _l(flinger->mStateLock);
sp<const DisplayDevice> hw(flinger->getDisplayDevice(display));
- result = flinger->captureScreenImplLocked(hw,
- producer, reqWidth, reqHeight, minLayerZ, maxLayerZ);
+ result = flinger->captureScreenImplLocked(hw, producer,
+ reqWidth, reqHeight, minLayerZ, maxLayerZ,
+ useIdentityTransform);
static_cast<GraphicProducerWrapper*>(producer->asBinder().get())->exit(result);
return true;
}
@@ -2761,7 +2825,7 @@ status_t SurfaceFlinger::captureScreen(const sp<IBinder>& display,
// which does the marshaling work forwards to our "fake remote" above.
sp<MessageBase> msg = new MessageCaptureScreen(this,
display, IGraphicBufferProducer::asInterface( wrapper ),
- reqWidth, reqHeight, minLayerZ, maxLayerZ);
+ reqWidth, reqHeight, minLayerZ, maxLayerZ, useIdentityTransform);
status_t res = postMessageAsync(msg);
if (res == NO_ERROR) {
@@ -2775,7 +2839,7 @@ void SurfaceFlinger::renderScreenImplLocked(
const sp<const DisplayDevice>& hw,
uint32_t reqWidth, uint32_t reqHeight,
uint32_t minLayerZ, uint32_t maxLayerZ,
- bool yswap)
+ bool yswap, bool useIdentityTransform)
{
ATRACE_CALL();
RenderEngine& engine(getRenderEngine());
@@ -2804,7 +2868,7 @@ void SurfaceFlinger::renderScreenImplLocked(
if (state.z >= minLayerZ && state.z <= maxLayerZ) {
if (layer->isVisible()) {
if (filtering) layer->setFiltering(true);
- layer->draw(hw);
+ layer->draw(hw, useIdentityTransform);
if (filtering) layer->setFiltering(false);
}
}
@@ -2821,7 +2885,8 @@ status_t SurfaceFlinger::captureScreenImplLocked(
const sp<const DisplayDevice>& hw,
const sp<IGraphicBufferProducer>& producer,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ)
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform)
{
ATRACE_CALL();
@@ -2875,7 +2940,7 @@ status_t SurfaceFlinger::captureScreenImplLocked(
// an EGLSurface and therefore we're not
// dependent on the context's EGLConfig.
renderScreenImplLocked(hw, reqWidth, reqHeight,
- minLayerZ, maxLayerZ, true);
+ minLayerZ, maxLayerZ, true, useIdentityTransform);
// Create a sync point and wait on it, so we know the buffer is
// ready before we pass it along. We can't trivially call glFlush(),
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 80bb619..717ee66 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -38,6 +38,7 @@
#include <binder/IMemory.h>
#include <ui/PixelFormat.h>
+#include <ui/mat4.h>
#include <gui/ISurfaceComposer.h>
#include <gui/ISurfaceComposerClient.h>
@@ -137,7 +138,7 @@ private:
friend class Client;
friend class DisplayEventConnection;
friend class Layer;
- friend class SurfaceTextureLayer;
+ friend class MonitoredProducer;
// This value is specified in number of frames. Log frame stats at most
// every half hour.
@@ -202,12 +203,15 @@ private:
virtual status_t captureScreen(const sp<IBinder>& display,
const sp<IGraphicBufferProducer>& producer,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ);
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform);
// called when screen needs to turn off
virtual void blank(const sp<IBinder>& display);
// called when screen is turning back on
virtual void unblank(const sp<IBinder>& display);
virtual status_t getDisplayInfo(const sp<IBinder>& display, DisplayInfo* info);
+ virtual status_t clearAnimationFrameStats();
+ virtual status_t getAnimationFrameStats(FrameStats* outStats) const;
/* ------------------------------------------------------------------------
* DeathRecipient interface
@@ -306,13 +310,14 @@ private:
const sp<const DisplayDevice>& hw,
uint32_t reqWidth, uint32_t reqHeight,
uint32_t minLayerZ, uint32_t maxLayerZ,
- bool yswap);
+ bool yswap, bool useIdentityTransform);
status_t captureScreenImplLocked(
const sp<const DisplayDevice>& hw,
const sp<IGraphicBufferProducer>& producer,
uint32_t reqWidth, uint32_t reqHeight,
- uint32_t minLayerZ, uint32_t maxLayerZ);
+ uint32_t minLayerZ, uint32_t maxLayerZ,
+ bool useIdentityTransform);
/* ------------------------------------------------------------------------
* EGL
@@ -472,6 +477,9 @@ private:
Daltonizer mDaltonizer;
bool mDaltonize;
+
+ mat4 mColorMatrix;
+ bool mHasColorMatrix;
};
}; // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerConsumer.cpp b/services/surfaceflinger/SurfaceFlingerConsumer.cpp
index 6dc093e..a412543 100644
--- a/services/surfaceflinger/SurfaceFlingerConsumer.cpp
+++ b/services/surfaceflinger/SurfaceFlingerConsumer.cpp
@@ -21,8 +21,9 @@
#include <private/gui/SyncFeatures.h>
-#include <utils/Trace.h>
#include <utils/Errors.h>
+#include <utils/NativeHandle.h>
+#include <utils/Trace.h>
namespace android {
@@ -112,6 +113,10 @@ bool SurfaceFlingerConsumer::getTransformToDisplayInverse() const {
return mTransformToDisplayInverse;
}
+sp<NativeHandle> SurfaceFlingerConsumer::getSidebandStream() const {
+ return mConsumer->getSidebandStream();
+}
+
// We need to determine the time when a buffer acquired now will be
// displayed. This can be calculated:
// time when previous buffer's actual-present fence was signaled
@@ -154,6 +159,26 @@ nsecs_t SurfaceFlingerConsumer::computeExpectedPresent()
return prevVsync + hwcLatency * vsyncPeriod + extraPadding;
}
+void SurfaceFlingerConsumer::setContentsChangedListener(
+ const wp<ContentsChangedListener>& listener) {
+ setFrameAvailableListener(listener);
+ Mutex::Autolock lock(mMutex);
+ mContentsChangedListener = listener;
+}
+
+void SurfaceFlingerConsumer::onSidebandStreamChanged() {
+ sp<ContentsChangedListener> listener;
+ { // scope for the lock
+ Mutex::Autolock lock(mMutex);
+ ALOG_ASSERT(mFrameAvailableListener.unsafe_get() == mContentsChangedListener.unsafe_get());
+ listener = mContentsChangedListener.promote();
+ }
+
+ if (listener != NULL) {
+ listener->onSidebandStreamChanged();
+ }
+}
+
// ---------------------------------------------------------------------------
}; // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerConsumer.h b/services/surfaceflinger/SurfaceFlingerConsumer.h
index 688ad32..0f1bf35 100644
--- a/services/surfaceflinger/SurfaceFlingerConsumer.h
+++ b/services/surfaceflinger/SurfaceFlingerConsumer.h
@@ -27,8 +27,13 @@ namespace android {
*/
class SurfaceFlingerConsumer : public GLConsumer {
public:
- SurfaceFlingerConsumer(const sp<BufferQueue>& bq, uint32_t tex)
- : GLConsumer(bq, tex, GLConsumer::TEXTURE_EXTERNAL, false)
+ struct ContentsChangedListener: public FrameAvailableListener {
+ virtual void onSidebandStreamChanged() = 0;
+ };
+
+ SurfaceFlingerConsumer(const sp<IGraphicBufferConsumer>& consumer,
+ uint32_t tex)
+ : GLConsumer(consumer, tex, GLConsumer::TEXTURE_EXTERNAL, false)
{}
class BufferRejecter {
@@ -54,9 +59,19 @@ public:
// must be called from SF main thread
bool getTransformToDisplayInverse() const;
+ // Sets the contents changed listener. This should be used instead of
+ // ConsumerBase::setFrameAvailableListener().
+ void setContentsChangedListener(const wp<ContentsChangedListener>& listener);
+
+ sp<NativeHandle> getSidebandStream() const;
+
private:
nsecs_t computeExpectedPresent();
+ virtual void onSidebandStreamChanged();
+
+ wp<ContentsChangedListener> mContentsChangedListener;
+
// Indicates this buffer must be transformed by the inverse transform of the screen
// it is displayed onto. This is applied after GLConsumer::mCurrentTransform.
// This must be set/read from SurfaceFlinger's main thread.
diff --git a/services/surfaceflinger/SurfaceTextureLayer.cpp b/services/surfaceflinger/SurfaceTextureLayer.cpp
deleted file mode 100644
index 9d79ce2..0000000
--- a/services/surfaceflinger/SurfaceTextureLayer.cpp
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.
- */
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <sys/types.h>
-
-#include <utils/Errors.h>
-
-#include "SurfaceFlinger.h"
-#include "SurfaceTextureLayer.h"
-
-namespace android {
-// ---------------------------------------------------------------------------
-
-
-SurfaceTextureLayer::SurfaceTextureLayer(const sp<SurfaceFlinger>& flinger)
- : BufferQueue(), flinger(flinger) {
-}
-
-SurfaceTextureLayer::~SurfaceTextureLayer() {
- // remove ourselves from SurfaceFlinger's list. We do this asynchronously
- // because we don't know where this dtor is called from, it could be
- // called with the mStateLock held, leading to a dead-lock (it actually
- // happens).
- class MessageCleanUpList : public MessageBase {
- sp<SurfaceFlinger> flinger;
- wp<IBinder> gbp;
- public:
- MessageCleanUpList(const sp<SurfaceFlinger>& flinger, const wp<IBinder>& gbp)
- : flinger(flinger), gbp(gbp) { }
- virtual bool handler() {
- Mutex::Autolock _l(flinger->mStateLock);
- flinger->mGraphicBufferProducerList.remove(gbp);
- return true;
- }
- };
- flinger->postMessageAsync(
- new MessageCleanUpList(flinger, static_cast<BnGraphicBufferProducer*>(this)) );
-}
-
-// ---------------------------------------------------------------------------
-}; // namespace android