summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/PointerIcon.aidl (renamed from services/input/SpotController.cpp)30
-rw-r--r--core/java/android/view/PointerIcon.java435
-rw-r--r--core/jni/Android.mk1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_view_PointerIcon.cpp149
-rw-r--r--core/jni/android_view_PointerIcon.h80
-rw-r--r--core/res/res/drawable-mdpi/pointer_spot_anchor.pngbin0 -> 6817 bytes
-rw-r--r--core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml5
-rw-r--r--core/res/res/drawable-mdpi/pointer_spot_hover.pngbin0 -> 9669 bytes
-rw-r--r--core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml5
-rw-r--r--core/res/res/drawable-mdpi/pointer_spot_touch.pngbin0 -> 2880 bytes
-rw-r--r--core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml5
-rwxr-xr-xcore/res/res/values/attrs.xml18
-rw-r--r--core/res/res/values/styles.xml8
-rw-r--r--core/res/res/values/themes.xml2
-rw-r--r--include/ui/Input.h5
-rw-r--r--libs/ui/Input.cpp9
-rw-r--r--libs/utils/Looper.cpp3
-rw-r--r--services/input/Android.mk1
-rw-r--r--services/input/InputReader.cpp762
-rw-r--r--services/input/InputReader.h87
-rw-r--r--services/input/PointerController.cpp373
-rw-r--r--services/input/PointerController.h187
-rw-r--r--services/input/SpotController.h71
-rw-r--r--services/input/SpriteController.cpp162
-rw-r--r--services/input/SpriteController.h100
-rw-r--r--services/input/tests/InputReader_test.cpp14
-rw-r--r--services/java/com/android/server/wm/InputManager.java57
-rw-r--r--services/jni/com_android_server_InputManager.cpp126
29 files changed, 1990 insertions, 707 deletions
diff --git a/services/input/SpotController.cpp b/core/java/android/view/PointerIcon.aidl
index dffad81..b09340b 100644
--- a/services/input/SpotController.cpp
+++ b/core/java/android/view/PointerIcon.aidl
@@ -14,32 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "SpotController"
+package android.view;
-//#define LOG_NDEBUG 0
-
-// Log debug messages about spot updates
-#define DEBUG_SPOT_UPDATES 0
-
-#include "SpotController.h"
-
-#include <cutils/log.h>
-
-namespace android {
-
-// --- SpotController ---
-
-SpotController::SpotController(const sp<Looper>& looper,
- const sp<SpriteController>& spriteController) :
- mLooper(looper), mSpriteController(spriteController) {
- mHandler = new WeakMessageHandler(this);
-}
-
-SpotController::~SpotController() {
- mLooper->removeMessages(mHandler);
-}
-
-void SpotController:: handleMessage(const Message& message) {
-}
-
-} // namespace android
+parcelable PointerIcon;
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
new file mode 100644
index 0000000..bb7ed41
--- /dev/null
+++ b/core/java/android/view/PointerIcon.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.internal.util.XmlUtils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * <p>
+ * Pointer icons can be provided either by the system using system styles,
+ * or by applications using bitmaps or application resources.
+ * </p>
+ *
+ * @hide
+ */
+public final class PointerIcon implements Parcelable {
+ private static final String TAG = "PointerIcon";
+
+ /** Style constant: Custom icon with a user-supplied bitmap. */
+ public static final int STYLE_CUSTOM = -1;
+
+ /** Style constant: Null icon. It has no bitmap. */
+ public static final int STYLE_NULL = 0;
+
+ /** Style constant: Arrow icon. (Default mouse pointer) */
+ public static final int STYLE_ARROW = 1000;
+
+ /** {@hide} Style constant: Spot hover icon for touchpads. */
+ public static final int STYLE_SPOT_HOVER = 2000;
+
+ /** {@hide} Style constant: Spot touch icon for touchpads. */
+ public static final int STYLE_SPOT_TOUCH = 2001;
+
+ /** {@hide} Style constant: Spot anchor icon for touchpads. */
+ public static final int STYLE_SPOT_ANCHOR = 2002;
+
+ // OEM private styles should be defined starting at this range to avoid
+ // conflicts with any system styles that may be defined in the future.
+ private static final int STYLE_OEM_FIRST = 10000;
+
+ // The default pointer icon.
+ private static final int STYLE_DEFAULT = STYLE_ARROW;
+
+ private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL);
+
+ private final int mStyle;
+ private int mSystemIconResourceId;
+ private Bitmap mBitmap;
+ private float mHotSpotX;
+ private float mHotSpotY;
+
+ private PointerIcon(int style) {
+ mStyle = style;
+ }
+
+ /**
+ * Gets a special pointer icon that has no bitmap.
+ *
+ * @return The null pointer icon.
+ *
+ * @see #STYLE_NULL
+ */
+ public static PointerIcon getNullIcon() {
+ return gNullIcon;
+ }
+
+ /**
+ * Gets the default pointer icon.
+ *
+ * @param context The context.
+ * @return The default pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ */
+ public static PointerIcon getDefaultIcon(Context context) {
+ return getSystemIcon(context, STYLE_DEFAULT);
+ }
+
+ /**
+ * Gets a system pointer icon for the given style.
+ * If style is not recognized, returns the default pointer icon.
+ *
+ * @param context The context.
+ * @param style The pointer icon style.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ */
+ public static PointerIcon getSystemIcon(Context context, int style) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ if (style == STYLE_NULL) {
+ return gNullIcon;
+ }
+
+ int styleIndex = getSystemIconStyleIndex(style);
+ if (styleIndex == 0) {
+ styleIndex = getSystemIconStyleIndex(STYLE_DEFAULT);
+ }
+
+ TypedArray a = context.obtainStyledAttributes(null,
+ com.android.internal.R.styleable.Pointer,
+ com.android.internal.R.attr.pointerStyle, 0);
+ int resourceId = a.getResourceId(styleIndex, -1);
+ a.recycle();
+
+ if (resourceId == -1) {
+ Log.w(TAG, "Missing theme resources for pointer icon style " + style);
+ return style == STYLE_DEFAULT ? gNullIcon : getSystemIcon(context, STYLE_DEFAULT);
+ }
+
+ PointerIcon icon = new PointerIcon(style);
+ if ((resourceId & 0xff000000) == 0x01000000) {
+ icon.mSystemIconResourceId = resourceId;
+ } else {
+ icon.loadResource(context.getResources(), resourceId);
+ }
+ return icon;
+ }
+
+ /**
+ * Creates a custom pointer from the given bitmap and hotspot information.
+ *
+ * @param bitmap The bitmap for the icon.
+ * @param hotspotX The X offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getWidth()) range.
+ * @param hotspotY The Y offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getHeight()) range.
+ * @return A pointer icon for this bitmap.
+ *
+ * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+ * parameters are invalid.
+ */
+ public static PointerIcon createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ if (bitmap == null) {
+ throw new IllegalArgumentException("bitmap must not be null");
+ }
+ validateHotSpot(bitmap, hotSpotX, hotSpotY);
+
+ PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+ icon.mBitmap = bitmap;
+ icon.mHotSpotX = hotSpotX;
+ icon.mHotSpotY = hotSpotY;
+ return icon;
+ }
+
+ /**
+ * Loads a custom pointer icon from an XML resource.
+ * <p>
+ * The XML resource should have the following form:
+ * <code>
+ * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+ * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:bitmap="@drawable/my_pointer_bitmap"
+ * android:hotSpotX="24"
+ * android:hotSpotY="24" /&gt;
+ * </code>
+ * </p>
+ *
+ * @param resources The resources object.
+ * @param resourceId The resource id.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if resources is null.
+ * @throws Resources.NotFoundException if the resource was not found or the drawable
+ * linked in the resource was not found.
+ */
+ public static PointerIcon loadCustomIcon(Resources resources, int resourceId) {
+ if (resources == null) {
+ throw new IllegalArgumentException("resources must not be null");
+ }
+
+ PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+ icon.loadResource(resources, resourceId);
+ return icon;
+ }
+
+ /**
+ * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded.
+ * Returns a pointer icon (not necessarily the same instance) with the information filled in.
+ *
+ * @param context The context.
+ * @return The loaded pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ * @see #isLoaded()
+ * @hide
+ */
+ public PointerIcon load(Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ if (mSystemIconResourceId == 0 || mBitmap != null) {
+ return this;
+ }
+
+ PointerIcon result = new PointerIcon(mStyle);
+ result.mSystemIconResourceId = mSystemIconResourceId;
+ result.loadResource(context.getResources(), mSystemIconResourceId);
+ return result;
+ }
+
+ /**
+ * Returns true if the pointer icon style is {@link #STYLE_NULL}.
+ *
+ * @return True if the pointer icon style is {@link #STYLE_NULL}.
+ */
+ public boolean isNullIcon() {
+ return mStyle == STYLE_NULL;
+ }
+
+ /**
+ * Returns true if the pointer icon has been loaded and its bitmap and hotspot
+ * information are available.
+ *
+ * @return True if the pointer icon is loaded.
+ * @see #load(Context)
+ */
+ public boolean isLoaded() {
+ return mBitmap != null || mStyle == STYLE_NULL;
+ }
+
+ /**
+ * Gets the style of the pointer icon.
+ *
+ * @return The pointer icon style.
+ */
+ public int getStyle() {
+ return mStyle;
+ }
+
+ /**
+ * Gets the bitmap of the pointer icon.
+ *
+ * @return The pointer icon bitmap, or null if the style is {@link #STYLE_NULL}.
+ *
+ * @throws IllegalStateException if the bitmap is not loaded.
+ * @see #isLoaded()
+ * @see #load(Context)
+ */
+ public Bitmap getBitmap() {
+ throwIfIconIsNotLoaded();
+ return mBitmap;
+ }
+
+ /**
+ * Gets the X offset of the pointer icon hotspot.
+ *
+ * @return The hotspot X offset.
+ *
+ * @throws IllegalStateException if the bitmap is not loaded.
+ * @see #isLoaded()
+ * @see #load(Context)
+ */
+ public float getHotSpotX() {
+ throwIfIconIsNotLoaded();
+ return mHotSpotX;
+ }
+
+ /**
+ * Gets the Y offset of the pointer icon hotspot.
+ *
+ * @return The hotspot Y offset.
+ *
+ * @throws IllegalStateException if the bitmap is not loaded.
+ * @see #isLoaded()
+ * @see #load(Context)
+ */
+ public float getHotSpotY() {
+ throwIfIconIsNotLoaded();
+ return mHotSpotY;
+ }
+
+ private void throwIfIconIsNotLoaded() {
+ if (!isLoaded()) {
+ throw new IllegalStateException("The icon is not loaded.");
+ }
+ }
+
+ public static final Parcelable.Creator<PointerIcon> CREATOR
+ = new Parcelable.Creator<PointerIcon>() {
+ public PointerIcon createFromParcel(Parcel in) {
+ int style = in.readInt();
+ if (style == STYLE_NULL) {
+ return getNullIcon();
+ }
+
+ int systemIconResourceId = in.readInt();
+ if (systemIconResourceId != 0) {
+ PointerIcon icon = new PointerIcon(style);
+ icon.mSystemIconResourceId = systemIconResourceId;
+ return icon;
+ }
+
+ Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in);
+ float hotSpotX = in.readFloat();
+ float hotSpotY = in.readFloat();
+ return PointerIcon.createCustomIcon(bitmap, hotSpotX, hotSpotY);
+ }
+
+ public PointerIcon[] newArray(int size) {
+ return new PointerIcon[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mStyle);
+
+ if (mStyle != STYLE_NULL) {
+ out.writeInt(mSystemIconResourceId);
+ if (mSystemIconResourceId == 0) {
+ mBitmap.writeToParcel(out, flags);
+ out.writeFloat(mHotSpotX);
+ out.writeFloat(mHotSpotY);
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null || !(other instanceof PointerIcon)) {
+ return false;
+ }
+
+ PointerIcon otherIcon = (PointerIcon) other;
+ if (mStyle != otherIcon.mStyle
+ || mSystemIconResourceId != otherIcon.mSystemIconResourceId) {
+ return false;
+ }
+
+ if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap
+ || mHotSpotX != otherIcon.mHotSpotX
+ || mHotSpotY != otherIcon.mHotSpotY)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void loadResource(Resources resources, int resourceId) {
+ XmlResourceParser parser = resources.getXml(resourceId);
+ final int bitmapRes;
+ final float hotSpotX;
+ final float hotSpotY;
+ try {
+ XmlUtils.beginDocument(parser, "pointer-icon");
+
+ TypedArray a = resources.obtainAttributes(
+ parser, com.android.internal.R.styleable.PointerIcon);
+ bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
+ hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+ hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+ a.recycle();
+ } catch (Exception ex) {
+ throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
+ } finally {
+ parser.close();
+ }
+
+ if (bitmapRes == 0) {
+ throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
+ }
+
+ Drawable drawable = resources.getDrawable(bitmapRes);
+ if (!(drawable instanceof BitmapDrawable)) {
+ throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
+ + "refer to a bitmap drawable.");
+ }
+
+ // Set the properties now that we have successfully loaded the icon.
+ mBitmap = ((BitmapDrawable)drawable).getBitmap();
+ mHotSpotX = hotSpotX;
+ mHotSpotY = hotSpotY;
+ }
+
+ private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
+ throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
+ }
+ if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
+ throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
+ }
+ }
+
+ private static int getSystemIconStyleIndex(int style) {
+ switch (style) {
+ case STYLE_ARROW:
+ return com.android.internal.R.styleable.Pointer_pointerIconArrow;
+ case STYLE_SPOT_HOVER:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
+ case STYLE_SPOT_TOUCH:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
+ case STYLE_SPOT_ANCHOR:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index f8f8761..290f528 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -54,6 +54,7 @@ LOCAL_SRC_FILES:= \
android_view_KeyCharacterMap.cpp \
android_view_GLES20Canvas.cpp \
android_view_MotionEvent.cpp \
+ android_view_PointerIcon.cpp \
android_view_VelocityTracker.cpp \
android_text_AndroidCharacter.cpp \
android_text_AndroidBidi.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b628b9d..a4a229a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -170,6 +170,7 @@ extern int register_android_view_InputChannel(JNIEnv* env);
extern int register_android_view_InputQueue(JNIEnv* env);
extern int register_android_view_KeyEvent(JNIEnv* env);
extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_PointerIcon(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
extern int register_android_content_res_ObbScanner(JNIEnv* env);
extern int register_android_content_res_Configuration(JNIEnv* env);
@@ -1212,6 +1213,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_view_InputQueue),
REG_JNI(register_android_view_KeyEvent),
REG_JNI(register_android_view_MotionEvent),
+ REG_JNI(register_android_view_PointerIcon),
REG_JNI(register_android_view_VelocityTracker),
REG_JNI(register_android_content_res_ObbScanner),
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
new file mode 100644
index 0000000..091341a
--- /dev/null
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "PointerIcon-JNI"
+
+#include "JNIHelp.h"
+
+#include "android_view_PointerIcon.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <android/graphics/GraphicsJNI.h>
+
+namespace android {
+
+static struct {
+ jclass clazz;
+ jfieldID mStyle;
+ jfieldID mBitmap;
+ jfieldID mHotSpotX;
+ jfieldID mHotSpotY;
+ jmethodID getSystemIcon;
+ jmethodID load;
+} gPointerIconClassInfo;
+
+
+// --- Global Functions ---
+
+jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env, jobject contextObj, int32_t style) {
+ jobject pointerIconObj = env->CallStaticObjectMethod(gPointerIconClassInfo.clazz,
+ gPointerIconClassInfo.getSystemIcon, contextObj, style);
+ if (env->ExceptionCheck()) {
+ LOGW("An exception occurred while getting a pointer icon with style %d.", style);
+ LOGW_EX(env);
+ env->ExceptionClear();
+ return NULL;
+ }
+ return pointerIconObj;
+}
+
+status_t android_view_PointerIcon_load(JNIEnv* env, jobject pointerIconObj, jobject contextObj,
+ PointerIcon* outPointerIcon) {
+ outPointerIcon->reset();
+
+ if (!pointerIconObj) {
+ return OK;
+ }
+
+ jobject loadedPointerIconObj = env->CallObjectMethod(pointerIconObj,
+ gPointerIconClassInfo.load, contextObj);
+ if (env->ExceptionCheck() || !loadedPointerIconObj) {
+ LOGW("An exception occurred while loading a pointer icon.");
+ LOGW_EX(env);
+ env->ExceptionClear();
+ return UNKNOWN_ERROR;
+ }
+
+ outPointerIcon->style = env->GetIntField(loadedPointerIconObj,
+ gPointerIconClassInfo.mStyle);
+ outPointerIcon->hotSpotX = env->GetFloatField(loadedPointerIconObj,
+ gPointerIconClassInfo.mHotSpotX);
+ outPointerIcon->hotSpotY = env->GetFloatField(loadedPointerIconObj,
+ gPointerIconClassInfo.mHotSpotY);
+
+ jobject bitmapObj = env->GetObjectField(loadedPointerIconObj, gPointerIconClassInfo.mBitmap);
+ if (bitmapObj) {
+ SkBitmap* bitmap = GraphicsJNI::getNativeBitmap(env, bitmapObj);
+ if (bitmap) {
+ outPointerIcon->bitmap = *bitmap; // use a shared pixel ref
+ }
+ env->DeleteLocalRef(bitmapObj);
+ }
+
+ env->DeleteLocalRef(loadedPointerIconObj);
+ return OK;
+}
+
+status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env, jobject contextObj,
+ int32_t style, PointerIcon* outPointerIcon) {
+ jobject pointerIconObj = android_view_PointerIcon_getSystemIcon(env, contextObj, style);
+ if (!pointerIconObj) {
+ outPointerIcon->reset();
+ return UNKNOWN_ERROR;
+ }
+
+ status_t status = android_view_PointerIcon_load(env, pointerIconObj,
+ contextObj, outPointerIcon);
+ env->DeleteLocalRef(pointerIconObj);
+ return status;
+}
+
+
+// --- JNI Registration ---
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className); \
+ var = jclass(env->NewGlobalRef(var));
+
+#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+ var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+ var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_view_PointerIcon(JNIEnv* env) {
+ FIND_CLASS(gPointerIconClassInfo.clazz, "android/view/PointerIcon");
+
+ GET_FIELD_ID(gPointerIconClassInfo.mBitmap, gPointerIconClassInfo.clazz,
+ "mBitmap", "Landroid/graphics/Bitmap;");
+
+ GET_FIELD_ID(gPointerIconClassInfo.mStyle, gPointerIconClassInfo.clazz,
+ "mStyle", "I");
+
+ GET_FIELD_ID(gPointerIconClassInfo.mHotSpotX, gPointerIconClassInfo.clazz,
+ "mHotSpotX", "F");
+
+ GET_FIELD_ID(gPointerIconClassInfo.mHotSpotY, gPointerIconClassInfo.clazz,
+ "mHotSpotY", "F");
+
+ GET_STATIC_METHOD_ID(gPointerIconClassInfo.getSystemIcon, gPointerIconClassInfo.clazz,
+ "getSystemIcon", "(Landroid/content/Context;I)Landroid/view/PointerIcon;");
+
+ GET_METHOD_ID(gPointerIconClassInfo.load, gPointerIconClassInfo.clazz,
+ "load", "(Landroid/content/Context;)Landroid/view/PointerIcon;");
+
+ return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
new file mode 100644
index 0000000..3bfd645
--- /dev/null
+++ b/core/jni/android_view_PointerIcon.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_VIEW_POINTER_ICON_H
+#define _ANDROID_VIEW_POINTER_ICON_H
+
+#include "jni.h"
+
+#include <utils/Errors.h>
+#include <SkBitmap.h>
+
+namespace android {
+
+/* Pointer icon styles.
+ * Must match the definition in android.view.PointerIcon.
+ */
+enum {
+ POINTER_ICON_STYLE_CUSTOM = -1,
+ POINTER_ICON_STYLE_NULL = 0,
+ POINTER_ICON_STYLE_ARROW = 1000,
+ POINTER_ICON_STYLE_SPOT_HOVER = 2000,
+ POINTER_ICON_STYLE_SPOT_TOUCH = 2001,
+ POINTER_ICON_STYLE_SPOT_ANCHOR = 2002,
+};
+
+/*
+ * Describes a pointer icon.
+ */
+struct PointerIcon {
+ inline PointerIcon() {
+ reset();
+ }
+
+ int32_t style;
+ SkBitmap bitmap;
+ float hotSpotX;
+ float hotSpotY;
+
+ inline bool isNullIcon() {
+ return style == POINTER_ICON_STYLE_NULL;
+ }
+
+ inline void reset() {
+ style = POINTER_ICON_STYLE_NULL;
+ bitmap.reset();
+ hotSpotX = 0;
+ hotSpotY = 0;
+ }
+};
+
+/* Gets a system pointer icon with the specified style. */
+extern jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env,
+ jobject contextObj, int32_t style);
+
+/* Loads the bitmap associated with a pointer icon.
+ * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
+extern status_t android_view_PointerIcon_load(JNIEnv* env,
+ jobject pointerIconObj, jobject contextObj, PointerIcon* outPointerIcon);
+
+/* Loads the bitmap associated with a pointer icon by style.
+ * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
+extern status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env,
+ jobject contextObj, int32_t style, PointerIcon* outPointerIcon);
+
+} // namespace android
+
+#endif // _ANDROID_OS_POINTER_ICON_H
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor.png b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
new file mode 100644
index 0000000..d7aca36
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml
new file mode 100644
index 0000000..2222b8e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ android:bitmap="@drawable/pointer_spot_anchor"
+ android:hotSpotX="33"
+ android:hotSpotY="33" />
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover.png b/core/res/res/drawable-mdpi/pointer_spot_hover.png
new file mode 100644
index 0000000..5041aa3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml
new file mode 100644
index 0000000..dc62a69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ android:bitmap="@drawable/pointer_spot_hover"
+ android:hotSpotX="33"
+ android:hotSpotY="33" />
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch.png b/core/res/res/drawable-mdpi/pointer_spot_touch.png
new file mode 100644
index 0000000..64a42a1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml
new file mode 100644
index 0000000..4bffee6
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ android:bitmap="@drawable/pointer_spot_touch"
+ android:hotSpotX="24"
+ android:hotSpotY="24" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 819ce58..e8767d8 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -757,6 +757,13 @@
<!-- Default style for the Switch widget. -->
<attr name="switchStyle" format="reference" />
+ <!-- ============== -->
+ <!-- Pointer styles -->
+ <!-- ============== -->
+ <eat-comment />
+
+ <!-- Reference to the Pointer style -->
+ <attr name="pointerStyle" format="reference" />
</declare-styleable>
<!-- **************************************************************** -->
@@ -4921,6 +4928,17 @@
<attr name="switchPadding" format="dimension" />
</declare-styleable>
+ <declare-styleable name="Pointer">
+ <!-- Reference to a pointer icon drawable with STYLE_ARROW -->
+ <attr name="pointerIconArrow" format="reference" />
+ <!-- Reference to a pointer icon drawable with STYLE_SPOT_HOVER -->
+ <attr name="pointerIconSpotHover" format="reference" />
+ <!-- Reference to a pointer icon drawable with STYLE_SPOT_TOUCH -->
+ <attr name="pointerIconSpotTouch" format="reference" />
+ <!-- Reference to a pointer icon drawable with STYLE_SPOT_ANCHOR -->
+ <attr name="pointerIconSpotAnchor" format="reference" />
+ </declare-styleable>
+
<declare-styleable name="PointerIcon">
<!-- Drawable to use as the icon bitmap. -->
<attr name="bitmap" format="reference" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index bf4c6d7..b4042c0 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -2210,4 +2210,12 @@
<item name="android:borderLeft">0dip</item>
<item name="android:borderRight">0dip</item>
</style>
+
+ <!-- Pointer styles -->
+ <style name="Pointer">
+ <item name="android:pointerIconArrow">@android:drawable/pointer_arrow_icon</item>
+ <item name="android:pointerIconSpotHover">@android:drawable/pointer_spot_hover_icon</item>
+ <item name="android:pointerIconSpotTouch">@android:drawable/pointer_spot_touch_icon</item>
+ <item name="android:pointerIconSpotAnchor">@android:drawable/pointer_spot_anchor_icon</item>
+ </style>
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index b1e4f0f..0748b10 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -323,6 +323,8 @@
<item name="fastScrollOverlayPosition">floating</item>
<item name="fastScrollTextColor">@android:color/primary_text_dark</item>
+ <!-- Pointer style -->
+ <item name="pointerStyle">@android:style/Pointer</item>
</style>
<!-- Variant of the default (dark) theme with no title bar -->
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 0dc29c8..9b92c73 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -620,6 +620,11 @@ private:
// Oldest sample to consider when calculating the velocity.
static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms
+ // When the total duration of the window of samples being averaged is less
+ // than the window size, the resulting velocity is scaled to reduce the impact
+ // of overestimation in short traces.
+ static const nsecs_t MIN_WINDOW = 100 * 1000000; // 100 ms
+
// The minimum duration between samples when estimating velocity.
static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index bbe579e..a95f432 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -832,6 +832,7 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const
const Position& oldestPosition =
oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)];
nsecs_t lastDuration = 0;
+
while (numTouches-- > 1) {
if (++index == HISTORY_SIZE) {
index = 0;
@@ -858,6 +859,14 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const
// Make sure we used at least one sample.
if (samplesUsed != 0) {
+ // Scale the velocity linearly if the window of samples is small.
+ nsecs_t totalDuration = newestMovement.eventTime - oldestMovement.eventTime;
+ if (totalDuration < MIN_WINDOW) {
+ float scale = float(totalDuration) / float(MIN_WINDOW);
+ accumVx *= scale;
+ accumVy *= scale;
+ }
+
*outVx = accumVx;
*outVy = accumVy;
return true;
diff --git a/libs/utils/Looper.cpp b/libs/utils/Looper.cpp
index d5dd126..b54fb9d 100644
--- a/libs/utils/Looper.cpp
+++ b/libs/utils/Looper.cpp
@@ -662,7 +662,8 @@ void Looper::wakeAndLock() {
#endif
void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {
- sendMessageAtTime(LLONG_MIN, handler, message);
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ sendMessageAtTime(now, handler, message);
}
void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,
diff --git a/services/input/Android.mk b/services/input/Android.mk
index f9f8623f..836c081 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -23,7 +23,6 @@ LOCAL_SRC_FILES:= \
InputReader.cpp \
InputWindow.cpp \
PointerController.cpp \
- SpotController.cpp \
SpriteController.cpp
LOCAL_SHARED_LIBRARIES := \
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 6db445e..1bc1bd1 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -36,7 +36,6 @@
// Log debug messages about gesture detection.
#define DEBUG_GESTURES 0
-
#include "InputReader.h"
#include <cutils/log.h>
@@ -71,23 +70,47 @@ static const float DRAG_MIN_SWITCH_SPEED = 50.0f; // pixels per second
// Tap gesture delay time.
// The time between down and up must be less than this to be considered a tap.
-static const nsecs_t TAP_INTERVAL = 100 * 1000000; // 100 ms
+static const nsecs_t TAP_INTERVAL = 150 * 1000000; // 150 ms
// The distance in pixels that the pointer is allowed to move from initial down
// to up and still be called a tap.
static const float TAP_SLOP = 5.0f; // 5 pixels
-// The transition from INDETERMINATE_MULTITOUCH to SWIPE or FREEFORM gesture mode is made when
-// all of the pointers have traveled this number of pixels from the start point.
-static const float MULTITOUCH_MIN_TRAVEL = 5.0f;
+// Time after the first touch points go down to settle on an initial centroid.
+// This is intended to be enough time to handle cases where the user puts down two
+// fingers at almost but not quite exactly the same time.
+static const nsecs_t MULTITOUCH_SETTLE_INTERVAL = 100 * 1000000; // 100ms
+
+// The transition from PRESS to SWIPE or FREEFORM gesture mode is made when
+// both of the pointers are moving at least this fast.
+static const float MULTITOUCH_MIN_SPEED = 150.0f; // pixels per second
-// The transition from INDETERMINATE_MULTITOUCH to SWIPE gesture mode can only occur when the
+// The transition from PRESS to SWIPE gesture mode can only occur when the
// cosine of the angle between the two vectors is greater than or equal to than this value
// which indicates that the vectors are oriented in the same direction.
// When the vectors are oriented in the exactly same direction, the cosine is 1.0.
// (In exactly opposite directions, the cosine is -1.0.)
static const float SWIPE_TRANSITION_ANGLE_COSINE = 0.5f; // cosine of 45 degrees
+// The transition from PRESS to SWIPE gesture mode can only occur when the
+// fingers are no more than this far apart relative to the diagonal size of
+// the touch pad. For example, a ratio of 0.5 means that the fingers must be
+// no more than half the diagonal size of the touch pad apart.
+static const float SWIPE_MAX_WIDTH_RATIO = 0.333f; // 1/3
+
+// The gesture movement speed factor relative to the size of the display.
+// Movement speed applies when the fingers are moving in the same direction.
+// Without acceleration, a full swipe of the touch pad diagonal in movement mode
+// will cover this portion of the display diagonal.
+static const float GESTURE_MOVEMENT_SPEED_RATIO = 0.8f;
+
+// The gesture zoom speed factor relative to the size of the display.
+// Zoom speed applies when the fingers are mostly moving relative to each other
+// to execute a scale gesture or similar.
+// Without acceleration, a full swipe of the touch pad diagonal in zoom mode
+// will cover this portion of the display diagonal.
+static const float GESTURE_ZOOM_SPEED_RATIO = 0.3f;
+
// --- Static Functions ---
@@ -112,14 +135,8 @@ inline static float avg(float x, float y) {
return (x + y) / 2;
}
-inline static float pythag(float x, float y) {
- return sqrtf(x * x + y * y);
-}
-
-inline static int32_t distanceSquared(int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
- int32_t dx = x1 - x2;
- int32_t dy = y1 - y2;
- return dx * dx + dy * dy;
+inline static float distance(float x1, float y1, float x2, float y2) {
+ return hypotf(x1 - x2, y1 - y2);
}
inline static int32_t signExtendNybble(int32_t value) {
@@ -224,6 +241,33 @@ static int32_t calculateEdgeFlagsUsingPointerBounds(
return edgeFlags;
}
+static void clampPositionUsingPointerBounds(
+ const sp<PointerControllerInterface>& pointerController, float* x, float* y) {
+ float minX, minY, maxX, maxY;
+ if (pointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
+ if (*x < minX) {
+ *x = minX;
+ } else if (*x > maxX) {
+ *x = maxX;
+ }
+ if (*y < minY) {
+ *y = minY;
+ } else if (*y > maxY) {
+ *y = maxY;
+ }
+ }
+}
+
+static float calculateCommonVector(float a, float b) {
+ if (a > 0 && b > 0) {
+ return a < b ? a : b;
+ } else if (a < 0 && b < 0) {
+ return a > b ? a : b;
+ } else {
+ return 0;
+ }
+}
+
// --- InputReader ---
@@ -1553,10 +1597,32 @@ void CursorInputMapper::sync(nsecs_t when) {
motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE;
+ if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) {
+ vscroll = mAccumulator.relWheel;
+ } else {
+ vscroll = 0;
+ }
+ if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) {
+ hscroll = mAccumulator.relHWheel;
+ } else {
+ hscroll = 0;
+ }
+
if (mPointerController != NULL) {
- mPointerController->move(deltaX, deltaY);
- if (buttonsChanged) {
- mPointerController->setButtonState(mLocked.buttonState);
+ if (deltaX != 0 || deltaY != 0 || vscroll != 0 || hscroll != 0
+ || buttonsChanged) {
+ mPointerController->setPresentation(
+ PointerControllerInterface::PRESENTATION_POINTER);
+
+ if (deltaX != 0 || deltaY != 0) {
+ mPointerController->move(deltaX, deltaY);
+ }
+
+ if (buttonsChanged) {
+ mPointerController->setButtonState(mLocked.buttonState);
+ }
+
+ mPointerController->unfade();
}
float x, y;
@@ -1574,20 +1640,6 @@ void CursorInputMapper::sync(nsecs_t when) {
}
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
-
- if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) {
- vscroll = mAccumulator.relWheel;
- } else {
- vscroll = 0;
- }
- if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) {
- hscroll = mAccumulator.relHWheel;
- } else {
- hscroll = 0;
- }
- if (hscroll != 0 || vscroll != 0) {
- mPointerController->unfade();
- }
} // release lock
// Moving an external trackball or mouse should wake the device.
@@ -1751,8 +1803,8 @@ void TouchInputMapper::dump(String8& dump) {
mLocked.pointerGestureXZoomScale);
dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n",
mLocked.pointerGestureYZoomScale);
- dump.appendFormat(INDENT4 "MaxSwipeWidthSquared: %d\n",
- mLocked.pointerGestureMaxSwipeWidthSquared);
+ dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n",
+ mLocked.pointerGestureMaxSwipeWidth);
}
} // release lock
}
@@ -1825,6 +1877,10 @@ void TouchInputMapper::configureParameters() {
mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime();
+ // TODO: Make this configurable.
+ //mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER;
+ mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS;
+
if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X)
|| getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
// The device is a cursor device with a touch pad attached.
@@ -1983,7 +2039,7 @@ bool TouchInputMapper::configureSurfaceLocked() {
mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale);
// Size of diagonal axis.
- float diagonalSize = pythag(width, height);
+ float diagonalSize = hypotf(width, height);
// TouchMajor and TouchMinor factors.
if (mCalibration.touchSizeCalibration != Calibration::TOUCH_SIZE_CALIBRATION_NONE) {
@@ -2178,30 +2234,39 @@ bool TouchInputMapper::configureSurfaceLocked() {
if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
int32_t rawWidth = mRawAxes.x.maxValue - mRawAxes.x.minValue + 1;
int32_t rawHeight = mRawAxes.y.maxValue - mRawAxes.y.minValue + 1;
+ float rawDiagonal = hypotf(rawWidth, rawHeight);
+ float displayDiagonal = hypotf(mLocked.associatedDisplayWidth,
+ mLocked.associatedDisplayHeight);
- // Scale movements such that one whole swipe of the touch pad covers a portion
- // of the display along whichever axis of the touch pad is longer.
+ // Scale movements such that one whole swipe of the touch pad covers a
+ // given area relative to the diagonal size of the display.
// Assume that the touch pad has a square aspect ratio such that movements in
// X and Y of the same number of raw units cover the same physical distance.
const float scaleFactor = 0.8f;
- mLocked.pointerGestureXMovementScale = rawWidth > rawHeight
- ? scaleFactor * float(mLocked.associatedDisplayWidth) / rawWidth
- : scaleFactor * float(mLocked.associatedDisplayHeight) / rawHeight;
+ mLocked.pointerGestureXMovementScale = GESTURE_MOVEMENT_SPEED_RATIO
+ * displayDiagonal / rawDiagonal;
mLocked.pointerGestureYMovementScale = mLocked.pointerGestureXMovementScale;
// Scale zooms to cover a smaller range of the display than movements do.
// This value determines the area around the pointer that is affected by freeform
// pointer gestures.
- mLocked.pointerGestureXZoomScale = mLocked.pointerGestureXMovementScale * 0.4f;
- mLocked.pointerGestureYZoomScale = mLocked.pointerGestureYMovementScale * 0.4f;
+ mLocked.pointerGestureXZoomScale = GESTURE_ZOOM_SPEED_RATIO
+ * displayDiagonal / rawDiagonal;
+ mLocked.pointerGestureYZoomScale = mLocked.pointerGestureXZoomScale;
+
+ // Max width between pointers to detect a swipe gesture is more than some fraction
+ // of the diagonal axis of the touch pad. Touches that are wider than this are
+ // translated into freeform gestures.
+ mLocked.pointerGestureMaxSwipeWidth = SWIPE_MAX_WIDTH_RATIO * rawDiagonal;
- // Max width between pointers to detect a swipe gesture is 3/4 of the short
- // axis of the touch pad. Touches that are wider than this are translated
- // into freeform gestures.
- mLocked.pointerGestureMaxSwipeWidthSquared = min(rawWidth, rawHeight) * 3 / 4;
- mLocked.pointerGestureMaxSwipeWidthSquared *=
- mLocked.pointerGestureMaxSwipeWidthSquared;
+ // Reset the current pointer gesture.
+ mPointerGesture.reset();
+
+ // Remove any current spots.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->clearSpots();
+ }
}
}
@@ -2628,6 +2693,11 @@ void TouchInputMapper::reset() {
{ // acquire lock
AutoMutex _l(mLock);
initializeLocked();
+
+ if (mPointerController != NULL
+ && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->clearSpots();
+ }
} // release lock
InputMapper::reset();
@@ -3070,7 +3140,7 @@ void TouchInputMapper::prepareTouches(int32_t* outEdgeFlags,
int32_t c2 = signExtendNybble(in.orientation & 0x0f);
if (c1 != 0 || c2 != 0) {
orientation = atan2f(c1, c2) * 0.5f;
- float scale = 1.0f + pythag(c1, c2) / 16.0f;
+ float scale = 1.0f + hypotf(c1, c2) / 16.0f;
touchMajor *= scale;
touchMinor /= scale;
toolMajor *= scale;
@@ -3155,22 +3225,35 @@ void TouchInputMapper::prepareTouches(int32_t* outEdgeFlags,
}
void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags) {
+ // Switch pointer presentation.
+ mPointerController->setPresentation(
+ mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+ ? PointerControllerInterface::PRESENTATION_SPOT
+ : PointerControllerInterface::PRESENTATION_POINTER);
+
// Update current gesture coordinates.
bool cancelPreviousGesture, finishPreviousGesture;
preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture);
+ // Show the pointer if needed.
+ if (mPointerGesture.currentGestureMode != PointerGesture::NEUTRAL
+ && mPointerGesture.currentGestureMode != PointerGesture::QUIET) {
+ mPointerController->unfade();
+ }
+
// Send events!
uint32_t metaState = getContext()->getGlobalMetaState();
// Update last coordinates of pointers that have moved so that we observe the new
// pointer positions at the same time as other pointers that have just gone up.
bool down = mPointerGesture.currentGestureMode == PointerGesture::CLICK_OR_DRAG
+ || mPointerGesture.currentGestureMode == PointerGesture::PRESS
|| mPointerGesture.currentGestureMode == PointerGesture::SWIPE
|| mPointerGesture.currentGestureMode == PointerGesture::FREEFORM;
bool moveNeeded = false;
if (down && !cancelPreviousGesture && !finishPreviousGesture
- && mPointerGesture.lastGesturePointerCount != 0
- && mPointerGesture.currentGesturePointerCount != 0) {
+ && !mPointerGesture.lastGestureIdBits.isEmpty()
+ && !mPointerGesture.currentGestureIdBits.isEmpty()) {
BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value
& mPointerGesture.lastGestureIdBits.value);
moveNeeded = updateMovedPointerCoords(
@@ -3284,11 +3367,8 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag
// Update state.
mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode;
if (!down) {
- mPointerGesture.lastGesturePointerCount = 0;
mPointerGesture.lastGestureIdBits.clear();
} else {
- uint32_t currentGesturePointerCount = mPointerGesture.currentGesturePointerCount;
- mPointerGesture.lastGesturePointerCount = currentGesturePointerCount;
mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits;
for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) {
uint32_t id = idBits.firstMarkedBit();
@@ -3328,77 +3408,51 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
// Choose an arbitrary pointer that just went down, if there is one.
// Otherwise choose an arbitrary remaining pointer.
// This guarantees we always have an active touch id when there is at least one pointer.
- // We always switch to the newest pointer down because that's usually where the user's
- // attention is focused.
- int32_t activeTouchId;
- BitSet32 downTouchIdBits(mCurrentTouch.idBits.value & ~mLastTouch.idBits.value);
- if (!downTouchIdBits.isEmpty()) {
- activeTouchId = mPointerGesture.activeTouchId = downTouchIdBits.firstMarkedBit();
- } else {
- activeTouchId = mPointerGesture.activeTouchId;
- if (activeTouchId < 0 || !mCurrentTouch.idBits.hasBit(activeTouchId)) {
- if (!mCurrentTouch.idBits.isEmpty()) {
- activeTouchId = mPointerGesture.activeTouchId =
- mCurrentTouch.idBits.firstMarkedBit();
- } else {
- activeTouchId = mPointerGesture.activeTouchId = -1;
- }
+ // We keep the same active touch id for as long as possible.
+ bool activeTouchChanged = false;
+ int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
+ int32_t activeTouchId = lastActiveTouchId;
+ if (activeTouchId < 0) {
+ if (!mCurrentTouch.idBits.isEmpty()) {
+ activeTouchChanged = true;
+ activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit();
+ mPointerGesture.firstTouchTime = when;
+ }
+ } else if (!mCurrentTouch.idBits.hasBit(activeTouchId)) {
+ activeTouchChanged = true;
+ if (!mCurrentTouch.idBits.isEmpty()) {
+ activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit();
+ } else {
+ activeTouchId = mPointerGesture.activeTouchId = -1;
}
}
- // Update the touch origin data to track where each finger originally went down.
- if (mCurrentTouch.pointerCount == 0 || mPointerGesture.touchOrigin.pointerCount == 0) {
- // Fast path when all fingers have gone up or down.
- mPointerGesture.touchOrigin.copyFrom(mCurrentTouch);
+ // Determine whether we are in quiet time.
+ bool isQuietTime = false;
+ if (activeTouchId < 0) {
+ mPointerGesture.resetQuietTime();
} else {
- // Slow path when only some fingers have gone up or down.
- for (BitSet32 idBits(mPointerGesture.touchOrigin.idBits.value
- & ~mCurrentTouch.idBits.value); !idBits.isEmpty(); ) {
- uint32_t id = idBits.firstMarkedBit();
- idBits.clearBit(id);
- mPointerGesture.touchOrigin.idBits.clearBit(id);
- uint32_t index = mPointerGesture.touchOrigin.idToIndex[id];
- uint32_t count = --mPointerGesture.touchOrigin.pointerCount;
- while (index < count) {
- mPointerGesture.touchOrigin.pointers[index] =
- mPointerGesture.touchOrigin.pointers[index + 1];
- uint32_t movedId = mPointerGesture.touchOrigin.pointers[index].id;
- mPointerGesture.touchOrigin.idToIndex[movedId] = index;
- index += 1;
+ isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL;
+ if (!isQuietTime) {
+ if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS
+ || mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
+ && mCurrentTouch.pointerCount < 2) {
+ // Enter quiet time when exiting swipe or freeform state.
+ // This is to prevent accidentally entering the hover state and flinging the
+ // pointer when finishing a swipe and there is still one pointer left onscreen.
+ isQuietTime = true;
+ } else if (mPointerGesture.lastGestureMode == PointerGesture::CLICK_OR_DRAG
+ && mCurrentTouch.pointerCount >= 2
+ && !isPointerDown(mCurrentTouch.buttonState)) {
+ // Enter quiet time when releasing the button and there are still two or more
+ // fingers down. This may indicate that one finger was used to press the button
+ // but it has not gone up yet.
+ isQuietTime = true;
+ }
+ if (isQuietTime) {
+ mPointerGesture.quietTime = when;
}
- }
- for (BitSet32 idBits(mCurrentTouch.idBits.value
- & ~mPointerGesture.touchOrigin.idBits.value); !idBits.isEmpty(); ) {
- uint32_t id = idBits.firstMarkedBit();
- idBits.clearBit(id);
- mPointerGesture.touchOrigin.idBits.markBit(id);
- uint32_t index = mPointerGesture.touchOrigin.pointerCount++;
- mPointerGesture.touchOrigin.pointers[index] =
- mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]];
- mPointerGesture.touchOrigin.idToIndex[id] = index;
- }
- }
-
- // Determine whether we are in quiet time.
- bool isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL;
- if (!isQuietTime) {
- if ((mPointerGesture.lastGestureMode == PointerGesture::SWIPE
- || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
- && mCurrentTouch.pointerCount < 2) {
- // Enter quiet time when exiting swipe or freeform state.
- // This is to prevent accidentally entering the hover state and flinging the
- // pointer when finishing a swipe and there is still one pointer left onscreen.
- isQuietTime = true;
- } else if (mPointerGesture.lastGestureMode == PointerGesture::CLICK_OR_DRAG
- && mCurrentTouch.pointerCount >= 2
- && !isPointerDown(mCurrentTouch.buttonState)) {
- // Enter quiet time when releasing the button and there are still two or more
- // fingers down. This may indicate that one finger was used to press the button
- // but it has not gone up yet.
- isQuietTime = true;
- }
- if (isQuietTime) {
- mPointerGesture.quietTime = when;
}
}
@@ -3413,10 +3467,17 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
mPointerGesture.activeGestureId = -1;
mPointerGesture.currentGestureMode = PointerGesture::QUIET;
- mPointerGesture.currentGesturePointerCount = 0;
mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerController->setButtonState(0);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+ mPointerGesture.spotIdBits.clear();
+ moveSpotsLocked();
+ }
} else if (isPointerDown(mCurrentTouch.buttonState)) {
- // Case 2: Button is pressed. (DRAG)
+ // Case 2: Button is pressed. (CLICK_OR_DRAG)
// The pointer follows the active touch point.
// Emit DOWN, MOVE, UP events at the pointer location.
//
@@ -3449,7 +3510,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
uint32_t id = mCurrentTouch.pointers[i].id;
float vx, vy;
if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) {
- float speed = pythag(vx, vy);
+ float speed = hypotf(vx, vy);
if (speed > bestSpeed) {
bestId = id;
bestSpeed = speed;
@@ -3458,6 +3519,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
}
if (bestId >= 0 && bestId != activeTouchId) {
mPointerGesture.activeTouchId = activeTouchId = bestId;
+ activeTouchChanged = true;
#if DEBUG_GESTURES
LOGD("Gestures: CLICK_OR_DRAG switched pointers, "
"bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed);
@@ -3474,6 +3536,10 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
* mLocked.pointerGestureXMovementScale;
float deltaY = (currentPointer.y - lastPointer.y)
* mLocked.pointerGestureYMovementScale;
+
+ // Move the pointer using a relative motion.
+ // When using spots, the click will occur at the position of the anchor
+ // spot and all other spots will move there.
mPointerController->move(deltaX, deltaY);
}
}
@@ -3482,7 +3548,6 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
mPointerController->getPosition(&x, &y);
mPointerGesture.currentGestureMode = PointerGesture::CLICK_OR_DRAG;
- mPointerGesture.currentGesturePointerCount = 1;
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
@@ -3490,26 +3555,49 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ if (activeTouchId >= 0) {
+ // Collapse all spots into one point at the pointer location.
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_DRAG;
+ mPointerGesture.spotIdBits.clear();
+ for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
+ uint32_t id = mCurrentTouch.pointers[i].id;
+ mPointerGesture.spotIdBits.markBit(id);
+ mPointerGesture.spotIdToIndex[id] = i;
+ mPointerGesture.spotCoords[i] = mPointerGesture.currentGestureCoords[0];
+ }
+ } else {
+ // No fingers. Generate a spot at the pointer location so the
+ // anchor appears to be pressed.
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_CLICK;
+ mPointerGesture.spotIdBits.clear();
+ mPointerGesture.spotIdBits.markBit(0);
+ mPointerGesture.spotIdToIndex[0] = 0;
+ mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+ }
+ moveSpotsLocked();
+ }
} else if (mCurrentTouch.pointerCount == 0) {
// Case 3. No fingers down and button is not pressed. (NEUTRAL)
*outFinishPreviousGesture = true;
- // Watch for taps coming out of HOVER or INDETERMINATE_MULTITOUCH mode.
+ // Watch for taps coming out of HOVER mode.
bool tapped = false;
if (mPointerGesture.lastGestureMode == PointerGesture::HOVER
- || mPointerGesture.lastGestureMode
- == PointerGesture::INDETERMINATE_MULTITOUCH) {
+ && mLastTouch.pointerCount == 1) {
if (when <= mPointerGesture.tapTime + TAP_INTERVAL) {
float x, y;
mPointerController->getPosition(&x, &y);
- if (fabs(x - mPointerGesture.initialPointerX) <= TAP_SLOP
- && fabs(y - mPointerGesture.initialPointerY) <= TAP_SLOP) {
+ if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP
+ && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) {
#if DEBUG_GESTURES
LOGD("Gestures: TAP");
#endif
mPointerGesture.activeGestureId = 0;
mPointerGesture.currentGestureMode = PointerGesture::TAP;
- mPointerGesture.currentGesturePointerCount = 1;
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(
mPointerGesture.activeGestureId);
@@ -3517,17 +3605,30 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
mPointerGesture.activeGestureId] = 0;
mPointerGesture.currentGestureCoords[0].clear();
mPointerGesture.currentGestureCoords[0].setAxisValue(
- AMOTION_EVENT_AXIS_X, mPointerGesture.initialPointerX);
+ AMOTION_EVENT_AXIS_X, mPointerGesture.tapX);
mPointerGesture.currentGestureCoords[0].setAxisValue(
- AMOTION_EVENT_AXIS_Y, mPointerGesture.initialPointerY);
+ AMOTION_EVENT_AXIS_Y, mPointerGesture.tapY);
mPointerGesture.currentGestureCoords[0].setAxisValue(
AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+ mPointerController->setButtonState(0);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_TAP;
+ mPointerGesture.spotIdBits.clear();
+ mPointerGesture.spotIdBits.markBit(lastActiveTouchId);
+ mPointerGesture.spotIdToIndex[lastActiveTouchId] = 0;
+ mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+ moveSpotsLocked();
+ }
+
tapped = true;
} else {
#if DEBUG_GESTURES
LOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f",
- x - mPointerGesture.initialPointerX,
- y - mPointerGesture.initialPointerY);
+ x - mPointerGesture.tapX,
+ y - mPointerGesture.tapY);
#endif
}
} else {
@@ -3537,14 +3638,22 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
#endif
}
}
+
if (!tapped) {
#if DEBUG_GESTURES
LOGD("Gestures: NEUTRAL");
#endif
mPointerGesture.activeGestureId = -1;
mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
- mPointerGesture.currentGesturePointerCount = 0;
mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerController->setButtonState(0);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+ mPointerGesture.spotIdBits.clear();
+ moveSpotsLocked();
+ }
}
} else if (mCurrentTouch.pointerCount == 1) {
// Case 4. Exactly one finger down, button is not pressed. (HOVER)
@@ -3565,6 +3674,9 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
* mLocked.pointerGestureXMovementScale;
float deltaY = (currentPointer.y - lastPointer.y)
* mLocked.pointerGestureYMovementScale;
+
+ // Move the pointer using a relative motion.
+ // When using spots, the hover will occur at the position of the anchor spot.
mPointerController->move(deltaX, deltaY);
}
@@ -3575,7 +3687,6 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
mPointerController->getPosition(&x, &y);
mPointerGesture.currentGestureMode = PointerGesture::HOVER;
- mPointerGesture.currentGesturePointerCount = 1;
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
@@ -3586,159 +3697,268 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
mPointerGesture.tapTime = when;
- mPointerGesture.initialPointerX = x;
- mPointerGesture.initialPointerY = y;
+ mPointerGesture.tapX = x;
+ mPointerGesture.tapY = y;
+ }
+
+ mPointerController->setButtonState(0);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_HOVER;
+ mPointerGesture.spotIdBits.clear();
+ mPointerGesture.spotIdBits.markBit(activeTouchId);
+ mPointerGesture.spotIdToIndex[activeTouchId] = 0;
+ mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+ moveSpotsLocked();
}
} else {
- // Case 5. At least two fingers down, button is not pressed. (SWIPE or FREEFORM
- // or INDETERMINATE_MULTITOUCH)
- // Initially we watch and wait for something interesting to happen so as to
- // avoid making a spurious guess as to the nature of the gesture. For example,
- // the fingers may be in transition to some other state such as pressing or
- // releasing the button or we may be performing a two finger tap.
+ // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
+ // We need to provide feedback for each finger that goes down so we cannot wait
+ // for the fingers to move before deciding what to do.
+ //
+ // The ambiguous case is deciding what to do when there are two fingers down but they
+ // have not moved enough to determine whether they are part of a drag or part of a
+ // freeform gesture, or just a press or long-press at the pointer location.
+ //
+ // When there are two fingers we start with the PRESS hypothesis and we generate a
+ // down at the pointer location.
//
- // Fix the centroid of the figure when the gesture actually starts.
- // We do not recalculate the centroid at any other time during the gesture because
- // it would affect the relationship of the touch points relative to the pointer location.
+ // When the two fingers move enough or when additional fingers are added, we make
+ // a decision to transition into SWIPE or FREEFORM mode accordingly.
LOG_ASSERT(activeTouchId >= 0);
- uint32_t currentTouchPointerCount = mCurrentTouch.pointerCount;
- if (currentTouchPointerCount > MAX_POINTERS) {
- currentTouchPointerCount = MAX_POINTERS;
- }
-
- if (mPointerGesture.lastGestureMode != PointerGesture::INDETERMINATE_MULTITOUCH
+ bool needReference = false;
+ bool settled = when >= mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL;
+ if (mPointerGesture.lastGestureMode != PointerGesture::PRESS
&& mPointerGesture.lastGestureMode != PointerGesture::SWIPE
&& mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
- mPointerGesture.currentGestureMode = PointerGesture::INDETERMINATE_MULTITOUCH;
-
*outFinishPreviousGesture = true;
- mPointerGesture.activeGestureId = -1;
-
- // Remember the initial pointer location.
- // Everything we do will be relative to this location.
- mPointerController->getPosition(&mPointerGesture.initialPointerX,
- &mPointerGesture.initialPointerY);
+ mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+ mPointerGesture.activeGestureId = 0;
- // Track taps.
- if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
- mPointerGesture.tapTime = when;
+ if (settled && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+ && mLastTouch.idBits.hasBit(mPointerGesture.activeTouchId)) {
+ // The spot is already visible and has settled, use it as the reference point
+ // for the gesture. Other spots will be positioned relative to this one.
+#if DEBUG_GESTURES
+ LOGD("Gestures: Using active spot as reference for MULTITOUCH, "
+ "settle time expired %0.3fms ago",
+ (when - mPointerGesture.firstTouchTime - MULTITOUCH_SETTLE_INTERVAL)
+ * 0.000001f);
+#endif
+ const PointerData& d = mLastTouch.pointers[mLastTouch.idToIndex[
+ mPointerGesture.activeTouchId]];
+ mPointerGesture.referenceTouchX = d.x;
+ mPointerGesture.referenceTouchY = d.y;
+ const PointerCoords& c = mPointerGesture.spotCoords[mPointerGesture.spotIdToIndex[
+ mPointerGesture.activeTouchId]];
+ mPointerGesture.referenceGestureX = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+ mPointerGesture.referenceGestureY = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+ } else {
+#if DEBUG_GESTURES
+ LOGD("Gestures: Using centroid as reference for MULTITOUCH, "
+ "settle time remaining %0.3fms",
+ (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when)
+ * 0.000001f);
+#endif
+ needReference = true;
}
-
- // Reset the touch origin to be relative to exactly where the fingers are now
- // in case they have moved some distance away as part of a previous gesture.
- // We want to know how far the fingers have traveled since we started considering
- // a multitouch gesture.
- mPointerGesture.touchOrigin.copyFrom(mCurrentTouch);
+ } else if (!settled && mCurrentTouch.pointerCount > mLastTouch.pointerCount) {
+ // Additional pointers have gone down but not yet settled.
+ // Reset the gesture.
+#if DEBUG_GESTURES
+ LOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, "
+ "settle time remaining %0.3fms",
+ (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when)
+ * 0.000001f);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+ mPointerGesture.activeGestureId = 0;
} else {
+ // Continue previous gesture.
mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
}
- if (mPointerGesture.currentGestureMode == PointerGesture::INDETERMINATE_MULTITOUCH) {
- // Wait for the pointers to start moving before doing anything.
- bool decideNow = true;
- for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
- const PointerData& current = mCurrentTouch.pointers[i];
- const PointerData& origin = mPointerGesture.touchOrigin.pointers[
- mPointerGesture.touchOrigin.idToIndex[current.id]];
- float distance = pythag(
- (current.x - origin.x) * mLocked.pointerGestureXZoomScale,
- (current.y - origin.y) * mLocked.pointerGestureYZoomScale);
- if (distance < MULTITOUCH_MIN_TRAVEL) {
- decideNow = false;
- break;
- }
- }
+ if (needReference) {
+ // Use the centroid and pointer location as the reference points for the gesture.
+ mCurrentTouch.getCentroid(&mPointerGesture.referenceTouchX,
+ &mPointerGesture.referenceTouchY);
+ mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+ &mPointerGesture.referenceGestureY);
+ }
- if (decideNow) {
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+ float d;
+ if (mCurrentTouch.pointerCount > 2) {
+ // There are more than two pointers, switch to FREEFORM.
+#if DEBUG_GESTURES
+ LOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+ mCurrentTouch.pointerCount);
+#endif
+ *outCancelPreviousGesture = true;
mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
- if (currentTouchPointerCount == 2
- && distanceSquared(
- mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y,
- mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y)
- <= mLocked.pointerGestureMaxSwipeWidthSquared) {
- const PointerData& current1 = mCurrentTouch.pointers[0];
- const PointerData& current2 = mCurrentTouch.pointers[1];
- const PointerData& origin1 = mPointerGesture.touchOrigin.pointers[
- mPointerGesture.touchOrigin.idToIndex[current1.id]];
- const PointerData& origin2 = mPointerGesture.touchOrigin.pointers[
- mPointerGesture.touchOrigin.idToIndex[current2.id]];
-
- float x1 = (current1.x - origin1.x) * mLocked.pointerGestureXZoomScale;
- float y1 = (current1.y - origin1.y) * mLocked.pointerGestureYZoomScale;
- float x2 = (current2.x - origin2.x) * mLocked.pointerGestureXZoomScale;
- float y2 = (current2.y - origin2.y) * mLocked.pointerGestureYZoomScale;
- float magnitude1 = pythag(x1, y1);
- float magnitude2 = pythag(x2, y2);
-
- // Calculate the dot product of the vectors.
+ } else if (((d = distance(
+ mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y,
+ mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y))
+ > mLocked.pointerGestureMaxSwipeWidth)) {
+ // There are two pointers but they are too far apart, switch to FREEFORM.
+#if DEBUG_GESTURES
+ LOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+ d, mLocked.pointerGestureMaxSwipeWidth);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ } else {
+ // There are two pointers. Wait for both pointers to start moving
+ // before deciding whether this is a SWIPE or FREEFORM gesture.
+ uint32_t id1 = mCurrentTouch.pointers[0].id;
+ uint32_t id2 = mCurrentTouch.pointers[1].id;
+
+ float vx1, vy1, vx2, vy2;
+ mPointerGesture.velocityTracker.getVelocity(id1, &vx1, &vy1);
+ mPointerGesture.velocityTracker.getVelocity(id2, &vx2, &vy2);
+
+ float speed1 = hypotf(vx1, vy1);
+ float speed2 = hypotf(vx2, vy2);
+ if (speed1 >= MULTITOUCH_MIN_SPEED && speed2 >= MULTITOUCH_MIN_SPEED) {
+ // Calculate the dot product of the velocity vectors.
// When the vectors are oriented in approximately the same direction,
// the angle betweeen them is near zero and the cosine of the angle
// approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2).
- // We know that the magnitude is at least MULTITOUCH_MIN_TRAVEL because
- // we checked it above.
- float dot = x1 * x2 + y1 * y2;
- float cosine = dot / (magnitude1 * magnitude2); // denominator always > 0
- if (cosine > SWIPE_TRANSITION_ANGLE_COSINE) {
+ float dot = vx1 * vx2 + vy1 * vy2;
+ float cosine = dot / (speed1 * speed2); // denominator always > 0
+ if (cosine >= SWIPE_TRANSITION_ANGLE_COSINE) {
+ // Pointers are moving in the same direction. Switch to SWIPE.
+#if DEBUG_GESTURES
+ LOGD("Gestures: PRESS transitioned to SWIPE, "
+ "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, "
+ "cosine %0.3f >= %0.3f",
+ speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED,
+ cosine, SWIPE_TRANSITION_ANGLE_COSINE);
+#endif
mPointerGesture.currentGestureMode = PointerGesture::SWIPE;
+ } else {
+ // Pointers are moving in different directions. Switch to FREEFORM.
+#if DEBUG_GESTURES
+ LOGD("Gestures: PRESS transitioned to FREEFORM, "
+ "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, "
+ "cosine %0.3f < %0.3f",
+ speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED,
+ cosine, SWIPE_TRANSITION_ANGLE_COSINE);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
}
}
-
- // Remember the initial centroid for the duration of the gesture.
- mPointerGesture.initialCentroidX = 0;
- mPointerGesture.initialCentroidY = 0;
- for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
- const PointerData& touch = mCurrentTouch.pointers[i];
- mPointerGesture.initialCentroidX += touch.x;
- mPointerGesture.initialCentroidY += touch.y;
- }
- mPointerGesture.initialCentroidX /= int32_t(currentTouchPointerCount);
- mPointerGesture.initialCentroidY /= int32_t(currentTouchPointerCount);
-
- mPointerGesture.activeGestureId = 0;
}
} else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
- // Switch to FREEFORM if additional pointers go down.
- if (currentTouchPointerCount > 2) {
+ // Switch from SWIPE to FREEFORM if additional pointers go down.
+ // Cancel previous gesture.
+ if (mCurrentTouch.pointerCount > 2) {
+#if DEBUG_GESTURES
+ LOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+ mCurrentTouch.pointerCount);
+#endif
*outCancelPreviousGesture = true;
mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
}
}
- if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+ // Move the reference points based on the overall group motion of the fingers.
+ // The objective is to calculate a vector delta that is common to the movement
+ // of all fingers.
+ BitSet32 commonIdBits(mLastTouch.idBits.value & mCurrentTouch.idBits.value);
+ if (!commonIdBits.isEmpty()) {
+ float commonDeltaX = 0, commonDeltaY = 0;
+ for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) {
+ bool first = (idBits == commonIdBits);
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+
+ const PointerData& cpd = mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]];
+ const PointerData& lpd = mLastTouch.pointers[mLastTouch.idToIndex[id]];
+ float deltaX = cpd.x - lpd.x;
+ float deltaY = cpd.y - lpd.y;
+
+ if (first) {
+ commonDeltaX = deltaX;
+ commonDeltaY = deltaY;
+ } else {
+ commonDeltaX = calculateCommonVector(commonDeltaX, deltaX);
+ commonDeltaY = calculateCommonVector(commonDeltaY, deltaY);
+ }
+ }
+
+ mPointerGesture.referenceTouchX += commonDeltaX;
+ mPointerGesture.referenceTouchY += commonDeltaY;
+ mPointerGesture.referenceGestureX +=
+ commonDeltaX * mLocked.pointerGestureXMovementScale;
+ mPointerGesture.referenceGestureY +=
+ commonDeltaY * mLocked.pointerGestureYMovementScale;
+ clampPositionUsingPointerBounds(mPointerController,
+ &mPointerGesture.referenceGestureX,
+ &mPointerGesture.referenceGestureY);
+ }
+
+ // Report gestures.
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+ // PRESS mode.
+#if DEBUG_GESTURES
+ LOGD("Gestures: PRESS activeTouchId=%d,"
+ "activeGestureId=%d, currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
+#endif
+ LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ mPointerGesture.referenceGestureX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ mPointerGesture.referenceGestureY);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_PRESS;
+ }
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
// SWIPE mode.
#if DEBUG_GESTURES
LOGD("Gestures: SWIPE activeTouchId=%d,"
"activeGestureId=%d, currentTouchPointerCount=%d",
- activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount);
+ activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
#endif
LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
- float x = (mCurrentTouch.pointers[0].x + mCurrentTouch.pointers[1].x
- - mPointerGesture.initialCentroidX * 2) * 0.5f
- * mLocked.pointerGestureXMovementScale + mPointerGesture.initialPointerX;
- float y = (mCurrentTouch.pointers[0].y + mCurrentTouch.pointers[1].y
- - mPointerGesture.initialCentroidY * 2) * 0.5f
- * mLocked.pointerGestureYMovementScale + mPointerGesture.initialPointerY;
-
- mPointerGesture.currentGesturePointerCount = 1;
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
mPointerGesture.currentGestureCoords[0].clear();
- mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
- mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ mPointerGesture.referenceGestureX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ mPointerGesture.referenceGestureY);
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ mPointerController->setButtonState(0); // touch is not actually following the pointer
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_SWIPE;
+ }
} else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
// FREEFORM mode.
#if DEBUG_GESTURES
LOGD("Gestures: FREEFORM activeTouchId=%d,"
"activeGestureId=%d, currentTouchPointerCount=%d",
- activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount);
+ activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
#endif
LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
- mPointerGesture.currentGesturePointerCount = currentTouchPointerCount;
mPointerGesture.currentGestureIdBits.clear();
BitSet32 mappedTouchIdBits;
@@ -3782,7 +4002,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
mPointerGesture.activeGestureId);
#endif
- for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
+ for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
uint32_t touchId = mCurrentTouch.pointers[i].id;
uint32_t gestureId;
if (!mappedTouchIdBits.hasBit(touchId)) {
@@ -3805,10 +4025,10 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
mPointerGesture.currentGestureIdBits.markBit(gestureId);
mPointerGesture.currentGestureIdToIndex[gestureId] = i;
- float x = (mCurrentTouch.pointers[i].x - mPointerGesture.initialCentroidX)
- * mLocked.pointerGestureXZoomScale + mPointerGesture.initialPointerX;
- float y = (mCurrentTouch.pointers[i].y - mPointerGesture.initialCentroidY)
- * mLocked.pointerGestureYZoomScale + mPointerGesture.initialPointerY;
+ float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX)
+ * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX;
+ float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY)
+ * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY;
mPointerGesture.currentGestureCoords[i].clear();
mPointerGesture.currentGestureCoords[i].setAxisValue(
@@ -3827,30 +4047,45 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
"activeGestureId=%d", mPointerGesture.activeGestureId);
#endif
}
- } else {
- // INDETERMINATE_MULTITOUCH mode.
- // Do nothing.
-#if DEBUG_GESTURES
- LOGD("Gestures: INDETERMINATE_MULTITOUCH");
-#endif
+
+ mPointerController->setButtonState(0); // touch is not actually following the pointer
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_FREEFORM;
+ }
}
- }
- // Unfade the pointer if the user is doing anything with the touch pad.
- mPointerController->setButtonState(mCurrentTouch.buttonState);
- if (mCurrentTouch.buttonState || mCurrentTouch.pointerCount != 0) {
- mPointerController->unfade();
+ // Update spot locations for PRESS, SWIPE and FREEFORM.
+ // We use the same calculation as we do to calculate the gesture pointers
+ // for FREEFORM so that the spots smoothly track gestures.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotIdBits.clear();
+ for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
+ uint32_t id = mCurrentTouch.pointers[i].id;
+ mPointerGesture.spotIdBits.markBit(id);
+ mPointerGesture.spotIdToIndex[id] = i;
+
+ float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX)
+ * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX;
+ float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY)
+ * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY;
+
+ mPointerGesture.spotCoords[i].clear();
+ mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ }
+ moveSpotsLocked();
+ }
}
#if DEBUG_GESTURES
LOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
- "currentGestureMode=%d, currentGesturePointerCount=%d, currentGestureIdBits=0x%08x, "
- "lastGestureMode=%d, lastGesturePointerCount=%d, lastGestureIdBits=0x%08x",
+ "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
+ "lastGestureMode=%d, lastGestureIdBits=0x%08x",
toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
- mPointerGesture.currentGestureMode, mPointerGesture.currentGesturePointerCount,
- mPointerGesture.currentGestureIdBits.value,
- mPointerGesture.lastGestureMode, mPointerGesture.lastGesturePointerCount,
- mPointerGesture.lastGestureIdBits.value);
+ mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
+ mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) {
uint32_t id = idBits.firstMarkedBit();
idBits.clearBit(id);
@@ -3874,6 +4109,11 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when,
#endif
}
+void TouchInputMapper::moveSpotsLocked() {
+ mPointerController->setSpots(mPointerGesture.spotGesture,
+ mPointerGesture.spotCoords, mPointerGesture.spotIdToIndex, mPointerGesture.spotIdBits);
+}
+
void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
int32_t action, int32_t flags, uint32_t metaState, int32_t edgeFlags,
const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits,
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 9ed1391..9b2f4d2 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -20,7 +20,6 @@
#include "EventHub.h"
#include "InputDispatcher.h"
#include "PointerController.h"
-#include "SpotController.h"
#include <ui/Input.h>
#include <ui/DisplayInfo.h>
@@ -90,9 +89,6 @@ public:
/* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
-
- /* Gets a spot controller associated with the specified touch pad device. */
- virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId) = 0;
};
@@ -648,6 +644,20 @@ protected:
idBits.clear();
buttonState = 0;
}
+
+ void getCentroid(float* outX, float* outY) {
+ float x = 0, y = 0;
+ if (pointerCount != 0) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ x += pointers[i].x;
+ y += pointers[i].y;
+ }
+ x /= pointerCount;
+ y /= pointerCount;
+ }
+ *outX = x;
+ *outY = y;
+ }
};
// Input sources supported by the device.
@@ -670,6 +680,12 @@ protected:
bool useJumpyTouchFilter;
bool useAveragingTouchFilter;
nsecs_t virtualKeyQuietTime;
+
+ enum GestureMode {
+ GESTURE_MODE_POINTER,
+ GESTURE_MODE_SPOTS,
+ };
+ GestureMode gestureMode;
} mParameters;
// Immutable calibration parameters in parsed form.
@@ -841,8 +857,8 @@ protected:
float pointerGestureXZoomScale;
float pointerGestureYZoomScale;
- // The maximum swipe width squared.
- int32_t pointerGestureMaxSwipeWidthSquared;
+ // The maximum swipe width.
+ float pointerGestureMaxSwipeWidth;
} mLocked;
virtual void configureParameters();
@@ -929,28 +945,32 @@ private:
// Emits HOVER_MOVE events at the pointer location.
HOVER,
- // More than two fingers involved but they haven't moved enough for us
- // to figure out what is intended.
- INDETERMINATE_MULTITOUCH,
+ // Exactly two fingers but neither have moved enough to clearly indicate
+ // whether a swipe or freeform gesture was intended. We consider the
+ // pointer to be pressed so this enables clicking or long-pressing on buttons.
+ // Pointer does not move.
+ // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate.
+ PRESS,
// Exactly two fingers moving in the same direction, button is not pressed.
// Pointer does not move.
// Emits DOWN, MOVE and UP events with a single pointer coordinate that
// follows the midpoint between both fingers.
- // The centroid is fixed when entering this state.
SWIPE,
// Two or more fingers moving in arbitrary directions, button is not pressed.
// Pointer does not move.
// Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow
// each finger individually relative to the initial centroid of the finger.
- // The centroid is fixed when entering this state.
FREEFORM,
// Waiting for quiet time to end before starting the next gesture.
QUIET,
};
+ // Time the first finger went down.
+ nsecs_t firstTouchTime;
+
// The active pointer id from the raw touch data.
int32_t activeTouchId; // -1 if none
@@ -959,32 +979,20 @@ private:
// Pointer coords and ids for the current and previous pointer gesture.
Mode currentGestureMode;
- uint32_t currentGesturePointerCount;
BitSet32 currentGestureIdBits;
uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
PointerCoords currentGestureCoords[MAX_POINTERS];
Mode lastGestureMode;
- uint32_t lastGesturePointerCount;
BitSet32 lastGestureIdBits;
uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
PointerCoords lastGestureCoords[MAX_POINTERS];
- // Tracks for all pointers originally went down.
- TouchData touchOrigin;
-
- // Describes how touch ids are mapped to gesture ids for freeform gestures.
- uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
-
- // Initial centroid of the movement.
- // Used to calculate how far the touch pointers have moved since the gesture started.
- int32_t initialCentroidX;
- int32_t initialCentroidY;
-
- // Initial pointer location.
- // Used to track where the pointer was when the gesture started.
- float initialPointerX;
- float initialPointerY;
+ // Pointer coords and ids for the current spots.
+ PointerControllerInterface::SpotGesture spotGesture;
+ BitSet32 spotIdBits; // same set of ids as touch ids
+ uint32_t spotIdToIndex[MAX_POINTER_ID + 1];
+ PointerCoords spotCoords[MAX_POINTERS];
// Time the pointer gesture last went down.
nsecs_t downTime;
@@ -992,26 +1000,34 @@ private:
// Time we started waiting for a tap gesture.
nsecs_t tapTime;
+ // Location of initial tap.
+ float tapX, tapY;
+
// Time we started waiting for quiescence.
nsecs_t quietTime;
+ // Reference points for multitouch gestures.
+ float referenceTouchX; // reference touch X/Y coordinates in surface units
+ float referenceTouchY;
+ float referenceGestureX; // reference gesture X/Y coordinates in pixels
+ float referenceGestureY;
+
+ // Describes how touch ids are mapped to gesture ids for freeform gestures.
+ uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
+
// A velocity tracker for determining whether to switch active pointers during drags.
VelocityTracker velocityTracker;
void reset() {
+ firstTouchTime = LLONG_MIN;
activeTouchId = -1;
activeGestureId = -1;
currentGestureMode = NEUTRAL;
- currentGesturePointerCount = 0;
currentGestureIdBits.clear();
lastGestureMode = NEUTRAL;
- lastGesturePointerCount = 0;
lastGestureIdBits.clear();
- touchOrigin.clear();
- initialCentroidX = 0;
- initialCentroidY = 0;
- initialPointerX = 0;
- initialPointerY = 0;
+ spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+ spotIdBits.clear();
downTime = 0;
velocityTracker.clear();
resetTapTime();
@@ -1035,6 +1051,7 @@ private:
void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags);
void preparePointerGestures(nsecs_t when,
bool* outCancelPreviousGesture, bool* outFinishPreviousGesture);
+ void moveSpotsLocked();
// Dispatches a motion event.
// If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
index 15effb7..ffef720 100644
--- a/services/input/PointerController.cpp
+++ b/services/input/PointerController.cpp
@@ -36,40 +36,49 @@ namespace android {
// --- PointerController ---
// Time to wait before starting the fade when the pointer is inactive.
-static const nsecs_t INACTIVITY_FADE_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
-static const nsecs_t INACTIVITY_FADE_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
-// Time to spend fading out the pointer completely.
-static const nsecs_t FADE_DURATION = 500 * 1000000LL; // 500 ms
+// Time to wait between animation frames.
+static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
+
+// Time to spend fading out the spot completely.
+static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
-// Time to wait between frames.
-static const nsecs_t FADE_FRAME_INTERVAL = 1000000000LL / 60;
+// Time to spend fading out the pointer completely.
+static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
-// Amount to subtract from alpha per frame.
-static const float FADE_DECAY_PER_FRAME = float(FADE_FRAME_INTERVAL) / FADE_DURATION;
+// --- PointerController ---
-PointerController::PointerController(const sp<Looper>& looper,
- const sp<SpriteController>& spriteController) :
- mLooper(looper), mSpriteController(spriteController) {
+PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController) :
+ mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
mHandler = new WeakMessageHandler(this);
AutoMutex _l(mLock);
+ mLocked.animationPending = false;
+
mLocked.displayWidth = -1;
mLocked.displayHeight = -1;
mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
+ mLocked.presentation = PRESENTATION_POINTER;
+ mLocked.presentationChanged = false;
+
+ mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL;
+
+ mLocked.pointerIsFading = true; // keep the pointer initially faded
mLocked.pointerX = 0;
mLocked.pointerY = 0;
- mLocked.buttonState = 0;
-
- mLocked.fadeAlpha = 1;
- mLocked.inactivityFadeDelay = INACTIVITY_FADE_DELAY_NORMAL;
+ mLocked.pointerAlpha = 0.0f;
+ mLocked.pointerSprite = mSpriteController->createSprite();
+ mLocked.pointerIconChanged = false;
- mLocked.visible = false;
+ mLocked.buttonState = 0;
- mLocked.sprite = mSpriteController->createSprite();
+ loadResources();
}
PointerController::~PointerController() {
@@ -77,7 +86,13 @@ PointerController::~PointerController() {
AutoMutex _l(mLock);
- mLocked.sprite.clear();
+ mLocked.pointerSprite.clear();
+
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ delete mLocked.spots.itemAt(i);
+ }
+ mLocked.spots.clear();
+ mLocked.recycledSprites.clear();
}
bool PointerController::getBounds(float* outMinX, float* outMinY,
@@ -130,8 +145,6 @@ void PointerController::setButtonState(uint32_t buttonState) {
if (mLocked.buttonState != buttonState) {
mLocked.buttonState = buttonState;
- unfadeBeforeUpdateLocked();
- updateLocked();
}
}
@@ -167,8 +180,7 @@ void PointerController::setPositionLocked(float x, float y) {
} else {
mLocked.pointerY = y;
}
- unfadeBeforeUpdateLocked();
- updateLocked();
+ updatePointerLocked();
}
}
@@ -182,32 +194,105 @@ void PointerController::getPosition(float* outX, float* outY) const {
void PointerController::fade() {
AutoMutex _l(mLock);
- startFadeLocked();
+ sendImmediateInactivityTimeoutLocked();
}
void PointerController::unfade() {
AutoMutex _l(mLock);
- if (unfadeBeforeUpdateLocked()) {
- updateLocked();
+ // Always reset the inactivity timer.
+ resetInactivityTimeoutLocked();
+
+ // Unfade immediately if needed.
+ if (mLocked.pointerIsFading) {
+ mLocked.pointerIsFading = false;
+ mLocked.pointerAlpha = 1.0f;
+ updatePointerLocked();
}
}
-void PointerController::setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay) {
+void PointerController::setPresentation(Presentation presentation) {
AutoMutex _l(mLock);
- if (mLocked.inactivityFadeDelay != inactivityFadeDelay) {
- mLocked.inactivityFadeDelay = inactivityFadeDelay;
- startInactivityFadeDelayLocked();
+ if (mLocked.presentation != presentation) {
+ mLocked.presentation = presentation;
+ mLocked.presentationChanged = true;
+
+ if (presentation != PRESENTATION_SPOT) {
+ fadeOutAndReleaseAllSpotsLocked();
+ }
+
+ updatePointerLocked();
}
}
-void PointerController::updateLocked() {
- mLocked.sprite->openTransaction();
- mLocked.sprite->setPosition(mLocked.pointerX, mLocked.pointerY);
- mLocked.sprite->setAlpha(mLocked.fadeAlpha);
- mLocked.sprite->setVisible(mLocked.visible);
- mLocked.sprite->closeTransaction();
+void PointerController::setSpots(SpotGesture spotGesture,
+ const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+#if DEBUG_POINTER_UPDATES
+ LOGD("setSpots: spotGesture=%d", spotGesture);
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ LOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id,
+ c.getAxisValue(AMOTION_EVENT_AXIS_X),
+ c.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+#endif
+
+ AutoMutex _l(mLock);
+
+ mSpriteController->openTransaction();
+
+ // Add or move spots for fingers that are down.
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
+ ? mResources.spotTouch : mResources.spotHover;
+ float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+ float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+ Spot* spot = getSpotLocked(id);
+ if (!spot) {
+ spot = createAndAddSpotLocked(id);
+ }
+
+ spot->updateSprite(&icon, x, y);
+ }
+
+ // Remove spots for fingers that went up.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id != Spot::INVALID_ID
+ && !spotIdBits.hasBit(spot->id)) {
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+ }
+
+ mSpriteController->closeTransaction();
+}
+
+void PointerController::clearSpots() {
+#if DEBUG_POINTER_UPDATES
+ LOGD("clearSpots");
+#endif
+
+ AutoMutex _l(mLock);
+
+ fadeOutAndReleaseAllSpotsLocked();
+}
+
+void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.inactivityTimeout != inactivityTimeout) {
+ mLocked.inactivityTimeout = inactivityTimeout;
+ resetInactivityTimeoutLocked();
+ }
}
void PointerController::setDisplaySize(int32_t width, int32_t height) {
@@ -226,7 +311,8 @@ void PointerController::setDisplaySize(int32_t width, int32_t height) {
mLocked.pointerY = 0;
}
- updateLocked();
+ fadeOutAndReleaseAllSpotsLocked();
+ updatePointerLocked();
}
}
@@ -283,74 +369,217 @@ void PointerController::setDisplayOrientation(int32_t orientation) {
mLocked.pointerY = y - 0.5f;
mLocked.displayOrientation = orientation;
- updateLocked();
+ updatePointerLocked();
}
}
-void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) {
+void PointerController::setPointerIcon(const SpriteIcon& icon) {
AutoMutex _l(mLock);
- mLocked.sprite->setBitmap(bitmap, hotSpotX, hotSpotY);
+ mLocked.pointerIcon = icon.copy();
+ mLocked.pointerIconChanged = true;
+
+ updatePointerLocked();
}
void PointerController::handleMessage(const Message& message) {
switch (message.what) {
- case MSG_FADE_STEP: {
- AutoMutex _l(mLock);
- fadeStepLocked();
+ case MSG_ANIMATE:
+ doAnimate();
+ break;
+ case MSG_INACTIVITY_TIMEOUT:
+ doInactivityTimeout();
break;
}
+}
+
+void PointerController::doAnimate() {
+ AutoMutex _l(mLock);
+
+ bool keepAnimating = false;
+ mLocked.animationPending = false;
+ nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
+
+ // Animate pointer fade.
+ if (mLocked.pointerIsFading) {
+ mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha <= 0) {
+ mLocked.pointerAlpha = 0;
+ } else {
+ keepAnimating = true;
+ }
+ updatePointerLocked();
+ }
+
+ // Animate spots that are fading out and being removed.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
+ if (spot->alpha <= 0) {
+ mLocked.spots.removeAt(i--);
+ releaseSpotLocked(spot);
+ } else {
+ spot->sprite->setAlpha(spot->alpha);
+ keepAnimating = true;
+ }
+ }
+ }
+
+ if (keepAnimating) {
+ startAnimationLocked();
}
}
-bool PointerController::unfadeBeforeUpdateLocked() {
- sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
+void PointerController::doInactivityTimeout() {
+ AutoMutex _l(mLock);
- if (isFadingLocked()) {
- mLocked.visible = true;
- mLocked.fadeAlpha = 1;
- return true; // update required to effect the unfade
+ if (!mLocked.pointerIsFading) {
+ mLocked.pointerIsFading = true;
+ startAnimationLocked();
}
- return false; // update not required
}
-void PointerController::startFadeLocked() {
- if (!isFadingLocked()) {
- sendFadeStepMessageDelayedLocked(0);
+void PointerController::startAnimationLocked() {
+ if (!mLocked.animationPending) {
+ mLocked.animationPending = true;
+ mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
}
}
-void PointerController::startInactivityFadeDelayLocked() {
- if (!isFadingLocked()) {
- sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
+void PointerController::resetInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+
+ nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT
+ ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
+ mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::sendImmediateInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+ mLooper->sendMessage(mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::updatePointerLocked() {
+ mSpriteController->openTransaction();
+
+ mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
+ mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+
+ if (mLocked.pointerAlpha > 0) {
+ mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
+ mLocked.pointerSprite->setVisible(true);
+ } else {
+ mLocked.pointerSprite->setVisible(false);
+ }
+
+ if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
+ mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER
+ ? mLocked.pointerIcon : mResources.spotAnchor);
+ mLocked.pointerIconChanged = false;
+ mLocked.presentationChanged = false;
}
+
+ mSpriteController->closeTransaction();
}
-void PointerController::fadeStepLocked() {
- if (mLocked.visible) {
- mLocked.fadeAlpha -= FADE_DECAY_PER_FRAME;
- if (mLocked.fadeAlpha < 0) {
- mLocked.fadeAlpha = 0;
- mLocked.visible = false;
- } else {
- sendFadeStepMessageDelayedLocked(FADE_FRAME_INTERVAL);
+PointerController::Spot* PointerController::getSpotLocked(uint32_t id) {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == id) {
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) {
+ // Remove spots until we have fewer than MAX_SPOTS remaining.
+ while (mLocked.spots.size() >= MAX_SPOTS) {
+ Spot* spot = removeFirstFadingSpotLocked();
+ if (!spot) {
+ spot = mLocked.spots.itemAt(0);
+ mLocked.spots.removeAt(0);
+ }
+ releaseSpotLocked(spot);
+ }
+
+ // Obtain a sprite from the recycled pool.
+ sp<Sprite> sprite;
+ if (! mLocked.recycledSprites.isEmpty()) {
+ sprite = mLocked.recycledSprites.top();
+ mLocked.recycledSprites.pop();
+ } else {
+ sprite = mSpriteController->createSprite();
+ }
+
+ // Return the new spot.
+ Spot* spot = new Spot(id, sprite);
+ mLocked.spots.push(spot);
+ return spot;
+}
+
+PointerController::Spot* PointerController::removeFirstFadingSpotLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ mLocked.spots.removeAt(i);
+ return spot;
}
- updateLocked();
+ }
+ return NULL;
+}
+
+void PointerController::releaseSpotLocked(Spot* spot) {
+ spot->sprite->clearIcon();
+
+ if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
+ mLocked.recycledSprites.push(spot->sprite);
+ }
+
+ delete spot;
+}
+
+void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
+ if (spot->id != Spot::INVALID_ID) {
+ spot->id = Spot::INVALID_ID;
+ startAnimationLocked();
}
}
-bool PointerController::isFadingLocked() {
- return !mLocked.visible || mLocked.fadeAlpha != 1;
+void PointerController::fadeOutAndReleaseAllSpotsLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ fadeOutAndReleaseSpotLocked(spot);
+ }
}
-nsecs_t PointerController::getInactivityFadeDelayTimeLocked() {
- return mLocked.inactivityFadeDelay == INACTIVITY_FADE_DELAY_SHORT
- ? INACTIVITY_FADE_DELAY_TIME_SHORT : INACTIVITY_FADE_DELAY_TIME_NORMAL;
+void PointerController::loadResources() {
+ mPolicy->loadPointerResources(&mResources);
}
-void PointerController::sendFadeStepMessageDelayedLocked(nsecs_t delayTime) {
- mLooper->removeMessages(mHandler, MSG_FADE_STEP);
- mLooper->sendMessageDelayed(delayTime, mHandler, Message(MSG_FADE_STEP));
+
+// --- PointerController::Spot ---
+
+void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) {
+ sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
+ sprite->setAlpha(alpha);
+ sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
+ sprite->setPosition(x, y);
+
+ this->x = x;
+ this->y = y;
+
+ if (icon != lastIcon) {
+ lastIcon = icon;
+ if (icon) {
+ sprite->setIcon(*icon);
+ sprite->setVisible(true);
+ } else {
+ sprite->setVisible(false);
+ }
+ }
}
} // namespace android
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
index d467a5a..afd6371 100644
--- a/services/input/PointerController.h
+++ b/services/input/PointerController.h
@@ -30,7 +30,10 @@
namespace android {
/**
- * Interface for tracking a single (mouse) pointer.
+ * Interface for tracking a mouse / touch pad pointer and touch pad spots.
+ *
+ * The spots are sprites on screen that visually represent the positions of
+ * fingers
*
* The pointer controller is responsible for providing synchronization and for tracking
* display orientation changes if needed.
@@ -64,8 +67,95 @@ public:
/* Fades the pointer out now. */
virtual void fade() = 0;
- /* Makes the pointer visible if it has faded out. */
+ /* Makes the pointer visible if it has faded out.
+ * The pointer never unfades itself automatically. This method must be called
+ * by the client whenever the pointer is moved or a button is pressed and it
+ * wants to ensure that the pointer becomes visible again. */
virtual void unfade() = 0;
+
+ enum Presentation {
+ // Show the mouse pointer.
+ PRESENTATION_POINTER,
+ // Show spots and a spot anchor in place of the mouse pointer.
+ PRESENTATION_SPOT,
+ };
+
+ /* Sets the mode of the pointer controller. */
+ virtual void setPresentation(Presentation presentation) = 0;
+
+ // Describes the current gesture.
+ enum SpotGesture {
+ // No gesture.
+ // Do not display any spots.
+ SPOT_GESTURE_NEUTRAL,
+ // Tap at current location.
+ // Briefly display one spot at the tapped location.
+ SPOT_GESTURE_TAP,
+ // Button pressed but no finger is down.
+ // Display spot at pressed location.
+ SPOT_GESTURE_BUTTON_CLICK,
+ // Button pressed and a finger is down.
+ // Display spot at pressed location.
+ SPOT_GESTURE_BUTTON_DRAG,
+ // One finger down and hovering.
+ // Display spot at the hovered location.
+ SPOT_GESTURE_HOVER,
+ // Two fingers down but not sure in which direction they are moving so we consider
+ // it a press at the pointer location.
+ // Display two spots near the pointer location.
+ SPOT_GESTURE_PRESS,
+ // Two fingers down and moving in same direction.
+ // Display two spots near the pointer location.
+ SPOT_GESTURE_SWIPE,
+ // Two or more fingers down and moving in arbitrary directions.
+ // Display two or more spots near the pointer location, one for each finger.
+ SPOT_GESTURE_FREEFORM,
+ };
+
+ /* Sets the spots for the current gesture.
+ * The spots are not subject to the inactivity timeout like the pointer
+ * itself it since they are expected to remain visible for so long as
+ * the fingers are on the touch pad.
+ *
+ * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant.
+ * For spotCoords, pressure != 0 indicates that the spot's location is being
+ * pressed (not hovering).
+ */
+ virtual void setSpots(SpotGesture spotGesture,
+ const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits) = 0;
+
+ /* Removes all spots. */
+ virtual void clearSpots() = 0;
+};
+
+
+/*
+ * Pointer resources.
+ */
+struct PointerResources {
+ SpriteIcon spotHover;
+ SpriteIcon spotTouch;
+ SpriteIcon spotAnchor;
+};
+
+
+/*
+ * Pointer controller policy interface.
+ *
+ * The pointer controller policy is used by the pointer controller to interact with
+ * the Window Manager and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class PointerControllerPolicyInterface : public virtual RefBase {
+protected:
+ PointerControllerPolicyInterface() { }
+ virtual ~PointerControllerPolicyInterface() { }
+
+public:
+ virtual void loadPointerResources(PointerResources* outResources) = 0;
};
@@ -79,12 +169,13 @@ protected:
virtual ~PointerController();
public:
- enum InactivityFadeDelay {
- INACTIVITY_FADE_DELAY_NORMAL = 0,
- INACTIVITY_FADE_DELAY_SHORT = 1,
+ enum InactivityTimeout {
+ INACTIVITY_TIMEOUT_NORMAL = 0,
+ INACTIVITY_TIMEOUT_SHORT = 1,
};
- PointerController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
+ PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController);
virtual bool getBounds(float* outMinX, float* outMinY,
float* outMaxX, float* outMaxY) const;
@@ -96,51 +187,101 @@ public:
virtual void fade();
virtual void unfade();
+ virtual void setPresentation(Presentation presentation);
+ virtual void setSpots(SpotGesture spotGesture,
+ const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
+ virtual void clearSpots();
+
void setDisplaySize(int32_t width, int32_t height);
void setDisplayOrientation(int32_t orientation);
- void setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
- void setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay);
+ void setPointerIcon(const SpriteIcon& icon);
+ void setInactivityTimeout(InactivityTimeout inactivityTimeout);
private:
+ static const size_t MAX_RECYCLED_SPRITES = 12;
+ static const size_t MAX_SPOTS = 12;
+
enum {
- MSG_FADE_STEP = 0,
+ MSG_ANIMATE,
+ MSG_INACTIVITY_TIMEOUT,
+ };
+
+ struct Spot {
+ static const uint32_t INVALID_ID = 0xffffffff;
+
+ uint32_t id;
+ sp<Sprite> sprite;
+ float alpha;
+ float scale;
+ float x, y;
+
+ inline Spot(uint32_t id, const sp<Sprite>& sprite)
+ : id(id), sprite(sprite), alpha(1.0f), scale(1.0f),
+ x(0.0f), y(0.0f), lastIcon(NULL) { }
+
+ void updateSprite(const SpriteIcon* icon, float x, float y);
+
+ private:
+ const SpriteIcon* lastIcon;
};
mutable Mutex mLock;
+ sp<PointerControllerPolicyInterface> mPolicy;
sp<Looper> mLooper;
sp<SpriteController> mSpriteController;
sp<WeakMessageHandler> mHandler;
+ PointerResources mResources;
+
struct Locked {
+ bool animationPending;
+ nsecs_t animationTime;
+
int32_t displayWidth;
int32_t displayHeight;
int32_t displayOrientation;
+ InactivityTimeout inactivityTimeout;
+
+ Presentation presentation;
+ bool presentationChanged;
+
+ bool pointerIsFading;
float pointerX;
float pointerY;
- uint32_t buttonState;
+ float pointerAlpha;
+ sp<Sprite> pointerSprite;
+ SpriteIcon pointerIcon;
+ bool pointerIconChanged;
- float fadeAlpha;
- InactivityFadeDelay inactivityFadeDelay;
-
- bool visible;
+ uint32_t buttonState;
- sp<Sprite> sprite;
+ Vector<Spot*> spots;
+ Vector<sp<Sprite> > recycledSprites;
} mLocked;
bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
void setPositionLocked(float x, float y);
- void updateLocked();
void handleMessage(const Message& message);
- bool unfadeBeforeUpdateLocked();
- void startFadeLocked();
- void startInactivityFadeDelayLocked();
- void fadeStepLocked();
- bool isFadingLocked();
- nsecs_t getInactivityFadeDelayTimeLocked();
- void sendFadeStepMessageDelayedLocked(nsecs_t delayTime);
+ void doAnimate();
+ void doInactivityTimeout();
+
+ void startAnimationLocked();
+
+ void resetInactivityTimeoutLocked();
+ void sendImmediateInactivityTimeoutLocked();
+ void updatePointerLocked();
+
+ Spot* getSpotLocked(uint32_t id);
+ Spot* createAndAddSpotLocked(uint32_t id);
+ Spot* removeFirstFadingSpotLocked();
+ void releaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseAllSpotsLocked();
+
+ void loadResources();
};
} // namespace android
diff --git a/services/input/SpotController.h b/services/input/SpotController.h
deleted file mode 100644
index 1d091d7..0000000
--- a/services/input/SpotController.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _UI_SPOT_CONTROLLER_H
-#define _UI_SPOT_CONTROLLER_H
-
-#include "SpriteController.h"
-
-#include <utils/RefBase.h>
-#include <utils/Looper.h>
-
-#include <SkBitmap.h>
-
-namespace android {
-
-/*
- * Interface for displaying spots on screen that visually represent the positions
- * of fingers on a touch pad.
- *
- * The spot controller is responsible for providing synchronization and for tracking
- * display orientation changes if needed.
- */
-class SpotControllerInterface : public virtual RefBase {
-protected:
- SpotControllerInterface() { }
- virtual ~SpotControllerInterface() { }
-
-public:
-
-};
-
-
-/*
- * Sprite-based spot controller implementation.
- */
-class SpotController : public SpotControllerInterface, public MessageHandler {
-protected:
- virtual ~SpotController();
-
-public:
- SpotController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
-
-private:
- mutable Mutex mLock;
-
- sp<Looper> mLooper;
- sp<SpriteController> mSpriteController;
- sp<WeakMessageHandler> mHandler;
-
- struct Locked {
- } mLocked;
-
- void handleMessage(const Message& message);
-};
-
-} // namespace android
-
-#endif // _UI_SPOT_CONTROLLER_H
diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp
index c6d4390..2fd1f0a 100644
--- a/services/input/SpriteController.cpp
+++ b/services/input/SpriteController.cpp
@@ -36,6 +36,9 @@ namespace android {
SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
mLooper(looper), mOverlayLayer(overlayLayer) {
mHandler = new WeakMessageHandler(this);
+
+ mLocked.transactionNestingCount = 0;
+ mLocked.deferredSpriteUpdate = false;
}
SpriteController::~SpriteController() {
@@ -51,17 +54,40 @@ sp<Sprite> SpriteController::createSprite() {
return new SpriteImpl(this);
}
+void SpriteController::openTransaction() {
+ AutoMutex _l(mLock);
+
+ mLocked.transactionNestingCount += 1;
+}
+
+void SpriteController::closeTransaction() {
+ AutoMutex _l(mLock);
+
+ LOG_ALWAYS_FATAL_IF(mLocked.transactionNestingCount == 0,
+ "Sprite closeTransaction() called but there is no open sprite transaction");
+
+ mLocked.transactionNestingCount -= 1;
+ if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) {
+ mLocked.deferredSpriteUpdate = false;
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ }
+}
+
void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
- bool wasEmpty = mInvalidatedSprites.isEmpty();
- mInvalidatedSprites.push(sprite);
+ bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
+ mLocked.invalidatedSprites.push(sprite);
if (wasEmpty) {
- mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ if (mLocked.transactionNestingCount != 0) {
+ mLocked.deferredSpriteUpdate = true;
+ } else {
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ }
}
}
void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
- bool wasEmpty = mDisposedSurfaces.isEmpty();
- mDisposedSurfaces.push(surfaceControl);
+ bool wasEmpty = mLocked.disposedSurfaces.isEmpty();
+ mLocked.disposedSurfaces.push(surfaceControl);
if (wasEmpty) {
mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
}
@@ -89,14 +115,14 @@ void SpriteController::doUpdateSprites() {
{ // acquire lock
AutoMutex _l(mLock);
- numSprites = mInvalidatedSprites.size();
+ numSprites = mLocked.invalidatedSprites.size();
for (size_t i = 0; i < numSprites; i++) {
- const sp<SpriteImpl>& sprite = mInvalidatedSprites.itemAt(i);
+ const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
sprite->resetDirtyLocked();
}
- mInvalidatedSprites.clear();
+ mLocked.invalidatedSprites.clear();
} // release lock
// Create missing surfaces.
@@ -105,8 +131,8 @@ void SpriteController::doUpdateSprites() {
SpriteUpdate& update = updates.editItemAt(i);
if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
- update.state.surfaceWidth = update.state.bitmap.width();
- update.state.surfaceHeight = update.state.bitmap.height();
+ update.state.surfaceWidth = update.state.icon.bitmap.width();
+ update.state.surfaceHeight = update.state.icon.bitmap.height();
update.state.surfaceDrawn = false;
update.state.surfaceVisible = false;
update.state.surfaceControl = obtainSurface(
@@ -123,8 +149,8 @@ void SpriteController::doUpdateSprites() {
SpriteUpdate& update = updates.editItemAt(i);
if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
- int32_t desiredWidth = update.state.bitmap.width();
- int32_t desiredHeight = update.state.bitmap.height();
+ int32_t desiredWidth = update.state.icon.bitmap.width();
+ int32_t desiredHeight = update.state.icon.bitmap.height();
if (update.state.surfaceWidth < desiredWidth
|| update.state.surfaceHeight < desiredHeight) {
if (!haveGlobalTransaction) {
@@ -187,16 +213,16 @@ void SpriteController::doUpdateSprites() {
SkPaint paint;
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
- surfaceCanvas.drawBitmap(update.state.bitmap, 0, 0, &paint);
+ surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
- if (surfaceInfo.w > uint32_t(update.state.bitmap.width())) {
+ if (surfaceInfo.w > uint32_t(update.state.icon.bitmap.width())) {
paint.setColor(0); // transparent fill color
- surfaceCanvas.drawRectCoords(update.state.bitmap.width(), 0,
- surfaceInfo.w, update.state.bitmap.height(), paint);
+ surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
+ surfaceInfo.w, update.state.icon.bitmap.height(), paint);
}
- if (surfaceInfo.h > uint32_t(update.state.bitmap.height())) {
+ if (surfaceInfo.h > uint32_t(update.state.icon.bitmap.height())) {
paint.setColor(0); // transparent fill color
- surfaceCanvas.drawRectCoords(0, update.state.bitmap.height(),
+ surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
surfaceInfo.w, surfaceInfo.h, paint);
}
@@ -246,8 +272,8 @@ void SpriteController::doUpdateSprites() {
&& (becomingVisible || (update.state.dirty & (DIRTY_POSITION
| DIRTY_HOTSPOT)))) {
status = update.state.surfaceControl->setPosition(
- update.state.positionX - update.state.hotSpotX,
- update.state.positionY - update.state.hotSpotY);
+ update.state.positionX - update.state.icon.hotSpotX,
+ update.state.positionY - update.state.icon.hotSpotY);
if (status) {
LOGE("Error %d setting sprite surface position.", status);
}
@@ -329,8 +355,10 @@ void SpriteController::doDisposeSurfaces() {
// Collect disposed surfaces.
Vector<sp<SurfaceControl> > disposedSurfaces;
{ // acquire lock
- disposedSurfaces = mDisposedSurfaces;
- mDisposedSurfaces.clear();
+ AutoMutex _l(mLock);
+
+ disposedSurfaces = mLocked.disposedSurfaces;
+ mLocked.disposedSurfaces.clear();
} // release lock
// Release the last reference to each surface outside of the lock.
@@ -349,7 +377,8 @@ sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height
sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
getpid(), String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888);
- if (surfaceControl == NULL) {
+ if (surfaceControl == NULL || !surfaceControl->isValid()
+ || !surfaceControl->getSurface()->isValid()) {
LOGE("Error creating sprite surface.");
return NULL;
}
@@ -360,7 +389,7 @@ sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height
// --- SpriteController::SpriteImpl ---
SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
- mController(controller), mTransactionNestingCount(0) {
+ mController(controller) {
}
SpriteController::SpriteImpl::~SpriteImpl() {
@@ -368,27 +397,33 @@ SpriteController::SpriteImpl::~SpriteImpl() {
// Let the controller take care of deleting the last reference to sprite
// surfaces so that we do not block the caller on an IPC here.
- if (mState.surfaceControl != NULL) {
- mController->disposeSurfaceLocked(mState.surfaceControl);
- mState.surfaceControl.clear();
+ if (mLocked.state.surfaceControl != NULL) {
+ mController->disposeSurfaceLocked(mLocked.state.surfaceControl);
+ mLocked.state.surfaceControl.clear();
}
}
-void SpriteController::SpriteImpl::setBitmap(const SkBitmap* bitmap,
- float hotSpotX, float hotSpotY) {
+void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
AutoMutex _l(mController->mLock);
- if (bitmap) {
- bitmap->copyTo(&mState.bitmap, SkBitmap::kARGB_8888_Config);
+ uint32_t dirty;
+ if (icon.isValid()) {
+ icon.bitmap.copyTo(&mLocked.state.icon.bitmap, SkBitmap::kARGB_8888_Config);
+
+ if (!mLocked.state.icon.isValid()
+ || mLocked.state.icon.hotSpotX != icon.hotSpotX
+ || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+ mLocked.state.icon.hotSpotX = icon.hotSpotX;
+ mLocked.state.icon.hotSpotY = icon.hotSpotY;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ } else {
+ dirty = DIRTY_BITMAP;
+ }
+ } else if (mLocked.state.icon.isValid()) {
+ mLocked.state.icon.bitmap.reset();
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
} else {
- mState.bitmap.reset();
- }
-
- uint32_t dirty = DIRTY_BITMAP;
- if (mState.hotSpotX != hotSpotX || mState.hotSpotY != hotSpotY) {
- mState.hotSpotX = hotSpotX;
- mState.hotSpotY = hotSpotY;
- dirty |= DIRTY_HOTSPOT;
+ return; // setting to invalid icon and already invalid so nothing to do
}
invalidateLocked(dirty);
@@ -397,8 +432,8 @@ void SpriteController::SpriteImpl::setBitmap(const SkBitmap* bitmap,
void SpriteController::SpriteImpl::setVisible(bool visible) {
AutoMutex _l(mController->mLock);
- if (mState.visible != visible) {
- mState.visible = visible;
+ if (mLocked.state.visible != visible) {
+ mLocked.state.visible = visible;
invalidateLocked(DIRTY_VISIBILITY);
}
}
@@ -406,9 +441,9 @@ void SpriteController::SpriteImpl::setVisible(bool visible) {
void SpriteController::SpriteImpl::setPosition(float x, float y) {
AutoMutex _l(mController->mLock);
- if (mState.positionX != x || mState.positionY != y) {
- mState.positionX = x;
- mState.positionY = y;
+ if (mLocked.state.positionX != x || mLocked.state.positionY != y) {
+ mLocked.state.positionX = x;
+ mLocked.state.positionY = y;
invalidateLocked(DIRTY_POSITION);
}
}
@@ -416,8 +451,8 @@ void SpriteController::SpriteImpl::setPosition(float x, float y) {
void SpriteController::SpriteImpl::setLayer(int32_t layer) {
AutoMutex _l(mController->mLock);
- if (mState.layer != layer) {
- mState.layer = layer;
+ if (mLocked.state.layer != layer) {
+ mLocked.state.layer = layer;
invalidateLocked(DIRTY_LAYER);
}
}
@@ -425,8 +460,8 @@ void SpriteController::SpriteImpl::setLayer(int32_t layer) {
void SpriteController::SpriteImpl::setAlpha(float alpha) {
AutoMutex _l(mController->mLock);
- if (mState.alpha != alpha) {
- mState.alpha = alpha;
+ if (mLocked.state.alpha != alpha) {
+ mLocked.state.alpha = alpha;
invalidateLocked(DIRTY_ALPHA);
}
}
@@ -435,38 +470,19 @@ void SpriteController::SpriteImpl::setTransformationMatrix(
const SpriteTransformationMatrix& matrix) {
AutoMutex _l(mController->mLock);
- if (mState.transformationMatrix != matrix) {
- mState.transformationMatrix = matrix;
+ if (mLocked.state.transformationMatrix != matrix) {
+ mLocked.state.transformationMatrix = matrix;
invalidateLocked(DIRTY_TRANSFORMATION_MATRIX);
}
}
-void SpriteController::SpriteImpl::openTransaction() {
- AutoMutex _l(mController->mLock);
-
- mTransactionNestingCount += 1;
-}
-
-void SpriteController::SpriteImpl::closeTransaction() {
- AutoMutex _l(mController->mLock);
-
- LOG_ALWAYS_FATAL_IF(mTransactionNestingCount == 0,
- "Sprite closeTransaction() called but there is no open sprite transaction");
+void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
+ bool wasDirty = mLocked.state.dirty;
+ mLocked.state.dirty |= dirty;
- mTransactionNestingCount -= 1;
- if (mTransactionNestingCount == 0 && mState.dirty) {
+ if (!wasDirty) {
mController->invalidateSpriteLocked(this);
}
}
-void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
- if (mTransactionNestingCount > 0) {
- bool wasDirty = mState.dirty;
- mState.dirty |= dirty;
- if (!wasDirty) {
- mController->invalidateSpriteLocked(this);
- }
- }
-}
-
} // namespace android
diff --git a/services/input/SpriteController.h b/services/input/SpriteController.h
index 27afb5e..50ae8a5 100644
--- a/services/input/SpriteController.h
+++ b/services/input/SpriteController.h
@@ -33,6 +33,8 @@ namespace android {
*/
struct SpriteTransformationMatrix {
inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { }
+ inline SpriteTransformationMatrix(float dsdx, float dtdx, float dsdy, float dtdy) :
+ dsdx(dsdx), dtdx(dtdx), dsdy(dsdy), dtdy(dtdy) { }
float dsdx;
float dtdx;
@@ -52,6 +54,35 @@ struct SpriteTransformationMatrix {
};
/*
+ * Icon that a sprite displays, including its hotspot.
+ */
+struct SpriteIcon {
+ inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { }
+ inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) :
+ bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
+
+ SkBitmap bitmap;
+ float hotSpotX;
+ float hotSpotY;
+
+ inline SpriteIcon copy() const {
+ SkBitmap bitmapCopy;
+ bitmap.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config);
+ return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY);
+ }
+
+ inline void reset() {
+ bitmap.reset();
+ hotSpotX = 0;
+ hotSpotY = 0;
+ }
+
+ inline bool isValid() const {
+ return !bitmap.isNull() && !bitmap.empty();
+ }
+};
+
+/*
* A sprite is a simple graphical object that is displayed on-screen above other layers.
* The basic sprite class is an interface.
* The implementation is provided by the sprite controller.
@@ -62,9 +93,21 @@ protected:
virtual ~Sprite() { }
public:
+ enum {
+ // The base layer for pointer sprites.
+ BASE_LAYER_POINTER = 0, // reserve space for 1 pointer
+
+ // The base layer for spot sprites.
+ BASE_LAYER_SPOT = 1, // reserve space for MAX_POINTER_ID spots
+ };
+
/* Sets the bitmap that is drawn by the sprite.
* The sprite retains a copy of the bitmap for subsequent rendering. */
- virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) = 0;
+ virtual void setIcon(const SpriteIcon& icon) = 0;
+
+ inline void clearIcon() {
+ setIcon(SpriteIcon());
+ }
/* Sets whether the sprite is visible. */
virtual void setVisible(bool visible) = 0;
@@ -81,14 +124,6 @@ public:
/* Sets the sprite transformation matrix. */
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
-
- /* Opens or closes a transaction to perform a batch of sprite updates as part of
- * a single operation such as setPosition and setAlpha. It is not necessary to
- * open a transaction when updating a single property.
- * Calls to openTransaction() nest and must be matched by an equal number
- * of calls to closeTransaction(). */
- virtual void openTransaction() = 0;
- virtual void closeTransaction() = 0;
};
/*
@@ -112,6 +147,14 @@ public:
/* Creates a new sprite, initially invisible. */
sp<Sprite> createSprite();
+ /* Opens or closes a transaction to perform a batch of sprite updates as part of
+ * a single operation such as setPosition and setAlpha. It is not necessary to
+ * open a transaction when updating a single property.
+ * Calls to openTransaction() nest and must be matched by an equal number
+ * of calls to closeTransaction(). */
+ void openTransaction();
+ void closeTransaction();
+
private:
enum {
MSG_UPDATE_SPRITES,
@@ -135,16 +178,14 @@ private:
* Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
struct SpriteState {
inline SpriteState() :
- dirty(0), hotSpotX(0), hotSpotY(0), visible(false),
+ dirty(0), visible(false),
positionX(0), positionY(0), layer(0), alpha(1.0f),
surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
}
uint32_t dirty;
- SkBitmap bitmap;
- float hotSpotX;
- float hotSpotY;
+ SpriteIcon icon;
bool visible;
float positionX;
float positionY;
@@ -159,7 +200,7 @@ private:
bool surfaceVisible;
inline bool wantSurfaceVisible() const {
- return visible && alpha > 0.0f && !bitmap.isNull() && !bitmap.empty();
+ return visible && alpha > 0.0f && icon.isValid();
}
};
@@ -177,37 +218,36 @@ private:
public:
SpriteImpl(const sp<SpriteController> controller);
- virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
+ virtual void setIcon(const SpriteIcon& icon);
virtual void setVisible(bool visible);
virtual void setPosition(float x, float y);
virtual void setLayer(int32_t layer);
virtual void setAlpha(float alpha);
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
- virtual void openTransaction();
- virtual void closeTransaction();
inline const SpriteState& getStateLocked() const {
- return mState;
+ return mLocked.state;
}
inline void resetDirtyLocked() {
- mState.dirty = 0;
+ mLocked.state.dirty = 0;
}
inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
int32_t width, int32_t height, bool drawn, bool visible) {
- mState.surfaceControl = surfaceControl;
- mState.surfaceWidth = width;
- mState.surfaceHeight = height;
- mState.surfaceDrawn = drawn;
- mState.surfaceVisible = visible;
+ mLocked.state.surfaceControl = surfaceControl;
+ mLocked.state.surfaceWidth = width;
+ mLocked.state.surfaceHeight = height;
+ mLocked.state.surfaceDrawn = drawn;
+ mLocked.state.surfaceVisible = visible;
}
private:
sp<SpriteController> mController;
- SpriteState mState; // guarded by mController->mLock
- uint32_t mTransactionNestingCount; // guarded by mController->mLock
+ struct Locked {
+ SpriteState state;
+ } mLocked; // guarded by mController->mLock
void invalidateLocked(uint32_t dirty);
};
@@ -232,8 +272,12 @@ private:
sp<SurfaceComposerClient> mSurfaceComposerClient;
- Vector<sp<SpriteImpl> > mInvalidatedSprites; // guarded by mLock
- Vector<sp<SurfaceControl> > mDisposedSurfaces; // guarded by mLock
+ struct Locked {
+ Vector<sp<SpriteImpl> > invalidatedSprites;
+ Vector<sp<SurfaceControl> > disposedSurfaces;
+ uint32_t transactionNestingCount;
+ bool deferredSpriteUpdate;
+ } mLocked; // guarded by mLock
void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index ba8ca9c..54bb9d7 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -97,6 +97,16 @@ private:
virtual void unfade() {
}
+
+ virtual void setPresentation(Presentation presentation) {
+ }
+
+ virtual void setSpots(SpotGesture spotGesture,
+ const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+ }
+
+ virtual void clearSpots() {
+ }
};
@@ -192,10 +202,6 @@ private:
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
return mPointerControllers.valueFor(deviceId);
}
-
- virtual sp<SpotControllerInterface> obtainSpotController(int32_t device) {
- return NULL;
- }
};
diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java
index b0978a3..69bde41 100644
--- a/services/java/com/android/server/wm/InputManager.java
+++ b/services/java/com/android/server/wm/InputManager.java
@@ -23,12 +23,6 @@ import org.xmlpull.v1.XmlPullParser;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.os.Looper;
import android.os.MessageQueue;
@@ -39,6 +33,7 @@ import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyEvent;
+import android.view.PointerIcon;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManager;
@@ -63,7 +58,8 @@ public class InputManager {
private final Context mContext;
private final WindowManagerService mWindowManagerService;
- private static native void nativeInit(Callbacks callbacks, MessageQueue messageQueue);
+ private static native void nativeInit(Context context,
+ Callbacks callbacks, MessageQueue messageQueue);
private static native void nativeStart();
private static native void nativeSetDisplaySize(int displayId, int width, int height);
private static native void nativeSetDisplayOrientation(int displayId, int rotation);
@@ -133,7 +129,7 @@ public class InputManager {
Looper looper = windowManagerService.mH.getLooper();
Slog.i(TAG, "Initializing input manager");
- nativeInit(mCallbacks, looper.getQueue());
+ nativeInit(mContext, mCallbacks, looper.getQueue());
}
public void start() {
@@ -435,48 +431,6 @@ public class InputManager {
}
}
- private static final class PointerIcon {
- public Bitmap bitmap;
- public float hotSpotX;
- public float hotSpotY;
-
- public static PointerIcon load(Resources resources, int resourceId) {
- PointerIcon icon = new PointerIcon();
-
- XmlResourceParser parser = resources.getXml(resourceId);
- final int bitmapRes;
- try {
- XmlUtils.beginDocument(parser, "pointer-icon");
-
- TypedArray a = resources.obtainAttributes(
- parser, com.android.internal.R.styleable.PointerIcon);
- bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
- icon.hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
- icon.hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
- a.recycle();
- } catch (Exception ex) {
- Slog.e(TAG, "Exception parsing pointer icon resource.", ex);
- return null;
- } finally {
- parser.close();
- }
-
- if (bitmapRes == 0) {
- Slog.e(TAG, "<pointer-icon> is missing bitmap attribute");
- return null;
- }
-
- Drawable drawable = resources.getDrawable(bitmapRes);
- if (!(drawable instanceof BitmapDrawable)) {
- Slog.e(TAG, "<pointer-icon> bitmap attribute must refer to a bitmap drawable");
- return null;
- }
-
- icon.bitmap = ((BitmapDrawable)drawable).getBitmap();
- return icon;
- }
- }
-
/*
* Callbacks from native.
*/
@@ -641,8 +595,7 @@ public class InputManager {
@SuppressWarnings("unused")
public PointerIcon getPointerIcon() {
- return PointerIcon.load(mContext.getResources(),
- com.android.internal.R.drawable.pointer_arrow_icon);
+ return PointerIcon.getDefaultIcon(mContext);
}
}
}
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index aaa305e..1f10d9c 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -36,13 +36,13 @@
#include <input/InputManager.h>
#include <input/PointerController.h>
-#include <input/SpotController.h>
#include <input/SpriteController.h>
#include <android_os_MessageQueue.h>
#include <android_view_KeyEvent.h>
#include <android_view_MotionEvent.h>
#include <android_view_InputChannel.h>
+#include <android_view_PointerIcon.h>
#include <android/graphics/GraphicsJNI.h>
#include "com_android_server_PowerManagerService.h"
@@ -101,12 +101,6 @@ static struct {
jfieldID navigation;
} gConfigurationClassInfo;
-static struct {
- jfieldID bitmap;
- jfieldID hotSpotX;
- jfieldID hotSpotY;
-} gPointerIconClassInfo;
-
// --- Global functions ---
@@ -128,17 +122,30 @@ static jobject getInputWindowHandleObjLocalRef(JNIEnv* env,
getInputWindowHandleObjLocalRef(env);
}
+static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t style,
+ SpriteIcon* outSpriteIcon) {
+ PointerIcon pointerIcon;
+ status_t status = android_view_PointerIcon_loadSystemIcon(env,
+ contextObj, style, &pointerIcon);
+ if (!status) {
+ pointerIcon.bitmap.copyTo(&outSpriteIcon->bitmap, SkBitmap::kARGB_8888_Config);
+ outSpriteIcon->hotSpotX = pointerIcon.hotSpotX;
+ outSpriteIcon->hotSpotY = pointerIcon.hotSpotY;
+ }
+}
+
// --- NativeInputManager ---
class NativeInputManager : public virtual RefBase,
public virtual InputReaderPolicyInterface,
- public virtual InputDispatcherPolicyInterface {
+ public virtual InputDispatcherPolicyInterface,
+ public virtual PointerControllerPolicyInterface {
protected:
virtual ~NativeInputManager();
public:
- NativeInputManager(jobject callbacksObj, const sp<Looper>& looper);
+ NativeInputManager(jobject contextObj, jobject callbacksObj, const sp<Looper>& looper);
inline sp<InputManager> getInputManager() const { return mInputManager; }
@@ -165,7 +172,6 @@ public:
virtual nsecs_t getVirtualKeyQuietTime();
virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
- virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId);
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -189,9 +195,14 @@ public:
virtual bool checkInjectEventsPermissionNonReentrant(
int32_t injectorPid, int32_t injectorUid);
+ /* --- PointerControllerPolicyInterface implementation --- */
+
+ virtual void loadPointerResources(PointerResources* outResources);
+
private:
sp<InputManager> mInputManager;
+ jobject mContextObj;
jobject mCallbacksObj;
sp<Looper> mLooper;
@@ -223,7 +234,7 @@ private:
wp<PointerController> pointerController;
} mLocked;
- void updateInactivityFadeDelayLocked(const sp<PointerController>& controller);
+ void updateInactivityTimeoutLocked(const sp<PointerController>& controller);
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
@@ -240,13 +251,15 @@ private:
-NativeInputManager::NativeInputManager(jobject callbacksObj, const sp<Looper>& looper) :
+NativeInputManager::NativeInputManager(jobject contextObj,
+ jobject callbacksObj, const sp<Looper>& looper) :
mLooper(looper),
mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1),
mKeyRepeatTimeout(-1), mKeyRepeatDelay(-1),
mMaxEventsPerSecond(-1) {
JNIEnv* env = jniEnv();
+ mContextObj = env->NewGlobalRef(contextObj);
mCallbacksObj = env->NewGlobalRef(callbacksObj);
{
@@ -265,6 +278,7 @@ NativeInputManager::NativeInputManager(jobject callbacksObj, const sp<Looper>& l
NativeInputManager::~NativeInputManager() {
JNIEnv* env = jniEnv();
+ env->DeleteGlobalRef(mContextObj);
env->DeleteGlobalRef(mCallbacksObj);
}
@@ -288,9 +302,13 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c
void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height) {
if (displayId == 0) {
- AutoMutex _l(mLock);
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ if (mLocked.displayWidth == width && mLocked.displayHeight == height) {
+ return;
+ }
- if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
mLocked.displayWidth = width;
mLocked.displayHeight = height;
@@ -298,7 +316,7 @@ void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_
if (controller != NULL) {
controller->setDisplaySize(width, height);
}
- }
+ } // release lock
}
}
@@ -428,40 +446,33 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32
if (controller == NULL) {
ensureSpriteControllerLocked();
- controller = new PointerController(mLooper, mLocked.spriteController);
+ controller = new PointerController(this, mLooper, mLocked.spriteController);
mLocked.pointerController = controller;
controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight);
controller->setDisplayOrientation(mLocked.displayOrientation);
JNIEnv* env = jniEnv();
- jobject iconObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.getPointerIcon);
- if (!checkAndClearExceptionFromCallback(env, "getPointerIcon") && iconObj) {
- jfloat iconHotSpotX = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotX);
- jfloat iconHotSpotY = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotY);
- jobject iconBitmapObj = env->GetObjectField(iconObj, gPointerIconClassInfo.bitmap);
- if (iconBitmapObj) {
- SkBitmap* iconBitmap = GraphicsJNI::getNativeBitmap(env, iconBitmapObj);
- if (iconBitmap) {
- controller->setPointerIcon(iconBitmap, iconHotSpotX, iconHotSpotY);
- }
- env->DeleteLocalRef(iconBitmapObj);
+ jobject pointerIconObj = env->CallObjectMethod(mCallbacksObj,
+ gCallbacksClassInfo.getPointerIcon);
+ if (!checkAndClearExceptionFromCallback(env, "getPointerIcon")) {
+ PointerIcon pointerIcon;
+ status_t status = android_view_PointerIcon_load(env, pointerIconObj,
+ mContextObj, &pointerIcon);
+ if (!status && !pointerIcon.isNullIcon()) {
+ controller->setPointerIcon(SpriteIcon(pointerIcon.bitmap,
+ pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+ } else {
+ controller->setPointerIcon(SpriteIcon());
}
- env->DeleteLocalRef(iconObj);
+ env->DeleteLocalRef(pointerIconObj);
}
- updateInactivityFadeDelayLocked(controller);
+ updateInactivityTimeoutLocked(controller);
}
return controller;
}
-sp<SpotControllerInterface> NativeInputManager::obtainSpotController(int32_t deviceId) {
- AutoMutex _l(mLock);
-
- ensureSpriteControllerLocked();
- return new SpotController(mLooper, mLocked.spriteController);
-}
-
void NativeInputManager::ensureSpriteControllerLocked() {
if (mLocked.spriteController == NULL) {
JNIEnv* env = jniEnv();
@@ -642,16 +653,16 @@ void NativeInputManager::setSystemUiVisibility(int32_t visibility) {
sp<PointerController> controller = mLocked.pointerController.promote();
if (controller != NULL) {
- updateInactivityFadeDelayLocked(controller);
+ updateInactivityTimeoutLocked(controller);
}
}
}
-void NativeInputManager::updateInactivityFadeDelayLocked(const sp<PointerController>& controller) {
+void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) {
bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
- controller->setInactivityFadeDelay(lightsOut
- ? PointerController::INACTIVITY_FADE_DELAY_SHORT
- : PointerController::INACTIVITY_FADE_DELAY_NORMAL);
+ controller->setInactivityTimeout(lightsOut
+ ? PointerController::INACTIVITY_TIMEOUT_SHORT
+ : PointerController::INACTIVITY_TIMEOUT_NORMAL);
}
bool NativeInputManager::isScreenOn() {
@@ -884,6 +895,17 @@ bool NativeInputManager::checkInjectEventsPermissionNonReentrant(
return result;
}
+void NativeInputManager::loadPointerResources(PointerResources* outResources) {
+ JNIEnv* env = jniEnv();
+
+ loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_HOVER,
+ &outResources->spotHover);
+ loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_TOUCH,
+ &outResources->spotTouch);
+ loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_ANCHOR,
+ &outResources->spotAnchor);
+}
+
// ----------------------------------------------------------------------------
@@ -899,10 +921,10 @@ static bool checkInputManagerUnitialized(JNIEnv* env) {
}
static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
- jobject callbacks, jobject messageQueueObj) {
+ jobject contextObj, jobject callbacksObj, jobject messageQueueObj) {
if (gNativeInputManager == NULL) {
sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
- gNativeInputManager = new NativeInputManager(callbacks, looper);
+ gNativeInputManager = new NativeInputManager(contextObj, callbacksObj, looper);
} else {
LOGE("Input manager already initialized.");
jniThrowRuntimeException(env, "Input manager already initialized.");
@@ -1246,7 +1268,8 @@ static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz)
static JNINativeMethod gInputManagerMethods[] = {
/* name, signature, funcPtr */
- { "nativeInit", "(Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
+ { "nativeInit", "(Landroid/content/Context;"
+ "Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
(void*) android_server_InputManager_nativeInit },
{ "nativeStart", "()V",
(void*) android_server_InputManager_nativeStart },
@@ -1372,7 +1395,7 @@ int register_android_server_InputManager(JNIEnv* env) {
"getPointerLayer", "()I");
GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, clazz,
- "getPointerIcon", "()Lcom/android/server/wm/InputManager$PointerIcon;");
+ "getPointerIcon", "()Landroid/view/PointerIcon;");
// KeyEvent
@@ -1421,19 +1444,6 @@ int register_android_server_InputManager(JNIEnv* env) {
GET_FIELD_ID(gConfigurationClassInfo.navigation, clazz,
"navigation", "I");
- // PointerIcon
-
- FIND_CLASS(clazz, "com/android/server/wm/InputManager$PointerIcon");
-
- GET_FIELD_ID(gPointerIconClassInfo.bitmap, clazz,
- "bitmap", "Landroid/graphics/Bitmap;");
-
- GET_FIELD_ID(gPointerIconClassInfo.hotSpotX, clazz,
- "hotSpotX", "F");
-
- GET_FIELD_ID(gPointerIconClassInfo.hotSpotY, clazz,
- "hotSpotY", "F");
-
return 0;
}