| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 1 | // | 
 | 2 | // Copyright 2010 The Android Open Source Project | 
 | 3 | // | 
 | 4 | // Provides a shared memory transport for input events. | 
 | 5 | // | 
 | 6 | #define LOG_TAG "InputTransport" | 
 | 7 |  | 
 | 8 | //#define LOG_NDEBUG 0 | 
 | 9 |  | 
 | 10 | // Log debug messages about channel signalling (send signal, receive signal) | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 11 | #define DEBUG_CHANNEL_SIGNALS 0 | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 12 |  | 
 | 13 | // Log debug messages whenever InputChannel objects are created/destroyed | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 14 | #define DEBUG_CHANNEL_LIFECYCLE 0 | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 15 |  | 
 | 16 | // Log debug messages about transport actions (initialize, reset, publish, ...) | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 17 | #define DEBUG_TRANSPORT_ACTIONS 0 | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 18 |  | 
 | 19 |  | 
 | 20 | #include <cutils/ashmem.h> | 
 | 21 | #include <cutils/log.h> | 
 | 22 | #include <errno.h> | 
 | 23 | #include <fcntl.h> | 
 | 24 | #include <sys/mman.h> | 
 | 25 | #include <ui/InputTransport.h> | 
 | 26 | #include <unistd.h> | 
 | 27 |  | 
 | 28 | namespace android { | 
 | 29 |  | 
 | 30 | // Must be at least sizeof(InputMessage) + sufficient space for pointer data | 
 | 31 | static const int DEFAULT_MESSAGE_BUFFER_SIZE = 16384; | 
 | 32 |  | 
 | 33 | // Signal sent by the producer to the consumer to inform it that a new message is | 
 | 34 | // available to be consumed in the shared memory buffer. | 
 | 35 | static const char INPUT_SIGNAL_DISPATCH = 'D'; | 
 | 36 |  | 
 | 37 | // Signal sent by the consumer to the producer to inform it that it has finished | 
| Jeff Brown | 8149991 | 2010-11-05 15:02:16 -0700 | [diff] [blame] | 38 | // consuming the most recent message and it handled it. | 
 | 39 | static const char INPUT_SIGNAL_FINISHED_HANDLED = 'f'; | 
 | 40 |  | 
 | 41 | // Signal sent by the consumer to the producer to inform it that it has finished | 
 | 42 | // consuming the most recent message but it did not handle it. | 
 | 43 | static const char INPUT_SIGNAL_FINISHED_UNHANDLED = 'u'; | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 44 |  | 
 | 45 |  | 
 | 46 | // --- InputChannel --- | 
 | 47 |  | 
 | 48 | InputChannel::InputChannel(const String8& name, int32_t ashmemFd, int32_t receivePipeFd, | 
 | 49 |         int32_t sendPipeFd) : | 
 | 50 |         mName(name), mAshmemFd(ashmemFd), mReceivePipeFd(receivePipeFd), mSendPipeFd(sendPipeFd) { | 
 | 51 | #if DEBUG_CHANNEL_LIFECYCLE | 
 | 52 |     LOGD("Input channel constructed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d", | 
 | 53 |             mName.string(), ashmemFd, receivePipeFd, sendPipeFd); | 
 | 54 | #endif | 
 | 55 |  | 
 | 56 |     int result = fcntl(mReceivePipeFd, F_SETFL, O_NONBLOCK); | 
 | 57 |     LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make receive pipe " | 
 | 58 |             "non-blocking.  errno=%d", mName.string(), errno); | 
 | 59 |  | 
 | 60 |     result = fcntl(mSendPipeFd, F_SETFL, O_NONBLOCK); | 
 | 61 |     LOG_ALWAYS_FATAL_IF(result != 0, "channel '%s' ~ Could not make send pipe " | 
 | 62 |             "non-blocking.  errno=%d", mName.string(), errno); | 
 | 63 | } | 
 | 64 |  | 
 | 65 | InputChannel::~InputChannel() { | 
 | 66 | #if DEBUG_CHANNEL_LIFECYCLE | 
 | 67 |     LOGD("Input channel destroyed: name='%s', ashmemFd=%d, receivePipeFd=%d, sendPipeFd=%d", | 
 | 68 |             mName.string(), mAshmemFd, mReceivePipeFd, mSendPipeFd); | 
 | 69 | #endif | 
 | 70 |  | 
 | 71 |     ::close(mAshmemFd); | 
 | 72 |     ::close(mReceivePipeFd); | 
 | 73 |     ::close(mSendPipeFd); | 
 | 74 | } | 
 | 75 |  | 
 | 76 | status_t InputChannel::openInputChannelPair(const String8& name, | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 77 |         sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) { | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 78 |     status_t result; | 
 | 79 |  | 
 | 80 |     int serverAshmemFd = ashmem_create_region(name.string(), DEFAULT_MESSAGE_BUFFER_SIZE); | 
 | 81 |     if (serverAshmemFd < 0) { | 
 | 82 |         result = -errno; | 
 | 83 |         LOGE("channel '%s' ~ Could not create shared memory region. errno=%d", | 
 | 84 |                 name.string(), errno); | 
 | 85 |     } else { | 
 | 86 |         result = ashmem_set_prot_region(serverAshmemFd, PROT_READ | PROT_WRITE); | 
 | 87 |         if (result < 0) { | 
 | 88 |             LOGE("channel '%s' ~ Error %d trying to set protection of ashmem fd %d.", | 
 | 89 |                     name.string(), result, serverAshmemFd); | 
 | 90 |         } else { | 
 | 91 |             // Dup the file descriptor because the server and client input channel objects that | 
 | 92 |             // are returned may have different lifetimes but they share the same shared memory region. | 
 | 93 |             int clientAshmemFd; | 
 | 94 |             clientAshmemFd = dup(serverAshmemFd); | 
 | 95 |             if (clientAshmemFd < 0) { | 
 | 96 |                 result = -errno; | 
 | 97 |                 LOGE("channel '%s' ~ Could not dup() shared memory region fd. errno=%d", | 
 | 98 |                         name.string(), errno); | 
 | 99 |             } else { | 
 | 100 |                 int forward[2]; | 
 | 101 |                 if (pipe(forward)) { | 
 | 102 |                     result = -errno; | 
 | 103 |                     LOGE("channel '%s' ~ Could not create forward pipe.  errno=%d", | 
 | 104 |                             name.string(), errno); | 
 | 105 |                 } else { | 
 | 106 |                     int reverse[2]; | 
 | 107 |                     if (pipe(reverse)) { | 
 | 108 |                         result = -errno; | 
 | 109 |                         LOGE("channel '%s' ~ Could not create reverse pipe.  errno=%d", | 
 | 110 |                                 name.string(), errno); | 
 | 111 |                     } else { | 
 | 112 |                         String8 serverChannelName = name; | 
 | 113 |                         serverChannelName.append(" (server)"); | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 114 |                         outServerChannel = new InputChannel(serverChannelName, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 115 |                                 serverAshmemFd, reverse[0], forward[1]); | 
 | 116 |  | 
 | 117 |                         String8 clientChannelName = name; | 
 | 118 |                         clientChannelName.append(" (client)"); | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 119 |                         outClientChannel = new InputChannel(clientChannelName, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 120 |                                 clientAshmemFd, forward[0], reverse[1]); | 
 | 121 |                         return OK; | 
 | 122 |                     } | 
 | 123 |                     ::close(forward[0]); | 
 | 124 |                     ::close(forward[1]); | 
 | 125 |                 } | 
 | 126 |                 ::close(clientAshmemFd); | 
 | 127 |             } | 
 | 128 |         } | 
 | 129 |         ::close(serverAshmemFd); | 
 | 130 |     } | 
 | 131 |  | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 132 |     outServerChannel.clear(); | 
 | 133 |     outClientChannel.clear(); | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 134 |     return result; | 
 | 135 | } | 
 | 136 |  | 
 | 137 | status_t InputChannel::sendSignal(char signal) { | 
| Jeff Brown | f67f299 | 2010-09-16 17:04:52 -0700 | [diff] [blame] | 138 |     ssize_t nWrite; | 
 | 139 |     do { | 
 | 140 |         nWrite = ::write(mSendPipeFd, & signal, 1); | 
 | 141 |     } while (nWrite == -1 && errno == EINTR); | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 142 |  | 
 | 143 |     if (nWrite == 1) { | 
 | 144 | #if DEBUG_CHANNEL_SIGNALS | 
 | 145 |         LOGD("channel '%s' ~ sent signal '%c'", mName.string(), signal); | 
 | 146 | #endif | 
 | 147 |         return OK; | 
 | 148 |     } | 
 | 149 |  | 
 | 150 | #if DEBUG_CHANNEL_SIGNALS | 
 | 151 |     LOGD("channel '%s' ~ error sending signal '%c', errno=%d", mName.string(), signal, errno); | 
 | 152 | #endif | 
 | 153 |     return -errno; | 
 | 154 | } | 
 | 155 |  | 
 | 156 | status_t InputChannel::receiveSignal(char* outSignal) { | 
| Jeff Brown | f67f299 | 2010-09-16 17:04:52 -0700 | [diff] [blame] | 157 |     ssize_t nRead; | 
 | 158 |     do { | 
 | 159 |         nRead = ::read(mReceivePipeFd, outSignal, 1); | 
 | 160 |     } while (nRead == -1 && errno == EINTR); | 
 | 161 |  | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 162 |     if (nRead == 1) { | 
 | 163 | #if DEBUG_CHANNEL_SIGNALS | 
 | 164 |         LOGD("channel '%s' ~ received signal '%c'", mName.string(), *outSignal); | 
 | 165 | #endif | 
 | 166 |         return OK; | 
 | 167 |     } | 
 | 168 |  | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 169 |     if (nRead == 0) { // check for EOF | 
 | 170 | #if DEBUG_CHANNEL_SIGNALS | 
 | 171 |         LOGD("channel '%s' ~ receive signal failed because peer was closed", mName.string()); | 
 | 172 | #endif | 
 | 173 |         return DEAD_OBJECT; | 
 | 174 |     } | 
 | 175 |  | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 176 |     if (errno == EAGAIN) { | 
 | 177 | #if DEBUG_CHANNEL_SIGNALS | 
 | 178 |         LOGD("channel '%s' ~ receive signal failed because no signal available", mName.string()); | 
 | 179 | #endif | 
 | 180 |         return WOULD_BLOCK; | 
 | 181 |     } | 
 | 182 |  | 
 | 183 | #if DEBUG_CHANNEL_SIGNALS | 
 | 184 |     LOGD("channel '%s' ~ receive signal failed, errno=%d", mName.string(), errno); | 
 | 185 | #endif | 
 | 186 |     return -errno; | 
 | 187 | } | 
 | 188 |  | 
 | 189 |  | 
 | 190 | // --- InputPublisher --- | 
 | 191 |  | 
 | 192 | InputPublisher::InputPublisher(const sp<InputChannel>& channel) : | 
 | 193 |         mChannel(channel), mSharedMessage(NULL), | 
 | 194 |         mPinned(false), mSemaphoreInitialized(false), mWasDispatched(false), | 
 | 195 |         mMotionEventSampleDataTail(NULL) { | 
 | 196 | } | 
 | 197 |  | 
 | 198 | InputPublisher::~InputPublisher() { | 
 | 199 |     reset(); | 
 | 200 |  | 
 | 201 |     if (mSharedMessage) { | 
 | 202 |         munmap(mSharedMessage, mAshmemSize); | 
 | 203 |     } | 
 | 204 | } | 
 | 205 |  | 
 | 206 | status_t InputPublisher::initialize() { | 
 | 207 | #if DEBUG_TRANSPORT_ACTIONS | 
 | 208 |     LOGD("channel '%s' publisher ~ initialize", | 
 | 209 |             mChannel->getName().string()); | 
 | 210 | #endif | 
 | 211 |  | 
 | 212 |     int ashmemFd = mChannel->getAshmemFd(); | 
 | 213 |     int result = ashmem_get_size_region(ashmemFd); | 
 | 214 |     if (result < 0) { | 
 | 215 |         LOGE("channel '%s' publisher ~ Error %d getting size of ashmem fd %d.", | 
 | 216 |                 mChannel->getName().string(), result, ashmemFd); | 
 | 217 |         return UNKNOWN_ERROR; | 
 | 218 |     } | 
 | 219 |     mAshmemSize = (size_t) result; | 
 | 220 |  | 
 | 221 |     mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize, | 
 | 222 |             PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); | 
 | 223 |     if (! mSharedMessage) { | 
 | 224 |         LOGE("channel '%s' publisher ~ mmap failed on ashmem fd %d.", | 
 | 225 |                 mChannel->getName().string(), ashmemFd); | 
 | 226 |         return NO_MEMORY; | 
 | 227 |     } | 
 | 228 |  | 
 | 229 |     mPinned = true; | 
 | 230 |     mSharedMessage->consumed = false; | 
 | 231 |  | 
 | 232 |     return reset(); | 
 | 233 | } | 
 | 234 |  | 
 | 235 | status_t InputPublisher::reset() { | 
 | 236 | #if DEBUG_TRANSPORT_ACTIONS | 
 | 237 |     LOGD("channel '%s' publisher ~ reset", | 
 | 238 |         mChannel->getName().string()); | 
 | 239 | #endif | 
 | 240 |  | 
 | 241 |     if (mPinned) { | 
 | 242 |         // Destroy the semaphore since we are about to unpin the memory region that contains it. | 
 | 243 |         int result; | 
 | 244 |         if (mSemaphoreInitialized) { | 
 | 245 |             if (mSharedMessage->consumed) { | 
 | 246 |                 result = sem_post(& mSharedMessage->semaphore); | 
 | 247 |                 if (result < 0) { | 
 | 248 |                     LOGE("channel '%s' publisher ~ Error %d in sem_post.", | 
 | 249 |                             mChannel->getName().string(), errno); | 
 | 250 |                     return UNKNOWN_ERROR; | 
 | 251 |                 } | 
 | 252 |             } | 
 | 253 |  | 
 | 254 |             result = sem_destroy(& mSharedMessage->semaphore); | 
 | 255 |             if (result < 0) { | 
 | 256 |                 LOGE("channel '%s' publisher ~ Error %d in sem_destroy.", | 
 | 257 |                         mChannel->getName().string(), errno); | 
 | 258 |                 return UNKNOWN_ERROR; | 
 | 259 |             } | 
 | 260 |  | 
 | 261 |             mSemaphoreInitialized = false; | 
 | 262 |         } | 
 | 263 |  | 
 | 264 |         // Unpin the region since we no longer care about its contents. | 
 | 265 |         int ashmemFd = mChannel->getAshmemFd(); | 
 | 266 |         result = ashmem_unpin_region(ashmemFd, 0, 0); | 
 | 267 |         if (result < 0) { | 
 | 268 |             LOGE("channel '%s' publisher ~ Error %d unpinning ashmem fd %d.", | 
 | 269 |                     mChannel->getName().string(), result, ashmemFd); | 
 | 270 |             return UNKNOWN_ERROR; | 
 | 271 |         } | 
 | 272 |  | 
 | 273 |         mPinned = false; | 
 | 274 |     } | 
 | 275 |  | 
 | 276 |     mMotionEventSampleDataTail = NULL; | 
 | 277 |     mWasDispatched = false; | 
 | 278 |     return OK; | 
 | 279 | } | 
 | 280 |  | 
 | 281 | status_t InputPublisher::publishInputEvent( | 
 | 282 |         int32_t type, | 
 | 283 |         int32_t deviceId, | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 284 |         int32_t source) { | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 285 |     if (mPinned) { | 
 | 286 |         LOGE("channel '%s' publisher ~ Attempted to publish a new event but publisher has " | 
 | 287 |                 "not yet been reset.", mChannel->getName().string()); | 
 | 288 |         return INVALID_OPERATION; | 
 | 289 |     } | 
 | 290 |  | 
 | 291 |     // Pin the region. | 
 | 292 |     // We do not check for ASHMEM_NOT_PURGED because we don't care about the previous | 
 | 293 |     // contents of the buffer so it does not matter whether it was purged in the meantime. | 
 | 294 |     int ashmemFd = mChannel->getAshmemFd(); | 
 | 295 |     int result = ashmem_pin_region(ashmemFd, 0, 0); | 
 | 296 |     if (result < 0) { | 
 | 297 |         LOGE("channel '%s' publisher ~ Error %d pinning ashmem fd %d.", | 
 | 298 |                 mChannel->getName().string(), result, ashmemFd); | 
 | 299 |         return UNKNOWN_ERROR; | 
 | 300 |     } | 
 | 301 |  | 
 | 302 |     mPinned = true; | 
 | 303 |  | 
 | 304 |     result = sem_init(& mSharedMessage->semaphore, 1, 1); | 
 | 305 |     if (result < 0) { | 
 | 306 |         LOGE("channel '%s' publisher ~ Error %d in sem_init.", | 
 | 307 |                 mChannel->getName().string(), errno); | 
 | 308 |         return UNKNOWN_ERROR; | 
 | 309 |     } | 
 | 310 |  | 
 | 311 |     mSemaphoreInitialized = true; | 
 | 312 |  | 
 | 313 |     mSharedMessage->consumed = false; | 
 | 314 |     mSharedMessage->type = type; | 
 | 315 |     mSharedMessage->deviceId = deviceId; | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 316 |     mSharedMessage->source = source; | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 317 |     return OK; | 
 | 318 | } | 
 | 319 |  | 
 | 320 | status_t InputPublisher::publishKeyEvent( | 
 | 321 |         int32_t deviceId, | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 322 |         int32_t source, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 323 |         int32_t action, | 
 | 324 |         int32_t flags, | 
 | 325 |         int32_t keyCode, | 
 | 326 |         int32_t scanCode, | 
 | 327 |         int32_t metaState, | 
 | 328 |         int32_t repeatCount, | 
 | 329 |         nsecs_t downTime, | 
 | 330 |         nsecs_t eventTime) { | 
 | 331 | #if DEBUG_TRANSPORT_ACTIONS | 
| Jeff Brown | af30ff6 | 2010-09-01 17:01:00 -0700 | [diff] [blame] | 332 |     LOGD("channel '%s' publisher ~ publishKeyEvent: deviceId=%d, source=0x%x, " | 
 | 333 |             "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d," | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 334 |             "downTime=%lld, eventTime=%lld", | 
 | 335 |             mChannel->getName().string(), | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 336 |             deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 337 |             downTime, eventTime); | 
 | 338 | #endif | 
 | 339 |  | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 340 |     status_t result = publishInputEvent(AINPUT_EVENT_TYPE_KEY, deviceId, source); | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 341 |     if (result < 0) { | 
 | 342 |         return result; | 
 | 343 |     } | 
 | 344 |  | 
 | 345 |     mSharedMessage->key.action = action; | 
 | 346 |     mSharedMessage->key.flags = flags; | 
 | 347 |     mSharedMessage->key.keyCode = keyCode; | 
 | 348 |     mSharedMessage->key.scanCode = scanCode; | 
 | 349 |     mSharedMessage->key.metaState = metaState; | 
 | 350 |     mSharedMessage->key.repeatCount = repeatCount; | 
 | 351 |     mSharedMessage->key.downTime = downTime; | 
 | 352 |     mSharedMessage->key.eventTime = eventTime; | 
 | 353 |     return OK; | 
 | 354 | } | 
 | 355 |  | 
 | 356 | status_t InputPublisher::publishMotionEvent( | 
 | 357 |         int32_t deviceId, | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 358 |         int32_t source, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 359 |         int32_t action, | 
| Jeff Brown | af30ff6 | 2010-09-01 17:01:00 -0700 | [diff] [blame] | 360 |         int32_t flags, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 361 |         int32_t edgeFlags, | 
 | 362 |         int32_t metaState, | 
 | 363 |         float xOffset, | 
 | 364 |         float yOffset, | 
 | 365 |         float xPrecision, | 
 | 366 |         float yPrecision, | 
 | 367 |         nsecs_t downTime, | 
 | 368 |         nsecs_t eventTime, | 
 | 369 |         size_t pointerCount, | 
 | 370 |         const int32_t* pointerIds, | 
 | 371 |         const PointerCoords* pointerCoords) { | 
 | 372 | #if DEBUG_TRANSPORT_ACTIONS | 
| Jeff Brown | af30ff6 | 2010-09-01 17:01:00 -0700 | [diff] [blame] | 373 |     LOGD("channel '%s' publisher ~ publishMotionEvent: deviceId=%d, source=0x%x, " | 
 | 374 |             "action=0x%x, flags=0x%x, edgeFlags=0x%x, metaState=0x%x, xOffset=%f, yOffset=%f, " | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 375 |             "xPrecision=%f, yPrecision=%f, downTime=%lld, eventTime=%lld, " | 
 | 376 |             "pointerCount=%d", | 
 | 377 |             mChannel->getName().string(), | 
| Jeff Brown | af30ff6 | 2010-09-01 17:01:00 -0700 | [diff] [blame] | 378 |             deviceId, source, action, flags, edgeFlags, metaState, xOffset, yOffset, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 379 |             xPrecision, yPrecision, downTime, eventTime, pointerCount); | 
 | 380 | #endif | 
 | 381 |  | 
 | 382 |     if (pointerCount > MAX_POINTERS || pointerCount < 1) { | 
 | 383 |         LOGE("channel '%s' publisher ~ Invalid number of pointers provided: %d.", | 
 | 384 |                 mChannel->getName().string(), pointerCount); | 
 | 385 |         return BAD_VALUE; | 
 | 386 |     } | 
 | 387 |  | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 388 |     status_t result = publishInputEvent(AINPUT_EVENT_TYPE_MOTION, deviceId, source); | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 389 |     if (result < 0) { | 
 | 390 |         return result; | 
 | 391 |     } | 
 | 392 |  | 
 | 393 |     mSharedMessage->motion.action = action; | 
| Jeff Brown | af30ff6 | 2010-09-01 17:01:00 -0700 | [diff] [blame] | 394 |     mSharedMessage->motion.flags = flags; | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 395 |     mSharedMessage->motion.edgeFlags = edgeFlags; | 
 | 396 |     mSharedMessage->motion.metaState = metaState; | 
 | 397 |     mSharedMessage->motion.xOffset = xOffset; | 
 | 398 |     mSharedMessage->motion.yOffset = yOffset; | 
 | 399 |     mSharedMessage->motion.xPrecision = xPrecision; | 
 | 400 |     mSharedMessage->motion.yPrecision = yPrecision; | 
 | 401 |     mSharedMessage->motion.downTime = downTime; | 
 | 402 |     mSharedMessage->motion.pointerCount = pointerCount; | 
 | 403 |  | 
 | 404 |     mSharedMessage->motion.sampleCount = 1; | 
 | 405 |     mSharedMessage->motion.sampleData[0].eventTime = eventTime; | 
 | 406 |  | 
 | 407 |     for (size_t i = 0; i < pointerCount; i++) { | 
 | 408 |         mSharedMessage->motion.pointerIds[i] = pointerIds[i]; | 
 | 409 |         mSharedMessage->motion.sampleData[0].coords[i] = pointerCoords[i]; | 
 | 410 |     } | 
 | 411 |  | 
 | 412 |     // Cache essential information about the motion event to ensure that a malicious consumer | 
 | 413 |     // cannot confuse the publisher by modifying the contents of the shared memory buffer while | 
 | 414 |     // it is being updated. | 
| Jeff Brown | 25943a8 | 2011-02-19 05:07:28 -0800 | [diff] [blame] | 415 |     if (action == AMOTION_EVENT_ACTION_MOVE | 
 | 416 |             || action == AMOTION_EVENT_ACTION_HOVER_MOVE) { | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 417 |         mMotionEventPointerCount = pointerCount; | 
 | 418 |         mMotionEventSampleDataStride = InputMessage::sampleDataStride(pointerCount); | 
 | 419 |         mMotionEventSampleDataTail = InputMessage::sampleDataPtrIncrement( | 
 | 420 |                 mSharedMessage->motion.sampleData, mMotionEventSampleDataStride); | 
 | 421 |     } else { | 
 | 422 |         mMotionEventSampleDataTail = NULL; | 
 | 423 |     } | 
 | 424 |     return OK; | 
 | 425 | } | 
 | 426 |  | 
 | 427 | status_t InputPublisher::appendMotionSample( | 
 | 428 |         nsecs_t eventTime, | 
 | 429 |         const PointerCoords* pointerCoords) { | 
 | 430 | #if DEBUG_TRANSPORT_ACTIONS | 
 | 431 |     LOGD("channel '%s' publisher ~ appendMotionSample: eventTime=%lld", | 
 | 432 |             mChannel->getName().string(), eventTime); | 
 | 433 | #endif | 
 | 434 |  | 
 | 435 |     if (! mPinned || ! mMotionEventSampleDataTail) { | 
 | 436 |         LOGE("channel '%s' publisher ~ Cannot append motion sample because there is no current " | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 437 |                 "AMOTION_EVENT_ACTION_MOVE event.", mChannel->getName().string()); | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 438 |         return INVALID_OPERATION; | 
 | 439 |     } | 
 | 440 |  | 
 | 441 |     InputMessage::SampleData* newTail = InputMessage::sampleDataPtrIncrement( | 
 | 442 |             mMotionEventSampleDataTail, mMotionEventSampleDataStride); | 
 | 443 |     size_t newBytesUsed = reinterpret_cast<char*>(newTail) - | 
 | 444 |             reinterpret_cast<char*>(mSharedMessage); | 
 | 445 |  | 
 | 446 |     if (newBytesUsed > mAshmemSize) { | 
| Jeff Brown | 50de30a | 2010-06-22 01:27:15 -0700 | [diff] [blame] | 447 | #if DEBUG_TRANSPORT_ACTIONS | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 448 |         LOGD("channel '%s' publisher ~ Cannot append motion sample because the shared memory " | 
 | 449 |                 "buffer is full.  Buffer size: %d bytes, pointers: %d, samples: %d", | 
 | 450 |                 mChannel->getName().string(), | 
 | 451 |                 mAshmemSize, mMotionEventPointerCount, mSharedMessage->motion.sampleCount); | 
| Jeff Brown | 50de30a | 2010-06-22 01:27:15 -0700 | [diff] [blame] | 452 | #endif | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 453 |         return NO_MEMORY; | 
 | 454 |     } | 
 | 455 |  | 
 | 456 |     int result; | 
 | 457 |     if (mWasDispatched) { | 
 | 458 |         result = sem_trywait(& mSharedMessage->semaphore); | 
 | 459 |         if (result < 0) { | 
 | 460 |             if (errno == EAGAIN) { | 
 | 461 |                 // Only possible source of contention is the consumer having consumed (or being in the | 
 | 462 |                 // process of consuming) the message and left the semaphore count at 0. | 
| Jeff Brown | 50de30a | 2010-06-22 01:27:15 -0700 | [diff] [blame] | 463 | #if DEBUG_TRANSPORT_ACTIONS | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 464 |                 LOGD("channel '%s' publisher ~ Cannot append motion sample because the message has " | 
 | 465 |                         "already been consumed.", mChannel->getName().string()); | 
| Jeff Brown | 50de30a | 2010-06-22 01:27:15 -0700 | [diff] [blame] | 466 | #endif | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 467 |                 return FAILED_TRANSACTION; | 
 | 468 |             } else { | 
 | 469 |                 LOGE("channel '%s' publisher ~ Error %d in sem_trywait.", | 
 | 470 |                         mChannel->getName().string(), errno); | 
 | 471 |                 return UNKNOWN_ERROR; | 
 | 472 |             } | 
 | 473 |         } | 
 | 474 |     } | 
 | 475 |  | 
 | 476 |     mMotionEventSampleDataTail->eventTime = eventTime; | 
 | 477 |     for (size_t i = 0; i < mMotionEventPointerCount; i++) { | 
 | 478 |         mMotionEventSampleDataTail->coords[i] = pointerCoords[i]; | 
 | 479 |     } | 
 | 480 |     mMotionEventSampleDataTail = newTail; | 
 | 481 |  | 
 | 482 |     mSharedMessage->motion.sampleCount += 1; | 
 | 483 |  | 
 | 484 |     if (mWasDispatched) { | 
 | 485 |         result = sem_post(& mSharedMessage->semaphore); | 
 | 486 |         if (result < 0) { | 
 | 487 |             LOGE("channel '%s' publisher ~ Error %d in sem_post.", | 
 | 488 |                     mChannel->getName().string(), errno); | 
 | 489 |             return UNKNOWN_ERROR; | 
 | 490 |         } | 
 | 491 |     } | 
 | 492 |     return OK; | 
 | 493 | } | 
 | 494 |  | 
 | 495 | status_t InputPublisher::sendDispatchSignal() { | 
 | 496 | #if DEBUG_TRANSPORT_ACTIONS | 
 | 497 |     LOGD("channel '%s' publisher ~ sendDispatchSignal", | 
 | 498 |             mChannel->getName().string()); | 
 | 499 | #endif | 
 | 500 |  | 
 | 501 |     mWasDispatched = true; | 
 | 502 |     return mChannel->sendSignal(INPUT_SIGNAL_DISPATCH); | 
 | 503 | } | 
 | 504 |  | 
