summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/accounts/AccountAndUser.java49
-rw-r--r--core/java/android/accounts/AccountManagerService.java32
-rw-r--r--core/java/android/animation/AnimatorSet.java14
-rw-r--r--core/java/android/app/Activity.java49
-rw-r--r--core/java/android/app/ActivityManagerNative.java20
-rw-r--r--core/java/android/app/ActivityOptions.java22
-rw-r--r--core/java/android/app/ActivityThread.java30
-rw-r--r--core/java/android/app/ContextImpl.java6
-rw-r--r--core/java/android/app/Fragment.java2
-rw-r--r--core/java/android/app/IActivityManager.java4
-rw-r--r--core/java/android/app/Instrumentation.java8
-rw-r--r--core/java/android/app/Service.java4
-rw-r--r--core/java/android/content/ContentProvider.java2
-rw-r--r--core/java/android/content/Intent.java20
-rw-r--r--core/java/android/content/SyncManager.java105
-rw-r--r--core/java/android/content/SyncStorageEngine.java22
-rw-r--r--core/java/android/content/pm/PackageInfo.java2
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/content/pm/PackageParser.java4
-rwxr-xr-xcore/java/android/content/res/Resources.java51
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl8
-rwxr-xr-xcore/java/android/hardware/input/InputManager.java363
-rw-r--r--core/java/android/hardware/input/KeyboardLayout.aidl19
-rw-r--r--core/java/android/hardware/input/KeyboardLayout.java91
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java4
-rw-r--r--core/java/android/net/NetworkQuotaInfo.java6
-rw-r--r--core/java/android/net/NetworkState.java6
-rw-r--r--core/java/android/net/NetworkStats.java9
-rw-r--r--core/java/android/net/NetworkStatsHistory.java6
-rw-r--r--core/java/android/net/nsd/DnsSdServiceInfo.java47
-rw-r--r--core/java/android/net/nsd/NsdManager.java50
-rw-r--r--core/java/android/provider/BrowserContract.java191
-rw-r--r--core/java/android/provider/ContactsContract.java8
-rw-r--r--core/java/android/service/dreams/Dream.java392
-rw-r--r--core/java/android/service/dreams/DreamManagerService.java182
-rw-r--r--core/java/android/service/dreams/IDreamManager.aidl30
-rw-r--r--core/java/android/service/dreams/IDreamService.aidl24
-rw-r--r--core/java/android/text/SpannableStringBuilder.java388
-rw-r--r--core/java/android/view/HardwareRenderer.java53
-rwxr-xr-xcore/java/android/view/InputDevice.java112
-rw-r--r--core/java/android/view/KeyCharacterMap.java87
-rw-r--r--core/java/android/view/MotionEvent.java78
-rw-r--r--core/java/android/view/TextureView.java17
-rw-r--r--core/java/android/view/View.java52
-rw-r--r--core/java/android/view/ViewRootImpl.java27
-rw-r--r--core/java/android/view/WindowManager.java6
-rw-r--r--core/java/android/view/WindowManagerImpl.java31
-rw-r--r--core/java/android/view/WindowManagerPolicy.java27
-rw-r--r--core/java/android/webkit/AutoCompletePopup.java8
-rw-r--r--core/java/android/webkit/DeviceMotionAndOrientationManager.java11
-rwxr-xr-xcore/java/android/webkit/DeviceMotionService.java2
-rwxr-xr-xcore/java/android/webkit/DeviceOrientationService.java2
-rw-r--r--core/java/android/webkit/FindActionModeCallback.java4
-rw-r--r--core/java/android/webkit/WebView.java29
-rw-r--r--core/java/android/webkit/WebViewClassic.java1318
-rw-r--r--core/java/android/webkit/WebViewCore.java212
-rw-r--r--core/java/android/webkit/WebViewInputDispatcher.java1157
-rwxr-xr-xcore/java/android/widget/AppSecurityPermissions.java6
-rw-r--r--core/java/android/widget/CheckBox.java12
-rw-r--r--core/java/android/widget/Chronometer.java1
-rw-r--r--core/java/android/widget/HorizontalScrollView.java34
-rw-r--r--core/java/android/widget/NumberPicker.java11
-rw-r--r--core/java/android/widget/ProgressBar.java123
-rw-r--r--core/java/android/widget/ScrollView.java38
-rw-r--r--core/java/android/widget/SpellChecker.java13
-rw-r--r--core/java/android/widget/Spinner.java2
-rw-r--r--core/java/android/widget/Switch.java11
67 files changed, 3726 insertions, 2040 deletions
diff --git a/core/java/android/accounts/AccountAndUser.java b/core/java/android/accounts/AccountAndUser.java
new file mode 100644
index 0000000..04157cc
--- /dev/null
+++ b/core/java/android/accounts/AccountAndUser.java
@@ -0,0 +1,49 @@
+/*
+ * 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.accounts;
+
+/**
+ * Used to store the Account and the UserId this account is associated with.
+ *
+ * @hide
+ */
+public class AccountAndUser {
+ public Account account;
+ public int userId;
+
+ public AccountAndUser(Account account, int userId) {
+ this.account = account;
+ this.userId = userId;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AccountAndUser)) return false;
+ final AccountAndUser other = (AccountAndUser) o;
+ return this.account.equals(other.account)
+ && this.userId == other.userId;
+ }
+
+ @Override
+ public int hashCode() {
+ return account.hashCode() + userId;
+ }
+
+ public String toString() {
+ return account.toString() + " u" + userId;
+ }
+}
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 197c1bd..2b643c2 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -1488,6 +1488,31 @@ public class AccountManagerService
}
}
+ /**
+ * Returns all the accounts qualified by user.
+ * @hide
+ */
+ public AccountAndUser[] getAllAccounts() {
+ ArrayList<AccountAndUser> allAccounts = new ArrayList<AccountAndUser>();
+ List<UserInfo> users = getAllUsers();
+ if (users == null) return new AccountAndUser[0];
+
+ synchronized(mUsers) {
+ for (UserInfo user : users) {
+ UserAccounts userAccounts = getUserAccounts(user.id);
+ if (userAccounts == null) continue;
+ synchronized (userAccounts.cacheLock) {
+ Account[] accounts = getAccountsFromCacheLocked(userAccounts, null);
+ for (int a = 0; a < accounts.length; a++) {
+ allAccounts.add(new AccountAndUser(accounts[a], user.id));
+ }
+ }
+ }
+ }
+ AccountAndUser[] accountsArray = new AccountAndUser[allAccounts.size()];
+ return allAccounts.toArray(accountsArray);
+ }
+
public Account[] getAccounts(String type) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAccounts: accountType " + type
@@ -1844,9 +1869,12 @@ public class AccountManagerService
File systemDir = Environment.getSystemSecureDirectory();
File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME);
if (userId == 0) {
- // Migrate old file, if it exists, to the new location
+ // Migrate old file, if it exists, to the new location.
+ // Make sure the new file doesn't already exist. A dummy file could have been
+ // accidentally created in the old location, causing the new one to become corrupted
+ // as well.
File oldFile = new File(systemDir, DATABASE_NAME);
- if (oldFile.exists()) {
+ if (oldFile.exists() && !databaseFile.exists()) {
// Check for use directory; create if it doesn't exist, else renameTo will fail
File userDir = new File(systemDir, "users/" + userId);
if (!userDir.exists()) {
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index c5a4171..f9fa444 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -420,11 +420,7 @@ public final class AnimatorSet extends Animator {
if (duration < 0) {
throw new IllegalArgumentException("duration must be a value of zero or greater");
}
- for (Node node : mNodes) {
- // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
- // insert "play-after" delays
- node.animation.setDuration(duration);
- }
+ // Just record the value for now - it will be used later when the AnimatorSet starts
mDuration = duration;
return this;
}
@@ -456,6 +452,14 @@ public final class AnimatorSet extends Animator {
mTerminated = false;
mStarted = true;
+ if (mDuration >= 0) {
+ // If the duration was set on this AnimatorSet, pass it along to all child animations
+ for (Node node : mNodes) {
+ // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
+ // insert "play-after" delays
+ node.animation.setDuration(mDuration);
+ }
+ }
// First, sort the nodes (if necessary). This will ensure that sortedNodes
// contains the animation nodes in the correct order.
sortNodes();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 3e123ba..1c820dc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -55,6 +55,7 @@ import android.text.method.TextKeyListener;
import android.util.AttributeSet;
import android.util.EventLog;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.ActionMode;
import android.view.ContextMenu;
@@ -642,6 +643,7 @@ public class Activity extends ContextThemeWrapper
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2 {
private static final String TAG = "Activity";
+ private static final boolean DEBUG_LIFECYCLE = false;
/** Standard activity result: operation canceled. */
public static final int RESULT_CANCELED = 0;
@@ -865,6 +867,7 @@ public class Activity extends ContextThemeWrapper
* @see #onPostCreate
*/
protected void onCreate(Bundle savedInstanceState) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
}
@@ -1013,6 +1016,7 @@ public class Activity extends ContextThemeWrapper
* @see #onResume
*/
protected void onStart() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
mCalled = true;
if (!mLoadersStarted) {
@@ -1073,6 +1077,7 @@ public class Activity extends ContextThemeWrapper
* @see #onPause
*/
protected void onResume() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
getApplication().dispatchActivityResumed(this);
mCalled = true;
}
@@ -1131,6 +1136,7 @@ public class Activity extends ContextThemeWrapper
final void performSaveInstanceState(Bundle outState) {
onSaveInstanceState(outState);
saveManagedDialogs(outState);
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
}
/**
@@ -1261,6 +1267,7 @@ public class Activity extends ContextThemeWrapper
* @see #onStop
*/
protected void onPause() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
getApplication().dispatchActivityPaused(this);
mCalled = true;
}
@@ -1347,6 +1354,7 @@ public class Activity extends ContextThemeWrapper
* @see #onDestroy
*/
protected void onStop() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
getApplication().dispatchActivityStopped(this);
mCalled = true;
@@ -1381,6 +1389,7 @@ public class Activity extends ContextThemeWrapper
* @see #isFinishing
*/
protected void onDestroy() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
mCalled = true;
// dismiss any dialogs we are managing.
@@ -1432,6 +1441,7 @@ public class Activity extends ContextThemeWrapper
* @param newConfig The new device configuration.
*/
public void onConfigurationChanged(Configuration newConfig) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onConfigurationChanged " + this + ": " + newConfig);
mCalled = true;
mFragments.dispatchConfigurationChanged(newConfig);
@@ -1613,11 +1623,13 @@ public class Activity extends ContextThemeWrapper
}
public void onLowMemory() {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this);
mCalled = true;
mFragments.dispatchLowMemory();
}
public void onTrimMemory(int level) {
+ if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level);
mCalled = true;
mFragments.dispatchTrimMemory(level);
}
@@ -2522,7 +2534,19 @@ public class Activity extends ContextThemeWrapper
if (onOptionsItemSelected(item)) {
return true;
}
- return mFragments.dispatchOptionsItemSelected(item);
+ if (mFragments.dispatchOptionsItemSelected(item)) {
+ return true;
+ }
+ if (item.getItemId() == android.R.id.home && mActionBar != null &&
+ (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ if (mParent == null) {
+ onNavigateUp();
+ } else {
+ mParent.onNavigateUpFromChild(this);
+ }
+ return true;
+ }
+ return false;
case Window.FEATURE_CONTEXT_MENU:
EventLog.writeEvent(50000, 1, item.getTitleCondensed());
@@ -2654,15 +2678,6 @@ public class Activity extends ContextThemeWrapper
if (mParent != null) {
return mParent.onOptionsItemSelected(item);
}
- if (item.getItemId() == android.R.id.home && mActionBar != null &&
- (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
- if (mParent == null) {
- onNavigateUp();
- } else {
- mParent.onNavigateUpFromChild(this);
- }
- return true;
- }
return false;
}
@@ -4641,7 +4656,7 @@ public class Activity extends ContextThemeWrapper
/**
* Print the Activity's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity <activity_component_name>".
+ * you run "adb shell dumpsys activity &lt;activity_component_name&gt;".
*
* @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
@@ -4865,11 +4880,19 @@ public class Activity extends ContextThemeWrapper
* Obtain an {@link Intent} that will launch an explicit target activity specified by
* this activity's logical parent. The logical parent is named in the application's manifest
* by the {@link android.R.attr#parentActivityName parentActivityName} attribute.
+ * Activity subclasses may override this method to modify the Intent returned by
+ * super.getParentActivityIntent() or to implement a different mechanism of retrieving
+ * the parent intent entirely.
*
- * @return a new Intent targeting the defined parent of this activity
+ * @return a new Intent targeting the defined parent of this activity or null if
+ * there is no valid parent.
*/
public Intent getParentActivityIntent() {
- return new Intent().setClassName(this, mActivityInfo.parentActivityName);
+ final String parentName = mActivityInfo.parentActivityName;
+ if (TextUtils.isEmpty(parentName)) {
+ return null;
+ }
+ return new Intent().setClassName(this, parentName);
}
// ------------------ Internal API ------------------
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index a3fdf3e..7e1589f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1000,7 +1000,7 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
}
return true;
}
-
+
case GOING_TO_SLEEP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
goingToSleep();
@@ -1015,6 +1015,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case SET_LOCK_SCREEN_SHOWN_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ setLockScreenShown(data.readInt() != 0);
+ reply.writeNoException();
+ return true;
+ }
+
case SET_DEBUG_APP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String pn = data.readString();
@@ -2912,6 +2919,17 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
+ public void setLockScreenShown(boolean shown) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(shown ? 1 : 0);
+ mRemote.transact(SET_LOCK_SCREEN_SHOWN_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
public void setDebugApp(
String packageName, boolean waitForDebugger, boolean persistent)
throws RemoteException
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index c637df0..c3cceaf 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -21,7 +21,6 @@ import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.IRemoteCallback;
-import android.os.Message;
import android.os.RemoteException;
import android.view.View;
@@ -121,6 +120,7 @@ public class ActivityOptions {
/**
* Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
* to find out when the given animation has started running.
+ * @hide
*/
public interface OnAnimationStartedListener {
void onAnimationStarted();
@@ -137,11 +137,31 @@ public class ActivityOptions {
* of the animation.
* @param startX The x starting location of the bitmap, in screen coordiantes.
* @param startY The y starting location of the bitmap, in screen coordinates.
+ * @return Returns a new ActivityOptions object that you can use to
+ * supply these options as the options Bundle when starting an activity.
+ */
+ public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+ Bitmap thumbnail, int startX, int startY) {
+ return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, null);
+ }
+
+ /**
+ * Create an ActivityOptions specifying an animation where a thumbnail
+ * is scaled from a given position to the new activity window that is
+ * being started.
+ *
+ * @param source The View that this thumbnail is animating from. This
+ * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+ * @param thumbnail The bitmap that will be shown as the initial thumbnail
+ * of the animation.
+ * @param startX The x starting location of the bitmap, in screen coordiantes.
+ * @param startY The y starting location of the bitmap, in screen coordinates.
* @param listener Optional OnAnimationStartedListener to find out when the
* requested animation has started running. If for some reason the animation
* is not executed, the callback will happen immediately.
* @return Returns a new ActivityOptions object that you can use to
* supply these options as the options Bundle when starting an activity.
+ * @hide
*/
public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 98c4e10..1489b2c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -138,6 +138,7 @@ public final class ActivityThread {
private static final boolean DEBUG_BACKUP = true;
private static final boolean DEBUG_CONFIGURATION = false;
private static final boolean DEBUG_SERVICE = false;
+ private static final boolean DEBUG_MEMORY_TRIM = false;
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
@@ -2779,9 +2780,21 @@ public final class ActivityThread {
performStopActivityInner(r, null, false, saveState);
}
- private static class StopInfo {
+ private static class StopInfo implements Runnable {
+ ActivityClientRecord activity;
+ Bundle state;
Bitmap thumbnail;
CharSequence description;
+
+ @Override public void run() {
+ // Tell activity manager we have been stopped.
+ try {
+ if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity);
+ ActivityManagerNative.getDefault().activityStopped(
+ activity.token, state, thumbnail, description);
+ } catch (RemoteException ex) {
+ }
+ }
}
private static final class ProviderRefCount {
@@ -2911,12 +2924,14 @@ public final class ActivityThread {
QueuedWork.waitToFinish();
}
- // Tell activity manager we have been stopped.
- try {
- ActivityManagerNative.getDefault().activityStopped(
- r.token, r.state, info.thumbnail, info.description);
- } catch (RemoteException ex) {
- }
+ // Schedule the call to tell the activity manager we have
+ // stopped. We don't do this immediately, because we want to
+ // have a chance for any other pending work (in particular memory
+ // trim requests) to complete before you tell the activity
+ // manager to proceed and allow us to go fully into the background.
+ info.activity = r;
+ info.state = r.state;
+ mH.post(info);
}
final void performRestartActivity(IBinder token) {
@@ -3749,6 +3764,7 @@ public final class ActivityThread {
}
final void handleTrimMemory(int level) {
+ if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
WindowManagerImpl.getDefault().trimMemory(level);
ArrayList<ComponentCallbacks2> callbacks;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c5d7b91..138a88f 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -325,9 +325,9 @@ class ContextImpl extends Context {
return createDropBoxManager();
}});
- registerService(INPUT_SERVICE, new ServiceFetcher() {
- public Object createService(ContextImpl ctx) {
- return new InputManager(ctx);
+ registerService(INPUT_SERVICE, new StaticServiceFetcher() {
+ public Object createStaticService() {
+ return InputManager.getInstance();
}});
registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index c493f0f..d3ba497 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -203,7 +203,7 @@ final class FragmentState implements Parcelable {
* <li> {@link #onCreateView} creates and returns the view hierarchy associated
* with the fragment.
* <li> {@link #onActivityCreated} tells the fragment that its activity has
- * completed its own {@link Activity#onCreate Activity.onCreaate}.
+ * completed its own {@link Activity#onCreate Activity.onCreate()}.
* <li> {@link #onStart} makes the fragment visible to the user (based on its
* containing activity being started).
* <li> {@link #onResume} makes the fragment interacting with the user (based on its
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index c71b186..3fc2280 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -205,7 +205,8 @@ public interface IActivityManager extends IInterface {
// Note: probably don't want to allow applications access to these.
public void goingToSleep() throws RemoteException;
public void wakingUp() throws RemoteException;
-
+ public void setLockScreenShown(boolean shown) throws RemoteException;
+
public void unhandledBack() throws RemoteException;
public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException;
public void setDebugApp(
@@ -588,4 +589,5 @@ public interface IActivityManager extends IInterface {
int GET_CURRENT_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+144;
int TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+145;
int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146;
+ int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147;
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index f955713..75c6e11 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -883,7 +883,7 @@ public class Instrumentation {
}
KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState,
deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source);
- InputManager.injectInputEvent(newEvent,
+ InputManager.getInstance().injectInputEvent(newEvent,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
@@ -926,7 +926,8 @@ public class Instrumentation {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
}
- InputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+ InputManager.getInstance().injectInputEvent(event,
+ InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
/**
@@ -945,7 +946,8 @@ public class Instrumentation {
if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {
event.setSource(InputDevice.SOURCE_TRACKBALL);
}
- InputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+ InputManager.getInstance().injectInputEvent(event,
+ InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
}
/**
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 207ae76..cb43d4c 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -666,8 +666,8 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
/**
* Print the Service's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity service <yourservicename>".
- * This is distinct from "dumpsys <servicename>", which only works for
+ * you run "adb shell dumpsys activity service &lt;yourservicename&gt;".
+ * This is distinct from "dumpsys &lt;servicename&gt;", which only works for
* named system services and which invokes the {@link IBinder#dump} method
* on the {@link IBinder} interface registered with ServiceManager.
*
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 05ef194..1206056 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1127,7 +1127,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
/**
* Print the Provider's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity provider <provider_component_name>".
+ * you run "adb shell dumpsys activity provider &lt;provider_component_name&gt;".
*
* @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 736dd24..18d682d 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2000,8 +2000,8 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_USB_ANLG_HEADSET_PLUG =
- "android.intent.action.USB_ANLG_HEADSET_PLUG";
+ public static final String ACTION_ANALOG_AUDIO_DOCK_PLUG =
+ "android.intent.action.ANALOG_AUDIO_DOCK_PLUG";
/**
* Broadcast Action: A digital audio speaker/headset plugged in or unplugged.
@@ -2015,8 +2015,8 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_USB_DGTL_HEADSET_PLUG =
- "android.intent.action.USB_DGTL_HEADSET_PLUG";
+ public static final String ACTION_DIGITAL_AUDIO_DOCK_PLUG =
+ "android.intent.action.DIGITAL_AUDIO_DOCK_PLUG";
/**
* Broadcast Action: A HMDI cable was plugged or unplugged
@@ -2034,7 +2034,7 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.HDMI_AUDIO_PLUG";
/**
- * Broadcast Action: A USB audio device was plugged in or unplugged.
+ * Broadcast Action: A USB audio accessory was plugged in or unplugged.
*
* <p>The intent will have the following extra values:
* <ul>
@@ -2046,11 +2046,11 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_USB_AUDIO_DEVICE_PLUG =
- "android.intent.action.USB_AUDIO_DEVICE_PLUG";
+ public static final String ACTION_USB_AUDIO_ACCESSORY_PLUG =
+ "android.intent.action.USB_AUDIO_ACCESSORY_PLUG";
/**
- * Broadcast Action: A USB audio accessory was plugged in or unplugged.
+ * Broadcast Action: A USB audio device was plugged in or unplugged.
*
* <p>The intent will have the following extra values:
* <ul>
@@ -2062,8 +2062,8 @@ public class Intent implements Parcelable, Cloneable {
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_USB_AUDIO_ACCESSORY_PLUG =
- "android.intent.action.USB_AUDIO_ACCESSORY_PLUG";
+ public static final String ACTION_USB_AUDIO_DEVICE_PLUG =
+ "android.intent.action.USB_AUDIO_DEVICE_PLUG";
/**
* <p>Broadcast Action: The user has switched on advanced settings in the settings app:</p>
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 06dfe90..34c40a0 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -22,6 +22,7 @@ import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import android.accounts.Account;
+import android.accounts.AccountAndUser;
import android.accounts.AccountManager;
import android.accounts.AccountManagerService;
import android.accounts.OnAccountsUpdateListener;
@@ -204,6 +205,9 @@ public class SyncManager implements OnAccountsUpdateListener {
private final PowerManager mPowerManager;
+ // Use this as a random offset to seed all periodic syncs
+ private int mSyncRandomOffsetMillis;
+
private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
@@ -237,22 +241,14 @@ public class SyncManager implements OnAccountsUpdateListener {
int count = 0;
- // For all known users on the system, get their accounts and add them to the list
+ // Get accounts from AccountManager for all the users on the system
// TODO: Limit this to active users, when such a concept exists.
+ AccountAndUser[] allAccounts = AccountManagerService.getSingleton().getAllAccounts();
for (UserInfo user : users) {
- accounts = AccountManagerService.getSingleton().getAccounts(user.id);
- count += accounts.length;
- }
-
- AccountAndUser[] allAccounts = new AccountAndUser[count];
- int index = 0;
- for (UserInfo user : users) {
- accounts = AccountManagerService.getSingleton().getAccounts(user.id);
- for (Account account : accounts) {
- allAccounts[index++] = new AccountAndUser(account, user.id);
- }
if (mBootCompleted) {
- mSyncStorageEngine.doDatabaseCleanup(accounts, user.id);
+ Account[] accountsForUser =
+ AccountManagerService.getSingleton().getAccounts(user.id);
+ mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
}
}
@@ -338,33 +334,6 @@ public class SyncManager implements OnAccountsUpdateListener {
private volatile boolean mBootCompleted = false;
- static class AccountAndUser {
- Account account;
- int userId;
-
- AccountAndUser(Account account, int userId) {
- this.account = account;
- this.userId = userId;
- }
-
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof AccountAndUser)) return false;
- final AccountAndUser other = (AccountAndUser) o;
- return this.account.equals(other.account)
- && this.userId == other.userId;
- }
-
- @Override
- public int hashCode() {
- return account.hashCode() + userId;
- }
-
- public String toString() {
- return account.toString() + " u" + userId;
- }
- }
-
private ConnectivityManager getConnectivityManager() {
synchronized (this) {
if (mConnManagerDoNotUseDirectly == null) {
@@ -472,6 +441,9 @@ public class SyncManager implements OnAccountsUpdateListener {
// do this synchronously to ensure we have the accounts before this call returns
onAccountsUpdated(null);
}
+
+ // Pick a random second in a day to seed all periodic syncs
+ mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
}
/**
@@ -700,6 +672,7 @@ public class SyncManager implements OnAccountsUpdateListener {
private void sendCheckAlarmsMessage() {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
+ mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS);
mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
}
@@ -748,6 +721,8 @@ public class SyncManager implements OnAccountsUpdateListener {
}
private void increaseBackoffSetting(SyncOperation op) {
+ // TODO: Use this function to align it to an already scheduled sync
+ // operation in the specified window
final long now = SystemClock.elapsedRealtime();
final Pair<Long, Long> previousSettings =
@@ -1094,6 +1069,8 @@ public class SyncManager implements OnAccountsUpdateListener {
final long now = SystemClock.elapsedRealtime();
pw.print("now: "); pw.print(now);
pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
+ pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000));
+ pw.println(" (HH:MM:SS)");
pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
pw.println(" (HH:MM:SS)");
pw.print("time spent syncing: ");
@@ -1805,6 +1782,9 @@ public class SyncManager implements OnAccountsUpdateListener {
AccountAndUser[] accounts = mAccounts;
final long nowAbsolute = System.currentTimeMillis();
+ final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
+ ? (nowAbsolute - mSyncRandomOffsetMillis) : 0;
+
ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
for (SyncStorageEngine.AuthorityInfo info : infos) {
// skip the sync if the account of this operation no longer exists
@@ -1826,16 +1806,32 @@ public class SyncManager implements OnAccountsUpdateListener {
SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
final Bundle extras = info.periodicSyncs.get(i).first;
- final Long periodInSeconds = info.periodicSyncs.get(i).second;
+ final Long periodInMillis = info.periodicSyncs.get(i).second * 1000;
// find when this periodic sync was last scheduled to run
final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
- // compute when this periodic sync should next run - this can be in the future
- // for example if the user changed the time, synced and changed back.
- final long nextPollTimeAbsolute = lastPollTimeAbsolute > nowAbsolute
- ? nowAbsolute
- : lastPollTimeAbsolute + periodInSeconds * 1000;
- // if it is ready to run then schedule it and mark it as having been scheduled
- if (nextPollTimeAbsolute <= nowAbsolute) {
+
+ long remainingMillis
+ = periodInMillis - (shiftedNowAbsolute % periodInMillis);
+
+ /*
+ * Sync scheduling strategy:
+ * Set the next periodic sync based on a random offset (in seconds).
+ *
+ * Also sync right now if any of the following cases hold
+ * and mark it as having been scheduled
+ *
+ * Case 1: This sync is ready to run now.
+ * Case 2: If the lastPollTimeAbsolute is in the future,
+ * sync now and reinitialize. This can happen for
+ * example if the user changed the time, synced and
+ * changed back.
+ * Case 3: If we failed to sync at the last scheduled time
+ */
+ if (remainingMillis == periodInMillis // Case 1
+ || lastPollTimeAbsolute > nowAbsolute // Case 2
+ || (nowAbsolute - lastPollTimeAbsolute
+ >= periodInMillis)) { // Case 3
+ // Sync now
final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
info.account, info.userId, info.authority);
final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
@@ -1853,12 +1849,13 @@ public class SyncManager implements OnAccountsUpdateListener {
info.account, info.userId, info.authority),
syncAdapterInfo.type.allowParallelSyncs()));
status.setPeriodicSyncTime(i, nowAbsolute);
- } else {
- // it isn't ready to run, remember this time if it is earlier than
- // earliestFuturePollTime
- if (nextPollTimeAbsolute < earliestFuturePollTime) {
- earliestFuturePollTime = nextPollTimeAbsolute;
- }
+ }
+ // Compute when this periodic sync should next run
+ final long nextPollTimeAbsolute = nowAbsolute + remainingMillis;
+
+ // remember this time if it is earlier than earliestFuturePollTime
+ if (nextPollTimeAbsolute < earliestFuturePollTime) {
+ earliestFuturePollTime = nextPollTimeAbsolute;
}
}
}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 9c81c9e..d821918 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -25,7 +25,7 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.accounts.Account;
-import android.content.SyncManager.AccountAndUser;
+import android.accounts.AccountAndUser;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
@@ -37,6 +37,7 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
@@ -49,6 +50,7 @@ import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.Random;
import java.util.TimeZone;
import java.util.List;
@@ -65,6 +67,7 @@ public class SyncStorageEngine extends Handler {
private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
+ private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
private static final String XML_ATTR_ENABLED = "enabled";
private static final String XML_ATTR_USER = "user";
private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
@@ -277,6 +280,8 @@ public class SyncStorageEngine extends Handler {
private static volatile SyncStorageEngine sSyncStorageEngine = null;
+ private int mSyncRandomOffset;
+
/**
* This file contains the core engine state: all accounts and the
* settings for them. It must never be lost, and should be changed
@@ -375,6 +380,10 @@ public class SyncStorageEngine extends Handler {
}
}
+ public int getSyncRandomOffset() {
+ return mSyncRandomOffset;
+ }
+
public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
synchronized (mAuthorities) {
mChangeListeners.register(callback, mask);
@@ -1465,6 +1474,16 @@ public class SyncStorageEngine extends Handler {
} catch (NumberFormatException e) {
// don't care
}
+ String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
+ try {
+ mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
+ } catch (NumberFormatException e) {
+ mSyncRandomOffset = 0;
+ }
+ if (mSyncRandomOffset == 0) {
+ Random random = new Random(System.currentTimeMillis());
+ mSyncRandomOffset = random.nextInt(86400);
+ }
mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
eventType = parser.next();
AuthorityInfo authority = null;
@@ -1705,6 +1724,7 @@ public class SyncStorageEngine extends Handler {
out.startTag(null, "accounts");
out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
+ out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
// Write the Sync Automatically flags for each user
final int M = mMasterSyncAutomatically.size();
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 415d58a..85f7aa5 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -154,7 +154,7 @@ public class PackageInfo implements Parcelable {
/**
* Flag for {@link #requestedPermissionsFlags}: the requested permission
* is required for the application to run; the user can not optionally
- * disable it.
+ * disable it. Currently all permissions are required.
*/
public static final int REQUESTED_PERMISSION_REQUIRED = 1<<0;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b06b4a5..5d890d4 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1095,6 +1095,18 @@ public abstract class PackageManager {
/** {@hide} */
public static final int ENFORCEMENT_YES = 1;
+ /** {@hide} */
+ public static String enforcementToString(int enforcement) {
+ switch (enforcement) {
+ case ENFORCEMENT_DEFAULT:
+ return "DEFAULT";
+ case ENFORCEMENT_YES:
+ return "YES";
+ default:
+ return Integer.toString(enforcement);
+ }
+ }
+
/**
* Retrieve overall information about an application package that is
* installed on the system.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 7571993..b6ebbdf 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -989,14 +989,16 @@ public class PackageParser {
// that may change.
String name = sa.getNonResourceString(
com.android.internal.R.styleable.AndroidManifestUsesPermission_name);
+ /* Not supporting optional permissions yet.
boolean required = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestUsesPermission_required, true);
+ */
sa.recycle();
if (name != null && !pkg.requestedPermissions.contains(name)) {
pkg.requestedPermissions.add(name.intern());
- pkg.requestedPermissionsRequired.add(required ? Boolean.TRUE : Boolean.FALSE);
+ pkg.requestedPermissionsRequired.add(Boolean.TRUE);
}
XmlUtils.skipCurrentTag(parser);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 2af58be..c682852 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -32,7 +32,6 @@ import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.TypedValue;
import android.util.LongSparseArray;
@@ -86,8 +85,8 @@ public class Resources {
// single-threaded, and after that these are immutable.
private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables
= new LongSparseArray<Drawable.ConstantState>();
- private static final SparseArray<ColorStateList> mPreloadedColorStateLists
- = new SparseArray<ColorStateList>();
+ private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists
+ = new LongSparseArray<ColorStateList>();
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
= new LongSparseArray<Drawable.ConstantState>();
private static boolean mPreloaded;
@@ -98,8 +97,8 @@ public class Resources {
// These are protected by the mTmpValue lock.
private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
- private final SparseArray<WeakReference<ColorStateList> > mColorStateListCache
- = new SparseArray<WeakReference<ColorStateList> >();
+ private final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
+ = new LongSparseArray<WeakReference<ColorStateList> >();
private final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
private boolean mPreloading;
@@ -118,22 +117,6 @@ public class Resources {
private CompatibilityInfo mCompatibilityInfo;
- private static final LongSparseArray<Object> EMPTY_ARRAY = new LongSparseArray<Object>(0) {
- @Override
- public void put(long k, Object o) {
- throw new UnsupportedOperationException();
- }
- @Override
- public void append(long k, Object o) {
- throw new UnsupportedOperationException();
- }
- };
-
- @SuppressWarnings("unchecked")
- private static <T> LongSparseArray<T> emptySparseArray() {
- return (LongSparseArray<T>) EMPTY_ARRAY;
- }
-
/** @hide */
public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
return selectSystemTheme(curTheme, targetSdkVersion,
@@ -180,9 +163,8 @@ public class Resources {
* @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
*/
- public Resources(AssetManager assets, DisplayMetrics metrics,
- Configuration config) {
- this(assets, metrics, config, (CompatibilityInfo) null);
+ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
+ this(assets, metrics, config, null);
}
/**
@@ -1883,7 +1865,8 @@ public class Resources {
return dr;
}
- Drawable.ConstantState cs = isColorDrawable ? sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
+ Drawable.ConstantState cs = isColorDrawable ?
+ sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
if (cs != null) {
dr = cs.newDrawable(this);
} else {
@@ -2005,21 +1988,21 @@ public class Resources {
}
}
- final int key = (value.assetCookie << 24) | value.data;
+ final long key = (((long) value.assetCookie) << 32) | value.data;
ColorStateList csl;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- csl = mPreloadedColorStateLists.get(key);
+ csl = sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
csl = ColorStateList.valueOf(value.data);
if (mPreloading) {
- mPreloadedColorStateLists.put(key, csl);
+ sPreloadedColorStateLists.put(key, csl);
}
return csl;
@@ -2030,7 +2013,7 @@ public class Resources {
return csl;
}
- csl = mPreloadedColorStateLists.get(key);
+ csl = sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
@@ -2063,14 +2046,13 @@ public class Resources {
if (csl != null) {
if (mPreloading) {
- mPreloadedColorStateLists.put(key, csl);
+ sPreloadedColorStateLists.put(key, csl);
} else {
synchronized (mTmpValue) {
//Log.i(TAG, "Saving cached color state list @ #" +
// Integer.toHexString(key.intValue())
// + " in " + this + ": " + csl);
- mColorStateListCache.put(
- key, new WeakReference<ColorStateList>(csl));
+ mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl));
}
}
}
@@ -2078,7 +2060,7 @@ public class Resources {
return csl;
}
- private ColorStateList getCachedColorStateList(int key) {
+ private ColorStateList getCachedColorStateList(long key) {
synchronized (mTmpValue) {
WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
if (wr != null) { // we have the key
@@ -2088,8 +2070,7 @@ public class Resources {
// Integer.toHexString(((Integer)key).intValue())
// + " in " + this + ": " + entry);
return entry;
- }
- else { // our entry has been purged
+ } else { // our entry has been purged
mColorStateListCache.delete(key);
}
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index c2abce5..47e0d1e 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -16,6 +16,7 @@
package android.hardware.input;
+import android.hardware.input.KeyboardLayout;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -34,4 +35,11 @@ interface IInputManager {
// Injects an input event into the system. To inject into windows owned by other
// applications, the caller must have the INJECT_EVENTS permission.
boolean injectInputEvent(in InputEvent ev, int mode);
+
+ // Keyboard layouts configuration.
+ KeyboardLayout[] getKeyboardLayouts();
+ KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor);
+ String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
+ void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+ String keyboardLayoutDescriptor);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 5ead1f4..3b3c237 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -16,37 +16,18 @@
package android.hardware.input;
-import com.android.internal.util.XmlUtils;
-
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.os.Bundle;
import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
+import android.util.SparseArray;
import android.view.InputDevice;
import android.view.InputEvent;
-import android.view.KeyCharacterMap;
-import android.view.KeyCharacterMap.UnavailableException;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
/**
* Provides information about input devices and available key layouts.
@@ -60,13 +41,10 @@ import java.util.List;
public final class InputManager {
private static final String TAG = "InputManager";
- private static final IInputManager sIm;
-
- private final Context mContext;
+ private static InputManager sInstance;
- // Used to simulate a persistent data store.
- // TODO: Replace with the real thing.
- private static final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
+ private final IInputManager mIm;
+ private final SparseArray<InputDevice> mInputDevices = new SparseArray<InputDevice>();
/**
* Broadcast Action: Query available keyboard layouts.
@@ -169,14 +147,25 @@ public final class InputManager {
*/
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; // see InputDispatcher.h
- static {
- IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);
- sIm = IInputManager.Stub.asInterface(b);
+ private InputManager(IInputManager im) {
+ mIm = im;
}
- /** @hide */
- public InputManager(Context context) {
- mContext = context;
+ /**
+ * Gets an instance of the input manager.
+ *
+ * @return The input manager instance.
+ *
+ * @hide
+ */
+ public static InputManager getInstance() {
+ synchronized (InputManager.class) {
+ if (sInstance == null) {
+ IBinder b = ServiceManager.getService(Context.INPUT_SERVICE);
+ sInstance = new InputManager(IInputManager.Stub.asInterface(b));
+ }
+ return sInstance;
+ }
}
/**
@@ -188,18 +177,16 @@ public final class InputManager {
* </p>
*
* @return A list of all supported keyboard layouts.
+ *
* @hide
*/
- public List<KeyboardLayout> getKeyboardLayouts() {
- ArrayList<KeyboardLayout> list = new ArrayList<KeyboardLayout>();
-
- final PackageManager pm = mContext.getPackageManager();
- Intent intent = new Intent(ACTION_QUERY_KEYBOARD_LAYOUTS);
- for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
- PackageManager.GET_META_DATA)) {
- loadKeyboardLayouts(pm, resolveInfo.activityInfo, list, null);
+ public KeyboardLayout[] getKeyboardLayouts() {
+ try {
+ return mIm.getKeyboardLayouts();
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not get list of keyboard layout informations.", ex);
+ return new KeyboardLayout[0];
}
- return list;
}
/**
@@ -216,20 +203,10 @@ public final class InputManager {
throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
}
- KeyboardLayoutDescriptor d = parseKeyboardLayoutDescriptor(keyboardLayoutDescriptor);
- if (d == null) {
- return null;
- }
-
- final PackageManager pm = mContext.getPackageManager();
try {
- ActivityInfo receiver = pm.getReceiverInfo(
- new ComponentName(d.packageName, d.receiverName),
- PackageManager.GET_META_DATA);
- return loadKeyboardLayouts(pm, receiver, null, d.keyboardLayoutName);
- } catch (NameNotFoundException ex) {
- Log.w(TAG, "Could not load keyboard layout '" + d.keyboardLayoutName
- + "' from receiver " + d.packageName + "/" + d.receiverName, ex);
+ return mIm.getKeyboardLayout(keyboardLayoutDescriptor);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not get keyboard layout information.", ex);
return null;
}
}
@@ -243,12 +220,17 @@ public final class InputManager {
*
* @hide
*/
- public String getInputDeviceKeyboardLayoutDescriptor(String inputDeviceDescriptor) {
+ public String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
if (inputDeviceDescriptor == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
- return mFakeRegistry.get(inputDeviceDescriptor);
+ try {
+ return mIm.getKeyboardLayoutForInputDevice(inputDeviceDescriptor);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not get keyboard layout for input device.", ex);
+ return null;
+ }
}
/**
@@ -264,92 +246,17 @@ public final class InputManager {
*
* @hide
*/
- public void setInputDeviceKeyboardLayoutDescriptor(String inputDeviceDescriptor,
+ public void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
String keyboardLayoutDescriptor) {
if (inputDeviceDescriptor == null) {
throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
}
- mFakeRegistry.put(inputDeviceDescriptor, keyboardLayoutDescriptor);
- }
-
- private KeyboardLayout loadKeyboardLayouts(
- PackageManager pm, ActivityInfo receiver,
- List<KeyboardLayout> list, String keyboardName) {
- Bundle metaData = receiver.metaData;
- if (metaData == null) {
- return null;
- }
-
- int configResId = metaData.getInt(META_DATA_KEYBOARD_LAYOUTS);
- if (configResId == 0) {
- Log.w(TAG, "Missing meta-data '" + META_DATA_KEYBOARD_LAYOUTS + "' on receiver "
- + receiver.packageName + "/" + receiver.name);
- return null;
- }
-
try {
- Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
- XmlResourceParser parser = resources.getXml(configResId);
- try {
- XmlUtils.beginDocument(parser, "keyboard-layouts");
-
- for (;;) {
- XmlUtils.nextElement(parser);
- String element = parser.getName();
- if (element == null) {
- break;
- }
- if (element.equals("keyboard-layout")) {
- TypedArray a = resources.obtainAttributes(
- parser, com.android.internal.R.styleable.KeyboardLayout);
- try {
- String name = a.getString(
- com.android.internal.R.styleable.KeyboardLayout_name);
- String label = a.getString(
- com.android.internal.R.styleable.KeyboardLayout_label);
- int kcmResId = a.getResourceId(
- com.android.internal.R.styleable.KeyboardLayout_kcm, 0);
- if (name == null || label == null || kcmResId == 0) {
- Log.w(TAG, "Missing required 'name', 'label' or 'kcm' "
- + "attributes in keyboard layout "
- + "resource from receiver "
- + receiver.packageName + "/" + receiver.name);
- } else {
- String descriptor = makeKeyboardLayoutDescriptor(
- receiver.packageName, receiver.name, name);
- KeyboardLayout c = new KeyboardLayout(
- descriptor, label, kcmResId);
- if (keyboardName != null && name.equals(keyboardName)) {
- return c;
- }
- if (list != null) {
- list.add(c);
- }
- }
- } finally {
- a.recycle();
- }
- } else {
- Log.w(TAG, "Skipping unrecognized element '" + element
- + "' in keyboard layout resource from receiver "
- + receiver.packageName + "/" + receiver.name);
- }
- }
- } finally {
- parser.close();
- }
- } catch (Exception ex) {
- Log.w(TAG, "Could not load keyboard layout resource from receiver "
- + receiver.packageName + "/" + receiver.name, ex);
- return null;
- }
- if (keyboardName != null) {
- Log.w(TAG, "Could not load keyboard layout '" + keyboardName
- + "' from receiver " + receiver.packageName + "/" + receiver.name
- + " because it was not declared in the keyboard layout resource.");
+ mIm.setKeyboardLayoutForInputDevice(inputDeviceDescriptor, keyboardLayoutDescriptor);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Could not set keyboard layout for input device.", ex);
}
- return null;
}
/**
@@ -359,15 +266,16 @@ public final class InputManager {
* speed set by {@link #tryPointerSpeed}.
* </p>
*
+ * @param context The application context.
* @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
* {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
*
* @hide
*/
- public int getPointerSpeed() {
+ public int getPointerSpeed(Context context) {
int speed = DEFAULT_POINTER_SPEED;
try {
- speed = Settings.System.getInt(mContext.getContentResolver(),
+ speed = Settings.System.getInt(context.getContentResolver(),
Settings.System.POINTER_SPEED);
} catch (SettingNotFoundException snfe) {
}
@@ -380,17 +288,18 @@ public final class InputManager {
* Requires {@link android.Manifest.permissions.WRITE_SETTINGS}.
* </p>
*
+ * @param context The application context.
* @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
* {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
*
* @hide
*/
- public void setPointerSpeed(int speed) {
+ public void setPointerSpeed(Context context, int speed) {
if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
throw new IllegalArgumentException("speed out of range");
}
- Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.putInt(context.getContentResolver(),
Settings.System.POINTER_SPEED, speed);
}
@@ -411,7 +320,7 @@ public final class InputManager {
}
try {
- sIm.tryPointerSpeed(speed);
+ mIm.tryPointerSpeed(speed);
} catch (RemoteException ex) {
Log.w(TAG, "Could not set temporary pointer speed.", ex);
}
@@ -424,12 +333,27 @@ public final class InputManager {
*
* @hide
*/
- public static InputDevice getInputDevice(int id) {
+ public InputDevice getInputDevice(int id) {
+ synchronized (mInputDevices) {
+ InputDevice inputDevice = mInputDevices.get(id);
+ if (inputDevice != null) {
+ return inputDevice;
+ }
+ }
+ final InputDevice newInputDevice;
try {
- return sIm.getInputDevice(id);
+ newInputDevice = mIm.getInputDevice(id);
} catch (RemoteException ex) {
throw new RuntimeException("Could not get input device information.", ex);
}
+ synchronized (mInputDevices) {
+ InputDevice inputDevice = mInputDevices.get(id);
+ if (inputDevice != null) {
+ return inputDevice;
+ }
+ mInputDevices.put(id, newInputDevice);
+ return newInputDevice;
+ }
}
/**
@@ -438,9 +362,9 @@ public final class InputManager {
*
* @hide
*/
- public static int[] getInputDeviceIds() {
+ public int[] getInputDeviceIds() {
try {
- return sIm.getInputDeviceIds();
+ return mIm.getInputDeviceIds();
} catch (RemoteException ex) {
throw new RuntimeException("Could not get input device ids.", ex);
}
@@ -458,10 +382,10 @@ public final class InputManager {
*
* @hide
*/
- public static boolean[] deviceHasKeys(int[] keyCodes) {
+ public boolean[] deviceHasKeys(int[] keyCodes) {
boolean[] ret = new boolean[keyCodes.length];
try {
- sIm.hasKeys(-1, InputDevice.SOURCE_ANY, keyCodes, ret);
+ mIm.hasKeys(-1, InputDevice.SOURCE_ANY, keyCodes, ret);
} catch (RemoteException e) {
// no fallback; just return the empty array
}
@@ -489,7 +413,7 @@ public final class InputManager {
*
* @hide
*/
- public static boolean injectInputEvent(InputEvent event, int mode) {
+ public boolean injectInputEvent(InputEvent event, int mode) {
if (event == null) {
throw new IllegalArgumentException("event must not be null");
}
@@ -500,152 +424,9 @@ public final class InputManager {
}
try {
- return sIm.injectInputEvent(event, mode);
+ return mIm.injectInputEvent(event, mode);
} catch (RemoteException ex) {
return false;
}
}
-
- private static String makeKeyboardLayoutDescriptor(String packageName,
- String receiverName, String keyboardName) {
- return packageName + "/" + receiverName + "/" + keyboardName;
- }
-
- private static KeyboardLayoutDescriptor parseKeyboardLayoutDescriptor(String descriptor) {
- int pos = descriptor.indexOf('/');
- if (pos < 0 || pos + 1 == descriptor.length()) {
- return null;
- }
- int pos2 = descriptor.indexOf('/', pos + 1);
- if (pos2 < pos + 2 || pos2 + 1 == descriptor.length()) {
- return null;
- }
-
- KeyboardLayoutDescriptor result = new KeyboardLayoutDescriptor();
- result.packageName = descriptor.substring(0, pos);
- result.receiverName = descriptor.substring(pos + 1, pos2);
- result.keyboardLayoutName = descriptor.substring(pos2 + 1);
- return result;
- }
-
- /**
- * Describes a keyboard layout.
- *
- * @hide
- */
- public static final class KeyboardLayout implements Parcelable,
- Comparable<KeyboardLayout> {
- private final String mDescriptor;
- private final String mLabel;
- private final int mKeyCharacterMapResId;
-
- private KeyCharacterMap mKeyCharacterMap;
-
- public static final Parcelable.Creator<KeyboardLayout> CREATOR =
- new Parcelable.Creator<KeyboardLayout>() {
- public KeyboardLayout createFromParcel(Parcel source) {
- return new KeyboardLayout(source);
- }
- public KeyboardLayout[] newArray(int size) {
- return new KeyboardLayout[size];
- }
- };
-
- private KeyboardLayout(String descriptor,
- String label, int keyCharacterMapResId) {
- mDescriptor = descriptor;
- mLabel = label;
- mKeyCharacterMapResId = keyCharacterMapResId;
- }
-
- private KeyboardLayout(Parcel source) {
- mDescriptor = source.readString();
- mLabel = source.readString();
- mKeyCharacterMapResId = source.readInt();
- }
-
- /**
- * Gets the keyboard layout descriptor, which can be used to retrieve
- * the keyboard layout again later using
- * {@link InputManager#getKeyboardLayout(String)}.
- *
- * @return The keyboard layout descriptor.
- */
- public String getDescriptor() {
- return mDescriptor;
- }
-
- /**
- * Gets the keyboard layout descriptive label to show in the user interface.
- * @return The keyboard layout descriptive label.
- */
- public String getLabel() {
- return mLabel;
- }
-
- /**
- * Loads the key character map associated with the keyboard layout.
- *
- * @param pm The package manager.
- * @return The key character map, or null if it could not be loaded for any reason.
- */
- public KeyCharacterMap loadKeyCharacterMap(PackageManager pm) {
- if (pm == null) {
- throw new IllegalArgumentException("pm must not be null");
- }
-
- if (mKeyCharacterMap == null) {
- KeyboardLayoutDescriptor d = parseKeyboardLayoutDescriptor(mDescriptor);
- if (d == null) {
- Log.e(TAG, "Could not load key character map '" + mDescriptor
- + "' because the descriptor could not be parsed.");
- return null;
- }
-
- CharSequence cs = pm.getText(d.packageName, mKeyCharacterMapResId, null);
- if (cs == null) {
- Log.e(TAG, "Could not load key character map '" + mDescriptor
- + "' because its associated resource could not be loaded.");
- return null;
- }
-
- try {
- mKeyCharacterMap = KeyCharacterMap.load(cs);
- } catch (UnavailableException ex) {
- Log.e(TAG, "Could not load key character map '" + mDescriptor
- + "' due to an error while parsing.", ex);
- return null;
- }
- }
- return mKeyCharacterMap;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mDescriptor);
- dest.writeString(mLabel);
- dest.writeInt(mKeyCharacterMapResId);
- }
-
- @Override
- public int compareTo(KeyboardLayout another) {
- return mLabel.compareToIgnoreCase(another.mLabel);
- }
-
- @Override
- public String toString() {
- return mLabel;
- }
- }
-
- private static final class KeyboardLayoutDescriptor {
- public String packageName;
- public String receiverName;
- public String keyboardLayoutName;
- }
}
diff --git a/core/java/android/hardware/input/KeyboardLayout.aidl b/core/java/android/hardware/input/KeyboardLayout.aidl
new file mode 100644
index 0000000..226e384
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayout.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.hardware.input;
+
+parcelable KeyboardLayout;
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
new file mode 100644
index 0000000..e75a6dc
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -0,0 +1,91 @@
+/*
+ * 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.hardware.input;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes a keyboard layout.
+ *
+ * @hide
+ */
+public final class KeyboardLayout implements Parcelable,
+ Comparable<KeyboardLayout> {
+ private final String mDescriptor;
+ private final String mLabel;
+
+ public static final Parcelable.Creator<KeyboardLayout> CREATOR =
+ new Parcelable.Creator<KeyboardLayout>() {
+ public KeyboardLayout createFromParcel(Parcel source) {
+ return new KeyboardLayout(source);
+ }
+ public KeyboardLayout[] newArray(int size) {
+ return new KeyboardLayout[size];
+ }
+ };
+
+ public KeyboardLayout(String descriptor, String label) {
+ mDescriptor = descriptor;
+ mLabel = label;
+ }
+
+ private KeyboardLayout(Parcel source) {
+ mDescriptor = source.readString();
+ mLabel = source.readString();
+ }
+
+ /**
+ * Gets the keyboard layout descriptor, which can be used to retrieve
+ * the keyboard layout again later using
+ * {@link InputManager#getKeyboardLayout(String)}.
+ *
+ * @return The keyboard layout descriptor.
+ */
+ public String getDescriptor() {
+ return mDescriptor;
+ }
+
+ /**
+ * Gets the keyboard layout descriptive label to show in the user interface.
+ * @return The keyboard layout descriptive label.
+ */
+ public String getLabel() {
+ return mLabel;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mDescriptor);
+ dest.writeString(mLabel);
+ }
+
+ @Override
+ public int compareTo(KeyboardLayout another) {
+ return mLabel.compareToIgnoreCase(another.mLabel);
+ }
+
+ @Override
+ public String toString() {
+ return mLabel;
+ }
+} \ No newline at end of file
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index ba7dc4a..332f40a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1712,8 +1712,8 @@ public class InputMethodService extends AbstractInputMethodService {
/**
* Override this to intercept key down events before they are processed by the
- * application. If you return true, the application will not itself
- * process the event. If you return true, the normal application processing
+ * application. If you return true, the application will not
+ * process the event itself. If you return false, the normal application processing
* will occur as if the IME had not seen the event at all.
*
* <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
diff --git a/core/java/android/net/NetworkQuotaInfo.java b/core/java/android/net/NetworkQuotaInfo.java
index 6535256..1725ed7 100644
--- a/core/java/android/net/NetworkQuotaInfo.java
+++ b/core/java/android/net/NetworkQuotaInfo.java
@@ -57,12 +57,12 @@ public class NetworkQuotaInfo implements Parcelable {
return mHardLimitBytes;
}
- /** {@inheritDoc} */
+ @Override
public int describeContents() {
return 0;
}
- /** {@inheritDoc} */
+ @Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(mEstimatedBytes);
out.writeLong(mSoftLimitBytes);
@@ -70,10 +70,12 @@ public class NetworkQuotaInfo implements Parcelable {
}
public static final Creator<NetworkQuotaInfo> CREATOR = new Creator<NetworkQuotaInfo>() {
+ @Override
public NetworkQuotaInfo createFromParcel(Parcel in) {
return new NetworkQuotaInfo(in);
}
+ @Override
public NetworkQuotaInfo[] newArray(int size) {
return new NetworkQuotaInfo[size];
}
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index 704111b..2fc69ad 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -52,12 +52,12 @@ public class NetworkState implements Parcelable {
subscriberId = in.readString();
}
- /** {@inheritDoc} */
+ @Override
public int describeContents() {
return 0;
}
- /** {@inheritDoc} */
+ @Override
public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(networkInfo, flags);
out.writeParcelable(linkProperties, flags);
@@ -66,10 +66,12 @@ public class NetworkState implements Parcelable {
}
public static final Creator<NetworkState> CREATOR = new Creator<NetworkState>() {
+ @Override
public NetworkState createFromParcel(Parcel in) {
return new NetworkState(in);
}
+ @Override
public NetworkState[] newArray(int size) {
return new NetworkState[size];
}
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 7a1ef66..844d055 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -155,7 +155,7 @@ public class NetworkStats implements Parcelable {
operations = parcel.createLongArray();
}
- /** {@inheritDoc} */
+ @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(elapsedRealtime);
dest.writeInt(size);
@@ -352,10 +352,9 @@ public class NetworkStats implements Parcelable {
* on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
* since operation counts are at data layer.
*/
- @Deprecated
public void spliceOperationsFrom(NetworkStats stats) {
for (int i = 0; i < size; i++) {
- final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]);
+ final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i]);
if (j == -1) {
operations[i] = 0;
} else {
@@ -663,16 +662,18 @@ public class NetworkStats implements Parcelable {
return writer.toString();
}
- /** {@inheritDoc} */
+ @Override
public int describeContents() {
return 0;
}
public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
+ @Override
public NetworkStats createFromParcel(Parcel in) {
return new NetworkStats(in);
}
+ @Override
public NetworkStats[] newArray(int size) {
return new NetworkStats[size];
}
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index faf8a3f..0003c6e 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -130,7 +130,7 @@ public class NetworkStatsHistory implements Parcelable {
totalBytes = in.readLong();
}
- /** {@inheritDoc} */
+ @Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(bucketDuration);
writeLongArray(out, bucketStart, bucketCount);
@@ -191,7 +191,7 @@ public class NetworkStatsHistory implements Parcelable {
writeVarLongArray(out, operations, bucketCount);
}
- /** {@inheritDoc} */
+ @Override
public int describeContents() {
return 0;
}
@@ -586,10 +586,12 @@ public class NetworkStatsHistory implements Parcelable {
}
public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() {
+ @Override
public NetworkStatsHistory createFromParcel(Parcel in) {
return new NetworkStatsHistory(in);
}
+ @Override
public NetworkStatsHistory[] newArray(int size) {
return new NetworkStatsHistory[size];
}
diff --git a/core/java/android/net/nsd/DnsSdServiceInfo.java b/core/java/android/net/nsd/DnsSdServiceInfo.java
index 47d6ec6..33c3eb9 100644
--- a/core/java/android/net/nsd/DnsSdServiceInfo.java
+++ b/core/java/android/net/nsd/DnsSdServiceInfo.java
@@ -19,6 +19,8 @@ package android.net.nsd;
import android.os.Parcelable;
import android.os.Parcel;
+import java.net.InetAddress;
+
/**
* Defines a service based on DNS service discovery
* {@hide}
@@ -27,20 +29,20 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
private String mServiceName;
- private String mRegistrationType;
+ private String mServiceType;
private DnsSdTxtRecord mTxtRecord;
- private String mHostname;
+ private InetAddress mHost;
private int mPort;
- DnsSdServiceInfo() {
+ public DnsSdServiceInfo() {
}
- DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
+ public DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
mServiceName = sn;
- mRegistrationType = rt;
+ mServiceType = rt;
mTxtRecord = tr;
}
@@ -59,13 +61,13 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
@Override
/** @hide */
public String getServiceType() {
- return mRegistrationType;
+ return mServiceType;
}
@Override
/** @hide */
public void setServiceType(String s) {
- mRegistrationType = s;
+ mServiceType = s;
}
public DnsSdTxtRecord getTxtRecord() {
@@ -76,12 +78,12 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
mTxtRecord = new DnsSdTxtRecord(t);
}
- public String getHostName() {
- return mHostname;
+ public InetAddress getHost() {
+ return mHost;
}
- public void setHostName(String s) {
- mHostname = s;
+ public void setHost(InetAddress s) {
+ mHost = s;
}
public int getPort() {
@@ -96,7 +98,9 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
StringBuffer sb = new StringBuffer();
sb.append("name: ").append(mServiceName).
- append("type: ").append(mRegistrationType).
+ append("type: ").append(mServiceType).
+ append("host: ").append(mHost).
+ append("port: ").append(mPort).
append("txtRecord: ").append(mTxtRecord);
return sb.toString();
}
@@ -109,9 +113,14 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
/** Implement the Parcelable interface */
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mServiceName);
- dest.writeString(mRegistrationType);
+ dest.writeString(mServiceType);
dest.writeParcelable(mTxtRecord, flags);
- dest.writeString(mHostname);
+ if (mHost != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(mHost.getAddress());
+ } else {
+ dest.writeByte((byte)0);
+ }
dest.writeInt(mPort);
}
@@ -121,9 +130,15 @@ public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable {
public DnsSdServiceInfo createFromParcel(Parcel in) {
DnsSdServiceInfo info = new DnsSdServiceInfo();
info.mServiceName = in.readString();
- info.mRegistrationType = in.readString();
+ info.mServiceType = in.readString();
info.mTxtRecord = in.readParcelable(null);
- info.mHostname = in.readString();
+
+ if (in.readByte() == 1) {
+ try {
+ info.mHost = InetAddress.getByAddress(in.createByteArray());
+ } catch (java.net.UnknownHostException e) {}
+ }
+
info.mPort = in.readInt();
return info;
}
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index a109a98..505f11b 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -93,6 +93,15 @@ public class NsdManager {
/** @hide */
public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 17;
+ /** @hide */
+ public static final int STOP_RESOLVE = BASE + 18;
+ /** @hide */
+ public static final int STOP_RESOLVE_FAILED = BASE + 19;
+ /** @hide */
+ public static final int STOP_RESOLVE_SUCCEEDED = BASE + 20;
+
+
+
/**
* Create a new Nsd instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -117,10 +126,23 @@ public class NsdManager {
/**
* Indicates that the operation failed because the framework is busy and
- * unable to service the request
+ * unable to service the request.
*/
public static final int BUSY = 2;
+ /**
+ * Indicates that the operation failed because it is already active.
+ */
+ public static final int ALREADY_ACTIVE = 3;
+
+ /**
+ * Indicates that the operation failed because maximum limit on
+ * service registrations has reached.
+ */
+ public static final int MAX_REGS_REACHED = 4;
+
+
+
/** Interface for callback invocation when framework channel is connected or lost */
public interface ChannelListener {
public void onChannelConnected(Channel c);
@@ -188,6 +210,7 @@ public class NsdManager {
private DnsSdRegisterListener mDnsSdRegisterListener;
private DnsSdUpdateRegistrationListener mDnsSdUpdateListener;
private DnsSdResolveListener mDnsSdResolveListener;
+ private ActionListener mDnsSdStopResolveListener;
AsyncChannel mAsyncChannel;
ServiceHandler mHandler;
@@ -278,6 +301,16 @@ public class NsdManager {
(DnsSdServiceInfo) message.obj);
}
break;
+ case STOP_RESOLVE_FAILED:
+ if (mDnsSdStopResolveListener!= null) {
+ mDnsSdStopResolveListener.onFailure(message.arg1);
+ }
+ break;
+ case STOP_RESOLVE_SUCCEEDED:
+ if (mDnsSdStopResolveListener != null) {
+ mDnsSdStopResolveListener.onSuccess();
+ }
+ break;
default:
Log.d(TAG, "Ignored " + message);
break;
@@ -345,6 +378,14 @@ public class NsdManager {
c.mDnsSdResolveListener = b;
}
+ /**
+ * Set the listener for stopping service resolution. Can be null.
+ */
+ public void setStopResolveListener(Channel c, ActionListener b) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ c.mDnsSdStopResolveListener = b;
+ }
+
public void registerService(Channel c, DnsSdServiceInfo serviceInfo) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo");
@@ -378,6 +419,13 @@ public class NsdManager {
c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo);
}
+ public void stopServiceResolve(Channel c) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ if (c.mDnsSdResolveListener == null) throw new
+ IllegalStateException("Resolve listener needs to be set first");
+ c.mAsyncChannel.sendMessage(STOP_RESOLVE);
+ }
+
/**
* Get a reference to NetworkService handler. This is used to establish
* an AsyncChannel communication with the service
diff --git a/core/java/android/provider/BrowserContract.java b/core/java/android/provider/BrowserContract.java
index d678205..118b5eb 100644
--- a/core/java/android/provider/BrowserContract.java
+++ b/core/java/android/provider/BrowserContract.java
@@ -30,6 +30,15 @@ import android.os.RemoteException;
import android.util.Pair;
/**
+ * <p>
+ * The contract between the browser provider and applications. Contains the definition
+ * for the supported URIS and columns.
+ * </p>
+ * <h3>Overview</h3>
+ * <p>
+ * BrowserContract defines an database of browser-related information which are bookmarks,
+ * history, images and the mapping between the image and URL.
+ * </p>
* @hide
*/
public class BrowserContract {
@@ -45,12 +54,14 @@ public class BrowserContract {
* the dirty flag is not automatically set and the "syncToNetwork" parameter
* is set to false when calling
* {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+ * @hide
*/
public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
/**
* A parameter for use when querying any table that allows specifying a limit on the number
* of rows returned.
+ * @hide
*/
public static final String PARAM_LIMIT = "limit";
@@ -58,6 +69,8 @@ public class BrowserContract {
* Generic columns for use by sync adapters. The specific functions of
* these columns are private to the sync adapter. Other clients of the API
* should not attempt to either read or write these columns.
+ *
+ * @hide
*/
interface BaseSyncColumns {
/** Generic column for use by sync adapters. */
@@ -74,6 +87,7 @@ public class BrowserContract {
/**
* Convenience definitions for use in implementing chrome bookmarks sync in the Bookmarks table.
+ * @hide
*/
public static final class ChromeSyncColumns {
private ChromeSyncColumns() {}
@@ -93,6 +107,7 @@ public class BrowserContract {
/**
* Columns that appear when each row of a table belongs to a specific
* account, including sync information that an account may need.
+ * @hide
*/
interface SyncColumns extends BaseSyncColumns {
/**
@@ -144,13 +159,14 @@ public class BrowserContract {
public static final String _ID = "_id";
/**
- * The URL of the bookmark.
+ * This column is valid when the row is a URL. The history table's URL
+ * can not be updated.
* <P>Type: TEXT (URL)</P>
*/
public static final String URL = "url";
/**
- * The user visible title of the bookmark.
+ * The user visible title.
* <P>Type: TEXT</P>
*/
public static final String TITLE = "title";
@@ -159,10 +175,14 @@ public class BrowserContract {
* The time that this row was created on its originating client (msecs
* since the epoch).
* <P>Type: INTEGER</P>
+ * @hide
*/
public static final String DATE_CREATED = "created";
}
+ /**
+ * @hide
+ */
interface ImageColumns {
/**
* The favicon of the bookmark, may be NULL.
@@ -182,7 +202,6 @@ public class BrowserContract {
* The touch icon for the web page, may be NULL.
* Must decode via {@link BitmapFactory#decodeByteArray}.
* <p>Type: BLOB (image)</p>
- * @hide
*/
public static final String TOUCH_ICON = "touch_icon";
}
@@ -200,9 +219,26 @@ public class BrowserContract {
*/
public static final String VISITS = "visits";
+ /**
+ * @hide
+ */
public static final String USER_ENTERED = "user_entered";
}
+ interface ImageMappingColumns {
+ /**
+ * The ID of the image in Images. One image can map onto the multiple URLs.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String IMAGE_ID = "image_id";
+
+ /**
+ * The URL. The URL can map onto the different type of images.
+ * <P>Type: TEXT (URL)</P>
+ */
+ public static final String URL = "url";
+ }
+
/**
* The bookmarks table, which holds the user's browser bookmarks.
*/
@@ -218,24 +254,71 @@ public class BrowserContract {
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
/**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is a bookmark.
+ */
+ public static final int BOOKMARK_TYPE_BOOKMARK = 1;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is a folder.
+ */
+ public static final int BOOKMARK_TYPE_FOLDER = 2;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is the bookmark bar folder.
+ */
+ public static final int BOOKMARK_TYPE_BOOKMARK_BAR_FOLDER = 3;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is other folder and
+ */
+ public static final int BOOKMARK_TYPE_OTHER_FOLDER = 4;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is other folder, .
+ */
+ public static final int BOOKMARK_TYPE_MOBILE_FOLDER = 5;
+
+ /**
+ * The type of the item.
+ * <P>Type: INTEGER</P>
+ * <p>Allowed values are:</p>
+ * <p>
+ * <ul>
+ * <li>{@link #BOOKMARK_TYPE_BOOKMARK}</li>
+ * <li>{@link #BOOKMARK_TYPE_FOLDER}</li>
+ * <li>{@link #BOOKMARK_TYPE_BOOKMARK_BAR_FOLDER}</li>
+ * <li>{@link #BOOKMARK_TYPE_OTHER_FOLDER}</li>
+ * <li>{@link #BOOKMARK_TYPE_MOBILE_FOLDER}</li>
+ * </ul>
+ * </p>
+ * <p> The TYPE_BOOKMARK_BAR_FOLDER, TYPE_OTHER_FOLDER and TYPE_MOBILE_FOLDER
+ * can not be updated or deleted.</p>
+ */
+ public static final String TYPE = "type";
+
+ /**
* The content:// style URI for the default folder
+ * @hide
*/
public static final Uri CONTENT_URI_DEFAULT_FOLDER =
Uri.withAppendedPath(CONTENT_URI, "folder");
/**
* Query parameter used to specify an account name
+ * @hide
*/
public static final String PARAM_ACCOUNT_NAME = "acct_name";
/**
* Query parameter used to specify an account type
+ * @hide
*/
public static final String PARAM_ACCOUNT_TYPE = "acct_type";
/**
* Builds a URI that points to a specific folder.
* @param folderId the ID of the folder to point to
+ * @hide
*/
public static final Uri buildFolderUri(long folderId) {
return ContentUris.withAppendedId(CONTENT_URI_DEFAULT_FOLDER, folderId);
@@ -255,6 +338,7 @@ public class BrowserContract {
* Query parameter to use if you want to see deleted bookmarks that are still
* around on the device and haven't been synced yet.
* @see #IS_DELETED
+ * @hide
*/
public static final String QUERY_PARAMETER_SHOW_DELETED = "show_deleted";
@@ -262,6 +346,7 @@ public class BrowserContract {
* Flag indicating if an item is a folder or bookmark. Non-zero values indicate
* a folder and zero indicates a bookmark.
* <P>Type: INTEGER (boolean)</P>
+ * @hide
*/
public static final String IS_FOLDER = "folder";
@@ -274,6 +359,7 @@ public class BrowserContract {
/**
* The source ID for an item's parent. Read-only.
* @see #PARENT
+ * @hide
*/
public static final String PARENT_SOURCE_ID = "parent_source";
@@ -281,6 +367,7 @@ public class BrowserContract {
* The position of the bookmark in relation to it's siblings that share the same
* {@link #PARENT}. May be negative.
* <P>Type: INTEGER</P>
+ * @hide
*/
public static final String POSITION = "position";
@@ -288,6 +375,7 @@ public class BrowserContract {
* The item that the bookmark should be inserted after.
* May be negative.
* <P>Type: INTEGER</P>
+ * @hide
*/
public static final String INSERT_AFTER = "insert_after";
@@ -296,6 +384,7 @@ public class BrowserContract {
* May be negative.
* <P>Type: INTEGER</P>
* @see #INSERT_AFTER
+ * @hide
*/
public static final String INSERT_AFTER_SOURCE_ID = "insert_after_source";
@@ -305,12 +394,14 @@ public class BrowserContract {
* to the URI when performing your query.
* <p>Type: INTEGER (non-zero if the item has been deleted, zero if it hasn't)
* @see #QUERY_PARAMETER_SHOW_DELETED
+ * @hide
*/
public static final String IS_DELETED = "deleted";
}
/**
* Read-only table that lists all the accounts that are used to provide bookmarks.
+ * @hide
*/
public static final class Accounts {
/**
@@ -410,6 +501,7 @@ public class BrowserContract {
* A table provided for sync adapters to use for storing private sync state data.
*
* @see SyncStateContract
+ * @hide
*/
public static final class SyncState implements SyncStateContract.Columns {
/**
@@ -459,8 +551,18 @@ public class BrowserContract {
}
/**
- * Stores images for URLs. Only support query() and update().
- * @hide
+ * <p>
+ * Stores images for URLs.
+ * </p>
+ * <p>
+ * The rows in this table can not be updated since there might have multiple URLs mapping onto
+ * the same image. If you want to update a URL's image, you need to add the new image in this
+ * table, then update the mapping onto the added image.
+ * </p>
+ * <p>
+ * Every image should be at least associated with one URL, otherwise it will be removed after a
+ * while.
+ * </p>
*/
public static final class Images implements ImageColumns {
/**
@@ -474,15 +576,93 @@ public class BrowserContract {
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "images");
/**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of images.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/images";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} of a single image.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/images";
+
+ /**
+ * Used in {@link Images#TYPE} column and indicats the row is a favicon.
+ */
+ public static final int IMAGE_TYPE_FAVICON = 1;
+
+ /**
+ * Used in {@link Images#TYPE} column and indicats the row is a precomposed touch icon.
+ */
+ public static final int IMAGE_TYPE_PRECOMPOSED_TOUCH_ICON = 2;
+
+ /**
+ * Used in {@link Images#TYPE} column and indicats the row is a touch icon.
+ */
+ public static final int IMAGE_TYPE_TOUCH_ICON = 4;
+
+ /**
+ * The type of item in the table.
+ * <P>Type: INTEGER</P>
+ * <p>Allowed values are:</p>
+ * <p>
+ * <ul>
+ * <li>{@link #IMAGE_TYPE_FAVICON}</li>
+ * <li>{@link #IMAGE_TYPE_PRECOMPOSED_TOUCH_ICON}</li>
+ * <li>{@link #IMAGE_TYPE_TOUCH_ICON}</li>
+ * </ul>
+ * </p>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The image data.
+ * <p>Type: BLOB (image)</p>
+ */
+ public static final String DATA = "data";
+
+ /**
* The URL the images came from.
* <P>Type: TEXT (URL)</P>
+ * @hide
*/
public static final String URL = "url_key";
}
/**
+ * <p>
+ * A table that stores the mappings between the image and the URL.
+ * </p>
+ * <p>
+ * Deleting or Updating a mapping might also deletes the mapped image if there is no other URL
+ * maps onto it.
+ * </p>
+ */
+ public static final class ImageMappings implements ImageMappingColumns {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private ImageMappings() {}
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "image_mappings");
+
+ /**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of image mappings.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image_mappings";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} of a single image mapping.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/image_mappings";
+ }
+
+ /**
* A combined view of bookmarks and history. All bookmarks in all folders are included and
* no folders are included.
+ * @hide
*/
public static final class Combined implements CommonColumns, HistoryColumns, ImageColumns {
/**
@@ -505,6 +685,7 @@ public class BrowserContract {
/**
* A table that stores settings specific to the browser. Only support query and insert.
+ * @hide
*/
public static final class Settings {
/**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 0e9306b..e4729c7 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -7452,7 +7452,7 @@ public final class ContactsContract {
/**
* <p>
* API allowing applications to send usage information for each {@link Data} row to the
- * Contacts Provider.
+ * Contacts Provider. Applications can also clear all usage information.
* </p>
* <p>
* With the feedback, Contacts Provider may return more contextually appropriate results for
@@ -7497,6 +7497,12 @@ public final class ContactsContract {
* boolean successful = resolver.update(uri, new ContentValues(), null, null) > 0;
* </pre>
* </p>
+ * <p>
+ * Applications can also clear all usage information with:
+ * <pre>
+ * boolean successful = resolver.delete(DataUsageFeedback.FEEDBACK_URI, null, null) > 0;
+ * </pre>
+ * </p>
*/
public static final class DataUsageFeedback {
diff --git a/core/java/android/service/dreams/Dream.java b/core/java/android/service/dreams/Dream.java
new file mode 100644
index 0000000..83464c9
--- /dev/null
+++ b/core/java/android/service/dreams/Dream.java
@@ -0,0 +1,392 @@
+/**
+ *
+ */
+package android.service.dreams;
+
+import com.android.internal.policy.PolicyManager;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+import android.view.ActionMode;
+import android.view.IWindowManager;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+
+/**
+ * @hide
+ *
+ */
+public class Dream extends Service implements Window.Callback {
+ private final static boolean DEBUG = true;
+ private final static String TAG = "Dream";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * To be supported, the service must also require the
+ * {@link android.Manifest.permission#BIND_WALLPAPER} permission so
+ * that other applications can not abuse it.
+ */
+ @SdkConstant(SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "android.service.dreams.Dream";
+
+ private Window mWindow;
+
+ private WindowManager mWindowManager;
+ private IDreamManager mSandman;
+
+ private boolean mInteractive;
+
+ final Handler mHandler = new Handler();
+
+ boolean mFinished = false;
+
+ // begin Window.Callback methods
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (!mInteractive) {
+ finish();
+ return true;
+ }
+ return mWindow.superDispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+ if (!mInteractive) {
+ finish();
+ return true;
+ }
+ return mWindow.superDispatchKeyShortcutEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (!mInteractive) {
+ finish();
+ return true;
+ }
+ return mWindow.superDispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (!mInteractive) {
+ finish();
+ return true;
+ }
+ return mWindow.superDispatchTrackballEvent(event);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (!mInteractive) {
+ finish();
+ return true;
+ }
+ return mWindow.superDispatchGenericMotionEvent(event);
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ return false;
+ }
+
+ @Override
+ public View onCreatePanelView(int featureId) {
+ return null;
+ }
+
+ @Override
+ public boolean onCreatePanelMenu(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onPreparePanel(int featureId, View view, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onMenuItemSelected(int featureId, MenuItem item) {
+ return false;
+ }
+
+ @Override
+ public void onWindowAttributesChanged(LayoutParams attrs) {
+
+ }
+
+ @Override
+ public void onContentChanged() {
+
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ mWindow.addFlags(
+ WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ );
+ lightsOut();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ }
+
+ @Override
+ public void onPanelClosed(int featureId, Menu menu) {
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ return false;
+ }
+
+ @Override
+ public ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback callback) {
+ return null;
+ }
+
+ @Override
+ public void onActionModeStarted(ActionMode mode) {
+ }
+
+ @Override
+ public void onActionModeFinished(ActionMode mode) {
+ }
+ // end Window.Callback methods
+
+ public WindowManager getWindowManager() {
+ return mWindowManager;
+ }
+
+ public Window getWindow() {
+ return mWindow;
+ }
+
+ /**
+ * Called when this Dream is constructed. Place your initialization here.
+ *
+ * Subclasses must call through to the superclass implementation.
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ if (DEBUG) Slog.v(TAG, "Dream created on thread " + Thread.currentThread().getId());
+
+ mSandman = IDreamManager.Stub.asInterface(ServiceManager.getService("dreams"));
+ }
+
+ /**
+ * Called when this Dream is started. Place your initialization here.
+ *
+ * Subclasses must call through to the superclass implementation.
+ *
+ * XXX(dsandler) Might want to make this final and have a different method for clients to override
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ /**
+ * Inflate a layout resource and set it to be the content view for this Dream.
+ * Behaves similarly to {@link android.app.Activity#setContentView(int)}.
+ *
+ * @param layoutResID Resource ID to be inflated.
+ *
+ * @see #setContentView(android.view.View)
+ * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
+ */
+ public void setContentView(int layoutResID) {
+ getWindow().setContentView(layoutResID);
+ }
+
+ /**
+ * Set a view to be the content view for this Dream.
+ * Behaves similarly to {@link android.app.Activity#setContentView(android.view.View)},
+ * including using {@link ViewGroup.LayoutParams#MATCH_PARENT} as the layout height and width of the view.
+ *
+ * @param view The desired content to display.
+ *
+ * @see #setContentView(int)
+ * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
+ */
+ public void setContentView(View view) {
+ getWindow().setContentView(view);
+ }
+
+ /**
+ * Set a view to be the content view for this Dream.
+ * Behaves similarly to
+ * {@link android.app.Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}.
+ *
+ * @param view The desired content to display.
+ * @param params Layout parameters for the view.
+ *
+ * @see #setContentView(android.view.View)
+ * @see #setContentView(int)
+ */
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getWindow().setContentView(view, params);
+ }
+
+ /**
+ * Add a view to the Dream's window, leaving other content views in place.
+ *
+ * @param view The desired content to display.
+ * @param params Layout parameters for the view.
+ */
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ getWindow().addContentView(view, params);
+ }
+
+ /**
+ * @param mInteractive the mInteractive to set
+ */
+ public void setInteractive(boolean mInteractive) {
+ this.mInteractive = mInteractive;
+ }
+
+ /**
+ * @return the mInteractive
+ */
+ public boolean isInteractive() {
+ return mInteractive;
+ }
+
+ /** Convenience method for setting View.SYSTEM_UI_FLAG_LOW_PROFILE on the content view. */
+ protected void lightsOut() {
+ // turn the lights down low
+ final View v = mWindow.getDecorView();
+ if (v != null) {
+ v.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ }
+ }
+
+ /**
+ * Finds a view that was identified by the id attribute from the XML that
+ * was processed in {@link #onCreate}.
+ *
+ * @return The view if found or null otherwise.
+ */
+ public View findViewById(int id) {
+ return getWindow().findViewById(id);
+ }
+
+ /**
+ * Called when this Dream is being removed from the screen and stopped.
+ */
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mWindowManager.removeView(mWindow.getDecorView());
+ }
+
+ /**
+ * Creates a new dream window, attaches the current content view, and shows it.
+ *
+ * @param windowToken Binder to attach to the window to allow access to the correct window type.
+ * @hide
+ */
+ final /*package*/ void attach(IBinder windowToken) {
+ if (DEBUG) Slog.v(TAG, "Dream attached on thread " + Thread.currentThread().getId());
+
+ mWindow = PolicyManager.makeNewWindow(this);
+ mWindow.setCallback(this);
+ mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+ mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
+
+ if (DEBUG) Slog.v(TAG, "attaching window token: " + windowToken
+ + " to window of type " + WindowManager.LayoutParams.TYPE_DREAM);
+
+ WindowManager.LayoutParams lp = mWindow.getAttributes();
+ lp.type = WindowManager.LayoutParams.TYPE_DREAM;
+ lp.token = windowToken;
+ lp.windowAnimations = com.android.internal.R.style.Animation_Dream;
+
+ //WindowManagerImpl.getDefault().addView(mWindow.getDecorView(), lp);
+
+ if (DEBUG) Slog.v(TAG, "created and attached window: " + mWindow);
+
+ mWindow.setWindowManager(null, windowToken, "dream", true);
+ mWindowManager = mWindow.getWindowManager();
+
+ // now make it visible
+ mHandler.post(new Runnable(){
+ @Override
+ public void run() {
+ if (DEBUG) Slog.v(TAG, "Dream window added on thread " + Thread.currentThread().getId());
+
+ getWindowManager().addView(mWindow.getDecorView(), mWindow.getAttributes());
+ }});
+ }
+
+ /**
+ * Stop the dream and wake up.
+ *
+ * After this method is called, the service will be stopped.
+ */
+ public void finish() {
+ if (mFinished) return;
+ try {
+ mSandman.awaken(); // assuming we were started by the DreamManager
+ stopSelf(); // if launched via any other means
+ mFinished = true;
+ } catch (RemoteException ex) {
+ // sigh
+ }
+ }
+
+ class IDreamServiceWrapper extends IDreamService.Stub {
+ public IDreamServiceWrapper() {
+ }
+
+ public void attach(IBinder windowToken) {
+ Dream.this.attach(windowToken);
+ }
+ }
+
+ /**
+ * Implement to return the implementation of the internal accessibility
+ * service interface. Subclasses should not override.
+ */
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new IDreamServiceWrapper();
+ }
+}
diff --git a/core/java/android/service/dreams/DreamManagerService.java b/core/java/android/service/dreams/DreamManagerService.java
new file mode 100644
index 0000000..8712fa2
--- /dev/null
+++ b/core/java/android/service/dreams/DreamManagerService.java
@@ -0,0 +1,182 @@
+package android.service.dreams;
+
+import static android.provider.Settings.Secure.SCREENSAVER_COMPONENT;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import com.android.internal.view.IInputMethod;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+import android.view.IWindowManager;
+import android.view.WindowManager;
+
+/**
+ *
+ * @hide
+ *
+ */
+
+public class DreamManagerService
+ extends IDreamManager.Stub
+ implements ServiceConnection
+{
+ private static final boolean DEBUG = true;
+ private static final String TAG = "DreamManagerService";
+
+ final Object mLock = new Object[0];
+
+ private Context mContext;
+ private IWindowManager mIWindowManager;
+
+ private ComponentName mCurrentDreamComponent;
+ private IDreamService mCurrentDream;
+ private Binder mCurrentDreamToken;
+
+ public DreamManagerService(Context context) {
+ if (DEBUG) Slog.v(TAG, "DreamManagerService startup");
+ mContext = context;
+ mIWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService(Context.WINDOW_SERVICE));
+ }
+
+ private void checkPermission(String permission) {
+ if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(permission)) {
+ throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
+ + ", must have permission " + permission);
+ }
+ }
+
+ // IDreamManager method
+ public void dream() {
+ ComponentName name = getDreamComponent();
+ if (name != null) {
+ synchronized (mLock) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ bindDreamComponentL(name, false);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+ }
+
+ // IDreamManager method
+ public void setDreamComponent(ComponentName name) {
+ Settings.Secure.putString(mContext.getContentResolver(), SCREENSAVER_COMPONENT, name.flattenToString());
+ }
+
+ // IDreamManager method
+ public ComponentName getDreamComponent() {
+ // TODO(dsandler) don't load this every time, watch the value
+ String component = Settings.Secure.getString(mContext.getContentResolver(), SCREENSAVER_COMPONENT);
+ if (component == null) {
+ component = mContext.getResources().getString(
+ com.android.internal.R.string.config_defaultDreamComponent);
+ }
+ if (component != null) {
+ return ComponentName.unflattenFromString(component);
+ } else {
+ return null;
+ }
+ }
+
+ // IDreamManager method
+ public void testDream(ComponentName name) {
+ if (DEBUG) Slog.v(TAG, "startDream name=" + name
+ + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+// checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
+ synchronized (mLock) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ bindDreamComponentL(name, true);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ // IDreamManager method
+ public void awaken() {
+ if (DEBUG) Slog.v(TAG, "awaken()");
+ synchronized (mLock) {
+ if (mCurrentDream != null) {
+ mContext.unbindService(this);
+ }
+ }
+ }
+
+ public void bindDreamComponentL(ComponentName componentName, boolean test) {
+ if (DEBUG) Slog.v(TAG, "bindDreamComponent: componentName=" + componentName
+ + " pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .setComponent(componentName)
+ .addFlags(
+ Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+ )
+ .putExtra("android.dreams.TEST", test);
+
+ if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+ Slog.w(TAG, "unable to bind service: " + componentName);
+ return;
+ }
+ mCurrentDreamComponent = componentName;
+ mCurrentDreamToken = new Binder();
+ try {
+ if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurrentDreamToken
+ + " for window type: " + WindowManager.LayoutParams.TYPE_DREAM);
+ mIWindowManager.addWindowToken(mCurrentDreamToken,
+ WindowManager.LayoutParams.TYPE_DREAM);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to add window token. Proceed at your own risk.");
+ }
+
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) Slog.v(TAG, "connected to dream: " + name + " binder=" + service + " thread=" + Thread.currentThread().getId());
+
+ mCurrentDream = IDreamService.Stub.asInterface(service);
+ try {
+ if (DEBUG) Slog.v(TAG, "attaching with token:" + mCurrentDreamToken);
+ mCurrentDream.attach(mCurrentDreamToken);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Unable to send window token to dream:" + ex);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) Slog.v(TAG, "disconnected: " + name + " service: " + mCurrentDream);
+ mCurrentDream = null;
+ mCurrentDreamToken = null;
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Dreamland:");
+ pw.print(" component="); pw.println(mCurrentDreamComponent);
+ pw.print(" token="); pw.println(mCurrentDreamToken);
+ pw.print(" dream="); pw.println(mCurrentDream);
+ }
+
+ public void systemReady() {
+ if (DEBUG) Slog.v(TAG, "ready to dream!");
+ }
+
+}
diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl
new file mode 100644
index 0000000..7225013
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamManager.aidl
@@ -0,0 +1,30 @@
+/**
+ * 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.service.dreams;
+
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.content.ComponentName;
+
+/** @hide */
+interface IDreamManager {
+ void dream();
+ void awaken();
+ void setDreamComponent(in ComponentName componentName);
+ ComponentName getDreamComponent();
+ void testDream(in ComponentName componentName);
+} \ No newline at end of file
diff --git a/core/java/android/service/dreams/IDreamService.aidl b/core/java/android/service/dreams/IDreamService.aidl
new file mode 100644
index 0000000..1bb241a
--- /dev/null
+++ b/core/java/android/service/dreams/IDreamService.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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.service.dreams;
+
+/**
+ * @hide
+ */
+oneway interface IDreamService {
+ void attach(IBinder windowToken);
+}
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index f7a7eb8..11c169e 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -18,6 +18,7 @@ package android.text;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.util.Log;
import com.android.internal.util.ArrayUtils;
@@ -50,6 +51,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
public SpannableStringBuilder(CharSequence text, int start, int end) {
int srclen = end - start;
+ if (srclen < 0) throw new StringIndexOutOfBoundsException();
+
int len = ArrayUtils.idealCharArraySize(srclen + 1);
mText = new char[len];
mGapStart = srclen;
@@ -72,7 +75,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (spans[i] instanceof NoCopySpan) {
continue;
}
-
+
int st = sp.getSpanStart(spans[i]) - start;
int en = sp.getSpanEnd(spans[i]) - start;
int fl = sp.getSpanFlags(spans[i]);
@@ -87,7 +90,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (en > end - start)
en = end - start;
- setSpan(spans[i], st, en, fl);
+ setSpan(false, spans[i], st, en, fl);
}
}
}
@@ -125,46 +128,38 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
private void resizeFor(int size) {
- int newlen = ArrayUtils.idealCharArraySize(size + 1);
- char[] newtext = new char[newlen];
+ final int oldLength = mText.length;
+ final int newLength = ArrayUtils.idealCharArraySize(size + 1);
+ final int after = oldLength - (mGapStart + mGapLength);
- int after = mText.length - (mGapStart + mGapLength);
+ char[] newText = new char[newLength];
+ System.arraycopy(mText, 0, newText, 0, mGapStart);
+ System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
+ mText = newText;
- System.arraycopy(mText, 0, newtext, 0, mGapStart);
- System.arraycopy(mText, mText.length - after,
- newtext, newlen - after, after);
+ final int delta = newLength - oldLength;
+ mGapLength += delta;
+ if (mGapLength < 1)
+ new Exception("mGapLength < 1").printStackTrace();
for (int i = 0; i < mSpanCount; i++) {
- if (mSpanStarts[i] > mGapStart)
- mSpanStarts[i] += newlen - mText.length;
- if (mSpanEnds[i] > mGapStart)
- mSpanEnds[i] += newlen - mText.length;
+ if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
+ if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
}
-
- int oldlen = mText.length;
- mText = newtext;
- mGapLength += mText.length - oldlen;
-
- if (mGapLength < 1)
- new Exception("mGapLength < 1").printStackTrace();
}
private void moveGapTo(int where) {
if (where == mGapStart)
return;
- boolean atend = (where == length());
+ boolean atEnd = (where == length());
if (where < mGapStart) {
int overlap = mGapStart - where;
-
- System.arraycopy(mText, where,
- mText, mGapStart + mGapLength - overlap, overlap);
+ System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
} else /* where > mGapStart */ {
int overlap = where - mGapStart;
-
- System.arraycopy(mText, where + mGapLength - overlap,
- mText, mGapStart, overlap);
+ System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
}
// XXX be more clever
@@ -179,7 +174,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
else if (start == where) {
int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
- if (flag == POINT || (atend && flag == PARAGRAPH))
+ if (flag == POINT || (atEnd && flag == PARAGRAPH))
start += mGapLength;
}
@@ -190,7 +185,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
else if (end == where) {
int flag = (mSpanFlags[i] & END_MASK);
- if (flag == POINT || (atend && flag == PARAGRAPH))
+ if (flag == POINT || (atEnd && flag == PARAGRAPH))
end += mGapLength;
}
@@ -217,7 +212,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (mGapLength > 2 * length())
resizeFor(length());
-
+
return ret; // == this
}
@@ -225,7 +220,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
public void clear() {
replace(0, length(), "", 0, 0);
}
-
+
// Documentation from interface
public void clearSpans() {
for (int i = mSpanCount - 1; i >= 0; i--) {
@@ -262,45 +257,50 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
return append(String.valueOf(text));
}
- private void change(int start, int end, CharSequence tb, int tbstart, int tbend) {
- checkRange("replace", start, end);
+ private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
+ // Can be negative
+ final int nbNewChars = (csEnd - csStart) - (end - start);
for (int i = mSpanCount - 1; i >= 0; i--) {
- if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
- int st = mSpanStarts[i];
- if (st > mGapStart)
- st -= mGapLength;
+ int spanStart = mSpanStarts[i];
+ if (spanStart > mGapStart)
+ spanStart -= mGapLength;
- int en = mSpanEnds[i];
- if (en > mGapStart)
- en -= mGapLength;
+ int spanEnd = mSpanEnds[i];
+ if (spanEnd > mGapStart)
+ spanEnd -= mGapLength;
- int ost = st;
- int oen = en;
+ if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
+ int ost = spanStart;
+ int oen = spanEnd;
int clen = length();
- if (st > start && st <= end) {
- for (st = end; st < clen; st++)
- if (st > end && charAt(st - 1) == '\n')
+ if (spanStart > start && spanStart <= end) {
+ for (spanStart = end; spanStart < clen; spanStart++)
+ if (spanStart > end && charAt(spanStart - 1) == '\n')
break;
}
- if (en > start && en <= end) {
- for (en = end; en < clen; en++)
- if (en > end && charAt(en - 1) == '\n')
+ if (spanEnd > start && spanEnd <= end) {
+ for (spanEnd = end; spanEnd < clen; spanEnd++)
+ if (spanEnd > end && charAt(spanEnd - 1) == '\n')
break;
}
- if (st != ost || en != oen)
- setSpan(mSpans[i], st, en, mSpanFlags[i]);
+ if (spanStart != ost || spanEnd != oen)
+ setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
}
+
+ int flags = 0;
+ if (spanStart == start) flags |= SPAN_START_AT_START;
+ else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
+ if (spanEnd == start) flags |= SPAN_END_AT_START;
+ else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
+ mSpanFlags[i] |= flags;
}
moveGapTo(end);
- // Can be negative
- final int nbNewChars = (tbend - tbstart) - (end - start);
-
if (nbNewChars >= mGapLength) {
resizeFor(mText.length + nbNewChars - mGapLength);
}
@@ -311,47 +311,24 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (mGapLength < 1)
new Exception("mGapLength < 1").printStackTrace();
- TextUtils.getChars(tb, tbstart, tbend, mText, start);
-
- if (tb instanceof Spanned) {
- Spanned sp = (Spanned) tb;
- Object[] spans = sp.getSpans(tbstart, tbend, Object.class);
-
- for (int i = 0; i < spans.length; i++) {
- int st = sp.getSpanStart(spans[i]);
- int en = sp.getSpanEnd(spans[i]);
-
- if (st < tbstart)
- st = tbstart;
- if (en > tbend)
- en = tbend;
-
- if (getSpanStart(spans[i]) < 0) {
- setSpan(false, spans[i],
- st - tbstart + start,
- en - tbstart + start,
- sp.getSpanFlags(spans[i]));
- }
- }
- }
+ TextUtils.getChars(cs, csStart, csEnd, mText, start);
if (end > start) {
// no need for span fixup on pure insertion
boolean atEnd = (mGapStart + mGapLength == mText.length);
for (int i = mSpanCount - 1; i >= 0; i--) {
- if (mSpanStarts[i] >= start &&
- mSpanStarts[i] < mGapStart + mGapLength) {
+ if (mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength) {
int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
- if (flag == POINT || (flag == PARAGRAPH && atEnd))
- mSpanStarts[i] = mGapStart + mGapLength;
- else
- mSpanStarts[i] = start;
+ if (flag == POINT || (flag == PARAGRAPH && atEnd)) {
+ mSpanStarts[i] = mGapStart + mGapLength;
+ } else {
+ mSpanStarts[i] = start;
+ }
}
- if (mSpanEnds[i] >= start &&
- mSpanEnds[i] < mGapStart + mGapLength) {
+ if (mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) {
int flag = (mSpanFlags[i] & END_MASK);
if (flag == POINT || (flag == PARAGRAPH && atEnd))
@@ -360,12 +337,34 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
mSpanEnds[i] = start;
}
- // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE
+ // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE, which are POINT_MARK and could
+ // get their boundaries swapped by the above code
if (mSpanEnds[i] < mSpanStarts[i]) {
removeSpan(i);
}
}
}
+
+ mSpanCountBeforeAdd = mSpanCount;
+
+ if (cs instanceof Spanned) {
+ Spanned sp = (Spanned) cs;
+ Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
+
+ for (int i = 0; i < spans.length; i++) {
+ int st = sp.getSpanStart(spans[i]);
+ int en = sp.getSpanEnd(spans[i]);
+
+ if (st < csStart) st = csStart;
+ if (en > csEnd) en = csEnd;
+
+ // Add span only if this object is not yet used as a span in this string
+ if (getSpanStart(spans[i]) < 0 && !(spans[i] instanceof SpanWatcher)) {
+ setSpan(false, spans[i], st - csStart + start, en - csStart + start,
+ sp.getSpanFlags(spans[i]));
+ }
+ }
+ }
}
private void removeSpan(int i) {
@@ -397,7 +396,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
// Documentation from interface
public SpannableStringBuilder replace(final int start, final int end,
- CharSequence tb, int tbstart, int tbend) {
+ CharSequence tb, int tbstart, int tbend) {
+ checkRange("replace", start, end);
+
int filtercount = mFilters.length;
for (int i = 0; i < filtercount; i++) {
CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
@@ -412,81 +413,108 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
final int origLen = end - start;
final int newLen = tbend - tbstart;
- if (origLen == 0 && newLen == 0) {
- return this;
- }
-
TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
sendBeforeTextChanged(textWatchers, start, origLen, newLen);
- if (origLen == 0 || newLen == 0) {
- change(start, end, tb, tbstart, tbend);
- } else {
- int selstart = Selection.getSelectionStart(this);
- int selend = Selection.getSelectionEnd(this);
-
- // XXX just make the span fixups in change() do the right thing
- // instead of this madness!
-
- checkRange("replace", start, end);
- moveGapTo(end);
-
- if (mGapLength < 2)
- resizeFor(length() + 1);
-
- for (int i = mSpanCount - 1; i >= 0; i--) {
- if (mSpanStarts[i] == mGapStart)
- mSpanStarts[i]++;
-
- if (mSpanEnds[i] == mGapStart)
- mSpanEnds[i]++;
- }
+ // Try to keep the cursor / selection at the same relative position during
+ // a text replacement. If replaced or replacement text length is zero, this
+ // is already taken care of.
+ boolean adjustSelection = origLen != 0 && newLen != 0;
+ int selectionStart = 0;
+ int selectionEnd = 0;
+ if (adjustSelection) {
+ selectionStart = Selection.getSelectionStart(this);
+ selectionEnd = Selection.getSelectionEnd(this);
+ }
- mText[mGapStart] = ' ';
- mGapStart++;
- mGapLength--;
+ change(start, end, tb, tbstart, tbend);
- if (mGapLength < 1) {
- new Exception("mGapLength < 1").printStackTrace();
- }
+ if (adjustSelection) {
+ if (selectionStart > start && selectionStart < end) {
+ final int offset = (selectionStart - start) * newLen / origLen;
+ selectionStart = start + offset;
- change(start + 1, start + 1, tb, tbstart, tbend);
- change(start, start + 1, "", 0, 0);
- change(start + newLen, start + newLen + origLen, "", 0, 0);
-
- /*
- * Special case to keep the cursor in the same position
- * if it was somewhere in the middle of the replaced region.
- * If it was at the start or the end or crossing the whole
- * replacement, it should already be where it belongs.
- * TODO: Is there some more general mechanism that could
- * accomplish this?
- */
- if (selstart > start && selstart < end) {
- long off = selstart - start;
-
- off = off * newLen / (end - start);
- selstart = (int) off + start;
-
- setSpan(false, Selection.SELECTION_START, selstart, selstart,
+ setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
Spanned.SPAN_POINT_POINT);
}
- if (selend > start && selend < end) {
- long off = selend - start;
+ if (selectionEnd > start && selectionEnd < end) {
+ final int offset = (selectionEnd - start) * newLen / origLen;
+ selectionEnd = start + offset;
- off = off * newLen / (end - start);
- selend = (int) off + start;
-
- setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT);
+ setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
+ Spanned.SPAN_POINT_POINT);
}
}
sendTextChanged(textWatchers, start, origLen, newLen);
sendAfterTextChanged(textWatchers);
+ // Span watchers need to be called after text watchers, which may update the layout
+ sendToSpanWatchers(start, end, newLen - origLen);
+
return this;
}
+ private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
+ for (int i = 0; i < mSpanCountBeforeAdd; i++) {
+ int spanStart = mSpanStarts[i];
+ int spanEnd = mSpanEnds[i];
+ if (spanStart > mGapStart) spanStart -= mGapLength;
+ if (spanEnd > mGapStart) spanEnd -= mGapLength;
+ int spanFlags = mSpanFlags[i];
+
+ int newReplaceEnd = replaceEnd + nbNewChars;
+ boolean spanChanged = false;
+ int previousSpanStart = spanStart;
+ if (spanStart > newReplaceEnd) {
+ if (nbNewChars != 0) {
+ previousSpanStart -= nbNewChars;
+ spanChanged = true;
+ }
+ } else if (spanStart >= replaceStart) {
+ // No change if span start was already at replace interval boundaries before replace
+ if ((spanStart != replaceStart ||
+ ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
+ (spanStart != newReplaceEnd ||
+ ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
+ // TODO previousSpanStart is incorrect, but we would need to save all the
+ // previous spans' positions before replace to provide it
+ spanChanged = true;
+ }
+ }
+ int previousSpanEnd = spanEnd;
+ if (spanEnd > newReplaceEnd) {
+ if (nbNewChars != 0) {
+ previousSpanEnd -= nbNewChars;
+ spanChanged = true;
+ }
+ } else if (spanEnd >= replaceStart) {
+ // No change if span start was already at replace interval boundaries before replace
+ if ((spanEnd != replaceStart ||
+ ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
+ (spanEnd != newReplaceEnd ||
+ ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
+ // TODO same as above for previousSpanEnd
+ spanChanged = true;
+ }
+ }
+
+ if (spanChanged) {
+ sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
+ }
+ mSpanFlags[i] &= ~SPAN_START_END_MASK;
+ }
+
+ // The spans starting at mIntermediateSpanCount were added from the replacement text
+ for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
+ int spanStart = mSpanStarts[i];
+ int spanEnd = mSpanEnds[i];
+ if (spanStart > mGapStart) spanStart -= mGapLength;
+ if (spanEnd > mGapStart) spanEnd -= mGapLength;
+ sendSpanAdded(mSpans[i], spanStart, spanEnd);
+ }
+ }
+
/**
* Mark the specified range of text with the specified object.
* The flags determine how the span will behave when text is
@@ -497,12 +525,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
private void setSpan(boolean send, Object what, int start, int end, int flags) {
- int nstart = start;
- int nend = end;
-
checkRange("setSpan", start, end);
- if ((flags & START_MASK) == (PARAGRAPH << START_SHIFT)) {
+ int flagsStart = (flags & START_MASK) >> START_SHIFT;
+ if (flagsStart == PARAGRAPH) {
if (start != 0 && start != length()) {
char c = charAt(start - 1);
@@ -511,7 +537,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
}
- if ((flags & END_MASK) == PARAGRAPH) {
+ int flagsEnd = flags & END_MASK;
+ if (flagsEnd == PARAGRAPH) {
if (end != 0 && end != length()) {
char c = charAt(end - 1);
@@ -520,21 +547,30 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
}
+ // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
+ if (flagsStart == POINT && flagsEnd == MARK && start == end) {
+ if (send) Log.e("SpannableStringBuilder",
+ "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
+ // Silently ignore invalid spans when they are created from this class.
+ // This avoids the duplication of the above test code before all the
+ // calls to setSpan that are done in this class
+ return;
+ }
+
+ int nstart = start;
+ int nend = end;
+
if (start > mGapStart) {
start += mGapLength;
} else if (start == mGapStart) {
- int flag = (flags & START_MASK) >> START_SHIFT;
-
- if (flag == POINT || (flag == PARAGRAPH && start == length()))
+ if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
start += mGapLength;
}
if (end > mGapStart) {
end += mGapLength;
} else if (end == mGapStart) {
- int flag = (flags & END_MASK);
-
- if (flag == POINT || (flag == PARAGRAPH && end == length()))
+ if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
end += mGapLength;
}
@@ -815,13 +851,12 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (end <= mGapStart) {
System.arraycopy(mText, start, dest, destoff, end - start);
} else if (start >= mGapStart) {
- System.arraycopy(mText, start + mGapLength,
- dest, destoff, end - start);
+ System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
} else {
System.arraycopy(mText, start, dest, destoff, mGapStart - start);
System.arraycopy(mText, mGapStart + mGapLength,
- dest, destoff + (mGapStart - start),
- end - mGapStart);
+ dest, destoff + (mGapStart - start),
+ end - mGapStart);
}
}
@@ -890,12 +925,14 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
}
- private void sendSpanChanged(Object what, int s, int e, int st, int en) {
- SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class);
- int n = recip.length;
-
+ private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
+ // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
+ // called, so that the order of the span does not affect this broadcast.
+ SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
+ Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
+ int n = spanWatchers.length;
for (int i = 0; i < n; i++) {
- recip[i].onSpanChanged(this, what, s, e, st, en);
+ spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
}
}
@@ -906,26 +943,23 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
private void checkRange(final String operation, int start, int end) {
if (end < start) {
throw new IndexOutOfBoundsException(operation + " " +
- region(start, end) +
- " has end before start");
+ region(start, end) + " has end before start");
}
int len = length();
if (start > len || end > len) {
throw new IndexOutOfBoundsException(operation + " " +
- region(start, end) +
- " ends beyond length " + len);
+ region(start, end) + " ends beyond length " + len);
}
if (start < 0 || end < 0) {
throw new IndexOutOfBoundsException(operation + " " +
- region(start, end) +
- " starts before 0");
+ region(start, end) + " starts before 0");
}
}
-/*
+ /*
private boolean isprint(char c) { // XXX
if (c >= ' ' && c <= '~')
return true;
@@ -1004,7 +1038,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
System.out.print("\n");
}
-*/
+ */
/**
* Don't call this yourself -- exists for Canvas to use internally.
@@ -1050,7 +1084,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
}
}
- /**
+ /**
* Don't call this yourself -- exists for Paint to use internally.
* {@hide}
*/
@@ -1086,8 +1120,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
if (end <= mGapStart) {
ret = p.getTextWidths(mText, start, end - start, widths);
} else if (start >= mGapStart) {
- ret = p.getTextWidths(mText, start + mGapLength, end - start,
- widths);
+ ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
} else {
char[] buf = TextUtils.obtain(end - start);
@@ -1232,12 +1265,21 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable
private int[] mSpanEnds;
private int[] mSpanFlags;
private int mSpanCount;
+ private int mSpanCountBeforeAdd;
// TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
+ private static final int MARK = 1;
private static final int POINT = 2;
private static final int PARAGRAPH = 3;
private static final int START_MASK = 0xF0;
private static final int END_MASK = 0x0F;
private static final int START_SHIFT = 4;
+
+ // These bits are not (currently) used by SPANNED flags
+ private static final int SPAN_START_AT_START = 0x1000;
+ private static final int SPAN_START_AT_END = 0x2000;
+ private static final int SPAN_END_AT_START = 0x4000;
+ private static final int SPAN_END_AT_END = 0x8000;
+ private static final int SPAN_START_END_MASK = 0xF000;
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index b0399fd..8bc36b7 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -237,6 +237,17 @@ public abstract class HardwareRenderer {
abstract boolean validate();
/**
+ * This method ensures the hardware renderer is in a valid state
+ * before executing the specified action.
+ *
+ * This method will attempt to set a valid state even if the window
+ * the renderer is attached to was destroyed.
+ *
+ * @return true if the action was run
+ */
+ abstract boolean safelyRun(Runnable action);
+
+ /**
* Setup the hardware renderer for drawing. This is called whenever the
* size of the target surface changes or when the surface is first created.
*
@@ -1380,20 +1391,39 @@ public abstract class HardwareRenderer {
}
@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 false;
+ usePbufferSurface(managedContext.getContext());
+ }
+ try {
+ action.run();
+ } finally {
if (needsContext) {
- Gl20RendererEglContext managedContext =
- (Gl20RendererEglContext) sEglContextStorage.get();
- if (managedContext == null) return;
- usePbufferSurface(managedContext.getContext());
+ sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
+ EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
+ }
- destroyResources(view);
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
+ 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);
+ }
+ });
}
}
@@ -1434,6 +1464,9 @@ public abstract class HardwareRenderer {
} else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE);
}
+
+ sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ EGL_NO_CONTEXT);
}
private static void usePbufferSurface(EGLContext eglContext) {
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 6f8d09b..75b2c746 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -39,13 +39,12 @@ import java.util.List;
* </p>
*/
public final class InputDevice implements Parcelable {
- private int mId;
- private String mName;
- private String mDescriptor;
- private int mSources;
- private int mKeyboardType;
- private String mKeyCharacterMapFile;
-
+ private final int mId;
+ private final String mName;
+ private final String mDescriptor;
+ private final int mSources;
+ private final int mKeyboardType;
+ private final KeyCharacterMap mKeyCharacterMap;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
/**
@@ -292,8 +291,43 @@ public final class InputDevice implements Parcelable {
*/
public static final int KEYBOARD_TYPE_ALPHABETIC = 2;
+ public static final Parcelable.Creator<InputDevice> CREATOR =
+ new Parcelable.Creator<InputDevice>() {
+ public InputDevice createFromParcel(Parcel in) {
+ return new InputDevice(in);
+ }
+ public InputDevice[] newArray(int size) {
+ return new InputDevice[size];
+ }
+ };
+
// Called by native code.
- private InputDevice() {
+ private InputDevice(int id, String name, String descriptor, int sources,
+ int keyboardType, KeyCharacterMap keyCharacterMap) {
+ mId = id;
+ mName = name;
+ mDescriptor = descriptor;
+ mSources = sources;
+ mKeyboardType = keyboardType;
+ mKeyCharacterMap = keyCharacterMap;
+ }
+
+ private InputDevice(Parcel in) {
+ mId = in.readInt();
+ mName = in.readString();
+ mDescriptor = in.readString();
+ mSources = in.readInt();
+ mKeyboardType = in.readInt();
+ mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
+
+ for (;;) {
+ int axis = in.readInt();
+ if (axis < 0) {
+ break;
+ }
+ addMotionRange(axis, in.readInt(),
+ in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
+ }
}
/**
@@ -302,7 +336,7 @@ public final class InputDevice implements Parcelable {
* @return The input device or null if not found.
*/
public static InputDevice getDevice(int id) {
- return InputManager.getInputDevice(id);
+ return InputManager.getInstance().getInputDevice(id);
}
/**
@@ -310,7 +344,7 @@ public final class InputDevice implements Parcelable {
* @return The input device ids.
*/
public static int[] getDeviceIds() {
- return InputManager.getInputDeviceIds();
+ return InputManager.getInstance().getInputDeviceIds();
}
/**
@@ -356,6 +390,22 @@ public final class InputDevice implements Parcelable {
}
/**
+ * Returns true if the device is a virtual input device rather than a real one,
+ * such as the virtual keyboard (see {@link KeyCharacterMap#VIRTUAL_KEYBOARD}).
+ * <p>
+ * Virtual input devices are provided to implement system-level functionality
+ * and should not be seen or configured by users.
+ * </p>
+ *
+ * @return True if the device is virtual.
+ *
+ * @see KeyCharacterMap#VIRTUAL_KEYBOARD
+ */
+ public boolean isVirtual() {
+ return mId < 0;
+ }
+
+ /**
* Gets the name of this input device.
* @return The input device name.
*/
@@ -384,11 +434,7 @@ public final class InputDevice implements Parcelable {
* @return The key character map.
*/
public KeyCharacterMap getKeyCharacterMap() {
- return KeyCharacterMap.load(mId);
- }
-
- String getKeyCharacterMapFile() {
- return mKeyCharacterMapFile;
+ return mKeyCharacterMap;
}
/**
@@ -453,6 +499,7 @@ public final class InputDevice implements Parcelable {
return mMotionRanges;
}
+ // Called from native code.
private void addMotionRange(int axis, int source,
float min, float max, float flat, float fuzz) {
mMotionRanges.add(new MotionRange(axis, source, min, max, flat, fuzz));
@@ -545,37 +592,6 @@ public final class InputDevice implements Parcelable {
}
}
- public static final Parcelable.Creator<InputDevice> CREATOR
- = new Parcelable.Creator<InputDevice>() {
- public InputDevice createFromParcel(Parcel in) {
- InputDevice result = new InputDevice();
- result.readFromParcel(in);
- return result;
- }
-
- public InputDevice[] newArray(int size) {
- return new InputDevice[size];
- }
- };
-
- private void readFromParcel(Parcel in) {
- mId = in.readInt();
- mName = in.readString();
- mDescriptor = in.readString();
- mSources = in.readInt();
- mKeyboardType = in.readInt();
- mKeyCharacterMapFile = in.readString();
-
- for (;;) {
- int axis = in.readInt();
- if (axis < 0) {
- break;
- }
- addMotionRange(axis, in.readInt(),
- in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
- }
- }
-
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mId);
@@ -583,7 +599,7 @@ public final class InputDevice implements Parcelable {
out.writeString(mDescriptor);
out.writeInt(mSources);
out.writeInt(mKeyboardType);
- out.writeString(mKeyCharacterMapFile);
+ mKeyCharacterMap.writeToParcel(out, flags);
final int numRanges = mMotionRanges.size();
for (int i = 0; i < numRanges; i++) {
@@ -623,8 +639,6 @@ public final class InputDevice implements Parcelable {
}
description.append("\n");
- description.append(" Key Character Map: ").append(mKeyCharacterMapFile).append("\n");
-
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index b03f086..3d165ea 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -16,18 +16,21 @@
package android.view;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.text.method.MetaKeyKeyListener;
import android.util.AndroidRuntimeException;
import android.util.SparseIntArray;
import android.hardware.input.InputManager;
import android.util.SparseArray;
+import android.view.InputDevice.MotionRange;
import java.lang.Character;
/**
* Describes the keys provided by a keyboard device and their associated labels.
*/
-public class KeyCharacterMap {
+public class KeyCharacterMap implements Parcelable {
/**
* The id of the device's primary built in keyboard is always 0.
*
@@ -134,12 +137,20 @@ public class KeyCharacterMap {
*/
public static final int MODIFIER_BEHAVIOR_CHORDED_OR_TOGGLED = 1;
- private static SparseArray<KeyCharacterMap> sInstances = new SparseArray<KeyCharacterMap>();
+ public static final Parcelable.Creator<KeyCharacterMap> CREATOR =
+ new Parcelable.Creator<KeyCharacterMap>() {
+ public KeyCharacterMap createFromParcel(Parcel in) {
+ return new KeyCharacterMap(in);
+ }
+ public KeyCharacterMap[] newArray(int size) {
+ return new KeyCharacterMap[size];
+ }
+ };
- private final int mDeviceId;
private int mPtr;
- private static native int nativeLoad(String file);
+ private static native int nativeReadFromParcel(Parcel in);
+ private static native void nativeWriteToParcel(int ptr, Parcel out);
private static native void nativeDispose(int ptr);
private static native char nativeGetCharacter(int ptr, int keyCode, int metaState);
@@ -149,10 +160,20 @@ public class KeyCharacterMap {
private static native char nativeGetMatch(int ptr, int keyCode, char[] chars, int metaState);
private static native char nativeGetDisplayLabel(int ptr, int keyCode);
private static native int nativeGetKeyboardType(int ptr);
- private static native KeyEvent[] nativeGetEvents(int ptr, int deviceId, char[] chars);
+ private static native KeyEvent[] nativeGetEvents(int ptr, char[] chars);
+
+ private KeyCharacterMap(Parcel in) {
+ if (in == null) {
+ throw new IllegalArgumentException("parcel must not be null");
+ }
+ mPtr = nativeReadFromParcel(in);
+ if (mPtr == 0) {
+ throw new RuntimeException("Could not read KeyCharacterMap from parcel.");
+ }
+ }
- private KeyCharacterMap(int deviceId, int ptr) {
- mDeviceId = deviceId;
+ // Called from native
+ private KeyCharacterMap(int ptr) {
mPtr = ptr;
}
@@ -174,33 +195,16 @@ public class KeyCharacterMap {
* is missing from the system.
*/
public static KeyCharacterMap load(int deviceId) {
- synchronized (sInstances) {
- KeyCharacterMap map = sInstances.get(deviceId);
- if (map == null) {
- String kcm = null;
- if (deviceId != VIRTUAL_KEYBOARD) {
- InputDevice device = InputDevice.getDevice(deviceId);
- if (device != null) {
- kcm = device.getKeyCharacterMapFile();
- }
- }
- if (kcm == null || kcm.length() == 0) {
- kcm = "/system/usr/keychars/Virtual.kcm";
- }
- int ptr = nativeLoad(kcm); // might throw
- map = new KeyCharacterMap(deviceId, ptr);
- sInstances.put(deviceId, map);
+ final InputManager im = InputManager.getInstance();
+ InputDevice inputDevice = im.getInputDevice(deviceId);
+ if (inputDevice == null) {
+ inputDevice = im.getInputDevice(VIRTUAL_KEYBOARD);
+ if (inputDevice == null) {
+ throw new UnavailableException(
+ "Could not load key character map for device " + deviceId);
}
- return map;
}
- }
-
- /**
- * TODO implement this
- * @hide
- */
- public static KeyCharacterMap load(CharSequence contents) {
- return null;
+ return inputDevice.getKeyCharacterMap();
}
/**
@@ -437,7 +441,7 @@ public class KeyCharacterMap {
if (chars == null) {
throw new IllegalArgumentException("chars must not be null.");
}
- return nativeGetEvents(mPtr, mDeviceId, chars);
+ return nativeGetEvents(mPtr, chars);
}
/**
@@ -527,7 +531,7 @@ public class KeyCharacterMap {
* @return True if at least one attached keyboard supports the specified key code.
*/
public static boolean deviceHasKey(int keyCode) {
- return InputManager.deviceHasKeys(new int[] { keyCode })[0];
+ return InputManager.getInstance().deviceHasKeys(new int[] { keyCode })[0];
}
/**
@@ -541,7 +545,20 @@ public class KeyCharacterMap {
* at the same index in the key codes array.
*/
public static boolean[] deviceHasKeys(int[] keyCodes) {
- return InputManager.deviceHasKeys(keyCodes);
+ return InputManager.getInstance().deviceHasKeys(keyCodes);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (out == null) {
+ throw new IllegalArgumentException("parcel must not be null");
+ }
+ nativeWriteToParcel(mPtr, out);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
}
/**
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 77fd8d2..e51ba3d 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -2715,6 +2715,67 @@ public final class MotionEvent extends InputEvent implements Parcelable {
}
/**
+ * Adds all of the movement samples of the specified event to this one if
+ * it is compatible. To be compatible, the event must have the same device id,
+ * source, action, flags, pointer count, pointer properties.
+ *
+ * Only applies to {@link #ACTION_MOVE} or {@link #ACTION_HOVER_MOVE} events.
+ *
+ * @param event The event whose movements samples should be added to this one
+ * if possible.
+ * @return True if batching was performed or false if batching was not possible.
+ * @hide
+ */
+ public final boolean addBatch(MotionEvent event) {
+ final int action = nativeGetAction(mNativePtr);
+ if (action != ACTION_MOVE && action != ACTION_HOVER_MOVE) {
+ return false;
+ }
+ if (action != nativeGetAction(event.mNativePtr)) {
+ return false;
+ }
+
+ if (nativeGetDeviceId(mNativePtr) != nativeGetDeviceId(event.mNativePtr)
+ || nativeGetSource(mNativePtr) != nativeGetSource(event.mNativePtr)
+ || nativeGetFlags(mNativePtr) != nativeGetFlags(event.mNativePtr)) {
+ return false;
+ }
+
+ final int pointerCount = nativeGetPointerCount(mNativePtr);
+ if (pointerCount != nativeGetPointerCount(event.mNativePtr)) {
+ return false;
+ }
+
+ synchronized (gSharedTempLock) {
+ ensureSharedTempPointerCapacity(Math.max(pointerCount, 2));
+ final PointerProperties[] pp = gSharedTempPointerProperties;
+ final PointerCoords[] pc = gSharedTempPointerCoords;
+
+ for (int i = 0; i < pointerCount; i++) {
+ nativeGetPointerProperties(mNativePtr, i, pp[0]);
+ nativeGetPointerProperties(event.mNativePtr, i, pp[1]);
+ if (!pp[0].equals(pp[1])) {
+ return false;
+ }
+ }
+
+ final int metaState = nativeGetMetaState(event.mNativePtr);
+ final int historySize = nativeGetHistorySize(event.mNativePtr);
+ for (int h = 0; h <= historySize; h++) {
+ final int historyPos = (h == historySize ? HISTORY_CURRENT : h);
+
+ for (int i = 0; i < pointerCount; i++) {
+ nativeGetPointerCoords(event.mNativePtr, i, historyPos, pc[i]);
+ }
+
+ final long eventTimeNanos = nativeGetEventTimeNanos(event.mNativePtr, historyPos);
+ nativeAddBatch(mNativePtr, eventTimeNanos, pc, metaState);
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns true if all points in the motion event are completely within the specified bounds.
* @hide
*/
@@ -3416,5 +3477,22 @@ public final class MotionEvent extends InputEvent implements Parcelable {
id = other.id;
toolType = other.toolType;
}
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof PointerProperties) {
+ return equals((PointerProperties)other);
+ }
+ return false;
+ }
+
+ private boolean equals(PointerProperties other) {
+ return other != null && id == other.id && toolType == other.toolType;
+ }
+
+ @Override
+ public int hashCode() {
+ return id | (toolType << 8);
+ }
}
}
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 3cd8b71..32029ba 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -187,7 +187,9 @@ public class TextureView extends View {
public void setOpaque(boolean opaque) {
if (opaque != mOpaque) {
mOpaque = opaque;
- updateLayer();
+ if (mLayer != null) {
+ updateLayer();
+ }
}
}
@@ -204,7 +206,18 @@ public class TextureView extends View {
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- destroySurface();
+ if (mLayer != null && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) {
+ boolean success = mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
+ @Override
+ public void run() {
+ destroySurface();
+ }
+ });
+
+ if (!success) {
+ Log.w(LOG_TAG, "TextureView was not able to destroy its surface: " + this);
+ }
+ }
}
private void destroySurface() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d62e32f..1fa19d1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4483,10 +4483,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
getDrawingRect(bounds);
info.setBoundsInParent(bounds);
- int[] locationOnScreen = mAttachInfo.mInvalidateChildLocation;
- getLocationOnScreen(locationOnScreen);
- bounds.offsetTo(0, 0);
- bounds.offset(locationOnScreen[0], locationOnScreen[1]);
+ getGlobalVisibleRect(bounds);
+ bounds.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
info.setBoundsInScreen(bounds);
if ((mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
@@ -8698,7 +8696,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
- invalidate(true);
+ postInvalidateOnAnimation();
}
}
}
@@ -8852,7 +8850,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (invalidate) {
// Invalidate to show the scrollbars
- invalidate(true);
+ postInvalidateOnAnimation();
}
if (scrollCache.state == ScrollabilityCache.OFF) {
@@ -9212,6 +9210,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
+ *
+ * @see #postDelayed
+ * @see #removeCallbacks
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
@@ -9241,6 +9242,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* result of true does not mean the Runnable will be processed --
* if the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
+ *
+ * @see #post
+ * @see #removeCallbacks
*/
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
@@ -9261,7 +9265,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @param action The Runnable that will be executed.
*
- * @hide
+ * @see #postOnAnimationDelayed
+ * @see #removeCallbacks
*/
public void postOnAnimation(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
@@ -9286,7 +9291,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param delayMillis The delay (in milliseconds) until the Runnable
* will be executed.
*
- * @hide
+ * @see #postOnAnimation
+ * @see #removeCallbacks
*/
public void postOnAnimationDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
@@ -9311,6 +9317,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* false otherwise. When the returned value is true, the Runnable
* may or may not have been actually removed from the message queue
* (for instance, if the Runnable was not in the queue already.)
+ *
+ * @see #post
+ * @see #postDelayed
+ * @see #postOnAnimation
+ * @see #postOnAnimationDelayed
*/
public boolean removeCallbacks(Runnable action) {
if (action != null) {
@@ -9335,6 +9346,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* only when this View is attached to a window.</p>
*
* @see #invalidate()
+ * @see #postInvalidateDelayed(long)
*/
public void postInvalidate() {
postInvalidateDelayed(0);
@@ -9354,6 +9366,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @see #invalidate(int, int, int, int)
* @see #invalidate(Rect)
+ * @see #postInvalidateDelayed(long, int, int, int, int)
*/
public void postInvalidate(int left, int top, int right, int bottom) {
postInvalidateDelayed(0, left, top, right, bottom);
@@ -9368,6 +9381,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @param delayMilliseconds the duration in milliseconds to delay the
* invalidation by
+ *
+ * @see #invalidate()
+ * @see #postInvalidate()
*/
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
@@ -9391,6 +9407,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param top The top coordinate of the rectangle to invalidate.
* @param right The right coordinate of the rectangle to invalidate.
* @param bottom The bottom coordinate of the rectangle to invalidate.
+ *
+ * @see #invalidate(int, int, int, int)
+ * @see #invalidate(Rect)
+ * @see #postInvalidate(int, int, int, int)
*/
public void postInvalidateDelayed(long delayMilliseconds, int left, int top,
int right, int bottom) {
@@ -9417,7 +9437,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* <p>This method can be invoked from outside of the UI thread
* only when this View is attached to a window.</p>
*
- * @hide
+ * @see #invalidate()
*/
public void postInvalidateOnAnimation() {
// We try only with the AttachInfo because there's no point in invalidating
@@ -9440,7 +9460,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param right The right coordinate of the rectangle to invalidate.
* @param bottom The bottom coordinate of the rectangle to invalidate.
*
- * @hide
+ * @see #invalidate(int, int, int, int)
+ * @see #invalidate(Rect)
*/
public void postInvalidateOnAnimation(int left, int top, int right, int bottom) {
// We try only with the AttachInfo because there's no point in invalidating
@@ -9769,7 +9790,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @attr ref android.R.styleable#View_scrollbarSize
*/
public int getScrollBarSize() {
- return mScrollCache == null ? ViewConfiguration.getScrollBarSize() :
+ return mScrollCache == null ? ViewConfiguration.get(mContext).getScaledScrollBarSize() :
mScrollCache.scrollBarSize;
}
@@ -12948,6 +12969,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* background
*/
public void setBackground(Drawable background) {
+ //noinspection deprecation
setBackgroundDrawable(background);
}
@@ -14273,7 +14295,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
+
if (animation != null) {
+ // If the screen is off assume the animation start time is now instead of
+ // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
+ // would cause the animation to start when the screen turns back on
+ if (mAttachInfo != null && !mAttachInfo.mScreenOn &&
+ animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
+ animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
+ }
animation.reset();
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 899fb32..2e3ff38 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5065,6 +5065,19 @@ public final class ViewRootImpl implements ViewParent,
}
/**
+ * Computes whether a view is visible on the screen.
+ *
+ * @param view The view to check.
+ * @return Whether the view is visible on the screen.
+ */
+ private boolean isDisplayedOnScreen(View view) {
+ return (view.mAttachInfo != null
+ && view.mAttachInfo.mWindowVisibility == View.VISIBLE
+ && view.getVisibility() == View.VISIBLE
+ && view.getGlobalVisibleRect(mTempRect));
+ }
+
+ /**
* Class for managing accessibility interactions initiated from the system
* and targeting the view hierarchy. A *ClientThread method is to be
* called from the interaction connection this ViewAncestor gives the
@@ -5175,7 +5188,7 @@ public final class ViewRootImpl implements ViewParent,
} else {
target = findViewByAccessibilityId(accessibilityViewId);
}
- if (target != null && target.getVisibility() == View.VISIBLE) {
+ if (target != null && isDisplayedOnScreen(target)) {
getAccessibilityNodePrefetcher().prefetchAccessibilityNodeInfos(target,
virtualDescendantId, prefetchFlags, infos);
}
@@ -5231,7 +5244,7 @@ public final class ViewRootImpl implements ViewParent,
}
if (root != null) {
View target = root.findViewById(viewId);
- if (target != null && target.getVisibility() == View.VISIBLE) {
+ if (target != null && isDisplayedOnScreen(target)) {
info = target.createAccessibilityNodeInfo();
}
}
@@ -5287,7 +5300,7 @@ public final class ViewRootImpl implements ViewParent,
} else {
target = ViewRootImpl.this.mView;
}
- if (target != null && target.getVisibility() == View.VISIBLE) {
+ if (target != null && isDisplayedOnScreen(target)) {
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
infos = provider.findAccessibilityNodeInfosByText(text,
@@ -5304,7 +5317,7 @@ public final class ViewRootImpl implements ViewParent,
final int viewCount = foundViews.size();
for (int i = 0; i < viewCount; i++) {
View foundView = foundViews.get(i);
- if (foundView.getVisibility() == View.VISIBLE) {
+ if (isDisplayedOnScreen(foundView)) {
provider = foundView.getAccessibilityNodeProvider();
if (provider != null) {
List<AccessibilityNodeInfo> infosFromProvider =
@@ -5367,7 +5380,7 @@ public final class ViewRootImpl implements ViewParent,
boolean succeeded = false;
try {
View target = findViewByAccessibilityId(accessibilityViewId);
- if (target != null && target.getVisibility() == View.VISIBLE) {
+ if (target != null && isDisplayedOnScreen(target)) {
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
succeeded = provider.performAccessibilityAction(action,
@@ -5505,7 +5518,7 @@ public final class ViewRootImpl implements ViewParent,
View child = parentGroup.getChildAt(i);
if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE
&& child.getAccessibilityViewId() != current.getAccessibilityViewId()
- && child.getVisibility() == View.VISIBLE) {
+ && isDisplayedOnScreen(child)) {
final long childNodeId = AccessibilityNodeInfo.makeNodeId(
child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
AccessibilityNodeInfo info = null;
@@ -5533,7 +5546,7 @@ public final class ViewRootImpl implements ViewParent,
final int childCount = rootGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = rootGroup.getChildAt(i);
- if (child.getVisibility() == View.VISIBLE
+ if (isDisplayedOnScreen(child)
&& outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
final long childNodeId = AccessibilityNodeInfo.makeNodeId(
child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f3ef329..bc310b0 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -423,6 +423,12 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_HIDDEN_NAV_CONSUMER = FIRST_SYSTEM_WINDOW+22;
/**
+ * Window type: Dreams (screen saver) window, just above keyguard.
+ * @hide
+ */
+ public static final int TYPE_DREAM = FIRST_SYSTEM_WINDOW+23;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index a45a87e..52bd860 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -432,23 +432,24 @@ public class WindowManagerImpl implements WindowManager {
*/
public void trimMemory(int level) {
if (HardwareRenderer.isAvailable()) {
- // On low and medium end gfx devices
- if (!ActivityManager.isHighEndGfx(getDefaultDisplay())) {
- if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) {
- // Destroy all hardware surfaces and resources associated to
- // known windows
- synchronized (this) {
- if (mViews == null) return;
- int count = mViews.length;
- for (int i = 0; i < count; i++) {
- mRoots[i].terminateHardwareResources();
- }
+ // On low-end gfx devices we trim when memory is moderate;
+ // on high-end devices we do this when low.
+ if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE
+ || (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
+ && !ActivityManager.isHighEndGfx(getDefaultDisplay()))) {
+ // Destroy all hardware surfaces and resources associated to
+ // known windows
+ synchronized (this) {
+ if (mViews == null) return;
+ int count = mViews.length;
+ for (int i = 0; i < count; i++) {
+ mRoots[i].terminateHardwareResources();
}
- // Force a full memory flush
- HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
- mNeedsEglTerminate = true;
- return;
}
+ // Force a full memory flush
+ HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+ mNeedsEglTerminate = true;
+ return;
}
HardwareRenderer.trimMemory(level);
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 491cd67..27baaea 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -667,7 +667,7 @@ public interface WindowManagerPolicy {
/**
* Create and return an animation to re-display a force hidden window.
*/
- public Animation createForceHideEnterAnimation();
+ public Animation createForceHideEnterAnimation(boolean onWallpaper);
/**
* Called from the input reader thread before a key is enqueued.
@@ -1049,6 +1049,31 @@ public interface WindowManagerPolicy {
public void lockNow();
/**
+ * Check to see if a screensaver should be run instead of powering off the screen on timeout.
+ *
+ * @return true if the screensaver should run, false if the screen should turn off.
+ *
+ * @hide
+ */
+ public boolean isScreenSaverEnabled();
+
+ /**
+ * Start the screensaver (if it is enabled and not yet running).
+ *
+ * @return Whether the screensaver was successfully started.
+ *
+ * @hide
+ */
+ public boolean startScreenSaver();
+
+ /**
+ * Stop the screensaver if it is running.
+ *
+ * @hide
+ */
+ public void stopScreenSaver();
+
+ /**
* Print the WindowManagerPolicy's state into the given stream.
*
* @param prefix Text to print at the front of each line.
diff --git a/core/java/android/webkit/AutoCompletePopup.java b/core/java/android/webkit/AutoCompletePopup.java
index b26156c..21d5e02 100644
--- a/core/java/android/webkit/AutoCompletePopup.java
+++ b/core/java/android/webkit/AutoCompletePopup.java
@@ -129,13 +129,13 @@ class AutoCompletePopup implements OnItemClickListener, Filter.FilterListener {
}
public void resetRect() {
- int left = mWebView.contentToViewX(mWebView.mEditTextBounds.left);
- int right = mWebView.contentToViewX(mWebView.mEditTextBounds.right);
+ int left = mWebView.contentToViewX(mWebView.mEditTextContentBounds.left);
+ int right = mWebView.contentToViewX(mWebView.mEditTextContentBounds.right);
int width = right - left;
mPopup.setWidth(width);
- int bottom = mWebView.contentToViewY(mWebView.mEditTextBounds.bottom);
- int top = mWebView.contentToViewY(mWebView.mEditTextBounds.top);
+ int bottom = mWebView.contentToViewY(mWebView.mEditTextContentBounds.bottom);
+ int top = mWebView.contentToViewY(mWebView.mEditTextContentBounds.top);
int height = bottom - top;
AbsoluteLayout.LayoutParams lp =
diff --git a/core/java/android/webkit/DeviceMotionAndOrientationManager.java b/core/java/android/webkit/DeviceMotionAndOrientationManager.java
index 79b78d8..ea1e9ff 100644
--- a/core/java/android/webkit/DeviceMotionAndOrientationManager.java
+++ b/core/java/android/webkit/DeviceMotionAndOrientationManager.java
@@ -22,9 +22,8 @@ package android.webkit;
*
* This could be part of WebViewCore, but have moved it to its own class to
* avoid bloat there.
- * @hide
*/
-public final class DeviceMotionAndOrientationManager {
+final class DeviceMotionAndOrientationManager {
private WebViewCore mWebViewCore;
public DeviceMotionAndOrientationManager(WebViewCore webViewCore) {
@@ -32,12 +31,12 @@ public final class DeviceMotionAndOrientationManager {
}
/**
- * Sets whether the Page for this WebViewCore should use a mock DeviceOrientation
+ * Sets that the Page for this WebViewCore should use a mock DeviceOrientation
* client.
*/
- public void useMock() {
+ public void setUseMock() {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- nativeUseMock(mWebViewCore);
+ nativeSetUseMock(mWebViewCore);
}
/**
@@ -66,7 +65,7 @@ public final class DeviceMotionAndOrientationManager {
}
// Native functions
- private static native void nativeUseMock(WebViewCore webViewCore);
+ private static native void nativeSetUseMock(WebViewCore webViewCore);
private static native void nativeSetMockOrientation(WebViewCore webViewCore,
boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
boolean canProvideGamma, double gamma);
diff --git a/core/java/android/webkit/DeviceMotionService.java b/core/java/android/webkit/DeviceMotionService.java
index b4d5759..9121429 100755
--- a/core/java/android/webkit/DeviceMotionService.java
+++ b/core/java/android/webkit/DeviceMotionService.java
@@ -153,6 +153,7 @@ final class DeviceMotionService implements SensorEventListener {
* SensorEventListener implementation.
* Callbacks happen on the thread on which we registered - the WebCore thread.
*/
+ @Override
public void onSensorChanged(SensorEvent event) {
assert(event.values.length == 3);
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
@@ -170,6 +171,7 @@ final class DeviceMotionService implements SensorEventListener {
}
}
+ @Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
}
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
index 47c8ab7..2e8656c 100755
--- a/core/java/android/webkit/DeviceOrientationService.java
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -184,6 +184,7 @@ final class DeviceOrientationService implements SensorEventListener {
* SensorEventListener implementation.
* Callbacks happen on the thread on which we registered - the WebCore thread.
*/
+ @Override
public void onSensorChanged(SensorEvent event) {
assert(event.values.length == 3);
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
@@ -217,6 +218,7 @@ final class DeviceOrientationService implements SensorEventListener {
}
}
+ @Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
}
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index 6c331ac..6b7263c 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -148,8 +148,8 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
mInput.showSoftInput(mEditText, 0);
}
- public void updateMatchCount(int matchIndex, int matchCount, boolean isNewFind) {
- if (!isNewFind) {
+ public void updateMatchCount(int matchIndex, int matchCount, boolean isEmptyFind) {
+ if (!isEmptyFind) {
mNumberOfMatches = matchCount;
mActiveMatchIndex = matchIndex;
updateMatchesString();
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 9492e38..d1cfc6b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -313,7 +313,6 @@ public class WebView extends AbsoluteLayout
/**
* Interface to listen for find results.
- * @hide
*/
public interface FindListener {
/**
@@ -1249,8 +1248,7 @@ public class WebView extends AbsoluteLayout
* Register the listener to be notified as find-on-page operations progress.
* This will replace the current listener.
*
- * @param listener An implementation of {@link WebView#FindListener}.
- * @hide
+ * @param listener An implementation of {@link FindListener}.
*/
public void setFindListener(FindListener listener) {
checkThread();
@@ -1258,11 +1256,15 @@ public class WebView extends AbsoluteLayout
}
/**
- * Highlight and scroll to the next occurance of String in findAll.
- * Wraps the page infinitely, and scrolls. Must be called after
- * calling findAll.
+ * Highlight and scroll to the next match found by {@link #findAll} or
+ * {@link #findAllAsync}, wrapping around page boundaries as necessary.
+ * Notifies any registered {@link FindListener}. If neither
+ * {@link #findAll} nor {@link #findAllAsync(String)} has been called yet,
+ * or if {@link #clearMatches} has been called since the last find
+ * operation, this function does nothing.
*
* @param forward Direction to search.
+ * @see #setFindListener
*/
public void findNext(boolean forward) {
checkThread();
@@ -1271,10 +1273,13 @@ public class WebView extends AbsoluteLayout
/**
* Find all instances of find on the page and highlight them.
+ * Notifies any registered {@link FindListener}.
*
* @param find String to find.
* @return int The number of occurances of the String "find"
* that were found.
+ * @deprecated {@link #findAllAsync} is preferred.
+ * @see #setFindListener
*/
public int findAll(String find) {
checkThread();
@@ -1283,10 +1288,12 @@ public class WebView extends AbsoluteLayout
/**
* Find all instances of find on the page and highlight them,
- * asynchronously.
+ * asynchronously. Notifies any registered {@link FindListener}.
+ * Successive calls to this or {@link #findAll} will cancel any
+ * pending searches.
*
* @param find String to find.
- * @hide
+ * @see #setFindListener
*/
public void findAllAsync(String find) {
checkThread();
@@ -1333,8 +1340,9 @@ public class WebView extends AbsoluteLayout
return getFactory().getStatics().findAddress(addr);
}
- /*
- * Clear the highlighting surrounding text matches created by findAll.
+ /**
+ * Clear the highlighting surrounding text matches created by
+ * {@link #findAll} or {@link #findAllAsync}.
*/
public void clearMatches() {
checkThread();
@@ -1531,6 +1539,7 @@ public class WebView extends AbsoluteLayout
*
* @deprecated The built-in zoom mechanism is preferred, see
* {@link WebSettings#setBuiltInZoomControls(boolean)}.
+ * @hide since API version 16.
*/
@Deprecated
public View getZoomControls() {
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 4c118ac..851fd22 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -57,6 +57,7 @@ import android.net.http.SslCertificate;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.provider.Settings;
@@ -100,7 +101,6 @@ import android.webkit.WebView.PictureListener;
import android.webkit.WebViewCore.DrawData;
import android.webkit.WebViewCore.EventHub;
import android.webkit.WebViewCore.TextFieldInitData;
-import android.webkit.WebViewCore.TouchEventData;
import android.webkit.WebViewCore.TouchHighlightData;
import android.webkit.WebViewCore.WebKitHitTest;
import android.widget.AbsoluteLayout;
@@ -461,6 +461,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
selectionStart = Math.min(selectionStart, editable.length());
selectionEnd = Math.min(selectionEnd, editable.length());
setSelection(selectionStart, selectionEnd);
+ finishComposingText();
}
public void replaceSelection(CharSequence text) {
@@ -827,6 +828,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
// the screen all-the-time. Good for profiling our drawing code
static private final boolean AUTO_REDRAW_HACK = false;
+
+ // The rate at which edit text is scrolled in content pixels per millisecond
+ static private final float TEXT_SCROLL_RATE = 0.01f;
+
+ // The presumed scroll rate for the first scroll of edit text
+ static private final long TEXT_SCROLL_FIRST_SCROLL_MS = 16;
+
// true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
private boolean mAutoRedraw;
@@ -846,12 +854,13 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private int mFieldPointer;
private PastePopupWindow mPasteWindow;
private AutoCompletePopup mAutoCompletePopup;
- Rect mEditTextBounds = new Rect();
+ Rect mEditTextContentBounds = new Rect();
Rect mEditTextContent = new Rect();
int mEditTextLayerId;
boolean mIsEditingText = false;
ArrayList<Message> mBatchedTextChanges = new ArrayList<Message>();
boolean mIsBatchingTextChanges = false;
+ private long mLastEditScroll = 0;
private static class OnTrimMemoryListener implements ComponentCallbacks2 {
private static OnTrimMemoryListener sInstance = null;
@@ -885,7 +894,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// the existing GL resources for the html5 video will be destroyed
// at native side.
// Here we just need to clean up the Surface Texture which is static.
- HTML5VideoInline.cleanupSurfaceTexture();
+ if (level >= TRIM_MEMORY_UI_HIDDEN) {
+ HTML5VideoInline.cleanupSurfaceTexture();
+ }
WebViewClassic.nativeOnTrimMemory(level);
}
@@ -978,6 +989,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
/**
* Touch mode
+ * TODO: Some of this is now unnecessary as it is handled by
+ * WebInputTouchDispatcher (such as click, long press, and double tap).
*/
private int mTouchMode = TOUCH_DONE_MODE;
private static final int TOUCH_INIT_MODE = 1;
@@ -991,35 +1004,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private static final int TOUCH_DRAG_LAYER_MODE = 9;
private static final int TOUCH_DRAG_TEXT_MODE = 10;
- // Whether to forward the touch events to WebCore
- // Can only be set by WebKit via JNI.
- private boolean mForwardTouchEvents = false;
-
- // Whether to prevent default during touch. The initial value depends on
- // mForwardTouchEvents. If WebCore wants all the touch events, it says yes
- // for touch down. Otherwise UI will wait for the answer of the first
- // confirmed move before taking over the control.
- private static final int PREVENT_DEFAULT_NO = 0;
- private static final int PREVENT_DEFAULT_MAYBE_YES = 1;
- private static final int PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN = 2;
- private static final int PREVENT_DEFAULT_YES = 3;
- private static final int PREVENT_DEFAULT_IGNORE = 4;
- private int mPreventDefault = PREVENT_DEFAULT_IGNORE;
-
// true when the touch movement exceeds the slop
private boolean mConfirmMove;
private boolean mTouchInEditText;
- // if true, touch events will be first processed by WebCore, if prevent
- // default is not set, the UI will continue handle them.
- private boolean mDeferTouchProcess;
-
- // to avoid interfering with the current touch events, track them
- // separately. Currently no snapping or fling in the deferred process mode
- private int mDeferTouchMode = TOUCH_DONE_MODE;
- private float mLastDeferTouchX;
- private float mLastDeferTouchY;
-
// Whether or not to draw the cursor ring.
private boolean mDrawCursorRing = true;
@@ -1057,9 +1045,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// pages with the space bar, in pixels.
private static final int PAGE_SCROLL_OVERLAP = 24;
- // Time between successive calls to text scroll fling animation
- private static final int TEXT_SCROLL_ANIMATION_DELAY_MS = 16;
-
/**
* These prevent calling requestLayout if either dimension is fixed. This
* depends on the layout parameters and the measure specs.
@@ -1227,9 +1212,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
static final int FOCUS_NODE_CHANGED = 147;
static final int AUTOFILL_FORM = 148;
- static final int ANIMATE_TEXT_SCROLL = 149;
+ static final int SCROLL_EDIT_TEXT = 149;
static final int EDIT_TEXT_SIZE_CHANGED = 150;
static final int SHOW_CARET_HANDLE = 151;
+ static final int UPDATE_CONTENT_BOUNDS = 152;
private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID;
private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT;
@@ -1406,7 +1392,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private boolean mSentAutoScrollMessage = false;
// used for serializing asynchronously handled touch events.
- private final TouchEventQueue mTouchEventQueue = new TouchEventQueue();
+ private WebViewInputDispatcher mInputDispatcher;
// Used to track whether picture updating was paused due to a window focus change.
private boolean mPictureUpdatePausedForFocusChange = false;
@@ -1496,6 +1482,68 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
+ private void onHandleUiEvent(MotionEvent event, int eventType, int flags) {
+ switch (eventType) {
+ case WebViewInputDispatcher.EVENT_TYPE_LONG_PRESS:
+ HitTestResult hitTest = getHitTestResult();
+ if (hitTest != null
+ && hitTest.getType() != HitTestResult.UNKNOWN_TYPE) {
+ performLongClick();
+ }
+ break;
+ case WebViewInputDispatcher.EVENT_TYPE_DOUBLE_TAP:
+ mZoomManager.handleDoubleTap(event.getX(), event.getY());
+ break;
+ case WebViewInputDispatcher.EVENT_TYPE_TOUCH:
+ onHandleUiTouchEvent(event);
+ break;
+ }
+ }
+
+ private void onHandleUiTouchEvent(MotionEvent ev) {
+ final ScaleGestureDetector detector =
+ mZoomManager.getMultiTouchGestureDetector();
+
+ float x = ev.getX();
+ float y = ev.getY();
+
+ if (detector != null) {
+ detector.onTouchEvent(ev);
+ if (detector.isInProgress()) {
+ mLastTouchTime = ev.getEventTime();
+ x = detector.getFocusX();
+ y = detector.getFocusY();
+
+ mWebView.cancelLongPress();
+ mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ if (!mZoomManager.supportsPanDuringZoom()) {
+ return;
+ }
+ mTouchMode = TOUCH_DRAG_MODE;
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ }
+ }
+
+ int action = ev.getActionMasked();
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
+ cancelTouch();
+ action = MotionEvent.ACTION_DOWN;
+ } else if (action == MotionEvent.ACTION_POINTER_UP && ev.getPointerCount() >= 2) {
+ // set mLastTouchX/Y to the remaining points for multi-touch.
+ mLastTouchX = Math.round(x);
+ mLastTouchY = Math.round(y);
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ // negative x or y indicate it is on the edge, skip it.
+ if (x < 0 || y < 0) {
+ return;
+ }
+ }
+
+ handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
+ }
+
// The webview that is bound to this WebViewClassic instance. Primarily needed for supplying
// as the first param in the WebViewClient and WebChromeClient callbacks.
final private WebView mWebView;
@@ -2865,7 +2913,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (rect.width() < mFocusedNode.mHitTestSlop) {
// ignore bounding boxes that are too small
continue;
- } else if (left != NO_LEFTEDGE && rect.width() > readingWidth) {
+ } else if (rect.width() > readingWidth) {
// stop when bounding box doesn't fit the screen width
// at reading scale
break;
@@ -3588,7 +3636,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
@Override
public void findNext(boolean forward) {
if (0 == mNativeClass) return; // client isn't initialized
- mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0);
+ if (mFindRequest != null) {
+ mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0, mFindRequest);
+ }
}
/**
@@ -3605,28 +3655,26 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private int findAllBody(String find, boolean isAsync) {
if (0 == mNativeClass) return 0; // client isn't initialized
- mLastFind = find;
+ mFindRequest = null;
if (find == null) return 0;
mWebViewCore.removeMessages(EventHub.FIND_ALL);
- WebViewCore.FindAllRequest request = new
- WebViewCore.FindAllRequest(find);
+ mFindRequest = new WebViewCore.FindAllRequest(find);
if (isAsync) {
- mWebViewCore.sendMessage(EventHub.FIND_ALL, request);
+ mWebViewCore.sendMessage(EventHub.FIND_ALL, mFindRequest);
return 0; // no need to wait for response
}
- synchronized(request) {
+ synchronized(mFindRequest) {
try {
- mWebViewCore.sendMessageAtFrontOfQueue(EventHub.FIND_ALL,
- request);
- while (request.mMatchCount == -1) {
- request.wait();
+ mWebViewCore.sendMessageAtFrontOfQueue(EventHub.FIND_ALL, mFindRequest);
+ while (mFindRequest.mMatchCount == -1) {
+ mFindRequest.wait();
}
}
catch (InterruptedException e) {
return 0;
}
+ return mFindRequest.mMatchCount;
}
- return request.mMatchCount;
}
/**
@@ -3657,7 +3705,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
return true;
}
if (text == null) {
- text = mLastFind;
+ text = mFindRequest == null ? null : mFindRequest.mSearchText;
}
if (text != null) {
mFindCallback.setText(text);
@@ -3683,9 +3731,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// or not we draw the highlights for matches.
private boolean mFindIsUp;
- // Keep track of the last string sent, so we can search again when find is
- // reopened.
- private String mLastFind;
+ // Keep track of the last find request sent.
+ private WebViewCore.FindAllRequest mFindRequest = null;
/**
* Return the first substring consisting of the address of a physical
@@ -3866,7 +3913,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
if (mAutoCompletePopup != null &&
mCurrentScrollingLayerId == mEditTextLayerId) {
- mEditTextBounds.offset(dx, dy);
+ mEditTextContentBounds.offset(dx, dy);
mAutoCompletePopup.resetRect();
}
nativeScrollLayer(mCurrentScrollingLayerId, x, y);
@@ -4369,8 +4416,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
boolean animateScroll = ((!mScroller.isFinished()
|| mVelocityTracker != null)
&& (mTouchMode != TOUCH_DRAG_MODE ||
- mHeldMotionless != MOTIONLESS_TRUE))
- || mDeferTouchMode == TOUCH_DRAG_MODE;
+ mHeldMotionless != MOTIONLESS_TRUE));
if (mTouchMode == TOUCH_DRAG_MODE) {
if (mHeldMotionless == MOTIONLESS_PENDING) {
mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
@@ -5008,8 +5054,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*
* debug only
*/
- public void useMockDeviceOrientation() {
- mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION);
+ public void setUseMockDeviceOrientation() {
+ mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_DEVICE_ORIENTATION);
}
/**
@@ -5570,7 +5616,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
addAccessibilityApisToJavaScript();
- mTouchEventQueue.reset();
updateHwAccelerated();
}
@@ -5819,20 +5864,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
private static final float MMA_WEIGHT_N = 5;
- private boolean hitFocusedPlugin(int contentX, int contentY) {
- // TODO: Figure out what to do with this (b/6111517)
- return false;
- }
-
- private boolean shouldForwardTouchEvent() {
- if (mFullScreenHolder != null) return true;
- if (mBlockWebkitViewMessages) return false;
- return mForwardTouchEvents
- && !mSelectingText
- && mPreventDefault != PREVENT_DEFAULT_IGNORE
- && mPreventDefault != PREVENT_DEFAULT_NO;
- }
-
private boolean inFullScreenMode() {
return mFullScreenHolder != null;
}
@@ -5902,23 +5933,17 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mWebView.requestFocus();
}
- if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, ev + " at " + ev.getEventTime()
- + " mTouchMode=" + mTouchMode
- + " numPointers=" + ev.getPointerCount());
+ if (mInputDispatcher == null) {
+ return false;
}
- // If WebKit wasn't interested in this multitouch gesture, enqueue
- // the event for handling directly rather than making the round trip
- // to WebKit and back.
- if (ev.getPointerCount() > 1 && mPreventDefault != PREVENT_DEFAULT_NO) {
- passMultiTouchToWebKit(ev, mTouchEventQueue.nextTouchSequence());
+ if (mInputDispatcher.postPointerEvent(ev, getScrollX(),
+ getScrollY() - getTitleHeight(), mZoomManager.getInvScale())) {
+ return true;
} else {
- mTouchEventQueue.enqueueTouchEvent(ev);
+ Log.w(LOGTAG, "mInputDispatcher rejected the event!");
+ return false;
}
-
- // Since all events are handled asynchronously, we always want the gesture stream.
- return true;
}
private float calculateDragAngle(int dx, int dy) {
@@ -5928,12 +5953,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/*
- * Common code for single touch and multi-touch.
- * (x, y) denotes current focus point, which is the touch point for single touch
- * and the middle point for multi-touch.
- */
- private boolean handleTouchEventCommon(MotionEvent ev, int action, int x, int y) {
- long eventTime = ev.getEventTime();
+ * Common code for single touch and multi-touch.
+ * (x, y) denotes current focus point, which is the touch point for single touch
+ * and the middle point for multi-touch.
+ */
+ private void handleTouchEventCommon(MotionEvent event, int action, int x, int y) {
+ ScaleGestureDetector detector = mZoomManager.getMultiTouchGestureDetector();
+
+ long eventTime = event.getEventTime();
// Due to the touch screen edge effect, a touch closer to the edge
// always snapped to the edge. As getViewWidth() can be different from
@@ -5949,7 +5976,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
switch (action) {
case MotionEvent.ACTION_DOWN: {
- mPreventDefault = PREVENT_DEFAULT_NO;
mConfirmMove = false;
mInitialHitTestResult = null;
if (!mEditTextScroller.isFinished()) {
@@ -5969,20 +5995,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) {
mTouchMode = TOUCH_DOUBLE_TAP_MODE;
} else {
- // commit the short press action for the previous tap
- doShortPress();
mTouchMode = TOUCH_INIT_MODE;
- mDeferTouchProcess = !mBlockWebkitViewMessages
- && (!inFullScreenMode() && mForwardTouchEvents)
- ? hitFocusedPlugin(contentX, contentY)
- : false;
}
} else { // the normal case
mTouchMode = TOUCH_INIT_MODE;
- mDeferTouchProcess = !mBlockWebkitViewMessages
- && (!inFullScreenMode() && mForwardTouchEvents)
- ? hitFocusedPlugin(contentX, contentY)
- : false;
+ // TODO: Have WebViewInputDispatch handle this
TouchHighlightData data = new TouchHighlightData();
data.mX = contentX;
data.mY = contentY;
@@ -5990,25 +6007,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
data.mNativeLayer = nativeScrollableLayer(
contentX, contentY, data.mNativeLayerRect, null);
data.mSlop = viewToContentDimension(mNavSlop);
- mTouchHighlightRegion.setEmpty();
+ removeTouchHighlight();
if (!mBlockWebkitViewMessages) {
- mTouchHighlightRequested = System.currentTimeMillis();
+ mTouchHighlightRequested = SystemClock.uptimeMillis();
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);
@@ -6055,58 +6059,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
SWITCH_TO_SHORTPRESS, TAP_TIMEOUT);
mPrivateHandler.sendEmptyMessageDelayed(
SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT);
- if (inFullScreenMode() || mDeferTouchProcess) {
- mPreventDefault = PREVENT_DEFAULT_YES;
- } else if (!mBlockWebkitViewMessages && mForwardTouchEvents) {
- mPreventDefault = PREVENT_DEFAULT_MAYBE_YES;
- } else {
- mPreventDefault = PREVENT_DEFAULT_NO;
- }
- // pass the touch events from UI thread to WebCore thread
- if (shouldForwardTouchEvent()) {
- TouchEventData ted = new TouchEventData();
- ted.mAction = action;
- ted.mIds = new int[1];
- ted.mIds[0] = ev.getPointerId(0);
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(contentX, contentY);
- ted.mPointsInView = new Point[1];
- ted.mPointsInView[0] = new Point(x, y);
- ted.mMetaState = ev.getMetaState();
- ted.mReprocess = mDeferTouchProcess;
- ted.mNativeLayer = nativeScrollableLayer(
- contentX, contentY, ted.mNativeLayerRect, null);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- if (mDeferTouchProcess) {
- // still needs to set them for compute deltaX/Y
- mLastTouchX = x;
- mLastTouchY = y;
- break;
- }
- if (!inFullScreenMode()) {
- mPrivateHandler.removeMessages(PREVENT_DEFAULT_TIMEOUT);
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
- action, 0), TAP_TIMEOUT);
- }
- }
}
startTouch(x, y, eventTime);
if (mIsEditingText) {
- mTouchInEditText = mEditTextBounds.contains(contentX, contentY);
+ mTouchInEditText = mEditTextContentBounds
+ .contains(contentX, contentY);
}
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;
}
@@ -6121,8 +6087,28 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
parent.requestDisallowInterceptTouchEvent(true);
}
if (deltaX != 0 || deltaY != 0) {
- snapDraggingCursor(contentX, contentY);
+ int handleX = contentX +
+ viewToContentDimension(mSelectDraggingOffset.x);
+ int handleY = contentY +
+ viewToContentDimension(mSelectDraggingOffset.y);
+ mSelectDraggingCursor.set(handleX, handleY);
+ boolean inCursorText =
+ mSelectDraggingTextQuad.containsPoint(handleX, handleY);
+ boolean inEditBounds = mEditTextContentBounds
+ .contains(handleX, handleY);
+ if (mIsEditingText && !inEditBounds) {
+ beginScrollEdit();
+ } else {
+ endScrollEdit();
+ }
+ if (inCursorText || (mIsEditingText && !inEditBounds)) {
+ snapDraggingCursor();
+ }
updateWebkitSelection();
+ if (!inCursorText && mIsEditingText && inEditBounds) {
+ // Visually snap even if we have moved the handle.
+ snapDraggingCursor();
+ }
mLastTouchX = x;
mLastTouchY = y;
invalidate();
@@ -6130,48 +6116,16 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
}
- // pass the touch events from UI thread to WebCore thread
- if (shouldForwardTouchEvent() && mConfirmMove && (firstMove
- || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) {
- TouchEventData ted = new TouchEventData();
- ted.mAction = action;
- ted.mIds = new int[1];
- ted.mIds[0] = ev.getPointerId(0);
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(contentX, contentY);
- ted.mPointsInView = new Point[1];
- ted.mPointsInView[0] = new Point(x, y);
- ted.mMetaState = ev.getMetaState();
- ted.mReprocess = mDeferTouchProcess;
- ted.mNativeLayer = mCurrentScrollingLayerId;
- ted.mNativeLayerRect.set(mScrollingLayerRect);
- ted.mMotionEvent = MotionEvent.obtain(ev);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- mLastSentTouchTime = eventTime;
- if (mDeferTouchProcess) {
- break;
- }
- if (firstMove && !inFullScreenMode()) {
- mPrivateHandler.sendMessageDelayed(mPrivateHandler
- .obtainMessage(PREVENT_DEFAULT_TIMEOUT,
- action, 0), TAP_TIMEOUT);
- }
- }
- if (mTouchMode == TOUCH_DONE_MODE
- || mPreventDefault == PREVENT_DEFAULT_YES) {
+ if (mTouchMode == TOUCH_DONE_MODE) {
// no dragging during scroll zoom animation, or when prevent
// default is yes
break;
}
if (mVelocityTracker == null) {
Log.e(LOGTAG, "Got null mVelocityTracker when "
- + "mPreventDefault = " + mPreventDefault
- + " mDeferTouchProcess = " + mDeferTouchProcess
+ " mTouchMode = " + mTouchMode);
} else {
- mVelocityTracker.addMovement(ev);
+ mVelocityTracker.addMovement(event);
}
if (mTouchMode != TOUCH_DRAG_MODE &&
@@ -6182,19 +6136,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
}
- if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES
- || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) {
- // track mLastTouchTime as we may need to do fling at
- // ACTION_UP
- mLastTouchTime = eventTime;
- break;
- }
-
// Only lock dragging to one axis if we don't have a scale in progress.
// Scaling implies free-roaming movement. Note this is only ever a question
// if mZoomManager.supportsPanDuringZoom() is true.
- final ScaleGestureDetector detector =
- mZoomManager.getMultiTouchGestureDetector();
mAverageAngle = calculateDragAngle(deltaX, deltaY);
if (detector == null || !detector.isInProgress()) {
// if it starts nearly horizontal or vertical, enforce it
@@ -6220,10 +6164,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
// do pan
- boolean done = false;
boolean keepScrollBarsVisible = false;
if (deltaX == 0 && deltaY == 0) {
- keepScrollBarsVisible = done = true;
+ keepScrollBarsVisible = true;
} else {
mAverageAngle +=
(calculateDragAngle(deltaX, deltaY) - mAverageAngle)
@@ -6300,37 +6243,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
ViewConfiguration.getScrollDefaultDelay());
// return false to indicate that we can't pan out of the
// view space
- return !done;
+ return;
} else {
mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
}
break;
}
case MotionEvent.ACTION_UP: {
+ endScrollEdit();
if (!mConfirmMove && mIsEditingText && mSelectionStarted &&
mIsCaretSelection) {
showPasteWindow();
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;
@@ -6339,66 +6265,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
case TOUCH_DOUBLE_TAP_MODE: // double tap
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- if (inFullScreenMode() || mDeferTouchProcess) {
- TouchEventData ted = new TouchEventData();
- ted.mIds = new int[1];
- ted.mIds[0] = ev.getPointerId(0);
- ted.mAction = WebViewCore.ACTION_DOUBLETAP;
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(contentX, contentY);
- ted.mPointsInView = new Point[1];
- ted.mPointsInView[0] = new Point(x, y);
- ted.mMetaState = ev.getMetaState();
- ted.mReprocess = mDeferTouchProcess;
- ted.mNativeLayer = nativeScrollableLayer(
- contentX, contentY,
- ted.mNativeLayerRect, null);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- } else if (mPreventDefault != PREVENT_DEFAULT_YES){
- mZoomManager.handleDoubleTap(mLastTouchX, mLastTouchY);
- mTouchMode = TOUCH_DONE_MODE;
- }
+ mTouchMode = TOUCH_DONE_MODE;
break;
case TOUCH_INIT_MODE: // tap
case TOUCH_SHORTPRESS_START_MODE:
case TOUCH_SHORTPRESS_MODE:
mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
- if (mConfirmMove) {
- Log.w(LOGTAG, "Miss a drag as we are waiting for" +
- " WebCore's response for touch down.");
- if (mPreventDefault != PREVENT_DEFAULT_YES
- && (computeMaxScrollX() > 0
- || computeMaxScrollY() > 0)) {
- // If the user has performed a very quick touch
- // sequence it is possible that we may get here
- // before WebCore has had a chance to process the events.
- // In this case, any call to preventDefault in the
- // JS touch handler will not have been executed yet.
- // Hence we will see both the UI (now) and WebCore
- // (when context switches) handling the event,
- // regardless of whether the web developer actually
- // doeses preventDefault in their touch handler. This
- // is the nature of our asynchronous touch model.
-
- // we will not rewrite drag code here, but we
- // will try fling if it applies.
- WebViewCore.reducePriority();
- // to get better performance, pause updating the
- // picture
- WebViewCore.pauseUpdatePicture(mWebViewCore);
- // fall through to TOUCH_DRAG_MODE
- } else {
- // WebKit may consume the touch event and modify
- // DOM. drawContentPicture() will be called with
- // animateSroll as true for better performance.
- // Force redraw in high-quality.
- invalidate();
- break;
- }
- } else {
+ if (!mConfirmMove) {
if (mSelectingText) {
// tapping on selection or controls does nothing
if (!mSelectionStarted) {
@@ -6413,8 +6287,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mPrivateHandler.sendEmptyMessageDelayed(
RELEASE_SINGLE_TAP, ViewConfiguration
.getDoubleTapTimeout());
- } else {
- doShortPress();
}
break;
}
@@ -6427,13 +6299,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// up, we don't want to do a fling
if (eventTime - mLastTouchTime <= MIN_FLING_TIME) {
if (mVelocityTracker == null) {
- Log.e(LOGTAG, "Got null mVelocityTracker when "
- + "mPreventDefault = "
- + mPreventDefault
- + " mDeferTouchProcess = "
- + mDeferTouchProcess);
+ Log.e(LOGTAG, "Got null mVelocityTracker");
} else {
- mVelocityTracker.addMovement(ev);
+ mVelocityTracker.addMovement(event);
}
// set to MOTIONLESS_IGNORE so that it won't keep
// removing and sending message in
@@ -6472,126 +6340,88 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
computeMaxScrollX(), 0, computeMaxScrollY());
invalidate();
}
- cancelWebCoreTouchEvent(contentX, contentY, false);
cancelTouch();
break;
}
}
- return true;
}
- private void passMultiTouchToWebKit(MotionEvent ev, long sequence) {
- TouchEventData ted = new TouchEventData();
- ted.mAction = ev.getActionMasked();
- final int count = ev.getPointerCount();
- ted.mIds = new int[count];
- ted.mPoints = new Point[count];
- ted.mPointsInView = new Point[count];
- for (int c = 0; c < count; c++) {
- ted.mIds[c] = ev.getPointerId(c);
- int x = viewToContentX((int) ev.getX(c) + getScrollX());
- int y = viewToContentY((int) ev.getY(c) + getScrollY());
- ted.mPoints[c] = new Point(x, y);
- ted.mPointsInView[c] = new Point((int) ev.getX(c), (int) ev.getY(c));
- }
- if (ted.mAction == MotionEvent.ACTION_POINTER_DOWN
- || ted.mAction == MotionEvent.ACTION_POINTER_UP) {
- ted.mActionIndex = ev.getActionIndex();
- }
- ted.mMetaState = ev.getMetaState();
- ted.mReprocess = true;
- ted.mMotionEvent = MotionEvent.obtain(ev);
- ted.mSequence = sequence;
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- mWebView.cancelLongPress();
- mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+ /**
+ * Returns the text scroll speed in content pixels per millisecond based on
+ * the touch location.
+ * @param coordinate The x or y touch coordinate in content space
+ * @param min The minimum coordinate (x or y) of the edit content bounds
+ * @param max The maximum coordinate (x or y) of the edit content bounds
+ */
+ private static float getTextScrollSpeed(int coordinate, int min, int max) {
+ if (coordinate < min) {
+ return (coordinate - min) * TEXT_SCROLL_RATE;
+ } else if (coordinate >= max) {
+ return (coordinate - max + 1) * TEXT_SCROLL_RATE;
+ } else {
+ return 0.0f;
+ }
}
- 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() + ")");
+ private void beginScrollEdit() {
+ if (mLastEditScroll == 0) {
+ mLastEditScroll = SystemClock.uptimeMillis() -
+ TEXT_SCROLL_FIRST_SCROLL_MS;
+ scrollEditWithCursor();
}
+ }
- 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();
- }
- }
- }
+ private void endScrollEdit() {
+ mLastEditScroll = 0;
+ }
- 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;
- }
+ private static int getTextScrollDelta(float speed, long deltaT) {
+ float distance = speed * deltaT;
+ int intDistance = (int)Math.floor(distance);
+ float probability = distance - intDistance;
+ if (Math.random() < probability) {
+ intDistance++;
}
-
- handleTouchEventCommon(ev, action, Math.round(x), Math.round(y));
+ return intDistance;
}
-
- 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();
+ /**
+ * Scrolls edit text a distance based on the last touch point,
+ * the last scroll time, and the edit text content bounds.
+ */
+ private void scrollEditWithCursor() {
+ if (mLastEditScroll != 0) {
+ int x = viewToContentX(mLastTouchX + getScrollX() + mSelectDraggingOffset.x);
+ float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left,
+ mEditTextContentBounds.right);
+ int y = viewToContentY(mLastTouchY + getScrollY() + mSelectDraggingOffset.y);
+ float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top,
+ mEditTextContentBounds.bottom);
+ if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) {
+ endScrollEdit();
+ } else {
+ long currentTime = SystemClock.uptimeMillis();
+ long timeSinceLastUpdate = currentTime - mLastEditScroll;
+ int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate);
+ int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate);
+ mLastEditScroll = currentTime;
+ if (deltaX == 0 && deltaY == 0) {
+ // By probability no text scroll this time. Try again later.
+ mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT,
+ TEXT_SCROLL_FIRST_SCROLL_MS);
+ } else {
+ int scrollX = getTextScrollX() + deltaX;
+ scrollX = Math.min(getMaxTextScrollX(), scrollX);
+ scrollX = Math.max(0, scrollX);
+ int scrollY = getTextScrollY() + deltaY;
+ scrollY = Math.min(getMaxTextScrollY(), scrollY);
+ scrollY = Math.max(0, scrollY);
+ scrollEditText(scrollX, scrollY);
+ int cursorX = mSelectDraggingCursor.x;
+ int cursorY = mSelectDraggingCursor.y;
+ mSelectDraggingCursor.set(x - deltaX, y - deltaY);
+ updateWebkitSelection();
+ mSelectDraggingCursor.set(cursorX, cursorY);
+ }
}
}
}
@@ -6736,23 +6566,22 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mTouchMode = TOUCH_DONE_MODE;
}
- private void snapDraggingCursor(int x, int y) {
- x += viewToContentDimension(mSelectDraggingOffset.x);
- y += viewToContentDimension(mSelectDraggingOffset.y);
- if (mSelectDraggingTextQuad.containsPoint(x, y)) {
- float scale = scaleAlongSegment(x, y,
- mSelectDraggingTextQuad.p4, mSelectDraggingTextQuad.p3);
- // clamp scale to ensure point is on the bottom segment
- scale = Math.max(0.0f, scale);
- scale = Math.min(scale, 1.0f);
- float newX = scaleCoordinate(scale,
- mSelectDraggingTextQuad.p4.x, mSelectDraggingTextQuad.p3.x);
- float newY = scaleCoordinate(scale,
- mSelectDraggingTextQuad.p4.y, mSelectDraggingTextQuad.p3.y);
- mSelectDraggingCursor.set(Math.round(newX), Math.round(newY));
- } else {
- mSelectDraggingCursor.set(x, y);
- }
+ private void snapDraggingCursor() {
+ float scale = scaleAlongSegment(
+ mSelectDraggingCursor.x, mSelectDraggingCursor.y,
+ mSelectDraggingTextQuad.p4, mSelectDraggingTextQuad.p3);
+ // clamp scale to ensure point is on the bottom segment
+ scale = Math.max(0.0f, scale);
+ scale = Math.min(scale, 1.0f);
+ float newX = scaleCoordinate(scale,
+ mSelectDraggingTextQuad.p4.x, mSelectDraggingTextQuad.p3.x);
+ float newY = scaleCoordinate(scale,
+ mSelectDraggingTextQuad.p4.y, mSelectDraggingTextQuad.p3.y);
+ int x = Math.max(mEditTextContentBounds.left,
+ Math.min(mEditTextContentBounds.right, Math.round(newX)));
+ int y = Math.max(mEditTextContentBounds.top,
+ Math.min(mEditTextContentBounds.bottom, Math.round(newY)));
+ mSelectDraggingCursor.set(x, y);
}
private static float scaleCoordinate(float scale, float coord1, float coord2) {
@@ -7231,39 +7060,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
return mZoomManager.zoomOut();
}
- private void doShortPress() {
- if (mNativeClass == 0) {
- return;
- }
- if (mPreventDefault == PREVENT_DEFAULT_YES) {
- return;
- }
- mTouchMode = TOUCH_DONE_MODE;
- switchOutDrawHistory();
- if (!mTouchHighlightRegion.isEmpty()) {
- // set mTouchHighlightRequested to 0 to cause an immediate
- // drawing of the touch rings
- mTouchHighlightRequested = 0;
- mWebView.invalidate(mTouchHighlightRegion.getBounds());
- mPrivateHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- removeTouchHighlight();
- }
- }, ViewConfiguration.getPressedStateDuration());
- }
- if (mFocusedNode != null && mFocusedNode.mIntentUrl != null) {
- mWebView.playSoundEffect(SoundEffectConstants.CLICK);
- overrideLoading(mFocusedNode.mIntentUrl);
- } else {
- WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData();
- // use "0" as generation id to inform WebKit to use the same x/y as
- // it used when processing GET_TOUCH_HIGHLIGHT_RECTS
- touchUpData.mMoveGeneration = 0;
- mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData);
- }
- }
-
/*
* Return true if the rect (e.g. plugin) is fully visible and maximized
* inside the WebView.
@@ -7544,490 +7340,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
private int getMaxTextScrollX() {
- return Math.max(0, mEditTextContent.width() - mEditTextBounds.width());
+ return Math.max(0, mEditTextContent.width() - mEditTextContentBounds.width());
}
private int getMaxTextScrollY() {
- return Math.max(0, mEditTextContent.height() - mEditTextBounds.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;
- }
- }
- }
- }
+ return Math.max(0, mEditTextContent.height() - mEditTextContentBounds.height());
}
//-------------------------------------------------------------------------
@@ -8038,7 +7355,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
/**
* General handler to receive message coming from webkit thread
*/
- class PrivateHandler extends Handler {
+ class PrivateHandler extends Handler implements WebViewInputDispatcher.UiCallbacks {
@Override
public void handleMessage(Message msg) {
// exclude INVAL_RECT_MSG_ID since it is frequently output
@@ -8079,20 +7396,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
((Message) msg.obj).sendToTarget();
break;
}
- case PREVENT_DEFAULT_TIMEOUT: {
- // if timeout happens, cancel it so that it won't block UI
- // to continue handling touch events
- if ((msg.arg1 == MotionEvent.ACTION_DOWN
- && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES)
- || (msg.arg1 == MotionEvent.ACTION_MOVE
- && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN)) {
- cancelWebCoreTouchEvent(
- viewToContentX(mLastTouchX + getScrollX()),
- viewToContentY(mLastTouchY + getScrollY()),
- true);
- }
- break;
- }
case SCROLL_SELECT_TEXT: {
if (mAutoScrollX == 0 && mAutoScrollY == 0) {
mSentAutoScrollMessage = false;
@@ -8108,48 +7411,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
break;
}
- case SWITCH_TO_SHORTPRESS: {
- if (mTouchMode == TOUCH_INIT_MODE) {
- mTouchMode = TOUCH_SHORTPRESS_MODE;
- } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) {
- mTouchMode = TOUCH_DONE_MODE;
- }
- break;
- }
- case SWITCH_TO_LONGPRESS: {
- removeTouchHighlight();
- if (inFullScreenMode() || mDeferTouchProcess) {
- TouchEventData ted = new TouchEventData();
- ted.mAction = WebViewCore.ACTION_LONGPRESS;
- ted.mIds = new int[1];
- ted.mIds[0] = 0;
- ted.mPoints = new Point[1];
- ted.mPoints[0] = new Point(viewToContentX(mLastTouchX + getScrollX()),
- viewToContentY(mLastTouchY + getScrollY()));
- ted.mPointsInView = new Point[1];
- ted.mPointsInView[0] = new Point(mLastTouchX, mLastTouchY);
- // metaState for long press is tricky. Should it be the
- // state when the press started or when the press was
- // released? Or some intermediary key state? For
- // simplicity for now, we don't set it.
- ted.mMetaState = 0;
- ted.mReprocess = mDeferTouchProcess;
- ted.mNativeLayer = nativeScrollableLayer(
- ted.mPoints[0].x, ted.mPoints[0].y,
- ted.mNativeLayerRect, null);
- ted.mSequence = mTouchEventQueue.nextTouchSequence();
- mTouchEventQueue.preQueueTouchEventData(ted);
- mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted);
- } else if (mPreventDefault != PREVENT_DEFAULT_YES) {
- mTouchMode = TOUCH_DONE_MODE;
- performLongClick();
- }
- break;
- }
- case RELEASE_SINGLE_TAP: {
- doShortPress();
- break;
- }
case SCROLL_TO_MSG_ID: {
// arg1 = animate, arg2 = onlyIfImeIsShowing
// obj = Point(x, y)
@@ -8207,6 +7468,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (mIsPaused) {
nativeSetPauseDrawing(mNativeClass, true);
}
+ mInputDispatcher = new WebViewInputDispatcher(this,
+ mWebViewCore.getInputDispatcherCallbacks());
break;
case UPDATE_TEXTFIELD_TEXT_MSG_ID:
// Make sure that the textfield is currently focused
@@ -8263,20 +7526,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
case WEBCORE_NEED_TOUCH_EVENTS:
- mForwardTouchEvents = (msg.arg1 != 0);
- break;
-
- case PREVENT_TOUCH_ID:
- if (inFullScreenMode()) {
- break;
- }
- TouchEventData ted = (TouchEventData) msg.obj;
-
- if (mTouchEventQueue.enqueueTouchEvent(ted)) {
- // WebCore is responding to us; remove pending timeout.
- // It will be re-posted when needed.
- removeMessages(PREVENT_DEFAULT_TIMEOUT);
- }
+ mInputDispatcher.setWebKitWantsTouchEvents(msg.arg1 != 0);
break;
case REQUEST_KEYBOARD:
@@ -8456,10 +7706,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mFieldPointer = initData.mFieldPointer;
mInputConnection.initEditorInfo(initData);
mInputConnection.setTextAndKeepSelection(initData.mText);
- mEditTextBounds.set(initData.mNodeBounds);
+ mEditTextContentBounds.set(initData.mContentBounds);
mEditTextLayerId = initData.mNodeLayerId;
nativeMapLayerRect(mNativeClass, mEditTextLayerId,
- mEditTextBounds);
+ mEditTextContentBounds);
mEditTextContent.set(initData.mContentRect);
relocateAutoCompletePopup();
}
@@ -8476,13 +7726,27 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
case UPDATE_MATCH_COUNT: {
- boolean isNewFind = mLastFind == null || !mLastFind.equals(msg.obj);
- if (mFindCallback != null)
- mFindCallback.updateMatchCount(msg.arg1, msg.arg2, isNewFind);
- if (mFindListener != null)
- mFindListener.onFindResultReceived(msg.arg1, msg.arg2, true);
+ WebViewCore.FindAllRequest request = (WebViewCore.FindAllRequest)msg.obj;
+ if (request == null) {
+ if (mFindCallback != null) {
+ mFindCallback.updateMatchCount(0, 0, true);
+ }
+ } else if (request == mFindRequest) {
+ int matchCount, matchIndex;
+ synchronized (mFindRequest) {
+ matchCount = request.mMatchCount;
+ matchIndex = request.mMatchIndex;
+ }
+ if (mFindCallback != null) {
+ mFindCallback.updateMatchCount(matchIndex, matchCount, false);
+ }
+ if (mFindListener != null) {
+ mFindListener.onFindResultReceived(matchIndex, matchCount, true);
+ }
+ }
break;
}
+
case CLEAR_CARET_HANDLE:
selectionDone();
break;
@@ -8500,10 +7764,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
msg.arg1, /* unused */0);
break;
- case ANIMATE_TEXT_SCROLL:
- computeEditTextScroll();
- break;
-
case EDIT_TEXT_SIZE_CHANGED:
if (msg.arg1 == mFieldPointer) {
mEditTextContent.set((Rect)msg.obj);
@@ -8518,11 +7778,34 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
break;
+ case UPDATE_CONTENT_BOUNDS:
+ mEditTextContentBounds.set((Rect) msg.obj);
+ break;
+
+ case SCROLL_EDIT_TEXT:
+ scrollEditWithCursor();
+ break;
+
default:
super.handleMessage(msg);
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) {
@@ -8585,13 +7868,16 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) {
return false;
}
- long delay = System.currentTimeMillis() - mTouchHighlightRequested;
+ long delay = SystemClock.uptimeMillis() - mTouchHighlightRequested;
if (delay < ViewConfiguration.getTapTimeout()) {
Rect r = mTouchHighlightRegion.getBounds();
mWebView.postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom);
return false;
}
- return true;
+ if (mInputDispatcher == null) {
+ return false;
+ }
+ return mInputDispatcher.shouldShowTapHighlight();
}
@@ -8701,8 +7987,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (viewRect.width() < getWidth() >> 1
|| viewRect.height() < getHeight() >> 1) {
mTouchHighlightRegion.union(viewRect);
- } else {
- Log.w(LOGTAG, "Skip the huge selection rect:"
+ } else if (DebugFlags.WEB_VIEW) {
+ Log.d(LOGTAG, "Skip the huge selection rect:"
+ viewRect);
}
}
@@ -8813,6 +8099,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
if (isPictureAfterFirstLayout) {
mViewManager.postReadyToDrawAll();
}
+ scrollEditWithCursor();
}
/**
@@ -8855,13 +8142,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
invalidate();
}
- private void computeEditTextScroll() {
- if (mEditTextScroller.computeScrollOffset()) {
- scrollEditText(mEditTextScroller.getCurrX(),
- mEditTextScroller.getCurrY());
- }
- }
-
private void scrollEditText(int scrollX, int scrollY) {
// Scrollable edit text. Scroll it.
float maxScrollX = getMaxTextScrollX();
@@ -8869,8 +8149,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mEditTextContent.offsetTo(-scrollX, -scrollY);
mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SCROLL_TEXT_INPUT, 0,
scrollY, (Float)scrollPercentX);
- mPrivateHandler.sendEmptyMessageDelayed(ANIMATE_TEXT_SCROLL,
- TEXT_SCROLL_ANIMATION_DELAY_MS);
}
private void beginTextBatch() {
@@ -9404,13 +8682,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
&& mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
hwAccelerated = true;
}
+
+ // result is of type LayerAndroid::InvalidateFlags, non zero means invalidate/redraw
int result = nativeSetHwAccelerated(mNativeClass, hwAccelerated);
- if (mWebViewCore == null || mBlockWebkitViewMessages) {
- return;
- }
- if (result == 1) {
- // Sync layers
- mWebViewCore.layersDraw();
+ if (mWebViewCore != null && !mBlockWebkitViewMessages && result != 0) {
+ mWebViewCore.contentDraw();
}
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index b4ebc09..15a2d48 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -40,6 +40,7 @@ import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
import android.webkit.WebViewClassic.FocusNodeHref;
+import android.webkit.WebViewInputDispatcher.WebKitCallbacks;
import junit.framework.Assert;
@@ -259,6 +260,10 @@ public final class WebViewCore {
return mBrowserFrame;
}
+ public WebKitCallbacks getInputDispatcherCallbacks() {
+ return mEventHub;
+ }
+
//-------------------------------------------------------------------------
// Common methods
//-------------------------------------------------------------------------
@@ -595,11 +600,6 @@ public final class WebViewCore {
Point wh);
/**
- * Update the layers' content
- */
- private native boolean nativeUpdateLayers(int nativeClass, int baseLayer);
-
- /**
* Notify webkit that animations have begun (on the hardware accelerated content)
*/
private native void nativeNotifyAnimationStarted(int nativeClass);
@@ -616,9 +616,6 @@ public final class WebViewCore {
int unichar, int repeatCount, boolean isShift, boolean isAlt,
boolean isSym, boolean isDown);
- private native void nativeClick(int nativeClass, int framePtr, int nodePtr,
- boolean fake);
-
private native void nativeSendListBoxChoices(int nativeClass,
boolean[] choices, int size);
@@ -661,8 +658,7 @@ public final class WebViewCore {
int x, int y);
private native String nativeRetrieveImageSource(int nativeClass,
int x, int y);
- private native void nativeTouchUp(int nativeClass,
- int touchGeneration, int framePtr, int nodePtr, int x, int y);
+ private native boolean nativeMouseClick(int nativeClass);
private native boolean nativeHandleTouchEvent(int nativeClass, int action,
int[] idArray, int[] xArray, int[] yArray, int count,
@@ -949,7 +945,7 @@ public final class WebViewCore {
public String mName;
public String mLabel;
public int mMaxLength;
- public Rect mNodeBounds;
+ public Rect mContentBounds;
public int mNodeLayerId;
public Rect mContentRect;
}
@@ -1025,8 +1021,8 @@ public final class WebViewCore {
"REQUEST_CURSOR_HREF", // = 137;
"ADD_JS_INTERFACE", // = 138;
"LOAD_DATA", // = 139;
- "TOUCH_UP", // = 140;
- "TOUCH_EVENT", // = 141;
+ "", // = 140;
+ "", // = 141;
"SET_ACTIVE", // = 142;
"ON_PAUSE", // = 143
"ON_RESUME", // = 144
@@ -1041,15 +1037,17 @@ public final class WebViewCore {
public FindAllRequest(String text) {
mSearchText = text;
mMatchCount = -1;
+ mMatchIndex = -1;
}
- public String mSearchText;
+ public final String mSearchText;
public int mMatchCount;
+ public int mMatchIndex;
}
/**
* @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;
@@ -1070,10 +1068,8 @@ public final class WebViewCore {
static final int REPLACE_TEXT = 114;
static final int PASS_TO_JS = 115;
static final int SET_GLOBAL_BOUNDS = 116;
- static final int CLICK = 118;
static final int SET_NETWORK_STATE = 119;
static final int DOC_HAS_IMAGES = 120;
- static final int FAKE_CLICK = 121;
static final int DELETE_SELECTION = 122;
static final int LISTBOX_CHOICES = 123;
static final int SINGLE_LISTBOX_CHOICE = 124;
@@ -1094,11 +1090,6 @@ public final class WebViewCore {
static final int ADD_JS_INTERFACE = 138;
static final int LOAD_DATA = 139;
- // motion
- static final int TOUCH_UP = 140;
- // message used to pass UI touch events to WebCore
- static final int TOUCH_EVENT = 141;
-
// Used to tell the focus controller not to draw the blinking cursor,
// based on whether the WebView has focus and whether the WebView's
// cursor matches the webpage's focus.
@@ -1113,9 +1104,6 @@ public final class WebViewCore {
// Load and save web archives
static final int SAVE_WEBARCHIVE = 147;
- // Update layers
- static final int WEBKIT_DRAW_LAYERS = 148;
-
static final int REMOVE_JS_INTERFACE = 149;
// Network-based messaging
@@ -1150,7 +1138,7 @@ public final class WebViewCore {
// accessibility support
static final int MODIFY_SELECTION = 190;
- static final int USE_MOCK_DEVICE_ORIENTATION = 191;
+ static final int SET_USE_MOCK_DEVICE_ORIENTATION = 191;
static final int AUTOFILL_FORM = 192;
@@ -1264,10 +1252,6 @@ public final class WebViewCore {
webkitDraw();
break;
- case WEBKIT_DRAW_LAYERS:
- webkitDrawLayers();
- break;
-
case DESTROY:
// Time to take down the world. Cancel all pending
// loads and destroy the native view and frame.
@@ -1297,7 +1281,13 @@ public final class WebViewCore {
} else {
xPercent = ((Float) msg.obj).floatValue();
}
- nativeScrollFocusedTextInput(mNativeClass, xPercent, msg.arg2);
+ Rect contentBounds = new Rect();
+ nativeScrollFocusedTextInput(mNativeClass, xPercent,
+ msg.arg2, contentBounds);
+ Message.obtain(
+ mWebViewClassic.mPrivateHandler,
+ WebViewClassic.UPDATE_CONTENT_BOUNDS,
+ contentBounds).sendToTarget();
break;
case LOAD_URL: {
@@ -1374,14 +1364,6 @@ public final class WebViewCore {
keyPress(msg.arg1);
break;
- case FAKE_CLICK:
- nativeClick(mNativeClass, msg.arg1, msg.arg2, true);
- break;
-
- case CLICK:
- nativeClick(mNativeClass, msg.arg1, msg.arg2, false);
- break;
-
case VIEW_SIZE_CHANGED: {
viewSizeChanged((WebViewClassic.ViewSizeData) msg.obj);
break;
@@ -1494,45 +1476,6 @@ public final class WebViewCore {
nativeCloseIdleConnections(mNativeClass);
break;
- case TOUCH_UP:
- TouchUpData touchUpData = (TouchUpData) msg.obj;
- if (touchUpData.mNativeLayer != 0) {
- nativeScrollLayer(mNativeClass,
- touchUpData.mNativeLayer,
- touchUpData.mNativeLayerRect);
- }
- nativeTouchUp(mNativeClass,
- touchUpData.mMoveGeneration,
- touchUpData.mFrame, touchUpData.mNode,
- touchUpData.mX, touchUpData.mY);
- break;
-
- case TOUCH_EVENT: {
- TouchEventData ted = (TouchEventData) msg.obj;
- final int count = ted.mPoints.length;
- int[] xArray = new int[count];
- int[] yArray = new int[count];
- for (int c = 0; c < count; c++) {
- xArray[c] = ted.mPoints[c].x;
- yArray[c] = ted.mPoints[c].y;
- }
- if (ted.mNativeLayer != 0) {
- nativeScrollLayer(mNativeClass,
- ted.mNativeLayer, ted.mNativeLayerRect);
- }
- ted.mNativeResult = nativeHandleTouchEvent(
- mNativeClass, ted.mAction, ted.mIds, xArray,
- yArray, count, ted.mActionIndex,
- ted.mMetaState);
- Message.obtain(
- mWebViewClassic.mPrivateHandler,
- WebViewClassic.PREVENT_TOUCH_ID,
- ted.mAction,
- ted.mNativeResult ? 1 : 0,
- ted).sendToTarget();
- break;
- }
-
case SET_ACTIVE:
nativeSetFocusControllerActive(mNativeClass, msg.arg1 == 1);
break;
@@ -1707,8 +1650,8 @@ public final class WebViewCore {
.sendToTarget();
break;
- case USE_MOCK_DEVICE_ORIENTATION:
- useMockDeviceOrientation();
+ case SET_USE_MOCK_DEVICE_ORIENTATION:
+ setUseMockDeviceOrientation();
break;
case AUTOFILL_FORM:
@@ -1777,21 +1720,32 @@ public final class WebViewCore {
nativeSelectAll(mNativeClass);
break;
case FIND_ALL: {
- FindAllRequest request = (FindAllRequest) msg.obj;
- if (request == null) {
- nativeFindAll(mNativeClass, null);
- } else {
- request.mMatchCount = nativeFindAll(
- mNativeClass, request.mSearchText);
- synchronized(request) {
+ FindAllRequest request = (FindAllRequest)msg.obj;
+ if (request != null) {
+ int matchCount = nativeFindAll(mNativeClass, request.mSearchText);
+ int matchIndex = nativeFindNext(mNativeClass, true);
+ synchronized (request) {
+ request.mMatchCount = matchCount;
+ request.mMatchIndex = matchIndex;
request.notify();
}
+ } else {
+ nativeFindAll(mNativeClass, null);
}
+ Message.obtain(mWebViewClassic.mPrivateHandler,
+ WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget();
break;
}
- case FIND_NEXT:
- nativeFindNext(mNativeClass, msg.arg1 != 0);
+ case FIND_NEXT: {
+ FindAllRequest request = (FindAllRequest)msg.obj;
+ int matchIndex = nativeFindNext(mNativeClass, msg.arg1 != 0);
+ synchronized (request) {
+ request.mMatchIndex = matchIndex;
+ }
+ Message.obtain(mWebViewClassic.mPrivateHandler,
+ WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget();
break;
+ }
}
}
};
@@ -1805,6 +1759,38 @@ public final class WebViewCore {
}
}
+ @Override
+ public Looper getWebKitLooper() {
+ return mHandler.getLooper();
+ }
+
+ @Override
+ public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
+ switch (eventType) {
+ case WebViewInputDispatcher.EVENT_TYPE_CLICK:
+ return nativeMouseClick(mNativeClass);
+
+ case WebViewInputDispatcher.EVENT_TYPE_TOUCH: {
+ int count = event.getPointerCount();
+ int[] idArray = new int[count];
+ int[] xArray = new int[count];
+ int[] yArray = new int[count];
+ for (int i = 0; i < count; i++) {
+ idArray[i] = event.getPointerId(i);
+ xArray[i] = (int) event.getX(i);
+ yArray[i] = (int) event.getY(i);
+ }
+ return nativeHandleTouchEvent(mNativeClass,
+ event.getActionMasked(),
+ idArray, xArray, yArray, count,
+ event.getActionIndex(), event.getMetaState());
+ }
+
+ default:
+ return false;
+ }
+ }
+
/**
* Send a message internally to the queue or to the handler
*/
@@ -2135,7 +2121,6 @@ public final class WebViewCore {
// Used to avoid posting more than one draw message.
private boolean mDrawIsScheduled;
- private boolean mDrawLayersIsScheduled;
// Used to avoid posting more than one split picture message.
private boolean mSplitPictureIsScheduled;
@@ -2181,25 +2166,6 @@ public final class WebViewCore {
DrawData mLastDrawData = null;
- // Only update the layers' content, not the base surface
- // PictureSet.
- private void webkitDrawLayers() {
- mDrawLayersIsScheduled = false;
- if (mDrawIsScheduled || mLastDrawData == null) {
- removeMessages(EventHub.WEBKIT_DRAW);
- webkitDraw();
- return;
- }
- // Directly update the layers we last passed to the UI side
- if (nativeUpdateLayers(mNativeClass, mLastDrawData.mBaseLayer)) {
- // If anything more complex than position has been touched, let's do a full draw
- webkitDraw();
- }
- mWebViewClassic.mPrivateHandler.removeMessages(WebViewClassic.INVAL_RECT_MSG_ID);
- mWebViewClassic.mPrivateHandler.sendMessageAtFrontOfQueue(mWebViewClassic.mPrivateHandler
- .obtainMessage(WebViewClassic.INVAL_RECT_MSG_ID));
- }
-
private Boolean m_skipDrawFlag = false;
private boolean m_drawWasSkipped = false;
@@ -2375,15 +2341,6 @@ public final class WebViewCore {
}
}
- // called from JNI
- void layersDraw() {
- synchronized (this) {
- if (mDrawLayersIsScheduled) return;
- mDrawLayersIsScheduled = true;
- mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW_LAYERS));
- }
- }
-
// called by JNI
private void contentScrollTo(int x, int y, boolean animate,
boolean onlyIfImeIsShowing) {
@@ -2825,17 +2782,6 @@ public final class WebViewCore {
.sendToTarget();
}
- // called by JNI
- private void updateMatchCount(int matchIndex, int matchCount,
- String findText) {
- if (mWebViewClassic == null) {
- return;
- }
- Message.obtain(mWebViewClassic.mPrivateHandler,
- WebViewClassic.UPDATE_MATCH_COUNT, matchIndex, matchCount,
- findText).sendToTarget();
- }
-
private native void nativeRevealSelection(int nativeClass);
private native String nativeRequestLabel(int nativeClass, int framePtr,
int nodePtr);
@@ -2843,7 +2789,7 @@ public final class WebViewCore {
* Scroll the focused textfield to (xPercent, y) in document space
*/
private native void nativeScrollFocusedTextInput(int nativeClass,
- float xPercent, int y);
+ float xPercent, int y, Rect contentBounds);
// these must be in document space (i.e. not scaled/zoomed).
private native void nativeSetScrollOffset(int nativeClass,
@@ -3047,8 +2993,8 @@ public final class WebViewCore {
// TODO: Figure out what to do with this (b/6111818)
}
- private void useMockDeviceOrientation() {
- mDeviceMotionAndOrientationManager.useMock();
+ private void setUseMockDeviceOrientation() {
+ mDeviceMotionAndOrientationManager.setUseMock();
}
public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
@@ -3086,7 +3032,7 @@ public final class WebViewCore {
private native void nativeAutoFillForm(int nativeClass, int queryId);
private native void nativeScrollLayer(int nativeClass, int layer, Rect rect);
private native int nativeFindAll(int nativeClass, String text);
- private native void nativeFindNext(int nativeClass, boolean forward);
+ private native int nativeFindNext(int nativeClass, boolean forward);
/**
* Deletes editable text between two points. Note that the selection may
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
new file mode 100644
index 0000000..c8677ec
--- /dev/null
+++ b/core/java/android/webkit/WebViewInputDispatcher.java
@@ -0,0 +1,1157 @@
+/*
+ * 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);
+ }
+ }
+
+ public boolean shouldShowTapHighlight() {
+ synchronized (mLock) {
+ return mPostLongPressScheduled || mPostClickScheduled;
+ }
+ }
+
+ private void postLongPress() {
+ synchronized (mLock) {
+ if (!mPostLongPressScheduled) {
+ return;
+ }
+ mPostLongPressScheduled = false;
+
+ MotionEvent event = mPostTouchStream.getLastEvent();
+ if (event == null) {
+ return;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ break;
+ default:
+ return;
+ }
+
+ MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+ eventToEnqueue.setAction(MotionEvent.ACTION_MOVE);
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_LONG_PRESS, 0,
+ mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+ enqueueEventLocked(d);
+ }
+ }
+
+ private void scheduleClickLocked() {
+ unscheduleClickLocked();
+ mPostClickScheduled = true;
+ mUiHandler.sendEmptyMessageDelayed(UiHandler.MSG_CLICK, DOUBLE_TAP_TIMEOUT);
+ }
+
+ private void unscheduleClickLocked() {
+ if (mPostClickScheduled) {
+ mPostClickScheduled = false;
+ mUiHandler.removeMessages(UiHandler.MSG_CLICK);
+ }
+ }
+
+ private void postClick() {
+ synchronized (mLock) {
+ if (!mPostClickScheduled) {
+ return;
+ }
+ mPostClickScheduled = false;
+
+ MotionEvent event = mPostTouchStream.getLastEvent();
+ if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
+ return;
+ }
+
+ MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_CLICK, 0,
+ mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+ enqueueEventLocked(d);
+ }
+ }
+
+ private void checkForDoubleTapOnDownLocked(MotionEvent event) {
+ mIsDoubleTapCandidate = false;
+ if (!mPostClickScheduled) {
+ return;
+ }
+ int deltaX = (int) mInitialDownX - (int) event.getX();
+ int deltaY = (int) mInitialDownY - (int) event.getY();
+ if ((deltaX * deltaX + deltaY * deltaY) < mDoubleTapSlopSquared) {
+ unscheduleClickLocked();
+ mIsDoubleTapCandidate = true;
+ }
+ }
+
+ private boolean isClickCandidateLocked(MotionEvent event) {
+ if (event == null
+ || event.getActionMasked() != MotionEvent.ACTION_UP
+ || !mIsTapCandidate) {
+ return false;
+ }
+ long downDuration = event.getEventTime() - event.getDownTime();
+ return downDuration < TAP_TIMEOUT;
+ }
+
+ private void enqueueDoubleTapLocked(MotionEvent event) {
+ unscheduleClickLocked();
+ MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
+ DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_DOUBLE_TAP, 0,
+ mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
+ enqueueEventLocked(d);
+ mIsDoubleTapCandidate = false;
+ }
+
+ private void checkForSlopLocked(MotionEvent event) {
+ if (!mIsTapCandidate) {
+ return;
+ }
+ int deltaX = (int) mInitialDownX - (int) event.getX();
+ int deltaY = (int) mInitialDownY - (int) event.getY();
+ if ((deltaX * deltaX + deltaY * deltaY) > mTouchSlopSquared) {
+ unscheduleLongPressLocked();
+ mIsTapCandidate = false;
+ }
+ }
+
+ private void updateStateTrackersLocked(DispatchEvent d, MotionEvent event) {
+ mPostLastWebKitXOffset = d.mWebKitXOffset;
+ mPostLastWebKitYOffset = d.mWebKitYOffset;
+ mPostLastWebKitScale = d.mWebKitScale;
+ int action = event != null ? event.getAction() : MotionEvent.ACTION_CANCEL;
+ if (d.mEventType != EVENT_TYPE_TOUCH) {
+ return;
+ }
+
+ if (action == MotionEvent.ACTION_CANCEL
+ || event.getPointerCount() > 1) {
+ unscheduleLongPressLocked();
+ unscheduleClickLocked();
+ mIsDoubleTapCandidate = false;
+ mIsTapCandidate = false;
+ } else if (action == MotionEvent.ACTION_DOWN) {
+ checkForDoubleTapOnDownLocked(event);
+ scheduleLongPressLocked();
+ mIsTapCandidate = true;
+ mInitialDownX = event.getX();
+ mInitialDownY = event.getY();
+ } else if (action == MotionEvent.ACTION_UP) {
+ unscheduleLongPressLocked();
+ if (isClickCandidateLocked(event)) {
+ if (mIsDoubleTapCandidate) {
+ enqueueDoubleTapLocked(event);
+ } else {
+ scheduleClickLocked();
+ }
+ }
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ checkForSlopLocked(event);
+ }
+ }
+
+ /**
+ * Dispatches pending web kit events.
+ * Must only be called from the web kit thread.
+ *
+ * This method may be used to flush the queue of pending input events
+ * immediately. This method may help to reduce input dispatch latency
+ * if called before certain expensive operations such as drawing.
+ */
+ public void dispatchWebKitEvents() {
+ dispatchWebKitEvents(false);
+ }
+
+ private void dispatchWebKitEvents(boolean calledFromHandler) {
+ for (;;) {
+ // Get the next event, but leave it in the queue so we can move it to the UI
+ // queue if a timeout occurs.
+ DispatchEvent d;
+ MotionEvent event;
+ final int eventType;
+ int flags;
+ synchronized (mLock) {
+ if (!ENABLE_EVENT_BATCHING) {
+ drainStaleWebKitEventsLocked();
+ }
+ d = mWebKitDispatchEventQueue.mHead;
+ if (d == null) {
+ if (mWebKitDispatchScheduled) {
+ mWebKitDispatchScheduled = false;
+ if (!calledFromHandler) {
+ mWebKitHandler.removeMessages(
+ WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
+ }
+ }
+ return;
+ }
+
+ event = d.mEvent;
+ if (event != null) {
+ event.offsetLocation(d.mWebKitXOffset, d.mWebKitYOffset);
+ event.scale(d.mWebKitScale);
+ d.mFlags |= FLAG_WEBKIT_TRANSFORMED_EVENT;
+ }
+
+ eventType = d.mEventType;
+ if (eventType == EVENT_TYPE_TOUCH) {
+ event = mWebKitTouchStream.update(event);
+ if (DEBUG && event == null && d.mEvent != null) {
+ Log.d(TAG, "dispatchWebKitEvents: dropped event " + d.mEvent);
+ }
+ }
+
+ d.mFlags |= FLAG_WEBKIT_IN_PROGRESS;
+ flags = d.mFlags;
+ }
+
+ // Handle the event.
+ final boolean preventDefault;
+ if (event == null) {
+ preventDefault = false;
+ } else {
+ preventDefault = dispatchWebKitEvent(event, eventType, flags);
+ }
+
+ synchronized (mLock) {
+ flags = d.mFlags;
+ d.mFlags = flags & ~FLAG_WEBKIT_IN_PROGRESS;
+ boolean recycleEvent = event != d.mEvent;
+
+ if ((flags & FLAG_WEBKIT_TIMEOUT) != 0) {
+ // A timeout occurred!
+ recycleDispatchEventLocked(d);
+ } else {
+ // Web kit finished in a timely manner. Dequeue the event.
+ assert mWebKitDispatchEventQueue.mHead == d;
+ mWebKitDispatchEventQueue.dequeue();
+
+ updateWebKitTimeoutLocked();
+
+ if ((flags & FLAG_PRIVATE) != 0) {
+ // Event was intended for web kit only. All done.
+ recycleDispatchEventLocked(d);
+ } else if (preventDefault) {
+ // Web kit has decided to consume the event!
+ if (d.mEventType == EVENT_TYPE_TOUCH) {
+ enqueueUiCancelTouchEventIfNeededLocked();
+ }
+ } else {
+ // Web kit is being friendly. Pass the event to the UI.
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ if (event != null && recycleEvent) {
+ event.recycle();
+ }
+ }
+ }
+ }
+
+ // Runs on web kit thread.
+ private boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags) {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchWebKitEvent: event=" + event
+ + ", eventType=" + eventType + ", flags=" + flags);
+ }
+ boolean preventDefault = mWebKitCallbacks.dispatchWebKitEvent(
+ event, eventType, flags);
+ if (DEBUG) {
+ Log.d(TAG, "dispatchWebKitEvent: preventDefault=" + preventDefault);
+ }
+ return preventDefault;
+ }
+
+ private boolean isMoveEventLocked(DispatchEvent d) {
+ return d.mEvent != null
+ && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE;
+ }
+
+ private void drainStaleWebKitEventsLocked() {
+ DispatchEvent d = mWebKitDispatchEventQueue.mHead;
+ while (d != null && d.mNext != null
+ && isMoveEventLocked(d)
+ && isMoveEventLocked(d.mNext)) {
+ DispatchEvent next = d.mNext;
+ skipWebKitEventLocked(d);
+ d = next;
+ }
+ mWebKitDispatchEventQueue.mHead = d;
+ }
+
+ // Runs on UI thread in response to the web kit thread appearing to be unresponsive.
+ private void handleWebKitTimeout() {
+ synchronized (mLock) {
+ if (!mWebKitTimeoutScheduled) {
+ return;
+ }
+ mWebKitTimeoutScheduled = false;
+
+ if (DEBUG) {
+ Log.d(TAG, "handleWebKitTimeout: timeout occurred!");
+ }
+
+ // Drain the web kit event queue.
+ DispatchEvent d = mWebKitDispatchEventQueue.dequeueList();
+
+ // If web kit was processing an event (must be at the head of the list because
+ // it can only do one at a time), then clone it or ignore it.
+ if ((d.mFlags & FLAG_WEBKIT_IN_PROGRESS) != 0) {
+ d.mFlags |= FLAG_WEBKIT_TIMEOUT;
+ if ((d.mFlags & FLAG_PRIVATE) != 0) {
+ d = d.mNext; // the event is private to web kit, ignore it
+ } else {
+ d = copyDispatchEventLocked(d);
+ d.mFlags &= ~FLAG_WEBKIT_IN_PROGRESS;
+ }
+ }
+
+ // Enqueue all non-private events for handling by the UI thread.
+ while (d != null) {
+ DispatchEvent next = d.mNext;
+ skipWebKitEventLocked(d);
+ d = next;
+ }
+
+ // Tell web kit to cancel all pending touches.
+ // This also prevents us from sending web kit any more touches until the
+ // next gesture begins. (As required to ensure touch event stream consistency.)
+ enqueueWebKitCancelTouchEventIfNeededLocked();
+ }
+ }
+
+ private void skipWebKitEventLocked(DispatchEvent d) {
+ d.mNext = null;
+ if ((d.mFlags & FLAG_PRIVATE) != 0) {
+ recycleDispatchEventLocked(d);
+ } else {
+ d.mFlags |= FLAG_WEBKIT_TIMEOUT;
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ /**
+ * Dispatches pending UI events.
+ * Must only be called from the UI thread.
+ *
+ * This method may be used to flush the queue of pending input events
+ * immediately. This method may help to reduce input dispatch latency
+ * if called before certain expensive operations such as drawing.
+ */
+ public void dispatchUiEvents() {
+ dispatchUiEvents(false);
+ }
+
+ private void dispatchUiEvents(boolean calledFromHandler) {
+ for (;;) {
+ MotionEvent event;
+ final int eventType;
+ final int flags;
+ synchronized (mLock) {
+ DispatchEvent d = mUiDispatchEventQueue.dequeue();
+ if (d == null) {
+ if (mUiDispatchScheduled) {
+ mUiDispatchScheduled = false;
+ if (!calledFromHandler) {
+ mUiHandler.removeMessages(UiHandler.MSG_DISPATCH_UI_EVENTS);
+ }
+ }
+ return;
+ }
+
+ event = d.mEvent;
+ if (event != null && (d.mFlags & FLAG_WEBKIT_TRANSFORMED_EVENT) != 0) {
+ event.scale(1.0f / d.mWebKitScale);
+ event.offsetLocation(-d.mWebKitXOffset, -d.mWebKitYOffset);
+ d.mFlags &= ~FLAG_WEBKIT_TRANSFORMED_EVENT;
+ }
+
+ eventType = d.mEventType;
+ if (eventType == EVENT_TYPE_TOUCH) {
+ event = mUiTouchStream.update(event);
+ if (DEBUG && event == null && d.mEvent != null) {
+ Log.d(TAG, "dispatchUiEvents: dropped event " + d.mEvent);
+ }
+ }
+
+ flags = d.mFlags;
+
+ updateStateTrackersLocked(d, event);
+ if (event == d.mEvent) {
+ d.mEvent = null; // retain ownership of event, don't recycle it yet
+ }
+ recycleDispatchEventLocked(d);
+ }
+
+ // Handle the event.
+ if (event != null) {
+ dispatchUiEvent(event, eventType, flags);
+ event.recycle();
+ }
+ }
+ }
+
+ // Runs on UI thread.
+ private void dispatchUiEvent(MotionEvent event, int eventType, int flags) {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchUiEvent: event=" + event
+ + ", eventType=" + eventType + ", flags=" + flags);
+ }
+ mUiCallbacks.dispatchUiEvent(event, eventType, flags);
+ }
+
+ private void enqueueEventLocked(DispatchEvent d) {
+ if (!shouldSkipWebKit(d.mEventType)) {
+ enqueueWebKitEventLocked(d);
+ } else {
+ enqueueUiEventLocked(d);
+ }
+ }
+
+ private boolean shouldSkipWebKit(int eventType) {
+ switch (eventType) {
+ case EVENT_TYPE_CLICK:
+ case EVENT_TYPE_HOVER:
+ case EVENT_TYPE_SCROLL:
+ return false;
+ case EVENT_TYPE_TOUCH:
+ return !mPostSendTouchEventsToWebKit
+ || mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
+ }
+ return true;
+ }
+
+ private void enqueueWebKitCancelTouchEventIfNeededLocked() {
+ // We want to cancel touch events that were delivered to web kit.
+ // Enqueue a null event at the end of the queue if needed.
+ if (mWebKitTouchStream.isCancelNeeded() || !mWebKitDispatchEventQueue.isEmpty()) {
+ DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
+ 0, 0, 1.0f);
+ enqueueWebKitEventUnbatchedLocked(d);
+ mPostDoNotSendTouchEventsToWebKitUntilNextGesture = true;
+ }
+ }
+
+ private void enqueueWebKitEventLocked(DispatchEvent d) {
+ if (batchEventLocked(d, mWebKitDispatchEventQueue.mTail)) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueWebKitEventLocked: batched event " + d.mEvent);
+ }
+ recycleDispatchEventLocked(d);
+ } else {
+ enqueueWebKitEventUnbatchedLocked(d);
+ }
+ }
+
+ private void enqueueWebKitEventUnbatchedLocked(DispatchEvent d) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueWebKitEventUnbatchedLocked: enqueued event " + d.mEvent);
+ }
+ mWebKitDispatchEventQueue.enqueue(d);
+ scheduleWebKitDispatchLocked();
+ updateWebKitTimeoutLocked();
+ }
+
+ private void scheduleWebKitDispatchLocked() {
+ if (!mWebKitDispatchScheduled) {
+ mWebKitHandler.sendEmptyMessage(WebKitHandler.MSG_DISPATCH_WEBKIT_EVENTS);
+ mWebKitDispatchScheduled = true;
+ }
+ }
+
+ private void updateWebKitTimeoutLocked() {
+ DispatchEvent d = mWebKitDispatchEventQueue.mHead;
+ if (d != null && mWebKitTimeoutScheduled && mWebKitTimeoutTime == d.mTimeoutTime) {
+ return;
+ }
+ if (mWebKitTimeoutScheduled) {
+ mUiHandler.removeMessages(UiHandler.MSG_WEBKIT_TIMEOUT);
+ mWebKitTimeoutScheduled = false;
+ }
+ if (d != null) {
+ mUiHandler.sendEmptyMessageAtTime(UiHandler.MSG_WEBKIT_TIMEOUT, d.mTimeoutTime);
+ mWebKitTimeoutScheduled = true;
+ mWebKitTimeoutTime = d.mTimeoutTime;
+ }
+ }
+
+ private void enqueueUiCancelTouchEventIfNeededLocked() {
+ // We want to cancel touch events that were delivered to the UI.
+ // Enqueue a null event at the end of the queue if needed.
+ if (mUiTouchStream.isCancelNeeded() || !mUiDispatchEventQueue.isEmpty()) {
+ DispatchEvent d = obtainDispatchEventLocked(null, EVENT_TYPE_TOUCH, FLAG_PRIVATE,
+ 0, 0, 1.0f);
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ private void enqueueUiEventLocked(DispatchEvent d) {
+ if (batchEventLocked(d, mUiDispatchEventQueue.mTail)) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueUiEventLocked: batched event " + d.mEvent);
+ }
+ recycleDispatchEventLocked(d);
+ } else {
+ enqueueUiEventUnbatchedLocked(d);
+ }
+ }
+
+ private void enqueueUiEventUnbatchedLocked(DispatchEvent d) {
+ if (DEBUG) {
+ Log.d(TAG, "enqueueUiEventUnbatchedLocked: enqueued event " + d.mEvent);
+ }
+ mUiDispatchEventQueue.enqueue(d);
+ scheduleUiDispatchLocked();
+ }
+
+ private void scheduleUiDispatchLocked() {
+ if (!mUiDispatchScheduled) {
+ mUiHandler.sendEmptyMessage(UiHandler.MSG_DISPATCH_UI_EVENTS);
+ mUiDispatchScheduled = true;
+ }
+ }
+
+ private boolean batchEventLocked(DispatchEvent in, DispatchEvent tail) {
+ if (!ENABLE_EVENT_BATCHING) {
+ return false;
+ }
+ if (tail != null && tail.mEvent != null && in.mEvent != null
+ && in.mEventType == tail.mEventType
+ && in.mFlags == tail.mFlags
+ && in.mWebKitXOffset == tail.mWebKitXOffset
+ && in.mWebKitYOffset == tail.mWebKitYOffset
+ && in.mWebKitScale == tail.mWebKitScale) {
+ return tail.mEvent.addBatch(in.mEvent);
+ }
+ return false;
+ }
+
+ private DispatchEvent obtainDispatchEventLocked(MotionEvent event,
+ int eventType, int flags, int webKitXOffset, int webKitYOffset, float webKitScale) {
+ DispatchEvent d = obtainUninitializedDispatchEventLocked();
+ d.mEvent = event;
+ d.mEventType = eventType;
+ d.mFlags = flags;
+ d.mTimeoutTime = SystemClock.uptimeMillis() + WEBKIT_TIMEOUT_MILLIS;
+ d.mWebKitXOffset = webKitXOffset;
+ d.mWebKitYOffset = webKitYOffset;
+ d.mWebKitScale = webKitScale;
+ if (DEBUG) {
+ Log.d(TAG, "Timeout time: " + (d.mTimeoutTime - SystemClock.uptimeMillis()));
+ }
+ return d;
+ }
+
+ private DispatchEvent copyDispatchEventLocked(DispatchEvent d) {
+ DispatchEvent copy = obtainUninitializedDispatchEventLocked();
+ if (d.mEvent != null) {
+ copy.mEvent = d.mEvent.copy();
+ }
+ copy.mEventType = d.mEventType;
+ copy.mFlags = d.mFlags;
+ copy.mTimeoutTime = d.mTimeoutTime;
+ copy.mWebKitXOffset = d.mWebKitXOffset;
+ copy.mWebKitYOffset = d.mWebKitYOffset;
+ copy.mWebKitScale = d.mWebKitScale;
+ copy.mNext = d.mNext;
+ return copy;
+ }
+
+ private DispatchEvent obtainUninitializedDispatchEventLocked() {
+ DispatchEvent d = mDispatchEventPool;
+ if (d != null) {
+ mDispatchEventPoolSize -= 1;
+ mDispatchEventPool = d.mNext;
+ d.mNext = null;
+ } else {
+ d = new DispatchEvent();
+ }
+ return d;
+ }
+
+ private void recycleDispatchEventLocked(DispatchEvent d) {
+ if (d.mEvent != null) {
+ d.mEvent.recycle();
+ d.mEvent = null;
+ }
+
+ if (mDispatchEventPoolSize < MAX_DISPATCH_EVENT_POOL_SIZE) {
+ mDispatchEventPoolSize += 1;
+ d.mNext = mDispatchEventPool;
+ mDispatchEventPool = d;
+ }
+ }
+
+ /* Implemented by {@link WebViewClassic} to perform operations on the UI thread. */
+ public static interface UiCallbacks {
+ /**
+ * Gets the UI thread's looper.
+ * @return The looper.
+ */
+ public Looper getUiLooper();
+
+ /**
+ * Gets the UI's context
+ * @return The context
+ */
+ public Context getContext();
+
+ /**
+ * Dispatches an event to the UI.
+ * @param event The event.
+ * @param eventType The event type.
+ * @param flags The event's dispatch flags.
+ */
+ public void dispatchUiEvent(MotionEvent event, int eventType, int flags);
+ }
+
+ /* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */
+ public static interface WebKitCallbacks {
+ /**
+ * Gets the web kit thread's looper.
+ * @return The looper.
+ */
+ public Looper getWebKitLooper();
+
+ /**
+ * Dispatches an event to web kit.
+ * @param event The event.
+ * @param eventType The event type.
+ * @param flags The event's dispatch flags.
+ * @return True if web kit wants to prevent default event handling.
+ */
+ public boolean dispatchWebKitEvent(MotionEvent event, int eventType, int flags);
+ }
+
+ // Runs on UI thread.
+ private final class UiHandler extends Handler {
+ public static final int MSG_DISPATCH_UI_EVENTS = 1;
+ public static final int MSG_WEBKIT_TIMEOUT = 2;
+ public static final int MSG_LONG_PRESS = 3;
+ public static final int MSG_CLICK = 4;
+
+ public UiHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISPATCH_UI_EVENTS:
+ dispatchUiEvents(true);
+ break;
+ case MSG_WEBKIT_TIMEOUT:
+ handleWebKitTimeout();
+ break;
+ case MSG_LONG_PRESS:
+ postLongPress();
+ break;
+ case MSG_CLICK:
+ postClick();
+ break;
+ default:
+ throw new IllegalStateException("Unknown message type: " + msg.what);
+ }
+ }
+ }
+
+ // Runs on web kit thread.
+ private final class WebKitHandler extends Handler {
+ public static final int MSG_DISPATCH_WEBKIT_EVENTS = 1;
+
+ public WebKitHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DISPATCH_WEBKIT_EVENTS:
+ dispatchWebKitEvents(true);
+ break;
+ default:
+ throw new IllegalStateException("Unknown message type: " + msg.what);
+ }
+ }
+ }
+
+ private static final class DispatchEvent {
+ public DispatchEvent mNext;
+
+ public MotionEvent mEvent;
+ public int mEventType;
+ public int mFlags;
+ public long mTimeoutTime;
+ public int mWebKitXOffset;
+ public int mWebKitYOffset;
+ public float mWebKitScale;
+ }
+
+ private static final class DispatchEventQueue {
+ public DispatchEvent mHead;
+ public DispatchEvent mTail;
+
+ public boolean isEmpty() {
+ return mHead != null;
+ }
+
+ public void enqueue(DispatchEvent d) {
+ if (mHead == null) {
+ mHead = d;
+ mTail = d;
+ } else {
+ mTail.mNext = d;
+ mTail = d;
+ }
+ }
+
+ public DispatchEvent dequeue() {
+ DispatchEvent d = mHead;
+ if (d != null) {
+ DispatchEvent next = d.mNext;
+ if (next == null) {
+ mHead = null;
+ mTail = null;
+ } else {
+ mHead = next;
+ d.mNext = null;
+ }
+ }
+ return d;
+ }
+
+ public DispatchEvent dequeueList() {
+ DispatchEvent d = mHead;
+ if (d != null) {
+ mHead = null;
+ mTail = null;
+ }
+ return d;
+ }
+ }
+
+ /**
+ * Keeps track of a stream of touch events so that we can discard touch
+ * events that would make the stream inconsistent.
+ */
+ private static final class TouchStream {
+ private MotionEvent mLastEvent;
+
+ /**
+ * Gets the last touch event that was delivered.
+ * @return The last touch event, or null if none.
+ */
+ public MotionEvent getLastEvent() {
+ return mLastEvent;
+ }
+
+ /**
+ * Updates the touch event stream.
+ * @param event The event that we intend to send, or null to cancel the
+ * touch event stream.
+ * @return The event that we should actually send, or null if no event should
+ * be sent because the proposed event would make the stream inconsistent.
+ */
+ public MotionEvent update(MotionEvent event) {
+ if (event == null) {
+ if (isCancelNeeded()) {
+ event = mLastEvent;
+ if (event != null) {
+ event.setAction(MotionEvent.ACTION_CANCEL);
+ mLastEvent = null;
+ }
+ }
+ return event;
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_MOVE:
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_DOWN:
+ case MotionEvent.ACTION_POINTER_UP:
+ if (mLastEvent == null
+ || mLastEvent.getAction() == MotionEvent.ACTION_UP) {
+ return null;
+ }
+ updateLastEvent(event);
+ return event;
+
+ case MotionEvent.ACTION_DOWN:
+ updateLastEvent(event);
+ return event;
+
+ case MotionEvent.ACTION_CANCEL:
+ if (mLastEvent == null) {
+ return null;
+ }
+ updateLastEvent(null);
+ return event;
+
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if there is a gesture in progress that may need to be canceled.
+ * @return True if cancel is needed.
+ */
+ public boolean isCancelNeeded() {
+ return mLastEvent != null && mLastEvent.getAction() != MotionEvent.ACTION_UP;
+ }
+
+ private void updateLastEvent(MotionEvent event) {
+ if (mLastEvent != null) {
+ mLastEvent.recycle();
+ }
+ mLastEvent = event != null ? MotionEvent.obtainNoHistory(event) : null;
+ }
+ }
+} \ No newline at end of file
diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java
index e6184d5..b409e26 100755
--- a/core/java/android/widget/AppSecurityPermissions.java
+++ b/core/java/android/widget/AppSecurityPermissions.java
@@ -490,13 +490,7 @@ public class AppSecurityPermissions implements View.OnClickListener {
// Development permissions are only shown to the user if they are already
// granted to the app -- if we are installing an app and they are not
// already granted, they will not be granted as part of the install.
- // Note we also need the app to have specified this permission is not
- // required -- this is not technically needed, but it helps various things
- // if we ensure apps always mark development permissions as option, so that
- // even not knowing what a permission is we can still know whether it will
- // be granted to the app when it is installed.
if ((existingReqFlags&PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0
- && (newReqFlags&PackageInfo.REQUESTED_PERMISSION_REQUIRED) == 0
&& (pInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
return true;
}
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index 0685eea..858c415 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -21,8 +21,6 @@ import android.util.AttributeSet;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import com.android.internal.R;
-
/**
* <p>
@@ -71,16 +69,6 @@ public class CheckBox extends CompoundButton {
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
- if (isChecked()) {
- event.getText().add(mContext.getString(R.string.checkbox_checked));
- } else {
- event.getText().add(mContext.getString(R.string.checkbox_not_checked));
- }
- }
-
- @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(CheckBox.class.getName());
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index 9c9eb4b..b7a126e 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -248,7 +248,6 @@ public class Chronometer extends TextView {
}
}
setText(text);
- Slog.v("Chronometer", "updateText: sec=" + seconds + " mFormat=" + mFormat + " text=" + text);
}
private void updateRunning() {
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 4e13ea1..c2559a5 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -77,7 +77,7 @@ public class HorizontalScrollView extends FrameLayout {
/**
* Position of the last motion event.
*/
- private float mLastMotionX;
+ private int mLastMotionX;
/**
* True when the layout has changed but the traversal has not come through yet.
@@ -460,7 +460,7 @@ public class HorizontalScrollView extends FrameLayout {
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
- final float x = ev.getX(pointerIndex);
+ final int x = (int) ev.getX(pointerIndex);
final int xDiff = (int) Math.abs(x - mLastMotionX);
if (xDiff > mTouchSlop) {
mIsBeingDragged = true;
@@ -473,7 +473,7 @@ public class HorizontalScrollView extends FrameLayout {
}
case MotionEvent.ACTION_DOWN: {
- final float x = ev.getX();
+ final int x = (int) ev.getX();
if (!inChild((int) x, (int) ev.getY())) {
mIsBeingDragged = false;
recycleVelocityTracker();
@@ -505,18 +505,18 @@ public class HorizontalScrollView extends FrameLayout {
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
- invalidate();
+ postInvalidateOnAnimation();
}
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
- mLastMotionX = ev.getX(index);
+ mLastMotionX = (int) ev.getX(index);
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
- mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
+ mLastMotionX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
break;
}
@@ -550,7 +550,7 @@ public class HorizontalScrollView extends FrameLayout {
}
// Remember where the motion event started
- mLastMotionX = ev.getX();
+ mLastMotionX = (int) ev.getX();
mActivePointerId = ev.getPointerId(0);
break;
}
@@ -558,7 +558,7 @@ public class HorizontalScrollView extends FrameLayout {
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
- final float x = ev.getX(activePointerIndex);
+ final int x = (int) ev.getX(activePointerIndex);
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
@@ -591,7 +591,7 @@ public class HorizontalScrollView extends FrameLayout {
}
if (mEdgeGlowLeft != null
&& (!mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished())) {
- invalidate();
+ postInvalidateOnAnimation();
}
}
}
@@ -608,7 +608,7 @@ public class HorizontalScrollView extends FrameLayout {
} else {
if (mScroller.springBack(mScrollX, mScrollY, 0,
getScrollRange(), 0, 0)) {
- invalidate();
+ postInvalidateOnAnimation();
}
}
}
@@ -626,7 +626,7 @@ public class HorizontalScrollView extends FrameLayout {
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
- invalidate();
+ postInvalidateOnAnimation();
}
mActivePointerId = INVALID_POINTER;
mIsBeingDragged = false;
@@ -654,7 +654,7 @@ public class HorizontalScrollView extends FrameLayout {
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- mLastMotionX = ev.getX(newPointerIndex);
+ mLastMotionX = (int) ev.getX(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
@@ -1084,7 +1084,7 @@ public class HorizontalScrollView extends FrameLayout {
dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;
mScroller.startScroll(scrollX, mScrollY, dx, 0);
- invalidate();
+ postInvalidateOnAnimation();
} else {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
@@ -1206,7 +1206,7 @@ public class HorizontalScrollView extends FrameLayout {
}
if (!awakenScrollBars()) {
- invalidate();
+ postInvalidateOnAnimation();
}
}
}
@@ -1452,7 +1452,7 @@ public class HorizontalScrollView extends FrameLayout {
newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT);
}
- invalidate();
+ postInvalidateOnAnimation();
}
}
@@ -1503,7 +1503,7 @@ public class HorizontalScrollView extends FrameLayout {
canvas.translate(-height + mPaddingTop, Math.min(0, scrollX));
mEdgeGlowLeft.setSize(height, getWidth());
if (mEdgeGlowLeft.draw(canvas)) {
- invalidate();
+ postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
@@ -1517,7 +1517,7 @@ public class HorizontalScrollView extends FrameLayout {
-(Math.max(getScrollRange(), scrollX) + width));
mEdgeGlowRight.setSize(height, width);
if (mEdgeGlowRight.draw(canvas)) {
- invalidate();
+ postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index d897a39..b2321d9 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -579,7 +579,7 @@ public class NumberPicker extends LinearLayout {
throw new IllegalArgumentException("minWidth > maxWidth");
}
- mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
+ mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);
attributesArray.recycle();
@@ -771,6 +771,8 @@ public class NumberPicker extends LinearLayout {
mLastDownEventTime = event.getEventTime();
mIngonreMoveEvents = false;
mShowSoftInputOnTap = false;
+ // Make sure we wupport flinging inside scrollables.
+ getParent().requestDisallowInterceptTouchEvent(true);
if (!mFlingScroller.isFinished()) {
mFlingScroller.forceFinished(true);
mAdjustScroller.forceFinished(true);
@@ -1096,12 +1098,7 @@ public class NumberPicker extends LinearLayout {
* @see #setMaxValue(int)
*/
public void setValue(int value) {
- if (mValue == value) {
- return;
- }
setValueInternal(value, false);
- initializeSelectorWheelIndices();
- invalidate();
}
/**
@@ -1498,6 +1495,8 @@ public class NumberPicker extends LinearLayout {
if (notifyChange) {
notifyChange(previous, current);
}
+ initializeSelectorWheelIndices();
+ invalidate();
}
/**
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 3bc4f7f..0b49404 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -37,9 +37,11 @@ import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.shapes.Shape;
import android.os.Parcel;
import android.os.Parcelable;
-import android.os.SystemClock;
import android.util.AttributeSet;
-import android.view.Choreographer;
+import android.util.Pool;
+import android.util.Poolable;
+import android.util.PoolableManager;
+import android.util.Pools;
import android.view.Gravity;
import android.view.RemotableViewMethod;
import android.view.View;
@@ -55,6 +57,8 @@ import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import android.widget.RemoteViews.RemoteView;
+import java.util.ArrayList;
+
/**
* <p>
@@ -218,6 +222,10 @@ public class ProgressBar extends View {
private boolean mShouldStartAnimationDrawable;
private boolean mInDrawing;
+ private boolean mAttached;
+ private boolean mRefreshIsPosted;
+
+ private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>();
private AccessibilityEventSender mAccessibilityEventSender;
@@ -558,29 +566,76 @@ public class ProgressBar extends View {
}
private class RefreshProgressRunnable implements Runnable {
+ public void run() {
+ synchronized (ProgressBar.this) {
+ final int count = mRefreshData.size();
+ for (int i = 0; i < count; i++) {
+ final RefreshData rd = mRefreshData.get(i);
+ doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
+ rd.recycle();
+ }
+ mRefreshData.clear();
+ mRefreshIsPosted = false;
+ }
+ }
+ }
- private int mId;
- private int mProgress;
- private boolean mFromUser;
+ private static class RefreshData implements Poolable<RefreshData> {
+ public int id;
+ public int progress;
+ public boolean fromUser;
- RefreshProgressRunnable(int id, int progress, boolean fromUser) {
- mId = id;
- mProgress = progress;
- mFromUser = fromUser;
- }
+ private RefreshData mNext;
+ private boolean mIsPooled;
- public void run() {
- doRefreshProgress(mId, mProgress, mFromUser, true);
- // Put ourselves back in the cache when we are done
- mRefreshProgressRunnable = this;
+ private static final int POOL_MAX = 24;
+ private static final Pool<RefreshData> sPool = Pools.synchronizedPool(
+ Pools.finitePool(new PoolableManager<RefreshData>() {
+ @Override
+ public RefreshData newInstance() {
+ return new RefreshData();
+ }
+
+ @Override
+ public void onAcquired(RefreshData element) {
+ }
+
+ @Override
+ public void onReleased(RefreshData element) {
+ }
+ }, POOL_MAX));
+
+ public static RefreshData obtain(int id, int progress, boolean fromUser) {
+ RefreshData rd = sPool.acquire();
+ rd.id = id;
+ rd.progress = progress;
+ rd.fromUser = fromUser;
+ return rd;
}
- public void setup(int id, int progress, boolean fromUser) {
- mId = id;
- mProgress = progress;
- mFromUser = fromUser;
+ public void recycle() {
+ sPool.release(this);
+ }
+
+ @Override
+ public void setNextPoolable(RefreshData element) {
+ mNext = element;
+ }
+
+ @Override
+ public RefreshData getNextPoolable() {
+ return mNext;
+ }
+
+ @Override
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ @Override
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
}
-
}
private synchronized void doRefreshProgress(int id, int progress, boolean fromUser,
@@ -619,14 +674,16 @@ public class ProgressBar extends View {
if (mRefreshProgressRunnable != null) {
// Use cached RefreshProgressRunnable if available
r = mRefreshProgressRunnable;
- // Uncache it
- mRefreshProgressRunnable = null;
- r.setup(id, progress, fromUser);
} else {
// Make a new one
- r = new RefreshProgressRunnable(id, progress, fromUser);
+ r = new RefreshProgressRunnable();
+ }
+ final RefreshData rd = RefreshData.obtain(id, progress, fromUser);
+ mRefreshData.add(rd);
+ if (mAttached && !mRefreshIsPosted) {
+ post(r);
+ mRefreshIsPosted = true;
}
- post(r);
}
}
@@ -1092,6 +1149,18 @@ public class ProgressBar extends View {
if (mIndeterminate) {
startAnimation();
}
+ if (mRefreshData != null) {
+ synchronized (this) {
+ final int count = mRefreshData.size();
+ for (int i = 0; i < count; i++) {
+ final RefreshData rd = mRefreshData.get(i);
+ doRefreshProgress(rd.id, rd.progress, rd.fromUser, true);
+ rd.recycle();
+ }
+ mRefreshData.clear();
+ }
+ }
+ mAttached = true;
}
@Override
@@ -1099,7 +1168,10 @@ public class ProgressBar extends View {
if (mIndeterminate) {
stopAnimation();
}
- if(mRefreshProgressRunnable != null) {
+ if (mRefreshProgressRunnable != null) {
+ removeCallbacks(mRefreshProgressRunnable);
+ }
+ if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
removeCallbacks(mRefreshProgressRunnable);
}
if (mAccessibilityEventSender != null) {
@@ -1108,6 +1180,7 @@ public class ProgressBar extends View {
// This should come after stopAnimation(), otherwise an invalidate message remains in the
// queue, which can prevent the entire view hierarchy from being GC'ed during a rotation
super.onDetachedFromWindow();
+ mAttached = false;
}
@Override
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index e0e3e93..0f0dbae 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -73,7 +73,7 @@ public class ScrollView extends FrameLayout {
/**
* Position of the last motion event.
*/
- private float mLastMotionY;
+ private int mLastMotionY;
/**
* True when the layout has changed but the traversal has not come through yet.
@@ -472,8 +472,8 @@ public class ScrollView extends FrameLayout {
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
- final float y = ev.getY(pointerIndex);
- final int yDiff = (int) Math.abs(y - mLastMotionY);
+ final int y = (int) ev.getY(pointerIndex);
+ final int yDiff = Math.abs(y - mLastMotionY);
if (yDiff > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionY = y;
@@ -487,7 +487,7 @@ public class ScrollView extends FrameLayout {
}
case MotionEvent.ACTION_DOWN: {
- final float y = ev.getY();
+ final int y = (int) ev.getY();
if (!inChild((int) ev.getX(), (int) y)) {
mIsBeingDragged = false;
recycleVelocityTracker();
@@ -522,7 +522,7 @@ public class ScrollView extends FrameLayout {
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
- invalidate();
+ postInvalidateOnAnimation();
}
break;
case MotionEvent.ACTION_POINTER_UP:
@@ -564,7 +564,7 @@ public class ScrollView extends FrameLayout {
}
// Remember where the motion event started
- mLastMotionY = ev.getY();
+ mLastMotionY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
break;
}
@@ -572,8 +572,8 @@ public class ScrollView extends FrameLayout {
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
- final float y = ev.getY(activePointerIndex);
- final int deltaY = (int) (mLastMotionY - y);
+ final int y = (int) ev.getY(activePointerIndex);
+ final int deltaY = mLastMotionY - y;
mLastMotionY = y;
final int oldX = mScrollX;
@@ -605,7 +605,7 @@ public class ScrollView extends FrameLayout {
}
if (mEdgeGlowTop != null
&& (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
- invalidate();
+ postInvalidateOnAnimation();
}
}
}
@@ -622,7 +622,7 @@ public class ScrollView extends FrameLayout {
} else {
if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
getScrollRange())) {
- invalidate();
+ postInvalidateOnAnimation();
}
}
}
@@ -634,7 +634,7 @@ public class ScrollView extends FrameLayout {
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
- invalidate();
+ postInvalidateOnAnimation();
}
mActivePointerId = INVALID_POINTER;
endDrag();
@@ -642,13 +642,13 @@ public class ScrollView extends FrameLayout {
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
- mLastMotionY = ev.getY(index);
+ mLastMotionY = (int) ev.getY(index);
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
- mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
+ mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
break;
}
return true;
@@ -663,7 +663,7 @@ public class ScrollView extends FrameLayout {
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- mLastMotionY = ev.getY(newPointerIndex);
+ mLastMotionY = (int) ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
@@ -1047,7 +1047,7 @@ public class ScrollView extends FrameLayout {
dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;
mScroller.startScroll(mScrollX, scrollY, 0, dy);
- invalidate();
+ postInvalidateOnAnimation();
} else {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
@@ -1174,7 +1174,7 @@ public class ScrollView extends FrameLayout {
if (!awakenScrollBars()) {
// Keep on drawing until the animation has finished.
- invalidate();
+ postInvalidateOnAnimation();
}
} else {
if (mFlingStrictSpan != null) {
@@ -1430,7 +1430,7 @@ public class ScrollView extends FrameLayout {
mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
}
- invalidate();
+ postInvalidateOnAnimation();
}
}
@@ -1495,7 +1495,7 @@ public class ScrollView extends FrameLayout {
canvas.translate(mPaddingLeft, Math.min(0, scrollY));
mEdgeGlowTop.setSize(width, getHeight());
if (mEdgeGlowTop.draw(canvas)) {
- invalidate();
+ postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
@@ -1509,7 +1509,7 @@ public class ScrollView extends FrameLayout {
canvas.rotate(180, width, 0);
mEdgeGlowBottom.setSize(width, height);
if (mEdgeGlowBottom.draw(canvas)) {
- invalidate();
+ postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 9afaee3..c725b64 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -227,8 +227,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
for (int i = 0; i < length; i++) {
final SpellParser spellParser = mSpellParsers[i];
if (spellParser.isFinished()) {
- spellParser.init(start, end);
- spellParser.parse();
+ spellParser.parse(start, end);
return;
}
}
@@ -240,8 +239,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
SpellParser spellParser = new SpellParser();
mSpellParsers[length] = spellParser;
- spellParser.init(start, end);
- spellParser.parse();
+ spellParser.parse(start, end);
}
private void spellCheck() {
@@ -421,8 +419,11 @@ public class SpellChecker implements SpellCheckerSessionListener {
private class SpellParser {
private Object mRange = new Object();
- public void init(int start, int end) {
- setRangeSpan((Editable) mTextView.getText(), start, end);
+ public void parse(int start, int end) {
+ if (end > start) {
+ setRangeSpan((Editable) mTextView.getText(), start, end);
+ parse();
+ }
}
public boolean isFinished() {
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index aef8a34..36d1ee0 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -907,7 +907,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public void onItemClick(AdapterView parent, View v, int position, long id) {
Spinner.this.setSelection(position);
if (mOnItemClickListener != null) {
- Spinner.this.performItemClick(null, position, mAdapter.getItemId(position));
+ Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
}
dismiss();
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index a897cc3..0786909 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -806,5 +806,16 @@ public class Switch extends CompoundButton {
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(Switch.class.getName());
+ CharSequence switchText = isChecked() ? mTextOn : mTextOff;
+ if (!TextUtils.isEmpty(switchText)) {
+ CharSequence oldText = info.getText();
+ if (TextUtils.isEmpty(oldText)) {
+ info.setText(switchText);
+ } else {
+ StringBuilder newText = new StringBuilder();
+ newText.append(oldText).append(' ').append(switchText);
+ info.setText(newText);
+ }
+ }
}
}