diff options
Diffstat (limited to 'core/java/android')
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 <activity_component_name>". * * @param prefix Desired prefix to prepend at each line of output. * @param fd The raw file descriptor that the dump is being sent to. @@ -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 <yourservicename>". + * This is distinct from "dumpsys <servicename>", which only works for * named system services and which invokes the {@link IBinder#dump} method * on the {@link IBinder} interface registered with ServiceManager. * diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 05ef194..1206056 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -1127,7 +1127,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { /** * Print the Provider's state into the given stream. This gets invoked if - * you run "adb shell dumpsys activity provider <provider_component_name>". + * you run "adb shell dumpsys activity provider <provider_component_name>". * * @param prefix Desired prefix to prepend at each line of output. * @param fd The raw file descriptor that the dump is being sent to. diff --git a/core/java/android/content/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); + } + } } } |
