diff options
Diffstat (limited to 'core/java/android')
21 files changed, 1614 insertions, 1155 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 <activity_component_name>". * * @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 <yourservicename>". + * This is distinct from "dumpsys <servicename>", 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 <provider_component_name>". * * @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/view/View.java b/core/java/android/view/View.java index d62e32f..adc6dda 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8698,7 +8698,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal invalidateParentCaches(); onScrollChanged(mScrollX, mScrollY, oldX, oldY); if (!awakenScrollBars()) { - invalidate(true); + postInvalidateOnAnimation(); } } } @@ -8852,7 +8852,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (invalidate) { // Invalidate to show the scrollbars - invalidate(true); + postInvalidateOnAnimation(); } if (scrollCache.state == ScrollabilityCache.OFF) { @@ -9212,6 +9212,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return Returns true if the Runnable was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. + * + * @see #postDelayed + * @see #removeCallbacks */ public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; @@ -9241,6 +9244,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * result of true does not mean the Runnable will be processed -- * if the looper is quit before the delivery time of the message * occurs then the message will be dropped. + * + * @see #post + * @see #removeCallbacks */ public boolean postDelayed(Runnable action, long delayMillis) { final AttachInfo attachInfo = mAttachInfo; @@ -9261,7 +9267,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param action The Runnable that will be executed. * - * @hide + * @see #postOnAnimationDelayed + * @see #removeCallbacks */ public void postOnAnimation(Runnable action) { final AttachInfo attachInfo = mAttachInfo; @@ -9286,7 +9293,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param delayMillis The delay (in milliseconds) until the Runnable * will be executed. * - * @hide + * @see #postOnAnimation + * @see #removeCallbacks */ public void postOnAnimationDelayed(Runnable action, long delayMillis) { final AttachInfo attachInfo = mAttachInfo; @@ -9311,6 +9319,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * false otherwise. When the returned value is true, the Runnable * may or may not have been actually removed from the message queue * (for instance, if the Runnable was not in the queue already.) + * + * @see #post + * @see #postDelayed + * @see #postOnAnimation + * @see #postOnAnimationDelayed */ public boolean removeCallbacks(Runnable action) { if (action != null) { @@ -9335,6 +9348,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * only when this View is attached to a window.</p> * * @see #invalidate() + * @see #postInvalidateDelayed(long) */ public void postInvalidate() { postInvalidateDelayed(0); @@ -9354,6 +9368,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @see #invalidate(int, int, int, int) * @see #invalidate(Rect) + * @see #postInvalidateDelayed(long, int, int, int, int) */ public void postInvalidate(int left, int top, int right, int bottom) { postInvalidateDelayed(0, left, top, right, bottom); @@ -9368,6 +9383,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param delayMilliseconds the duration in milliseconds to delay the * invalidation by + * + * @see #invalidate() + * @see #postInvalidate() */ public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating @@ -9391,6 +9409,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param top The top coordinate of the rectangle to invalidate. * @param right The right coordinate of the rectangle to invalidate. * @param bottom The bottom coordinate of the rectangle to invalidate. + * + * @see #invalidate(int, int, int, int) + * @see #invalidate(Rect) + * @see #postInvalidate(int, int, int, int) */ public void postInvalidateDelayed(long delayMilliseconds, int left, int top, int right, int bottom) { @@ -9417,7 +9439,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * - * @hide + * @see #invalidate() */ public void postInvalidateOnAnimation() { // We try only with the AttachInfo because there's no point in invalidating @@ -9440,7 +9462,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param right The right coordinate of the rectangle to invalidate. * @param bottom The bottom coordinate of the rectangle to invalidate. * - * @hide + * @see #invalidate(int, int, int, int) + * @see #invalidate(Rect) */ public void postInvalidateOnAnimation(int left, int top, int right, int bottom) { // We try only with the AttachInfo because there's no point in invalidating @@ -9769,7 +9792,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @attr ref android.R.styleable#View_scrollbarSize */ public int getScrollBarSize() { - return mScrollCache == null ? ViewConfiguration.getScrollBarSize() : + return mScrollCache == null ? ViewConfiguration.get(mContext).getScaledScrollBarSize() : mScrollCache.scrollBarSize; } @@ -12948,6 +12971,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * background */ public void setBackground(Drawable background) { + //noinspection deprecation setBackgroundDrawable(background); } @@ -14273,7 +14297,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public void setAnimation(Animation animation) { mCurrentAnimation = animation; + if (animation != null) { + // If the screen is off assume the animation start time is now instead of + // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time + // would cause the animation to start when the screen turns back on + if (mAttachInfo != null && !mAttachInfo.mScreenOn && + animation.getStartTime() == Animation.START_ON_FIRST_FRAME) { + animation.setStartTime(AnimationUtils.currentAnimationTimeMillis()); + } animation.reset(); } } diff --git a/core/java/android/webkit/DeviceMotionAndOrientationManager.java b/core/java/android/webkit/DeviceMotionAndOrientationManager.java index 79b78d8..ea1e9ff 100644 --- a/core/java/android/webkit/DeviceMotionAndOrientationManager.java +++ b/core/java/android/webkit/DeviceMotionAndOrientationManager.java @@ -22,9 +22,8 @@ package android.webkit; * * This could be part of WebViewCore, but have moved it to its own class to * avoid bloat there. - * @hide */ -public final class DeviceMotionAndOrientationManager { +final class DeviceMotionAndOrientationManager { private WebViewCore mWebViewCore; public DeviceMotionAndOrientationManager(WebViewCore webViewCore) { @@ -32,12 +31,12 @@ public final class DeviceMotionAndOrientationManager { } /** - * Sets whether the Page for this WebViewCore should use a mock DeviceOrientation + * Sets that the Page for this WebViewCore should use a mock DeviceOrientation * client. */ - public void useMock() { + public void setUseMock() { assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName()); - nativeUseMock(mWebViewCore); + nativeSetUseMock(mWebViewCore); } /** @@ -66,7 +65,7 @@ public final class DeviceMotionAndOrientationManager { } // Native functions - private static native void nativeUseMock(WebViewCore webViewCore); + private static native void nativeSetUseMock(WebViewCore webViewCore); private static native void nativeSetMockOrientation(WebViewCore webViewCore, boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma); diff --git a/core/java/android/webkit/DeviceMotionService.java b/core/java/android/webkit/DeviceMotionService.java index b4d5759..9121429 100755 --- a/core/java/android/webkit/DeviceMotionService.java +++ b/core/java/android/webkit/DeviceMotionService.java @@ -153,6 +153,7 @@ final class DeviceMotionService implements SensorEventListener { * SensorEventListener implementation. * Callbacks happen on the thread on which we registered - the WebCore thread. */ + @Override public void onSensorChanged(SensorEvent event) { assert(event.values.length == 3); assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName()); @@ -170,6 +171,7 @@ final class DeviceMotionService implements SensorEventListener { } } + @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName()); } diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java index 47c8ab7..2e8656c 100755 --- a/core/java/android/webkit/DeviceOrientationService.java +++ b/core/java/android/webkit/DeviceOrientationService.java @@ -184,6 +184,7 @@ final class DeviceOrientationService implements SensorEventListener { * SensorEventListener implementation. * Callbacks happen on the thread on which we registered - the WebCore thread. */ + @Override public void onSensorChanged(SensorEvent event) { assert(event.values.length == 3); assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName()); @@ -217,6 +218,7 @@ final class DeviceOrientationService implements SensorEventListener { } } + @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName()); } 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 88e1eb7..9895a87 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); @@ -5011,8 +5049,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * * debug only */ - public void useMockDeviceOrientation() { - mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION); + public void setUseMockDeviceOrientation() { + mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_DEVICE_ORIENTATION); } /** @@ -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) { @@ -9440,13 +8596,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE) { hwAccelerated = true; } + + // result is of type LayerAndroid::InvalidateFlags, non zero means invalidate/redraw int result = nativeSetHwAccelerated(mNativeClass, hwAccelerated); - if (mWebViewCore == null || mBlockWebkitViewMessages) { - return; - } - if (result == 1) { - // Sync layers - mWebViewCore.layersDraw(); + if (mWebViewCore != null && !mBlockWebkitViewMessages && result != 0) { + mWebViewCore.contentDraw(); } } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index ec2cd5c..15a2d48 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 //------------------------------------------------------------------------- @@ -595,11 +600,6 @@ public final class WebViewCore { Point wh); /** - * Update the layers' content - */ - private native boolean nativeUpdateLayers(int nativeClass, int baseLayer); - - /** * Notify webkit that animations have begun (on the hardware accelerated content) */ private native void nativeNotifyAnimationStarted(int nativeClass); @@ -616,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); @@ -661,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, @@ -1025,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 @@ -1051,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; @@ -1072,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; @@ -1096,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. @@ -1115,9 +1104,6 @@ public final class WebViewCore { // Load and save web archives static final int SAVE_WEBARCHIVE = 147; - // Update layers - static final int WEBKIT_DRAW_LAYERS = 148; - static final int REMOVE_JS_INTERFACE = 149; // Network-based messaging @@ -1152,7 +1138,7 @@ public final class WebViewCore { // accessibility support static final int MODIFY_SELECTION = 190; - static final int USE_MOCK_DEVICE_ORIENTATION = 191; + static final int SET_USE_MOCK_DEVICE_ORIENTATION = 191; static final int AUTOFILL_FORM = 192; @@ -1266,10 +1252,6 @@ public final class WebViewCore { webkitDraw(); break; - case WEBKIT_DRAW_LAYERS: - webkitDrawLayers(); - break; - case DESTROY: // Time to take down the world. Cancel all pending // loads and destroy the native view and frame. @@ -1382,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; @@ -1502,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; @@ -1715,8 +1650,8 @@ public final class WebViewCore { .sendToTarget(); break; - case USE_MOCK_DEVICE_ORIENTATION: - useMockDeviceOrientation(); + case SET_USE_MOCK_DEVICE_ORIENTATION: + setUseMockDeviceOrientation(); break; case AUTOFILL_FORM: @@ -1824,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 */ @@ -2154,7 +2121,6 @@ public final class WebViewCore { // Used to avoid posting more than one draw message. private boolean mDrawIsScheduled; - private boolean mDrawLayersIsScheduled; // Used to avoid posting more than one split picture message. private boolean mSplitPictureIsScheduled; @@ -2200,25 +2166,6 @@ public final class WebViewCore { DrawData mLastDrawData = null; - // Only update the layers' content, not the base surface - // PictureSet. - private void webkitDrawLayers() { - mDrawLayersIsScheduled = false; - if (mDrawIsScheduled || mLastDrawData == null) { - removeMessages(EventHub.WEBKIT_DRAW); - webkitDraw(); - return; - } - // Directly update the layers we last passed to the UI side - if (nativeUpdateLayers(mNativeClass, mLastDrawData.mBaseLayer)) { - // If anything more complex than position has been touched, let's do a full draw - webkitDraw(); - } - mWebViewClassic.mPrivateHandler.removeMessages(WebViewClassic.INVAL_RECT_MSG_ID); - mWebViewClassic.mPrivateHandler.sendMessageAtFrontOfQueue(mWebViewClassic.mPrivateHandler - .obtainMessage(WebViewClassic.INVAL_RECT_MSG_ID)); - } - private Boolean m_skipDrawFlag = false; private boolean m_drawWasSkipped = false; @@ -2394,15 +2341,6 @@ public final class WebViewCore { } } - // called from JNI - void layersDraw() { - synchronized (this) { - if (mDrawLayersIsScheduled) return; - mDrawLayersIsScheduled = true; - mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW_LAYERS)); - } - } - // called by JNI private void contentScrollTo(int x, int y, boolean animate, boolean onlyIfImeIsShowing) { @@ -3055,8 +2993,8 @@ public final class WebViewCore { // TODO: Figure out what to do with this (b/6111818) } - private void useMockDeviceOrientation() { - mDeviceMotionAndOrientationManager.useMock(); + private void setUseMockDeviceOrientation() { + mDeviceMotionAndOrientationManager.setUseMock(); } public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, 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 diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 4e13ea1..c2559a5 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -77,7 +77,7 @@ public class HorizontalScrollView extends FrameLayout { /** * Position of the last motion event. */ - private float mLastMotionX; + private int mLastMotionX; /** * True when the layout has changed but the traversal has not come through yet. @@ -460,7 +460,7 @@ public class HorizontalScrollView extends FrameLayout { } final int pointerIndex = ev.findPointerIndex(activePointerId); - final float x = ev.getX(pointerIndex); + final int x = (int) ev.getX(pointerIndex); final int xDiff = (int) Math.abs(x - mLastMotionX); if (xDiff > mTouchSlop) { mIsBeingDragged = true; @@ -473,7 +473,7 @@ public class HorizontalScrollView extends FrameLayout { } case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); + final int x = (int) ev.getX(); if (!inChild((int) x, (int) ev.getY())) { mIsBeingDragged = false; recycleVelocityTracker(); @@ -505,18 +505,18 @@ public class HorizontalScrollView extends FrameLayout { mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) { - invalidate(); + postInvalidateOnAnimation(); } break; case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); - mLastMotionX = ev.getX(index); + mLastMotionX = (int) ev.getX(index); mActivePointerId = ev.getPointerId(index); break; } case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); - mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId)); + mLastMotionX = (int) ev.getX(ev.findPointerIndex(mActivePointerId)); break; } @@ -550,7 +550,7 @@ public class HorizontalScrollView extends FrameLayout { } // Remember where the motion event started - mLastMotionX = ev.getX(); + mLastMotionX = (int) ev.getX(); mActivePointerId = ev.getPointerId(0); break; } @@ -558,7 +558,7 @@ public class HorizontalScrollView extends FrameLayout { if (mIsBeingDragged) { // Scroll to follow the motion event final int activePointerIndex = ev.findPointerIndex(mActivePointerId); - final float x = ev.getX(activePointerIndex); + final int x = (int) ev.getX(activePointerIndex); final int deltaX = (int) (mLastMotionX - x); mLastMotionX = x; @@ -591,7 +591,7 @@ public class HorizontalScrollView extends FrameLayout { } if (mEdgeGlowLeft != null && (!mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished())) { - invalidate(); + postInvalidateOnAnimation(); } } } @@ -608,7 +608,7 @@ public class HorizontalScrollView extends FrameLayout { } else { if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) { - invalidate(); + postInvalidateOnAnimation(); } } } @@ -626,7 +626,7 @@ public class HorizontalScrollView extends FrameLayout { case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) { - invalidate(); + postInvalidateOnAnimation(); } mActivePointerId = INVALID_POINTER; mIsBeingDragged = false; @@ -654,7 +654,7 @@ public class HorizontalScrollView extends FrameLayout { // active pointer and adjust accordingly. // TODO: Make this decision more intelligent. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionX = ev.getX(newPointerIndex); + mLastMotionX = (int) ev.getX(newPointerIndex); mActivePointerId = ev.getPointerId(newPointerIndex); if (mVelocityTracker != null) { mVelocityTracker.clear(); @@ -1084,7 +1084,7 @@ public class HorizontalScrollView extends FrameLayout { dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX; mScroller.startScroll(scrollX, mScrollY, dx, 0); - invalidate(); + postInvalidateOnAnimation(); } else { if (!mScroller.isFinished()) { mScroller.abortAnimation(); @@ -1206,7 +1206,7 @@ public class HorizontalScrollView extends FrameLayout { } if (!awakenScrollBars()) { - invalidate(); + postInvalidateOnAnimation(); } } } @@ -1452,7 +1452,7 @@ public class HorizontalScrollView extends FrameLayout { newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT); } - invalidate(); + postInvalidateOnAnimation(); } } @@ -1503,7 +1503,7 @@ public class HorizontalScrollView extends FrameLayout { canvas.translate(-height + mPaddingTop, Math.min(0, scrollX)); mEdgeGlowLeft.setSize(height, getWidth()); if (mEdgeGlowLeft.draw(canvas)) { - invalidate(); + postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } @@ -1517,7 +1517,7 @@ public class HorizontalScrollView extends FrameLayout { -(Math.max(getScrollRange(), scrollX) + width)); mEdgeGlowRight.setSize(height, width); if (mEdgeGlowRight.draw(canvas)) { - invalidate(); + postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index e0e3e93..0f0dbae 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -73,7 +73,7 @@ public class ScrollView extends FrameLayout { /** * Position of the last motion event. */ - private float mLastMotionY; + private int mLastMotionY; /** * True when the layout has changed but the traversal has not come through yet. @@ -472,8 +472,8 @@ public class ScrollView extends FrameLayout { } final int pointerIndex = ev.findPointerIndex(activePointerId); - final float y = ev.getY(pointerIndex); - final int yDiff = (int) Math.abs(y - mLastMotionY); + final int y = (int) ev.getY(pointerIndex); + final int yDiff = Math.abs(y - mLastMotionY); if (yDiff > mTouchSlop) { mIsBeingDragged = true; mLastMotionY = y; @@ -487,7 +487,7 @@ public class ScrollView extends FrameLayout { } case MotionEvent.ACTION_DOWN: { - final float y = ev.getY(); + final int y = (int) ev.getY(); if (!inChild((int) ev.getX(), (int) y)) { mIsBeingDragged = false; recycleVelocityTracker(); @@ -522,7 +522,7 @@ public class ScrollView extends FrameLayout { mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { - invalidate(); + postInvalidateOnAnimation(); } break; case MotionEvent.ACTION_POINTER_UP: @@ -564,7 +564,7 @@ public class ScrollView extends FrameLayout { } // Remember where the motion event started - mLastMotionY = ev.getY(); + mLastMotionY = (int) ev.getY(); mActivePointerId = ev.getPointerId(0); break; } @@ -572,8 +572,8 @@ public class ScrollView extends FrameLayout { if (mIsBeingDragged) { // Scroll to follow the motion event final int activePointerIndex = ev.findPointerIndex(mActivePointerId); - final float y = ev.getY(activePointerIndex); - final int deltaY = (int) (mLastMotionY - y); + final int y = (int) ev.getY(activePointerIndex); + final int deltaY = mLastMotionY - y; mLastMotionY = y; final int oldX = mScrollX; @@ -605,7 +605,7 @@ public class ScrollView extends FrameLayout { } if (mEdgeGlowTop != null && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) { - invalidate(); + postInvalidateOnAnimation(); } } } @@ -622,7 +622,7 @@ public class ScrollView extends FrameLayout { } else { if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { - invalidate(); + postInvalidateOnAnimation(); } } } @@ -634,7 +634,7 @@ public class ScrollView extends FrameLayout { case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { - invalidate(); + postInvalidateOnAnimation(); } mActivePointerId = INVALID_POINTER; endDrag(); @@ -642,13 +642,13 @@ public class ScrollView extends FrameLayout { break; case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); - mLastMotionY = ev.getY(index); + mLastMotionY = (int) ev.getY(index); mActivePointerId = ev.getPointerId(index); break; } case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); - mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId)); + mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); break; } return true; @@ -663,7 +663,7 @@ public class ScrollView extends FrameLayout { // active pointer and adjust accordingly. // TODO: Make this decision more intelligent. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionY = ev.getY(newPointerIndex); + mLastMotionY = (int) ev.getY(newPointerIndex); mActivePointerId = ev.getPointerId(newPointerIndex); if (mVelocityTracker != null) { mVelocityTracker.clear(); @@ -1047,7 +1047,7 @@ public class ScrollView extends FrameLayout { dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY; mScroller.startScroll(mScrollX, scrollY, 0, dy); - invalidate(); + postInvalidateOnAnimation(); } else { if (!mScroller.isFinished()) { mScroller.abortAnimation(); @@ -1174,7 +1174,7 @@ public class ScrollView extends FrameLayout { if (!awakenScrollBars()) { // Keep on drawing until the animation has finished. - invalidate(); + postInvalidateOnAnimation(); } } else { if (mFlingStrictSpan != null) { @@ -1430,7 +1430,7 @@ public class ScrollView extends FrameLayout { mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling"); } - invalidate(); + postInvalidateOnAnimation(); } } @@ -1495,7 +1495,7 @@ public class ScrollView extends FrameLayout { canvas.translate(mPaddingLeft, Math.min(0, scrollY)); mEdgeGlowTop.setSize(width, getHeight()); if (mEdgeGlowTop.draw(canvas)) { - invalidate(); + postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } @@ -1509,7 +1509,7 @@ public class ScrollView extends FrameLayout { canvas.rotate(180, width, 0); mEdgeGlowBottom.setSize(width, height); if (mEdgeGlowBottom.draw(canvas)) { - invalidate(); + postInvalidateOnAnimation(); } canvas.restoreToCount(restoreCount); } |