| Jeff Brown | 02d85b5 | 2010-12-06 17:13:33 -0800 | [diff] [blame] | 505 | status_t InputPublisher::receiveFinishedSignal(bool* outHandled) { | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 506 | #if DEBUG_TRANSPORT_ACTIONS | 
 | 507 |     LOGD("channel '%s' publisher ~ receiveFinishedSignal", | 
 | 508 |             mChannel->getName().string()); | 
 | 509 | #endif | 
 | 510 |  | 
 | 511 |     char signal; | 
 | 512 |     status_t result = mChannel->receiveSignal(& signal); | 
 | 513 |     if (result) { | 
| Jeff Brown | 02d85b5 | 2010-12-06 17:13:33 -0800 | [diff] [blame] | 514 |         *outHandled = false; | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 515 |         return result; | 
 | 516 |     } | 
| Jeff Brown | 8149991 | 2010-11-05 15:02:16 -0700 | [diff] [blame] | 517 |     if (signal == INPUT_SIGNAL_FINISHED_HANDLED) { | 
| Jeff Brown | 02d85b5 | 2010-12-06 17:13:33 -0800 | [diff] [blame] | 518 |         *outHandled = true; | 
| Jeff Brown | 8149991 | 2010-11-05 15:02:16 -0700 | [diff] [blame] | 519 |     } else if (signal == INPUT_SIGNAL_FINISHED_UNHANDLED) { | 
| Jeff Brown | 02d85b5 | 2010-12-06 17:13:33 -0800 | [diff] [blame] | 520 |         *outHandled = false; | 
| Jeff Brown | 8149991 | 2010-11-05 15:02:16 -0700 | [diff] [blame] | 521 |     } else { | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 522 |         LOGE("channel '%s' publisher ~ Received unexpected signal '%c' from consumer", | 
 | 523 |                 mChannel->getName().string(), signal); | 
 | 524 |         return UNKNOWN_ERROR; | 
 | 525 |     } | 
 | 526 |     return OK; | 
 | 527 | } | 
 | 528 |  | 
 | 529 | // --- InputConsumer --- | 
 | 530 |  | 
 | 531 | InputConsumer::InputConsumer(const sp<InputChannel>& channel) : | 
 | 532 |         mChannel(channel), mSharedMessage(NULL) { | 
 | 533 | } | 
 | 534 |  | 
 | 535 | InputConsumer::~InputConsumer() { | 
 | 536 |     if (mSharedMessage) { | 
 | 537 |         munmap(mSharedMessage, mAshmemSize); | 
 | 538 |     } | 
 | 539 | } | 
 | 540 |  | 
 | 541 | status_t InputConsumer::initialize() { | 
 | 542 | #if DEBUG_TRANSPORT_ACTIONS | 
 | 543 |     LOGD("channel '%s' consumer ~ initialize", | 
 | 544 |             mChannel->getName().string()); | 
 | 545 | #endif | 
 | 546 |  | 
 | 547 |     int ashmemFd = mChannel->getAshmemFd(); | 
 | 548 |     int result = ashmem_get_size_region(ashmemFd); | 
 | 549 |     if (result < 0) { | 
 | 550 |         LOGE("channel '%s' consumer ~ Error %d getting size of ashmem fd %d.", | 
 | 551 |                 mChannel->getName().string(), result, ashmemFd); | 
 | 552 |         return UNKNOWN_ERROR; | 
 | 553 |     } | 
 | 554 |  | 
 | 555 |     mAshmemSize = (size_t) result; | 
 | 556 |  | 
 | 557 |     mSharedMessage = static_cast<InputMessage*>(mmap(NULL, mAshmemSize, | 
 | 558 |             PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0)); | 
 | 559 |     if (! mSharedMessage) { | 
 | 560 |         LOGE("channel '%s' consumer ~ mmap failed on ashmem fd %d.", | 
 | 561 |                 mChannel->getName().string(), ashmemFd); | 
 | 562 |         return NO_MEMORY; | 
 | 563 |     } | 
 | 564 |  | 
 | 565 |     return OK; | 
 | 566 | } | 
 | 567 |  | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 568 | status_t InputConsumer::consume(InputEventFactoryInterface* factory, InputEvent** outEvent) { | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 569 | #if DEBUG_TRANSPORT_ACTIONS | 
 | 570 |     LOGD("channel '%s' consumer ~ consume", | 
 | 571 |             mChannel->getName().string()); | 
 | 572 | #endif | 
 | 573 |  | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 574 |     *outEvent = NULL; | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 575 |  | 
 | 576 |     int ashmemFd = mChannel->getAshmemFd(); | 
 | 577 |     int result = ashmem_pin_region(ashmemFd, 0, 0); | 
 | 578 |     if (result != ASHMEM_NOT_PURGED) { | 
 | 579 |         if (result == ASHMEM_WAS_PURGED) { | 
 | 580 |             LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d because it was purged " | 
 | 581 |                     "which probably indicates that the publisher and consumer are out of sync.", | 
 | 582 |                     mChannel->getName().string(), result, ashmemFd); | 
 | 583 |             return INVALID_OPERATION; | 
 | 584 |         } | 
 | 585 |  | 
 | 586 |         LOGE("channel '%s' consumer ~ Error %d pinning ashmem fd %d.", | 
 | 587 |                 mChannel->getName().string(), result, ashmemFd); | 
 | 588 |         return UNKNOWN_ERROR; | 
 | 589 |     } | 
 | 590 |  | 
 | 591 |     if (mSharedMessage->consumed) { | 
 | 592 |         LOGE("channel '%s' consumer ~ The current message has already been consumed.", | 
 | 593 |                 mChannel->getName().string()); | 
 | 594 |         return INVALID_OPERATION; | 
 | 595 |     } | 
 | 596 |  | 
 | 597 |     // Acquire but *never release* the semaphore.  Contention on the semaphore is used to signal | 
 | 598 |     // to the publisher that the message has been consumed (or is in the process of being | 
 | 599 |     // consumed).  Eventually the publisher will reinitialize the semaphore for the next message. | 
 | 600 |     result = sem_wait(& mSharedMessage->semaphore); | 
 | 601 |     if (result < 0) { | 
 | 602 |         LOGE("channel '%s' consumer ~ Error %d in sem_wait.", | 
 | 603 |                 mChannel->getName().string(), errno); | 
 | 604 |         return UNKNOWN_ERROR; | 
 | 605 |     } | 
 | 606 |  | 
 | 607 |     mSharedMessage->consumed = true; | 
 | 608 |  | 
 | 609 |     switch (mSharedMessage->type) { | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 610 |     case AINPUT_EVENT_TYPE_KEY: { | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 611 |         KeyEvent* keyEvent = factory->createKeyEvent(); | 
 | 612 |         if (! keyEvent) return NO_MEMORY; | 
 | 613 |  | 
 | 614 |         populateKeyEvent(keyEvent); | 
 | 615 |  | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 616 |         *outEvent = keyEvent; | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 617 |         break; | 
 | 618 |     } | 
 | 619 |  | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 620 |     case AINPUT_EVENT_TYPE_MOTION: { | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 621 |         MotionEvent* motionEvent = factory->createMotionEvent(); | 
 | 622 |         if (! motionEvent) return NO_MEMORY; | 
 | 623 |  | 
 | 624 |         populateMotionEvent(motionEvent); | 
 | 625 |  | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 626 |         *outEvent = motionEvent; | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 627 |         break; | 
 | 628 |     } | 
 | 629 |  | 
 | 630 |     default: | 
 | 631 |         LOGE("channel '%s' consumer ~ Received message of unknown type %d", | 
 | 632 |                 mChannel->getName().string(), mSharedMessage->type); | 
 | 633 |         return UNKNOWN_ERROR; | 
 | 634 |     } | 
 | 635 |  | 
 | 636 |     return OK; | 
 | 637 | } | 
 | 638 |  | 
