summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/accounts/AccountManagerService.java7
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/app/Fragment.java2
-rw-r--r--core/java/android/app/Service.java4
-rw-r--r--core/java/android/content/ContentProvider.java2
-rw-r--r--core/java/android/content/SyncManager.java59
-rw-r--r--core/java/android/content/SyncStorageEngine.java20
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/view/HardwareRenderer.java53
-rw-r--r--core/java/android/view/MotionEvent.java78
-rw-r--r--core/java/android/view/TextureView.java13
-rw-r--r--core/java/android/webkit/WebView.java1
-rw-r--r--core/java/android/webkit/WebViewClassic.java1064
-rw-r--r--core/java/android/webkit/WebViewCore.java103
-rw-r--r--core/java/android/webkit/WebViewInputDispatcher.java1151
15 files changed, 1519 insertions, 1052 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 241fecb..2b643c2 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -1869,9 +1869,12 @@ public class AccountManagerService
File systemDir = Environment.getSystemSecureDirectory();
File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME);
if (userId == 0) {
- // Migrate old file, if it exists, to the new location
+ // Migrate old file, if it exists, to the new location.
+ // Make sure the new file doesn't already exist. A dummy file could have been
+ // accidentally created in the old location, causing the new one to become corrupted
+ // as well.
File oldFile = new File(systemDir, DATABASE_NAME);
- if (oldFile.exists()) {
+ if (oldFile.exists() && !databaseFile.exists()) {
// Check for use directory; create if it doesn't exist, else renameTo will fail
File userDir = new File(systemDir, "users/" + userId);
if (!userDir.exists()) {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 227900e..1c820dc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4656,7 +4656,7 @@ public class Activity extends ContextThemeWrapper
/**
* Print the Activity's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity <activity_component_name>".
+ * you run "adb shell dumpsys activity &lt;activity_component_name&gt;".
*
* @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index c493f0f..d3ba497 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -203,7 +203,7 @@ final class FragmentState implements Parcelable {
* <li> {@link #onCreateView} creates and returns the view hierarchy associated
* with the fragment.
* <li> {@link #onActivityCreated} tells the fragment that its activity has
- * completed its own {@link Activity#onCreate Activity.onCreaate}.
+ * completed its own {@link Activity#onCreate Activity.onCreate()}.
* <li> {@link #onStart} makes the fragment visible to the user (based on its
* containing activity being started).
* <li> {@link #onResume} makes the fragment interacting with the user (based on its
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 207ae76..cb43d4c 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -666,8 +666,8 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
/**
* Print the Service's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity service <yourservicename>".
- * This is distinct from "dumpsys <servicename>", which only works for
+ * you run "adb shell dumpsys activity service &lt;yourservicename&gt;".
+ * This is distinct from "dumpsys &lt;servicename&gt;", which only works for
* named system services and which invokes the {@link IBinder#dump} method
* on the {@link IBinder} interface registered with ServiceManager.
*
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 05ef194..1206056 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1127,7 +1127,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
/**
* Print the Provider's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity provider <provider_component_name>".
+ * you run "adb shell dumpsys activity provider &lt;provider_component_name&gt;".
*
* @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 6219de7..34c40a0 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -205,6 +205,9 @@ public class SyncManager implements OnAccountsUpdateListener {
private final PowerManager mPowerManager;
+ // Use this as a random offset to seed all periodic syncs
+ private int mSyncRandomOffsetMillis;
+
private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
@@ -438,6 +441,9 @@ public class SyncManager implements OnAccountsUpdateListener {
// do this synchronously to ensure we have the accounts before this call returns
onAccountsUpdated(null);
}
+
+ // Pick a random second in a day to seed all periodic syncs
+ mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
}
/**
@@ -666,6 +672,7 @@ public class SyncManager implements OnAccountsUpdateListener {
private void sendCheckAlarmsMessage() {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
+ mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS);
mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
}
@@ -714,6 +721,8 @@ public class SyncManager implements OnAccountsUpdateListener {
}
private void increaseBackoffSetting(SyncOperation op) {
+ // TODO: Use this function to align it to an already scheduled sync
+ // operation in the specified window
final long now = SystemClock.elapsedRealtime();
final Pair<Long, Long> previousSettings =
@@ -1060,6 +1069,8 @@ public class SyncManager implements OnAccountsUpdateListener {
final long now = SystemClock.elapsedRealtime();
pw.print("now: "); pw.print(now);
pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
+ pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000));
+ pw.println(" (HH:MM:SS)");
pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
pw.println(" (HH:MM:SS)");
pw.print("time spent syncing: ");
@@ -1771,6 +1782,9 @@ public class SyncManager implements OnAccountsUpdateListener {
AccountAndUser[] accounts = mAccounts;
final long nowAbsolute = System.currentTimeMillis();
+ final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
+ ? (nowAbsolute - mSyncRandomOffsetMillis) : 0;
+
ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
for (SyncStorageEngine.AuthorityInfo info : infos) {
// skip the sync if the account of this operation no longer exists
@@ -1792,16 +1806,32 @@ public class SyncManager implements OnAccountsUpdateListener {
SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
final Bundle extras = info.periodicSyncs.get(i).first;
- final Long periodInSeconds = info.periodicSyncs.get(i).second;
+ final Long periodInMillis = info.periodicSyncs.get(i).second * 1000;
// find when this periodic sync was last scheduled to run
final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
- // compute when this periodic sync should next run - this can be in the future
- // for example if the user changed the time, synced and changed back.
- final long nextPollTimeAbsolute = lastPollTimeAbsolute > nowAbsolute
- ? nowAbsolute
- : lastPollTimeAbsolute + periodInSeconds * 1000;
- // if it is ready to run then schedule it and mark it as having been scheduled
- if (nextPollTimeAbsolute <= nowAbsolute) {
+
+ long remainingMillis
+ = periodInMillis - (shiftedNowAbsolute % periodInMillis);
+
+ /*
+ * Sync scheduling strategy:
+ * Set the next periodic sync based on a random offset (in seconds).
+ *
+ * Also sync right now if any of the following cases hold
+ * and mark it as having been scheduled
+ *
+ * Case 1: This sync is ready to run now.
+ * Case 2: If the lastPollTimeAbsolute is in the future,
+ * sync now and reinitialize. This can happen for
+ * example if the user changed the time, synced and
+ * changed back.
+ * Case 3: If we failed to sync at the last scheduled time
+ */
+ if (remainingMillis == periodInMillis // Case 1
+ || lastPollTimeAbsolute > nowAbsolute // Case 2
+ || (nowAbsolute - lastPollTimeAbsolute
+ >= periodInMillis)) { // Case 3
+ // Sync now
final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
info.account, info.userId, info.authority);
final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
@@ -1819,12 +1849,13 @@ public class SyncManager implements OnAccountsUpdateListener {
info.account, info.userId, info.authority),
syncAdapterInfo.type.allowParallelSyncs()));
status.setPeriodicSyncTime(i, nowAbsolute);
- } else {
- // it isn't ready to run, remember this time if it is earlier than
- // earliestFuturePollTime
- if (nextPollTimeAbsolute < earliestFuturePollTime) {
- earliestFuturePollTime = nextPollTimeAbsolute;
- }
+ }
+ // Compute when this periodic sync should next run
+ final long nextPollTimeAbsolute = nowAbsolute + remainingMillis;
+
+ // remember this time if it is earlier than earliestFuturePollTime
+ if (nextPollTimeAbsolute < earliestFuturePollTime) {
+ earliestFuturePollTime = nextPollTimeAbsolute;
}
}
}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index d3baf70..d821918 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -37,6 +37,7 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
@@ -49,6 +50,7 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.Random;
import java.util.TimeZone;
import java.util.List;
@@ -65,6 +67,7 @@ public class SyncStorageEngine extends Handler {
private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
+ private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
private static final String XML_ATTR_ENABLED = "enabled";
private static final String XML_ATTR_USER = "user";
private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
@@ -277,6 +280,8 @@ public class SyncStorageEngine extends Handler {
private static volatile SyncStorageEngine sSyncStorageEngine = null;
+ private int mSyncRandomOffset;
+
/**
* This file contains the core engine state: all accounts and the
* settings for them. It must never be lost, and should be changed
@@ -375,6 +380,10 @@ public class SyncStorageEngine extends Handler {
}
}
+ public int getSyncRandomOffset() {
+ return mSyncRandomOffset;
+ }
+
public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
synchronized (mAuthorities) {
mChangeListeners.register(callback, mask);
@@ -1465,6 +1474,16 @@ public class SyncStorageEngine extends Handler {
} catch (NumberFormatException e) {
// don't care
}
+ String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
+ try {
+ mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
+ } catch (NumberFormatException e) {
+ mSyncRandomOffset = 0;
+ }
+ if (mSyncRandomOffset == 0) {
+ Random random = new Random(System.currentTimeMillis());
+ mSyncRandomOffset = random.nextInt(86400);
+ }
mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
eventType = parser.next();
AuthorityInfo authority = null;
@@ -1705,6 +1724,7 @@ public class SyncStorageEngine extends Handler {
out.startTag(null, "accounts");
out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
+ out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
// Write the Sync Automatically flags for each user
final int M = mMasterSyncAutomatically.size();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b06b4a5..5d890d4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1095,6 +1095,18 @@ public abstract class PackageManager {
/** {@hide} */
public static final int ENFORCEMENT_YES = 1;
+ /** {@hide} */
+ public static String enforcementToString(int enforcement) {
+ switch (enforcement) {
+ case ENFORCEMENT_DEFAULT:
+ return "DEFAULT";
+ case ENFORCEMENT_YES:
+ return "YES";
+ default:
+ return Integer.toString(enforcement);
+ }
+ }
+
/**
* Retrieve overall information about an application package that is
* installed on the system.
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index aa0ac74..8bc36b7 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -237,6 +237,17 @@ public abstract class HardwareRenderer {
abstract boolean validate();
/**
+ * This method ensures the hardware renderer is in a valid state
+ * before executing the specified action.
+ *
+ * This method will attempt to set a valid state even if the window
+ * the renderer is attached to was destroyed.
+ *
+ * @return true if the action was run
+ */
+ abstract boolean safelyRun(Runnable action);
+
+ /**
* Setup the hardware renderer for drawing. This is called whenever the
* size of the target surface changes or when the surface is first created.
*
@@ -1380,26 +1391,40 @@ public abstract class HardwareRenderer {
}
@Override
- void destroyHardwareResources(View view) {
- if (view != null) {
- boolean needsContext = true;
- if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false;
-
- if (needsContext) {
- Gl20RendererEglContext managedContext =
- (Gl20RendererEglContext) sEglContextStorage.get();
- if (managedContext == null) return;
- usePbufferSurface(managedContext.getContext());
- }
-
- destroyResources(view);
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
+ boolean safelyRun(Runnable action) {
+ boolean needsContext = true;
+ if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false;
+
+ if (needsContext) {
+ Gl20RendererEglContext managedContext =
+ (Gl20RendererEglContext) sEglContextStorage.get();
+ if (managedContext == null) return false;
+ usePbufferSurface(managedContext.getContext());
+ }
+ try {
+ action.run();
+ } finally {
if (needsContext) {
sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
}
+
+ return true;
+ }
+
+ @Override
+ void destroyHardwareResources(final View view) {
+ if (view != null) {
+ safelyRun(new Runnable() {
+ @Override
+ public void run() {
+ destroyResources(view);
+ GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
+ }
+ });
+ }
}
private static void destroyResources(View view) {
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 77fd8d2..e51ba3d 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2715,6 +2715,67 @@ public final class MotionEvent extends InputEvent implements Parcelable {
}
/**
+ * Adds all of the movement samples of the specified event to this one if
+ * it is compatible. To be compatible, the event must have the same device id,
+ * source, action, flags, pointer count, pointer properties.
+ *
+ * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
+ *
+ * @param event The event whose movements samples should be added to this one
+ * if possible.
+ * @return True if batching was performed or false if batching was not possible.
+ * @hide
+ */
+ public final boolean addBatch(MotionEvent event) {
+ final int action = nativeGetAction(mNativePtr);
+ if (action != ACTION_MOVE && action != ACTION_HOVER_MOVE) {
+ return false;
+ }
+ if (action != nativeGetAction(event.mNativePtr)) {
+ return false;
+ }
+
+ if (nativeGetDeviceId(mNativePtr) != nativeGetDeviceId(event.mNativePtr)
+ || nativeGetSource(mNativePtr) != nativeGetSource(event.mNativePtr)
+ || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)) {
+ return false;
+ }
+
+ final int pointerCount = nativeGetPointerCount(mNativePtr);
+ if (pointerCount != nativeGetPointerCount(event.mNativePtr)) {
+ return false;
+ }
+
+ synchronized (gSharedTempLock) {
+ ensureSharedTempPointerCapacity(Math.max(pointerCount, 2));
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ final PointerCoords[] pc = gSharedTempPointerCoords;
+
+ for (int i = 0; i < pointerCount; i++) {
+ nativeGetPointerProperties(mNativePtr, i, pp[0]);
+ nativeGetPointerProperties(event.mNativePtr, i, pp[1]);
+ if (!pp[0].equals(pp[1])) {
+ return false;
+ }
+ }
+
+ final int metaState = nativeGetMetaState(event.mNativePtr);
+ final int historySize = nativeGetHistorySize(event.mNativePtr);
+ for (int h = 0; h <= historySize; h++) {
+ final int historyPos = (h == historySize ? HISTORY_CURRENT : h);
+
+ for (int i = 0; i < pointerCount; i++) {
+ nativeGetPointerCoords(event.mNativePtr, i, historyPos, pc[i]);
+ }
+
+ final long eventTimeNanos = nativeGetEventTimeNanos(event.mNativePtr, historyPos);
+ nativeAddBatch(mNativePtr, eventTimeNanos, pc, metaState);
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns true if all points in the motion event are completely within the specified bounds.
* @hide
*/
@@ -3416,5 +3477,22 @@ public final class MotionEvent extends InputEvent implements Parcelable {
id = other.id;
toolType = other.toolType;
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof PointerProperties) {
+ return equals((PointerProperties)other);
+ }
+ return false;
+ }
+
+ private boolean equals(PointerProperties other) {
+ return other != null && id == other.id && toolType == other.toolType;
+ }
+
+ @Override
+ public int hashCode() {
+ return id | (toolType << 8);
+ }
}
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 3cd8b71..ba62e65 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -204,7 +204,18 @@ public class TextureView extends View {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- destroySurface();
+ if (mLayer != null && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) {
+ boolean success = mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
+ @Override
+ public void run() {
+ destroySurface();
+ }
+ });
+
+ if (!success) {
+ Log.w(LOG_TAG, "TextureView was not able to destroy its surface: " + this);
+ }
+ }
}
private void destroySurface() {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 84632c6..d1cfc6b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1539,6 +1539,7 @@ public class WebView extends AbsoluteLayout
*
* @deprecated The built-in zoom mechanism is preferred, see
* {@link WebSettings#setBuiltInZoomControls(boolean)}.
+ * @hide since API version 16.
*/
@Deprecated
public View getZoomControls() {
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index d7ebf5c..5dc2681 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -57,6 +57,7 @@ import android.net.http.SslCertificate;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.provider.Settings;
@@ -100,7 +101,6 @@ import android.webkit.WebView.PictureListener;
import android.webkit.WebViewCore.DrawData;
import android.webkit.WebViewCore.EventHub;
import android.webkit.WebViewCore.TextFieldInitData;
-import android.webkit.WebViewCore.TouchEventData;
import android.webkit.WebViewCore.TouchHighlightData;
import android.webkit.WebViewCore.WebKitHitTest;
import android.widget.AbsoluteLayout;
@@ -981,6 +981,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
/**
* Touch mode
+ * TODO: Some of this is now unnecessary as it is handled by
+ * WebInputTouchDispatcher (such as click, long press, and double tap).
*/
private int mTouchMode = TOUCH_DONE_MODE;
private static final int TOUCH_INIT_MODE = 1;
@@ -994,35 +996,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private static final int TOUCH_DRAG_LAYER_MODE = 9;
private static final int TOUCH_DRAG_TEXT_MODE = 10;
- // Whether to forward the touch events to WebCore
- // Can only be set by WebKit via JNI.
- private boolean mForwardTouchEvents = false;
-
- // Whether to prevent default during touch. The initial value depends on
- // mForwardTouchEvents. If WebCore wants all the touch events, it says yes
- // for touch down. Otherwise UI will wait for the answer of the first
- // confirmed move before taking over the control.
- private static final int PREVENT_DEFAULT_NO = 0;
- private static final int PREVENT_DEFAULT_MAYBE_YES = 1;
- private static final int PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN = 2;
- private static final int PREVENT_DEFAULT_YES = 3;
- private static final int PREVENT_DEFAULT_IGNORE = 4;
- private int mPreventDefault = PREVENT_DEFAULT_IGNORE;
-
// true when the touch movement exceeds the slop
private boolean mConfirmMove;
private boolean mTouchInEditText;
- // if true, touch events will be first processed by WebCore, if prevent
- // default is not set, the UI will continue handle them.
- private boolean mDeferTouchProcess;
-
- // to avoid interfering with the current touch events, track them
- // separately. Currently no snapping or fling in the deferred process mode
- private int mDeferTouchMode = TOUCH_DONE_MODE;
- private float mLastDeferTouchX;
- private float mLastDeferTouchY;
-
// Whether or not to draw the cursor ring.
private boolean mDrawCursorRing = true;
@@ -1410,7 +1387,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private boolean mSentAutoScrollMessage = false;
// used for serializing asynchronously handled touch events.
- private final TouchEventQueue mTouchEventQueue = new TouchEventQueue();
+ private WebViewInputDispatcher mInputDispatcher;
// Used to track whether picture updating was paused due to a window focus change.
private boolean mPictureUpdatePausedForFocusChange = false;
@@ -1500,6 +1477,68 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
+ private void onHandleUiEvent(MotionEvent event, int eventType, int flags) {
+ switch (eventType) {
+ case WebViewInputDispatcher.EVENT_TYPE_LONG_PRESS:
+ HitTestResult hitTest = getHitTestResult();
+ if (hitTest != null
+ && hitTest.getType() != HitTestResult.UNKNOWN_TYPE) {
+ performLongClick();
+ }
+ break;
+ case WebViewInputDispatcher.EVENT_TYPE_DOUBLE_TAP:
+ mZoomManager.handleDoubleTap(event.getX(), event.getY());
+ break;
+ case WebViewInputDispatcher.EVENT_TYPE_TOUCH:
+ onHandleUiTouchEvent(event);
+ break;
+ }
+ }
+
+ private void onHandleUiTouchEvent(MotionEvent ev) {
+ final ScaleGestureDetector detector =
+ mZoomManager.getMultiTouchGestureDetector();
+
+ float x = ev.getX();
+ float y = ev.getY();
+
+ if (detector != null) {
+ detector.onTouchEvent(ev);
+ if (detector.isInProgress()) {
+ mLastTouchTime = ev.getEventTime();
+ x = detector.getFocusX();
+ y = detector.getFocusY();
+
+ mWebView.cancelLongPress();
+ mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ if (!mZoomManager.supportsPanDuringZoom()) {
+ return;
+ }
+ mTouchMode = TOUCH_DRAG_MODE;
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ }
+ }
+
+ int action = ev.getActionMasked();
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ cancelTouch();
+ action = MotionEvent.ACTION_DOWN;
+ } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) {
+ // set mLastTouchX/Y to the remaining points for multi-touch.
+ mLastTouchX = Math.round(x);
+ mLastTouchY = Math.round(y);
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ // negative x or y indicate it is on the edge, skip it.
+ if (x < 0 || y < 0) {
+ return;
+ }
+ }
+
+ handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
+ }
+
// The webview that is bound to this WebViewClassic instance. Primarily needed for supplying
// as the first param in the WebViewClient and WebChromeClient callbacks.
final private WebView mWebView;
@@ -4372,8 +4411,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
boolean animateScroll = ((!mScroller.isFinished()
|| mVelocityTracker != null)
&& (mTouchMode != TOUCH_DRAG_MODE ||
- mHeldMotionless != MOTIONLESS_TRUE))
- || mDeferTouchMode == TOUCH_DRAG_MODE;
+ mHeldMotionless != MOTIONLESS_TRUE));
if (mTouchMode == TOUCH_DRAG_MODE) {
if (mHeldMotionless == MOTIONLESS_PENDING) {
mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
@@ -5573,7 +5611,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
addAccessibilityApisToJavaScript();
- mTouchEventQueue.reset();
updateHwAccelerated();
}
@@ -5822,20 +5859,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
private static final float MMA_WEIGHT_N = 5;
- private boolean hitFocusedPlugin(int contentX, int contentY) {
- // TODO: Figure out what to do with this (b/6111517)
- return false;
- }
-
- private boolean shouldForwardTouchEvent() {
- if (mFullScreenHolder != null) return true;
- if (mBlockWebkitViewMessages) return false;
- return mForwardTouchEvents
- && !mSelectingText
- && mPreventDefault != PREVENT_DEFAULT_IGNORE
- && mPreventDefault != PREVENT_DEFAULT_NO;
- }
-
private boolean inFullScreenMode() {
return mFullScreenHolder != null;
}
@@ -5905,23 +5928,17 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mWebView.requestFocus();
}
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, ev + " at " + ev.getEventTime()
- + " mTouchMode=" + mTouchMode
- + " numPointers=" + ev.getPointerCount());
+ if (mInputDispatcher == null) {
+ return false;
}
- // If WebKit wasn't interested in this multitouch gesture, enqueue
- // the event for handling directly rather than making the round trip
- // to WebKit and back.
- if (ev.getPointerCount() > 1 && mPreventDefault != PREVENT_DEFAULT_NO) {
- passMultiTouchToWebKit(ev, mTouchEventQueue.nextTouchSequence());
+ if (mInputDispatcher.postPointerEvent(ev, getScrollX(),
+ getScrollY() - getTitleHeight(), mZoomManager.getInvScale())) {
+ return true;
} else {
- mTouchEventQueue.enqueueTouchEvent(ev);
+ Log.w(LOGTAG, "mInputDispatcher rejected the event!");
+ return false;
}
-
- // Since all events are handled asynchronously, we always want the gesture stream.
- return true;
}
private float calculateDragAngle(int dx, int dy) {
@@ -5931,12 +5948,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/*
- * Common code for single touch and multi-touch.
- * (x, y) denotes current focus point, which is the touch point for single touch
- * and the middle point for multi-touch.
- */
- private boolean handleTouchEventCommon(MotionEvent ev, int action, int x, int y) {
- long eventTime = ev.getEventTime();
+ * Common code for single touch and multi-touch.
+ * (x, y) denotes current focus point, which is the touch point for single touch
+ * and the middle point for multi-touch.
+ */
+ private void handleTouchEventCommon(MotionEvent event, int action, int x, int y) {
+ ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
+
+ long eventTime = event.getEventTime();
// Due to the touch screen edge effect, a touch closer to the edge
// always snapped to the edge. As getViewWidth() can be different from
@@ -5952,7 +5971,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
switch (action) {
case MotionEvent.ACTION_DOWN: {
- mPreventDefault = PREVENT_DEFAULT_NO;
mConfirmMove = false;
mInitialHitTestResult = null;
if (!mEditTextScroller.isFinished()) {
@@ -5972,20 +5990,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
mTouchMode = TOUCH_DOUBLE_TAP_MODE;
} else {
- // commit the short press action for the previous tap
- doShortPress();
mTouchMode = TOUCH_INIT_MODE;
- mDeferTouchProcess = !mBlockWebkitViewMessages
- && (!inFullScreenMode() && mForwardTouchEvents)
- ? hitFocusedPlugin(contentX, contentY)
- : false;
}
} else { // the normal case
mTouchMode = TOUCH_INIT_MODE;
- mDeferTouchProcess = !mBlockWebkitViewMessages
- && (!inFullScreenMode() && mForwardTouchEvents)
- ? hitFocusedPlugin(contentX, contentY)
- : false;
+ // TODO: Have WebViewInputDispatch handle this
TouchHighlightData data = new TouchHighlightData();
data.mX = contentX;
data.mY = contentY;
@@ -5999,19 +6008,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mWebViewCore.sendMessageAtFrontOfQueue(
EventHub.HIT_TEST, data);
}
- if (DEBUG_TOUCH_HIGHLIGHT) {
- if (getSettings().getNavDump()) {
- mTouchHighlightX = x + getScrollX();
- mTouchHighlightY = y + getScrollY();
- mPrivateHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- mTouchHighlightX = mTouchHighlightY = 0;
- invalidate();
- }
- }, TOUCH_HIGHLIGHT_ELAPSE_TIME);
- }
- }
if (mLogEvent && eventTime - mLastTouchUpTime < 1000) {
EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION,
(eventTime - mLastTouchUpTime), eventTime);
@@ -6058,43 +6054,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
SWITCH_TO_SHORTPRESS, TAP_TIMEOUT);
mPrivateHandler.sendEmptyMessageDelayed(
SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
- if (inFullScreenMode() || mDeferTouchProcess) {
- mPreventDefault = PREVENT_DEFAULT_YES;
- } else if (!mBlockWebkitViewMessages && mForwardTouchEvents) {
- mPreventDefault = PREVENT_DEFAULT_MAYBE_YES;
- } else {
- mPreventDefault = PREVENT_DEFAULT_NO;
- }
- // pass the touch events from UI thread to WebCore thread
- if (shouldForwardTouchEvent()) {
- TouchEventData ted = new TouchEventData();
- ted.mAction = action;
- ted.mIds = new int[1];
- ted.mIds[0] = ev.getPointerId(0);
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(contentX, contentY);
- ted.mPointsInView = new Point[1];
- ted.mPointsInView[0] = new Point(x, y);
- ted.mMetaState = ev.getMetaState();
- ted.mReprocess = mDeferTouchProcess;
- ted.mNativeLayer = nativeScrollableLayer(
- contentX, contentY, ted.mNativeLayerRect, null);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- if (mDeferTouchProcess) {
- // still needs to set them for compute deltaX/Y
- mLastTouchX = x;
- mLastTouchY = y;
- break;
- }
- if (!inFullScreenMode()) {
- mPrivateHandler.removeMessages(PREVENT_DEFAULT_TIMEOUT);
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
- action, 0), TAP_TIMEOUT);
- }
- }
}
startTouch(x, y, eventTime);
if (mIsEditingText) {
@@ -6104,13 +6063,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
}
case MotionEvent.ACTION_MOVE: {
- boolean firstMove = false;
if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY)
>= mTouchSlopSquare) {
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
mConfirmMove = true;
- firstMove = true;
if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
mTouchMode = TOUCH_INIT_MODE;
}
@@ -6149,48 +6106,16 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
}
- // pass the touch events from UI thread to WebCore thread
- if (shouldForwardTouchEvent() && mConfirmMove && (firstMove
- || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
- TouchEventData ted = new TouchEventData();
- ted.mAction = action;
- ted.mIds = new int[1];
- ted.mIds[0] = ev.getPointerId(0);
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(contentX, contentY);
- ted.mPointsInView = new Point[1];
- ted.mPointsInView[0] = new Point(x, y);
- ted.mMetaState = ev.getMetaState();
- ted.mReprocess = mDeferTouchProcess;
- ted.mNativeLayer = mCurrentScrollingLayerId;
- ted.mNativeLayerRect.set(mScrollingLayerRect);
- ted.mMotionEvent = MotionEvent.obtain(ev);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- mLastSentTouchTime = eventTime;
- if (mDeferTouchProcess) {
- break;
- }
- if (firstMove && !inFullScreenMode()) {
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
- action, 0), TAP_TIMEOUT);
- }
- }
- if (mTouchMode == TOUCH_DONE_MODE
- || mPreventDefault == PREVENT_DEFAULT_YES) {
+ if (mTouchMode == TOUCH_DONE_MODE) {
// no dragging during scroll zoom animation, or when prevent
// default is yes
break;
}
if (mVelocityTracker == null) {
Log.e(LOGTAG, "Got null mVelocityTracker when "
- + "mPreventDefault = " + mPreventDefault
- + " mDeferTouchProcess = " + mDeferTouchProcess
+ " mTouchMode = " + mTouchMode);
} else {
- mVelocityTracker.addMovement(ev);
+ mVelocityTracker.addMovement(event);
}
if (mTouchMode != TOUCH_DRAG_MODE &&
@@ -6201,19 +6126,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
}
- if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES
- || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
- // track mLastTouchTime as we may need to do fling at
- // ACTION_UP
- mLastTouchTime = eventTime;
- break;
- }
-
// Only lock dragging to one axis if we don't have a scale in progress.
// Scaling implies free-roaming movement. Note this is only ever a question
// if mZoomManager.supportsPanDuringZoom() is true.
- final ScaleGestureDetector detector =
- mZoomManager.getMultiTouchGestureDetector();
mAverageAngle = calculateDragAngle(deltaX, deltaY);
if (detector == null || !detector.isInProgress()) {
// if it starts nearly horizontal or vertical, enforce it
@@ -6239,10 +6154,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
// do pan
- boolean done = false;
boolean keepScrollBarsVisible = false;
if (deltaX == 0 && deltaY == 0) {
- keepScrollBarsVisible = done = true;
+ keepScrollBarsVisible = true;
} else {
mAverageAngle +=
(calculateDragAngle(deltaX, deltaY) - mAverageAngle)
@@ -6319,7 +6233,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
ViewConfiguration.getScrollDefaultDelay());
// return false to indicate that we can't pan out of the
// view space
- return !done;
+ return;
} else {
mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
}
@@ -6332,24 +6246,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
stopTouch();
break;
}
- // pass the touch events from UI thread to WebCore thread
- if (shouldForwardTouchEvent()) {
- TouchEventData ted = new TouchEventData();
- ted.mIds = new int[1];
- ted.mIds[0] = ev.getPointerId(0);
- ted.mAction = action;
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(contentX, contentY);
- ted.mPointsInView = new Point[1];
- ted.mPointsInView[0] = new Point(x, y);
- ted.mMetaState = ev.getMetaState();
- ted.mReprocess = mDeferTouchProcess;
- ted.mNativeLayer = mCurrentScrollingLayerId;
- ted.mNativeLayerRect.set(mScrollingLayerRect);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- }
mLastTouchUpTime = eventTime;
if (mSentAutoScrollMessage) {
mAutoScrollX = mAutoScrollY = 0;
@@ -6358,66 +6254,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
case TOUCH_DOUBLE_TAP_MODE: // double tap
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- if (inFullScreenMode() || mDeferTouchProcess) {
- TouchEventData ted = new TouchEventData();
- ted.mIds = new int[1];
- ted.mIds[0] = ev.getPointerId(0);
- ted.mAction = WebViewCore.ACTION_DOUBLETAP;
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(contentX, contentY);
- ted.mPointsInView = new Point[1];
- ted.mPointsInView[0] = new Point(x, y);
- ted.mMetaState = ev.getMetaState();
- ted.mReprocess = mDeferTouchProcess;
- ted.mNativeLayer = nativeScrollableLayer(
- contentX, contentY,
- ted.mNativeLayerRect, null);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- } else if (mPreventDefault != PREVENT_DEFAULT_YES){
- mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
- mTouchMode = TOUCH_DONE_MODE;
- }
+ mTouchMode = TOUCH_DONE_MODE;
break;
case TOUCH_INIT_MODE: // tap
case TOUCH_SHORTPRESS_START_MODE:
case TOUCH_SHORTPRESS_MODE:
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- if (mConfirmMove) {
- Log.w(LOGTAG, "Miss a drag as we are waiting for" +
- " WebCore's response for touch down.");
- if (mPreventDefault != PREVENT_DEFAULT_YES
- && (computeMaxScrollX() > 0
- || computeMaxScrollY() > 0)) {
- // If the user has performed a very quick touch
- // sequence it is possible that we may get here
- // before WebCore has had a chance to process the events.
- // In this case, any call to preventDefault in the
- // JS touch handler will not have been executed yet.
- // Hence we will see both the UI (now) and WebCore
- // (when context switches) handling the event,
- // regardless of whether the web developer actually
- // doeses preventDefault in their touch handler. This
- // is the nature of our asynchronous touch model.
-
- // we will not rewrite drag code here, but we
- // will try fling if it applies.
- WebViewCore.reducePriority();
- // to get better performance, pause updating the
- // picture
- WebViewCore.pauseUpdatePicture(mWebViewCore);
- // fall through to TOUCH_DRAG_MODE
- } else {
- // WebKit may consume the touch event and modify
- // DOM. drawContentPicture() will be called with
- // animateSroll as true for better performance.
- // Force redraw in high-quality.
- invalidate();
- break;
- }
- } else {
+ if (!mConfirmMove) {
if (mSelectingText) {
// tapping on selection or controls does nothing
if (!mSelectionStarted) {
@@ -6432,8 +6276,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mPrivateHandler.sendEmptyMessageDelayed(
RELEASE_SINGLE_TAP, ViewConfiguration
.getDoubleTapTimeout());
- } else {
- doShortPress();
}
break;
}
@@ -6446,13 +6288,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// up, we don't want to do a fling
if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
if (mVelocityTracker == null) {
- Log.e(LOGTAG, "Got null mVelocityTracker when "
- + "mPreventDefault = "
- + mPreventDefault
- + " mDeferTouchProcess = "
- + mDeferTouchProcess);
+ Log.e(LOGTAG, "Got null mVelocityTracker");
} else {
- mVelocityTracker.addMovement(ev);
+ mVelocityTracker.addMovement(event);
}
// set to MOTIONLESS_IGNORE so that it won't keep
// removing and sending message in
@@ -6491,128 +6329,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
computeMaxScrollX(), 0, computeMaxScrollY());
invalidate();
}
- cancelWebCoreTouchEvent(contentX, contentY, false);
cancelTouch();
break;
}
}
- return true;
- }
-
- private void passMultiTouchToWebKit(MotionEvent ev, long sequence) {
- TouchEventData ted = new TouchEventData();
- ted.mAction = ev.getActionMasked();
- final int count = ev.getPointerCount();
- ted.mIds = new int[count];
- ted.mPoints = new Point[count];
- ted.mPointsInView = new Point[count];
- for (int c = 0; c < count; c++) {
- ted.mIds[c] = ev.getPointerId(c);
- int x = viewToContentX((int) ev.getX(c) + getScrollX());
- int y = viewToContentY((int) ev.getY(c) + getScrollY());
- ted.mPoints[c] = new Point(x, y);
- ted.mPointsInView[c] = new Point((int) ev.getX(c), (int) ev.getY(c));
- }
- if (ted.mAction == MotionEvent.ACTION_POINTER_DOWN
- || ted.mAction == MotionEvent.ACTION_POINTER_UP) {
- ted.mActionIndex = ev.getActionIndex();
- }
- ted.mMetaState = ev.getMetaState();
- ted.mReprocess = true;
- ted.mMotionEvent = MotionEvent.obtain(ev);
- ted.mSequence = sequence;
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- mWebView.cancelLongPress();
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- }
-
- void handleMultiTouchInWebView(MotionEvent ev) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "multi-touch: " + ev + " at " + ev.getEventTime()
- + " mTouchMode=" + mTouchMode
- + " numPointers=" + ev.getPointerCount()
- + " scrolloffset=(" + getScrollX() + "," + getScrollY() + ")");
- }
-
- final ScaleGestureDetector detector =
- mZoomManager.getMultiTouchGestureDetector();
-
- // A few apps use WebView but don't instantiate gesture detector.
- // We don't need to support multi touch for them.
- if (detector == null) return;
-
- float x = ev.getX();
- float y = ev.getY();
-
- if (mPreventDefault != PREVENT_DEFAULT_YES) {
- detector.onTouchEvent(ev);
-
- if (detector.isInProgress()) {
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "detector is in progress");
- }
- mLastTouchTime = ev.getEventTime();
- x = detector.getFocusX();
- y = detector.getFocusY();
-
- mWebView.cancelLongPress();
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- if (!mZoomManager.supportsPanDuringZoom()) {
- return;
- }
- mTouchMode = TOUCH_DRAG_MODE;
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- }
- }
-
- int action = ev.getActionMasked();
- if (action == MotionEvent.ACTION_POINTER_DOWN) {
- cancelTouch();
- action = MotionEvent.ACTION_DOWN;
- } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) {
- // set mLastTouchX/Y to the remaining points for multi-touch.
- mLastTouchX = Math.round(x);
- mLastTouchY = Math.round(y);
- } else if (action == MotionEvent.ACTION_MOVE) {
- // negative x or y indicate it is on the edge, skip it.
- if (x < 0 || y < 0) {
- return;
- }
- }
-
- handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
- }
-
- private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) {
- if (shouldForwardTouchEvent()) {
- if (removeEvents) {
- mWebViewCore.removeMessages(EventHub.TOUCH_EVENT);
- }
- TouchEventData ted = new TouchEventData();
- ted.mIds = new int[1];
- ted.mIds[0] = 0;
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(x, y);
- ted.mPointsInView = new Point[1];
- int viewX = contentToViewX(x) - getScrollX();
- int viewY = contentToViewY(y) - getScrollY();
- ted.mPointsInView[0] = new Point(viewX, viewY);
- ted.mAction = MotionEvent.ACTION_CANCEL;
- ted.mNativeLayer = nativeScrollableLayer(
- x, y, ted.mNativeLayerRect, null);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- mPreventDefault = PREVENT_DEFAULT_IGNORE;
-
- if (removeEvents) {
- // Mark this after sending the message above; we should
- // be willing to ignore the cancel event that we just sent.
- mTouchEventQueue.ignoreCurrentlyMissingEvents();
- }
- }
}
private void startTouch(float x, float y, long eventTime) {
@@ -7249,39 +6969,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
return mZoomManager.zoomOut();
}
- private void doShortPress() {
- if (mNativeClass == 0) {
- return;
- }
- if (mPreventDefault == PREVENT_DEFAULT_YES) {
- return;
- }
- mTouchMode = TOUCH_DONE_MODE;
- switchOutDrawHistory();
- if (!mTouchHighlightRegion.isEmpty()) {
- // set mTouchHighlightRequested to 0 to cause an immediate
- // drawing of the touch rings
- mTouchHighlightRequested = 0;
- mWebView.invalidate(mTouchHighlightRegion.getBounds());
- mPrivateHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- removeTouchHighlight();
- }
- }, ViewConfiguration.getPressedStateDuration());
- }
- if (mFocusedNode != null && mFocusedNode.mIntentUrl != null) {
- mWebView.playSoundEffect(SoundEffectConstants.CLICK);
- overrideLoading(mFocusedNode.mIntentUrl);
- } else {
- WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
- // use "0" as generation id to inform WebKit to use the same x/y as
- // it used when processing GET_TOUCH_HIGHLIGHT_RECTS
- touchUpData.mMoveGeneration = 0;
- mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
- }
- }
-
/*
* Return true if the rect (e.g. plugin) is fully visible and maximized
* inside the WebView.
@@ -7569,485 +7256,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
return Math.max(0, mEditTextContent.height() - mEditTextContentBounds.height());
}
- /**
- * Used only by TouchEventQueue to store pending touch events.
- */
- private static class QueuedTouch {
- long mSequence;
- MotionEvent mEvent; // Optional
- TouchEventData mTed; // Optional
-
- QueuedTouch mNext;
-
- public QueuedTouch set(TouchEventData ted) {
- mSequence = ted.mSequence;
- mTed = ted;
- mEvent = null;
- mNext = null;
- return this;
- }
-
- public QueuedTouch set(MotionEvent ev, long sequence) {
- mEvent = MotionEvent.obtain(ev);
- mSequence = sequence;
- mTed = null;
- mNext = null;
- return this;
- }
-
- public QueuedTouch add(QueuedTouch other) {
- if (other.mSequence < mSequence) {
- other.mNext = this;
- return other;
- }
-
- QueuedTouch insertAt = this;
- while (insertAt.mNext != null && insertAt.mNext.mSequence < other.mSequence) {
- insertAt = insertAt.mNext;
- }
- other.mNext = insertAt.mNext;
- insertAt.mNext = other;
- return this;
- }
- }
-
- /**
- * WebView handles touch events asynchronously since some events must be passed to WebKit
- * for potentially slower processing. TouchEventQueue serializes touch events regardless
- * of which path they take to ensure that no events are ever processed out of order
- * by WebView.
- */
- private class TouchEventQueue {
- private long mNextTouchSequence = Long.MIN_VALUE + 1;
- private long mLastHandledTouchSequence = Long.MIN_VALUE;
- private long mIgnoreUntilSequence = Long.MIN_VALUE + 1;
-
- // Events waiting to be processed.
- private QueuedTouch mTouchEventQueue;
-
- // Known events that are waiting on a response before being enqueued.
- private QueuedTouch mPreQueue;
-
- // Pool of QueuedTouch objects saved for later use.
- private QueuedTouch mQueuedTouchRecycleBin;
- private int mQueuedTouchRecycleCount;
-
- private long mLastEventTime = Long.MAX_VALUE;
- private static final int MAX_RECYCLED_QUEUED_TOUCH = 15;
-
- // milliseconds until we abandon hope of getting all of a previous gesture
- private static final int QUEUED_GESTURE_TIMEOUT = 1000;
-
- private QueuedTouch obtainQueuedTouch() {
- if (mQueuedTouchRecycleBin != null) {
- QueuedTouch result = mQueuedTouchRecycleBin;
- mQueuedTouchRecycleBin = result.mNext;
- mQueuedTouchRecycleCount--;
- return result;
- }
- return new QueuedTouch();
- }
-
- /**
- * Allow events with any currently missing sequence numbers to be skipped in processing.
- */
- public void ignoreCurrentlyMissingEvents() {
- mIgnoreUntilSequence = mNextTouchSequence;
-
- // Run any events we have available and complete, pre-queued or otherwise.
- runQueuedAndPreQueuedEvents();
- }
-
- private void runQueuedAndPreQueuedEvents() {
- QueuedTouch qd = mPreQueue;
- boolean fromPreQueue = true;
- while (qd != null && qd.mSequence == mLastHandledTouchSequence + 1) {
- handleQueuedTouch(qd);
- QueuedTouch recycleMe = qd;
- if (fromPreQueue) {
- mPreQueue = qd.mNext;
- } else {
- mTouchEventQueue = qd.mNext;
- }
- recycleQueuedTouch(recycleMe);
- mLastHandledTouchSequence++;
-
- long nextPre = mPreQueue != null ? mPreQueue.mSequence : Long.MAX_VALUE;
- long nextQueued = mTouchEventQueue != null ?
- mTouchEventQueue.mSequence : Long.MAX_VALUE;
- fromPreQueue = nextPre < nextQueued;
- qd = fromPreQueue ? mPreQueue : mTouchEventQueue;
- }
- }
-
- /**
- * Add a TouchEventData to the pre-queue.
- *
- * An event in the pre-queue is an event that we know about that
- * has been sent to webkit, but that we haven't received back and
- * enqueued into the normal touch queue yet. If webkit ever times
- * out and we need to ignore currently missing events, we'll run
- * events from the pre-queue to patch the holes.
- *
- * @param ted TouchEventData to pre-queue
- */
- public void preQueueTouchEventData(TouchEventData ted) {
- QueuedTouch newTouch = obtainQueuedTouch().set(ted);
- if (mPreQueue == null) {
- mPreQueue = newTouch;
- } else {
- QueuedTouch insertionPoint = mPreQueue;
- while (insertionPoint.mNext != null &&
- insertionPoint.mNext.mSequence < newTouch.mSequence) {
- insertionPoint = insertionPoint.mNext;
- }
- newTouch.mNext = insertionPoint.mNext;
- insertionPoint.mNext = newTouch;
- }
- }
-
- private void recycleQueuedTouch(QueuedTouch qd) {
- if (mQueuedTouchRecycleCount < MAX_RECYCLED_QUEUED_TOUCH) {
- qd.mNext = mQueuedTouchRecycleBin;
- mQueuedTouchRecycleBin = qd;
- mQueuedTouchRecycleCount++;
- }
- }
-
- /**
- * Reset the touch event queue. This will dump any pending events
- * and reset the sequence numbering.
- */
- public void reset() {
- mNextTouchSequence = Long.MIN_VALUE + 1;
- mLastHandledTouchSequence = Long.MIN_VALUE;
- mIgnoreUntilSequence = Long.MIN_VALUE + 1;
- while (mTouchEventQueue != null) {
- QueuedTouch recycleMe = mTouchEventQueue;
- mTouchEventQueue = mTouchEventQueue.mNext;
- recycleQueuedTouch(recycleMe);
- }
- while (mPreQueue != null) {
- QueuedTouch recycleMe = mPreQueue;
- mPreQueue = mPreQueue.mNext;
- recycleQueuedTouch(recycleMe);
- }
- }
-
- /**
- * Return the next valid sequence number for tagging incoming touch events.
- * @return The next touch event sequence number
- */
- public long nextTouchSequence() {
- return mNextTouchSequence++;
- }
-
- /**
- * Enqueue a touch event in the form of TouchEventData.
- * The sequence number will be read from the mSequence field of the argument.
- *
- * If the touch event's sequence number is the next in line to be processed, it will
- * be handled before this method returns. Any subsequent events that have already
- * been queued will also be processed in their proper order.
- *
- * @param ted Touch data to be processed in order.
- * @return true if the event was processed before returning, false if it was just enqueued.
- */
- public boolean enqueueTouchEvent(TouchEventData ted) {
- // Remove from the pre-queue if present
- QueuedTouch preQueue = mPreQueue;
- if (preQueue != null) {
- // On exiting this block, preQueue is set to the pre-queued QueuedTouch object
- // if it was present in the pre-queue, and removed from the pre-queue itself.
- if (preQueue.mSequence == ted.mSequence) {
- mPreQueue = preQueue.mNext;
- } else {
- QueuedTouch prev = preQueue;
- preQueue = null;
- while (prev.mNext != null) {
- if (prev.mNext.mSequence == ted.mSequence) {
- preQueue = prev.mNext;
- prev.mNext = preQueue.mNext;
- break;
- } else {
- prev = prev.mNext;
- }
- }
- }
- }
-
- if (ted.mSequence < mLastHandledTouchSequence) {
- // Stale event and we already moved on; drop it. (Should not be common.)
- Log.w(LOGTAG, "Stale touch event " + MotionEvent.actionToString(ted.mAction) +
- " received from webcore; ignoring");
- return false;
- }
-
- if (dropStaleGestures(ted.mMotionEvent, ted.mSequence)) {
- return false;
- }
-
- // dropStaleGestures above might have fast-forwarded us to
- // an event we have already.
- runNextQueuedEvents();
-
- if (mLastHandledTouchSequence + 1 == ted.mSequence) {
- if (preQueue != null) {
- recycleQueuedTouch(preQueue);
- preQueue = null;
- }
- handleQueuedTouchEventData(ted);
-
- mLastHandledTouchSequence++;
-
- // Do we have any more? Run them if so.
- runNextQueuedEvents();
- } else {
- // Reuse the pre-queued object if we had it.
- QueuedTouch qd = preQueue != null ? preQueue : obtainQueuedTouch().set(ted);
- mTouchEventQueue = mTouchEventQueue == null ? qd : mTouchEventQueue.add(qd);
- }
- return true;
- }
-
- /**
- * Enqueue a touch event in the form of a MotionEvent from the framework.
- *
- * If the touch event's sequence number is the next in line to be processed, it will
- * be handled before this method returns. Any subsequent events that have already
- * been queued will also be processed in their proper order.
- *
- * @param ev MotionEvent to be processed in order
- */
- public void enqueueTouchEvent(MotionEvent ev) {
- final long sequence = nextTouchSequence();
-
- if (dropStaleGestures(ev, sequence)) {
- return;
- }
-
- // dropStaleGestures above might have fast-forwarded us to
- // an event we have already.
- runNextQueuedEvents();
-
- if (mLastHandledTouchSequence + 1 == sequence) {
- handleQueuedMotionEvent(ev);
-
- mLastHandledTouchSequence++;
-
- // Do we have any more? Run them if so.
- runNextQueuedEvents();
- } else {
- QueuedTouch qd = obtainQueuedTouch().set(ev, sequence);
- mTouchEventQueue = mTouchEventQueue == null ? qd : mTouchEventQueue.add(qd);
- }
- }
-
- private void runNextQueuedEvents() {
- QueuedTouch qd = mTouchEventQueue;
- while (qd != null && qd.mSequence == mLastHandledTouchSequence + 1) {
- handleQueuedTouch(qd);
- QueuedTouch recycleMe = qd;
- qd = qd.mNext;
- recycleQueuedTouch(recycleMe);
- mLastHandledTouchSequence++;
- }
- mTouchEventQueue = qd;
- }
-
- private boolean dropStaleGestures(MotionEvent ev, long sequence) {
- if (ev != null && ev.getAction() == MotionEvent.ACTION_MOVE && !mConfirmMove) {
- // This is to make sure that we don't attempt to process a tap
- // or long press when webkit takes too long to get back to us.
- // The movement will be properly confirmed when we process the
- // enqueued event later.
- final int dx = Math.round(ev.getX()) - mLastTouchX;
- final int dy = Math.round(ev.getY()) - mLastTouchY;
- if (dx * dx + dy * dy > mTouchSlopSquare) {
- mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- }
- }
-
- if (mTouchEventQueue == null) {
- return sequence <= mLastHandledTouchSequence;
- }
-
- // If we have a new down event and it's been a while since the last event
- // we saw, catch up as best we can and keep going.
- if (ev != null && ev.getAction() == MotionEvent.ACTION_DOWN) {
- long eventTime = ev.getEventTime();
- long lastHandledEventTime = mLastEventTime;
- if (eventTime > lastHandledEventTime + QUEUED_GESTURE_TIMEOUT) {
- Log.w(LOGTAG, "Got ACTION_DOWN but still waiting on stale event. " +
- "Catching up.");
- runQueuedAndPreQueuedEvents();
-
- // Drop leftovers that we truly don't have.
- QueuedTouch qd = mTouchEventQueue;
- while (qd != null && qd.mSequence < sequence) {
- QueuedTouch recycleMe = qd;
- qd = qd.mNext;
- recycleQueuedTouch(recycleMe);
- }
- mTouchEventQueue = qd;
- mLastHandledTouchSequence = sequence - 1;
- }
- }
-
- if (mIgnoreUntilSequence - 1 > mLastHandledTouchSequence) {
- QueuedTouch qd = mTouchEventQueue;
- while (qd != null && qd.mSequence < mIgnoreUntilSequence) {
- QueuedTouch recycleMe = qd;
- qd = qd.mNext;
- recycleQueuedTouch(recycleMe);
- }
- mTouchEventQueue = qd;
- mLastHandledTouchSequence = mIgnoreUntilSequence - 1;
- }
-
- if (mPreQueue != null) {
- // Drop stale prequeued events
- QueuedTouch qd = mPreQueue;
- while (qd != null && qd.mSequence < mIgnoreUntilSequence) {
- QueuedTouch recycleMe = qd;
- qd = qd.mNext;
- recycleQueuedTouch(recycleMe);
- }
- mPreQueue = qd;
- }
-
- return sequence <= mLastHandledTouchSequence;
- }
-
- private void handleQueuedTouch(QueuedTouch qt) {
- if (qt.mTed != null) {
- handleQueuedTouchEventData(qt.mTed);
- } else {
- handleQueuedMotionEvent(qt.mEvent);
- qt.mEvent.recycle();
- }
- }
-
- private void handleQueuedMotionEvent(MotionEvent ev) {
- mLastEventTime = ev.getEventTime();
- int action = ev.getActionMasked();
- if (ev.getPointerCount() > 1) { // Multi-touch
- handleMultiTouchInWebView(ev);
- } else {
- final ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
- if (detector != null && mPreventDefault != PREVENT_DEFAULT_YES) {
- // ScaleGestureDetector needs a consistent event stream to operate properly.
- // It won't take any action with fewer than two pointers, but it needs to
- // update internal bookkeeping state.
- detector.onTouchEvent(ev);
- }
-
- handleTouchEventCommon(ev, action, Math.round(ev.getX()), Math.round(ev.getY()));
- }
- }
-
- private void handleQueuedTouchEventData(TouchEventData ted) {
- if (ted.mMotionEvent != null) {
- mLastEventTime = ted.mMotionEvent.getEventTime();
- }
- if (!ted.mReprocess) {
- if (ted.mAction == MotionEvent.ACTION_DOWN
- && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) {
- // if prevent default is called from WebCore, UI
- // will not handle the rest of the touch events any
- // more.
- mPreventDefault = ted.mNativeResult ? PREVENT_DEFAULT_YES
- : PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN;
- } else if (ted.mAction == MotionEvent.ACTION_MOVE
- && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
- // the return for the first ACTION_MOVE will decide
- // whether UI will handle touch or not. Currently no
- // support for alternating prevent default
- mPreventDefault = ted.mNativeResult ? PREVENT_DEFAULT_YES
- : PREVENT_DEFAULT_NO;
- }
- if (mPreventDefault == PREVENT_DEFAULT_YES) {
- mTouchHighlightRegion.setEmpty();
- }
- } else {
- if (ted.mPoints.length > 1) { // multi-touch
- if (!ted.mNativeResult && mPreventDefault != PREVENT_DEFAULT_YES) {
- mPreventDefault = PREVENT_DEFAULT_NO;
- handleMultiTouchInWebView(ted.mMotionEvent);
- } else {
- mPreventDefault = PREVENT_DEFAULT_YES;
- }
- return;
- }
-
- // prevent default is not called in WebCore, so the
- // message needs to be reprocessed in UI
- if (!ted.mNativeResult) {
- // Following is for single touch.
- switch (ted.mAction) {
- case MotionEvent.ACTION_DOWN:
- mLastDeferTouchX = ted.mPointsInView[0].x;
- mLastDeferTouchY = ted.mPointsInView[0].y;
- mDeferTouchMode = TOUCH_INIT_MODE;
- break;
- case MotionEvent.ACTION_MOVE: {
- // no snapping in defer process
- int x = ted.mPointsInView[0].x;
- int y = ted.mPointsInView[0].y;
-
- if (mDeferTouchMode != TOUCH_DRAG_MODE) {
- mDeferTouchMode = TOUCH_DRAG_MODE;
- mLastDeferTouchX = x;
- mLastDeferTouchY = y;
- startScrollingLayer(x, y);
- startDrag();
- }
- int deltaX = pinLocX((int) (getScrollX()
- + mLastDeferTouchX - x))
- - getScrollX();
- int deltaY = pinLocY((int) (getScrollY()
- + mLastDeferTouchY - y))
- - getScrollY();
- doDrag(deltaX, deltaY);
- if (deltaX != 0) mLastDeferTouchX = x;
- if (deltaY != 0) mLastDeferTouchY = y;
- break;
- }
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (mDeferTouchMode == TOUCH_DRAG_MODE) {
- // no fling in defer process
- mScroller.springBack(getScrollX(), getScrollY(), 0,
- computeMaxScrollX(), 0,
- computeMaxScrollY());
- invalidate();
- WebViewCore.resumePriority();
- WebViewCore.resumeUpdatePicture(mWebViewCore);
- }
- mDeferTouchMode = TOUCH_DONE_MODE;
- break;
- case WebViewCore.ACTION_DOUBLETAP:
- // doDoubleTap() needs mLastTouchX/Y as anchor
- mLastDeferTouchX = ted.mPointsInView[0].x;
- mLastDeferTouchY = ted.mPointsInView[0].y;
- mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
- mDeferTouchMode = TOUCH_DONE_MODE;
- break;
- case WebViewCore.ACTION_LONGPRESS:
- HitTestResult hitTest = getHitTestResult();
- if (hitTest != null && hitTest.getType()
- != HitTestResult.UNKNOWN_TYPE) {
- performLongClick();
- }
- mDeferTouchMode = TOUCH_DONE_MODE;
- break;
- }
- }
- }
- }
- }
-
//-------------------------------------------------------------------------
// Methods can be called from a separate thread, like WebViewCore
// If it needs to call the View system, it has to send message.
@@ -8056,7 +7264,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
/**
* General handler to receive message coming from webkit thread
*/
- class PrivateHandler extends Handler {
+ class PrivateHandler extends Handler implements WebViewInputDispatcher.UiCallbacks {
@Override
public void handleMessage(Message msg) {
// exclude INVAL_RECT_MSG_ID since it is frequently output
@@ -8097,20 +7305,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
((Message) msg.obj).sendToTarget();
break;
}
- case PREVENT_DEFAULT_TIMEOUT: {
- // if timeout happens, cancel it so that it won't block UI
- // to continue handling touch events
- if ((msg.arg1 == MotionEvent.ACTION_DOWN
- && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES)
- || (msg.arg1 == MotionEvent.ACTION_MOVE
- && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN)) {
- cancelWebCoreTouchEvent(
- viewToContentX(mLastTouchX + getScrollX()),
- viewToContentY(mLastTouchY + getScrollY()),
- true);
- }
- break;
- }
case SCROLL_SELECT_TEXT: {
if (mAutoScrollX == 0 && mAutoScrollY == 0) {
mSentAutoScrollMessage = false;
@@ -8126,48 +7320,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
break;
}
- case SWITCH_TO_SHORTPRESS: {
- if (mTouchMode == TOUCH_INIT_MODE) {
- mTouchMode = TOUCH_SHORTPRESS_MODE;
- } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
- mTouchMode = TOUCH_DONE_MODE;
- }
- break;
- }
- case SWITCH_TO_LONGPRESS: {
- removeTouchHighlight();
- if (inFullScreenMode() || mDeferTouchProcess) {
- TouchEventData ted = new TouchEventData();
- ted.mAction = WebViewCore.ACTION_LONGPRESS;
- ted.mIds = new int[1];
- ted.mIds[0] = 0;
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(viewToContentX(mLastTouchX + getScrollX()),
- viewToContentY(mLastTouchY + getScrollY()));
- ted.mPointsInView = new Point[1];
- ted.mPointsInView[0] = new Point(mLastTouchX, mLastTouchY);
- // metaState for long press is tricky. Should it be the
- // state when the press started or when the press was
- // released? Or some intermediary key state? For
- // simplicity for now, we don't set it.
- ted.mMetaState = 0;
- ted.mReprocess = mDeferTouchProcess;
- ted.mNativeLayer = nativeScrollableLayer(
- ted.mPoints[0].x, ted.mPoints[0].y,
- ted.mNativeLayerRect, null);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- } else if (mPreventDefault != PREVENT_DEFAULT_YES) {
- mTouchMode = TOUCH_DONE_MODE;
- performLongClick();
- }
- break;
- }
- case RELEASE_SINGLE_TAP: {
- doShortPress();
- break;
- }
case SCROLL_TO_MSG_ID: {
// arg1 = animate, arg2 = onlyIfImeIsShowing
// obj = Point(x, y)
@@ -8225,6 +7377,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (mIsPaused) {
nativeSetPauseDrawing(mNativeClass, true);
}
+ mInputDispatcher = new WebViewInputDispatcher(this,
+ mWebViewCore.getInputDispatcherCallbacks());
break;
case UPDATE_TEXTFIELD_TEXT_MSG_ID:
// Make sure that the textfield is currently focused
@@ -8281,20 +7435,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
case WEBCORE_NEED_TOUCH_EVENTS:
- mForwardTouchEvents = (msg.arg1 != 0);
- break;
-
- case PREVENT_TOUCH_ID:
- if (inFullScreenMode()) {
- break;
- }
- TouchEventData ted = (TouchEventData) msg.obj;
-
- if (mTouchEventQueue.enqueueTouchEvent(ted)) {
- // WebCore is responding to us; remove pending timeout.
- // It will be re-posted when needed.
- removeMessages(PREVENT_DEFAULT_TIMEOUT);
- }
+ mInputDispatcher.setWebKitWantsTouchEvents(msg.arg1 != 0);
break;
case REQUEST_KEYBOARD:
@@ -8559,6 +7700,21 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
}
}
+
+ @Override
+ public Looper getUiLooper() {
+ return getLooper();
+ }
+
+ @Override
+ public void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
+ onHandleUiEvent(event, eventType, flags);
+ }
+
+ @Override
+ public Context getContext() {
+ return WebViewClassic.this.getContext();
+ }
}
private void setHitTestTypeFromUrl(String url) {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 2caec01..dd05e8c 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -40,6 +40,7 @@ import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.webkit.WebViewClassic.FocusNodeHref;
+import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
import junit.framework.Assert;
@@ -259,6 +260,10 @@ public final class WebViewCore {
return mBrowserFrame;
}
+ public WebKitCallbacks getInputDispatcherCallbacks() {
+ return mEventHub;
+ }
+
//-------------------------------------------------------------------------
// Common methods
//-------------------------------------------------------------------------
@@ -611,9 +616,6 @@ public final class WebViewCore {
int unichar, int repeatCount, boolean isShift, boolean isAlt,
boolean isSym, boolean isDown);
- private native void nativeClick(int nativeClass, int framePtr, int nodePtr,
- boolean fake);
-
private native void nativeSendListBoxChoices(int nativeClass,
boolean[] choices, int size);
@@ -656,8 +658,7 @@ public final class WebViewCore {
int x, int y);
private native String nativeRetrieveImageSource(int nativeClass,
int x, int y);
- private native void nativeTouchUp(int nativeClass,
- int touchGeneration, int framePtr, int nodePtr, int x, int y);
+ private native boolean nativeMouseClick(int nativeClass);
private native boolean nativeHandleTouchEvent(int nativeClass, int action,
int[] idArray, int[] xArray, int[] yArray, int count,
@@ -1020,8 +1021,8 @@ public final class WebViewCore {
"REQUEST_CURSOR_HREF", // = 137;
"ADD_JS_INTERFACE", // = 138;
"LOAD_DATA", // = 139;
- "TOUCH_UP", // = 140;
- "TOUCH_EVENT", // = 141;
+ "", // = 140;
+ "", // = 141;
"SET_ACTIVE", // = 142;
"ON_PAUSE", // = 143
"ON_RESUME", // = 144
@@ -1046,7 +1047,7 @@ public final class WebViewCore {
/**
* @hide
*/
- public class EventHub {
+ public class EventHub implements WebViewInputDispatcher.WebKitCallbacks {
// Message Ids
static final int REVEAL_SELECTION = 96;
static final int SCROLL_TEXT_INPUT = 99;
@@ -1067,10 +1068,8 @@ public final class WebViewCore {
static final int REPLACE_TEXT = 114;
static final int PASS_TO_JS = 115;
static final int SET_GLOBAL_BOUNDS = 116;
- static final int CLICK = 118;
static final int SET_NETWORK_STATE = 119;
static final int DOC_HAS_IMAGES = 120;
- static final int FAKE_CLICK = 121;
static final int DELETE_SELECTION = 122;
static final int LISTBOX_CHOICES = 123;
static final int SINGLE_LISTBOX_CHOICE = 124;
@@ -1091,11 +1090,6 @@ public final class WebViewCore {
static final int ADD_JS_INTERFACE = 138;
static final int LOAD_DATA = 139;
- // motion
- static final int TOUCH_UP = 140;
- // message used to pass UI touch events to WebCore
- static final int TOUCH_EVENT = 141;
-
// Used to tell the focus controller not to draw the blinking cursor,
// based on whether the WebView has focus and whether the WebView's
// cursor matches the webpage's focus.
@@ -1370,14 +1364,6 @@ public final class WebViewCore {
keyPress(msg.arg1);
break;
- case FAKE_CLICK:
- nativeClick(mNativeClass, msg.arg1, msg.arg2, true);
- break;
-
- case CLICK:
- nativeClick(mNativeClass, msg.arg1, msg.arg2, false);
- break;
-
case VIEW_SIZE_CHANGED: {
viewSizeChanged((WebViewClassic.ViewSizeData) msg.obj);
break;
@@ -1490,45 +1476,6 @@ public final class WebViewCore {
nativeCloseIdleConnections(mNativeClass);
break;
- case TOUCH_UP:
- TouchUpData touchUpData = (TouchUpData) msg.obj;
- if (touchUpData.mNativeLayer != 0) {
- nativeScrollLayer(mNativeClass,
- touchUpData.mNativeLayer,
- touchUpData.mNativeLayerRect);
- }
- nativeTouchUp(mNativeClass,
- touchUpData.mMoveGeneration,
- touchUpData.mFrame, touchUpData.mNode,
- touchUpData.mX, touchUpData.mY);
- break;
-
- case TOUCH_EVENT: {
- TouchEventData ted = (TouchEventData) msg.obj;
- final int count = ted.mPoints.length;
- int[] xArray = new int[count];
- int[] yArray = new int[count];
- for (int c = 0; c < count; c++) {
- xArray[c] = ted.mPoints[c].x;
- yArray[c] = ted.mPoints[c].y;
- }
- if (ted.mNativeLayer != 0) {
- nativeScrollLayer(mNativeClass,
- ted.mNativeLayer, ted.mNativeLayerRect);
- }
- ted.mNativeResult = nativeHandleTouchEvent(
- mNativeClass, ted.mAction, ted.mIds, xArray,
- yArray, count, ted.mActionIndex,
- ted.mMetaState);
- Message.obtain(
- mWebViewClassic.mPrivateHandler,
- WebViewClassic.PREVENT_TOUCH_ID,
- ted.mAction,
- ted.mNativeResult ? 1 : 0,
- ted).sendToTarget();
- break;
- }
-
case SET_ACTIVE:
nativeSetFocusControllerActive(mNativeClass, msg.arg1 == 1);
break;
@@ -1812,6 +1759,38 @@ public final class WebViewCore {
}
}
+ @Override
+ public Looper getWebKitLooper() {
+ return mHandler.getLooper();
+ }
+
+ @Override
+ public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
+ switch (eventType) {
+ case WebViewInputDispatcher.EVENT_TYPE_CLICK:
+ return nativeMouseClick(mNativeClass);
+
+ case WebViewInputDispatcher.EVENT_TYPE_TOUCH: {
+ int count = event.getPointerCount();
+ int[] idArray = new int[count];
+ int[] xArray = new int[count];
+ int[] yArray = new int[count];
+ for (int i = 0; i < count; i++) {
+ idArray[i] = event.getPointerId(i);
+ xArray[i] = (int) event.getX(i);
+ yArray[i] = (int) event.getY(i);
+ }
+ return nativeHandleTouchEvent(mNativeClass,
+ event.getActionMasked(),
+ idArray, xArray, yArray, count,
+ event.getActionIndex(), event.getMetaState());
+ }
+
+ default:
+ return false;
+ }
+ }
+
/**
* Send a message internally to the queue or to the handler
*/
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
new file mode 100644
index 0000000..e7024d9
--- /dev/null
+++ b/core/java/android/webkit/WebViewInputDispatcher.java
@@ -0,0 +1,1151 @@
+/*
+ * Copyright (C) 2012 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.webkit;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * Perform asynchronous dispatch of input events in a {@link WebView}.
+ *
+ * This dispatcher is shared by the UI thread ({@link WebViewClassic}) and web kit
+ * thread ({@link WebViewCore}). The UI thread enqueues events for
+ * processing, waits for the web kit thread to handle them, and then performs
+ * additional processing depending on the outcome.
+ *
+ * How it works:
+ *
+ * 1. The web view thread receives an input event from the input system on the UI
+ * thread in its {@link WebViewClassic#onTouchEvent} handler. It sends the input event
+ * to the dispatcher, then immediately returns true to the input system to indicate that
+ * it will handle the event.
+ *
+ * 2. The web kit thread is notified that an event has been enqueued. Meanwhile additional
+ * events may be enqueued from the UI thread. In some cases, the dispatcher may decide to
+ * coalesce motion events into larger batches or to cancel events that have been
+ * sitting in the queue for too long.
+ *
+ * 3. The web kit thread wakes up and handles all input events that are waiting for it.
+ * After processing each input event, it informs the dispatcher whether the web application
+ * has decided to handle the event itself and to prevent default event handling.
+ *
+ * 4. If web kit indicates that it wants to prevent default event handling, then web kit
+ * consumes the remainder of the gesture and web view receives a cancel event if
+ * needed. Otherwise, the web view handles the gesture on the UI thread normally.
+ *
+ * 5. If the web kit thread takes too long to handle an input event, then it loses the
+ * right to handle it. The dispatcher synthesizes a cancellation event for web kit and
+ * then tells the web view on the UI thread to handle the event that timed out along
+ * with the rest of the gesture.
+ *
+ * One thing to keep in mind about the dispatcher is that what goes into the dispatcher
+ * is not necessarily what the web kit or UI thread will see. As mentioned above, the
+ * dispatcher may tweak the input event stream to improve responsiveness. Both web view and
+ * web kit are guaranteed to perceive a consistent stream of input events but
+ * they might not always see the same events (especially if one decides
+ * to prevent the other from handling a particular gesture).
+ *
+ * This implementation very deliberately does not refer to the {@link WebViewClassic}
+ * or {@link WebViewCore} classes, preferring to communicate with them only via
+ * interfaces to avoid unintentional coupling to their implementation details.
+ *
+ * Currently, the input dispatcher only handles pointer events (includes touch,
+ * hover and scroll events). In principle, it could be extended to handle trackball
+ * and key events if needed.
+ *
+ * @hide
+ */
+final class WebViewInputDispatcher {
+ private static final String TAG = "WebViewInputDispatcher";
+ private static final boolean DEBUG = false;
+ // This enables batching of MotionEvents. It will combine multiple MotionEvents
+ // together into a single MotionEvent if more events come in while we are
+ // still waiting on the processing of a previous event.
+ // If this is set to false, we will instead opt to drop ACTION_MOVE
+ // events we cannot keep up with.
+ // TODO: If batching proves to be working well, remove this
+ private static final boolean ENABLE_EVENT_BATCHING = true;
+
+ private final Object mLock = new Object();
+
+ // Pool of queued input events. (guarded by mLock)
+ private static final int MAX_DISPATCH_EVENT_POOL_SIZE = 10;
+ private DispatchEvent mDispatchEventPool;
+ private int mDispatchEventPoolSize;
+
+ // Posted state, tracks events posted to the dispatcher. (guarded by mLock)
+ private final TouchStream mPostTouchStream = new TouchStream();
+ private boolean mPostSendTouchEventsToWebKit;
+ private boolean mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
+ private boolean mPostLongPressScheduled;
+ private boolean mPostClickScheduled;
+ private int mPostLastWebKitXOffset;
+ private int mPostLastWebKitYOffset;
+ private float mPostLastWebKitScale;
+
+ // State for event tracking (click, longpress, double tap, etc..)
+ private boolean mIsDoubleTapCandidate;
+ private boolean mIsTapCandidate;
+ private float mInitialDownX;
+ private float mInitialDownY;
+ private float mTouchSlopSquared;
+ private float mDoubleTapSlopSquared;
+
+ // Web kit state, tracks events observed by web kit. (guarded by mLock)
+ private final DispatchEventQueue mWebKitDispatchEventQueue = new DispatchEventQueue();
+ private final TouchStream mWebKitTouchStream = new TouchStream();
+ private final WebKitCallbacks mWebKitCallbacks;
+ private final WebKitHandler mWebKitHandler;
+ private boolean mWebKitDispatchScheduled;
+ private boolean mWebKitTimeoutScheduled;
+ private long mWebKitTimeoutTime;
+
+ // UI state, tracks events observed by the UI. (guarded by mLock)
+ private final DispatchEventQueue mUiDispatchEventQueue = new DispatchEventQueue();
+ private final TouchStream mUiTouchStream = new TouchStream();
+ private final UiCallbacks mUiCallbacks;
+ private final UiHandler mUiHandler;
+ private boolean mUiDispatchScheduled;
+
+ // Give up on web kit handling of input events when this timeout expires.
+ private static final long WEBKIT_TIMEOUT_MILLIS = 200;
+ private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
+ private static final int LONG_PRESS_TIMEOUT =
+ ViewConfiguration.getLongPressTimeout() + TAP_TIMEOUT;
+ private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+
+ /**
+ * Event type: Indicates a touch event type.
+ *
+ * This event is delivered together with a {@link MotionEvent} with one of the
+ * following actions: {@link MotionEvent#ACTION_DOWN}, {@link MotionEvent#ACTION_MOVE},
+ * {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_POINTER_DOWN},
+ * {@link MotionEvent#ACTION_POINTER_UP}, {@link MotionEvent#ACTION_CANCEL}.
+ */
+ public static final int EVENT_TYPE_TOUCH = 0;
+
+ /**
+ * Event type: Indicates a hover event type.
+ *
+ * This event is delivered together with a {@link MotionEvent} with one of the
+ * following actions: {@link MotionEvent#ACTION_HOVER_ENTER},
+ * {@link MotionEvent#ACTION_HOVER_MOVE}, {@link MotionEvent#ACTION_HOVER_MOVE}.
+ */
+ public static final int EVENT_TYPE_HOVER = 1;
+
+ /**
+ * Event type: Indicates a scroll event type.
+ *
+ * This event is delivered together with a {@link MotionEvent} with action
+ * {@link MotionEvent#ACTION_SCROLL}.
+ */
+ public static final int EVENT_TYPE_SCROLL = 2;
+
+ /**
+ * Event type: Indicates a long-press event type.
+ *
+ * This event is delivered in the middle of a sequence of {@link #EVENT_TYPE_TOUCH} events.
+ * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_MOVE}
+ * that indicates the current touch coordinates of the long-press.
+ *
+ * This event is sent when the current touch gesture has been held longer than
+ * the long-press interval.
+ */
+ public static final int EVENT_TYPE_LONG_PRESS = 3;
+
+ /**
+ * Event type: Indicates a click event type.
+ *
+ * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
+ * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
+ * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
+ * that indicates the location of the click.
+ *
+ * This event is sent shortly after the end of a touch after the double-tap
+ * interval has expired to indicate a click.
+ */
+ public static final int EVENT_TYPE_CLICK = 4;
+
+ /**
+ * Event type: Indicates a double-tap event type.
+ *
+ * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
+ * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
+ * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
+ * that indicates the location of the double-tap.
+ *
+ * This event is sent immediately after a sequence of two touches separated
+ * in time by no more than the double-tap interval and separated in space
+ * by no more than the double-tap slop.
+ */
+ public static final int EVENT_TYPE_DOUBLE_TAP = 5;
+
+ /**
+ * Flag: This event is private to this queue. Do not forward it.
+ */
+ public static final int FLAG_PRIVATE = 1 << 0;
+
+ /**
+ * Flag: This event is currently being processed by web kit.
+ * If a timeout occurs, make a copy of it before forwarding the event to another queue.
+ */
+ public static final int FLAG_WEBKIT_IN_PROGRESS = 1 << 1;
+
+ /**
+ * Flag: A timeout occurred while waiting for web kit to process this input event.
+ */
+ public static final int FLAG_WEBKIT_TIMEOUT = 1 << 2;
+
+ /**
+ * Flag: Indicates that the event was transformed for delivery to web kit.
+ * The event must be transformed back before being delivered to the UI.
+ */
+ public static final int FLAG_WEBKIT_TRANSFORMED_EVENT = 1 << 3;
+
+ public WebViewInputDispatcher(UiCallbacks uiCallbacks, WebKitCallbacks webKitCallbacks) {
+ this.mUiCallbacks = uiCallbacks;
+ mUiHandler = new UiHandler(uiCallbacks.getUiLooper());
+
+ this.mWebKitCallbacks = webKitCallbacks;
+ mWebKitHandler = new WebKitHandler(webKitCallbacks.getWebKitLooper());
+
+ ViewConfiguration config = ViewConfiguration.get(mUiCallbacks.getContext());
+ mDoubleTapSlopSquared = config.getScaledDoubleTapSlop();
+ mDoubleTapSlopSquared = (mDoubleTapSlopSquared * mDoubleTapSlopSquared);
+ mTouchSlopSquared = config.getScaledTouchSlop();
+ mTouchSlopSquared = (mTouchSlopSquared * mTouchSlopSquared);
+ }
+
+ /**
+ * Sets whether web kit wants to receive touch events.
+ *
+ * @param enable True to enable dispatching of touch events to web kit, otherwise
+ * web kit will be skipped.
+ */
+ public void setWebKitWantsTouchEvents(boolean enable) {
+ if (DEBUG) {
+ Log.d(TAG, "webkitWantsTouchEvents: " + enable);
+ }
+ synchronized (mLock) {
+ if (mPostSendTouchEventsToWebKit != enable) {
+ if (!enable) {
+ enqueueWebKitCancelTouchEventIfNeededLocked();
+ }
+ mPostSendTouchEventsToWebKit = enable;
+ }
+ }
+ }
+
+ /**
+ * Posts a pointer event to the dispatch queue.
+ *
+ * @param event The event to post.
+ * @param webKitXOffset X offset to apply to events before dispatching them to web kit.
+ * @param webKitYOffset Y offset to apply to events before dispatching them to web kit.
+ * @param webKitScale The scale factor to apply to translated events before dispatching
+ * them to web kit.
+ * @return True if the dispatcher will handle the event, false if the event is unsupported.
+ */
+ public boolean postPointerEvent(MotionEvent event,
+ int webKitXOffset, int webKitYOffset, float webKitScale) {
+ if (event == null
+ || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
+ throw new IllegalArgumentException("event must be a pointer event");
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "postPointerEvent: " + event);
+ }
+
+ final int action = event.getActionMasked();
+ final int eventType;
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_CANCEL:
+ eventType = EVENT_TYPE_TOUCH;
+ break;
+ case MotionEvent.ACTION_SCROLL:
+ eventType = EVENT_TYPE_SCROLL;
+ break;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_HOVER_EXIT:
+ eventType = EVENT_TYPE_HOVER;
+ break;
+ default:
+ return false; // currently unsupported event type
+ }
+
+ synchronized (mLock) {
+ // Ensure that the event is consistent and should be delivered.
+ MotionEvent eventToEnqueue = event;
+ if (eventType == EVENT_TYPE_TOUCH) {
+ eventToEnqueue = mPostTouchStream.update(event);
+ if (eventToEnqueue == null) {
+ if (DEBUG) {
+ Log.d(TAG, "postPointerEvent: dropped event " + event);
+ }
+ unscheduleLongPressLocked();
+ unscheduleClickLocked();
+ return false;
+ }
+
+ if (mPostSendTouchEventsToWebKit
+ && mPostDoNotSendTouchEventsToWebKitUntilNextGesture
+ && action == MotionEvent.ACTION_DOWN) {
+ // Recover from a previous web kit timeout.
+ mPostDoNotSendTouchEventsToWebKitUntilNextGesture = false;
+ }
+ }
+
+ // Copy the event because we need to retain ownership.
+ if (eventToEnqueue == event) {
+ eventToEnqueue = event.copy();
+ }
+
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, eventType, 0,
+ webKitXOffset, webKitYOffset, webKitScale);
+ enqueueEventLocked(d);
+ }
+ return true;
+ }
+
+ private void scheduleLongPressLocked() {
+ unscheduleLongPressLocked();
+ mPostLongPressScheduled = true;
+ mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_LONG_PRESS,
+ LONG_PRESS_TIMEOUT);
+ }
+
+ private void unscheduleLongPressLocked() {
+ if (mPostLongPressScheduled) {
+ mPostLongPressScheduled = false;
+ mUiHandler.removeMessages(UiHandler.MSG_LONG_PRESS);
+ }
+ }
+
+ private void postLongPress() {
+ synchronized (mLock) {
+ if (!mPostLongPressScheduled) {
+ return;
+ }
+ mPostLongPressScheduled = false;
+
+ MotionEvent event = mPostTouchStream.getLastEvent();
+ if (event == null) {
+ return;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ break;
+ default:
+ return;
+ }
+
+ MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+ eventToEnqueue.setAction(MotionEvent.ACTION_MOVE);
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_LONG_PRESS, 0,
+ mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+ enqueueEventLocked(d);
+ }
+ }
+
+ private void scheduleClickLocked() {
+ unscheduleClickLocked();
+ mPostClickScheduled = true;
+ mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_CLICK, DOUBLE_TAP_TIMEOUT);
+ }
+
+ private void unscheduleClickLocked() {
+ if (mPostClickScheduled) {
+ mPostClickScheduled = false;
+ mUiHandler.removeMessages(UiHandler.MSG_CLICK);
+ }
+ }
+
+ private void postClick() {
+ synchronized (mLock) {
+ if (!mPostClickScheduled) {
+ return;
+ }
+ mPostClickScheduled = false;
+
+ MotionEvent event = mPostTouchStream.getLastEvent();
+ if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
+ return;
+ }
+
+ MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_CLICK, 0,
+ mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+ enqueueEventLocked(d);
+ }
+ }
+
+ private void checkForDoubleTapOnDownLocked(MotionEvent event) {
+ mIsDoubleTapCandidate = false;
+ if (!mPostClickScheduled) {
+ return;
+ }
+ int deltaX = (int) mInitialDownX - (int) event.getX();
+ int deltaY = (int) mInitialDownY - (int) event.getY();
+ if ((deltaX * deltaX + deltaY * deltaY) < mDoubleTapSlopSquared) {
+ unscheduleClickLocked();
+ mIsDoubleTapCandidate = true;
+ }
+ }
+
+ private boolean isClickCandidateLocked(MotionEvent event) {
+ if (event == null
+ || event.getActionMasked() != MotionEvent.ACTION_UP
+ || !mIsTapCandidate) {
+ return false;
+ }
+ long downDuration = event.getEventTime() - event.getDownTime();
+ return downDuration < TAP_TIMEOUT;
+ }
+
+ private void enqueueDoubleTapLocked(MotionEvent event) {
+ unscheduleClickLocked();
+ MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_DOUBLE_TAP, 0,
+ mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+ enqueueEventLocked(d);
+ mIsDoubleTapCandidate = false;
+ }
+
+ private void checkForSlopLocked(MotionEvent event) {
+ if (!mIsTapCandidate) {
+ return;
+ }
+ int deltaX = (int) mInitialDownX - (int) event.getX();
+ int deltaY = (int) mInitialDownY - (int) event.getY();
+ if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquared) {
+ unscheduleLongPressLocked();
+ mIsTapCandidate = false;
+ }
+ }
+
+ private void updateStateTrackersLocked(DispatchEvent d, MotionEvent event) {
+ mPostLastWebKitXOffset = d.mWebKitXOffset;
+ mPostLastWebKitYOffset = d.mWebKitYOffset;
+ mPostLastWebKitScale = d.mWebKitScale;
+ int action = event != null ? event.getAction() : MotionEvent.ACTION_CANCEL;
+ if (d.mEventType != EVENT_TYPE_TOUCH) {
+ return;
+ }
+
+ if (action == MotionEvent.ACTION_CANCEL
+ || event.getPointerCount() > 1) {
+ unscheduleLongPressLocked();
+ unscheduleClickLocked();
+ mIsDoubleTapCandidate = false;
+ mIsTapCandidate = false;
+ } else if (action == MotionEvent.ACTION_DOWN) {
+ checkForDoubleTapOnDownLocked(event);
+ scheduleLongPressLocked();
+ mIsTapCandidate = true;
+ mInitialDownX = event.getX();
+ mInitialDownY = event.getY();
+ } else if (action == MotionEvent.ACTION_UP) {
+ unscheduleLongPressLocked();
+ if (isClickCandidateLocked(event)) {
+ if (mIsDoubleTapCandidate) {
+ enqueueDoubleTapLocked(event);
+ } else {
+ scheduleClickLocked();
+ }
+ }
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ checkForSlopLocked(event);
+ }
+ }
+
+ /**
+ * Dispatches pending web kit events.
+ * Must only be called from the web kit thread.
+ *
+ * This method may be used to flush the queue of pending input events
+ * immediately. This method may help to reduce input dispatch latency
+ * if called before certain expensive operations such as drawing.
+ */
+ public void dispatchWebKitEvents() {
+ dispatchWebKitEvents(false);
+ }
+
+ private void dispatchWebKitEvents(boolean calledFromHandler) {
+ for (;;) {
+ // Get the next event, but leave it in the queue so we can move it to the UI
+ // queue if a timeout occurs.
+ DispatchEvent d;
+ MotionEvent event;
+ final int eventType;
+ int flags;
+ synchronized (mLock) {
+ if (!ENABLE_EVENT_BATCHING) {
+ drainStaleWebKitEventsLocked();
+ }
+ d = mWebKitDispatchEventQueue.mHead;
+ if (d == null) {
+ if (mWebKitDispatchScheduled) {
+ mWebKitDispatchScheduled = false;
+ if (!calledFromHandler) {
+ mWebKitHandler.removeMessages(
+ WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
+ }
+ }
+ return;
+ }
+
+ event = d.mEvent;
+ if (event != null) {
+ event.offsetLocation(d.mWebKitXOffset, d.mWebKitYOffset);
+ event.scale(d.mWebKitScale);
+ d.mFlags |= FLAG_WEBKIT_TRANSFORMED_EVENT;
+ }
+
+ eventType = d.mEventType;
+ if (eventType == EVENT_TYPE_TOUCH) {
+ event = mWebKitTouchStream.update(event);
+ if (DEBUG && event == null && d.mEvent != null) {
+ Log.d(TAG, "dispatchWebKitEvents: dropped event " + d.mEvent);
+ }
+ }
+
+ d.mFlags |= FLAG_WEBKIT_IN_PROGRESS;
+ flags = d.mFlags;
+ }
+
+ // Handle the event.
+ final boolean preventDefault;
+ if (event == null) {
+ preventDefault = false;
+ } else {
+ preventDefault = dispatchWebKitEvent(event, eventType, flags);
+ }
+
+ synchronized (mLock) {
+ flags = d.mFlags;
+ d.mFlags = flags & ~FLAG_WEBKIT_IN_PROGRESS;
+ boolean recycleEvent = event != d.mEvent;
+
+ if ((flags & FLAG_WEBKIT_TIMEOUT) != 0) {
+ // A timeout occurred!
+ recycleDispatchEventLocked(d);
+ } else {
+ // Web kit finished in a timely manner. Dequeue the event.
+ assert mWebKitDispatchEventQueue.mHead == d;
+ mWebKitDispatchEventQueue.dequeue();
+
+ updateWebKitTimeoutLocked();
+
+ if ((flags & FLAG_PRIVATE) != 0) {
+ // Event was intended for web kit only. All done.
+ recycleDispatchEventLocked(d);
+ } else if (preventDefault) {
+ // Web kit has decided to consume the event!
+ if (d.mEventType == EVENT_TYPE_TOUCH) {
+ enqueueUiCancelTouchEventIfNeededLocked();
+ }
+ } else {
+ // Web kit is being friendly. Pass the event to the UI.
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ if (event != null && recycleEvent) {
+ event.recycle();
+ }
+ }
+ }
+ }
+
+ // Runs on web kit thread.
+ private boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchWebKitEvent: event=" + event
+ + ", eventType=" + eventType + ", flags=" + flags);
+ }
+ boolean preventDefault = mWebKitCallbacks.dispatchWebKitEvent(
+ event, eventType, flags);
+ if (DEBUG) {
+ Log.d(TAG, "dispatchWebKitEvent: preventDefault=" + preventDefault);
+ }
+ return preventDefault;
+ }
+
+ private boolean isMoveEventLocked(DispatchEvent d) {
+ return d.mEvent != null
+ && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
+ }
+
+ private void drainStaleWebKitEventsLocked() {
+ DispatchEvent d = mWebKitDispatchEventQueue.mHead;
+ while (d != null && d.mNext != null
+ && isMoveEventLocked(d)
+ && isMoveEventLocked(d.mNext)) {
+ DispatchEvent next = d.mNext;
+ skipWebKitEventLocked(d);
+ d = next;
+ }
+ mWebKitDispatchEventQueue.mHead = d;
+ }
+
+ // Runs on UI thread in response to the web kit thread appearing to be unresponsive.
+ private void handleWebKitTimeout() {
+ synchronized (mLock) {
+ if (!mWebKitTimeoutScheduled) {
+ return;
+ }
+ mWebKitTimeoutScheduled = false;
+
+ if (DEBUG) {
+ Log.d(TAG, "handleWebKitTimeout: timeout occurred!");
+ }
+
+ // Drain the web kit event queue.
+ DispatchEvent d = mWebKitDispatchEventQueue.dequeueList();
+
+ // If web kit was processing an event (must be at the head of the list because
+ // it can only do one at a time), then clone it or ignore it.
+ if ((d.mFlags & FLAG_WEBKIT_IN_PROGRESS) != 0) {
+ d.mFlags |= FLAG_WEBKIT_TIMEOUT;
+ if ((d.mFlags & FLAG_PRIVATE) != 0) {
+ d = d.mNext; // the event is private to web kit, ignore it
+ } else {
+ d = copyDispatchEventLocked(d);
+ d.mFlags &= ~FLAG_WEBKIT_IN_PROGRESS;
+ }
+ }
+
+ // Enqueue all non-private events for handling by the UI thread.
+ while (d != null) {
+ DispatchEvent next = d.mNext;
+ skipWebKitEventLocked(d);
+ d = next;
+ }
+
+ // Tell web kit to cancel all pending touches.
+ // This also prevents us from sending web kit any more touches until the
+ // next gesture begins. (As required to ensure touch event stream consistency.)
+ enqueueWebKitCancelTouchEventIfNeededLocked();
+ }
+ }
+
+ private void skipWebKitEventLocked(DispatchEvent d) {
+ d.mNext = null;
+ if ((d.mFlags & FLAG_PRIVATE) != 0) {
+ recycleDispatchEventLocked(d);
+ } else {
+ d.mFlags |= FLAG_WEBKIT_TIMEOUT;
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ /**
+ * Dispatches pending UI events.
+ * Must only be called from the UI thread.
+ *
+ * This method may be used to flush the queue of pending input events
+ * immediately. This method may help to reduce input dispatch latency
+ * if called before certain expensive operations such as drawing.
+ */
+ public void dispatchUiEvents() {
+ dispatchUiEvents(false);
+ }
+
+ private void dispatchUiEvents(boolean calledFromHandler) {
+ for (;;) {
+ MotionEvent event;
+ final int eventType;
+ final int flags;
+ synchronized (mLock) {
+ DispatchEvent d = mUiDispatchEventQueue.dequeue();
+ if (d == null) {
+ if (mUiDispatchScheduled) {
+ mUiDispatchScheduled = false;
+ if (!calledFromHandler) {
+ mUiHandler.removeMessages(UiHandler.MSG_DISPATCH_UI_EVENTS);
+ }
+ }
+ return;
+ }
+
+ event = d.mEvent;
+ if (event != null && (d.mFlags & FLAG_WEBKIT_TRANSFORMED_EVENT) != 0) {
+ event.scale(1.0f / d.mWebKitScale);
+ event.offsetLocation(-d.mWebKitXOffset, -d.mWebKitYOffset);
+ d.mFlags &= ~FLAG_WEBKIT_TRANSFORMED_EVENT;
+ }
+
+ eventType = d.mEventType;
+ if (eventType == EVENT_TYPE_TOUCH) {
+ event = mUiTouchStream.update(event);
+ if (DEBUG && event == null && d.mEvent != null) {
+ Log.d(TAG, "dispatchUiEvents: dropped event " + d.mEvent);
+ }
+ }
+
+ flags = d.mFlags;
+
+ updateStateTrackersLocked(d, event);
+ if (event == d.mEvent) {
+ d.mEvent = null; // retain ownership of event, don't recycle it yet
+ }
+ recycleDispatchEventLocked(d);
+ }
+
+ // Handle the event.
+ if (event != null) {
+ dispatchUiEvent(event, eventType, flags);
+ event.recycle();
+ }
+ }
+ }
+
+ // Runs on UI thread.
+ private void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchUiEvent: event=" + event
+ + ", eventType=" + eventType + ", flags=" + flags);
+ }
+ mUiCallbacks.dispatchUiEvent(event, eventType, flags);
+ }
+
+ private void enqueueEventLocked(DispatchEvent d) {
+ if (!shouldSkipWebKit(d.mEventType)) {
+ enqueueWebKitEventLocked(d);
+ } else {
+ enqueueUiEventLocked(d);
+ }
+ }
+
+ private boolean shouldSkipWebKit(int eventType) {
+ switch (eventType) {
+ case EVENT_TYPE_CLICK:
+ case EVENT_TYPE_HOVER:
+ case EVENT_TYPE_SCROLL:
+ return false;
+ case EVENT_TYPE_TOUCH:
+ return !mPostSendTouchEventsToWebKit
+ || mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
+ }
+ return true;
+ }
+
+ private void enqueueWebKitCancelTouchEventIfNeededLocked() {
+ // We want to cancel touch events that were delivered to web kit.
+ // Enqueue a null event at the end of the queue if needed.
+ if (mWebKitTouchStream.isCancelNeeded() || !mWebKitDispatchEventQueue.isEmpty()) {
+ DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
+ 0, 0, 1.0f);
+ enqueueWebKitEventUnbatchedLocked(d);
+ mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
+ }
+ }
+
+ private void enqueueWebKitEventLocked(DispatchEvent d) {
+ if (batchEventLocked(d, mWebKitDispatchEventQueue.mTail)) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueWebKitEventLocked: batched event " + d.mEvent);
+ }
+ recycleDispatchEventLocked(d);
+ } else {
+ enqueueWebKitEventUnbatchedLocked(d);
+ }
+ }
+
+ private void enqueueWebKitEventUnbatchedLocked(DispatchEvent d) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueWebKitEventUnbatchedLocked: enqueued event " + d.mEvent);
+ }
+ mWebKitDispatchEventQueue.enqueue(d);
+ scheduleWebKitDispatchLocked();
+ updateWebKitTimeoutLocked();
+ }
+
+ private void scheduleWebKitDispatchLocked() {
+ if (!mWebKitDispatchScheduled) {
+ mWebKitHandler.sendEmptyMessage(WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
+ mWebKitDispatchScheduled = true;
+ }
+ }
+
+ private void updateWebKitTimeoutLocked() {
+ DispatchEvent d = mWebKitDispatchEventQueue.mHead;
+ if (d != null && mWebKitTimeoutScheduled && mWebKitTimeoutTime == d.mTimeoutTime) {
+ return;
+ }
+ if (mWebKitTimeoutScheduled) {
+ mUiHandler.removeMessages(UiHandler.MSG_WEBKIT_TIMEOUT);
+ mWebKitTimeoutScheduled = false;
+ }
+ if (d != null) {
+ mUiHandler.sendEmptyMessageAtTime(UiHandler.MSG_WEBKIT_TIMEOUT, d.mTimeoutTime);
+ mWebKitTimeoutScheduled = true;
+ mWebKitTimeoutTime = d.mTimeoutTime;
+ }
+ }
+
+ private void enqueueUiCancelTouchEventIfNeededLocked() {
+ // We want to cancel touch events that were delivered to the UI.
+ // Enqueue a null event at the end of the queue if needed.
+ if (mUiTouchStream.isCancelNeeded() || !mUiDispatchEventQueue.isEmpty()) {
+ DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
+ 0, 0, 1.0f);
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ private void enqueueUiEventLocked(DispatchEvent d) {
+ if (batchEventLocked(d, mUiDispatchEventQueue.mTail)) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueUiEventLocked: batched event " + d.mEvent);
+ }
+ recycleDispatchEventLocked(d);
+ } else {
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ private void enqueueUiEventUnbatchedLocked(DispatchEvent d) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueUiEventUnbatchedLocked: enqueued event " + d.mEvent);
+ }
+ mUiDispatchEventQueue.enqueue(d);
+ scheduleUiDispatchLocked();
+ }
+
+ private void scheduleUiDispatchLocked() {
+ if (!mUiDispatchScheduled) {
+ mUiHandler.sendEmptyMessage(UiHandler.MSG_DISPATCH_UI_EVENTS);
+ mUiDispatchScheduled = true;
+ }
+ }
+
+ private boolean batchEventLocked(DispatchEvent in, DispatchEvent tail) {
+ if (!ENABLE_EVENT_BATCHING) {
+ return false;
+ }
+ if (tail != null && tail.mEvent != null && in.mEvent != null
+ && in.mEventType == tail.mEventType
+ && in.mFlags == tail.mFlags
+ && in.mWebKitXOffset == tail.mWebKitXOffset
+ && in.mWebKitYOffset == tail.mWebKitYOffset
+ && in.mWebKitScale == tail.mWebKitScale) {
+ return tail.mEvent.addBatch(in.mEvent);
+ }
+ return false;
+ }
+
+ private DispatchEvent obtainDispatchEventLocked(MotionEvent event,
+ int eventType, int flags, int webKitXOffset, int webKitYOffset, float webKitScale) {
+ DispatchEvent d = obtainUninitializedDispatchEventLocked();
+ d.mEvent = event;
+ d.mEventType = eventType;
+ d.mFlags = flags;
+ d.mTimeoutTime = SystemClock.uptimeMillis() + WEBKIT_TIMEOUT_MILLIS;
+ d.mWebKitXOffset = webKitXOffset;
+ d.mWebKitYOffset = webKitYOffset;
+ d.mWebKitScale = webKitScale;
+ if (DEBUG) {
+ Log.d(TAG, "Timeout time: " + (d.mTimeoutTime - SystemClock.uptimeMillis()));
+ }
+ return d;
+ }
+
+ private DispatchEvent copyDispatchEventLocked(DispatchEvent d) {
+ DispatchEvent copy = obtainUninitializedDispatchEventLocked();
+ if (d.mEvent != null) {
+ copy.mEvent = d.mEvent.copy();
+ }
+ copy.mEventType = d.mEventType;
+ copy.mFlags = d.mFlags;
+ copy.mTimeoutTime = d.mTimeoutTime;
+ copy.mWebKitXOffset = d.mWebKitXOffset;
+ copy.mWebKitYOffset = d.mWebKitYOffset;
+ copy.mWebKitScale = d.mWebKitScale;
+ copy.mNext = d.mNext;
+ return copy;
+ }
+
+ private DispatchEvent obtainUninitializedDispatchEventLocked() {
+ DispatchEvent d = mDispatchEventPool;
+ if (d != null) {
+ mDispatchEventPoolSize -= 1;
+ mDispatchEventPool = d.mNext;
+ d.mNext = null;
+ } else {
+ d = new DispatchEvent();
+ }
+ return d;
+ }
+
+ private void recycleDispatchEventLocked(DispatchEvent d) {
+ if (d.mEvent != null) {
+ d.mEvent.recycle();
+ d.mEvent = null;
+ }
+
+ if (mDispatchEventPoolSize < MAX_DISPATCH_EVENT_POOL_SIZE) {
+ mDispatchEventPoolSize += 1;
+ d.mNext = mDispatchEventPool;
+ mDispatchEventPool = d;
+ }
+ }
+
+ /* Implemented by {@link WebViewClassic} to perform operations on the UI thread. */
+ public static interface UiCallbacks {
+ /**
+ * Gets the UI thread's looper.
+ * @return The looper.
+ */
+ public Looper getUiLooper();
+
+ /**
+ * Gets the UI's context
+ * @return The context
+ */
+ public Context getContext();
+
+ /**
+ * Dispatches an event to the UI.
+ * @param event The event.
+ * @param eventType The event type.
+ * @param flags The event's dispatch flags.
+ */
+ public void dispatchUiEvent(MotionEvent event, int eventType, int flags);
+ }
+
+ /* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */
+ public static interface WebKitCallbacks {
+ /**
+ * Gets the web kit thread's looper.
+ * @return The looper.
+ */
+ public Looper getWebKitLooper();
+
+ /**
+ * Dispatches an event to web kit.
+ * @param event The event.
+ * @param eventType The event type.
+ * @param flags The event's dispatch flags.
+ * @return True if web kit wants to prevent default event handling.
+ */
+ public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags);
+ }
+
+ // Runs on UI thread.
+ private final class UiHandler extends Handler {
+ public static final int MSG_DISPATCH_UI_EVENTS = 1;
+ public static final int MSG_WEBKIT_TIMEOUT = 2;
+ public static final int MSG_LONG_PRESS = 3;
+ public static final int MSG_CLICK = 4;
+
+ public UiHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISPATCH_UI_EVENTS:
+ dispatchUiEvents(true);
+ break;
+ case MSG_WEBKIT_TIMEOUT:
+ handleWebKitTimeout();
+ break;
+ case MSG_LONG_PRESS:
+ postLongPress();
+ break;
+ case MSG_CLICK:
+ postClick();
+ break;
+ default:
+ throw new IllegalStateException("Unknown message type: " + msg.what);
+ }
+ }
+ }
+
+ // Runs on web kit thread.
+ private final class WebKitHandler extends Handler {
+ public static final int MSG_DISPATCH_WEBKIT_EVENTS = 1;
+
+ public WebKitHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISPATCH_WEBKIT_EVENTS:
+ dispatchWebKitEvents(true);
+ break;
+ default:
+ throw new IllegalStateException("Unknown message type: " + msg.what);
+ }
+ }
+ }
+
+ private static final class DispatchEvent {
+ public DispatchEvent mNext;
+
+ public MotionEvent mEvent;
+ public int mEventType;
+ public int mFlags;
+ public long mTimeoutTime;
+ public int mWebKitXOffset;
+ public int mWebKitYOffset;
+ public float mWebKitScale;
+ }
+
+ private static final class DispatchEventQueue {
+ public DispatchEvent mHead;
+ public DispatchEvent mTail;
+
+ public boolean isEmpty() {
+ return mHead != null;
+ }
+
+ public void enqueue(DispatchEvent d) {
+ if (mHead == null) {
+ mHead = d;
+ mTail = d;
+ } else {
+ mTail.mNext = d;
+ mTail = d;
+ }
+ }
+
+ public DispatchEvent dequeue() {
+ DispatchEvent d = mHead;
+ if (d != null) {
+ DispatchEvent next = d.mNext;
+ if (next == null) {
+ mHead = null;
+ mTail = null;
+ } else {
+ mHead = next;
+ d.mNext = null;
+ }
+ }
+ return d;
+ }
+
+ public DispatchEvent dequeueList() {
+ DispatchEvent d = mHead;
+ if (d != null) {
+ mHead = null;
+ mTail = null;
+ }
+ return d;
+ }
+ }
+
+ /**
+ * Keeps track of a stream of touch events so that we can discard touch
+ * events that would make the stream inconsistent.
+ */
+ private static final class TouchStream {
+ private MotionEvent mLastEvent;
+
+ /**
+ * Gets the last touch event that was delivered.
+ * @return The last touch event, or null if none.
+ */
+ public MotionEvent getLastEvent() {
+ return mLastEvent;
+ }
+
+ /**
+ * Updates the touch event stream.
+ * @param event The event that we intend to send, or null to cancel the
+ * touch event stream.
+ * @return The event that we should actually send, or null if no event should
+ * be sent because the proposed event would make the stream inconsistent.
+ */
+ public MotionEvent update(MotionEvent event) {
+ if (event == null) {
+ if (isCancelNeeded()) {
+ event = mLastEvent;
+ if (event != null) {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ mLastEvent = null;
+ }
+ }
+ return event;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mLastEvent == null
+ || mLastEvent.getAction() == MotionEvent.ACTION_UP) {
+ return null;
+ }
+ updateLastEvent(event);
+ return event;
+
+ case MotionEvent.ACTION_DOWN:
+ updateLastEvent(event);
+ return event;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (mLastEvent == null) {
+ return null;
+ }
+ updateLastEvent(null);
+ return event;
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if there is a gesture in progress that may need to be canceled.
+ * @return True if cancel is needed.
+ */
+ public boolean isCancelNeeded() {
+ return mLastEvent != null && mLastEvent.getAction() != MotionEvent.ACTION_UP;
+ }
+
+ private void updateLastEvent(MotionEvent event) {
+ if (mLastEvent != null) {
+ mLastEvent.recycle();
+ }
+ mLastEvent = event != null ? MotionEvent.obtainNoHistory(event) : null;
+ }
+ }
+} \ No newline at end of file