summaryrefslogtreecommitdiffstats
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/hwui/FontRenderer.cpp24
-rw-r--r--libs/hwui/FontRenderer.h3
-rw-r--r--libs/hwui/Layer.h13
-rw-r--r--libs/hwui/LayerCache.cpp24
-rw-r--r--libs/hwui/LayerCache.h4
-rw-r--r--libs/hwui/OpenGLRenderer.cpp226
-rw-r--r--libs/hwui/OpenGLRenderer.h26
-rw-r--r--libs/hwui/ProgramCache.cpp5
-rw-r--r--libs/hwui/ProgramCache.h13
-rw-r--r--libs/hwui/Snapshot.h74
-rw-r--r--libs/ui/InputDispatcher.cpp2162
-rw-r--r--libs/ui/InputManager.cpp48
-rw-r--r--libs/ui/InputReader.cpp165
-rw-r--r--libs/utils/PollLoop.cpp8
14 files changed, 2115 insertions, 680 deletions
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 5d7f8bf..2959814 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -166,6 +166,8 @@ void Font::measureUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t
renderUTF(paint, text, start, len, numGlyphs, 0, 0, MEASURE, NULL, 0, 0, bounds);
}
+#define SkAutoKern_AdjustF(prev, next) (((next) - (prev) + 32) >> 6 << 16)
+
void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t len,
int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap,
uint32_t bitmapW, uint32_t bitmapH,Rect *bounds) {
@@ -173,12 +175,16 @@ void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t
return;
}
- int penX = x, penY = y;
+ SkFixed penX = SkIntToFixed(x);
+ int penY = y;
int glyphsLeft = 1;
if (numGlyphs > 0) {
glyphsLeft = numGlyphs;
}
+ SkFixed prevRsbDelta = 0;
+ penX += SK_Fixed1 / 2;
+
text += start;
while (glyphsLeft > 0) {
@@ -190,23 +196,25 @@ void Font::renderUTF(SkPaint* paint, const char* text, uint32_t start, uint32_t
}
CachedGlyphInfo* cachedGlyph = getCachedUTFChar(paint, utfChar);
+ penX += SkAutoKern_AdjustF(prevRsbDelta, cachedGlyph->mLsbDelta);
+ prevRsbDelta = cachedGlyph->mRsbDelta;
// If it's still not valid, we couldn't cache it, so we shouldn't draw garbage
if (cachedGlyph->mIsValid) {
switch(mode) {
case FRAMEBUFFER:
- drawCachedGlyph(cachedGlyph, penX, penY);
+ drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY);
break;
case BITMAP:
- drawCachedGlyph(cachedGlyph, penX, penY, bitmap, bitmapW, bitmapH);
+ drawCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bitmap, bitmapW, bitmapH);
break;
case MEASURE:
- measureCachedGlyph(cachedGlyph, penX, penY, bounds);
+ measureCachedGlyph(cachedGlyph, SkFixedFloor(penX), penY, bounds);
break;
}
}
- penX += SkFixedFloor(cachedGlyph->mAdvanceX);
+ penX += cachedGlyph->mAdvanceX;
// If we were given a specific number of glyphs, decrement
if (numGlyphs > 0) {
@@ -220,6 +228,8 @@ void Font::updateGlyphCache(SkPaint* paint, const SkGlyph& skiaGlyph, CachedGlyp
glyph->mAdvanceY = skiaGlyph.fAdvanceY;
glyph->mBitmapLeft = skiaGlyph.fLeft;
glyph->mBitmapTop = skiaGlyph.fTop;
+ glyph->mLsbDelta = skiaGlyph.fLsbDelta;
+ glyph->mRsbDelta = skiaGlyph.fRsbDelta;
uint32_t startX = 0;
uint32_t startY = 0;
@@ -352,7 +362,7 @@ void FontRenderer::flushAllAndInvalidate() {
bool FontRenderer::cacheBitmap(const SkGlyph& glyph, uint32_t* retOriginX, uint32_t* retOriginY) {
// If the glyph is too tall, don't cache it
- if (glyph.fWidth > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
+ if (glyph.fHeight > mCacheLines[mCacheLines.size() - 1]->mMaxHeight) {
LOGE("Font size to large to fit in cache. width, height = %i, %i",
(int) glyph.fWidth, (int) glyph.fHeight);
return false;
@@ -616,7 +626,7 @@ void FontRenderer::setFont(SkPaint* paint, uint32_t fontId, float fontSize) {
const float maxPrecacheFontSize = 40.0f;
bool isNewFont = currentNumFonts != mActiveFonts.size();
- if(isNewFont && fontSize <= maxPrecacheFontSize ){
+ if (isNewFont && fontSize <= maxPrecacheFontSize) {
precacheLatin(paint);
}
}
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index a03ea92..de5c019 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -94,6 +94,9 @@ protected:
// Values below contain a glyph's origin in the bitmap
int32_t mBitmapLeft;
int32_t mBitmapTop;
+ // Auto-kerning
+ SkFixed mLsbDelta;
+ SkFixed mRsbDelta;
};
Font(FontRenderer* state, uint32_t fontId, float fontSize);
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index d4db782..c527038 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -63,28 +63,19 @@ struct LayerSize {
*/
struct Layer {
/**
- * Coordinates of the layer corresponding to this snapshot.
- * Only set when the flag kFlagIsLayer is set.
+ * Coordinates of the layer.
*/
Rect layer;
/**
* Name of the texture used to render the layer.
- * Only set when the flag kFlagIsLayer is set.
*/
GLuint texture;
/**
- * Name of the FBO used to render the layer.
- * Only set when the flag kFlagIsLayer is set.
- */
- GLuint fbo;
- /**
* Opacity of the layer.
- * Only set when the flag kFlagIsLayer is set.
*/
- float alpha;
+ int alpha;
/**
* Blending mode of the layer.
- * Only set when the flag kFlagIsLayer is set.
*/
SkXfermode::Mode mode;
/**
diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp
index a204778..1a18766 100644
--- a/libs/hwui/LayerCache.cpp
+++ b/libs/hwui/LayerCache.cpp
@@ -87,7 +87,6 @@ void LayerCache::deleteLayer(Layer* layer) {
if (layer) {
mSize -= layer->layer.getWidth() * layer->layer.getHeight() * 4;
- glDeleteFramebuffers(1, &layer->fbo);
glDeleteTextures(1, &layer->texture);
delete layer;
}
@@ -99,7 +98,7 @@ void LayerCache::clear() {
mCache.setOnEntryRemovedListener(NULL);
}
-Layer* LayerCache::get(LayerSize& size, GLuint previousFbo) {
+Layer* LayerCache::get(LayerSize& size) {
Layer* layer = mCache.remove(size);
if (layer) {
LAYER_LOGD("Reusing layer");
@@ -111,10 +110,6 @@ Layer* LayerCache::get(LayerSize& size, GLuint previousFbo) {
layer = new Layer;
layer->blend = true;
- // Generate the FBO and attach the texture
- glGenFramebuffers(1, &layer->fbo);
- glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
-
// Generate the texture in which the FBO will draw
glGenTextures(1, &layer->texture);
glBindTexture(GL_TEXTURE_2D, layer->texture);
@@ -128,23 +123,6 @@ Layer* LayerCache::get(LayerSize& size, GLuint previousFbo) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size.width, size.height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-
- // Bind texture to FBO
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
- layer->texture, 0);
-
- GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- if (status != GL_FRAMEBUFFER_COMPLETE) {
- LOGE("Framebuffer incomplete (GL error code 0x%x)", status);
-
- glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
-
- glDeleteFramebuffers(1, &layer->fbo);
- glDeleteTextures(1, &layer->texture);
- delete layer;
-
- return NULL;
- }
}
return layer;
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index 9860994..c0c7542 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -62,10 +62,8 @@ public:
* size of the cache goes down.
*
* @param size The dimensions of the desired layer
- * @param previousFbo The name of the FBO to bind to if creating a new
- * layer fails
*/
- Layer* get(LayerSize& size, GLuint previousFbo);
+ Layer* get(LayerSize& size);
/**
* Adds the layer to the cache. The layer will not be added if there is
* not enough space available.
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 6c90704..b1f5f6b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -80,6 +80,24 @@ static const Blender gBlends[] = {
{ SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }
};
+// This array contains the swapped version of each SkXfermode. For instance
+// this array's SrcOver blending mode is actually DstOver. You can refer to
+// createLayer() for more information on the purpose of this array.
+static const Blender gBlendsSwap[] = {
+ { SkXfermode::kClear_Mode, GL_ZERO, GL_ZERO },
+ { SkXfermode::kSrc_Mode, GL_ZERO, GL_ONE },
+ { SkXfermode::kDst_Mode, GL_ONE, GL_ZERO },
+ { SkXfermode::kSrcOver_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE },
+ { SkXfermode::kDstOver_Mode, GL_ONE, GL_ONE_MINUS_SRC_ALPHA },
+ { SkXfermode::kSrcIn_Mode, GL_ZERO, GL_SRC_ALPHA },
+ { SkXfermode::kDstIn_Mode, GL_DST_ALPHA, GL_ZERO },
+ { SkXfermode::kSrcOut_Mode, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA },
+ { SkXfermode::kDstOut_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ZERO },
+ { SkXfermode::kSrcATop_Mode, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA },
+ { SkXfermode::kDstATop_Mode, GL_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA },
+ { SkXfermode::kXor_Mode, GL_ONE_MINUS_DST_ALPHA, GL_ONE_MINUS_SRC_ALPHA }
+};
+
static const GLenum gTextureUnits[] = {
GL_TEXTURE0,
GL_TEXTURE1,
@@ -122,8 +140,6 @@ void OpenGLRenderer::setViewport(int width, int height) {
mWidth = width;
mHeight = height;
- mFirstSnapshot->height = height;
- mFirstSnapshot->viewport.set(0, 0, width, height);
}
void OpenGLRenderer::prepare() {
@@ -155,14 +171,19 @@ void OpenGLRenderer::acquireContext() {
}
void OpenGLRenderer::releaseContext() {
- glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
+ glViewport(0, 0, mWidth, mHeight);
glEnable(GL_SCISSOR_TEST);
setScissorFromClip();
+ glDisable(GL_DITHER);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
if (mCaches.blend) {
glEnable(GL_BLEND);
glBlendFunc(mCaches.lastSrcMode, mCaches.lastDstMode);
+ glBlendEquation(GL_FUNC_ADD);
} else {
glDisable(GL_BLEND);
}
@@ -202,17 +223,10 @@ int OpenGLRenderer::saveSnapshot(int flags) {
bool OpenGLRenderer::restoreSnapshot() {
bool restoreClip = mSnapshot->flags & Snapshot::kFlagClipSet;
bool restoreLayer = mSnapshot->flags & Snapshot::kFlagIsLayer;
- bool restoreOrtho = mSnapshot->flags & Snapshot::kFlagDirtyOrtho;
sp<Snapshot> current = mSnapshot;
sp<Snapshot> previous = mSnapshot->previous;
- if (restoreOrtho) {
- Rect& r = previous->viewport;
- glViewport(r.left, r.top, r.right, r.bottom);
- mOrthoMatrix.load(current->orthoMatrix);
- }
-
mSaveCount--;
mSnapshot = previous;
@@ -253,11 +267,7 @@ int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
mode = SkXfermode::kSrcOver_Mode;
}
- if (alpha > 0 && !mSnapshot->invisible) {
- createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
- } else {
- mSnapshot->invisible = true;
- }
+ createLayer(mSnapshot, left, top, right, bottom, alpha, mode, flags);
return count;
}
@@ -273,79 +283,123 @@ int OpenGLRenderer::saveLayerAlpha(float left, float top, float right, float bot
}
}
+/**
+ * Layers are viewed by Skia are slightly different than layers in image editing
+ * programs (for instance.) When a layer is created, previously created layers
+ * and the frame buffer still receive every drawing command. For instance, if a
+ * layer is created and a shape intersecting the bounds of the layers and the
+ * framebuffer is draw, the shape will be drawn on both (unless the layer was
+ * created with the SkCanvas::kClipToLayer_SaveFlag flag.)
+ *
+ * A way to implement layers is to create an FBO for each layer, backed by an RGBA
+ * texture. Unfortunately, this is inefficient as it requires every primitive to
+ * be drawn n + 1 times, where n is the number of active layers. In practice this
+ * means, for every primitive:
+ * - Switch active frame buffer
+ * - Change viewport, clip and projection matrix
+ * - Issue the drawing
+ *
+ * Switching rendering target n + 1 times per drawn primitive is extremely costly.
+ * To avoid this, layers are implemented in a different way here.
+ *
+ * This implementation relies on the frame buffer being at least RGBA 8888. When
+ * a layer is created, only a texture is created, not an FBO. The content of the
+ * frame buffer contained within the layer's bounds is copied into this texture
+ * using glCopyTexImage2D(). The layer's region is then cleared(1) in the frame
+ * buffer and drawing continues as normal. This technique therefore treats the
+ * frame buffer as a scratch buffer for the layers.
+ *
+ * To compose the layers back onto the frame buffer, each layer texture
+ * (containing the original frame buffer data) is drawn as a simple quad over
+ * the frame buffer. The trick is that the quad is set as the composition
+ * destination in the blending equation, and the frame buffer becomes the source
+ * of the composition.
+ *
+ * Drawing layers with an alpha value requires an extra step before composition.
+ * An empty quad is drawn over the layer's region in the frame buffer. This quad
+ * is drawn with the rgba color (0,0,0,alpha). The alpha value offered by the
+ * quad is used to multiply the colors in the frame buffer. This is achieved by
+ * changing the GL blend functions for the GL_FUNC_ADD blend equation to
+ * GL_ZERO, GL_SRC_ALPHA.
+ *
+ * Because glCopyTexImage2D() can be slow, an alternative implementation might
+ * be use to draw a single clipped layer. The implementation described above
+ * is correct in every case.
+ *
+ * (1) The frame buffer is actually not cleared right away. To allow the GPU
+ * to potentially optimize series of calls to glCopyTexImage2D, the frame
+ * buffer is left untouched until the first drawing operation. Only when
+ * something actually gets drawn are the layers regions cleared.
+ */
bool OpenGLRenderer::createLayer(sp<Snapshot> snapshot, float left, float top,
float right, float bottom, int alpha, SkXfermode::Mode mode,int flags) {
LAYER_LOGD("Requesting layer %fx%f", right - left, bottom - top);
LAYER_LOGD("Layer cache size = %d", mCaches.layerCache.getSize());
+ // Window coordinates of the layer
Rect bounds(left, top, right, bottom);
- // TODO: Apply transformations and treat layers in screen coordinates
- // mSnapshot->transform->mapRect(bounds);
+ mSnapshot->transform->mapRect(bounds);
- GLuint previousFbo = snapshot->previous.get() ? snapshot->previous->fbo : 0;
- LayerSize size(bounds.getWidth(), bounds.getHeight());
+ // Layers only make sense if they are in the framebuffer's bounds
+ bounds.intersect(*mSnapshot->clipRect);
+ if (bounds.isEmpty()) return false;
- Layer* layer = mCaches.layerCache.get(size, previousFbo);
+ LayerSize size(bounds.getWidth(), bounds.getHeight());
+ Layer* layer = mCaches.layerCache.get(size);
if (!layer) {
return false;
}
- glBindFramebuffer(GL_FRAMEBUFFER, layer->fbo);
-
- // Clear the FBO
- glDisable(GL_SCISSOR_TEST);
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- glClear(GL_COLOR_BUFFER_BIT);
- glEnable(GL_SCISSOR_TEST);
-
layer->mode = mode;
- layer->alpha = alpha / 255.0f;
+ layer->alpha = alpha;
layer->layer.set(bounds);
// Save the layer in the snapshot
snapshot->flags |= Snapshot::kFlagIsLayer;
snapshot->layer = layer;
- snapshot->fbo = layer->fbo;
- // TODO: Temporary until real layer support is implemented
- snapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
- // TODO: Temporary until real layer support is implemented
- snapshot->resetClip(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
- snapshot->viewport.set(0.0f, 0.0f, bounds.getWidth(), bounds.getHeight());
- snapshot->height = bounds.getHeight();
- snapshot->flags |= Snapshot::kFlagDirtyOrtho;
- snapshot->orthoMatrix.load(mOrthoMatrix);
- setScissorFromClip();
+ // Copy the framebuffer into the layer
+ glBindTexture(GL_TEXTURE_2D, layer->texture);
+ glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bounds.left, mHeight - bounds.bottom,
+ bounds.getWidth(), bounds.getHeight(), 0);
+
+ if (flags & SkCanvas::kClipToLayer_SaveFlag) {
+ if (mSnapshot->clipTransformed(bounds)) setScissorFromClip();
+ }
- // Change the ortho projection
- glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
- mOrthoMatrix.loadOrtho(0.0f, bounds.getWidth(), bounds.getHeight(), 0.0f, -1.0f, 1.0f);
+ // Enqueue the buffer coordinates to clear the corresponding region later
+ mLayers.push(new Rect(bounds));
return true;
}
+/**
+ * Read the documentation of createLayer() before doing anything in this method.
+ */
void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
if (!current->layer) {
LOGE("Attempting to compose a layer that does not exist");
return;
}
- // Unbind current FBO and restore previous one
- // Most of the time, previous->fbo will be 0 to bind the default buffer
- glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
-
// Restore the clip from the previous snapshot
const Rect& clip = *previous->clipRect;
- glScissor(clip.left, previous->height - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
Layer* layer = current->layer;
const Rect& rect = layer->layer;
- // FBOs are already drawn with a top-left origin, don't flip the texture
+ if (layer->alpha < 255) {
+ drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
+ layer->alpha << 24, SkXfermode::kDstIn_Mode, true);
+ }
+
+ // Layers are already drawn with a top-left origin, don't flip the texture
resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
- drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
- layer->texture, layer->alpha, layer->mode, layer->blend);
+ drawTextureMesh(rect.left, rect.top, rect.right, rect.bottom, layer->texture,
+ 1.0f, layer->mode, layer->blend, &mMeshVertices[0].position[0],
+ &mMeshVertices[0].texture[0], NULL, 0, true, true);
resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
@@ -355,13 +409,32 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
if (!mCaches.layerCache.put(size, layer)) {
LAYER_LOGD("Deleting layer");
- glDeleteFramebuffers(1, &layer->fbo);
glDeleteTextures(1, &layer->texture);
delete layer;
}
}
+void OpenGLRenderer::clearLayerRegions() {
+ if (mLayers.size() == 0) return;
+
+ for (uint32_t i = 0; i < mLayers.size(); i++) {
+ Rect* bounds = mLayers.itemAt(i);
+
+ // Clear the framebuffer where the layer will draw
+ glScissor(bounds->left, mHeight - bounds->bottom,
+ bounds->getWidth(), bounds->getHeight());
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ delete bounds;
+ }
+ mLayers.clear();
+
+ // Restore the clip
+ setScissorFromClip();
+}
+
///////////////////////////////////////////////////////////////////////////////
// Transforms
///////////////////////////////////////////////////////////////////////////////
@@ -397,7 +470,7 @@ void OpenGLRenderer::concatMatrix(SkMatrix* matrix) {
void OpenGLRenderer::setScissorFromClip() {
const Rect& clip = *mSnapshot->clipRect;
- glScissor(clip.left, mSnapshot->height - clip.bottom, clip.getWidth(), clip.getHeight());
+ glScissor(clip.left, mHeight - clip.bottom, clip.getWidth(), clip.getHeight());
}
const Rect& OpenGLRenderer::getClipBounds() {
@@ -405,8 +478,6 @@ const Rect& OpenGLRenderer::getClipBounds() {
}
bool OpenGLRenderer::quickReject(float left, float top, float right, float bottom) {
- if (mSnapshot->invisible) return true;
-
Rect r(left, top, right, bottom);
mSnapshot->transform->mapRect(r);
return !mSnapshot->clipRect->intersects(r);
@@ -503,6 +574,7 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
Patch* mesh = mCaches.patchCache.get(patch);
mesh->updateVertices(bitmap, left, top, right, bottom,
&patch->xDivs[0], &patch->yDivs[0], patch->numXDivs, patch->numYDivs);
+ mesh->dump();
// Specify right and bottom as +1.0f from left/top to prevent scaling since the
// patch mesh already defines the final size
@@ -512,7 +584,6 @@ void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
}
void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
- if (mSnapshot->invisible) return;
const Rect& clip = *mSnapshot->clipRect;
drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode, true);
}
@@ -545,18 +616,10 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom,
void OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
float x, float y, SkPaint* paint) {
- if (mSnapshot->invisible || text == NULL || count == 0 ||
- (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
+ if (text == NULL || count == 0 || (paint->getAlpha() == 0 && paint->getXfermode() == NULL)) {
return;
}
-
- float scaleX = paint->getTextScaleX();
- bool applyScaleX = scaleX < 0.9999f || scaleX > 1.0001f;
- if (applyScaleX) {
- save(SkCanvas::kMatrix_SaveFlag);
- translate(x - (x * scaleX), 0.0f);
- scale(scaleX, 1.0f);
- }
+ paint->setAntiAlias(true);
float length = -1.0f;
switch (paint->getTextAlign()) {
@@ -606,21 +669,16 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
mode, false, true);
const Rect& clip = mSnapshot->getLocalClip();
+ clearLayerRegions();
fontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
drawTextDecorations(text, bytesCount, length, x, y, paint);
-
- if (applyScaleX) {
- restore();
- }
}
void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
- if (mSnapshot->invisible) return;
-
GLuint textureUnit = 0;
glActiveTexture(gTextureUnits[textureUnit]);
@@ -647,6 +705,8 @@ void OpenGLRenderer::drawPath(SkPath* path, SkPaint* paint) {
setupTextureAlpha8(texture, textureUnit, x, y, r, g, b, a, mode, true, true);
+ clearLayerRegions();
+
// Draw the mesh
glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
glDisableVertexAttribArray(mCaches.currentProgram->getAttrib("texCoords"));
@@ -778,6 +838,7 @@ void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t
}
}
+// Same values used by Skia
#define kStdStrikeThru_Offset (-6.0f / 21.0f)
#define kStdUnderline_Offset (1.0f / 9.0f)
#define kStdUnderline_Thickness (1.0f / 18.0f)
@@ -831,6 +892,8 @@ void OpenGLRenderer::drawTextDecorations(const char* text, int bytesCount, float
void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom,
int color, SkXfermode::Mode mode, bool ignoreTransform) {
+ clearLayerRegions();
+
// If a shader is set, preserve only the alpha
if (mShader) {
color |= 0x00ffffff;
@@ -905,7 +968,10 @@ void OpenGLRenderer::drawTextureRect(float left, float top, float right, float b
void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom,
GLuint texture, float alpha, SkXfermode::Mode mode, bool blend,
- GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount) {
+ GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount,
+ bool swapSrcDst, bool ignoreTransform) {
+ clearLayerRegions();
+
ProgramDescription description;
description.hasTexture = true;
if (mColorFilter) {
@@ -915,10 +981,15 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b
mModelView.loadTranslate(left, top, 0.0f);
mModelView.scale(right - left, bottom - top, 1.0f);
- chooseBlending(blend || alpha < 1.0f, mode, description);
+ chooseBlending(blend || alpha < 1.0f, mode, description, swapSrcDst);
useProgram(mCaches.programCache.get(description));
- mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+ if (!ignoreTransform) {
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
+ } else {
+ mat4 m;
+ mCaches.currentProgram->set(mOrthoMatrix, mModelView, m);
+ }
// Texture
bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0);
@@ -948,7 +1019,7 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b
}
void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
- ProgramDescription& description) {
+ ProgramDescription& description, bool swapSrcDst) {
blend = blend || mode != SkXfermode::kSrcOver_Mode;
if (blend) {
if (mode < SkXfermode::kPlus_Mode) {
@@ -956,8 +1027,8 @@ void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
glEnable(GL_BLEND);
}
- GLenum sourceMode = gBlends[mode].src;
- GLenum destMode = gBlends[mode].dst;
+ GLenum sourceMode = swapSrcDst ? gBlendsSwap[mode].src : gBlends[mode].src;
+ GLenum destMode = swapSrcDst ? gBlendsSwap[mode].dst : gBlends[mode].dst;
if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) {
glBlendFunc(sourceMode, destMode);
@@ -970,6 +1041,7 @@ void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
// the blending, turn blending off here
if (mExtensions.hasFramebufferFetch()) {
description.framebufferMode = mode;
+ description.swapSrcDst = swapSrcDst;
}
if (mCaches.blend) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 50f42c2..3126754 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -29,6 +29,7 @@
#include <utils/RefBase.h>
#include <utils/ResourceTypes.h>
+#include <utils/Vector.h>
#include "Extensions.h"
#include "Matrix.h"
@@ -156,6 +157,12 @@ private:
int alpha, SkXfermode::Mode mode, int flags);
/**
+ * Clears all the regions corresponding to the current list of layers.
+ * This method MUST be invoked before any drawing operation.
+ */
+ void clearLayerRegions();
+
+ /**
* Draws a colored rectangle with the specified color. The specified coordinates
* are transformed by the current snapshot's transform matrix.
*
@@ -166,9 +173,10 @@ private:
* @param color The rectangle's ARGB color, defined as a packed 32 bits word
* @param mode The Skia xfermode to use
* @param ignoreTransform True if the current transform should be ignored
+ * @paran ignoreBlending True if the blending is set by the caller
*/
void drawColorRect(float left, float top, float right, float bottom,
- int color, SkXfermode::Mode mode, bool ignoreTransform = false);
+ int color, SkXfermode::Mode mode, bool ignoreTransform = false);
/**
* Draws a textured rectangle with the specified texture. The specified coordinates
@@ -216,10 +224,13 @@ private:
* @param texCoords The texture coordinates of each vertex
* @param indices The indices of the vertices, can be NULL
* @param elementsCount The number of elements in the mesh, required by indices
+ * @param swapSrcDst Whether or not the src and dst blending operations should be swapped
+ * @param ignoreTransform True if the current transform should be ignored
*/
void drawTextureMesh(float left, float top, float right, float bottom, GLuint texture,
float alpha, SkXfermode::Mode mode, bool blend,
- GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount = 0);
+ GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount = 0,
+ bool swapSrcDst = false, bool ignoreTransform = false);
/**
* Prepares the renderer to draw the specified shadow.
@@ -322,8 +333,13 @@ private:
* Enable or disable blending as necessary. This function sets the appropriate
* blend function based on the specified xfermode.
*/
- inline void chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description);
+ inline void chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description,
+ bool swapSrcDst = false);
+ /**
+ * Safely retrieves the mode from the specified xfermode. If the specified
+ * xfermode is null, the mode is assumed to be SkXfermode::kSrcOver_Mode.
+ */
inline SkXfermode::Mode getXfermode(SkXfermode* mode);
/**
@@ -375,6 +391,10 @@ private:
// Various caches
Caches& mCaches;
+
+ // List of rectangles to clear due to calls to saveLayer()
+ Vector<Rect*> mLayers;
+
}; // class OpenGLRenderer
}; // namespace uirenderer
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index ff65c1b..becbc22 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -119,6 +119,8 @@ const char* gFS_Main_FragColor =
" gl_FragColor = fragColor;\n";
const char* gFS_Main_FragColor_Blend =
" gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n";
+const char* gFS_Main_FragColor_Blend_Swap =
+ " gl_FragColor = blendFramebuffer(gl_LastFragColor, fragColor);\n";
const char* gFS_Main_ApplyColorOp[4] = {
// None
"",
@@ -376,7 +378,8 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
if (!blendFramebuffer) {
shader.append(gFS_Main_FragColor);
} else {
- shader.append(gFS_Main_FragColor_Blend);
+ shader.append(!description.swapSrcDst ?
+ gFS_Main_FragColor_Blend : gFS_Main_FragColor_Blend_Swap);
}
}
// End the shader
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 8f5304d..0a17052 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -35,7 +35,7 @@ namespace uirenderer {
///////////////////////////////////////////////////////////////////////////////
// Debug
-#define DEBUG_PROGRAM_CACHE 1
+#define DEBUG_PROGRAM_CACHE 0
// Debug
#if DEBUG_PROGRAM_CACHE
@@ -44,6 +44,9 @@ namespace uirenderer {
#define PROGRAM_LOGD(...)
#endif
+/*
+ * IMPORTANT: All 32 bits are used, switch to a long.
+ */
#define PROGRAM_KEY_TEXTURE 0x1
#define PROGRAM_KEY_A8_TEXTURE 0x2
#define PROGRAM_KEY_BITMAP 0x4
@@ -53,6 +56,7 @@ namespace uirenderer {
#define PROGRAM_KEY_COLOR_LIGHTING 0x40
#define PROGRAM_KEY_COLOR_BLEND 0x80
#define PROGRAM_KEY_BITMAP_NPOT 0x100
+#define PROGRAM_KEY_SWAP_SRC_DST 0x2000
#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
#define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800
@@ -70,6 +74,9 @@ namespace uirenderer {
// Types
///////////////////////////////////////////////////////////////////////////////
+/*
+ * IMPORTANT: All 32 bits are used, switch to a long.
+ */
typedef uint32_t programid;
///////////////////////////////////////////////////////////////////////////////
@@ -95,7 +102,7 @@ struct ProgramDescription {
shadersMode(SkXfermode::kClear_Mode), isBitmapFirst(false),
bitmapWrapS(GL_CLAMP_TO_EDGE), bitmapWrapT(GL_CLAMP_TO_EDGE),
colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode),
- framebufferMode(SkXfermode::kClear_Mode) {
+ framebufferMode(SkXfermode::kClear_Mode), swapSrcDst(false) {
}
// Texturing
@@ -118,6 +125,7 @@ struct ProgramDescription {
// Framebuffer blending (requires Extensions.hasFramebufferFetch())
// Ignored for all values < SkXfermode::kPlus_Mode
SkXfermode::Mode framebufferMode;
+ bool swapSrcDst;
inline uint32_t getEnumForWrap(GLenum wrap) const {
switch (wrap) {
@@ -163,6 +171,7 @@ struct ProgramDescription {
break;
}
key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT;
+ if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST;
return key;
}
}; // struct ProgramDescription
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index da48243..062c986 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -43,7 +43,7 @@ namespace uirenderer {
*/
class Snapshot: public LightRefBase<Snapshot> {
public:
- Snapshot(): invisible(false), flags(0), previous(NULL), layer(NULL), fbo(0) {
+ Snapshot(): flags(0), previous(NULL), layer(NULL) {
transform = &mTransformRoot;
clipRect = &mClipRectRoot;
}
@@ -53,13 +53,7 @@ public:
* the previous snapshot.
*/
Snapshot(const sp<Snapshot>& s, int saveFlags):
- height(s->height),
- invisible(s->invisible),
- flags(0),
- previous(s),
- layer(NULL),
- fbo(s->fbo),
- viewport(s->viewport) {
+ flags(0), previous(s), layer(NULL) {
if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
mTransformRoot.load(*s->transform);
transform = &mTransformRoot;
@@ -97,24 +91,36 @@ public:
*/
kFlagIsLayer = 0x2,
/**
- * Indicates that this snapshot has changed the ortho matrix.
- */
- kFlagDirtyOrtho = 0x4,
- /**
* Indicates that the local clip should be recomputed.
*/
- kFlagDirtyLocalClip = 0x8,
+ kFlagDirtyLocalClip = 0x4,
};
/**
- * Intersects the current clip with the new clip rectangle.
+ * Modifies the current clip with the new clip rectangle and
+ * the specified operation. The specified rectangle is transformed
+ * by this snapshot's trasnformation.
*/
- bool clip(float left, float top, float right, float bottom, SkRegion::Op op) {
- bool clipped = false;
-
+ bool clip(float left, float top, float right, float bottom,
+ SkRegion::Op op = SkRegion::kIntersect_Op) {
Rect r(left, top, right, bottom);
transform->mapRect(r);
+ return clipTransformed(r, op);
+ }
+ /**
+ * Modifies the current clip with the new clip rectangle and
+ * the specified operation. The specified rectangle is considered
+ * already transformed.
+ */
+ bool clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op) {
+ bool clipped = false;
+
+ // NOTE: The unimplemented operations require support for regions
+ // Supporting regions would require using a stencil buffer instead
+ // of the scissor. The stencil buffer itself is not too expensive
+ // (memory cost excluded) but on fillrate limited devices, managing
+ // the stencil might have a negative impact on the framerate.
switch (op) {
case SkRegion::kDifference_Op:
break;
@@ -162,29 +168,6 @@ public:
return mLocalClip;
}
- // TODO: Temporary
- void resetTransform(float x, float y, float z) {
- transform = &mTransformRoot;
- transform->loadTranslate(x, y, z);
- }
-
- // TODO: Temporary
- void resetClip(float left, float top, float right, float bottom) {
- clipRect = &mClipRectRoot;
- clipRect->set(left, top, right, bottom);
- flags |= Snapshot::kFlagClipSet | Snapshot::kFlagDirtyLocalClip;
- }
-
- /**
- * Height of the framebuffer the snapshot is rendering into.
- */
- int height;
-
- /**
- * If true, the layer won't be rendered.
- */
- bool invisible;
-
/**
* Dirty flags.
*/
@@ -199,17 +182,6 @@ public:
* Only set when the flag kFlagIsLayer is set.
*/
Layer* layer;
- GLuint fbo;
-
- /**
- * Current viewport.
- */
- Rect viewport;
-
- /**
- * Contains the previous ortho matrix.
- */
- mat4 orthoMatrix;
/**
* Local transformation. Holds the current translation, scale and
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index df232d4..b8a26b0 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -31,8 +31,15 @@
// Log debug messages about input event throttling.
#define DEBUG_THROTTLING 0
+// Log debug messages about input focus tracking.
+#define DEBUG_FOCUS 0
+
+// Log debug messages about the app switch latency optimization.
+#define DEBUG_APP_SWITCH 0
+
#include <cutils/log.h>
#include <ui/InputDispatcher.h>
+#include <ui/PowerManager.h>
#include <stddef.h>
#include <unistd.h>
@@ -41,31 +48,62 @@
namespace android {
-// TODO, this needs to be somewhere else, perhaps in the policy
-static inline bool isMovementKey(int32_t keyCode) {
- return keyCode == AKEYCODE_DPAD_UP
- || keyCode == AKEYCODE_DPAD_DOWN
- || keyCode == AKEYCODE_DPAD_LEFT
- || keyCode == AKEYCODE_DPAD_RIGHT;
-}
+// Delay between reporting long touch events to the power manager.
+const nsecs_t EVENT_IGNORE_DURATION = 300 * 1000000LL; // 300 ms
+
+// 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
+
static inline nsecs_t now() {
return systemTime(SYSTEM_TIME_MONOTONIC);
}
+static inline const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
+
+// --- InputWindow ---
+
+bool InputWindow::visibleFrameIntersects(const InputWindow* other) const {
+ return visibleFrameRight > other->visibleFrameLeft
+ && visibleFrameLeft < other->visibleFrameRight
+ && visibleFrameBottom > other->visibleFrameTop
+ && visibleFrameTop < other->visibleFrameBottom;
+}
+
+bool InputWindow::touchableAreaContainsPoint(int32_t x, int32_t y) const {
+ return x >= touchableAreaLeft && x <= touchableAreaRight
+ && y >= touchableAreaTop && y <= touchableAreaBottom;
+}
+
+
// --- InputDispatcher ---
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
- mPolicy(policy) {
+ mPolicy(policy),
+ mPendingEvent(NULL), mAppSwitchDueTime(LONG_LONG_MAX),
+ mDispatchEnabled(true), mDispatchFrozen(false),
+ mFocusedWindow(NULL), mTouchDown(false), mTouchedWindow(NULL),
+ mFocusedApplication(NULL),
+ mCurrentInputTargetsValid(false),
+ mInputTargetWaitCause(INPUT_TARGET_WAIT_CAUSE_NONE) {
mPollLoop = new PollLoop(false);
- mInboundQueue.head.refCount = -1;
- mInboundQueue.head.type = EventEntry::TYPE_SENTINEL;
- mInboundQueue.head.eventTime = LONG_LONG_MIN;
+ mInboundQueue.headSentinel.refCount = -1;
+ mInboundQueue.headSentinel.type = EventEntry::TYPE_SENTINEL;
+ mInboundQueue.headSentinel.eventTime = LONG_LONG_MIN;
- mInboundQueue.tail.refCount = -1;
- mInboundQueue.tail.type = EventEntry::TYPE_SENTINEL;
- mInboundQueue.tail.eventTime = LONG_LONG_MAX;
+ mInboundQueue.tailSentinel.refCount = -1;
+ mInboundQueue.tailSentinel.type = EventEntry::TYPE_SENTINEL;
+ mInboundQueue.tailSentinel.eventTime = LONG_LONG_MAX;
mKeyRepeatState.lastKeyEntry = NULL;
@@ -77,21 +115,19 @@ InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& polic
mThrottleState.originalSampleCount = 0;
LOGD("Throttling - Max events per second = %d", maxEventsPerSecond);
#endif
-
- mCurrentInputTargetsValid = false;
}
InputDispatcher::~InputDispatcher() {
- resetKeyRepeatLocked();
+ { // acquire lock
+ AutoMutex _l(mLock);
- while (mConnectionsByReceiveFd.size() != 0) {
- unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel);
+ resetKeyRepeatLocked();
+ releasePendingEventLocked(true);
+ drainInboundQueueLocked();
}
- for (EventEntry* entry = mInboundQueue.head.next; entry != & mInboundQueue.tail; ) {
- EventEntry* next = entry->next;
- mAllocator.releaseEventEntry(next);
- entry = next;
+ while (mConnectionsByReceiveFd.size() != 0) {
+ unregisterInputChannel(mConnectionsByReceiveFd.valueAt(0)->inputChannel);
}
}
@@ -99,167 +135,282 @@ void InputDispatcher::dispatchOnce() {
nsecs_t keyRepeatTimeout = mPolicy->getKeyRepeatTimeout();
nsecs_t keyRepeatDelay = mPolicy->getKeyRepeatDelay();
- bool skipPoll = false;
- nsecs_t currentTime;
nsecs_t nextWakeupTime = LONG_LONG_MAX;
{ // acquire lock
AutoMutex _l(mLock);
- currentTime = now();
+ dispatchOnceInnerLocked(keyRepeatTimeout, keyRepeatDelay, & nextWakeupTime);
- // Reset the key repeat timer whenever we disallow key events, even if the next event
- // is not a key. This is to ensure that we abort a key repeat if the device is just coming
- // out of sleep.
- // XXX we should handle resetting input state coming out of sleep more generally elsewhere
- if (keyRepeatTimeout < 0) {
- resetKeyRepeatLocked();
+ if (runCommandsLockedInterruptible()) {
+ nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
}
+ } // release lock
- // Detect and process timeouts for all connections and determine if there are any
- // synchronous event dispatches pending. This step is entirely non-interruptible.
- bool hasPendingSyncTarget = false;
- size_t activeConnectionCount = mActiveConnections.size();
- for (size_t i = 0; i < activeConnectionCount; i++) {
- Connection* connection = mActiveConnections.itemAt(i);
+ // Wait for callback or timeout or wake. (make sure we round up, not down)
+ nsecs_t currentTime = now();
+ int32_t timeoutMillis;
+ if (nextWakeupTime > currentTime) {
+ uint64_t timeout = uint64_t(nextWakeupTime - currentTime);
+ timeout = (timeout + 999999LL) / 1000000LL;
+ timeoutMillis = timeout > INT_MAX ? -1 : int32_t(timeout);
+ } else {
+ timeoutMillis = 0;
+ }
- if (connection->hasPendingSyncTarget()) {
- hasPendingSyncTarget = true;
- }
+ mPollLoop->pollOnce(timeoutMillis);
+}
+
+void InputDispatcher::dispatchOnceInnerLocked(nsecs_t keyRepeatTimeout,
+ nsecs_t keyRepeatDelay, nsecs_t* nextWakeupTime) {
+ nsecs_t currentTime = now();
+
+ // Reset the key repeat timer whenever we disallow key events, even if the next event
+ // is not a key. This is to ensure that we abort a key repeat if the device is just coming
+ // out of sleep.
+ if (keyRepeatTimeout < 0) {
+ resetKeyRepeatLocked();
+ }
+
+ // If dispatching is disabled, drop all events in the queue.
+ if (! mDispatchEnabled) {
+ if (mPendingEvent || ! mInboundQueue.isEmpty()) {
+ LOGI("Dropping pending events because input dispatch is disabled.");
+ releasePendingEventLocked(true);
+ drainInboundQueueLocked();
+ }
+ return;
+ }
+
+ // If dispatching is frozen, do not process timeouts or try to deliver any new events.
+ if (mDispatchFrozen) {
+#if DEBUG_FOCUS
+ LOGD("Dispatch frozen. Waiting some more.");
+#endif
+ return;
+ }
- nsecs_t connectionTimeoutTime = connection->nextTimeoutTime;
- if (connectionTimeoutTime <= currentTime) {
- mTimedOutConnections.add(connection);
- } else if (connectionTimeoutTime < nextWakeupTime) {
- nextWakeupTime = connectionTimeoutTime;
+ // 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;
+ }
+
+ // Detect and process timeouts for all connections and determine if there are any
+ // synchronous event dispatches pending. This step is entirely non-interruptible.
+ bool havePendingSyncTarget = false;
+ size_t activeConnectionCount = mActiveConnections.size();
+ for (size_t i = 0; i < activeConnectionCount; i++) {
+ Connection* connection = mActiveConnections.itemAt(i);
+
+ if (connection->hasPendingSyncTarget()) {
+ if (isAppSwitchDue) {
+ connection->preemptSyncTarget();
+ } else {
+ havePendingSyncTarget = true;
}
}
- size_t timedOutConnectionCount = mTimedOutConnections.size();
- for (size_t i = 0; i < timedOutConnectionCount; i++) {
- Connection* connection = mTimedOutConnections.itemAt(i);
- timeoutDispatchCycleLocked(currentTime, connection);
- skipPoll = true;
- }
- mTimedOutConnections.clear();
-
- // If we don't have a pending sync target, then we can begin delivering a new event.
- // (Otherwise we wait for dispatch to complete for that target.)
- if (! hasPendingSyncTarget) {
- if (mInboundQueue.isEmpty()) {
- if (mKeyRepeatState.lastKeyEntry) {
- if (currentTime >= mKeyRepeatState.nextRepeatTime) {
- processKeyRepeatLockedInterruptible(currentTime, keyRepeatDelay);
- skipPoll = true;
- } else {
- if (mKeyRepeatState.nextRepeatTime < nextWakeupTime) {
- nextWakeupTime = mKeyRepeatState.nextRepeatTime;
- }
+ nsecs_t connectionTimeoutTime = connection->nextTimeoutTime;
+ if (connectionTimeoutTime <= currentTime) {
+ mTimedOutConnections.add(connection);
+ } else if (connectionTimeoutTime < *nextWakeupTime) {
+ *nextWakeupTime = connectionTimeoutTime;
+ }
+ }
+
+ size_t timedOutConnectionCount = mTimedOutConnections.size();
+ for (size_t i = 0; i < timedOutConnectionCount; i++) {
+ Connection* connection = mTimedOutConnections.itemAt(i);
+ timeoutDispatchCycleLocked(currentTime, connection);
+ *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
+ }
+ mTimedOutConnections.clear();
+
+ // If we have a pending synchronous target, skip dispatch.
+ if (havePendingSyncTarget) {
+ return;
+ }
+
+ // 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, keyRepeatDelay);
+ } else {
+ if (mKeyRepeatState.nextRepeatTime < *nextWakeupTime) {
+ *nextWakeupTime = mKeyRepeatState.nextRepeatTime;
}
}
- } else {
- // Inbound queue has at least one entry.
- EventEntry* entry = mInboundQueue.head.next;
-
- // Consider throttling the entry if it is a move event and there are no
- // other events behind it in the queue. Due to movement batching, additional
- // samples may be appended to this event by the time the throttling timeout
- // expires.
- // TODO Make this smarter and consider throttling per device independently.
- if (entry->type == EventEntry::TYPE_MOTION) {
- MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
- int32_t deviceId = motionEntry->deviceId;
- uint32_t source = motionEntry->source;
- if (motionEntry->next == & mInboundQueue.tail
- && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
- && deviceId == mThrottleState.lastDeviceId
- && source == mThrottleState.lastSource) {
- nsecs_t nextTime = mThrottleState.lastEventTime
- + mThrottleState.minTimeBetweenEvents;
- if (currentTime < nextTime) {
- // Throttle it!
+ }
+ if (! mPendingEvent) {
+ return;
+ }
+ } else {
+ // Inbound queue has at least one entry.
+ EventEntry* entry = mInboundQueue.headSentinel.next;
+
+ // Throttle the entry if it is a move event and there are no
+ // other events behind it in the queue. Due to movement batching, additional
+ // samples may be appended to this event by the time the throttling timeout
+ // expires.
+ // TODO Make this smarter and consider throttling per device independently.
+ if (entry->type == EventEntry::TYPE_MOTION) {
+ MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
+ int32_t deviceId = motionEntry->deviceId;
+ uint32_t source = motionEntry->source;
+ if (! isAppSwitchDue
+ && motionEntry->next == & mInboundQueue.tailSentinel // exactly one event
+ && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
+ && deviceId == mThrottleState.lastDeviceId
+ && source == mThrottleState.lastSource) {
+ nsecs_t nextTime = mThrottleState.lastEventTime
+ + mThrottleState.minTimeBetweenEvents;
+ if (currentTime < nextTime) {
+ // Throttle it!
#if DEBUG_THROTTLING
- LOGD("Throttling - Delaying motion event for "
- "device 0x%x, source 0x%08x by up to %0.3fms.",
- deviceId, source, (nextTime - currentTime) * 0.000001);
+ LOGD("Throttling - Delaying motion event for "
+ "device 0x%x, source 0x%08x by up to %0.3fms.",
+ deviceId, source, (nextTime - currentTime) * 0.000001);
#endif
- if (nextTime < nextWakeupTime) {
- nextWakeupTime = nextTime;
- }
- if (mThrottleState.originalSampleCount == 0) {
- mThrottleState.originalSampleCount =
- motionEntry->countSamples();
- }
- goto Throttle;
+ if (nextTime < *nextWakeupTime) {
+ *nextWakeupTime = nextTime;
+ }
+ if (mThrottleState.originalSampleCount == 0) {
+ mThrottleState.originalSampleCount =
+ motionEntry->countSamples();
}
+ return;
}
+ }
#if DEBUG_THROTTLING
- if (mThrottleState.originalSampleCount != 0) {
- uint32_t count = motionEntry->countSamples();
- LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
- count - mThrottleState.originalSampleCount,
- mThrottleState.originalSampleCount, count);
- mThrottleState.originalSampleCount = 0;
- }
+ if (mThrottleState.originalSampleCount != 0) {
+ uint32_t count = motionEntry->countSamples();
+ LOGD("Throttling - Motion event sample count grew by %d from %d to %d.",
+ count - mThrottleState.originalSampleCount,
+ mThrottleState.originalSampleCount, count);
+ mThrottleState.originalSampleCount = 0;
+ }
#endif
- mThrottleState.lastEventTime = entry->eventTime < currentTime
- ? entry->eventTime : currentTime;
- mThrottleState.lastDeviceId = deviceId;
- mThrottleState.lastSource = source;
- }
+ mThrottleState.lastEventTime = entry->eventTime < currentTime
+ ? entry->eventTime : currentTime;
+ mThrottleState.lastDeviceId = deviceId;
+ mThrottleState.lastSource = source;
+ }
- // Start processing the entry but leave it on the queue until later so that the
- // input reader can keep appending samples onto a motion event between the
- // time we started processing it and the time we finally enqueue dispatch
- // entries for it.
- switch (entry->type) {
- case EventEntry::TYPE_CONFIGURATION_CHANGED: {
- ConfigurationChangedEntry* typedEntry =
- static_cast<ConfigurationChangedEntry*>(entry);
- processConfigurationChangedLockedInterruptible(currentTime, typedEntry);
- break;
- }
+ mInboundQueue.dequeue(entry);
+ mPendingEvent = entry;
+ }
+ }
- case EventEntry::TYPE_KEY: {
- KeyEntry* typedEntry = static_cast<KeyEntry*>(entry);
- processKeyLockedInterruptible(currentTime, typedEntry, keyRepeatTimeout);
- break;
- }
+ // Now we have an event to dispatch.
+ assert(mPendingEvent != NULL);
+ bool wasDispatched = false;
+ bool wasDropped = false;
+ switch (mPendingEvent->type) {
+ case EventEntry::TYPE_CONFIGURATION_CHANGED: {
+ ConfigurationChangedEntry* typedEntry =
+ static_cast<ConfigurationChangedEntry*>(mPendingEvent);
+ wasDispatched = dispatchConfigurationChangedLocked(currentTime, typedEntry);
+ break;
+ }
- case EventEntry::TYPE_MOTION: {
- MotionEntry* typedEntry = static_cast<MotionEntry*>(entry);
- processMotionLockedInterruptible(currentTime, typedEntry);
- break;
- }
+ case EventEntry::TYPE_KEY: {
+ KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
+ if (isAppSwitchPendingLocked()) {
+ if (isAppSwitchKey(typedEntry->keyCode)) {
+ resetPendingAppSwitchLocked(true);
+ } else if (isAppSwitchDue) {
+ LOGI("Dropping key because of pending overdue app switch.");
+ wasDropped = true;
+ break;
+ }
+ }
+ wasDispatched = dispatchKeyLocked(currentTime, typedEntry, keyRepeatTimeout,
+ nextWakeupTime);
+ break;
+ }
- default:
- assert(false);
- break;
- }
+ case EventEntry::TYPE_MOTION: {
+ MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
+ if (isAppSwitchDue) {
+ LOGI("Dropping motion because of pending overdue app switch.");
+ wasDropped = true;
+ break;
+ }
+ wasDispatched = dispatchMotionLocked(currentTime, typedEntry, nextWakeupTime);
+ break;
+ }
- // Dequeue and release the event entry that we just processed.
- mInboundQueue.dequeue(entry);
- mAllocator.releaseEventEntry(entry);
- skipPoll = true;
+ default:
+ assert(false);
+ wasDropped = true;
+ break;
+ }
- Throttle: ;
- }
- }
+ if (wasDispatched || wasDropped) {
+ releasePendingEventLocked(wasDropped);
+ *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
+ }
+}
- // Run any deferred commands.
- skipPoll |= runCommandsLockedInterruptible();
- } // release lock
+bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
+ bool needWake = mInboundQueue.isEmpty();
+ mInboundQueue.enqueueAtTail(entry);
- // If we dispatched anything, don't poll just now. Wait for the next iteration.
- // Contents may have shifted during flight.
- if (skipPoll) {
- return;
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY:
+ needWake |= detectPendingAppSwitchLocked(static_cast<KeyEntry*>(entry));
+ break;
}
- // Wait for callback or timeout or wake. (make sure we round up, not down)
- nsecs_t timeout = (nextWakeupTime - currentTime + 999999LL) / 1000000LL;
- int32_t timeoutMillis = timeout > INT_MAX ? -1 : timeout > 0 ? int32_t(timeout) : 0;
- mPollLoop->pollOnce(timeoutMillis);
+ return needWake;
+}
+
+bool InputDispatcher::isAppSwitchKey(int32_t keyCode) {
+ return keyCode == AKEYCODE_HOME || keyCode == AKEYCODE_ENDCALL;
+}
+
+bool InputDispatcher::isAppSwitchPendingLocked() {
+ return mAppSwitchDueTime != LONG_LONG_MAX;
+}
+
+bool InputDispatcher::detectPendingAppSwitchLocked(KeyEntry* inboundKeyEntry) {
+ if (inboundKeyEntry->action == AKEY_EVENT_ACTION_UP
+ && ! (inboundKeyEntry->flags & AKEY_EVENT_FLAG_CANCELED)
+ && isAppSwitchKey(inboundKeyEntry->keyCode)
+ && isEventFromReliableSourceLocked(inboundKeyEntry)) {
+#if DEBUG_APP_SWITCH
+ LOGD("App switch is pending!");
+#endif
+ mAppSwitchDueTime = inboundKeyEntry->eventTime + APP_SWITCH_TIMEOUT;
+ return true; // need wake
+ }
+ return false;
+}
+
+void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
+ mAppSwitchDueTime = LONG_LONG_MAX;
+
+#if DEBUG_APP_SWITCH
+ if (handled) {
+ LOGD("App switch has arrived.");
+ } else {
+ LOGD("App switch was abandoned.");
+ }
+#endif
}
bool InputDispatcher::runCommandsLockedInterruptible() {
@@ -285,78 +436,52 @@ InputDispatcher::CommandEntry* InputDispatcher::postCommandLocked(Command comman
return commandEntry;
}
-void InputDispatcher::processConfigurationChangedLockedInterruptible(
- nsecs_t currentTime, ConfigurationChangedEntry* entry) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
- LOGD("processConfigurationChanged - eventTime=%lld", entry->eventTime);
-#endif
-
- // Reset key repeating in case a keyboard device was added or removed or something.
- resetKeyRepeatLocked();
-
- mLock.unlock();
-
- mPolicy->notifyConfigurationChanged(entry->eventTime);
+void InputDispatcher::drainInboundQueueLocked() {
+ while (! mInboundQueue.isEmpty()) {
+ EventEntry* entry = mInboundQueue.dequeueAtHead();
+ releaseInboundEventLocked(entry, true /*wasDropped*/);
+ }
+}
- mLock.lock();
+void InputDispatcher::releasePendingEventLocked(bool wasDropped) {
+ if (mPendingEvent) {
+ releaseInboundEventLocked(mPendingEvent, wasDropped);
+ mPendingEvent = NULL;
+ }
}
-void InputDispatcher::processKeyLockedInterruptible(
- nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout) {
-#if DEBUG_OUTBOUND_EVENT_DETAILS
- LOGD("processKey - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, action=0x%x, "
- "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%lld",
- entry->eventTime, entry->deviceId, entry->source, entry->policyFlags, entry->action,
- entry->flags, entry->keyCode, entry->scanCode, entry->metaState,
- entry->downTime);
+void InputDispatcher::releaseInboundEventLocked(EventEntry* entry, bool wasDropped) {
+ if (wasDropped) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("Pending event was dropped.");
#endif
-
- if (entry->action == AKEY_EVENT_ACTION_DOWN && ! entry->isInjected()) {
- 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 + keyRepeatTimeout;
- }
- mKeyRepeatState.lastKeyEntry = entry;
- entry->refCount += 1;
- } else {
- resetKeyRepeatLocked();
+ setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
}
+ mAllocator.releaseEventEntry(entry);
+}
- identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry);
+bool InputDispatcher::isEventFromReliableSourceLocked(EventEntry* entry) {
+ return ! entry->isInjected()
+ || entry->injectorUid == 0
+ || mPolicy->checkInjectEventsPermissionNonReentrant(
+ entry->injectorPid, entry->injectorUid);
}
-void InputDispatcher::processKeyRepeatLockedInterruptible(
+void InputDispatcher::resetKeyRepeatLocked() {
+ if (mKeyRepeatState.lastKeyEntry) {
+ mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
+ mKeyRepeatState.lastKeyEntry = NULL;
+ }
+}
+
+InputDispatcher::KeyEntry* InputDispatcher::synthesizeKeyRepeatLocked(
nsecs_t currentTime, nsecs_t keyRepeatDelay) {
KeyEntry* entry = mKeyRepeatState.lastKeyEntry;
- // Search the inbound queue for a key up corresponding to this device.
- // It doesn't make sense to generate a key repeat event if the key is already up.
- for (EventEntry* queuedEntry = mInboundQueue.head.next;
- queuedEntry != & mInboundQueue.tail; queuedEntry = entry->next) {
- if (queuedEntry->type == EventEntry::TYPE_KEY) {
- KeyEntry* queuedKeyEntry = static_cast<KeyEntry*>(queuedEntry);
- if (queuedKeyEntry->deviceId == entry->deviceId
- && entry->action == AKEY_EVENT_ACTION_UP) {
- resetKeyRepeatLocked();
- return;
- }
- }
- }
-
- // Synthesize a key repeat.
// Reuse the repeated key entry if it is otherwise unreferenced.
uint32_t policyFlags = entry->policyFlags & POLICY_FLAG_RAW_MASK;
if (entry->refCount == 1) {
+ entry->recycle();
entry->eventTime = currentTime;
entry->policyFlags = policyFlags;
entry->repeatCount += 1;
@@ -371,31 +496,194 @@ void InputDispatcher::processKeyRepeatLockedInterruptible(
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;
if (entry->repeatCount == 1) {
entry->flags |= AKEY_EVENT_FLAG_LONG_PRESS;
}
mKeyRepeatState.nextRepeatTime = currentTime + keyRepeatDelay;
+ return entry;
+}
+
+bool InputDispatcher::dispatchConfigurationChangedLocked(
+ nsecs_t currentTime, ConfigurationChangedEntry* entry) {
+#if DEBUG_OUTBOUND_EVENT_DETAILS
+ LOGD("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::dispatchKeyLocked(
+ nsecs_t currentTime, KeyEntry* entry, nsecs_t keyRepeatTimeout,
+ nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ logOutboundKeyDetailsLocked("dispatchKey - ", entry);
+
+ if (entry->repeatCount == 0
+ && entry->action == AKEY_EVENT_ACTION_DOWN
+ && ! entry->isInjected()) {
+ 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 + keyRepeatTimeout;
+ }
+ mKeyRepeatState.lastKeyEntry = entry;
+ entry->refCount += 1;
+ } else if (! entry->syntheticRepeat) {
+ resetKeyRepeatLocked();
+ }
+
+ entry->dispatchInProgress = true;
+ startFindingTargetsLocked();
+ }
+
+ // Identify targets.
+ if (! mCurrentInputTargetsValid) {
+ InputWindow* window = NULL;
+ int32_t injectionResult = findFocusedWindowLocked(currentTime,
+ entry, nextWakeupTime, & window);
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
+
+ addMonitoringTargetsLocked();
+ finishFindingTargetsLocked(window);
+ }
+
+ // Give the policy a chance to intercept the key.
+ if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible);
+ commandEntry->inputChannel = mCurrentInputChannel;
+ commandEntry->keyEntry = entry;
+ entry->refCount += 1;
+ return false; // wait for the command to run
+ }
+ if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+ return true;
+ }
+
+ // Dispatch the key.
+ dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+
+ // Poke user activity.
+ pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, POWER_MANAGER_BUTTON_EVENT);
+ return true;
+}
+
+void InputDispatcher::logOutboundKeyDetailsLocked(const char* prefix, const KeyEntry* entry) {
#if DEBUG_OUTBOUND_EVENT_DETAILS
- LOGD("processKeyRepeat - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
+ LOGD("%seventTime=%lld, deviceId=0x%x, 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",
+ "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);
+ entry->downTime);
#endif
+}
+
+bool InputDispatcher::dispatchMotionLocked(
+ nsecs_t currentTime, MotionEntry* entry, nsecs_t* nextWakeupTime) {
+ // Preprocessing.
+ if (! entry->dispatchInProgress) {
+ logOutboundMotionDetailsLocked("dispatchMotion - ", entry);
+
+ entry->dispatchInProgress = true;
+ startFindingTargetsLocked();
+ }
+
+ bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
+
+ // Identify targets.
+ if (! mCurrentInputTargetsValid) {
+ InputWindow* window = NULL;
+ int32_t injectionResult;
+ if (isPointerEvent) {
+ // Pointer event. (eg. touchscreen)
+ injectionResult = findTouchedWindowLocked(currentTime,
+ entry, nextWakeupTime, & window);
+ } else {
+ // Non touch event. (eg. trackball)
+ injectionResult = findFocusedWindowLocked(currentTime,
+ entry, nextWakeupTime, & window);
+ }
+ if (injectionResult == INPUT_EVENT_INJECTION_PENDING) {
+ return false;
+ }
+
+ setInjectionResultLocked(entry, injectionResult);
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ return true;
+ }
- identifyInputTargetsAndDispatchKeyLockedInterruptible(currentTime, entry);
+ addMonitoringTargetsLocked();
+ finishFindingTargetsLocked(window);
+ }
+
+ // Dispatch the motion.
+ dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+
+ // Poke user activity.
+ int32_t eventType;
+ if (isPointerEvent) {
+ switch (entry->action) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ eventType = POWER_MANAGER_TOUCH_EVENT;
+ break;
+ case AMOTION_EVENT_ACTION_UP:
+ eventType = POWER_MANAGER_TOUCH_UP_EVENT;
+ break;
+ default:
+ if (entry->eventTime - entry->downTime >= EVENT_IGNORE_DURATION) {
+ eventType = POWER_MANAGER_TOUCH_EVENT;
+ } else {
+ eventType = POWER_MANAGER_LONG_TOUCH_EVENT;
+ }
+ break;
+ }
+ } else {
+ eventType = POWER_MANAGER_BUTTON_EVENT;
+ }
+ pokeUserActivityLocked(entry->eventTime, mCurrentInputWindowType, eventType);
+ return true;
}
-void InputDispatcher::processMotionLockedInterruptible(
- nsecs_t currentTime, MotionEntry* entry) {
+
+void InputDispatcher::logOutboundMotionDetailsLocked(const char* prefix, const MotionEntry* entry) {
#if DEBUG_OUTBOUND_EVENT_DETAILS
- LOGD("processMotion - eventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
+ LOGD("%seventTime=%lld, deviceId=0x%x, source=0x%x, policyFlags=0x%x, "
"action=0x%x, flags=0x%x, "
"metaState=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->edgeFlags, entry->xPrecision, entry->yPrecision,
@@ -403,7 +691,7 @@ void InputDispatcher::processMotionLockedInterruptible(
// Print the most recent sample that we have available, this may change due to batching.
size_t sampleCount = 1;
- MotionSample* sample = & entry->firstSample;
+ const MotionSample* sample = & entry->firstSample;
for (; sample->next != NULL; sample = sample->next) {
sampleCount += 1;
}
@@ -425,94 +713,541 @@ void InputDispatcher::processMotionLockedInterruptible(
LOGD(" ... Total movement samples currently batched %d ...", sampleCount);
}
#endif
-
- identifyInputTargetsAndDispatchMotionLockedInterruptible(currentTime, entry);
}
-void InputDispatcher::identifyInputTargetsAndDispatchKeyLockedInterruptible(
- nsecs_t currentTime, KeyEntry* entry) {
+void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
+ EventEntry* eventEntry, bool resumeWithAppendedMotionSample) {
#if DEBUG_DISPATCH_CYCLE
- LOGD("identifyInputTargetsAndDispatchKey");
+ LOGD("dispatchEventToCurrentInputTargets - "
+ "resumeWithAppendedMotionSample=%s",
+ toString(resumeWithAppendedMotionSample));
#endif
- entry->dispatchInProgress = true;
- mCurrentInputTargetsValid = false;
- mLock.unlock();
+ assert(eventEntry->dispatchInProgress); // should already have been set to true
- mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
- entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
- entry->downTime, entry->eventTime);
+ for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
+ const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
+ ssize_t connectionIndex = getConnectionIndex(inputTarget.inputChannel);
+ if (connectionIndex >= 0) {
+ sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
+ prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
+ resumeWithAppendedMotionSample);
+ } else {
+ LOGW("Framework requested delivery of an input event to channel '%s' but it "
+ "is not registered with the input dispatcher.",
+ inputTarget.inputChannel->getName().string());
+ }
+ }
+}
+
+void InputDispatcher::startFindingTargetsLocked() {
+ mCurrentInputTargetsValid = false;
mCurrentInputTargets.clear();
- int32_t injectionResult = mPolicy->waitForKeyEventTargets(& mReusableKeyEvent,
- entry->policyFlags, entry->injectorPid, entry->injectorUid,
- mCurrentInputTargets);
+ mCurrentInputChannel.clear();
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+}
- mLock.lock();
+void InputDispatcher::finishFindingTargetsLocked(const InputWindow* window) {
+ mCurrentInputWindowType = window->layoutParamsType;
+ mCurrentInputChannel = window->inputChannel;
mCurrentInputTargetsValid = true;
+}
+
+int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
+ const EventEntry* entry, const InputApplication* application, const InputWindow* window,
+ nsecs_t* nextWakeupTime) {
+ if (application == NULL && window == NULL) {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY) {
+#if DEBUG_FOCUS
+ LOGD("Waiting for system to become ready for input.");
+#endif
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_SYSTEM_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = LONG_LONG_MAX;
+ mInputTargetWaitTimeoutExpired = false;
+ }
+ } else {
+ if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+#if DEBUG_FOCUS
+ LOGD("Waiting for application to become ready for input: name=%s, window=%s",
+ application ? application->name.string() : "<unknown>",
+ window ? window->inputChannel->getName().string() : "<unknown>");
+#endif
+ nsecs_t timeout = window ? window->dispatchingTimeout :
+ application ? application->dispatchingTimeout : DEFAULT_INPUT_DISPATCHING_TIMEOUT;
+
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
+ mInputTargetWaitStartTime = currentTime;
+ mInputTargetWaitTimeoutTime = currentTime + timeout;
+ mInputTargetWaitTimeoutExpired = false;
+ }
+ }
+
+ if (mInputTargetWaitTimeoutExpired) {
+ return INPUT_EVENT_INJECTION_TIMED_OUT;
+ }
+
+ if (currentTime >= mInputTargetWaitTimeoutTime) {
+ LOGI("Application is not ready for input: name=%s, window=%s,"
+ "%01.1fms since event, %01.1fms since wait started",
+ application ? application->name.string() : "<unknown>",
+ window ? window->inputChannel->getName().string() : "<unknown>",
+ (currentTime - entry->eventTime) / 1000000.0,
+ (currentTime - mInputTargetWaitStartTime) / 1000000.0);
+
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doTargetsNotReadyTimeoutLockedInterruptible);
+ if (application) {
+ commandEntry->inputApplicationHandle = application->handle;
+ }
+ if (window) {
+ commandEntry->inputChannel = window->inputChannel;
+ }
+
+ // 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;
+ }
+}
- setInjectionResultLocked(entry, injectionResult);
+void InputDispatcher::resumeAfterTargetsNotReadyTimeoutLocked(nsecs_t newTimeout) {
+ if (newTimeout > 0) {
+ // Extend the timeout.
+ mInputTargetWaitTimeoutTime = now() + newTimeout;
+ } else {
+ // Give up.
+ mInputTargetWaitTimeoutExpired = true;
+ }
+}
- if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
- dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+nsecs_t InputDispatcher::getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(
+ nsecs_t currentTime) {
+ if (mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
+ return currentTime - mInputTargetWaitStartTime;
}
+ return 0;
}
-void InputDispatcher::identifyInputTargetsAndDispatchMotionLockedInterruptible(
- nsecs_t currentTime, MotionEntry* entry) {
-#if DEBUG_DISPATCH_CYCLE
- LOGD("identifyInputTargetsAndDispatchMotion");
+void InputDispatcher::resetANRTimeoutsLocked() {
+#if DEBUG_FOCUS
+ LOGD("Resetting ANR timeouts.");
#endif
- entry->dispatchInProgress = true;
- mCurrentInputTargetsValid = false;
- mLock.unlock();
+ // Reset timeouts for all active connections.
+ nsecs_t currentTime = now();
+ for (size_t i = 0; i < mActiveConnections.size(); i++) {
+ Connection* connection = mActiveConnections[i];
+ connection->resetTimeout(currentTime);
+ }
- mReusableMotionEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
- entry->edgeFlags, entry->metaState,
- 0, 0, entry->xPrecision, entry->yPrecision,
- entry->downTime, entry->eventTime, entry->pointerCount, entry->pointerIds,
- entry->firstSample.pointerCoords);
+ // Reset input target wait timeout.
+ mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_NONE;
+}
+int32_t InputDispatcher::findFocusedWindowLocked(nsecs_t currentTime, const EventEntry* entry,
+ nsecs_t* nextWakeupTime, InputWindow** outWindow) {
+ *outWindow = NULL;
mCurrentInputTargets.clear();
- int32_t injectionResult = mPolicy->waitForMotionEventTargets(& mReusableMotionEvent,
- entry->policyFlags, entry->injectorPid, entry->injectorUid,
- mCurrentInputTargets);
- mLock.lock();
- mCurrentInputTargetsValid = true;
+ int32_t injectionResult;
- setInjectionResultLocked(entry, injectionResult);
+ // If there is no currently focused window and no focused application
+ // then drop the event.
+ if (! mFocusedWindow) {
+ if (mFocusedApplication) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because there is no focused window but there is a "
+ "focused application that may eventually add a window: '%s'.",
+ mFocusedApplication->name.string());
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplication, NULL, nextWakeupTime);
+ goto Unresponsive;
+ }
- if (injectionResult == INPUT_EVENT_INJECTION_SUCCEEDED) {
- dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
+ LOGI("Dropping event because there is no focused window or focused application.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
}
+
+ // Check permissions.
+ if (! checkInjectionPermission(mFocusedWindow, entry->injectorPid, entry->injectorUid)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+
+ // If the currently focused window is paused then keep waiting.
+ if (mFocusedWindow->paused) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because focused window is paused.");
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplication, mFocusedWindow, nextWakeupTime);
+ goto Unresponsive;
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ *outWindow = mFocusedWindow;
+ addWindowTargetLocked(mFocusedWindow, InputTarget::FLAG_SYNC,
+ getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(currentTime));
+
+ // Done.
+Failed:
+Unresponsive:
+#if DEBUG_FOCUS
+ LOGD("findFocusedWindow finished: injectionResult=%d",
+ injectionResult);
+ logDispatchStateLocked();
+#endif
+ return injectionResult;
}
-void InputDispatcher::dispatchEventToCurrentInputTargetsLocked(nsecs_t currentTime,
- EventEntry* eventEntry, bool resumeWithAppendedMotionSample) {
-#if DEBUG_DISPATCH_CYCLE
- LOGD("dispatchEventToCurrentInputTargets - "
- "resumeWithAppendedMotionSample=%s",
- resumeWithAppendedMotionSample ? "true" : "false");
+int32_t InputDispatcher::findTouchedWindowLocked(nsecs_t currentTime, const MotionEntry* entry,
+ nsecs_t* nextWakeupTime, InputWindow** outWindow) {
+ enum InjectionPermission {
+ INJECTION_PERMISSION_UNKNOWN,
+ INJECTION_PERMISSION_GRANTED,
+ INJECTION_PERMISSION_DENIED
+ };
+
+ *outWindow = NULL;
+ mCurrentInputTargets.clear();
+
+ nsecs_t startTime = now();
+
+ // For security reasons, we defer updating the touch state until we are sure that
+ // event injection will be allowed.
+ //
+ // FIXME In the original code, screenWasOff could never be set to true.
+ // The reason is that the POLICY_FLAG_WOKE_HERE
+ // and POLICY_FLAG_BRIGHT_HERE flags were set only when preprocessing raw
+ // EV_KEY, EV_REL and EV_ABS events. As it happens, the touch event was
+ // actually enqueued using the policyFlags that appeared in the final EV_SYN
+ // events upon which no preprocessing took place. So policyFlags was always 0.
+ // In the new native input dispatcher we're a bit more careful about event
+ // preprocessing so the touches we receive can actually have non-zero policyFlags.
+ // Unfortunately we obtain undesirable behavior.
+ //
+ // Here's what happens:
+ //
+ // When the device dims in anticipation of going to sleep, touches
+ // in windows which have FLAG_TOUCHABLE_WHEN_WAKING cause
+ // the device to brighten and reset the user activity timer.
+ // Touches on other windows (such as the launcher window)
+ // are dropped. Then after a moment, the device goes to sleep. Oops.
+ //
+ // Also notice how screenWasOff was being initialized using POLICY_FLAG_BRIGHT_HERE
+ // instead of POLICY_FLAG_WOKE_HERE...
+ //
+ bool screenWasOff = false; // original policy: policyFlags & POLICY_FLAG_BRIGHT_HERE;
+
+ int32_t action = entry->action;
+
+ // Update the touch state as needed based on the properties of the touch event.
+ int32_t injectionResult;
+ InjectionPermission injectionPermission;
+ if (action == AMOTION_EVENT_ACTION_DOWN) {
+ /* Case 1: ACTION_DOWN */
+
+ InputWindow* newTouchedWindow = NULL;
+ mTempTouchedOutsideTargets.clear();
+
+ int32_t x = int32_t(entry->firstSample.pointerCoords[0].x);
+ int32_t y = int32_t(entry->firstSample.pointerCoords[0].y);
+ InputWindow* topErrorWindow = NULL;
+ bool obscured = false;
+
+ // Traverse windows from front to back to find touched window and outside targets.
+ size_t numWindows = mWindows.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ InputWindow* window = & mWindows.editItemAt(i);
+ int32_t flags = window->layoutParamsFlags;
+
+ if (flags & InputWindow::FLAG_SYSTEM_ERROR) {
+ if (! topErrorWindow) {
+ topErrorWindow = window;
+ }
+ }
+
+ if (window->visible) {
+ if (! (flags & InputWindow::FLAG_NOT_TOUCHABLE)) {
+ bool isTouchModal = (flags & (InputWindow::FLAG_NOT_FOCUSABLE
+ | InputWindow::FLAG_NOT_TOUCH_MODAL)) == 0;
+ if (isTouchModal || window->touchableAreaContainsPoint(x, y)) {
+ if (! screenWasOff || flags & InputWindow::FLAG_TOUCHABLE_WHEN_WAKING) {
+ newTouchedWindow = window;
+ obscured = isWindowObscuredLocked(window);
+ }
+ break; // found touched window, exit window loop
+ }
+ }
+
+ if (flags & InputWindow::FLAG_WATCH_OUTSIDE_TOUCH) {
+ OutsideTarget outsideTarget;
+ outsideTarget.window = window;
+ outsideTarget.obscured = isWindowObscuredLocked(window);
+ mTempTouchedOutsideTargets.push(outsideTarget);
+ }
+ }
+ }
+
+ // 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 (topErrorWindow && newTouchedWindow != topErrorWindow) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because system error window is pending.");
#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, NULL, nextWakeupTime);
+ injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ goto Unresponsive;
+ }
- assert(eventEntry->dispatchInProgress); // should already have been set to true
+ // If we did not find a touched window then fail.
+ if (! newTouchedWindow) {
+ if (mFocusedApplication) {
+#if DEBUG_FOCUS
+ LOGD("Waiting because there is no touched window but there is a "
+ "focused application that may eventually add a new window: '%s'.",
+ mFocusedApplication->name.string());
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ mFocusedApplication, NULL, nextWakeupTime);
+ injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ goto Unresponsive;
+ }
- for (size_t i = 0; i < mCurrentInputTargets.size(); i++) {
- const InputTarget& inputTarget = mCurrentInputTargets.itemAt(i);
+ LOGI("Dropping event because there is no touched window or focused application.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ injectionPermission = INJECTION_PERMISSION_UNKNOWN;
+ goto Failed;
+ }
- ssize_t connectionIndex = getConnectionIndex(inputTarget.inputChannel);
- if (connectionIndex >= 0) {
- sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex);
- prepareDispatchCycleLocked(currentTime, connection, eventEntry, & inputTarget,
- resumeWithAppendedMotionSample);
+ // Check permissions.
+ if (! checkInjectionPermission(newTouchedWindow, entry->injectorPid, entry->injectorUid)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+
+ // If the touched window is paused then keep waiting.
+ if (newTouchedWindow->paused) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("Waiting because touched window is paused.");
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, newTouchedWindow, nextWakeupTime);
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ goto Unresponsive;
+ }
+
+ // Success! Update the touch dispatch state for real.
+ releaseTouchedWindowLocked();
+
+ mTouchedWindow = newTouchedWindow;
+ mTouchedWindowIsObscured = obscured;
+
+ if (newTouchedWindow->hasWallpaper) {
+ mTouchedWallpaperWindows.appendVector(mWallpaperWindows);
+ }
+ } else {
+ /* Case 2: Everything but ACTION_DOWN */
+
+ // Check permissions.
+ if (! checkInjectionPermission(mTouchedWindow, entry->injectorPid, entry->injectorUid)) {
+ injectionResult = INPUT_EVENT_INJECTION_PERMISSION_DENIED;
+ injectionPermission = INJECTION_PERMISSION_DENIED;
+ goto Failed;
+ }
+
+ // If the pointer is not currently down, then ignore the event.
+ if (! mTouchDown) {
+ LOGI("Dropping event because the pointer is not down.");
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ goto Failed;
+ }
+
+ // If there is no currently touched window then fail.
+ if (! mTouchedWindow) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("Dropping event because there is no touched window to receive it.");
+#endif
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ goto Failed;
+ }
+
+ // If the touched window is paused then keep waiting.
+ if (mTouchedWindow->paused) {
+#if DEBUG_INPUT_DISPATCHER_POLICY
+ LOGD("Waiting because touched window is paused.");
+#endif
+ injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
+ NULL, mTouchedWindow, nextWakeupTime);
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+ goto Unresponsive;
+ }
+ }
+
+ // Success! Output targets.
+ injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
+
+ {
+ size_t numWallpaperWindows = mTouchedWallpaperWindows.size();
+ for (size_t i = 0; i < numWallpaperWindows; i++) {
+ addWindowTargetLocked(mTouchedWallpaperWindows[i],
+ InputTarget::FLAG_WINDOW_IS_OBSCURED, 0);
+ }
+
+ size_t numOutsideTargets = mTempTouchedOutsideTargets.size();
+ for (size_t i = 0; i < numOutsideTargets; i++) {
+ const OutsideTarget& outsideTarget = mTempTouchedOutsideTargets[i];
+ int32_t outsideTargetFlags = InputTarget::FLAG_OUTSIDE;
+ if (outsideTarget.obscured) {
+ outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+ addWindowTargetLocked(outsideTarget.window, outsideTargetFlags, 0);
+ }
+ mTempTouchedOutsideTargets.clear();
+
+ int32_t targetFlags = InputTarget::FLAG_SYNC;
+ if (mTouchedWindowIsObscured) {
+ targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ }
+ addWindowTargetLocked(mTouchedWindow, targetFlags,
+ getTimeSpentWaitingForApplicationWhileFindingTargetsLocked(currentTime));
+ *outWindow = mTouchedWindow;
+ }
+
+Failed:
+ // Check injection permission once and for all.
+ if (injectionPermission == INJECTION_PERMISSION_UNKNOWN) {
+ if (checkInjectionPermission(action == AMOTION_EVENT_ACTION_DOWN ? NULL : mTouchedWindow,
+ entry->injectorPid, entry->injectorUid)) {
+ injectionPermission = INJECTION_PERMISSION_GRANTED;
} else {
- LOGW("Framework requested delivery of an input event to channel '%s' but it "
- "is not registered with the input dispatcher.",
- inputTarget.inputChannel->getName().string());
+ injectionPermission = INJECTION_PERMISSION_DENIED;
}
}
+
+ // Update final pieces of touch state if the injector had permission.
+ if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
+ if (action == AMOTION_EVENT_ACTION_DOWN) {
+ if (mTouchDown) {
+ // This is weird. We got a down but we thought it was already down!
+ LOGW("Pointer down received while already down.");
+ } else {
+ mTouchDown = true;
+ }
+
+ if (injectionResult != INPUT_EVENT_INJECTION_SUCCEEDED) {
+ // Since we failed to identify a target for this touch down, we may still
+ // be holding on to an earlier target from a previous touch down. Release it.
+ releaseTouchedWindowLocked();
+ }
+ } else if (action == AMOTION_EVENT_ACTION_UP) {
+ mTouchDown = false;
+ releaseTouchedWindowLocked();
+ }
+ } else {
+ LOGW("Not updating touch focus because injection was denied.");
+ }
+
+Unresponsive:
+#if DEBUG_FOCUS
+ LOGD("findTouchedWindow finished: injectionResult=%d, injectionPermission=%d",
+ injectionResult, injectionPermission);
+ logDispatchStateLocked();
+#endif
+ return injectionResult;
+}
+
+void InputDispatcher::releaseTouchedWindowLocked() {
+ mTouchedWindow = NULL;
+ mTouchedWindowIsObscured = false;
+ mTouchedWallpaperWindows.clear();
+}
+
+void InputDispatcher::addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
+ nsecs_t timeSpentWaitingForApplication) {
+ mCurrentInputTargets.push();
+
+ InputTarget& target = mCurrentInputTargets.editTop();
+ target.inputChannel = window->inputChannel;
+ target.flags = targetFlags;
+ target.timeout = window->dispatchingTimeout;
+ target.timeSpentWaitingForApplication = timeSpentWaitingForApplication;
+ target.xOffset = - window->frameLeft;
+ target.yOffset = - window->frameTop;
+}
+
+void InputDispatcher::addMonitoringTargetsLocked() {
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ mCurrentInputTargets.push();
+
+ InputTarget& target = mCurrentInputTargets.editTop();
+ target.inputChannel = mMonitoringChannels[i];
+ target.flags = 0;
+ target.timeout = -1;
+ target.timeSpentWaitingForApplication = 0;
+ target.xOffset = 0;
+ target.yOffset = 0;
+ }
+}
+
+bool InputDispatcher::checkInjectionPermission(const InputWindow* window,
+ int32_t injectorPid, int32_t injectorUid) {
+ if (injectorUid > 0 && (window == NULL || window->ownerUid != injectorUid)) {
+ bool result = mPolicy->checkInjectEventsPermissionNonReentrant(injectorPid, injectorUid);
+ if (! result) {
+ if (window) {
+ LOGW("Permission denied: injecting event from pid %d uid %d to window "
+ "with input channel %s owned by uid %d",
+ injectorPid, injectorUid, window->inputChannel->getName().string(),
+ window->ownerUid);
+ } else {
+ LOGW("Permission denied: injecting event from pid %d uid %d",
+ injectorPid, injectorUid);
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+bool InputDispatcher::isWindowObscuredLocked(const InputWindow* window) {
+ size_t numWindows = mWindows.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ const InputWindow* other = & mWindows.itemAt(i);
+ if (other == window) {
+ break;
+ }
+ if (other->visible && window->visibleFrameIntersects(other)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void InputDispatcher::pokeUserActivityLocked(nsecs_t eventTime,
+ int32_t windowType, int32_t eventType) {
+ CommandEntry* commandEntry = postCommandLocked(
+ & InputDispatcher::doPokeUserActivityLockedInterruptible);
+ commandEntry->eventTime = eventTime;
+ commandEntry->windowType = windowType;
+ commandEntry->userActivityEventType = eventType;
}
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
@@ -523,15 +1258,21 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
"xOffset=%f, yOffset=%f, resumeWithAppendedMotionSample=%s",
connection->getInputChannelName(), inputTarget->flags, inputTarget->timeout,
inputTarget->xOffset, inputTarget->yOffset,
- resumeWithAppendedMotionSample ? "true" : "false");
+ toString(resumeWithAppendedMotionSample));
#endif
// Skip this event if the connection status is not normal.
- // We don't want to queue outbound events at all if the connection is broken or
+ // We don't want to enqueue additional outbound events if the connection is broken or
// not responding.
if (connection->status != Connection::STATUS_NORMAL) {
- LOGV("channel '%s' ~ Dropping event because the channel status is %s",
- connection->getStatusLabel());
+ LOGW("channel '%s' ~ Dropping event because the channel status is %s",
+ connection->getInputChannelName(), connection->getStatusLabel());
+
+ // If the connection is not responding but the user is poking the application anyways,
+ // retrigger the original timeout.
+ if (connection->status == Connection::STATUS_NOT_RESPONDING) {
+ timeoutDispatchCycleLocked(currentTime, connection);
+ }
return;
}
@@ -612,17 +1353,45 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
}
}
+ // Bring the input state back in line with reality in case it drifted off during an ANR.
+ if (connection->inputState.isOutOfSync()) {
+ mTempCancelationEvents.clear();
+ connection->inputState.synthesizeCancelationEvents(& mAllocator, mTempCancelationEvents);
+ connection->inputState.resetOutOfSync();
+
+ if (! mTempCancelationEvents.isEmpty()) {
+ LOGI("channel '%s' ~ Generated %d cancelation events to bring channel back in sync "
+ "with reality.",
+ connection->getInputChannelName(), mTempCancelationEvents.size());
+
+ for (size_t i = 0; i < mTempCancelationEvents.size(); i++) {
+ EventEntry* cancelationEventEntry = mTempCancelationEvents.itemAt(i);
+ switch (cancelationEventEntry->type) {
+ case EventEntry::TYPE_KEY:
+ logOutboundKeyDetailsLocked(" ",
+ static_cast<KeyEntry*>(cancelationEventEntry));
+ break;
+ case EventEntry::TYPE_MOTION:
+ logOutboundMotionDetailsLocked(" ",
+ static_cast<MotionEntry*>(cancelationEventEntry));
+ break;
+ }
+
+ DispatchEntry* cancelationDispatchEntry =
+ mAllocator.obtainDispatchEntry(cancelationEventEntry,
+ 0, inputTarget->xOffset, inputTarget->yOffset, inputTarget->timeout);
+ connection->outboundQueue.enqueueAtTail(cancelationDispatchEntry);
+
+ mAllocator.releaseEventEntry(cancelationEventEntry);
+ }
+ }
+ }
+
// This is a new event.
// Enqueue a new dispatch entry onto the outbound queue for this connection.
- DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry); // increments ref
- dispatchEntry->targetFlags = inputTarget->flags;
- dispatchEntry->xOffset = inputTarget->xOffset;
- dispatchEntry->yOffset = inputTarget->yOffset;
- dispatchEntry->timeout = inputTarget->timeout;
- dispatchEntry->inProgress = false;
- dispatchEntry->headMotionSample = NULL;
- dispatchEntry->tailMotionSample = NULL;
-
+ DispatchEntry* dispatchEntry = mAllocator.obtainDispatchEntry(eventEntry, // increments ref
+ inputTarget->flags, inputTarget->xOffset, inputTarget->yOffset,
+ inputTarget->timeout);
if (dispatchEntry->isSyncTarget()) {
eventEntry->pendingSyncDispatches += 1;
}
@@ -647,12 +1416,13 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
// If the outbound queue was previously empty, start the dispatch cycle going.
if (wasEmpty) {
activateConnectionLocked(connection.get());
- startDispatchCycleLocked(currentTime, connection);
+ startDispatchCycleLocked(currentTime, connection,
+ inputTarget->timeSpentWaitingForApplication);
}
}
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
- const sp<Connection>& connection) {
+ const sp<Connection>& connection, nsecs_t timeSpentWaitingForApplication) {
#if DEBUG_DISPATCH_CYCLE
LOGD("channel '%s' ~ startDispatchCycle",
connection->getInputChannelName());
@@ -661,12 +1431,37 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
assert(connection->status == Connection::STATUS_NORMAL);
assert(! connection->outboundQueue.isEmpty());
- DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
+ DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
assert(! dispatchEntry->inProgress);
- // TODO throttle successive ACTION_MOVE motion events for the same device
- // possible implementation could set a brief poll timeout here and resume starting the
- // dispatch cycle when elapsed
+ // Mark the dispatch entry as in progress.
+ dispatchEntry->inProgress = true;
+
+ // Update the connection's input state.
+ InputState::Consistency consistency = connection->inputState.trackEvent(
+ dispatchEntry->eventEntry);
+
+#if FILTER_INPUT_EVENTS
+ // Filter out inconsistent sequences of input events.
+ // The input system may drop or inject events in a way that could violate implicit
+ // invariants on input state and potentially cause an application to crash
+ // or think that a key or pointer is stuck down. Technically we make no guarantees
+ // of consistency but it would be nice to improve on this where possible.
+ // XXX: This code is a proof of concept only. Not ready for prime time.
+ if (consistency == InputState::TOLERABLE) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ Sending an event that is inconsistent with the connection's "
+ "current input state but that is likely to be tolerated by the application.",
+ connection->getInputChannelName());
+#endif
+ } else if (consistency == InputState::BROKEN) {
+ LOGI("channel '%s' ~ Dropping an event that is inconsistent with the connection's "
+ "current input state and that is likely to cause the application to crash.",
+ connection->getInputChannelName());
+ startNextDispatchCycleLocked(currentTime, connection);
+ return;
+ }
+#endif
// Publish the event.
status_t status;
@@ -790,12 +1585,10 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
}
// Record information about the newly started dispatch cycle.
- dispatchEntry->inProgress = true;
-
connection->lastEventTime = dispatchEntry->eventEntry->eventTime;
connection->lastDispatchTime = currentTime;
- nsecs_t timeout = dispatchEntry->timeout;
+ nsecs_t timeout = dispatchEntry->timeout - timeSpentWaitingForApplication;
connection->setNextTimeoutTime(currentTime, timeout);
// Notify other system components.
@@ -844,9 +1637,14 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
return;
}
+ startNextDispatchCycleLocked(currentTime, connection);
+}
+
+void InputDispatcher::startNextDispatchCycleLocked(nsecs_t currentTime,
+ const sp<Connection>& connection) {
// Start the next dispatch cycle for this connection.
while (! connection->outboundQueue.isEmpty()) {
- DispatchEntry* dispatchEntry = connection->outboundQueue.head.next;
+ DispatchEntry* dispatchEntry = connection->outboundQueue.headSentinel.next;
if (dispatchEntry->inProgress) {
// Finish or resume current event in progress.
if (dispatchEntry->tailMotionSample) {
@@ -855,7 +1653,7 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
dispatchEntry->inProgress = false;
dispatchEntry->headMotionSample = dispatchEntry->tailMotionSample;
dispatchEntry->tailMotionSample = NULL;
- startDispatchCycleLocked(currentTime, connection);
+ startDispatchCycleLocked(currentTime, connection, 0);
return;
}
// Finished.
@@ -868,7 +1666,7 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
// If the head is not in progress, then we must have already dequeued the in
// progress event, which means we actually aborted it (due to ANR).
// So just start the next event for this connection.
- startDispatchCycleLocked(currentTime, connection);
+ startDispatchCycleLocked(currentTime, connection, 0);
return;
}
}
@@ -884,58 +1682,74 @@ void InputDispatcher::timeoutDispatchCycleLocked(nsecs_t currentTime,
connection->getInputChannelName());
#endif
- if (connection->status != Connection::STATUS_NORMAL) {
+ if (connection->status == Connection::STATUS_NORMAL) {
+ // Enter the not responding state.
+ connection->status = Connection::STATUS_NOT_RESPONDING;
+ connection->lastANRTime = currentTime;
+ } else if (connection->status != Connection::STATUS_NOT_RESPONDING) {
+ // Connection is broken or dead.
return;
}
- // Enter the not responding state.
- connection->status = Connection::STATUS_NOT_RESPONDING;
- connection->lastANRTime = currentTime;
-
// Notify other system components.
- // This enqueues a command which will eventually either call
- // resumeAfterTimeoutDispatchCycleLocked or abortDispatchCycleLocked.
+ // This enqueues a command which will eventually call resumeAfterTimeoutDispatchCycleLocked.
onDispatchCycleANRLocked(currentTime, connection);
}
void InputDispatcher::resumeAfterTimeoutDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection, nsecs_t newTimeout) {
#if DEBUG_DISPATCH_CYCLE
- LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked",
- connection->getInputChannelName());
+ LOGD("channel '%s' ~ resumeAfterTimeoutDispatchCycleLocked - newTimeout=%lld",
+ connection->getInputChannelName(), newTimeout);
#endif
if (connection->status != Connection::STATUS_NOT_RESPONDING) {
return;
}
- // Resume normal dispatch.
- connection->status = Connection::STATUS_NORMAL;
- connection->setNextTimeoutTime(currentTime, newTimeout);
+ if (newTimeout > 0) {
+ // The system has decided to give the application some more time.
+ // Keep waiting synchronously and resume normal dispatch.
+ connection->status = Connection::STATUS_NORMAL;
+ connection->setNextTimeoutTime(currentTime, newTimeout);
+ } else {
+ // The system is about to throw up an ANR dialog and has requested that we abort dispatch.
+ // Reset the timeout.
+ connection->nextTimeoutTime = LONG_LONG_MAX;
+
+ // Input state will no longer be realistic.
+ connection->inputState.setOutOfSync();
+
+ if (! connection->outboundQueue.isEmpty()) {
+ // Make the current pending dispatch asynchronous (if it isn't already) so that
+ // subsequent events can be delivered to the ANR dialog or to another application.
+ DispatchEntry* currentDispatchEntry = connection->outboundQueue.headSentinel.next;
+ currentDispatchEntry->preemptSyncTarget();
+
+ // Drain all but the first entry in the outbound queue. We keep the first entry
+ // since that is the one that dispatch is stuck on. We throw away the others
+ // so that we don't spam the application with stale messages if it eventually
+ // wakes up and recovers from the ANR.
+ drainOutboundQueueLocked(connection.get(), currentDispatchEntry->next);
+ }
+ }
}
void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection, bool broken) {
#if DEBUG_DISPATCH_CYCLE
LOGD("channel '%s' ~ abortDispatchCycle - broken=%s",
- connection->getInputChannelName(), broken ? "true" : "false");
+ connection->getInputChannelName(), toString(broken));
#endif
// Clear the pending timeout.
connection->nextTimeoutTime = LONG_LONG_MAX;
- // Clear the outbound queue.
- if (! connection->outboundQueue.isEmpty()) {
- do {
- DispatchEntry* dispatchEntry = connection->outboundQueue.dequeueAtHead();
- if (dispatchEntry->isSyncTarget()) {
- decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
- }
- mAllocator.releaseDispatchEntry(dispatchEntry);
- } while (! connection->outboundQueue.isEmpty());
+ // Input state will no longer be realistic.
+ connection->inputState.setOutOfSync();
- deactivateConnectionLocked(connection.get());
- }
+ // Clear the outbound queue.
+ drainOutboundQueueLocked(connection.get(), connection->outboundQueue.headSentinel.next);
// Handle the case where the connection appears to be unrecoverably broken.
// Ignore already broken or zombie connections.
@@ -950,6 +1764,26 @@ void InputDispatcher::abortDispatchCycleLocked(nsecs_t currentTime,
}
}
+void InputDispatcher::drainOutboundQueueLocked(Connection* connection,
+ DispatchEntry* firstDispatchEntryToDrain) {
+ for (DispatchEntry* dispatchEntry = firstDispatchEntryToDrain;
+ dispatchEntry != & connection->outboundQueue.tailSentinel;) {
+ DispatchEntry* next = dispatchEntry->next;
+ connection->outboundQueue.dequeue(dispatchEntry);
+
+ if (dispatchEntry->isSyncTarget()) {
+ decrementPendingSyncDispatchesLocked(dispatchEntry->eventEntry);
+ }
+ mAllocator.releaseDispatchEntry(dispatchEntry);
+
+ dispatchEntry = next;
+ }
+
+ if (connection->outboundQueue.isEmpty()) {
+ deactivateConnectionLocked(connection);
+ }
+}
+
bool InputDispatcher::handleReceiveCallback(int receiveFd, int events, void* data) {
InputDispatcher* d = static_cast<InputDispatcher*>(data);
@@ -1000,57 +1834,19 @@ void InputDispatcher::notifyConfigurationChanged(nsecs_t eventTime) {
LOGD("notifyConfigurationChanged - eventTime=%lld", eventTime);
#endif
- bool wasEmpty;
+ bool needWake;
{ // acquire lock
AutoMutex _l(mLock);
ConfigurationChangedEntry* newEntry = mAllocator.obtainConfigurationChangedEntry(eventTime);
-
- wasEmpty = mInboundQueue.isEmpty();
- mInboundQueue.enqueueAtTail(newEntry);
+ needWake = enqueueInboundEventLocked(newEntry);
} // release lock
- if (wasEmpty) {
+ if (needWake) {
mPollLoop->wake();
}
}
-void InputDispatcher::notifyAppSwitchComing(nsecs_t eventTime) {
-#if DEBUG_INBOUND_EVENT_DETAILS
- LOGD("notifyAppSwitchComing - eventTime=%lld", eventTime);
-#endif
-
- // Remove movement keys from the queue from most recent to least recent, stopping at the
- // first non-movement key.
- // TODO: Include a detailed description of why we do this...
-
- { // acquire lock
- AutoMutex _l(mLock);
-
- for (EventEntry* entry = mInboundQueue.tail.prev; entry != & mInboundQueue.head; ) {
- EventEntry* prev = entry->prev;
-
- if (entry->type == EventEntry::TYPE_KEY) {
- KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
- if (isMovementKey(keyEntry->keyCode)) {
- LOGV("Dropping movement key during app switch: keyCode=%d, action=%d",
- keyEntry->keyCode, keyEntry->action);
- mInboundQueue.dequeue(keyEntry);
-
- setInjectionResultLocked(entry, INPUT_EVENT_INJECTION_FAILED);
-
- mAllocator.releaseKeyEntry(keyEntry);
- } else {
- // stop at last non-movement key
- break;
- }
- }
-
- entry = prev;
- }
- } // release lock
-}
-
void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t source,
uint32_t policyFlags, int32_t action, int32_t flags,
int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime) {
@@ -1061,7 +1857,7 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t sou
keyCode, scanCode, metaState, downTime);
#endif
- bool wasEmpty;
+ bool needWake;
{ // acquire lock
AutoMutex _l(mLock);
@@ -1070,11 +1866,10 @@ void InputDispatcher::notifyKey(nsecs_t eventTime, int32_t deviceId, int32_t sou
deviceId, source, policyFlags, action, flags, keyCode, scanCode,
metaState, repeatCount, downTime);
- wasEmpty = mInboundQueue.isEmpty();
- mInboundQueue.enqueueAtTail(newEntry);
+ needWake = enqueueInboundEventLocked(newEntry);
} // release lock
- if (wasEmpty) {
+ if (needWake) {
mPollLoop->wake();
}
}
@@ -1101,7 +1896,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t
}
#endif
- bool wasEmpty;
+ bool needWake;
{ // acquire lock
AutoMutex _l(mLock);
@@ -1112,8 +1907,8 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t
// Try to append a move sample to the tail of the inbound queue for this device.
// Give up if we encounter a non-move motion event for this device since that
// means we cannot append any new samples until a new motion event has started.
- for (EventEntry* entry = mInboundQueue.tail.prev;
- entry != & mInboundQueue.head; entry = entry->prev) {
+ for (EventEntry* entry = mInboundQueue.tailSentinel.prev;
+ entry != & mInboundQueue.headSentinel; entry = entry->prev) {
if (entry->type != EventEntry::TYPE_MOTION) {
// Keep looking for motion events.
continue;
@@ -1140,18 +1935,6 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t
LOGD("Appended motion sample onto batch for most recent "
"motion event for this device in the inbound queue.");
#endif
-
- // Sanity check for special case because dispatch is interruptible.
- // The dispatch logic is partially interruptible and releases its lock while
- // identifying targets. However, as soon as the targets have been identified,
- // the dispatcher proceeds to write a dispatch entry into all relevant outbound
- // queues and then promptly removes the motion entry from the queue.
- //
- // Consequently, we should never observe the case where the inbound queue contains
- // an in-progress motion entry unless the current input targets are invalid
- // (currently being computed). Check for this!
- assert(! (motionEntry->dispatchInProgress && mCurrentInputTargetsValid));
-
return; // done!
}
@@ -1178,7 +1961,7 @@ void InputDispatcher::notifyMotion(nsecs_t eventTime, int32_t deviceId, int32_t
for (size_t i = 0; i < mActiveConnections.size(); i++) {
Connection* connection = mActiveConnections.itemAt(i);
if (! connection->outboundQueue.isEmpty()) {
- DispatchEntry* dispatchEntry = connection->outboundQueue.tail.prev;
+ DispatchEntry* dispatchEntry = connection->outboundQueue.tailSentinel.prev;
if (dispatchEntry->isSyncTarget()) {
if (dispatchEntry->eventEntry->type != EventEntry::TYPE_MOTION) {
goto NoBatchingOrStreaming;
@@ -1220,11 +2003,10 @@ NoBatchingOrStreaming:;
xPrecision, yPrecision, downTime,
pointerCount, pointerIds, pointerCoords);
- wasEmpty = mInboundQueue.isEmpty();
- mInboundQueue.enqueueAtTail(newEntry);
+ needWake = enqueueInboundEventLocked(newEntry);
} // release lock
- if (wasEmpty) {
+ if (needWake) {
mPollLoop->wake();
}
}
@@ -1240,11 +2022,15 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
nsecs_t endTime = now() + milliseconds_to_nanoseconds(timeoutMillis);
EventEntry* injectedEntry;
- bool wasEmpty;
+ bool needWake;
{ // acquire lock
AutoMutex _l(mLock);
- injectedEntry = createEntryFromInputEventLocked(event);
+ injectedEntry = createEntryFromInjectedInputEventLocked(event);
+ if (! injectedEntry) {
+ return INPUT_EVENT_INJECTION_FAILED;
+ }
+
injectedEntry->refCount += 1;
injectedEntry->injectorPid = injectorPid;
injectedEntry->injectorUid = injectorUid;
@@ -1253,12 +2039,10 @@ int32_t InputDispatcher::injectInputEvent(const InputEvent* event,
injectedEntry->injectionIsAsync = true;
}
- wasEmpty = mInboundQueue.isEmpty();
- mInboundQueue.enqueueAtTail(injectedEntry);
-
+ needWake = enqueueInboundEventLocked(injectedEntry);
} // release lock
- if (wasEmpty) {
+ if (needWake) {
mPollLoop->wake();
}
@@ -1361,11 +2145,42 @@ void InputDispatcher::decrementPendingSyncDispatchesLocked(EventEntry* entry) {
}
}
-InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked(
+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 isValidMotionAction(int32_t action) {
+ 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_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+InputDispatcher::EventEntry* InputDispatcher::createEntryFromInjectedInputEventLocked(
const InputEvent* event) {
switch (event->getType()) {
case AINPUT_EVENT_TYPE_KEY: {
const KeyEvent* keyEvent = static_cast<const KeyEvent*>(event);
+ if (! isValidKeyAction(keyEvent->getAction())) {
+ LOGE("Dropping injected key event since it has invalid action code 0x%x",
+ keyEvent->getAction());
+ return NULL;
+ }
+
uint32_t policyFlags = POLICY_FLAG_INJECTED;
KeyEntry* keyEntry = mAllocator.obtainKeyEntry(keyEvent->getEventTime(),
@@ -1378,6 +2193,17 @@ InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked(
case AINPUT_EVENT_TYPE_MOTION: {
const MotionEvent* motionEvent = static_cast<const MotionEvent*>(event);
+ if (! isValidMotionAction(motionEvent->getAction())) {
+ LOGE("Dropping injected motion event since it has invalid action code 0x%x.",
+ motionEvent->getAction());
+ return NULL;
+ }
+ if (motionEvent->getPointerCount() == 0
+ || motionEvent->getPointerCount() > MAX_POINTERS) {
+ LOGE("Dropping injected motion event since it has an invalid pointer count %d.",
+ motionEvent->getPointerCount());
+ }
+
uint32_t policyFlags = POLICY_FLAG_INJECTED;
const nsecs_t* sampleEventTimes = motionEvent->getSampleEventTimes();
@@ -1405,33 +2231,143 @@ InputDispatcher::EventEntry* InputDispatcher::createEntryFromInputEventLocked(
}
}
-void InputDispatcher::resetKeyRepeatLocked() {
- if (mKeyRepeatState.lastKeyEntry) {
- mAllocator.releaseKeyEntry(mKeyRepeatState.lastKeyEntry);
- mKeyRepeatState.lastKeyEntry = NULL;
+void InputDispatcher::setInputWindows(const Vector<InputWindow>& inputWindows) {
+#if DEBUG_FOCUS
+ LOGD("setInputWindows");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ sp<InputChannel> touchedWindowChannel;
+ if (mTouchedWindow) {
+ touchedWindowChannel = mTouchedWindow->inputChannel;
+ mTouchedWindow = NULL;
+ }
+ size_t numTouchedWallpapers = mTouchedWallpaperWindows.size();
+ if (numTouchedWallpapers != 0) {
+ for (size_t i = 0; i < numTouchedWallpapers; i++) {
+ mTempTouchedWallpaperChannels.push(mTouchedWallpaperWindows[i]->inputChannel);
+ }
+ mTouchedWallpaperWindows.clear();
+ }
+
+ bool hadFocusedWindow = mFocusedWindow != NULL;
+
+ mFocusedWindow = NULL;
+ mWallpaperWindows.clear();
+
+ mWindows.clear();
+ mWindows.appendVector(inputWindows);
+
+ size_t numWindows = mWindows.size();
+ for (size_t i = 0; i < numWindows; i++) {
+ InputWindow* window = & mWindows.editItemAt(i);
+ if (window->hasFocus) {
+ mFocusedWindow = window;
+ }
+
+ if (window->layoutParamsType == InputWindow::TYPE_WALLPAPER) {
+ mWallpaperWindows.push(window);
+
+ for (size_t j = 0; j < numTouchedWallpapers; j++) {
+ if (window->inputChannel == mTempTouchedWallpaperChannels[i]) {
+ mTouchedWallpaperWindows.push(window);
+ }
+ }
+ }
+
+ if (window->inputChannel == touchedWindowChannel) {
+ mTouchedWindow = window;
+ }
+ }
+
+ mTempTouchedWallpaperChannels.clear();
+
+ if ((hadFocusedWindow && ! mFocusedWindow)
+ || (mFocusedWindow && ! mFocusedWindow->visible)) {
+ preemptInputDispatchInnerLocked();
+ }
+
+#if DEBUG_FOCUS
+ logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mPollLoop->wake();
+}
+
+void InputDispatcher::setFocusedApplication(const InputApplication* inputApplication) {
+#if DEBUG_FOCUS
+ LOGD("setFocusedApplication");
+#endif
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ releaseFocusedApplicationLocked();
+
+ if (inputApplication) {
+ mFocusedApplicationStorage = *inputApplication;
+ mFocusedApplication = & mFocusedApplicationStorage;
+ }
+
+#if DEBUG_FOCUS
+ logDispatchStateLocked();
+#endif
+ } // release lock
+
+ // Wake up poll loop since it may need to make new input dispatching choices.
+ mPollLoop->wake();
+}
+
+void InputDispatcher::releaseFocusedApplicationLocked() {
+ if (mFocusedApplication) {
+ mFocusedApplication = NULL;
+ mFocusedApplicationStorage.handle.clear();
}
}
-void InputDispatcher::preemptInputDispatch() {
-#if DEBUG_DISPATCH_CYCLE
- LOGD("preemptInputDispatch");
+void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
+#if DEBUG_FOCUS
+ LOGD("setInputDispatchMode: enabled=%d, frozen=%d", enabled, frozen);
#endif
- bool preemptedOne = false;
+ bool changed;
{ // acquire lock
AutoMutex _l(mLock);
- for (size_t i = 0; i < mActiveConnections.size(); i++) {
- Connection* connection = mActiveConnections[i];
- if (connection->hasPendingSyncTarget()) {
-#if DEBUG_DISPATCH_CYCLE
- LOGD("channel '%s' ~ Preempted pending synchronous dispatch",
- connection->getInputChannelName());
-#endif
- connection->outboundQueue.tail.prev->targetFlags &= ~ InputTarget::FLAG_SYNC;
- preemptedOne = true;
+ if (mDispatchEnabled != enabled || mDispatchFrozen != frozen) {
+ if (mDispatchFrozen && ! frozen) {
+ resetANRTimeoutsLocked();
}
+
+ 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.
+ mPollLoop->wake();
+ }
+}
+
+void InputDispatcher::preemptInputDispatch() {
+#if DEBUG_FOCUS
+ LOGD("preemptInputDispatch");
+#endif
+
+ bool preemptedOne;
+ { // acquire lock
+ AutoMutex _l(mLock);
+ preemptedOne = preemptInputDispatchInnerLocked();
} // release lock
if (preemptedOne) {
@@ -1440,9 +2376,99 @@ void InputDispatcher::preemptInputDispatch() {
}
}
-status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel) {
+bool InputDispatcher::preemptInputDispatchInnerLocked() {
+ bool preemptedOne = false;
+ for (size_t i = 0; i < mActiveConnections.size(); i++) {
+ Connection* connection = mActiveConnections[i];
+ if (connection->hasPendingSyncTarget()) {
+#if DEBUG_DISPATCH_CYCLE
+ LOGD("channel '%s' ~ Preempted pending synchronous dispatch",
+ connection->getInputChannelName());
+#endif
+ connection->preemptSyncTarget();
+ preemptedOne = true;
+ }
+ }
+ return preemptedOne;
+}
+
+void InputDispatcher::logDispatchStateLocked() {
+ String8 dump;
+ dumpDispatchStateLocked(dump);
+ LOGD("%s", dump.string());
+}
+
+void InputDispatcher::dumpDispatchStateLocked(String8& dump) {
+ dump.appendFormat(" dispatchEnabled: %d\n", mDispatchEnabled);
+ dump.appendFormat(" dispatchFrozen: %d\n", mDispatchFrozen);
+
+ if (mFocusedApplication) {
+ dump.appendFormat(" focusedApplication: name='%s', dispatchingTimeout=%0.3fms\n",
+ mFocusedApplication->name.string(),
+ mFocusedApplication->dispatchingTimeout / 1000000.0);
+ } else {
+ dump.append(" focusedApplication: <null>\n");
+ }
+ dump.appendFormat(" focusedWindow: '%s'\n",
+ mFocusedWindow != NULL ? mFocusedWindow->inputChannel->getName().string() : "<null>");
+ dump.appendFormat(" touchedWindow: '%s', touchDown=%d\n",
+ mTouchedWindow != NULL ? mTouchedWindow->inputChannel->getName().string() : "<null>",
+ mTouchDown);
+ for (size_t i = 0; i < mTouchedWallpaperWindows.size(); i++) {
+ dump.appendFormat(" touchedWallpaperWindows[%d]: '%s'\n",
+ i, mTouchedWallpaperWindows[i]->inputChannel->getName().string());
+ }
+ for (size_t i = 0; i < mWindows.size(); i++) {
+ dump.appendFormat(" windows[%d]: '%s', paused=%s, hasFocus=%s, hasWallpaper=%s, "
+ "visible=%s, flags=0x%08x, type=0x%08x, "
+ "frame=[%d,%d][%d,%d], "
+ "visibleFrame=[%d,%d][%d,%d], "
+ "touchableArea=[%d,%d][%d,%d], "
+ "ownerPid=%d, ownerUid=%d, dispatchingTimeout=%0.3fms\n",
+ i, mWindows[i].inputChannel->getName().string(),
+ toString(mWindows[i].paused),
+ toString(mWindows[i].hasFocus),
+ toString(mWindows[i].hasWallpaper),
+ toString(mWindows[i].visible),
+ mWindows[i].layoutParamsFlags, mWindows[i].layoutParamsType,
+ mWindows[i].frameLeft, mWindows[i].frameTop,
+ mWindows[i].frameRight, mWindows[i].frameBottom,
+ mWindows[i].visibleFrameLeft, mWindows[i].visibleFrameTop,
+ mWindows[i].visibleFrameRight, mWindows[i].visibleFrameBottom,
+ mWindows[i].touchableAreaLeft, mWindows[i].touchableAreaTop,
+ mWindows[i].touchableAreaRight, mWindows[i].touchableAreaBottom,
+ mWindows[i].ownerPid, mWindows[i].ownerUid,
+ mWindows[i].dispatchingTimeout / 1000000.0);
+ }
+
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ const sp<InputChannel>& channel = mMonitoringChannels[i];
+ dump.appendFormat(" monitoringChannel[%d]: '%s'\n",
+ i, channel->getName().string());
+ }
+
+ for (size_t i = 0; i < mActiveConnections.size(); i++) {
+ const Connection* connection = mActiveConnections[i];
+ dump.appendFormat(" activeConnection[%d]: '%s', status=%s, hasPendingSyncTarget=%s, "
+ "inputState.isNeutral=%s, inputState.isOutOfSync=%s\n",
+ i, connection->getInputChannelName(), connection->getStatusLabel(),
+ toString(connection->hasPendingSyncTarget()),
+ toString(connection->inputState.isNeutral()),
+ toString(connection->inputState.isOutOfSync()));
+ }
+
+ if (isAppSwitchPendingLocked()) {
+ dump.appendFormat(" appSwitch: pending, due in %01.1fms\n",
+ (mAppSwitchDueTime - now()) / 1000000.0);
+ } else {
+ dump.append(" appSwitch: not pending\n");
+ }
+}
+
+status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel, bool monitor) {
#if DEBUG_REGISTRATION
- LOGD("channel '%s' ~ registerInputChannel", inputChannel->getName().string());
+ LOGD("channel '%s' ~ registerInputChannel - monitor=%s", inputChannel->getName().string(),
+ toString(monitor));
#endif
{ // acquire lock
@@ -1465,6 +2491,10 @@ status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChan
int32_t receiveFd = inputChannel->getReceivePipeFd();
mConnectionsByReceiveFd.add(receiveFd, connection);
+ if (monitor) {
+ mMonitoringChannels.push(inputChannel);
+ }
+
mPollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this);
runCommandsLockedInterruptible();
@@ -1492,6 +2522,13 @@ status_t InputDispatcher::unregisterInputChannel(const sp<InputChannel>& inputCh
connection->status = Connection::STATUS_ZOMBIE;
+ for (size_t i = 0; i < mMonitoringChannels.size(); i++) {
+ if (mMonitoringChannels[i] == inputChannel) {
+ mMonitoringChannels.removeAt(i);
+ break;
+ }
+ }
+
mPollLoop->removeCallback(inputChannel->getReceivePipeFd());
nsecs_t currentTime = now();
@@ -1578,6 +2615,15 @@ void InputDispatcher::onDispatchCycleBrokenLocked(
commandEntry->connection = connection;
}
+void InputDispatcher::doNotifyConfigurationChangedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->notifyConfigurationChanged(commandEntry->eventTime);
+
+ mLock.lock();
+}
+
void InputDispatcher::doNotifyInputChannelBrokenLockedInterruptible(
CommandEntry* commandEntry) {
sp<Connection> connection = commandEntry->connection;
@@ -1598,17 +2644,12 @@ void InputDispatcher::doNotifyInputChannelANRLockedInterruptible(
if (connection->status != Connection::STATUS_ZOMBIE) {
mLock.unlock();
- nsecs_t newTimeout;
- bool resume = mPolicy->notifyInputChannelANR(connection->inputChannel, newTimeout);
+ nsecs_t newTimeout = mPolicy->notifyInputChannelANR(connection->inputChannel);
mLock.lock();
nsecs_t currentTime = now();
- if (resume) {
- resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout);
- } else {
- abortDispatchCycleLocked(currentTime, connection, false /*(not) broken*/);
- }
+ resumeAfterTimeoutDispatchCycleLocked(currentTime, connection, newTimeout);
}
}
@@ -1625,6 +2666,57 @@ void InputDispatcher::doNotifyInputChannelRecoveredFromANRLockedInterruptible(
}
}
+void InputDispatcher::doInterceptKeyBeforeDispatchingLockedInterruptible(
+ CommandEntry* commandEntry) {
+ KeyEntry* entry = commandEntry->keyEntry;
+ mReusableKeyEvent.initialize(entry->deviceId, entry->source, entry->action, entry->flags,
+ entry->keyCode, entry->scanCode, entry->metaState, entry->repeatCount,
+ entry->downTime, entry->eventTime);
+
+ mLock.unlock();
+
+ bool consumed = mPolicy->interceptKeyBeforeDispatching(commandEntry->inputChannel,
+ & mReusableKeyEvent, entry->policyFlags);
+
+ mLock.lock();
+
+ entry->interceptKeyResult = consumed
+ ? KeyEntry::INTERCEPT_KEY_RESULT_SKIP
+ : KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ mAllocator.releaseKeyEntry(entry);
+}
+
+void InputDispatcher::doPokeUserActivityLockedInterruptible(CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ mPolicy->pokeUserActivity(commandEntry->eventTime, commandEntry->windowType,
+ commandEntry->userActivityEventType);
+
+ mLock.lock();
+}
+
+void InputDispatcher::doTargetsNotReadyTimeoutLockedInterruptible(
+ CommandEntry* commandEntry) {
+ mLock.unlock();
+
+ nsecs_t newTimeout;
+ if (commandEntry->inputChannel.get()) {
+ newTimeout = mPolicy->notifyInputChannelANR(commandEntry->inputChannel);
+ } else if (commandEntry->inputApplicationHandle.get()) {
+ newTimeout = mPolicy->notifyANR(commandEntry->inputApplicationHandle);
+ } else {
+ newTimeout = 0;
+ }
+
+ mLock.lock();
+
+ resumeAfterTargetsNotReadyTimeoutLocked(newTimeout);
+}
+
+void InputDispatcher::dump(String8& dump) {
+ dumpDispatchStateLocked(dump);
+}
+
// --- InputDispatcher::Allocator ---
@@ -1668,6 +2760,8 @@ InputDispatcher::KeyEntry* InputDispatcher::Allocator::obtainKeyEntry(nsecs_t ev
entry->metaState = metaState;
entry->repeatCount = repeatCount;
entry->downTime = downTime;
+ entry->syntheticRepeat = false;
+ entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
return entry;
}
@@ -1702,10 +2796,18 @@ InputDispatcher::MotionEntry* InputDispatcher::Allocator::obtainMotionEntry(nsec
}
InputDispatcher::DispatchEntry* InputDispatcher::Allocator::obtainDispatchEntry(
- EventEntry* eventEntry) {
+ EventEntry* eventEntry,
+ int32_t targetFlags, float xOffset, float yOffset, nsecs_t timeout) {
DispatchEntry* entry = mDispatchEntryPool.alloc();
entry->eventEntry = eventEntry;
eventEntry->refCount += 1;
+ entry->targetFlags = targetFlags;
+ entry->xOffset = xOffset;
+ entry->yOffset = yOffset;
+ entry->timeout = timeout;
+ entry->inProgress = false;
+ entry->headMotionSample = NULL;
+ entry->tailMotionSample = NULL;
return entry;
}
@@ -1788,6 +2890,25 @@ void InputDispatcher::Allocator::appendMotionSample(MotionEntry* motionEntry,
motionEntry->lastSample = sample;
}
+
+// --- InputDispatcher::EventEntry ---
+
+void InputDispatcher::EventEntry::recycle() {
+ injectionResult = INPUT_EVENT_INJECTION_PENDING;
+ dispatchInProgress = false;
+ pendingSyncDispatches = 0;
+}
+
+
+// --- InputDispatcher::KeyEntry ---
+
+void InputDispatcher::KeyEntry::recycle() {
+ EventEntry::recycle();
+ syntheticRepeat = false;
+ interceptKeyResult = INTERCEPT_KEY_RESULT_UNKNOWN;
+}
+
+
// --- InputDispatcher::MotionEntry ---
uint32_t InputDispatcher::MotionEntry::countSamples() const {
@@ -1798,6 +2919,189 @@ uint32_t InputDispatcher::MotionEntry::countSamples() const {
return count;
}
+
+// --- InputDispatcher::InputState ---
+
+InputDispatcher::InputState::InputState() :
+ mIsOutOfSync(false) {
+}
+
+InputDispatcher::InputState::~InputState() {
+}
+
+bool InputDispatcher::InputState::isNeutral() const {
+ return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
+}
+
+bool InputDispatcher::InputState::isOutOfSync() const {
+ return mIsOutOfSync;
+}
+
+void InputDispatcher::InputState::setOutOfSync() {
+ if (! isNeutral()) {
+ mIsOutOfSync = true;
+ }
+}
+
+void InputDispatcher::InputState::resetOutOfSync() {
+ mIsOutOfSync = false;
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent(
+ const EventEntry* entry) {
+ switch (entry->type) {
+ case EventEntry::TYPE_KEY:
+ return trackKey(static_cast<const KeyEntry*>(entry));
+
+ case EventEntry::TYPE_MOTION:
+ return trackMotion(static_cast<const MotionEntry*>(entry));
+
+ default:
+ return CONSISTENT;
+ }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackKey(
+ const KeyEntry* entry) {
+ int32_t action = entry->action;
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
+ KeyMemento& memento = mKeyMementos.editItemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source
+ && memento.keyCode == entry->keyCode
+ && memento.scanCode == entry->scanCode) {
+ switch (action) {
+ case AKEY_EVENT_ACTION_UP:
+ mKeyMementos.removeAt(i);
+ if (isNeutral()) {
+ mIsOutOfSync = false;
+ }
+ return CONSISTENT;
+
+ case AKEY_EVENT_ACTION_DOWN:
+ return TOLERABLE;
+
+ default:
+ return BROKEN;
+ }
+ }
+ }
+
+ switch (action) {
+ case AKEY_EVENT_ACTION_DOWN: {
+ mKeyMementos.push();
+ KeyMemento& memento = mKeyMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.keyCode = entry->keyCode;
+ memento.scanCode = entry->scanCode;
+ memento.downTime = entry->downTime;
+ return CONSISTENT;
+ }
+
+ default:
+ return BROKEN;
+ }
+}
+
+InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackMotion(
+ const MotionEntry* entry) {
+ int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK;
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ MotionMemento& memento = mMotionMementos.editItemAt(i);
+ if (memento.deviceId == entry->deviceId
+ && memento.source == entry->source) {
+ switch (action) {
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ mMotionMementos.removeAt(i);
+ if (isNeutral()) {
+ mIsOutOfSync = false;
+ }
+ return CONSISTENT;
+
+ case AMOTION_EVENT_ACTION_DOWN:
+ return TOLERABLE;
+
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ if (entry->pointerCount == memento.pointerCount + 1) {
+ memento.setPointers(entry);
+ return CONSISTENT;
+ }
+ return BROKEN;
+
+ case AMOTION_EVENT_ACTION_POINTER_UP:
+ if (entry->pointerCount == memento.pointerCount - 1) {
+ memento.setPointers(entry);
+ return CONSISTENT;
+ }
+ return BROKEN;
+
+ case AMOTION_EVENT_ACTION_MOVE:
+ if (entry->pointerCount == memento.pointerCount) {
+ return CONSISTENT;
+ }
+ return BROKEN;
+
+ default:
+ return BROKEN;
+ }
+ }
+ }
+
+ switch (action) {
+ case AMOTION_EVENT_ACTION_DOWN: {
+ mMotionMementos.push();
+ MotionMemento& memento = mMotionMementos.editTop();
+ memento.deviceId = entry->deviceId;
+ memento.source = entry->source;
+ memento.xPrecision = entry->xPrecision;
+ memento.yPrecision = entry->yPrecision;
+ memento.downTime = entry->downTime;
+ memento.setPointers(entry);
+ return CONSISTENT;
+ }
+
+ default:
+ return BROKEN;
+ }
+}
+
+void InputDispatcher::InputState::MotionMemento::setPointers(const MotionEntry* entry) {
+ pointerCount = entry->pointerCount;
+ for (uint32_t i = 0; i < entry->pointerCount; i++) {
+ pointerIds[i] = entry->pointerIds[i];
+ pointerCoords[i] = entry->lastSample->pointerCoords[i];
+ }
+}
+
+void InputDispatcher::InputState::synthesizeCancelationEvents(
+ Allocator* allocator, Vector<EventEntry*>& outEvents) const {
+ for (size_t i = 0; i < mKeyMementos.size(); i++) {
+ const KeyMemento& memento = mKeyMementos.itemAt(i);
+ outEvents.push(allocator->obtainKeyEntry(now(),
+ memento.deviceId, memento.source, 0,
+ AKEY_EVENT_ACTION_UP, AKEY_EVENT_FLAG_CANCELED,
+ memento.keyCode, memento.scanCode, 0, 0, memento.downTime));
+ }
+
+ for (size_t i = 0; i < mMotionMementos.size(); i++) {
+ const MotionMemento& memento = mMotionMementos.itemAt(i);
+ outEvents.push(allocator->obtainMotionEntry(now(),
+ memento.deviceId, memento.source, 0,
+ AMOTION_EVENT_ACTION_CANCEL, 0, 0, 0,
+ memento.xPrecision, memento.yPrecision, memento.downTime,
+ memento.pointerCount, memento.pointerIds, memento.pointerCoords));
+ }
+}
+
+void InputDispatcher::InputState::clear() {
+ mKeyMementos.clear();
+ mMotionMementos.clear();
+ mIsOutOfSync = false;
+}
+
+
// --- InputDispatcher::Connection ---
InputDispatcher::Connection::Connection(const sp<InputChannel>& inputChannel) :
@@ -1818,6 +3122,14 @@ void InputDispatcher::Connection::setNextTimeoutTime(nsecs_t currentTime, nsecs_
nextTimeoutTime = (timeout >= 0) ? currentTime + timeout : LONG_LONG_MAX;
}
+void InputDispatcher::Connection::resetTimeout(nsecs_t currentTime) {
+ if (outboundQueue.isEmpty()) {
+ nextTimeoutTime = LONG_LONG_MAX;
+ } else {
+ setNextTimeoutTime(currentTime, outboundQueue.headSentinel.next->timeout);
+ }
+}
+
const char* InputDispatcher::Connection::getStatusLabel() const {
switch (status) {
case STATUS_NORMAL:
@@ -1839,8 +3151,8 @@ const char* InputDispatcher::Connection::getStatusLabel() const {
InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchEntryForEvent(
const EventEntry* eventEntry) const {
- for (DispatchEntry* dispatchEntry = outboundQueue.tail.prev;
- dispatchEntry != & outboundQueue.head; dispatchEntry = dispatchEntry->prev) {
+ for (DispatchEntry* dispatchEntry = outboundQueue.tailSentinel.prev;
+ dispatchEntry != & outboundQueue.headSentinel; dispatchEntry = dispatchEntry->prev) {
if (dispatchEntry->eventEntry == eventEntry) {
return dispatchEntry;
}
@@ -1848,9 +3160,11 @@ InputDispatcher::DispatchEntry* InputDispatcher::Connection::findQueuedDispatchE
return NULL;
}
+
// --- InputDispatcher::CommandEntry ---
-InputDispatcher::CommandEntry::CommandEntry() {
+InputDispatcher::CommandEntry::CommandEntry() :
+ keyEntry(NULL) {
}
InputDispatcher::CommandEntry::~CommandEntry() {
diff --git a/libs/ui/InputManager.cpp b/libs/ui/InputManager.cpp
index ed4f07b..09fce38 100644
--- a/libs/ui/InputManager.cpp
+++ b/libs/ui/InputManager.cpp
@@ -72,52 +72,12 @@ status_t InputManager::stop() {
return OK;
}
-status_t InputManager::registerInputChannel(const sp<InputChannel>& inputChannel) {
- return mDispatcher->registerInputChannel(inputChannel);
+sp<InputReaderInterface> InputManager::getReader() {
+ return mReader;
}
-status_t InputManager::unregisterInputChannel(const sp<InputChannel>& inputChannel) {
- return mDispatcher->unregisterInputChannel(inputChannel);
-}
-
-int32_t InputManager::injectInputEvent(const InputEvent* event,
- int32_t injectorPid, int32_t injectorUid, int32_t syncMode, int32_t timeoutMillis) {
- return mDispatcher->injectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis);
-}
-
-void InputManager::preemptInputDispatch() {
- mDispatcher->preemptInputDispatch();
-}
-
-void InputManager::getInputConfiguration(InputConfiguration* outConfiguration) {
- mReader->getInputConfiguration(outConfiguration);
-}
-
-status_t InputManager::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
- return mReader->getInputDeviceInfo(deviceId, outDeviceInfo);
-}
-
-void InputManager::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
- mReader->getInputDeviceIds(outDeviceIds);
-}
-
-int32_t InputManager::getScanCodeState(int32_t deviceId, uint32_t sourceMask,
- int32_t scanCode) {
- return mReader->getScanCodeState(deviceId, sourceMask, scanCode);
-}
-
-int32_t InputManager::getKeyCodeState(int32_t deviceId, uint32_t sourceMask,
- int32_t keyCode) {
- return mReader->getKeyCodeState(deviceId, sourceMask, keyCode);
-}
-
-int32_t InputManager::getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) {
- return mReader->getSwitchState(deviceId, sourceMask, sw);
-}
-
-bool InputManager::hasKeys(int32_t deviceId, uint32_t sourceMask,
- size_t numCodes, const int32_t* keyCodes, uint8_t* outFlags) {
- return mReader->hasKeys(deviceId, sourceMask, numCodes, keyCodes, outFlags);
+sp<InputDispatcherInterface> InputManager::getDispatcher() {
+ return mDispatcher;
}
} // namespace android
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index d57b38c..88084c0 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -573,6 +573,60 @@ bool InputReader::markSupportedKeyCodes(int32_t deviceId, uint32_t sourceMask, s
} // release device registy reader lock
}
+void InputReader::dump(String8& dump) {
+ dumpDeviceInfo(dump);
+}
+
+static void dumpMotionRange(String8& dump,
+ const char* name, const InputDeviceInfo::MotionRange* range) {
+ if (range) {
+ dump.appendFormat(" %s = { min: %0.3f, max: %0.3f, flat: %0.3f, fuzz: %0.3f }\n",
+ name, range->min, range->max, range->flat, range->fuzz);
+ }
+}
+
+#define DUMP_MOTION_RANGE(range) \
+ dumpMotionRange(dump, #range, deviceInfo.getMotionRange(AINPUT_MOTION_RANGE_##range));
+
+void InputReader::dumpDeviceInfo(String8& dump) {
+ Vector<int32_t> deviceIds;
+ getInputDeviceIds(deviceIds);
+
+ InputDeviceInfo deviceInfo;
+ for (size_t i = 0; i < deviceIds.size(); i++) {
+ int32_t deviceId = deviceIds[i];
+
+ status_t result = getInputDeviceInfo(deviceId, & deviceInfo);
+ if (result == NAME_NOT_FOUND) {
+ continue;
+ } else if (result != OK) {
+ dump.appendFormat(" ** Unexpected error %d getting information about input devices.\n",
+ result);
+ continue;
+ }
+
+ dump.appendFormat(" Device %d: '%s'\n",
+ deviceInfo.getId(), deviceInfo.getName().string());
+ dump.appendFormat(" sources = 0x%08x\n",
+ deviceInfo.getSources());
+ dump.appendFormat(" keyboardType = %d\n",
+ deviceInfo.getKeyboardType());
+
+ dump.append(" motion ranges:\n");
+ DUMP_MOTION_RANGE(X);
+ DUMP_MOTION_RANGE(Y);
+ DUMP_MOTION_RANGE(PRESSURE);
+ DUMP_MOTION_RANGE(SIZE);
+ DUMP_MOTION_RANGE(TOUCH_MAJOR);
+ DUMP_MOTION_RANGE(TOUCH_MINOR);
+ DUMP_MOTION_RANGE(TOOL_MAJOR);
+ DUMP_MOTION_RANGE(TOOL_MINOR);
+ DUMP_MOTION_RANGE(ORIENTATION);
+ }
+}
+
+#undef DUMP_MOTION_RANGE
+
// --- InputReaderThread ---
@@ -740,10 +794,6 @@ int32_t InputMapper::getMetaState() {
}
bool InputMapper::applyStandardPolicyActions(nsecs_t when, int32_t policyActions) {
- if (policyActions & InputReaderPolicyInterface::ACTION_APP_SWITCH_COMING) {
- getDispatcher()->notifyAppSwitchComing(when);
- }
-
return policyActions & InputReaderPolicyInterface::ACTION_DISPATCH;
}
@@ -1249,20 +1299,12 @@ void TouchInputMapper::initializeLocked() {
mLocked.orientedRanges.haveOrientation = false;
}
-static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) {
- if (axis.valid) {
- LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d",
- name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
- } else {
- LOGI(INDENT "Raw %s axis: unknown range", name);
- }
-}
-
void TouchInputMapper::configure() {
InputMapper::configure();
// Configure basic parameters.
configureParameters();
+ logParameters();
// Configure absolute axis information.
configureRawAxes();
@@ -1287,6 +1329,18 @@ void TouchInputMapper::configureParameters() {
mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
}
+void TouchInputMapper::logParameters() {
+ if (mParameters.useBadTouchFilter) {
+ LOGI(INDENT "Bad touch filter enabled.");
+ }
+ if (mParameters.useAveragingTouchFilter) {
+ LOGI(INDENT "Averaging touch filter enabled.");
+ }
+ if (mParameters.useJumpyTouchFilter) {
+ LOGI(INDENT "Jumpy touch filter enabled.");
+ }
+}
+
void TouchInputMapper::configureRawAxes() {
mRawAxes.x.clear();
mRawAxes.y.clear();
@@ -1298,6 +1352,15 @@ void TouchInputMapper::configureRawAxes() {
mRawAxes.orientation.clear();
}
+static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) {
+ if (axis.valid) {
+ LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d",
+ name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
+ } else {
+ LOGI(INDENT "Raw %s axis: unknown range", name);
+ }
+}
+
void TouchInputMapper::logRawAxes() {
logAxisInfo(mRawAxes.x, "x");
logAxisInfo(mRawAxes.y, "y");
@@ -1331,8 +1394,10 @@ bool TouchInputMapper::configureSurfaceLocked() {
bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height;
if (sizeChanged) {
- LOGI("Device configured: id=0x%x, name=%s (display size was changed)",
+ LOGI("Device reconfigured (display size changed): id=0x%x, name=%s",
getDeviceId(), getDeviceName().string());
+ LOGI(INDENT "Width: %dpx", width);
+ LOGI(INDENT "Height: %dpx", height);
mLocked.surfaceWidth = width;
mLocked.surfaceHeight = height;
@@ -1500,9 +1565,41 @@ bool TouchInputMapper::configureSurfaceLocked() {
mLocked.orientedRanges.y.fuzz = orientedYScale;
}
+ if (sizeChanged) {
+ logMotionRangesLocked();
+ }
+
return true;
}
+static void logMotionRangeInfo(InputDeviceInfo::MotionRange* range, const char* name) {
+ if (range) {
+ LOGI(INDENT "Output %s range: min=%f, max=%f, flat=%f, fuzz=%f",
+ name, range->min, range->max, range->flat, range->fuzz);
+ } else {
+ LOGI(INDENT "Output %s range: unsupported", name);
+ }
+}
+
+void TouchInputMapper::logMotionRangesLocked() {
+ logMotionRangeInfo(& mLocked.orientedRanges.x, "x");
+ logMotionRangeInfo(& mLocked.orientedRanges.y, "y");
+ logMotionRangeInfo(mLocked.orientedRanges.havePressure
+ ? & mLocked.orientedRanges.pressure : NULL, "pressure");
+ logMotionRangeInfo(mLocked.orientedRanges.haveSize
+ ? & mLocked.orientedRanges.size : NULL, "size");
+ logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea
+ ? & mLocked.orientedRanges.touchMajor : NULL, "touchMajor");
+ logMotionRangeInfo(mLocked.orientedRanges.haveTouchArea
+ ? & mLocked.orientedRanges.touchMinor : NULL, "touchMinor");
+ logMotionRangeInfo(mLocked.orientedRanges.haveToolArea
+ ? & mLocked.orientedRanges.toolMajor : NULL, "toolMajor");
+ logMotionRangeInfo(mLocked.orientedRanges.haveToolArea
+ ? & mLocked.orientedRanges.toolMinor : NULL, "toolMinor");
+ logMotionRangeInfo(mLocked.orientedRanges.haveOrientation
+ ? & mLocked.orientedRanges.orientation : NULL, "orientation");
+}
+
void TouchInputMapper::configureVirtualKeysLocked() {
assert(mRawAxes.x.valid && mRawAxes.y.valid);
@@ -1768,16 +1865,18 @@ void TouchInputMapper::resolveCalibration() {
}
void TouchInputMapper::logCalibration() {
+ LOGI(INDENT "Calibration:");
+
// Touch Area
switch (mCalibration.touchAreaCalibration) {
case Calibration::TOUCH_AREA_CALIBRATION_NONE:
- LOGI(INDENT " touch.touchArea.calibration: none");
+ LOGI(INDENT INDENT "touch.touchArea.calibration: none");
break;
case Calibration::TOUCH_AREA_CALIBRATION_GEOMETRIC:
- LOGI(INDENT " touch.touchArea.calibration: geometric");
+ LOGI(INDENT INDENT "touch.touchArea.calibration: geometric");
break;
case Calibration::TOUCH_AREA_CALIBRATION_PRESSURE:
- LOGI(INDENT " touch.touchArea.calibration: pressure");
+ LOGI(INDENT INDENT "touch.touchArea.calibration: pressure");
break;
default:
assert(false);
@@ -1786,40 +1885,40 @@ void TouchInputMapper::logCalibration() {
// Tool Area
switch (mCalibration.toolAreaCalibration) {
case Calibration::TOOL_AREA_CALIBRATION_NONE:
- LOGI(INDENT " touch.toolArea.calibration: none");
+ LOGI(INDENT INDENT "touch.toolArea.calibration: none");
break;
case Calibration::TOOL_AREA_CALIBRATION_GEOMETRIC:
- LOGI(INDENT " touch.toolArea.calibration: geometric");
+ LOGI(INDENT INDENT "touch.toolArea.calibration: geometric");
break;
case Calibration::TOOL_AREA_CALIBRATION_LINEAR:
- LOGI(INDENT " touch.toolArea.calibration: linear");
+ LOGI(INDENT INDENT "touch.toolArea.calibration: linear");
break;
default:
assert(false);
}
if (mCalibration.haveToolAreaLinearScale) {
- LOGI(INDENT " touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale);
+ LOGI(INDENT INDENT "touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale);
}
if (mCalibration.haveToolAreaLinearBias) {
- LOGI(INDENT " touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias);
+ LOGI(INDENT INDENT "touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias);
}
if (mCalibration.haveToolAreaIsSummed) {
- LOGI(INDENT " touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed);
+ LOGI(INDENT INDENT "touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed);
}
// Pressure
switch (mCalibration.pressureCalibration) {
case Calibration::PRESSURE_CALIBRATION_NONE:
- LOGI(INDENT " touch.pressure.calibration: none");
+ LOGI(INDENT INDENT "touch.pressure.calibration: none");
break;
case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
- LOGI(INDENT " touch.pressure.calibration: physical");
+ LOGI(INDENT INDENT "touch.pressure.calibration: physical");
break;
case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
- LOGI(INDENT " touch.pressure.calibration: amplitude");
+ LOGI(INDENT INDENT "touch.pressure.calibration: amplitude");
break;
default:
assert(false);
@@ -1827,10 +1926,10 @@ void TouchInputMapper::logCalibration() {
switch (mCalibration.pressureSource) {
case Calibration::PRESSURE_SOURCE_PRESSURE:
- LOGI(INDENT " touch.pressure.source: pressure");
+ LOGI(INDENT INDENT "touch.pressure.source: pressure");
break;
case Calibration::PRESSURE_SOURCE_TOUCH:
- LOGI(INDENT " touch.pressure.source: touch");
+ LOGI(INDENT INDENT "touch.pressure.source: touch");
break;
case Calibration::PRESSURE_SOURCE_DEFAULT:
break;
@@ -1839,16 +1938,16 @@ void TouchInputMapper::logCalibration() {
}
if (mCalibration.havePressureScale) {
- LOGI(INDENT " touch.pressure.scale: %f", mCalibration.pressureScale);
+ LOGI(INDENT INDENT "touch.pressure.scale: %f", mCalibration.pressureScale);
}
// Size
switch (mCalibration.sizeCalibration) {
case Calibration::SIZE_CALIBRATION_NONE:
- LOGI(INDENT " touch.size.calibration: none");
+ LOGI(INDENT INDENT "touch.size.calibration: none");
break;
case Calibration::SIZE_CALIBRATION_NORMALIZED:
- LOGI(INDENT " touch.size.calibration: normalized");
+ LOGI(INDENT INDENT "touch.size.calibration: normalized");
break;
default:
assert(false);
@@ -1857,10 +1956,10 @@ void TouchInputMapper::logCalibration() {
// Orientation
switch (mCalibration.orientationCalibration) {
case Calibration::ORIENTATION_CALIBRATION_NONE:
- LOGI(INDENT " touch.orientation.calibration: none");
+ LOGI(INDENT INDENT "touch.orientation.calibration: none");
break;
case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
- LOGI(INDENT " touch.orientation.calibration: interpolated");
+ LOGI(INDENT INDENT "touch.orientation.calibration: interpolated");
break;
default:
assert(false);
diff --git a/libs/utils/PollLoop.cpp b/libs/utils/PollLoop.cpp
index 6d3eeee..fe76cd0 100644
--- a/libs/utils/PollLoop.cpp
+++ b/libs/utils/PollLoop.cpp
@@ -119,7 +119,8 @@ int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) {
if (outData != NULL) *outData = pending.data;
return pending.ident;
}
-
+
+ // Wait for wakeAndLock() waiters to run then set mPolling to true.
mLock.lock();
while (mWaiters != 0) {
mResume.wait(mLock);
@@ -127,6 +128,7 @@ int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) {
mPolling = true;
mLock.unlock();
+ // Poll.
int32_t result;
size_t requestedCount = mRequestedFds.size();
@@ -168,6 +170,7 @@ int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) {
}
#endif
+ // Process the poll results.
mPendingCallbacks.clear();
mPendingFds.clear();
mPendingFdsPos = 0;
@@ -218,6 +221,7 @@ int32_t PollLoop::pollOnce(int timeoutMillis, int* outEvents, void** outData) {
}
Done:
+ // Set mPolling to false and wake up the wakeAndLock() waiters.
mLock.lock();
mPolling = false;
if (mWaiters != 0) {
@@ -357,11 +361,13 @@ ssize_t PollLoop::getRequestIndexLocked(int fd) {
void PollLoop::wakeAndLock() {
mLock.lock();
+
mWaiters += 1;
while (mPolling) {
wake();
mAwake.wait(mLock);
}
+
mWaiters -= 1;
if (mWaiters == 0) {
mResume.signal();