Merge "HID usage should take precedence over scan code."
diff --git a/api/16.txt b/api/16.txt
index 8ff7675..aa23b4d 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -25204,7 +25204,6 @@
method public java.lang.String getTitle();
method public java.lang.String getUrl();
method public deprecated int getVisibleTitleHeight();
- method public deprecated android.view.View getZoomControls();
method public void goBack();
method public void goBackOrForward(int);
method public void goForward();
@@ -41832,7 +41831,7 @@
method public static void fail();
}
- public class AssertionFailedError extends java.lang.Error {
+ public class AssertionFailedError extends java.lang.AssertionError {
ctor public AssertionFailedError();
ctor public AssertionFailedError(java.lang.String);
}
@@ -41890,9 +41889,9 @@
method public synchronized void addListener(junit.framework.TestListener);
method public void endTest(junit.framework.Test);
method public synchronized int errorCount();
- method public synchronized java.util.Enumeration errors();
+ method public synchronized java.util.Enumeration<junit.framework.TestFailure> errors();
method public synchronized int failureCount();
- method public synchronized java.util.Enumeration failures();
+ method public synchronized java.util.Enumeration<junit.framework.TestFailure> failures();
method public synchronized void removeListener(junit.framework.TestListener);
method protected void run(junit.framework.TestCase);
method public synchronized int runCount();
@@ -41909,21 +41908,21 @@
public class TestSuite implements junit.framework.Test {
ctor public TestSuite();
- ctor public TestSuite(java.lang.Class, java.lang.String);
- ctor public TestSuite(java.lang.Class);
+ ctor public TestSuite(java.lang.Class<?>);
+ ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>, java.lang.String);
ctor public TestSuite(java.lang.String);
method public void addTest(junit.framework.Test);
- method public void addTestSuite(java.lang.Class);
+ method public void addTestSuite(java.lang.Class<? extends junit.framework.TestCase>);
method public int countTestCases();
- method public static junit.framework.Test createTest(java.lang.Class, java.lang.String);
+ method public static junit.framework.Test createTest(java.lang.Class<?>, java.lang.String);
method public java.lang.String getName();
- method public static java.lang.reflect.Constructor getTestConstructor(java.lang.Class) throws java.lang.NoSuchMethodException;
+ method public static java.lang.reflect.Constructor<?> getTestConstructor(java.lang.Class) throws java.lang.NoSuchMethodException;
method public void run(junit.framework.TestResult);
method public void runTest(junit.framework.Test, junit.framework.TestResult);
method public void setName(java.lang.String);
method public junit.framework.Test testAt(int);
method public int testCount();
- method public java.util.Enumeration tests();
+ method public java.util.Enumeration<junit.framework.Test> tests();
}
}
@@ -41946,7 +41945,7 @@
method protected static java.util.Properties getPreferences();
method public junit.framework.Test getTest(java.lang.String);
method public static boolean inVAJava();
- method protected java.lang.Class loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
+ method protected java.lang.Class<?> loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
method protected java.lang.String processArguments(java.lang.String[]);
method protected abstract void runFailed(java.lang.String);
method public static void savePreferences() throws java.io.IOException;
diff --git a/api/current.txt b/api/current.txt
index ee964f7..bb313cd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19697,12 +19697,12 @@
method public final void testApplicationTestCaseSetUpProperly() throws java.lang.Exception;
}
- public class AssertionFailedError extends java.lang.Error {
+ public deprecated class AssertionFailedError extends java.lang.Error {
ctor public AssertionFailedError();
ctor public AssertionFailedError(java.lang.String);
}
- public class ComparisonFailure extends android.test.AssertionFailedError {
+ public deprecated class ComparisonFailure extends android.test.AssertionFailedError {
ctor public ComparisonFailure(java.lang.String, java.lang.String, java.lang.String);
}
@@ -19744,6 +19744,7 @@
ctor public InstrumentationTestSuite(android.app.Instrumentation);
ctor public InstrumentationTestSuite(java.lang.String, android.app.Instrumentation);
ctor public InstrumentationTestSuite(java.lang.Class, android.app.Instrumentation);
+ method public void addTestSuite(java.lang.Class);
}
public class IsolatedContext extends android.content.ContextWrapper {
@@ -25795,7 +25796,6 @@
method public java.lang.String getTitle();
method public java.lang.String getUrl();
method public deprecated int getVisibleTitleHeight();
- method public deprecated android.view.View getZoomControls();
method public void goBack();
method public void goBackOrForward(int);
method public void goForward();
@@ -42498,15 +42498,21 @@
method public static void assertTrue(boolean);
method public static void fail(java.lang.String);
method public static void fail();
+ method public static void failNotEquals(java.lang.String, java.lang.Object, java.lang.Object);
+ method public static void failNotSame(java.lang.String, java.lang.Object, java.lang.Object);
+ method public static void failSame(java.lang.String);
+ method public static java.lang.String format(java.lang.String, java.lang.Object, java.lang.Object);
}
- public class AssertionFailedError extends java.lang.Error {
+ public class AssertionFailedError extends java.lang.AssertionError {
ctor public AssertionFailedError();
ctor public AssertionFailedError(java.lang.String);
}
public class ComparisonFailure extends junit.framework.AssertionFailedError {
ctor public ComparisonFailure(java.lang.String, java.lang.String, java.lang.String);
+ method public java.lang.String getActual();
+ method public java.lang.String getExpected();
}
public abstract interface Protectable {
@@ -42558,9 +42564,9 @@
method public synchronized void addListener(junit.framework.TestListener);
method public void endTest(junit.framework.Test);
method public synchronized int errorCount();
- method public synchronized java.util.Enumeration errors();
+ method public synchronized java.util.Enumeration<junit.framework.TestFailure> errors();
method public synchronized int failureCount();
- method public synchronized java.util.Enumeration failures();
+ method public synchronized java.util.Enumeration<junit.framework.TestFailure> failures();
method public synchronized void removeListener(junit.framework.TestListener);
method protected void run(junit.framework.TestCase);
method public synchronized int runCount();
@@ -42577,21 +42583,24 @@
public class TestSuite implements junit.framework.Test {
ctor public TestSuite();
- ctor public TestSuite(java.lang.Class, java.lang.String);
- ctor public TestSuite(java.lang.Class);
+ ctor public TestSuite(java.lang.Class<?>);
+ ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>, java.lang.String);
ctor public TestSuite(java.lang.String);
+ ctor public TestSuite(java.lang.Class<?>...);
+ ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>[], java.lang.String);
method public void addTest(junit.framework.Test);
- method public void addTestSuite(java.lang.Class);
+ method public void addTestSuite(java.lang.Class<? extends junit.framework.TestCase>);
method public int countTestCases();
- method public static junit.framework.Test createTest(java.lang.Class, java.lang.String);
+ method public static junit.framework.Test createTest(java.lang.Class<?>, java.lang.String);
method public java.lang.String getName();
- method public static java.lang.reflect.Constructor getTestConstructor(java.lang.Class) throws java.lang.NoSuchMethodException;
+ method public static java.lang.reflect.Constructor<?> getTestConstructor(java.lang.Class<?>) throws java.lang.NoSuchMethodException;
method public void run(junit.framework.TestResult);
method public void runTest(junit.framework.Test, junit.framework.TestResult);
method public void setName(java.lang.String);
method public junit.framework.Test testAt(int);
method public int testCount();
- method public java.util.Enumeration tests();
+ method public java.util.Enumeration<junit.framework.Test> tests();
+ method public static junit.framework.Test warning(java.lang.String);
}
}
@@ -42608,13 +42617,13 @@
method public java.lang.String extractClassName(java.lang.String);
method public static java.lang.String getFilteredTrace(java.lang.Throwable);
method public static java.lang.String getFilteredTrace(java.lang.String);
- method public junit.runner.TestSuiteLoader getLoader();
+ method public deprecated junit.runner.TestSuiteLoader getLoader();
method public static java.lang.String getPreference(java.lang.String);
method public static int getPreference(java.lang.String, int);
method protected static java.util.Properties getPreferences();
method public junit.framework.Test getTest(java.lang.String);
- method public static boolean inVAJava();
- method protected java.lang.Class loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
+ method public static deprecated boolean inVAJava();
+ method protected java.lang.Class<?> loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
method protected java.lang.String processArguments(java.lang.String[]);
method protected abstract void runFailed(java.lang.String);
method public static void savePreferences() throws java.io.IOException;
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 @@
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 @@
/**
* 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 @@
* <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 @@
/**
* 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 @@
/**
* 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 @@
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 @@
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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.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.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 @@
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 @@
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 int getSyncRandomOffset() {
+ return mSyncRandomOffset;
+ }
+
public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
synchronized (mAuthorities) {
mChangeListeners.register(callback, mask);
@@ -1465,6 +1474,16 @@
} 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 @@
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 @@
/** {@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 @@
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 @@
}
@Override
- void destroyHardwareResources(View view) {
- if (view != null) {
- boolean needsContext = true;
- if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false;
+ 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;
- usePbufferSurface(managedContext.getContext());
- }
+ if (needsContext) {
+ Gl20RendererEglContext managedContext =
+ (Gl20RendererEglContext) sEglContextStorage.get();
+ if (managedContext == null) return false;
+ usePbufferSurface(managedContext.getContext());
+ }
- destroyResources(view);
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
-
+ 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 @@
}
/**
+ * 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 @@
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 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- destroySurface();
+ if (mLayer != null && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) {
+ boolean success = mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
+ @Override
+ public void run() {
+ destroySurface();
+ }
+ });
+
+ if (!success) {
+ Log.w(LOG_TAG, "TextureView was not able to destroy its surface: " + this);
+ }
+ }
}
private void destroySurface() {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 84632c6..d1cfc6b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1539,6 +1539,7 @@
*
* @deprecated The built-in zoom mechanism is preferred, see
* {@link WebSettings#setBuiltInZoomControls(boolean)}.
+ * @hide since API version 16.
*/
@Deprecated
public View getZoomControls() {
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index d7ebf5c..5dc2681 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -57,6 +57,7 @@
import android.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.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 @@
/**
* 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 @@
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 @@
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 @@
}
+ 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 @@
boolean animateScroll = ((!mScroller.isFinished()
|| mVelocityTracker != null)
&& (mTouchMode != TOUCH_DRAG_MODE ||
- mHeldMotionless != MOTIONLESS_TRUE))
- || mDeferTouchMode == TOUCH_DRAG_MODE;
+ mHeldMotionless != MOTIONLESS_TRUE));
if (mTouchMode == TOUCH_DRAG_MODE) {
if (mHeldMotionless == MOTIONLESS_PENDING) {
mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
@@ -5573,7 +5611,6 @@
addAccessibilityApisToJavaScript();
- mTouchEventQueue.reset();
updateHwAccelerated();
}
@@ -5822,20 +5859,6 @@
*/
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 @@
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 @@
}
/*
- * 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 @@
switch (action) {
case MotionEvent.ACTION_DOWN: {
- mPreventDefault = PREVENT_DEFAULT_NO;
mConfirmMove = false;
mInitialHitTestResult = null;
if (!mEditTextScroller.isFinished()) {
@@ -5972,20 +5990,11 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
// 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 @@
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 @@
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 @@
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 @@
mPrivateHandler.sendEmptyMessageDelayed(
RELEASE_SINGLE_TAP, ViewConfiguration
.getDoubleTapTimeout());
- } else {
- doShortPress();
}
break;
}
@@ -6446,13 +6288,9 @@
// 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 @@
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 @@
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 @@
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 @@
/**
* 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 @@
((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 @@
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 @@
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 @@
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 @@
break;
}
}
+
+ @Override
+ public Looper getUiLooper() {
+ return getLooper();
+ }
+
+ @Override
+ public void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
+ onHandleUiEvent(event, eventType, flags);
+ }
+
+ @Override
+ public Context getContext() {
+ return WebViewClassic.this.getContext();
+ }
}
private void setHitTestTypeFromUrl(String url) {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 2caec01..dd05e8c 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -40,6 +40,7 @@
import android.view.SurfaceView;
import android.view.View;
import android.webkit.WebViewClassic.FocusNodeHref;
+import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
import junit.framework.Assert;
@@ -259,6 +260,10 @@
return mBrowserFrame;
}
+ public WebKitCallbacks getInputDispatcherCallbacks() {
+ return mEventHub;
+ }
+
//-------------------------------------------------------------------------
// Common methods
//-------------------------------------------------------------------------
@@ -611,9 +616,6 @@
int unichar, int repeatCount, boolean isShift, boolean isAlt,
boolean isSym, boolean isDown);
- private native void nativeClick(int nativeClass, int framePtr, int nodePtr,
- boolean fake);
-
private native void nativeSendListBoxChoices(int nativeClass,
boolean[] choices, int size);
@@ -656,8 +658,7 @@
int x, int y);
private native String nativeRetrieveImageSource(int nativeClass,
int x, int y);
- private native void nativeTouchUp(int nativeClass,
- int touchGeneration, int framePtr, int nodePtr, int x, int y);
+ private native boolean nativeMouseClick(int nativeClass);
private native boolean nativeHandleTouchEvent(int nativeClass, int action,
int[] idArray, int[] xArray, int[] yArray, int count,
@@ -1020,8 +1021,8 @@
"REQUEST_CURSOR_HREF", // = 137;
"ADD_JS_INTERFACE", // = 138;
"LOAD_DATA", // = 139;
- "TOUCH_UP", // = 140;
- "TOUCH_EVENT", // = 141;
+ "", // = 140;
+ "", // = 141;
"SET_ACTIVE", // = 142;
"ON_PAUSE", // = 143
"ON_RESUME", // = 144
@@ -1046,7 +1047,7 @@
/**
* @hide
*/
- public class EventHub {
+ public class EventHub implements WebViewInputDispatcher.WebKitCallbacks {
// Message Ids
static final int REVEAL_SELECTION = 96;
static final int SCROLL_TEXT_INPUT = 99;
@@ -1067,10 +1068,8 @@
static final int REPLACE_TEXT = 114;
static final int PASS_TO_JS = 115;
static final int SET_GLOBAL_BOUNDS = 116;
- static final int CLICK = 118;
static final int SET_NETWORK_STATE = 119;
static final int DOC_HAS_IMAGES = 120;
- static final int FAKE_CLICK = 121;
static final int DELETE_SELECTION = 122;
static final int LISTBOX_CHOICES = 123;
static final int SINGLE_LISTBOX_CHOICE = 124;
@@ -1091,11 +1090,6 @@
static final int ADD_JS_INTERFACE = 138;
static final int LOAD_DATA = 139;
- // motion
- static final int TOUCH_UP = 140;
- // message used to pass UI touch events to WebCore
- static final int TOUCH_EVENT = 141;
-
// Used to tell the focus controller not to draw the blinking cursor,
// based on whether the WebView has focus and whether the WebView's
// cursor matches the webpage's focus.
@@ -1370,14 +1364,6 @@
keyPress(msg.arg1);
break;
- case FAKE_CLICK:
- nativeClick(mNativeClass, msg.arg1, msg.arg2, true);
- break;
-
- case CLICK:
- nativeClick(mNativeClass, msg.arg1, msg.arg2, false);
- break;
-
case VIEW_SIZE_CHANGED: {
viewSizeChanged((WebViewClassic.ViewSizeData) msg.obj);
break;
@@ -1490,45 +1476,6 @@
nativeCloseIdleConnections(mNativeClass);
break;
- case TOUCH_UP:
- TouchUpData touchUpData = (TouchUpData) msg.obj;
- if (touchUpData.mNativeLayer != 0) {
- nativeScrollLayer(mNativeClass,
- touchUpData.mNativeLayer,
- touchUpData.mNativeLayerRect);
- }
- nativeTouchUp(mNativeClass,
- touchUpData.mMoveGeneration,
- touchUpData.mFrame, touchUpData.mNode,
- touchUpData.mX, touchUpData.mY);
- break;
-
- case TOUCH_EVENT: {
- TouchEventData ted = (TouchEventData) msg.obj;
- final int count = ted.mPoints.length;
- int[] xArray = new int[count];
- int[] yArray = new int[count];
- for (int c = 0; c < count; c++) {
- xArray[c] = ted.mPoints[c].x;
- yArray[c] = ted.mPoints[c].y;
- }
- if (ted.mNativeLayer != 0) {
- nativeScrollLayer(mNativeClass,
- ted.mNativeLayer, ted.mNativeLayerRect);
- }
- ted.mNativeResult = nativeHandleTouchEvent(
- mNativeClass, ted.mAction, ted.mIds, xArray,
- yArray, count, ted.mActionIndex,
- ted.mMetaState);
- Message.obtain(
- mWebViewClassic.mPrivateHandler,
- WebViewClassic.PREVENT_TOUCH_ID,
- ted.mAction,
- ted.mNativeResult ? 1 : 0,
- ted).sendToTarget();
- break;
- }
-
case SET_ACTIVE:
nativeSetFocusControllerActive(mNativeClass, msg.arg1 == 1);
break;
@@ -1812,6 +1759,38 @@
}
}
+ @Override
+ public Looper getWebKitLooper() {
+ return mHandler.getLooper();
+ }
+
+ @Override
+ public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
+ switch (eventType) {
+ case WebViewInputDispatcher.EVENT_TYPE_CLICK:
+ return nativeMouseClick(mNativeClass);
+
+ case WebViewInputDispatcher.EVENT_TYPE_TOUCH: {
+ int count = event.getPointerCount();
+ int[] idArray = new int[count];
+ int[] xArray = new int[count];
+ int[] yArray = new int[count];
+ for (int i = 0; i < count; i++) {
+ idArray[i] = event.getPointerId(i);
+ xArray[i] = (int) event.getX(i);
+ yArray[i] = (int) event.getY(i);
+ }
+ return nativeHandleTouchEvent(mNativeClass,
+ event.getActionMasked(),
+ idArray, xArray, yArray, count,
+ event.getActionIndex(), event.getMetaState());
+ }
+
+ default:
+ return false;
+ }
+ }
+
/**
* Send a message internally to the queue or to the handler
*/
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
new file mode 100644
index 0000000..e7024d9
--- /dev/null
+++ b/core/java/android/webkit/WebViewInputDispatcher.java
@@ -0,0 +1,1151 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * Perform asynchronous dispatch of input events in a {@link WebView}.
+ *
+ * This dispatcher is shared by the UI thread ({@link WebViewClassic}) and web kit
+ * thread ({@link WebViewCore}). The UI thread enqueues events for
+ * processing, waits for the web kit thread to handle them, and then performs
+ * additional processing depending on the outcome.
+ *
+ * How it works:
+ *
+ * 1. The web view thread receives an input event from the input system on the UI
+ * thread in its {@link WebViewClassic#onTouchEvent} handler. It sends the input event
+ * to the dispatcher, then immediately returns true to the input system to indicate that
+ * it will handle the event.
+ *
+ * 2. The web kit thread is notified that an event has been enqueued. Meanwhile additional
+ * events may be enqueued from the UI thread. In some cases, the dispatcher may decide to
+ * coalesce motion events into larger batches or to cancel events that have been
+ * sitting in the queue for too long.
+ *
+ * 3. The web kit thread wakes up and handles all input events that are waiting for it.
+ * After processing each input event, it informs the dispatcher whether the web application
+ * has decided to handle the event itself and to prevent default event handling.
+ *
+ * 4. If web kit indicates that it wants to prevent default event handling, then web kit
+ * consumes the remainder of the gesture and web view receives a cancel event if
+ * needed. Otherwise, the web view handles the gesture on the UI thread normally.
+ *
+ * 5. If the web kit thread takes too long to handle an input event, then it loses the
+ * right to handle it. The dispatcher synthesizes a cancellation event for web kit and
+ * then tells the web view on the UI thread to handle the event that timed out along
+ * with the rest of the gesture.
+ *
+ * One thing to keep in mind about the dispatcher is that what goes into the dispatcher
+ * is not necessarily what the web kit or UI thread will see. As mentioned above, the
+ * dispatcher may tweak the input event stream to improve responsiveness. Both web view and
+ * web kit are guaranteed to perceive a consistent stream of input events but
+ * they might not always see the same events (especially if one decides
+ * to prevent the other from handling a particular gesture).
+ *
+ * This implementation very deliberately does not refer to the {@link WebViewClassic}
+ * or {@link WebViewCore} classes, preferring to communicate with them only via
+ * interfaces to avoid unintentional coupling to their implementation details.
+ *
+ * Currently, the input dispatcher only handles pointer events (includes touch,
+ * hover and scroll events). In principle, it could be extended to handle trackball
+ * and key events if needed.
+ *
+ * @hide
+ */
+final class WebViewInputDispatcher {
+ private static final String TAG = "WebViewInputDispatcher";
+ private static final boolean DEBUG = false;
+ // This enables batching of MotionEvents. It will combine multiple MotionEvents
+ // together into a single MotionEvent if more events come in while we are
+ // still waiting on the processing of a previous event.
+ // If this is set to false, we will instead opt to drop ACTION_MOVE
+ // events we cannot keep up with.
+ // TODO: If batching proves to be working well, remove this
+ private static final boolean ENABLE_EVENT_BATCHING = true;
+
+ private final Object mLock = new Object();
+
+ // Pool of queued input events. (guarded by mLock)
+ private static final int MAX_DISPATCH_EVENT_POOL_SIZE = 10;
+ private DispatchEvent mDispatchEventPool;
+ private int mDispatchEventPoolSize;
+
+ // Posted state, tracks events posted to the dispatcher. (guarded by mLock)
+ private final TouchStream mPostTouchStream = new TouchStream();
+ private boolean mPostSendTouchEventsToWebKit;
+ private boolean mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
+ private boolean mPostLongPressScheduled;
+ private boolean mPostClickScheduled;
+ private int mPostLastWebKitXOffset;
+ private int mPostLastWebKitYOffset;
+ private float mPostLastWebKitScale;
+
+ // State for event tracking (click, longpress, double tap, etc..)
+ private boolean mIsDoubleTapCandidate;
+ private boolean mIsTapCandidate;
+ private float mInitialDownX;
+ private float mInitialDownY;
+ private float mTouchSlopSquared;
+ private float mDoubleTapSlopSquared;
+
+ // Web kit state, tracks events observed by web kit. (guarded by mLock)
+ private final DispatchEventQueue mWebKitDispatchEventQueue = new DispatchEventQueue();
+ private final TouchStream mWebKitTouchStream = new TouchStream();
+ private final WebKitCallbacks mWebKitCallbacks;
+ private final WebKitHandler mWebKitHandler;
+ private boolean mWebKitDispatchScheduled;
+ private boolean mWebKitTimeoutScheduled;
+ private long mWebKitTimeoutTime;
+
+ // UI state, tracks events observed by the UI. (guarded by mLock)
+ private final DispatchEventQueue mUiDispatchEventQueue = new DispatchEventQueue();
+ private final TouchStream mUiTouchStream = new TouchStream();
+ private final UiCallbacks mUiCallbacks;
+ private final UiHandler mUiHandler;
+ private boolean mUiDispatchScheduled;
+
+ // Give up on web kit handling of input events when this timeout expires.
+ private static final long WEBKIT_TIMEOUT_MILLIS = 200;
+ private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
+ private static final int LONG_PRESS_TIMEOUT =
+ ViewConfiguration.getLongPressTimeout() + TAP_TIMEOUT;
+ private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
+
+ /**
+ * Event type: Indicates a touch event type.
+ *
+ * This event is delivered together with a {@link MotionEvent} with one of the
+ * following actions: {@link MotionEvent#ACTION_DOWN}, {@link MotionEvent#ACTION_MOVE},
+ * {@link MotionEvent#ACTION_UP}, {@link MotionEvent#ACTION_POINTER_DOWN},
+ * {@link MotionEvent#ACTION_POINTER_UP}, {@link MotionEvent#ACTION_CANCEL}.
+ */
+ public static final int EVENT_TYPE_TOUCH = 0;
+
+ /**
+ * Event type: Indicates a hover event type.
+ *
+ * This event is delivered together with a {@link MotionEvent} with one of the
+ * following actions: {@link MotionEvent#ACTION_HOVER_ENTER},
+ * {@link MotionEvent#ACTION_HOVER_MOVE}, {@link MotionEvent#ACTION_HOVER_MOVE}.
+ */
+ public static final int EVENT_TYPE_HOVER = 1;
+
+ /**
+ * Event type: Indicates a scroll event type.
+ *
+ * This event is delivered together with a {@link MotionEvent} with action
+ * {@link MotionEvent#ACTION_SCROLL}.
+ */
+ public static final int EVENT_TYPE_SCROLL = 2;
+
+ /**
+ * Event type: Indicates a long-press event type.
+ *
+ * This event is delivered in the middle of a sequence of {@link #EVENT_TYPE_TOUCH} events.
+ * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_MOVE}
+ * that indicates the current touch coordinates of the long-press.
+ *
+ * This event is sent when the current touch gesture has been held longer than
+ * the long-press interval.
+ */
+ public static final int EVENT_TYPE_LONG_PRESS = 3;
+
+ /**
+ * Event type: Indicates a click event type.
+ *
+ * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
+ * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
+ * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
+ * that indicates the location of the click.
+ *
+ * This event is sent shortly after the end of a touch after the double-tap
+ * interval has expired to indicate a click.
+ */
+ public static final int EVENT_TYPE_CLICK = 4;
+
+ /**
+ * Event type: Indicates a double-tap event type.
+ *
+ * This event is delivered after a sequence of {@link #EVENT_TYPE_TOUCH} events that
+ * comprise a complete gesture ending with {@link MotionEvent#ACTION_UP}.
+ * It includes a {@link MotionEvent} with action {@link MotionEvent#ACTION_UP}
+ * that indicates the location of the double-tap.
+ *
+ * This event is sent immediately after a sequence of two touches separated
+ * in time by no more than the double-tap interval and separated in space
+ * by no more than the double-tap slop.
+ */
+ public static final int EVENT_TYPE_DOUBLE_TAP = 5;
+
+ /**
+ * Flag: This event is private to this queue. Do not forward it.
+ */
+ public static final int FLAG_PRIVATE = 1 << 0;
+
+ /**
+ * Flag: This event is currently being processed by web kit.
+ * If a timeout occurs, make a copy of it before forwarding the event to another queue.
+ */
+ public static final int FLAG_WEBKIT_IN_PROGRESS = 1 << 1;
+
+ /**
+ * Flag: A timeout occurred while waiting for web kit to process this input event.
+ */
+ public static final int FLAG_WEBKIT_TIMEOUT = 1 << 2;
+
+ /**
+ * Flag: Indicates that the event was transformed for delivery to web kit.
+ * The event must be transformed back before being delivered to the UI.
+ */
+ public static final int FLAG_WEBKIT_TRANSFORMED_EVENT = 1 << 3;
+
+ public WebViewInputDispatcher(UiCallbacks uiCallbacks, WebKitCallbacks webKitCallbacks) {
+ this.mUiCallbacks = uiCallbacks;
+ mUiHandler = new UiHandler(uiCallbacks.getUiLooper());
+
+ this.mWebKitCallbacks = webKitCallbacks;
+ mWebKitHandler = new WebKitHandler(webKitCallbacks.getWebKitLooper());
+
+ ViewConfiguration config = ViewConfiguration.get(mUiCallbacks.getContext());
+ mDoubleTapSlopSquared = config.getScaledDoubleTapSlop();
+ mDoubleTapSlopSquared = (mDoubleTapSlopSquared * mDoubleTapSlopSquared);
+ mTouchSlopSquared = config.getScaledTouchSlop();
+ mTouchSlopSquared = (mTouchSlopSquared * mTouchSlopSquared);
+ }
+
+ /**
+ * Sets whether web kit wants to receive touch events.
+ *
+ * @param enable True to enable dispatching of touch events to web kit, otherwise
+ * web kit will be skipped.
+ */
+ public void setWebKitWantsTouchEvents(boolean enable) {
+ if (DEBUG) {
+ Log.d(TAG, "webkitWantsTouchEvents: " + enable);
+ }
+ synchronized (mLock) {
+ if (mPostSendTouchEventsToWebKit != enable) {
+ if (!enable) {
+ enqueueWebKitCancelTouchEventIfNeededLocked();
+ }
+ mPostSendTouchEventsToWebKit = enable;
+ }
+ }
+ }
+
+ /**
+ * Posts a pointer event to the dispatch queue.
+ *
+ * @param event The event to post.
+ * @param webKitXOffset X offset to apply to events before dispatching them to web kit.
+ * @param webKitYOffset Y offset to apply to events before dispatching them to web kit.
+ * @param webKitScale The scale factor to apply to translated events before dispatching
+ * them to web kit.
+ * @return True if the dispatcher will handle the event, false if the event is unsupported.
+ */
+ public boolean postPointerEvent(MotionEvent event,
+ int webKitXOffset, int webKitYOffset, float webKitScale) {
+ if (event == null
+ || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
+ throw new IllegalArgumentException("event must be a pointer event");
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "postPointerEvent: " + event);
+ }
+
+ final int action = event.getActionMasked();
+ final int eventType;
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ case MotionEvent.ACTION_CANCEL:
+ eventType = EVENT_TYPE_TOUCH;
+ break;
+ case MotionEvent.ACTION_SCROLL:
+ eventType = EVENT_TYPE_SCROLL;
+ break;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_HOVER_EXIT:
+ eventType = EVENT_TYPE_HOVER;
+ break;
+ default:
+ return false; // currently unsupported event type
+ }
+
+ synchronized (mLock) {
+ // Ensure that the event is consistent and should be delivered.
+ MotionEvent eventToEnqueue = event;
+ if (eventType == EVENT_TYPE_TOUCH) {
+ eventToEnqueue = mPostTouchStream.update(event);
+ if (eventToEnqueue == null) {
+ if (DEBUG) {
+ Log.d(TAG, "postPointerEvent: dropped event " + event);
+ }
+ unscheduleLongPressLocked();
+ unscheduleClickLocked();
+ return false;
+ }
+
+ if (mPostSendTouchEventsToWebKit
+ && mPostDoNotSendTouchEventsToWebKitUntilNextGesture
+ && action == MotionEvent.ACTION_DOWN) {
+ // Recover from a previous web kit timeout.
+ mPostDoNotSendTouchEventsToWebKitUntilNextGesture = false;
+ }
+ }
+
+ // Copy the event because we need to retain ownership.
+ if (eventToEnqueue == event) {
+ eventToEnqueue = event.copy();
+ }
+
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, eventType, 0,
+ webKitXOffset, webKitYOffset, webKitScale);
+ enqueueEventLocked(d);
+ }
+ return true;
+ }
+
+ private void scheduleLongPressLocked() {
+ unscheduleLongPressLocked();
+ mPostLongPressScheduled = true;
+ mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_LONG_PRESS,
+ LONG_PRESS_TIMEOUT);
+ }
+
+ private void unscheduleLongPressLocked() {
+ if (mPostLongPressScheduled) {
+ mPostLongPressScheduled = false;
+ mUiHandler.removeMessages(UiHandler.MSG_LONG_PRESS);
+ }
+ }
+
+ private void postLongPress() {
+ synchronized (mLock) {
+ if (!mPostLongPressScheduled) {
+ return;
+ }
+ mPostLongPressScheduled = false;
+
+ MotionEvent event = mPostTouchStream.getLastEvent();
+ if (event == null) {
+ return;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ break;
+ default:
+ return;
+ }
+
+ MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+ eventToEnqueue.setAction(MotionEvent.ACTION_MOVE);
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_LONG_PRESS, 0,
+ mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+ enqueueEventLocked(d);
+ }
+ }
+
+ private void scheduleClickLocked() {
+ unscheduleClickLocked();
+ mPostClickScheduled = true;
+ mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_CLICK, DOUBLE_TAP_TIMEOUT);
+ }
+
+ private void unscheduleClickLocked() {
+ if (mPostClickScheduled) {
+ mPostClickScheduled = false;
+ mUiHandler.removeMessages(UiHandler.MSG_CLICK);
+ }
+ }
+
+ private void postClick() {
+ synchronized (mLock) {
+ if (!mPostClickScheduled) {
+ return;
+ }
+ mPostClickScheduled = false;
+
+ MotionEvent event = mPostTouchStream.getLastEvent();
+ if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
+ return;
+ }
+
+ MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_CLICK, 0,
+ mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+ enqueueEventLocked(d);
+ }
+ }
+
+ private void checkForDoubleTapOnDownLocked(MotionEvent event) {
+ mIsDoubleTapCandidate = false;
+ if (!mPostClickScheduled) {
+ return;
+ }
+ int deltaX = (int) mInitialDownX - (int) event.getX();
+ int deltaY = (int) mInitialDownY - (int) event.getY();
+ if ((deltaX * deltaX + deltaY * deltaY) < mDoubleTapSlopSquared) {
+ unscheduleClickLocked();
+ mIsDoubleTapCandidate = true;
+ }
+ }
+
+ private boolean isClickCandidateLocked(MotionEvent event) {
+ if (event == null
+ || event.getActionMasked() != MotionEvent.ACTION_UP
+ || !mIsTapCandidate) {
+ return false;
+ }
+ long downDuration = event.getEventTime() - event.getDownTime();
+ return downDuration < TAP_TIMEOUT;
+ }
+
+ private void enqueueDoubleTapLocked(MotionEvent event) {
+ unscheduleClickLocked();
+ MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_DOUBLE_TAP, 0,
+ mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+ enqueueEventLocked(d);
+ mIsDoubleTapCandidate = false;
+ }
+
+ private void checkForSlopLocked(MotionEvent event) {
+ if (!mIsTapCandidate) {
+ return;
+ }
+ int deltaX = (int) mInitialDownX - (int) event.getX();
+ int deltaY = (int) mInitialDownY - (int) event.getY();
+ if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquared) {
+ unscheduleLongPressLocked();
+ mIsTapCandidate = false;
+ }
+ }
+
+ private void updateStateTrackersLocked(DispatchEvent d, MotionEvent event) {
+ mPostLastWebKitXOffset = d.mWebKitXOffset;
+ mPostLastWebKitYOffset = d.mWebKitYOffset;
+ mPostLastWebKitScale = d.mWebKitScale;
+ int action = event != null ? event.getAction() : MotionEvent.ACTION_CANCEL;
+ if (d.mEventType != EVENT_TYPE_TOUCH) {
+ return;
+ }
+
+ if (action == MotionEvent.ACTION_CANCEL
+ || event.getPointerCount() > 1) {
+ unscheduleLongPressLocked();
+ unscheduleClickLocked();
+ mIsDoubleTapCandidate = false;
+ mIsTapCandidate = false;
+ } else if (action == MotionEvent.ACTION_DOWN) {
+ checkForDoubleTapOnDownLocked(event);
+ scheduleLongPressLocked();
+ mIsTapCandidate = true;
+ mInitialDownX = event.getX();
+ mInitialDownY = event.getY();
+ } else if (action == MotionEvent.ACTION_UP) {
+ unscheduleLongPressLocked();
+ if (isClickCandidateLocked(event)) {
+ if (mIsDoubleTapCandidate) {
+ enqueueDoubleTapLocked(event);
+ } else {
+ scheduleClickLocked();
+ }
+ }
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ checkForSlopLocked(event);
+ }
+ }
+
+ /**
+ * Dispatches pending web kit events.
+ * Must only be called from the web kit thread.
+ *
+ * This method may be used to flush the queue of pending input events
+ * immediately. This method may help to reduce input dispatch latency
+ * if called before certain expensive operations such as drawing.
+ */
+ public void dispatchWebKitEvents() {
+ dispatchWebKitEvents(false);
+ }
+
+ private void dispatchWebKitEvents(boolean calledFromHandler) {
+ for (;;) {
+ // Get the next event, but leave it in the queue so we can move it to the UI
+ // queue if a timeout occurs.
+ DispatchEvent d;
+ MotionEvent event;
+ final int eventType;
+ int flags;
+ synchronized (mLock) {
+ if (!ENABLE_EVENT_BATCHING) {
+ drainStaleWebKitEventsLocked();
+ }
+ d = mWebKitDispatchEventQueue.mHead;
+ if (d == null) {
+ if (mWebKitDispatchScheduled) {
+ mWebKitDispatchScheduled = false;
+ if (!calledFromHandler) {
+ mWebKitHandler.removeMessages(
+ WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
+ }
+ }
+ return;
+ }
+
+ event = d.mEvent;
+ if (event != null) {
+ event.offsetLocation(d.mWebKitXOffset, d.mWebKitYOffset);
+ event.scale(d.mWebKitScale);
+ d.mFlags |= FLAG_WEBKIT_TRANSFORMED_EVENT;
+ }
+
+ eventType = d.mEventType;
+ if (eventType == EVENT_TYPE_TOUCH) {
+ event = mWebKitTouchStream.update(event);
+ if (DEBUG && event == null && d.mEvent != null) {
+ Log.d(TAG, "dispatchWebKitEvents: dropped event " + d.mEvent);
+ }
+ }
+
+ d.mFlags |= FLAG_WEBKIT_IN_PROGRESS;
+ flags = d.mFlags;
+ }
+
+ // Handle the event.
+ final boolean preventDefault;
+ if (event == null) {
+ preventDefault = false;
+ } else {
+ preventDefault = dispatchWebKitEvent(event, eventType, flags);
+ }
+
+ synchronized (mLock) {
+ flags = d.mFlags;
+ d.mFlags = flags & ~FLAG_WEBKIT_IN_PROGRESS;
+ boolean recycleEvent = event != d.mEvent;
+
+ if ((flags & FLAG_WEBKIT_TIMEOUT) != 0) {
+ // A timeout occurred!
+ recycleDispatchEventLocked(d);
+ } else {
+ // Web kit finished in a timely manner. Dequeue the event.
+ assert mWebKitDispatchEventQueue.mHead == d;
+ mWebKitDispatchEventQueue.dequeue();
+
+ updateWebKitTimeoutLocked();
+
+ if ((flags & FLAG_PRIVATE) != 0) {
+ // Event was intended for web kit only. All done.
+ recycleDispatchEventLocked(d);
+ } else if (preventDefault) {
+ // Web kit has decided to consume the event!
+ if (d.mEventType == EVENT_TYPE_TOUCH) {
+ enqueueUiCancelTouchEventIfNeededLocked();
+ }
+ } else {
+ // Web kit is being friendly. Pass the event to the UI.
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ if (event != null && recycleEvent) {
+ event.recycle();
+ }
+ }
+ }
+ }
+
+ // Runs on web kit thread.
+ private boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchWebKitEvent: event=" + event
+ + ", eventType=" + eventType + ", flags=" + flags);
+ }
+ boolean preventDefault = mWebKitCallbacks.dispatchWebKitEvent(
+ event, eventType, flags);
+ if (DEBUG) {
+ Log.d(TAG, "dispatchWebKitEvent: preventDefault=" + preventDefault);
+ }
+ return preventDefault;
+ }
+
+ private boolean isMoveEventLocked(DispatchEvent d) {
+ return d.mEvent != null
+ && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
+ }
+
+ private void drainStaleWebKitEventsLocked() {
+ DispatchEvent d = mWebKitDispatchEventQueue.mHead;
+ while (d != null && d.mNext != null
+ && isMoveEventLocked(d)
+ && isMoveEventLocked(d.mNext)) {
+ DispatchEvent next = d.mNext;
+ skipWebKitEventLocked(d);
+ d = next;
+ }
+ mWebKitDispatchEventQueue.mHead = d;
+ }
+
+ // Runs on UI thread in response to the web kit thread appearing to be unresponsive.
+ private void handleWebKitTimeout() {
+ synchronized (mLock) {
+ if (!mWebKitTimeoutScheduled) {
+ return;
+ }
+ mWebKitTimeoutScheduled = false;
+
+ if (DEBUG) {
+ Log.d(TAG, "handleWebKitTimeout: timeout occurred!");
+ }
+
+ // Drain the web kit event queue.
+ DispatchEvent d = mWebKitDispatchEventQueue.dequeueList();
+
+ // If web kit was processing an event (must be at the head of the list because
+ // it can only do one at a time), then clone it or ignore it.
+ if ((d.mFlags & FLAG_WEBKIT_IN_PROGRESS) != 0) {
+ d.mFlags |= FLAG_WEBKIT_TIMEOUT;
+ if ((d.mFlags & FLAG_PRIVATE) != 0) {
+ d = d.mNext; // the event is private to web kit, ignore it
+ } else {
+ d = copyDispatchEventLocked(d);
+ d.mFlags &= ~FLAG_WEBKIT_IN_PROGRESS;
+ }
+ }
+
+ // Enqueue all non-private events for handling by the UI thread.
+ while (d != null) {
+ DispatchEvent next = d.mNext;
+ skipWebKitEventLocked(d);
+ d = next;
+ }
+
+ // Tell web kit to cancel all pending touches.
+ // This also prevents us from sending web kit any more touches until the
+ // next gesture begins. (As required to ensure touch event stream consistency.)
+ enqueueWebKitCancelTouchEventIfNeededLocked();
+ }
+ }
+
+ private void skipWebKitEventLocked(DispatchEvent d) {
+ d.mNext = null;
+ if ((d.mFlags & FLAG_PRIVATE) != 0) {
+ recycleDispatchEventLocked(d);
+ } else {
+ d.mFlags |= FLAG_WEBKIT_TIMEOUT;
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ /**
+ * Dispatches pending UI events.
+ * Must only be called from the UI thread.
+ *
+ * This method may be used to flush the queue of pending input events
+ * immediately. This method may help to reduce input dispatch latency
+ * if called before certain expensive operations such as drawing.
+ */
+ public void dispatchUiEvents() {
+ dispatchUiEvents(false);
+ }
+
+ private void dispatchUiEvents(boolean calledFromHandler) {
+ for (;;) {
+ MotionEvent event;
+ final int eventType;
+ final int flags;
+ synchronized (mLock) {
+ DispatchEvent d = mUiDispatchEventQueue.dequeue();
+ if (d == null) {
+ if (mUiDispatchScheduled) {
+ mUiDispatchScheduled = false;
+ if (!calledFromHandler) {
+ mUiHandler.removeMessages(UiHandler.MSG_DISPATCH_UI_EVENTS);
+ }
+ }
+ return;
+ }
+
+ event = d.mEvent;
+ if (event != null && (d.mFlags & FLAG_WEBKIT_TRANSFORMED_EVENT) != 0) {
+ event.scale(1.0f / d.mWebKitScale);
+ event.offsetLocation(-d.mWebKitXOffset, -d.mWebKitYOffset);
+ d.mFlags &= ~FLAG_WEBKIT_TRANSFORMED_EVENT;
+ }
+
+ eventType = d.mEventType;
+ if (eventType == EVENT_TYPE_TOUCH) {
+ event = mUiTouchStream.update(event);
+ if (DEBUG && event == null && d.mEvent != null) {
+ Log.d(TAG, "dispatchUiEvents: dropped event " + d.mEvent);
+ }
+ }
+
+ flags = d.mFlags;
+
+ updateStateTrackersLocked(d, event);
+ if (event == d.mEvent) {
+ d.mEvent = null; // retain ownership of event, don't recycle it yet
+ }
+ recycleDispatchEventLocked(d);
+ }
+
+ // Handle the event.
+ if (event != null) {
+ dispatchUiEvent(event, eventType, flags);
+ event.recycle();
+ }
+ }
+ }
+
+ // Runs on UI thread.
+ private void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchUiEvent: event=" + event
+ + ", eventType=" + eventType + ", flags=" + flags);
+ }
+ mUiCallbacks.dispatchUiEvent(event, eventType, flags);
+ }
+
+ private void enqueueEventLocked(DispatchEvent d) {
+ if (!shouldSkipWebKit(d.mEventType)) {
+ enqueueWebKitEventLocked(d);
+ } else {
+ enqueueUiEventLocked(d);
+ }
+ }
+
+ private boolean shouldSkipWebKit(int eventType) {
+ switch (eventType) {
+ case EVENT_TYPE_CLICK:
+ case EVENT_TYPE_HOVER:
+ case EVENT_TYPE_SCROLL:
+ return false;
+ case EVENT_TYPE_TOUCH:
+ return !mPostSendTouchEventsToWebKit
+ || mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
+ }
+ return true;
+ }
+
+ private void enqueueWebKitCancelTouchEventIfNeededLocked() {
+ // We want to cancel touch events that were delivered to web kit.
+ // Enqueue a null event at the end of the queue if needed.
+ if (mWebKitTouchStream.isCancelNeeded() || !mWebKitDispatchEventQueue.isEmpty()) {
+ DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
+ 0, 0, 1.0f);
+ enqueueWebKitEventUnbatchedLocked(d);
+ mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
+ }
+ }
+
+ private void enqueueWebKitEventLocked(DispatchEvent d) {
+ if (batchEventLocked(d, mWebKitDispatchEventQueue.mTail)) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueWebKitEventLocked: batched event " + d.mEvent);
+ }
+ recycleDispatchEventLocked(d);
+ } else {
+ enqueueWebKitEventUnbatchedLocked(d);
+ }
+ }
+
+ private void enqueueWebKitEventUnbatchedLocked(DispatchEvent d) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueWebKitEventUnbatchedLocked: enqueued event " + d.mEvent);
+ }
+ mWebKitDispatchEventQueue.enqueue(d);
+ scheduleWebKitDispatchLocked();
+ updateWebKitTimeoutLocked();
+ }
+
+ private void scheduleWebKitDispatchLocked() {
+ if (!mWebKitDispatchScheduled) {
+ mWebKitHandler.sendEmptyMessage(WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
+ mWebKitDispatchScheduled = true;
+ }
+ }
+
+ private void updateWebKitTimeoutLocked() {
+ DispatchEvent d = mWebKitDispatchEventQueue.mHead;
+ if (d != null && mWebKitTimeoutScheduled && mWebKitTimeoutTime == d.mTimeoutTime) {
+ return;
+ }
+ if (mWebKitTimeoutScheduled) {
+ mUiHandler.removeMessages(UiHandler.MSG_WEBKIT_TIMEOUT);
+ mWebKitTimeoutScheduled = false;
+ }
+ if (d != null) {
+ mUiHandler.sendEmptyMessageAtTime(UiHandler.MSG_WEBKIT_TIMEOUT, d.mTimeoutTime);
+ mWebKitTimeoutScheduled = true;
+ mWebKitTimeoutTime = d.mTimeoutTime;
+ }
+ }
+
+ private void enqueueUiCancelTouchEventIfNeededLocked() {
+ // We want to cancel touch events that were delivered to the UI.
+ // Enqueue a null event at the end of the queue if needed.
+ if (mUiTouchStream.isCancelNeeded() || !mUiDispatchEventQueue.isEmpty()) {
+ DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
+ 0, 0, 1.0f);
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ private void enqueueUiEventLocked(DispatchEvent d) {
+ if (batchEventLocked(d, mUiDispatchEventQueue.mTail)) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueUiEventLocked: batched event " + d.mEvent);
+ }
+ recycleDispatchEventLocked(d);
+ } else {
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ private void enqueueUiEventUnbatchedLocked(DispatchEvent d) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueUiEventUnbatchedLocked: enqueued event " + d.mEvent);
+ }
+ mUiDispatchEventQueue.enqueue(d);
+ scheduleUiDispatchLocked();
+ }
+
+ private void scheduleUiDispatchLocked() {
+ if (!mUiDispatchScheduled) {
+ mUiHandler.sendEmptyMessage(UiHandler.MSG_DISPATCH_UI_EVENTS);
+ mUiDispatchScheduled = true;
+ }
+ }
+
+ private boolean batchEventLocked(DispatchEvent in, DispatchEvent tail) {
+ if (!ENABLE_EVENT_BATCHING) {
+ return false;
+ }
+ if (tail != null && tail.mEvent != null && in.mEvent != null
+ && in.mEventType == tail.mEventType
+ && in.mFlags == tail.mFlags
+ && in.mWebKitXOffset == tail.mWebKitXOffset
+ && in.mWebKitYOffset == tail.mWebKitYOffset
+ && in.mWebKitScale == tail.mWebKitScale) {
+ return tail.mEvent.addBatch(in.mEvent);
+ }
+ return false;
+ }
+
+ private DispatchEvent obtainDispatchEventLocked(MotionEvent event,
+ int eventType, int flags, int webKitXOffset, int webKitYOffset, float webKitScale) {
+ DispatchEvent d = obtainUninitializedDispatchEventLocked();
+ d.mEvent = event;
+ d.mEventType = eventType;
+ d.mFlags = flags;
+ d.mTimeoutTime = SystemClock.uptimeMillis() + WEBKIT_TIMEOUT_MILLIS;
+ d.mWebKitXOffset = webKitXOffset;
+ d.mWebKitYOffset = webKitYOffset;
+ d.mWebKitScale = webKitScale;
+ if (DEBUG) {
+ Log.d(TAG, "Timeout time: " + (d.mTimeoutTime - SystemClock.uptimeMillis()));
+ }
+ return d;
+ }
+
+ private DispatchEvent copyDispatchEventLocked(DispatchEvent d) {
+ DispatchEvent copy = obtainUninitializedDispatchEventLocked();
+ if (d.mEvent != null) {
+ copy.mEvent = d.mEvent.copy();
+ }
+ copy.mEventType = d.mEventType;
+ copy.mFlags = d.mFlags;
+ copy.mTimeoutTime = d.mTimeoutTime;
+ copy.mWebKitXOffset = d.mWebKitXOffset;
+ copy.mWebKitYOffset = d.mWebKitYOffset;
+ copy.mWebKitScale = d.mWebKitScale;
+ copy.mNext = d.mNext;
+ return copy;
+ }
+
+ private DispatchEvent obtainUninitializedDispatchEventLocked() {
+ DispatchEvent d = mDispatchEventPool;
+ if (d != null) {
+ mDispatchEventPoolSize -= 1;
+ mDispatchEventPool = d.mNext;
+ d.mNext = null;
+ } else {
+ d = new DispatchEvent();
+ }
+ return d;
+ }
+
+ private void recycleDispatchEventLocked(DispatchEvent d) {
+ if (d.mEvent != null) {
+ d.mEvent.recycle();
+ d.mEvent = null;
+ }
+
+ if (mDispatchEventPoolSize < MAX_DISPATCH_EVENT_POOL_SIZE) {
+ mDispatchEventPoolSize += 1;
+ d.mNext = mDispatchEventPool;
+ mDispatchEventPool = d;
+ }
+ }
+
+ /* Implemented by {@link WebViewClassic} to perform operations on the UI thread. */
+ public static interface UiCallbacks {
+ /**
+ * Gets the UI thread's looper.
+ * @return The looper.
+ */
+ public Looper getUiLooper();
+
+ /**
+ * Gets the UI's context
+ * @return The context
+ */
+ public Context getContext();
+
+ /**
+ * Dispatches an event to the UI.
+ * @param event The event.
+ * @param eventType The event type.
+ * @param flags The event's dispatch flags.
+ */
+ public void dispatchUiEvent(MotionEvent event, int eventType, int flags);
+ }
+
+ /* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */
+ public static interface WebKitCallbacks {
+ /**
+ * Gets the web kit thread's looper.
+ * @return The looper.
+ */
+ public Looper getWebKitLooper();
+
+ /**
+ * Dispatches an event to web kit.
+ * @param event The event.
+ * @param eventType The event type.
+ * @param flags The event's dispatch flags.
+ * @return True if web kit wants to prevent default event handling.
+ */
+ public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags);
+ }
+
+ // Runs on UI thread.
+ private final class UiHandler extends Handler {
+ public static final int MSG_DISPATCH_UI_EVENTS = 1;
+ public static final int MSG_WEBKIT_TIMEOUT = 2;
+ public static final int MSG_LONG_PRESS = 3;
+ public static final int MSG_CLICK = 4;
+
+ public UiHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISPATCH_UI_EVENTS:
+ dispatchUiEvents(true);
+ break;
+ case MSG_WEBKIT_TIMEOUT:
+ handleWebKitTimeout();
+ break;
+ case MSG_LONG_PRESS:
+ postLongPress();
+ break;
+ case MSG_CLICK:
+ postClick();
+ break;
+ default:
+ throw new IllegalStateException("Unknown message type: " + msg.what);
+ }
+ }
+ }
+
+ // Runs on web kit thread.
+ private final class WebKitHandler extends Handler {
+ public static final int MSG_DISPATCH_WEBKIT_EVENTS = 1;
+
+ public WebKitHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISPATCH_WEBKIT_EVENTS:
+ dispatchWebKitEvents(true);
+ break;
+ default:
+ throw new IllegalStateException("Unknown message type: " + msg.what);
+ }
+ }
+ }
+
+ private static final class DispatchEvent {
+ public DispatchEvent mNext;
+
+ public MotionEvent mEvent;
+ public int mEventType;
+ public int mFlags;
+ public long mTimeoutTime;
+ public int mWebKitXOffset;
+ public int mWebKitYOffset;
+ public float mWebKitScale;
+ }
+
+ private static final class DispatchEventQueue {
+ public DispatchEvent mHead;
+ public DispatchEvent mTail;
+
+ public boolean isEmpty() {
+ return mHead != null;
+ }
+
+ public void enqueue(DispatchEvent d) {
+ if (mHead == null) {
+ mHead = d;
+ mTail = d;
+ } else {
+ mTail.mNext = d;
+ mTail = d;
+ }
+ }
+
+ public DispatchEvent dequeue() {
+ DispatchEvent d = mHead;
+ if (d != null) {
+ DispatchEvent next = d.mNext;
+ if (next == null) {
+ mHead = null;
+ mTail = null;
+ } else {
+ mHead = next;
+ d.mNext = null;
+ }
+ }
+ return d;
+ }
+
+ public DispatchEvent dequeueList() {
+ DispatchEvent d = mHead;
+ if (d != null) {
+ mHead = null;
+ mTail = null;
+ }
+ return d;
+ }
+ }
+
+ /**
+ * Keeps track of a stream of touch events so that we can discard touch
+ * events that would make the stream inconsistent.
+ */
+ private static final class TouchStream {
+ private MotionEvent mLastEvent;
+
+ /**
+ * Gets the last touch event that was delivered.
+ * @return The last touch event, or null if none.
+ */
+ public MotionEvent getLastEvent() {
+ return mLastEvent;
+ }
+
+ /**
+ * Updates the touch event stream.
+ * @param event The event that we intend to send, or null to cancel the
+ * touch event stream.
+ * @return The event that we should actually send, or null if no event should
+ * be sent because the proposed event would make the stream inconsistent.
+ */
+ public MotionEvent update(MotionEvent event) {
+ if (event == null) {
+ if (isCancelNeeded()) {
+ event = mLastEvent;
+ if (event != null) {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ mLastEvent = null;
+ }
+ }
+ return event;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mLastEvent == null
+ || mLastEvent.getAction() == MotionEvent.ACTION_UP) {
+ return null;
+ }
+ updateLastEvent(event);
+ return event;
+
+ case MotionEvent.ACTION_DOWN:
+ updateLastEvent(event);
+ return event;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (mLastEvent == null) {
+ return null;
+ }
+ updateLastEvent(null);
+ return event;
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if there is a gesture in progress that may need to be canceled.
+ * @return True if cancel is needed.
+ */
+ public boolean isCancelNeeded() {
+ return mLastEvent != null && mLastEvent.getAction() != MotionEvent.ACTION_UP;
+ }
+
+ private void updateLastEvent(MotionEvent event) {
+ if (mLastEvent != null) {
+ mLastEvent.recycle();
+ }
+ mLastEvent = event != null ? MotionEvent.obtainNoHistory(event) : null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 11ecd1f..aef631f 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -23,6 +23,7 @@
import android.net.Uri;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -57,7 +58,24 @@
* @param path The path of the input media file.
* @throws IllegalArgumentException If the path is invalid.
*/
- public native void setDataSource(String path) throws IllegalArgumentException;
+ public void setDataSource(String path) throws IllegalArgumentException {
+ FileInputStream is = null;
+ try {
+ is = new FileInputStream(path);
+ FileDescriptor fd = is.getFD();
+ setDataSource(fd, 0, 0x7ffffffffffffffL);
+ } catch (FileNotFoundException fileEx) {
+ throw new IllegalArgumentException();
+ } catch (IOException ioEx) {
+ throw new IllegalArgumentException();
+ }
+
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (Exception e) {}
+ }
/**
* Sets the data source (URI) to use. Call this
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 0dc3b65..297dadf 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -131,13 +131,6 @@
"setDataSource failed");
}
-
-static void android_media_MediaMetadataRetriever_setDataSource(
- JNIEnv *env, jobject thiz, jstring path) {
- android_media_MediaMetadataRetriever_setDataSourceAndHeaders(
- env, thiz, path, NULL, NULL);
-}
-
static void android_media_MediaMetadataRetriever_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
ALOGV("setDataSource");
@@ -447,8 +440,6 @@
// JNI mapping between Java methods and native methods
static JNINativeMethod nativeMethods[] = {
- {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaMetadataRetriever_setDataSource},
-
{
"_setDataSource",
"(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
diff --git a/native/android/native_window.cpp b/native/android/native_window.cpp
index c58ee00..99c0fd3 100644
--- a/native/android/native_window.cpp
+++ b/native/android/native_window.cpp
@@ -60,13 +60,16 @@
int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window, int32_t width,
int32_t height, int32_t format) {
- int32_t err = native_window_set_buffers_geometry(window, width, height, format);
+ int32_t err = native_window_set_buffers_format(window, format);
if (!err) {
- int mode = NATIVE_WINDOW_SCALING_MODE_FREEZE;
- if (width && height) {
- mode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
- }
- err = native_window_set_scaling_mode(window, mode);
+ err = native_window_set_buffers_user_dimensions(window, width, height);
+ if (!err) {
+ int mode = NATIVE_WINDOW_SCALING_MODE_FREEZE;
+ if (width && height) {
+ mode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
+ }
+ err = native_window_set_scaling_mode(window, mode);
+ }
}
return err;
}
diff --git a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java
new file mode 100644
index 0000000..d445d5c
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java
@@ -0,0 +1,52 @@
+/*
+ * 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 com.android.internal.policy.impl;
+
+import android.view.View;
+
+interface BiometricSensorUnlock {
+ // Returns 'true' if the biometric sensor is available and is selected by user.
+ public boolean installedAndSelected();
+
+ // Returns 'true' if the biometric sensor has started its unlock procedure but has not yet
+ // accepted or rejected the user.
+ public boolean isRunning();
+
+ // Show the interface, but don't start the unlock procedure. The interface should disappear
+ // after the specified timeout. If the timeout is 0, the interface shows until another event,
+ // such as calling hide(), causes it to disappear.
+ public void show(long timeoutMilliseconds);
+
+ // Hide the interface, if any, exposing the lockscreen.
+ public void hide();
+
+ // Stop the unlock procedure if running. Returns 'true' if it was in fact running.
+ public boolean stop();
+
+ // Start the unlock procedure. Returns ‘false’ if it can’t be started or if the backup should
+ // be used.
+ public boolean start(boolean suppressBiometricUnlock);
+
+ // Provide a view to work within.
+ public void initializeAreaView(View topView);
+
+ // Clean up any resources used by the biometric unlock.
+ public void cleanUp();
+
+ // Returns the Device Policy Manager quality (e.g. PASSWORD_QUALITY_BIOMETRIC_WEAK).
+ public int getQuality();
+}
diff --git a/policy/src/com/android/internal/policy/impl/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/FaceUnlock.java
index 31fbaafd..7b0a086 100644
--- a/policy/src/com/android/internal/policy/impl/FaceUnlock.java
+++ b/policy/src/com/android/internal/policy/impl/FaceUnlock.java
@@ -21,6 +21,7 @@
import com.android.internal.policy.IFaceLockInterface;
import com.android.internal.widget.LockPatternUtils;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -33,7 +34,7 @@
import android.util.Log;
import android.view.View;
-public class FaceUnlock implements Handler.Callback {
+public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
private static final boolean DEBUG = false;
private static final String TAG = "FULLockscreen";
@@ -52,10 +53,6 @@
private boolean mServiceRunning = false;
private final Object mServiceRunningLock = new Object();
- // Long enough to stay visible while dialer comes up
- // Short enough to not be visible if the user goes back immediately
- private final int VIEW_AREA_EMERGENCY_DIALER_TIMEOUT = 1000;
-
// Long enough to stay visible while the service starts
// Short enough to not have to wait long for backup if service fails to start or crashes
// The service can take a couple of seconds to start on the first try after boot
@@ -80,22 +77,65 @@
mHandler = new Handler(this);
}
- public void cleanUp() {
- if (mService != null) {
- try {
- mService.unregisterCallback(mFaceLockCallback);
- } catch (RemoteException e) {
- // Not much we can do
- }
- stop();
- mService = null;
- }
+ // Indicates whether FaceLock is in use
+ public boolean installedAndSelected() {
+ return (mLockPatternUtils.usingBiometricWeak() &&
+ mLockPatternUtils.isBiometricWeakInstalled());
}
- /** When screen is turned on and focused, need to bind to FaceLock service if we are using
- * FaceLock, but only if we're not dealing with a call
- */
- public void activateIfAble(boolean hasOverlay) {
+ public boolean isRunning() {
+ return mServiceRunning;
+ }
+
+ // Shows the FaceLock area for a period of time
+ public void show(long timeoutMillis) {
+ showArea();
+ if (timeoutMillis > 0)
+ mHandler.sendEmptyMessageDelayed(MSG_HIDE_AREA_VIEW, timeoutMillis);
+ }
+
+ // Hides the FaceLock area immediately
+ public void hide() {
+ // Remove messages to prevent a delayed show message from undo-ing the hide
+ removeAreaDisplayMessages();
+ mHandler.sendEmptyMessage(MSG_HIDE_AREA_VIEW);
+ }
+
+ // Tells FaceLock to stop and then unbinds from the FaceLock service
+ public boolean stop() {
+ boolean wasRunning = false;
+ if (installedAndSelected()) {
+ stopUi();
+
+ if (mBoundToService) {
+ wasRunning = true;
+ if (DEBUG) Log.d(TAG, "before unbind from FaceLock service");
+ if (mService != null) {
+ try {
+ mService.unregisterCallback(mFaceLockCallback);
+ } catch (RemoteException e) {
+ // Not much we can do
+ }
+ }
+ mContext.unbindService(mConnection);
+ if (DEBUG) Log.d(TAG, "after unbind from FaceLock service");
+ mBoundToService = false;
+ } else {
+ // This is usually not an error when this happens. Sometimes we will tell it to
+ // unbind multiple times because it's called from both onWindowFocusChanged and
+ // onDetachedFromWindow.
+ if (DEBUG) Log.d(TAG, "Attempt to unbind from FaceLock when not bound");
+ }
+ }
+
+ return wasRunning;
+ }
+
+ /**
+ * When screen is turned on and focused, need to bind to FaceLock service if we are using
+ * FaceLock, but only if we're not dealing with a call
+ */
+ public boolean start(boolean suppressBiometricUnlock) {
final boolean tooManyFaceUnlockTries = mUpdateMonitor.getMaxFaceUnlockAttemptsReached();
final int failedBackupAttempts = mUpdateMonitor.getFailedAttempts();
final boolean backupIsTimedOut =
@@ -103,42 +143,31 @@
if (tooManyFaceUnlockTries) Log.i(TAG, "tooManyFaceUnlockTries: " + tooManyFaceUnlockTries);
if (mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
&& installedAndSelected()
- && !hasOverlay
+ && !suppressBiometricUnlock
&& !tooManyFaceUnlockTries
&& !backupIsTimedOut) {
bind();
// Show FaceLock area, but only for a little bit so lockpattern will become visible if
// FaceLock fails to start or crashes
- showAreaWithTimeout(VIEW_AREA_SERVICE_TIMEOUT);
+ show(VIEW_AREA_SERVICE_TIMEOUT);
// When switching between portrait and landscape view while FaceLock is running, the
// screen will eventually go dark unless we poke the wakelock when FaceLock is
// restarted
mKeyguardScreenCallback.pokeWakelock();
} else {
- hideArea();
+ hide();
+ return false;
}
- }
- public boolean isServiceRunning() {
- return mServiceRunning;
- }
-
- public int viewAreaEmergencyDialerTimeout() {
- return VIEW_AREA_EMERGENCY_DIALER_TIMEOUT;
- }
-
- // Indicates whether FaceLock is in use
- public boolean installedAndSelected() {
- return (mLockPatternUtils.usingBiometricWeak() &&
- mLockPatternUtils.isBiometricWeakInstalled());
+ return true;
}
// Takes care of FaceLock area when layout is created
- public void initializeAreaView(View view) {
+ public void initializeAreaView(View topView) {
if (installedAndSelected()) {
- mAreaView = view.findViewById(R.id.faceLockAreaView);
+ mAreaView = topView.findViewById(R.id.faceLockAreaView);
if (mAreaView == null) {
Log.e(TAG, "Layout does not have areaView and FaceLock is enabled");
}
@@ -147,13 +176,20 @@
}
}
- // Stops FaceLock if it is running and reports back whether it was running or not
- public boolean stopIfRunning() {
- if (installedAndSelected() && mBoundToService) {
- stopAndUnbind();
- return true;
+ public void cleanUp() {
+ if (mService != null) {
+ try {
+ mService.unregisterCallback(mFaceLockCallback);
+ } catch (RemoteException e) {
+ // Not much we can do
+ }
+ stopUi();
+ mService = null;
}
- return false;
+ }
+
+ public int getQuality() {
+ return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
}
// Handles covering or exposing FaceLock area on the client side when FaceLock starts or stops
@@ -186,28 +222,15 @@
}
// Shows the FaceLock area immediately
- public void showArea() {
+ private void showArea() {
// Remove messages to prevent a delayed hide message from undo-ing the show
removeAreaDisplayMessages();
mHandler.sendEmptyMessage(MSG_SHOW_AREA_VIEW);
}
- // Hides the FaceLock area immediately
- public void hideArea() {
- // Remove messages to prevent a delayed show message from undo-ing the hide
- removeAreaDisplayMessages();
- mHandler.sendEmptyMessage(MSG_HIDE_AREA_VIEW);
- }
-
- // Shows the FaceLock area for a period of time
- public void showAreaWithTimeout(long timeoutMillis) {
- showArea();
- mHandler.sendEmptyMessageDelayed(MSG_HIDE_AREA_VIEW, timeoutMillis);
- }
-
// Binds to FaceLock service. This call does not tell it to start, but it causes the service
// to call the onServiceConnected callback, which then starts FaceLock.
- public void bind() {
+ private void bind() {
if (installedAndSelected()) {
if (!mBoundToService) {
if (DEBUG) Log.d(TAG, "before bind to FaceLock service");
@@ -223,32 +246,6 @@
}
}
- // Tells FaceLock to stop and then unbinds from the FaceLock service
- public void stopAndUnbind() {
- if (installedAndSelected()) {
- stop();
-
- if (mBoundToService) {
- if (DEBUG) Log.d(TAG, "before unbind from FaceLock service");
- if (mService != null) {
- try {
- mService.unregisterCallback(mFaceLockCallback);
- } catch (RemoteException e) {
- // Not much we can do
- }
- }
- mContext.unbindService(mConnection);
- if (DEBUG) Log.d(TAG, "after unbind from FaceLock service");
- mBoundToService = false;
- } else {
- // This is usually not an error when this happens. Sometimes we will tell it to
- // unbind multiple times because it's called from both onWindowFocusChanged and
- // onDetachedFromWindow.
- if (DEBUG) Log.d(TAG, "Attempt to unbind from FaceLock when not bound");
- }
- }
- }
-
private ServiceConnection mConnection = new ServiceConnection() {
// Completes connection, registers callback and starts FaceLock when service is bound
@Override
@@ -268,7 +265,7 @@
int[] position;
position = new int[2];
mAreaView.getLocationInWindow(position);
- start(mAreaView.getWindowToken(), position[0], position[1],
+ startUi(mAreaView.getWindowToken(), position[0], position[1],
mAreaView.getWidth(), mAreaView.getHeight());
}
}
@@ -286,7 +283,7 @@
};
// Tells the FaceLock service to start displaying its UI and perform recognition
- public void start(IBinder windowToken, int x, int y, int w, int h) {
+ private void startUi(IBinder windowToken, int x, int y, int w, int h) {
if (installedAndSelected()) {
synchronized (mServiceRunningLock) {
if (!mServiceRunning) {
@@ -300,14 +297,14 @@
}
mServiceRunning = true;
} else {
- if (DEBUG) Log.w(TAG, "start() attempted while running");
+ if (DEBUG) Log.w(TAG, "startUi() attempted while running");
}
}
}
}
// Tells the FaceLock service to stop displaying its UI and stop recognition
- public void stop() {
+ private void stopUi() {
if (installedAndSelected()) {
// Note that attempting to stop FaceLock when it's not running is not an issue.
// FaceLock can return, which stops it and then we try to stop it when the
@@ -333,7 +330,7 @@
public void unlock() {
if (DEBUG) Log.d(TAG, "FaceLock unlock()");
showArea(); // Keep fallback covered
- stopAndUnbind();
+ stop();
mKeyguardScreenCallback.keyguardDone(true);
mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
@@ -344,8 +341,8 @@
@Override
public void cancel() {
if (DEBUG) Log.d(TAG, "FaceLock cancel()");
- hideArea(); // Expose fallback
- stopAndUnbind();
+ hide(); // Expose fallback
+ stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
@@ -355,8 +352,8 @@
public void reportFailedAttempt() {
if (DEBUG) Log.d(TAG, "FaceLock reportFailedAttempt()");
mUpdateMonitor.reportFailedFaceUnlockAttempt();
- hideArea(); // Expose fallback
- stopAndUnbind();
+ hide(); // Expose fallback
+ stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
@@ -364,7 +361,7 @@
@Override
public void exposeFallback() {
if (DEBUG) Log.d(TAG, "FaceLock exposeFallback()");
- hideArea(); // Expose fallback
+ hide(); // Expose fallback
}
// Allows the Face Unlock service to poke the wake lock to keep the lockscreen alive
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index d42f96a..c382646 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -101,15 +101,17 @@
private boolean mShowLockBeforeUnlock = false;
- // The following were added to support FaceLock
- private FaceUnlock mFaceUnlock;
- private final Object mFaceLockStartupLock = new Object();
+ // Interface to a biometric sensor that can optionally be used to unlock the device
+ private BiometricSensorUnlock mBiometricUnlock;
+ private final Object mBiometricUnlockStartupLock = new Object();
+ // Long enough to stay visible while dialer comes up
+ // Short enough to not be visible if the user goes back immediately
+ private final int BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT = 1000;
private boolean mRequiresSim;
- //True if we have some sort of overlay on top of the Lockscreen
- //Also true if we've activated a phone call, either emergency dialing or incoming
- //This resets when the phone is turned off with no current call
- private boolean mHasOverlay;
+ // True if the biometric unlock should not be displayed. For example, if there is an overlay on
+ // lockscreen or the user is plugging in / unplugging the device.
+ private boolean mSupressBiometricUnlock;
//True if a dialog is currently displaying on top of this window
//Unlike other overlays, this does not close with a power button cycle
private boolean mHasDialog = false;
@@ -308,15 +310,15 @@
}
public void takeEmergencyCallAction() {
- mHasOverlay = true;
+ mSupressBiometricUnlock = true;
- // Continue showing FaceLock area until dialer comes up or call is resumed
- if (mFaceUnlock.installedAndSelected() && mFaceUnlock.isServiceRunning()) {
- mFaceUnlock.showAreaWithTimeout(mFaceUnlock.viewAreaEmergencyDialerTimeout());
+ if (mBiometricUnlock.installedAndSelected() && mBiometricUnlock.isRunning()) {
+ // Continue covering backup lock until dialer comes up or call is resumed
+ mBiometricUnlock.show(BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT);
}
- // FaceLock must be stopped if it is running when emergency call is pressed
- mFaceUnlock.stopAndUnbind();
+ // The biometric unlock must be stopped if it is running when emergency call is pressed
+ mBiometricUnlock.stop();
pokeWakelock(EMERGENCY_CALL_TIMEOUT);
if (TelephonyManager.getDefault().getCallState()
@@ -421,7 +423,7 @@
LockPatternUtils lockPatternUtils, KeyguardWindowController controller) {
super(context, callback);
- mFaceUnlock = new FaceUnlock(context, updateMonitor, lockPatternUtils,
+ mBiometricUnlock = new FaceUnlock(context, updateMonitor, lockPatternUtils,
mKeyguardScreenCallback);
mConfiguration = context.getResources().getConfiguration();
mEnableFallback = false;
@@ -429,7 +431,7 @@
mUpdateMonitor = updateMonitor;
mLockPatternUtils = lockPatternUtils;
mWindowController = controller;
- mHasOverlay = false;
+ mSupressBiometricUnlock = false;
mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
mScreenOn = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)).isScreenOn();
@@ -528,8 +530,8 @@
if (DEBUG) Log.d(TAG, "screen off");
mScreenOn = false;
mForgotPattern = false;
- mHasOverlay = mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE ||
- mHasDialog;
+ mSupressBiometricUnlock =
+ mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE || mHasDialog;
// Emulate activity life-cycle for both lock and unlock screen.
if (mLockScreen != null) {
@@ -541,25 +543,25 @@
saveWidgetState();
- // When screen is turned off, need to unbind from FaceLock service if using FaceLock
- mFaceUnlock.stopAndUnbind();
+ // The biometric unlock must stop when screen turns off.
+ mBiometricUnlock.stop();
}
@Override
public void onScreenTurnedOn() {
if (DEBUG) Log.d(TAG, "screen on");
- boolean runFaceLock = false;
- //Make sure to start facelock iff the screen is both on and focused
- synchronized(mFaceLockStartupLock) {
+ boolean startBiometricUnlock = false;
+ // Start the biometric unlock if and only if the screen is both on and focused
+ synchronized(mBiometricUnlockStartupLock) {
mScreenOn = true;
- runFaceLock = mWindowFocused;
+ startBiometricUnlock = mWindowFocused;
}
show();
restoreWidgetState();
- if (runFaceLock) mFaceUnlock.activateIfAble(mHasOverlay);
+ if (startBiometricUnlock) mBiometricUnlock.start(mSupressBiometricUnlock);
}
private void saveWidgetState() {
@@ -578,25 +580,26 @@
}
}
- /** Unbind from facelock if something covers this window (such as an alarm)
- * bind to facelock if the lockscreen window just came into focus, and the screen is on
+ /**
+ * Stop the biometric unlock if something covers this window (such as an alarm)
+ * Start the biometric unlock if the lockscreen window just came into focus and the screen is on
*/
@Override
public void onWindowFocusChanged (boolean hasWindowFocus) {
if (DEBUG) Log.d(TAG, hasWindowFocus ? "focused" : "unfocused");
- boolean runFaceLock = false;
- //Make sure to start facelock iff the screen is both on and focused
- synchronized(mFaceLockStartupLock) {
- if(mScreenOn && !mWindowFocused) runFaceLock = hasWindowFocus;
+ boolean startBiometricUnlock = false;
+ // Start the biometric unlock if and only if the screen is both on and focused
+ synchronized(mBiometricUnlockStartupLock) {
+ if (mScreenOn && !mWindowFocused) startBiometricUnlock = hasWindowFocus;
mWindowFocused = hasWindowFocus;
}
if (!hasWindowFocus) {
- mHasOverlay = true;
- mFaceUnlock.stopAndUnbind();
- mFaceUnlock.hideArea();
+ mSupressBiometricUnlock = true;
+ mBiometricUnlock.stop();
+ mBiometricUnlock.hide();
} else {
mHasDialog = false;
- if (runFaceLock) mFaceUnlock.activateIfAble(mHasOverlay);
+ if (startBiometricUnlock) mBiometricUnlock.start(mSupressBiometricUnlock);
}
}
@@ -610,14 +613,14 @@
((KeyguardScreen) mUnlockScreen).onResume();
}
- if (mFaceUnlock.installedAndSelected() && !mHasOverlay) {
+ if (mBiometricUnlock.installedAndSelected() && !mSupressBiometricUnlock) {
// Note that show() gets called before the screen turns off to set it up for next time
- // it is turned on. We don't want to set a timeout on the FaceLock area here because it
- // may be gone by the time the screen is turned on again. We set the timeout when the
- // screen turns on instead.
- mFaceUnlock.showArea();
+ // it is turned on. We don't want to set a timeout on the biometric unlock here because
+ // it may be gone by the time the screen is turned on again. We set the timeout when
+ // the screen turns on instead.
+ mBiometricUnlock.show(0);
} else {
- mFaceUnlock.hideArea();
+ mBiometricUnlock.hide();
}
}
@@ -651,9 +654,9 @@
removeCallbacks(mRecreateRunnable);
- // When view is hidden, need to unbind from FaceLock service if we are using FaceLock
+ // When view is hidden, we need to stop the biometric unlock
// e.g., when device becomes unlocked
- mFaceUnlock.stopAndUnbind();
+ mBiometricUnlock.stop();
super.onDetachedFromWindow();
}
@@ -670,16 +673,19 @@
InfoCallbackImpl mInfoCallback = new InfoCallbackImpl() {
- /** When somebody plugs in or unplugs the device, we don't want to display faceunlock */
+ /**
+ * When somebody plugs in or unplugs the device, we don't want to display the biometric
+ * unlock.
+ */
@Override
public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn,
int batteryLevel) {
- mHasOverlay |= mPluggedIn != pluggedIn;
+ mSupressBiometricUnlock |= mPluggedIn != pluggedIn;
mPluggedIn = pluggedIn;
- //If it's already running, don't close it down: the unplug didn't start it
- if (!mFaceUnlock.isServiceRunning()) {
- mFaceUnlock.stopAndUnbind();
- mFaceUnlock.hideArea();
+ // If it's already running, don't close it down: the unplug didn't start it
+ if (!mBiometricUnlock.isRunning()) {
+ mBiometricUnlock.stop();
+ mBiometricUnlock.hide();
}
}
@@ -690,20 +696,20 @@
| (mUpdateMonitor.isClockVisible() ? View.STATUS_BAR_DISABLE_CLOCK : 0));
}
- //We need to stop faceunlock when a phonecall comes in
+ // We need to stop the biometric unlock when a phone call comes in
@Override
public void onPhoneStateChanged(int phoneState) {
if (DEBUG) Log.d(TAG, "phone state: " + phoneState);
if(phoneState == TelephonyManager.CALL_STATE_RINGING) {
- mHasOverlay = true;
- mFaceUnlock.stopAndUnbind();
- mFaceUnlock.hideArea();
+ mSupressBiometricUnlock = true;
+ mBiometricUnlock.stop();
+ mBiometricUnlock.hide();
}
}
@Override
public void onUserChanged(int userId) {
- mFaceUnlock.stopAndUnbind();
+ mBiometricUnlock.stop();
mLockPatternUtils.setCurrentUser(userId);
updateScreen(getInitialMode(), true);
}
@@ -766,7 +772,7 @@
mUnlockScreen = null;
}
mUpdateMonitor.removeCallback(this);
- mFaceUnlock.cleanUp();
+ mBiometricUnlock.cleanUp();
}
private boolean isSecure() {
@@ -816,10 +822,10 @@
final UnlockMode unlockMode = getUnlockMode();
if (mode == Mode.UnlockScreen && unlockMode != UnlockMode.Unknown) {
if (force || mUnlockScreen == null || unlockMode != mUnlockScreenMode) {
- boolean restartFaceLock = mFaceUnlock.stopIfRunning();
+ boolean restartBiometricUnlock = mBiometricUnlock.stop();
recreateUnlockScreen(unlockMode);
- if (restartFaceLock || force) {
- mFaceUnlock.activateIfAble(mHasOverlay);
+ if (restartBiometricUnlock) {
+ mBiometricUnlock.start(mSupressBiometricUnlock);
}
}
}
@@ -933,7 +939,8 @@
throw new IllegalArgumentException("unknown unlock mode " + unlockMode);
}
initializeTransportControlView(unlockView);
- mFaceUnlock.initializeAreaView(unlockView); // Only shows view if FaceLock is enabled
+ // Only shows view if the biometric unlock is enabled
+ mBiometricUnlock.initializeAreaView(unlockView);
mUnlockScreenMode = unlockMode;
return unlockView;
diff --git a/services/java/com/android/server/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java
index 16eeb7ba..b943c09 100644
--- a/services/java/com/android/server/DeviceStorageMonitorService.java
+++ b/services/java/com/android/server/DeviceStorageMonitorService.java
@@ -25,6 +25,7 @@
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageManager;
import android.os.Binder;
+import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
@@ -336,7 +337,9 @@
//log the event to event log with the amount of free storage(in bytes) left on the device
EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
// Pack up the values and broadcast them to everyone
- Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
+ Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated()
+ ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS
+ : Intent.ACTION_MANAGE_PACKAGE_STORAGE);
lowMemIntent.putExtra("memory", mFreeMem);
lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
NotificationManager mNotificationMgr =
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index bb7f4fc..bb73b29 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -21,6 +21,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.ENFORCEMENT_DEFAULT;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
@@ -2558,9 +2559,13 @@
if (p.perm != null) {
pw.print(" perm="); pw.println(p.perm);
}
+ if (READ_EXTERNAL_STORAGE.equals(p.name)) {
+ pw.print(" enforcement=");
+ pw.println(PackageManager.enforcementToString(mReadExternalStorageEnforcement));
+ }
}
}
-
+
void dumpSharedUsersLPr(PrintWriter pw, String packageName, DumpState dumpState) {
boolean printedSomething = false;
for (SharedUserSetting su : mSharedUsers.values()) {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index d9425aab..f48b56d1 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -3974,7 +3974,6 @@
wtoken.willBeHidden = false;
if (wtoken.hidden == visible) {
- final int N = wtoken.allAppWindows.size();
boolean changed = false;
if (DEBUG_APP_TRANSITIONS) Slog.v(
TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden
@@ -3986,23 +3985,19 @@
if (wtoken.mAppAnimator.animation == sDummyAnimation) {
wtoken.mAppAnimator.animation = null;
}
- applyAnimationLocked(wtoken, lp, transit, visible);
- changed = true;
- if (wtoken.mAppAnimator.animation != null) {
+ if (applyAnimationLocked(wtoken, lp, transit, visible)) {
delayed = runningAppAnimation = true;
}
+ changed = true;
}
+ final int N = wtoken.allAppWindows.size();
for (int i=0; i<N; i++) {
WindowState win = wtoken.allAppWindows.get(i);
if (win == wtoken.startingWindow) {
continue;
}
- if (win.mWinAnimator.isAnimating()) {
- delayed = true;
- }
-
//Slog.i(TAG, "Window " + win + ": vis=" + win.isVisible());
//win.dump(" ");
if (visible) {
@@ -4055,6 +4050,12 @@
delayed = true;
}
+ for (int i = wtoken.allAppWindows.size() - 1; i >= 0 && !delayed; i--) {
+ if (wtoken.allAppWindows.get(i).mWinAnimator.isWindowAnimating()) {
+ delayed = true;
+ }
+ }
+
return delayed;
}
@@ -4917,7 +4918,11 @@
// have been drawn.
boolean haveBootMsg = false;
boolean haveApp = false;
+ // if the wallpaper service is disabled on the device, we're never going to have
+ // wallpaper, don't bother waiting for it
boolean haveWallpaper = false;
+ boolean wallpaperEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableWallpaperService);
boolean haveKeyguard = true;
final int N = mWindows.size();
for (int i=0; i<N; i++) {
@@ -4953,7 +4958,8 @@
if (DEBUG_SCREEN_ON || DEBUG_BOOT) {
Slog.i(TAG, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages
+ " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
- + " haveWall=" + haveWallpaper + " haveKeyguard=" + haveKeyguard);
+ + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
+ + " haveKeyguard=" + haveKeyguard);
}
// If we are turning on the screen to show the boot message,
@@ -4965,7 +4971,8 @@
// If we are turning on the screen after the boot is completed
// normally, don't do so until we have the application and
// wallpaper.
- if (mSystemBooted && ((!haveApp && !haveKeyguard) || !haveWallpaper)) {
+ if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
+ (wallpaperEnabled && !haveWallpaper))) {
return;
}
}
@@ -7262,6 +7269,7 @@
pw.flush();
Slog.w(TAG, "This window was lost: " + ws);
Slog.w(TAG, sw.toString());
+ ws.mWinAnimator.destroySurfaceLocked();
}
}
Slog.w(TAG, "Current app token list:");
@@ -9333,7 +9341,7 @@
pw.print(" mWaitingForConfig="); pw.println(mWaitingForConfig);
pw.print(" mRotation="); pw.print(mRotation);
pw.print(" mAltOrientation="); pw.println(mAltOrientation);
- pw.print(" mLastWindowForcedOrientation"); pw.print(mLastWindowForcedOrientation);
+ pw.print(" mLastWindowForcedOrientation="); pw.print(mLastWindowForcedOrientation);
pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation);
pw.print(" mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
if (mAnimator.mScreenRotationAnimation != null) {
@@ -9535,4 +9543,9 @@
void bulkSetParameters(final int bulkUpdateParams) {
mH.sendMessage(mH.obtainMessage(H.BULK_UPDATE_PARAMETERS, bulkUpdateParams, 0));
}
+
+ static String getCaller() {
+ StackTraceElement caller = Thread.currentThread().getStackTrace()[4];
+ return caller.getClassName() + "." + caller.getMethodName() + ":" + caller.getLineNumber();
+ }
}
diff --git a/test-runner/src/android/test/AssertionFailedError.java b/test-runner/src/android/test/AssertionFailedError.java
index 7af5806..b3ac6d1 100644
--- a/test-runner/src/android/test/AssertionFailedError.java
+++ b/test-runner/src/android/test/AssertionFailedError.java
@@ -19,8 +19,7 @@
/**
* Thrown when an assertion failed.
*
- * Note: Most users of this class should simply use junit.framework.AssertionFailedError,
- * which provides the same functionality.
+ * @deprecated use junit.framework.AssertionFailedError
*/
public class AssertionFailedError extends Error {
diff --git a/test-runner/src/android/test/ComparisonFailure.java b/test-runner/src/android/test/ComparisonFailure.java
index e7e9698..3fa76f5 100644
--- a/test-runner/src/android/test/ComparisonFailure.java
+++ b/test-runner/src/android/test/ComparisonFailure.java
@@ -19,8 +19,7 @@
/**
* Thrown when an assert equals for Strings failed.
*
- * Note: Most users of this class should simply use junit.framework.ComparisonFailure,
- * which provides the same functionality at a lighter weight.
+ * @deprecated use junit.framework.ComparisonFailure
*/
public class ComparisonFailure extends AssertionFailedError {
private junit.framework.ComparisonFailure mComparison;
diff --git a/test-runner/src/junit/runner/BaseTestRunner.java b/test-runner/src/junit/runner/BaseTestRunner.java
index e073ef7..8cfd7fa 100644
--- a/test-runner/src/junit/runner/BaseTestRunner.java
+++ b/test-runner/src/junit/runner/BaseTestRunner.java
@@ -1,10 +1,24 @@
package junit.runner;
-import junit.framework.*;
-import java.lang.reflect.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.text.NumberFormat;
-import java.io.*;
-import java.util.*;
+import java.util.Properties;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestListener;
+import junit.framework.TestSuite;
/**
* Base class for all test runners.
@@ -19,8 +33,8 @@
boolean fLoading= true;
/*
- * Implementation of TestListener
- */
+ * Implementation of TestListener
+ */
public synchronized void startTest(Test test) {
testStarted(test.toString());
}
@@ -32,9 +46,9 @@
protected static Properties getPreferences() {
if (fPreferences == null) {
fPreferences= new Properties();
- fPreferences.put("loading", "true");
- fPreferences.put("filterstack", "true");
- readPreferences();
+ fPreferences.put("loading", "true");
+ fPreferences.put("filterstack", "true");
+ readPreferences();
}
return fPreferences;
}
@@ -48,8 +62,9 @@
}
}
+ // android-changed remove 'static' qualifier for API compatibility
public void setPreference(String key, String value) {
- getPreferences().setProperty(key, value);
+ getPreferences().put(key, value);
}
public synchronized void endTest(Test test) {
@@ -97,8 +112,8 @@
Method suiteMethod= null;
try {
suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]);
- } catch(Exception e) {
- // try to extract a test suite automatically
+ } catch(Exception e) {
+ // try to extract a test suite automatically
clearStatus();
return new TestSuite(testClass);
}
@@ -108,7 +123,7 @@
}
Test test= null;
try {
- test= (Test)suiteMethod.invoke(null); // static method
+ test= (Test)suiteMethod.invoke(null, (Object[])new Class[0]); // static method
if (test == null)
return test;
}
@@ -163,7 +178,7 @@
fLoading= enable;
}
/**
- * Extract the class name from a String
+ * Extract the class name from a String in VA/Java style
*/
public String extractClassName(String className) {
if(className.startsWith("Default package for"))
@@ -186,11 +201,24 @@
*/
protected abstract void runFailed(String message);
+ // BEGIN android-changed - add back getLoader() for API compatibility
+ /**
+ * Returns the loader to be used.
+ *
+ * @deprecated not present in JUnit4.10
+ */
+ public TestSuiteLoader getLoader() {
+ if (useReloadingTestSuiteLoader())
+ return new ReloadingTestSuiteLoader();
+ return new StandardTestSuiteLoader();
+ }
+ // END android-changed
+
/**
* Returns the loaded Class for a suite name.
*/
- protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
- return getLoader().load(suiteClassName);
+ protected Class<?> loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
+ return Class.forName(suiteClassName);
}
/**
@@ -199,29 +227,20 @@
protected void clearStatus() { // Belongs in the GUI TestRunner class
}
- /**
- * Returns the loader to be used.
- */
- public TestSuiteLoader getLoader() {
- if (useReloadingTestSuiteLoader())
- return new ReloadingTestSuiteLoader();
- return new StandardTestSuiteLoader();
- }
-
protected boolean useReloadingTestSuiteLoader() {
- return getPreference("loading").equals("true") && !inVAJava() && fLoading;
+ return getPreference("loading").equals("true") && fLoading;
}
private static File getPreferencesFile() {
- String home= System.getProperty("user.home");
- return new File(home, "junit.properties");
- }
+ String home= System.getProperty("user.home");
+ return new File(home, "junit.properties");
+ }
- private static void readPreferences() {
- InputStream is= null;
- try {
- is= new FileInputStream(getPreferencesFile());
- setPreferences(new Properties(getPreferences()));
+ private static void readPreferences() {
+ InputStream is= null;
+ try {
+ is= new FileInputStream(getPreferencesFile());
+ setPreferences(new Properties(getPreferences()));
getPreferences().load(is);
} catch (IOException e) {
try {
@@ -230,32 +249,22 @@
} catch (IOException e1) {
}
}
- }
+ }
- public static String getPreference(String key) {
- return getPreferences().getProperty(key);
- }
+ public static String getPreference(String key) {
+ return getPreferences().getProperty(key);
+ }
- public static int getPreference(String key, int dflt) {
- String value= getPreference(key);
- int intValue= dflt;
- if (value == null)
- return intValue;
- try {
- intValue= Integer.parseInt(value);
- } catch (NumberFormatException ne) {
- }
- return intValue;
- }
-
- public static boolean inVAJava() {
+ public static int getPreference(String key, int dflt) {
+ String value= getPreference(key);
+ int intValue= dflt;
+ if (value == null)
+ return intValue;
try {
- Class.forName("com.ibm.uvm.tools.DebugSupport");
+ intValue= Integer.parseInt(value);
+ } catch (NumberFormatException ne) {
}
- catch (Exception e) {
- return false;
- }
- return true;
+ return intValue;
}
/**
@@ -270,6 +279,13 @@
return BaseTestRunner.getFilteredTrace(trace);
}
+ // BEGIN android-changed - add back this method for API compatibility
+ /** @deprecated not present in JUnit4.10 */
+ public static boolean inVAJava() {
+ return false;
+ }
+ // END android-changed
+
/**
* Filters stack frames from internal JUnit classes
*/
@@ -303,14 +319,14 @@
static boolean filterLine(String line) {
String[] patterns= new String[] {
- "junit.framework.TestCase",
- "junit.framework.TestResult",
- "junit.framework.TestSuite",
- "junit.framework.Assert.", // don't filter AssertionFailure
- "junit.swingui.TestRunner",
- "junit.awtui.TestRunner",
- "junit.textui.TestRunner",
- "java.lang.reflect.Method.invoke("
+ "junit.framework.TestCase",
+ "junit.framework.TestResult",
+ "junit.framework.TestSuite",
+ "junit.framework.Assert.", // don't filter AssertionFailure
+ "junit.swingui.TestRunner",
+ "junit.awtui.TestRunner",
+ "junit.textui.TestRunner",
+ "java.lang.reflect.Method.invoke("
};
for (int i= 0; i < patterns.length; i++) {
if (line.indexOf(patterns[i]) > 0)
@@ -319,8 +335,8 @@
return false;
}
- static {
- fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
- }
+ static {
+ fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
+ }
}
diff --git a/test-runner/src/junit/runner/ClassPathTestCollector.java b/test-runner/src/junit/runner/ClassPathTestCollector.java
index 8a3c702..f48ddee 100644
--- a/test-runner/src/junit/runner/ClassPathTestCollector.java
+++ b/test-runner/src/junit/runner/ClassPathTestCollector.java
@@ -13,69 +13,69 @@
* {@hide} - Not needed for 1.0 SDK
*/
public abstract class ClassPathTestCollector implements TestCollector {
-
- static final int SUFFIX_LENGTH= ".class".length();
-
- public ClassPathTestCollector() {
- }
-
- public Enumeration collectTests() {
- String classPath= System.getProperty("java.class.path");
- Hashtable result = collectFilesInPath(classPath);
- return result.elements();
- }
- public Hashtable collectFilesInPath(String classPath) {
- Hashtable result= collectFilesInRoots(splitClassPath(classPath));
- return result;
- }
-
- Hashtable collectFilesInRoots(Vector roots) {
- Hashtable result= new Hashtable(100);
- Enumeration e= roots.elements();
- while (e.hasMoreElements())
- gatherFiles(new File((String)e.nextElement()), "", result);
- return result;
- }
+ static final int SUFFIX_LENGTH= ".class".length();
- void gatherFiles(File classRoot, String classFileName, Hashtable result) {
- File thisRoot= new File(classRoot, classFileName);
- if (thisRoot.isFile()) {
- if (isTestClass(classFileName)) {
- String className= classNameFromFile(classFileName);
- result.put(className, className);
- }
- return;
- }
- String[] contents= thisRoot.list();
- if (contents != null) {
- for (int i= 0; i < contents.length; i++)
- gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result);
- }
- }
-
- Vector splitClassPath(String classPath) {
- Vector result= new Vector();
- String separator= System.getProperty("path.separator");
- StringTokenizer tokenizer= new StringTokenizer(classPath, separator);
- while (tokenizer.hasMoreTokens())
- result.addElement(tokenizer.nextToken());
- return result;
- }
-
- protected boolean isTestClass(String classFileName) {
- return
- classFileName.endsWith(".class") &&
- classFileName.indexOf('$') < 0 &&
- classFileName.indexOf("Test") > 0;
- }
-
- protected String classNameFromFile(String classFileName) {
- // convert /a/b.class to a.b
- String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH);
- String s2= s.replace(File.separatorChar, '.');
- if (s2.startsWith("."))
- return s2.substring(1);
- return s2;
- }
+ public ClassPathTestCollector() {
+ }
+
+ public Enumeration collectTests() {
+ String classPath= System.getProperty("java.class.path");
+ Hashtable result = collectFilesInPath(classPath);
+ return result.elements();
+ }
+
+ public Hashtable collectFilesInPath(String classPath) {
+ Hashtable result= collectFilesInRoots(splitClassPath(classPath));
+ return result;
+ }
+
+ Hashtable collectFilesInRoots(Vector roots) {
+ Hashtable result= new Hashtable(100);
+ Enumeration e= roots.elements();
+ while (e.hasMoreElements())
+ gatherFiles(new File((String)e.nextElement()), "", result);
+ return result;
+ }
+
+ void gatherFiles(File classRoot, String classFileName, Hashtable result) {
+ File thisRoot= new File(classRoot, classFileName);
+ if (thisRoot.isFile()) {
+ if (isTestClass(classFileName)) {
+ String className= classNameFromFile(classFileName);
+ result.put(className, className);
+ }
+ return;
+ }
+ String[] contents= thisRoot.list();
+ if (contents != null) {
+ for (int i= 0; i < contents.length; i++)
+ gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result);
+ }
+ }
+
+ Vector splitClassPath(String classPath) {
+ Vector result= new Vector();
+ String separator= System.getProperty("path.separator");
+ StringTokenizer tokenizer= new StringTokenizer(classPath, separator);
+ while (tokenizer.hasMoreTokens())
+ result.addElement(tokenizer.nextToken());
+ return result;
+ }
+
+ protected boolean isTestClass(String classFileName) {
+ return
+ classFileName.endsWith(".class") &&
+ classFileName.indexOf('$') < 0 &&
+ classFileName.indexOf("Test") > 0;
+ }
+
+ protected String classNameFromFile(String classFileName) {
+ // convert /a/b.class to a.b
+ String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH);
+ String s2= s.replace(File.separatorChar, '.');
+ if (s2.startsWith("."))
+ return s2.substring(1);
+ return s2;
+ }
}
diff --git a/test-runner/src/junit/runner/FailureDetailView.java b/test-runner/src/junit/runner/FailureDetailView.java
index 7108cec..1b8365a 100644
--- a/test-runner/src/junit/runner/FailureDetailView.java
+++ b/test-runner/src/junit/runner/FailureDetailView.java
@@ -1,7 +1,7 @@
package junit.runner;
// The following line was removed for compatibility with Android libraries.
-//import java.awt.Component;
+//import java.awt.Component;
import junit.framework.*;
@@ -17,12 +17,12 @@
// */
// public Component getComponent();
- /**
- * Shows details of a TestFailure
- */
- public void showFailure(TestFailure failure);
- /**
- * Clears the view
- */
- public void clear();
+ /**
+ * Shows details of a TestFailure
+ */
+ public void showFailure(TestFailure failure);
+ /**
+ * Clears the view
+ */
+ public void clear();
}
diff --git a/test-runner/src/junit/runner/LoadingTestCollector.java b/test-runner/src/junit/runner/LoadingTestCollector.java
index b1760b1..489d9d6 100644
--- a/test-runner/src/junit/runner/LoadingTestCollector.java
+++ b/test-runner/src/junit/runner/LoadingTestCollector.java
@@ -12,59 +12,59 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class LoadingTestCollector extends ClassPathTestCollector {
-
- TestCaseClassLoader fLoader;
-
- public LoadingTestCollector() {
- fLoader= new TestCaseClassLoader();
- }
-
- protected boolean isTestClass(String classFileName) {
- try {
- if (classFileName.endsWith(".class")) {
- Class testClass= classFromFile(classFileName);
- return (testClass != null) && isTestClass(testClass);
- }
- }
- catch (ClassNotFoundException expected) {
- }
- catch (NoClassDefFoundError notFatal) {
- }
- return false;
- }
-
- Class classFromFile(String classFileName) throws ClassNotFoundException {
- String className= classNameFromFile(classFileName);
- if (!fLoader.isExcluded(className))
- return fLoader.loadClass(className, false);
- return null;
- }
-
- boolean isTestClass(Class testClass) {
- if (hasSuiteMethod(testClass))
- return true;
- if (Test.class.isAssignableFrom(testClass) &&
- Modifier.isPublic(testClass.getModifiers()) &&
- hasPublicConstructor(testClass))
- return true;
- return false;
- }
-
- boolean hasSuiteMethod(Class testClass) {
- try {
- testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]);
- } catch(Exception e) {
- return false;
- }
- return true;
- }
-
- boolean hasPublicConstructor(Class testClass) {
- try {
- TestSuite.getTestConstructor(testClass);
- } catch(NoSuchMethodException e) {
- return false;
- }
- return true;
- }
+
+ TestCaseClassLoader fLoader;
+
+ public LoadingTestCollector() {
+ fLoader= new TestCaseClassLoader();
+ }
+
+ protected boolean isTestClass(String classFileName) {
+ try {
+ if (classFileName.endsWith(".class")) {
+ Class testClass= classFromFile(classFileName);
+ return (testClass != null) && isTestClass(testClass);
+ }
+ }
+ catch (ClassNotFoundException expected) {
+ }
+ catch (NoClassDefFoundError notFatal) {
+ }
+ return false;
+ }
+
+ Class classFromFile(String classFileName) throws ClassNotFoundException {
+ String className= classNameFromFile(classFileName);
+ if (!fLoader.isExcluded(className))
+ return fLoader.loadClass(className, false);
+ return null;
+ }
+
+ boolean isTestClass(Class testClass) {
+ if (hasSuiteMethod(testClass))
+ return true;
+ if (Test.class.isAssignableFrom(testClass) &&
+ Modifier.isPublic(testClass.getModifiers()) &&
+ hasPublicConstructor(testClass))
+ return true;
+ return false;
+ }
+
+ boolean hasSuiteMethod(Class testClass) {
+ try {
+ testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]);
+ } catch(Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ boolean hasPublicConstructor(Class testClass) {
+ try {
+ TestSuite.getTestConstructor(testClass);
+ } catch(NoSuchMethodException e) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java b/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
index a6d84fe..c4d80d0 100644
--- a/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
+++ b/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
@@ -5,16 +5,16 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class ReloadingTestSuiteLoader implements TestSuiteLoader {
-
- public Class load(String suiteClassName) throws ClassNotFoundException {
- return createLoader().loadClass(suiteClassName, true);
- }
-
- public Class reload(Class aClass) throws ClassNotFoundException {
- return createLoader().loadClass(aClass.getName(), true);
- }
-
- protected TestCaseClassLoader createLoader() {
- return new TestCaseClassLoader();
- }
+
+ public Class load(String suiteClassName) throws ClassNotFoundException {
+ return createLoader().loadClass(suiteClassName, true);
+ }
+
+ public Class reload(Class aClass) throws ClassNotFoundException {
+ return createLoader().loadClass(aClass.getName(), true);
+ }
+
+ protected TestCaseClassLoader createLoader() {
+ return new TestCaseClassLoader();
+ }
}
diff --git a/test-runner/src/junit/runner/SimpleTestCollector.java b/test-runner/src/junit/runner/SimpleTestCollector.java
index 543168f..6cb0e19 100644
--- a/test-runner/src/junit/runner/SimpleTestCollector.java
+++ b/test-runner/src/junit/runner/SimpleTestCollector.java
@@ -8,14 +8,14 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class SimpleTestCollector extends ClassPathTestCollector {
-
- public SimpleTestCollector() {
- }
-
- protected boolean isTestClass(String classFileName) {
- return
- classFileName.endsWith(".class") &&
- classFileName.indexOf('$') < 0 &&
- classFileName.indexOf("Test") > 0;
- }
+
+ public SimpleTestCollector() {
+ }
+
+ protected boolean isTestClass(String classFileName) {
+ return
+ classFileName.endsWith(".class") &&
+ classFileName.indexOf('$') < 0 &&
+ classFileName.indexOf("Test") > 0;
+ }
}
diff --git a/test-runner/src/junit/runner/Sorter.java b/test-runner/src/junit/runner/Sorter.java
index 66f551e..7731f66 100644
--- a/test-runner/src/junit/runner/Sorter.java
+++ b/test-runner/src/junit/runner/Sorter.java
@@ -11,29 +11,29 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class Sorter {
- public static interface Swapper {
- public void swap(Vector values, int left, int right);
- }
-
- public static void sortStrings(Vector values , int left, int right, Swapper swapper) {
- int oleft= left;
- int oright= right;
- String mid= (String)values.elementAt((left + right) / 2);
- do {
- while (((String)(values.elementAt(left))).compareTo(mid) < 0)
- left++;
- while (mid.compareTo((String)(values.elementAt(right))) < 0)
- right--;
- if (left <= right) {
- swapper.swap(values, left, right);
- left++;
- right--;
- }
- } while (left <= right);
-
- if (oleft < right)
- sortStrings(values, oleft, right, swapper);
- if (left < oright)
- sortStrings(values, left, oright, swapper);
- }
+ public static interface Swapper {
+ public void swap(Vector values, int left, int right);
+ }
+
+ public static void sortStrings(Vector values , int left, int right, Swapper swapper) {
+ int oleft= left;
+ int oright= right;
+ String mid= (String)values.elementAt((left + right) / 2);
+ do {
+ while (((String)(values.elementAt(left))).compareTo(mid) < 0)
+ left++;
+ while (mid.compareTo((String)(values.elementAt(right))) < 0)
+ right--;
+ if (left <= right) {
+ swapper.swap(values, left, right);
+ left++;
+ right--;
+ }
+ } while (left <= right);
+
+ if (oleft < right)
+ sortStrings(values, oleft, right, swapper);
+ if (left < oright)
+ sortStrings(values, left, oright, swapper);
+ }
}
diff --git a/test-runner/src/junit/runner/StandardTestSuiteLoader.java b/test-runner/src/junit/runner/StandardTestSuiteLoader.java
index bce7dec..381e684 100644
--- a/test-runner/src/junit/runner/StandardTestSuiteLoader.java
+++ b/test-runner/src/junit/runner/StandardTestSuiteLoader.java
@@ -5,16 +5,16 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class StandardTestSuiteLoader implements TestSuiteLoader {
- /**
- * Uses the system class loader to load the test class
- */
- public Class load(String suiteClassName) throws ClassNotFoundException {
- return Class.forName(suiteClassName);
- }
- /**
- * Uses the system class loader to load the test class
- */
- public Class reload(Class aClass) throws ClassNotFoundException {
- return aClass;
- }
+ /**
+ * Uses the system class loader to load the test class
+ */
+ public Class load(String suiteClassName) throws ClassNotFoundException {
+ return Class.forName(suiteClassName);
+ }
+ /**
+ * Uses the system class loader to load the test class
+ */
+ public Class reload(Class aClass) throws ClassNotFoundException {
+ return aClass;
+ }
}
diff --git a/test-runner/src/junit/runner/TestCaseClassLoader.java b/test-runner/src/junit/runner/TestCaseClassLoader.java
index 3a510c6..09eec7f 100644
--- a/test-runner/src/junit/runner/TestCaseClassLoader.java
+++ b/test-runner/src/junit/runner/TestCaseClassLoader.java
@@ -14,7 +14,7 @@
* loader. They will be shared across test runs.
* <p>
* The list of excluded package paths is specified in
- * a properties file "excluded.properties" that is located in
+ * a properties file "excluded.properties" that is located in
* the same place as the TestCaseClassLoader class.
* <p>
* <b>Known limitation:</b> the TestCaseClassLoader cannot load classes
@@ -22,204 +22,204 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class TestCaseClassLoader extends ClassLoader {
- /** scanned class path */
- private Vector fPathItems;
- /** default excluded paths */
- private String[] defaultExclusions= {
- "junit.framework.",
- "junit.extensions.",
- "junit.runner."
- };
- /** name of excluded properties file */
- static final String EXCLUDED_FILE= "excluded.properties";
- /** excluded paths */
- private Vector fExcluded;
-
- /**
- * Constructs a TestCaseLoader. It scans the class path
- * and the excluded package paths
- */
- public TestCaseClassLoader() {
- this(System.getProperty("java.class.path"));
- }
-
- /**
- * Constructs a TestCaseLoader. It scans the class path
- * and the excluded package paths
- */
- public TestCaseClassLoader(String classPath) {
- scanPath(classPath);
- readExcludedPackages();
- }
+ /** scanned class path */
+ private Vector fPathItems;
+ /** default excluded paths */
+ private String[] defaultExclusions= {
+ "junit.framework.",
+ "junit.extensions.",
+ "junit.runner."
+ };
+ /** name of excluded properties file */
+ static final String EXCLUDED_FILE= "excluded.properties";
+ /** excluded paths */
+ private Vector fExcluded;
- private void scanPath(String classPath) {
- String separator= System.getProperty("path.separator");
- fPathItems= new Vector(10);
- StringTokenizer st= new StringTokenizer(classPath, separator);
- while (st.hasMoreTokens()) {
- fPathItems.addElement(st.nextToken());
- }
- }
-
- public URL getResource(String name) {
- return ClassLoader.getSystemResource(name);
- }
-
- public InputStream getResourceAsStream(String name) {
- return ClassLoader.getSystemResourceAsStream(name);
- }
-
- public boolean isExcluded(String name) {
- for (int i= 0; i < fExcluded.size(); i++) {
- if (name.startsWith((String) fExcluded.elementAt(i))) {
- return true;
- }
- }
- return false;
- }
-
- public synchronized Class loadClass(String name, boolean resolve)
- throws ClassNotFoundException {
-
- Class c= findLoadedClass(name);
- if (c != null)
- return c;
- //
- // Delegate the loading of excluded classes to the
- // standard class loader.
- //
- if (isExcluded(name)) {
- try {
- c= findSystemClass(name);
- return c;
- } catch (ClassNotFoundException e) {
- // keep searching
- }
- }
- if (c == null) {
- byte[] data= lookupClassData(name);
- if (data == null)
- throw new ClassNotFoundException();
- c= defineClass(name, data, 0, data.length);
- }
- if (resolve)
- resolveClass(c);
- return c;
- }
-
- private byte[] lookupClassData(String className) throws ClassNotFoundException {
- byte[] data= null;
- for (int i= 0; i < fPathItems.size(); i++) {
- String path= (String) fPathItems.elementAt(i);
- String fileName= className.replace('.', '/')+".class";
- if (isJar(path)) {
- data= loadJarData(path, fileName);
- } else {
- data= loadFileData(path, fileName);
- }
- if (data != null)
- return data;
- }
- throw new ClassNotFoundException(className);
- }
-
- boolean isJar(String pathEntry) {
- return pathEntry.endsWith(".jar") ||
- pathEntry.endsWith(".apk") ||
- pathEntry.endsWith(".zip");
- }
+ /**
+ * Constructs a TestCaseLoader. It scans the class path
+ * and the excluded package paths
+ */
+ public TestCaseClassLoader() {
+ this(System.getProperty("java.class.path"));
+ }
- private byte[] loadFileData(String path, String fileName) {
- File file= new File(path, fileName);
- if (file.exists()) {
- return getClassData(file);
- }
- return null;
- }
-
- private byte[] getClassData(File f) {
- try {
- FileInputStream stream= new FileInputStream(f);
- ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
- byte[] b= new byte[1000];
- int n;
- while ((n= stream.read(b)) != -1)
- out.write(b, 0, n);
- stream.close();
- out.close();
- return out.toByteArray();
+ /**
+ * Constructs a TestCaseLoader. It scans the class path
+ * and the excluded package paths
+ */
+ public TestCaseClassLoader(String classPath) {
+ scanPath(classPath);
+ readExcludedPackages();
+ }
- } catch (IOException e) {
- }
- return null;
- }
+ private void scanPath(String classPath) {
+ String separator= System.getProperty("path.separator");
+ fPathItems= new Vector(10);
+ StringTokenizer st= new StringTokenizer(classPath, separator);
+ while (st.hasMoreTokens()) {
+ fPathItems.addElement(st.nextToken());
+ }
+ }
- private byte[] loadJarData(String path, String fileName) {
- ZipFile zipFile= null;
- InputStream stream= null;
- File archive= new File(path);
- if (!archive.exists())
- return null;
- try {
- zipFile= new ZipFile(archive);
- } catch(IOException io) {
- return null;
- }
- ZipEntry entry= zipFile.getEntry(fileName);
- if (entry == null)
- return null;
- int size= (int) entry.getSize();
- try {
- stream= zipFile.getInputStream(entry);
- byte[] data= new byte[size];
- int pos= 0;
- while (pos < size) {
- int n= stream.read(data, pos, data.length - pos);
- pos += n;
- }
- zipFile.close();
- return data;
- } catch (IOException e) {
- } finally {
- try {
- if (stream != null)
- stream.close();
- } catch (IOException e) {
- }
- }
- return null;
- }
-
- private void readExcludedPackages() {
- fExcluded= new Vector(10);
- for (int i= 0; i < defaultExclusions.length; i++)
- fExcluded.addElement(defaultExclusions[i]);
-
- InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE);
- if (is == null)
- return;
- Properties p= new Properties();
- try {
- p.load(is);
- }
- catch (IOException e) {
- return;
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- }
- }
- for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) {
- String key= (String)e.nextElement();
- if (key.startsWith("excluded.")) {
- String path= p.getProperty(key);
- path= path.trim();
- if (path.endsWith("*"))
- path= path.substring(0, path.length()-1);
- if (path.length() > 0)
- fExcluded.addElement(path);
- }
- }
- }
+ public URL getResource(String name) {
+ return ClassLoader.getSystemResource(name);
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return ClassLoader.getSystemResourceAsStream(name);
+ }
+
+ public boolean isExcluded(String name) {
+ for (int i= 0; i < fExcluded.size(); i++) {
+ if (name.startsWith((String) fExcluded.elementAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public synchronized Class loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+
+ Class c= findLoadedClass(name);
+ if (c != null)
+ return c;
+ //
+ // Delegate the loading of excluded classes to the
+ // standard class loader.
+ //
+ if (isExcluded(name)) {
+ try {
+ c= findSystemClass(name);
+ return c;
+ } catch (ClassNotFoundException e) {
+ // keep searching
+ }
+ }
+ if (c == null) {
+ byte[] data= lookupClassData(name);
+ if (data == null)
+ throw new ClassNotFoundException();
+ c= defineClass(name, data, 0, data.length);
+ }
+ if (resolve)
+ resolveClass(c);
+ return c;
+ }
+
+ private byte[] lookupClassData(String className) throws ClassNotFoundException {
+ byte[] data= null;
+ for (int i= 0; i < fPathItems.size(); i++) {
+ String path= (String) fPathItems.elementAt(i);
+ String fileName= className.replace('.', '/')+".class";
+ if (isJar(path)) {
+ data= loadJarData(path, fileName);
+ } else {
+ data= loadFileData(path, fileName);
+ }
+ if (data != null)
+ return data;
+ }
+ throw new ClassNotFoundException(className);
+ }
+
+ boolean isJar(String pathEntry) {
+ return pathEntry.endsWith(".jar") ||
+ pathEntry.endsWith(".apk") ||
+ pathEntry.endsWith(".zip");
+ }
+
+ private byte[] loadFileData(String path, String fileName) {
+ File file= new File(path, fileName);
+ if (file.exists()) {
+ return getClassData(file);
+ }
+ return null;
+ }
+
+ private byte[] getClassData(File f) {
+ try {
+ FileInputStream stream= new FileInputStream(f);
+ ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
+ byte[] b= new byte[1000];
+ int n;
+ while ((n= stream.read(b)) != -1)
+ out.write(b, 0, n);
+ stream.close();
+ out.close();
+ return out.toByteArray();
+
+ } catch (IOException e) {
+ }
+ return null;
+ }
+
+ private byte[] loadJarData(String path, String fileName) {
+ ZipFile zipFile= null;
+ InputStream stream= null;
+ File archive= new File(path);
+ if (!archive.exists())
+ return null;
+ try {
+ zipFile= new ZipFile(archive);
+ } catch(IOException io) {
+ return null;
+ }
+ ZipEntry entry= zipFile.getEntry(fileName);
+ if (entry == null)
+ return null;
+ int size= (int) entry.getSize();
+ try {
+ stream= zipFile.getInputStream(entry);
+ byte[] data= new byte[size];
+ int pos= 0;
+ while (pos < size) {
+ int n= stream.read(data, pos, data.length - pos);
+ pos += n;
+ }
+ zipFile.close();
+ return data;
+ } catch (IOException e) {
+ } finally {
+ try {
+ if (stream != null)
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ return null;
+ }
+
+ private void readExcludedPackages() {
+ fExcluded= new Vector(10);
+ for (int i= 0; i < defaultExclusions.length; i++)
+ fExcluded.addElement(defaultExclusions[i]);
+
+ InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE);
+ if (is == null)
+ return;
+ Properties p= new Properties();
+ try {
+ p.load(is);
+ }
+ catch (IOException e) {
+ return;
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) {
+ String key= (String)e.nextElement();
+ if (key.startsWith("excluded.")) {
+ String path= p.getProperty(key);
+ path= path.trim();
+ if (path.endsWith("*"))
+ path= path.substring(0, path.length()-1);
+ if (path.length() > 0)
+ fExcluded.addElement(path);
+ }
+ }
+ }
}
diff --git a/test-runner/src/junit/runner/TestCollector.java b/test-runner/src/junit/runner/TestCollector.java
index 208dccd..3ac9d9e 100644
--- a/test-runner/src/junit/runner/TestCollector.java
+++ b/test-runner/src/junit/runner/TestCollector.java
@@ -5,13 +5,13 @@
/**
* Collects Test class names to be presented
- * by the TestSelector.
+ * by the TestSelector.
* @see TestSelector
* {@hide} - Not needed for 1.0 SDK
*/
public interface TestCollector {
- /**
- * Returns an enumeration of Strings with qualified class names
- */
- public Enumeration collectTests();
+ /**
+ * Returns an enumeration of Strings with qualified class names
+ */
+ public Enumeration collectTests();
}
diff --git a/test-runner/src/junit/runner/TestRunListener.java b/test-runner/src/junit/runner/TestRunListener.java
index 0e95819..0410f0c 100644
--- a/test-runner/src/junit/runner/TestRunListener.java
+++ b/test-runner/src/junit/runner/TestRunListener.java
@@ -6,15 +6,15 @@
* making it suitable for remote test execution.
* {@hide} - Not needed for 1.0 SDK
*/
- public interface TestRunListener {
- /* test status constants*/
- public static final int STATUS_ERROR= 1;
- public static final int STATUS_FAILURE= 2;
+public interface TestRunListener {
+ /* test status constants*/
+ public static final int STATUS_ERROR= 1;
+ public static final int STATUS_FAILURE= 2;
- public void testRunStarted(String testSuiteName, int testCount);
- public void testRunEnded(long elapsedTime);
- public void testRunStopped(long elapsedTime);
- public void testStarted(String testName);
- public void testEnded(String testName);
- public void testFailed(int status, String testName, String trace);
+ public void testRunStarted(String testSuiteName, int testCount);
+ public void testRunEnded(long elapsedTime);
+ public void testRunStopped(long elapsedTime);
+ public void testStarted(String testName);
+ public void testEnded(String testName);
+ public void testFailed(int status, String testName, String trace);
}
diff --git a/test-runner/src/junit/runner/TestSuiteLoader.java b/test-runner/src/junit/runner/TestSuiteLoader.java
index 39a4cf7..581ea23 100644
--- a/test-runner/src/junit/runner/TestSuiteLoader.java
+++ b/test-runner/src/junit/runner/TestSuiteLoader.java
@@ -4,6 +4,6 @@
* An interface to define how a test suite should be loaded.
*/
public interface TestSuiteLoader {
- abstract public Class load(String suiteClassName) throws ClassNotFoundException;
- abstract public Class reload(Class aClass) throws ClassNotFoundException;
+ abstract public Class load(String suiteClassName) throws ClassNotFoundException;
+ abstract public Class reload(Class aClass) throws ClassNotFoundException;
}
diff --git a/test-runner/src/junit/runner/Version.java b/test-runner/src/junit/runner/Version.java
index b4541ab..4a6dc85 100644
--- a/test-runner/src/junit/runner/Version.java
+++ b/test-runner/src/junit/runner/Version.java
@@ -4,11 +4,17 @@
* This class defines the current version of JUnit
*/
public class Version {
- private Version() {
- // don't instantiate
- }
+ private Version() {
+ // don't instantiate
+ }
- public static String id() {
- return "3.8.1";
- }
+ public static String id() {
+ return "4.10";
+ }
+
+ // android-changed
+ /** @hide - not needed for public API */
+ public static void main(String[] args) {
+ System.out.println(id());
+ }
}
diff --git a/test-runner/src/junit/textui/ResultPrinter.java b/test-runner/src/junit/textui/ResultPrinter.java
index 5c97112..4b26558 100644
--- a/test-runner/src/junit/textui/ResultPrinter.java
+++ b/test-runner/src/junit/textui/ResultPrinter.java
@@ -14,129 +14,129 @@
import junit.runner.BaseTestRunner;
public class ResultPrinter implements TestListener {
- PrintStream fWriter;
- int fColumn= 0;
-
- public ResultPrinter(PrintStream writer) {
- fWriter= writer;
- }
-
- /* API for use by textui.TestRunner
- */
+ PrintStream fWriter;
+ int fColumn= 0;
- synchronized void print(TestResult result, long runTime) {
- printHeader(runTime);
- printErrors(result);
- printFailures(result);
- printFooter(result);
- }
+ public ResultPrinter(PrintStream writer) {
+ fWriter= writer;
+ }
- void printWaitPrompt() {
- getWriter().println();
- getWriter().println("<RETURN> to continue");
- }
-
- /* Internal methods
- */
+ /* API for use by textui.TestRunner
+ */
- protected void printHeader(long runTime) {
- getWriter().println();
- getWriter().println("Time: "+elapsedTimeAsString(runTime));
- }
-
- protected void printErrors(TestResult result) {
- printDefects(result.errors(), result.errorCount(), "error");
- }
-
- protected void printFailures(TestResult result) {
- printDefects(result.failures(), result.failureCount(), "failure");
- }
-
- protected void printDefects(Enumeration booBoos, int count, String type) {
- if (count == 0) return;
- if (count == 1)
- getWriter().println("There was " + count + " " + type + ":");
- else
- getWriter().println("There were " + count + " " + type + "s:");
- for (int i= 1; booBoos.hasMoreElements(); i++) {
- printDefect((TestFailure) booBoos.nextElement(), i);
- }
- }
-
- public void printDefect(TestFailure booBoo, int count) { // only public for testing purposes
- printDefectHeader(booBoo, count);
- printDefectTrace(booBoo);
- }
+ synchronized void print(TestResult result, long runTime) {
+ printHeader(runTime);
+ printErrors(result);
+ printFailures(result);
+ printFooter(result);
+ }
- protected void printDefectHeader(TestFailure booBoo, int count) {
- // I feel like making this a println, then adding a line giving the throwable a chance to print something
- // before we get to the stack trace.
- getWriter().print(count + ") " + booBoo.failedTest());
- }
+ void printWaitPrompt() {
+ getWriter().println();
+ getWriter().println("<RETURN> to continue");
+ }
- protected void printDefectTrace(TestFailure booBoo) {
- getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace()));
- }
+ /* Internal methods
+ */
- protected void printFooter(TestResult result) {
- if (result.wasSuccessful()) {
- getWriter().println();
- getWriter().print("OK");
- getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")");
+ protected void printHeader(long runTime) {
+ getWriter().println();
+ getWriter().println("Time: "+elapsedTimeAsString(runTime));
+ }
- } else {
- getWriter().println();
- getWriter().println("FAILURES!!!");
- getWriter().println("Tests run: "+result.runCount()+
- ", Failures: "+result.failureCount()+
- ", Errors: "+result.errorCount());
- }
- getWriter().println();
- }
+ protected void printErrors(TestResult result) {
+ printDefects(result.errors(), result.errorCount(), "error");
+ }
+
+ protected void printFailures(TestResult result) {
+ printDefects(result.failures(), result.failureCount(), "failure");
+ }
+
+ protected void printDefects(Enumeration<TestFailure> booBoos, int count, String type) {
+ if (count == 0) return;
+ if (count == 1)
+ getWriter().println("There was " + count + " " + type + ":");
+ else
+ getWriter().println("There were " + count + " " + type + "s:");
+ for (int i= 1; booBoos.hasMoreElements(); i++) {
+ printDefect(booBoos.nextElement(), i);
+ }
+ }
+
+ public void printDefect(TestFailure booBoo, int count) { // only public for testing purposes
+ printDefectHeader(booBoo, count);
+ printDefectTrace(booBoo);
+ }
+
+ protected void printDefectHeader(TestFailure booBoo, int count) {
+ // I feel like making this a println, then adding a line giving the throwable a chance to print something
+ // before we get to the stack trace.
+ getWriter().print(count + ") " + booBoo.failedTest());
+ }
+
+ protected void printDefectTrace(TestFailure booBoo) {
+ getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace()));
+ }
+
+ protected void printFooter(TestResult result) {
+ if (result.wasSuccessful()) {
+ getWriter().println();
+ getWriter().print("OK");
+ getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")");
+
+ } else {
+ getWriter().println();
+ getWriter().println("FAILURES!!!");
+ getWriter().println("Tests run: "+result.runCount()+
+ ", Failures: "+result.failureCount()+
+ ", Errors: "+result.errorCount());
+ }
+ getWriter().println();
+ }
- /**
- * Returns the formatted string of the elapsed time.
- * Duplicated from BaseTestRunner. Fix it.
- */
- protected String elapsedTimeAsString(long runTime) {
- // The following line was altered for compatibility with
- // Android libraries.
- return Double.toString((double)runTime/1000);
- }
+ /**
+ * Returns the formatted string of the elapsed time.
+ * Duplicated from BaseTestRunner. Fix it.
+ */
+ protected String elapsedTimeAsString(long runTime) {
+ // The following line was altered for compatibility with
+ // Android libraries.
+ return Double.toString((double)runTime/1000);
+ }
- public PrintStream getWriter() {
- return fWriter;
- }
- /**
- * @see junit.framework.TestListener#addError(Test, Throwable)
- */
- public void addError(Test test, Throwable t) {
- getWriter().print("E");
- }
+ public PrintStream getWriter() {
+ return fWriter;
+ }
+ /**
+ * @see junit.framework.TestListener#addError(Test, Throwable)
+ */
+ public void addError(Test test, Throwable t) {
+ getWriter().print("E");
+ }
- /**
- * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
- */
- public void addFailure(Test test, AssertionFailedError t) {
- getWriter().print("F");
- }
+ /**
+ * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
+ */
+ public void addFailure(Test test, AssertionFailedError t) {
+ getWriter().print("F");
+ }
- /**
- * @see junit.framework.TestListener#endTest(Test)
- */
- public void endTest(Test test) {
- }
+ /**
+ * @see junit.framework.TestListener#endTest(Test)
+ */
+ public void endTest(Test test) {
+ }
- /**
- * @see junit.framework.TestListener#startTest(Test)
- */
- public void startTest(Test test) {
- getWriter().print(".");
- if (fColumn++ >= 40) {
- getWriter().println();
- fColumn= 0;
- }
- }
+ /**
+ * @see junit.framework.TestListener#startTest(Test)
+ */
+ public void startTest(Test test) {
+ getWriter().print(".");
+ if (fColumn++ >= 40) {
+ getWriter().println();
+ fColumn= 0;
+ }
+ }
}
diff --git a/test-runner/src/junit/textui/TestRunner.java b/test-runner/src/junit/textui/TestRunner.java
index 8bdc325..e955e0e 100644
--- a/test-runner/src/junit/textui/TestRunner.java
+++ b/test-runner/src/junit/textui/TestRunner.java
@@ -3,187 +3,201 @@
import java.io.PrintStream;
-import junit.framework.*;
-import junit.runner.*;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+import junit.runner.BaseTestRunner;
+import junit.runner.Version;
/**
* A command line based tool to run tests.
* <pre>
* java junit.textui.TestRunner [-wait] TestCaseClass
* </pre>
- * TestRunner expects the name of a TestCase class as argument.
- * If this class defines a static <code>suite</code> method it
- * will be invoked and the returned test is run. Otherwise all
+ *
+ * <p>TestRunner expects the name of a TestCase class as argument.
+ * If this class defines a static <code>suite</code> method it
+ * will be invoked and the returned test is run. Otherwise all
* the methods starting with "test" having no arguments are run.
* <p>
* When the wait command line argument is given TestRunner
* waits until the users types RETURN.
* <p>
* TestRunner prints a trace as the tests are executed followed by a
- * summary at the end.
+ * summary at the end.
*/
public class TestRunner extends BaseTestRunner {
- private ResultPrinter fPrinter;
-
- public static final int SUCCESS_EXIT= 0;
- public static final int FAILURE_EXIT= 1;
- public static final int EXCEPTION_EXIT= 2;
+ private ResultPrinter fPrinter;
- /**
- * Constructs a TestRunner.
- */
- public TestRunner() {
- this(System.out);
- }
+ public static final int SUCCESS_EXIT= 0;
+ public static final int FAILURE_EXIT= 1;
+ public static final int EXCEPTION_EXIT= 2;
- /**
- * Constructs a TestRunner using the given stream for all the output
- */
- public TestRunner(PrintStream writer) {
- this(new ResultPrinter(writer));
- }
-
- /**
- * Constructs a TestRunner using the given ResultPrinter all the output
- */
- public TestRunner(ResultPrinter printer) {
- fPrinter= printer;
- }
-
- /**
- * Runs a suite extracted from a TestCase subclass.
- */
- static public void run(Class testClass) {
- run(new TestSuite(testClass));
- }
+ /**
+ * Constructs a TestRunner.
+ */
+ public TestRunner() {
+ this(System.out);
+ }
- /**
- * Runs a single test and collects its results.
- * This method can be used to start a test run
- * from your program.
- * <pre>
- * public static void main (String[] args) {
- * test.textui.TestRunner.run(suite());
- * }
- * </pre>
- */
- static public TestResult run(Test test) {
- TestRunner runner= new TestRunner();
- return runner.doRun(test);
- }
+ /**
+ * Constructs a TestRunner using the given stream for all the output
+ */
+ public TestRunner(PrintStream writer) {
+ this(new ResultPrinter(writer));
+ }
- /**
- * Runs a single test and waits until the user
- * types RETURN.
- */
- static public void runAndWait(Test suite) {
- TestRunner aTestRunner= new TestRunner();
- aTestRunner.doRun(suite, true);
- }
+ /**
+ * Constructs a TestRunner using the given ResultPrinter all the output
+ */
+ public TestRunner(ResultPrinter printer) {
+ fPrinter= printer;
+ }
- /**
- * Always use the StandardTestSuiteLoader. Overridden from
- * BaseTestRunner.
- */
- public TestSuiteLoader getLoader() {
- return new StandardTestSuiteLoader();
- }
+ /**
+ * Runs a suite extracted from a TestCase subclass.
+ */
+ static public void run(Class<? extends TestCase> testClass) {
+ run(new TestSuite(testClass));
+ }
- public void testFailed(int status, Test test, Throwable t) {
- }
-
- public void testStarted(String testName) {
- }
-
- public void testEnded(String testName) {
- }
+ /**
+ * Runs a single test and collects its results.
+ * This method can be used to start a test run
+ * from your program.
+ * <pre>
+ * public static void main (String[] args) {
+ * test.textui.TestRunner.run(suite());
+ * }
+ * </pre>
+ */
+ static public TestResult run(Test test) {
+ TestRunner runner= new TestRunner();
+ return runner.doRun(test);
+ }
- /**
- * Creates the TestResult to be used for the test run.
- */
- protected TestResult createTestResult() {
- return new TestResult();
- }
-
- public TestResult doRun(Test test) {
- return doRun(test, false);
- }
-
- public TestResult doRun(Test suite, boolean wait) {
- TestResult result= createTestResult();
- result.addListener(fPrinter);
- long startTime= System.currentTimeMillis();
- suite.run(result);
- long endTime= System.currentTimeMillis();
- long runTime= endTime-startTime;
- fPrinter.print(result, runTime);
+ /**
+ * Runs a single test and waits until the user
+ * types RETURN.
+ */
+ static public void runAndWait(Test suite) {
+ TestRunner aTestRunner= new TestRunner();
+ aTestRunner.doRun(suite, true);
+ }
- pause(wait);
- return result;
- }
+ @Override
+ public void testFailed(int status, Test test, Throwable t) {
+ }
- protected void pause(boolean wait) {
- if (!wait) return;
- fPrinter.printWaitPrompt();
- try {
- System.in.read();
- }
- catch(Exception e) {
- }
- }
-
- public static void main(String args[]) {
- TestRunner aTestRunner= new TestRunner();
- try {
- TestResult r= aTestRunner.start(args);
- if (!r.wasSuccessful())
- System.exit(FAILURE_EXIT);
- System.exit(SUCCESS_EXIT);
- } catch(Exception e) {
- System.err.println(e.getMessage());
- System.exit(EXCEPTION_EXIT);
- }
- }
+ @Override
+ public void testStarted(String testName) {
+ }
- /**
- * Starts a test run. Analyzes the command line arguments
- * and runs the given test suite.
- */
- protected TestResult start(String args[]) throws Exception {
- String testCase= "";
- boolean wait= false;
-
- for (int i= 0; i < args.length; i++) {
- if (args[i].equals("-wait"))
- wait= true;
- else if (args[i].equals("-c"))
- testCase= extractClassName(args[++i]);
- else if (args[i].equals("-v"))
- System.err.println("JUnit "+Version.id()+" by Kent Beck and Erich Gamma");
- else
- testCase= args[i];
- }
-
- if (testCase.equals(""))
- throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
+ @Override
+ public void testEnded(String testName) {
+ }
- try {
- Test suite= getTest(testCase);
- return doRun(suite, wait);
- }
- catch(Exception e) {
- throw new Exception("Could not create and run test suite: "+e);
- }
- }
-
- protected void runFailed(String message) {
- System.err.println(message);
- System.exit(FAILURE_EXIT);
- }
-
- public void setPrinter(ResultPrinter printer) {
- fPrinter= printer;
- }
-
-
+ /**
+ * Creates the TestResult to be used for the test run.
+ */
+ protected TestResult createTestResult() {
+ return new TestResult();
+ }
+
+ public TestResult doRun(Test test) {
+ return doRun(test, false);
+ }
+
+ public TestResult doRun(Test suite, boolean wait) {
+ TestResult result= createTestResult();
+ result.addListener(fPrinter);
+ long startTime= System.currentTimeMillis();
+ suite.run(result);
+ long endTime= System.currentTimeMillis();
+ long runTime= endTime-startTime;
+ fPrinter.print(result, runTime);
+
+ pause(wait);
+ return result;
+ }
+
+ protected void pause(boolean wait) {
+ if (!wait) return;
+ fPrinter.printWaitPrompt();
+ try {
+ System.in.read();
+ }
+ catch(Exception e) {
+ }
+ }
+
+ public static void main(String args[]) {
+ TestRunner aTestRunner= new TestRunner();
+ try {
+ TestResult r= aTestRunner.start(args);
+ if (!r.wasSuccessful())
+ System.exit(FAILURE_EXIT);
+ System.exit(SUCCESS_EXIT);
+ } catch(Exception e) {
+ System.err.println(e.getMessage());
+ System.exit(EXCEPTION_EXIT);
+ }
+ }
+
+ /**
+ * Starts a test run. Analyzes the command line arguments
+ * and runs the given test suite.
+ */
+ public TestResult start(String args[]) throws Exception {
+ String testCase= "";
+ String method= "";
+ boolean wait= false;
+
+ for (int i= 0; i < args.length; i++) {
+ if (args[i].equals("-wait"))
+ wait= true;
+ else if (args[i].equals("-c"))
+ testCase= extractClassName(args[++i]);
+ else if (args[i].equals("-m")) {
+ String arg= args[++i];
+ int lastIndex= arg.lastIndexOf('.');
+ testCase= arg.substring(0, lastIndex);
+ method= arg.substring(lastIndex + 1);
+ } else if (args[i].equals("-v"))
+ System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma");
+ else
+ testCase= args[i];
+ }
+
+ if (testCase.equals(""))
+ throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
+
+ try {
+ if (!method.equals(""))
+ return runSingleMethod(testCase, method, wait);
+ Test suite= getTest(testCase);
+ return doRun(suite, wait);
+ } catch (Exception e) {
+ throw new Exception("Could not create and run test suite: " + e);
+ }
+ }
+
+ protected TestResult runSingleMethod(String testCase, String method, boolean wait) throws Exception {
+ Class<? extends TestCase> testClass= loadSuiteClass(testCase).asSubclass(TestCase.class);
+ Test test= TestSuite.createTest(testClass, method);
+ return doRun(test, wait);
+ }
+
+ @Override
+ protected void runFailed(String message) {
+ System.err.println(message);
+ System.exit(FAILURE_EXIT);
+ }
+
+ public void setPrinter(ResultPrinter printer) {
+ fPrinter= printer;
+ }
+
+
}