| Jeff Brown | 8149991 | 2010-11-05 15:02:16 -0700 | [diff] [blame] | 639 | status_t InputConsumer::sendFinishedSignal(bool handled) { | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 640 | #if DEBUG_TRANSPORT_ACTIONS | 
| Jeff Brown | 8149991 | 2010-11-05 15:02:16 -0700 | [diff] [blame] | 641 |     LOGD("channel '%s' consumer ~ sendFinishedSignal: handled=%d", | 
 | 642 |             mChannel->getName().string(), handled); | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 643 | #endif | 
 | 644 |  | 
| Jeff Brown | 8149991 | 2010-11-05 15:02:16 -0700 | [diff] [blame] | 645 |     return mChannel->sendSignal(handled | 
 | 646 |             ? INPUT_SIGNAL_FINISHED_HANDLED | 
 | 647 |             : INPUT_SIGNAL_FINISHED_UNHANDLED); | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 648 | } | 
 | 649 |  | 
 | 650 | status_t InputConsumer::receiveDispatchSignal() { | 
 | 651 | #if DEBUG_TRANSPORT_ACTIONS | 
 | 652 |     LOGD("channel '%s' consumer ~ receiveDispatchSignal", | 
 | 653 |             mChannel->getName().string()); | 
 | 654 | #endif | 
 | 655 |  | 
 | 656 |     char signal; | 
 | 657 |     status_t result = mChannel->receiveSignal(& signal); | 
 | 658 |     if (result) { | 
 | 659 |         return result; | 
 | 660 |     } | 
 | 661 |     if (signal != INPUT_SIGNAL_DISPATCH) { | 
 | 662 |         LOGE("channel '%s' consumer ~ Received unexpected signal '%c' from publisher", | 
 | 663 |                 mChannel->getName().string(), signal); | 
 | 664 |         return UNKNOWN_ERROR; | 
 | 665 |     } | 
 | 666 |     return OK; | 
 | 667 | } | 
 | 668 |  | 
 | 669 | void InputConsumer::populateKeyEvent(KeyEvent* keyEvent) const { | 
 | 670 |     keyEvent->initialize( | 
 | 671 |             mSharedMessage->deviceId, | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 672 |             mSharedMessage->source, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 673 |             mSharedMessage->key.action, | 
 | 674 |             mSharedMessage->key.flags, | 
 | 675 |             mSharedMessage->key.keyCode, | 
 | 676 |             mSharedMessage->key.scanCode, | 
 | 677 |             mSharedMessage->key.metaState, | 
 | 678 |             mSharedMessage->key.repeatCount, | 
 | 679 |             mSharedMessage->key.downTime, | 
 | 680 |             mSharedMessage->key.eventTime); | 
 | 681 | } | 
 | 682 |  | 
 | 683 | void InputConsumer::populateMotionEvent(MotionEvent* motionEvent) const { | 
 | 684 |     motionEvent->initialize( | 
 | 685 |             mSharedMessage->deviceId, | 
| Jeff Brown | 5c1ed84 | 2010-07-14 18:48:53 -0700 | [diff] [blame] | 686 |             mSharedMessage->source, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 687 |             mSharedMessage->motion.action, | 
| Jeff Brown | af30ff6 | 2010-09-01 17:01:00 -0700 | [diff] [blame] | 688 |             mSharedMessage->motion.flags, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 689 |             mSharedMessage->motion.edgeFlags, | 
 | 690 |             mSharedMessage->motion.metaState, | 
| Jeff Brown | f4a4ec2 | 2010-06-16 01:53:36 -0700 | [diff] [blame] | 691 |             mSharedMessage->motion.xOffset, | 
 | 692 |             mSharedMessage->motion.yOffset, | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 693 |             mSharedMessage->motion.xPrecision, | 
 | 694 |             mSharedMessage->motion.yPrecision, | 
 | 695 |             mSharedMessage->motion.downTime, | 
 | 696 |             mSharedMessage->motion.sampleData[0].eventTime, | 
 | 697 |             mSharedMessage->motion.pointerCount, | 
 | 698 |             mSharedMessage->motion.pointerIds, | 
 | 699 |             mSharedMessage->motion.sampleData[0].coords); | 
 | 700 |  | 
 | 701 |     size_t sampleCount = mSharedMessage->motion.sampleCount; | 
 | 702 |     if (sampleCount > 1) { | 
 | 703 |         InputMessage::SampleData* sampleData = mSharedMessage->motion.sampleData; | 
 | 704 |         size_t sampleDataStride = InputMessage::sampleDataStride( | 
 | 705 |                 mSharedMessage->motion.pointerCount); | 
 | 706 |  | 
 | 707 |         while (--sampleCount > 0) { | 
 | 708 |             sampleData = InputMessage::sampleDataPtrIncrement(sampleData, sampleDataStride); | 
 | 709 |             motionEvent->addSample(sampleData->eventTime, sampleData->coords); | 
 | 710 |         } | 
 | 711 |     } | 
| Jeff Brown | e839a58 | 2010-04-22 18:58:52 -0700 | [diff] [blame] | 712 | } | 
 | 713 |  | 
 | 714 | } // namespace android |