diff options
Diffstat (limited to 'core/java')
24 files changed, 856 insertions, 221 deletions
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index b0bad07..9d6ee80 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -556,7 +556,7 @@ public abstract class AccessibilityService extends Service { public IAccessibilityServiceClientWrapper(Context context, Looper looper, Callbacks callback) { mCallback = callback; - mCaller = new HandlerCaller(context, looper, this); + mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/); } public void setConnection(IAccessibilityServiceConnection connection, int connectionId) { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index e779746..e96b475 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4818,8 +4818,8 @@ public class Activity extends ContextThemeWrapper * <code>android:immersive</code> but may be changed at runtime by * {@link #setImmersive}. * + * @see #setImmersive(boolean) * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE - * @hide */ public boolean isImmersive() { try { @@ -4831,7 +4831,7 @@ public class Activity extends ContextThemeWrapper /** * Adjust the current immersive mode setting. - * + * * Note that changing this value will have no effect on the activity's * {@link android.content.pm.ActivityInfo} structure; that is, if * <code>android:immersive</code> is set to <code>true</code> @@ -4840,9 +4840,8 @@ public class Activity extends ContextThemeWrapper * always have its {@link android.content.pm.ActivityInfo#FLAG_IMMERSIVE * FLAG_IMMERSIVE} bit set. * - * @see #isImmersive + * @see #isImmersive() * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE - * @hide */ public void setImmersive(boolean i) { try { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d880817..d62372c 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -113,7 +113,6 @@ import java.util.TimeZone; import java.util.regex.Pattern; import libcore.io.DropBox; -import libcore.io.EventLogger; import libcore.io.IoUtils; import dalvik.system.CloseGuard; @@ -4981,13 +4980,6 @@ public final class ActivityThread { } } - private static class EventLoggingReporter implements EventLogger.Reporter { - @Override - public void report (int code, Object... list) { - EventLog.writeEvent(code, list); - } - } - private class DropBoxReporter implements DropBox.Reporter { private DropBoxManager dropBox; @@ -5017,9 +5009,6 @@ public final class ActivityThread { Environment.initForCurrentUser(); - // Set the reporter for event logging in libcore - EventLogger.setReporter(new EventLoggingReporter()); - Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index e2ca1dd..8f3b62d 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -161,7 +161,6 @@ public class ActivityInfo extends ComponentInfo */ public static final int FLAG_SHOW_ON_LOCK_SCREEN = 0x0400; /** - * @hide * Bit in {@link #flags} corresponding to an immersive activity * that wishes not to be interrupted by notifications. * Applications that hide the system notification bar with @@ -174,7 +173,14 @@ public class ActivityInfo extends ComponentInfo * {@link #FLAG_IMMERSIVE} set, however, will not be interrupted; the * notification may be shown in some other way (such as a small floating * "toast" window). - * {@see android.app.Notification#FLAG_HIGH_PRIORITY} + * + * Note that this flag will always reflect the Activity's + * <code>android:immersive</code> manifest definition, even if the Activity's + * immersive state is changed at runtime via + * {@link android.app.Activity#setImmersive(boolean)}. + * + * @see android.app.Notification#FLAG_HIGH_PRIORITY + * @see android.app.Activity#setImmersive(boolean) */ public static final int FLAG_IMMERSIVE = 0x0800; /** diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index 5324f81..d78262b 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -70,7 +70,8 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub public IInputMethodSessionWrapper(Context context, InputMethodSession inputMethodSession) { - mCaller = new HandlerCaller(context, this); + mCaller = new HandlerCaller(context, null, + this, true /*asyncHandler*/); mInputMethodSession = inputMethodSession; } diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 5275314..2d67875 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -102,7 +102,8 @@ class IInputMethodWrapper extends IInputMethod.Stub public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) { mTarget = new WeakReference<AbstractInputMethodService>(context); - mCaller = new HandlerCaller(context.getApplicationContext(), this); + mCaller = new HandlerCaller(context.getApplicationContext(), null, + this, true /*asyncHandler*/); mInputMethod = new WeakReference<InputMethod>(inputMethod); mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion; } diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java index 3a06dc0..37601fc 100644 --- a/core/java/android/net/EthernetDataTracker.java +++ b/core/java/android/net/EthernetDataTracker.java @@ -74,7 +74,7 @@ public class EthernetDataTracker implements NetworkStateTracker { } public void interfaceLinkStateChanged(String iface, boolean up) { - if (mIface.equals(iface) && mLinkUp != up) { + if (mIface.equals(iface)) { Log.d(TAG, "Interface " + iface + " link " + (up ? "up" : "down")); mLinkUp = up; mTracker.mNetworkInfo.setIsAvailable(up); diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 336960e..e343e83 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -939,8 +939,9 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click * listener should be called in the proper order (between other * processing). May be null. + * @hide */ - void performClick(PreferenceScreen preferenceScreen) { + public void performClick(PreferenceScreen preferenceScreen) { if (!isEnabled()) { return; diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java index 5ca7d79..17f88f1 100644 --- a/core/java/android/preference/PreferenceManager.java +++ b/core/java/android/preference/PreferenceManager.java @@ -138,7 +138,10 @@ public class PreferenceManager { private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener; - PreferenceManager(Activity activity, int firstRequestCode) { + /** + * @hide + */ + public PreferenceManager(Activity activity, int firstRequestCode) { mActivity = activity; mNextRequestCode = firstRequestCode; diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index 46f2723..1a10644 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -240,28 +240,8 @@ public class SearchManagerService extends ISearchManager.Stub { @Override public ComponentName getAssistIntent(int userHandle) { try { - if (userHandle != UserHandle.getCallingUserId()) { - // Requesting a different user, make sure that they have the permission - if (ActivityManager.checkComponentPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - Binder.getCallingUid(), -1, true) - == PackageManager.PERMISSION_GRANTED) { - // Translate to the current user id, if caller wasn't aware - if (userHandle == UserHandle.USER_CURRENT) { - long identity = Binder.clearCallingIdentity(); - userHandle = ActivityManagerNative.getDefault().getCurrentUser().id; - Binder.restoreCallingIdentity(identity); - } - } else { - String msg = "Permission Denial: " - + "Request to getAssistIntent for " + userHandle - + " but is calling from user " + UserHandle.getCallingUserId() - + "; this requires " - + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; - Slog.w(TAG, msg); - return null; - } - } + userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userHandle, true, false, "getAssistIntent", null); IPackageManager pm = AppGlobals.getPackageManager(); Intent assistIntent = new Intent(Intent.ACTION_ASSIST); ResolveInfo info = diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 6d5705d..d1b23e4 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -961,13 +961,7 @@ public abstract class WallpaperService extends Service { IWallpaperEngineWrapper(WallpaperService context, IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight) { - if (DEBUG && mCallbackLooper != null) { - mCallbackLooper.setMessageLogging(new LogPrinter(Log.VERBOSE, TAG)); - } - mCaller = new HandlerCaller(context, - mCallbackLooper != null - ? mCallbackLooper : context.getMainLooper(), - this); + mCaller = new HandlerCaller(context, context.getMainLooper(), this, true); mConnection = conn; mWindowToken = windowToken; mWindowType = windowType; diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index 5ef86b1..200b57b 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -411,9 +411,6 @@ public class Time { * @throws android.util.TimeFormatException if s cannot be parsed. */ public boolean parse(String s) { - if (s == null) { - throw new NullPointerException("time string is null"); - } if (nativeParse(s)) { timezone = TIMEZONE_UTC; return true; diff --git a/core/java/android/util/PropertyValueModel.java b/core/java/android/util/PropertyValueModel.java new file mode 100755 index 0000000..eb9c47d --- /dev/null +++ b/core/java/android/util/PropertyValueModel.java @@ -0,0 +1,143 @@ +/* + * 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.util; + +/** + * A value model for a {@link Property property} of a host object. This class can be used for + * both reflective and non-reflective property implementations. + * + * @param <H> the host type, where the host is the object that holds this property + * @param <T> the value type + * + * @see Property + * @see ValueModel + */ +public class PropertyValueModel<H, T> extends ValueModel<T> { + private final H mHost; + private final Property<H, T> mProperty; + + private PropertyValueModel(H host, Property<H, T> property) { + mProperty = property; + mHost = host; + } + + /** + * Returns the host. + * + * @return the host + */ + public H getHost() { + return mHost; + } + + /** + * Returns the property. + * + * @return the property + */ + public Property<H, T> getProperty() { + return mProperty; + } + + @Override + public Class<T> getType() { + return mProperty.getType(); + } + + @Override + public T get() { + return mProperty.get(mHost); + } + + @Override + public void set(T value) { + mProperty.set(mHost, value); + } + + /** + * Return an appropriate PropertyValueModel for this host and property. + * + * @param host the host + * @param property the property + * @return the value model + */ + public static <H, T> PropertyValueModel<H, T> of(H host, Property<H, T> property) { + return new PropertyValueModel<H, T>(host, property); + } + + /** + * Return a PropertyValueModel for this {@code host} and a + * reflective property, constructed from this {@code propertyType} and {@code propertyName}. + * + * @param host + * @param propertyType the property type + * @param propertyName the property name + * @return a value model with this host and a reflective property with this type and name + * + * @see Property#of + */ + public static <H, T> PropertyValueModel<H, T> of(H host, Class<T> propertyType, + String propertyName) { + return of(host, Property.of((Class<H>) host.getClass(), propertyType, propertyName)); + } + + private static Class getNullaryMethodReturnType(Class c, String name) { + try { + return c.getMethod(name).getReturnType(); + } catch (NoSuchMethodException e) { + return null; + } + } + + private static Class getFieldType(Class c, String name) { + try { + return c.getField(name).getType(); + } catch (NoSuchFieldException e) { + return null; + } + } + + private static String capitalize(String name) { + if (name.isEmpty()) { + return name; + } + return Character.toUpperCase(name.charAt(0)) + name.substring(1); + } + + /** + * Return a PropertyValueModel for this {@code host} and and {@code propertyName}. + * + * @param host the host + * @param propertyName the property name + * @return a value model with this host and a reflective property with this name + */ + public static PropertyValueModel of(Object host, String propertyName) { + Class clazz = host.getClass(); + String suffix = capitalize(propertyName); + Class propertyType = getNullaryMethodReturnType(clazz, "get" + suffix); + if (propertyType == null) { + propertyType = getNullaryMethodReturnType(clazz, "is" + suffix); + } + if (propertyType == null) { + propertyType = getFieldType(clazz, propertyName); + } + if (propertyType == null) { + throw new NoSuchPropertyException(propertyName); + } + return of(host, propertyType, propertyName); + } +} diff --git a/core/java/android/util/ValueModel.java b/core/java/android/util/ValueModel.java new file mode 100755 index 0000000..4789682 --- /dev/null +++ b/core/java/android/util/ValueModel.java @@ -0,0 +1,74 @@ +/* + * 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.util; + +/** + * A ValueModel is an abstraction for a 'slot' or place in memory in which a value + * may be stored and retrieved. A common implementation of ValueModel is a regular property of + * an object, whose value may be retrieved by calling the appropriate <em>getter</em> + * method and set by calling the corresponding <em>setter</em> method. + * + * @param <T> the value type + * + * @see PropertyValueModel + */ +public abstract class ValueModel<T> { + /** + * The empty model should be used in place of {@code null} to indicate that a + * model has not been set. The empty model has no value and does nothing when it is set. + */ + public static final ValueModel EMPTY = new ValueModel() { + @Override + public Class getType() { + return Object.class; + } + + @Override + public Object get() { + return null; + } + + @Override + public void set(Object value) { + + } + }; + + protected ValueModel() { + } + + /** + * Returns the type of this property. + * + * @return the property type + */ + public abstract Class<T> getType(); + + /** + * Returns the value of this property. + * + * @return the property value + */ + public abstract T get(); + + /** + * Sets the value of this property. + * + * @param value the new value for this property + */ + public abstract void set(T value); +}
\ No newline at end of file diff --git a/core/java/android/view/SimulatedDpad.java b/core/java/android/view/SimulatedDpad.java new file mode 100644 index 0000000..b03e4c7 --- /dev/null +++ b/core/java/android/view/SimulatedDpad.java @@ -0,0 +1,298 @@ +/* + * 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.view; + +import android.app.SearchManager; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.util.Log; + +/** + * This class creates DPAD events from touchpad events. + * + * @see ViewRootImpl + */ + +//TODO: Make this class an internal class of ViewRootImpl.java +class SimulatedDpad { + + private static final String TAG = "SimulatedDpad"; + + // Maximum difference in milliseconds between the down and up of a touch + // event for it to be considered a tap + // TODO:Read this value from a configuration file + private static final int MAX_TAP_TIME = 250; + // Where the cutoff is for determining an edge swipe + private static final float EDGE_SWIPE_THRESHOLD = 0.9f; + private static final int MSG_FLICK = 313; + // TODO: Pass touch slop from the input device + private static final int TOUCH_SLOP = 30; + // The position of the previous touchpad event + private float mLastTouchpadXPosition; + private float mLastTouchpadYPosition; + // Where the touchpad was initially pressed + private float mTouchpadEnterXPosition; + private float mTouchpadEnterYPosition; + // When the most recent ACTION_HOVER_ENTER occurred + private long mLastTouchPadStartTimeMs = 0; + // When the most recent direction key was sent + private long mLastTouchPadKeySendTimeMs = 0; + // When the most recent touch event of any type occurred + private long mLastTouchPadEventTimeMs = 0; + // Did the swipe begin in a valid region + private boolean mEdgeSwipePossible; + + private final Context mContext; + + // How quickly keys were sent; + private int mKeySendRateMs = 0; + private int mLastKeySent; + // Last movement in device screen pixels + private float mLastMoveX = 0; + private float mLastMoveY = 0; + // Offset from the initial touch. Gets reset as direction keys are sent. + private float mAccumulatedX; + private float mAccumulatedY; + + // Change in position allowed during tap events + private float mTouchSlop; + private float mTouchSlopSquared; + // Has the TouchSlop constraint been invalidated + private boolean mAlwaysInTapRegion = true; + + // Information from the most recent event. + // Used to determine what device sent the event during a fling. + private int mLastSource; + private int mLastMetaState; + private int mLastDeviceId; + + // TODO: Currently using screen dimensions tuned to a Galaxy Nexus, need to + // read this from a config file instead + private int mDistancePerTick; + private int mDistancePerTickSquared; + // Highest rate that the flinged events can occur at before dying out + private int mMaxRepeatDelay; + // The square of the minimum distance needed for a flick to register + private int mMinFlickDistanceSquared; + // How quickly the repeated events die off + private float mFlickDecay; + + public SimulatedDpad(Context context) { + mDistancePerTick = SystemProperties.getInt("persist.vr_dist_tick", 64); + mDistancePerTickSquared = mDistancePerTick * mDistancePerTick; + mMaxRepeatDelay = SystemProperties.getInt("persist.vr_repeat_delay", 300); + mMinFlickDistanceSquared = SystemProperties.getInt("persist.vr_min_flick", 20); + mMinFlickDistanceSquared *= mMinFlickDistanceSquared; + mFlickDecay = Float.parseFloat(SystemProperties.get( + "persist.sys.vr_flick_decay", "1.3")); + mTouchSlop = TOUCH_SLOP; + mTouchSlopSquared = mTouchSlop * mTouchSlop; + + mContext = context; + } + + private final Handler mHandler = new Handler(true /*async*/) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_FLICK: { + final long time = SystemClock.uptimeMillis(); + ViewRootImpl viewroot = (ViewRootImpl) msg.obj; + // Send the key + viewroot.enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_DOWN, msg.arg2, 0, mLastMetaState, + mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource)); + viewroot.enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_UP, msg.arg2, 0, mLastMetaState, + mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource)); + + // Increase the delay by the decay factor and resend + final int delay = (int) Math.ceil(mFlickDecay * msg.arg1); + if (delay <= mMaxRepeatDelay) { + Message msgCopy = Message.obtain(msg); + msgCopy.arg1 = delay; + msgCopy.setAsynchronous(true); + mHandler.sendMessageDelayed(msgCopy, delay); + } + break; + } + } + } + }; + + public void updateTouchPad(ViewRootImpl viewroot, MotionEvent event, + boolean synthesizeNewKeys) { + if (!synthesizeNewKeys) { + mHandler.removeMessages(MSG_FLICK); + } + InputDevice device = event.getDevice(); + if (device == null) { + return; + } + // Store what time the touchpad event occurred + final long time = SystemClock.uptimeMillis(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mLastTouchPadStartTimeMs = time; + mAlwaysInTapRegion = true; + mTouchpadEnterXPosition = event.getX(); + mTouchpadEnterYPosition = event.getY(); + mAccumulatedX = 0; + mAccumulatedY = 0; + mLastMoveX = 0; + mLastMoveY = 0; + if (device.getMotionRange(MotionEvent.AXIS_Y).getMax() + * EDGE_SWIPE_THRESHOLD < event.getY()) { + // Did the swipe begin in a valid region + mEdgeSwipePossible = true; + } + // Clear any flings + if (synthesizeNewKeys) { + mHandler.removeMessages(MSG_FLICK); + } + break; + case MotionEvent.ACTION_MOVE: + // Determine whether the move is slop or an intentional move + float deltaX = event.getX() - mTouchpadEnterXPosition; + float deltaY = event.getY() - mTouchpadEnterYPosition; + if (mTouchSlopSquared < deltaX * deltaX + deltaY * deltaY) { + mAlwaysInTapRegion = false; + } + // Checks if the swipe has crossed the midpoint + // and if our swipe gesture is complete + if (event.getY() < (device.getMotionRange(MotionEvent.AXIS_Y).getMax() + * .5) && mEdgeSwipePossible) { + mEdgeSwipePossible = false; + + Intent intent = + ((SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, UserHandle.USER_CURRENT_OR_SELF); + if (intent != null) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mContext.startActivity(intent); + } catch (ActivityNotFoundException e){ + Log.e(TAG, "Could not start search activity"); + } + } else { + Log.e(TAG, "Could not find a search activity"); + } + } + // Find the difference in position between the two most recent + // touchpad events + mLastMoveX = event.getX() - mLastTouchpadXPosition; + mLastMoveY = event.getY() - mLastTouchpadYPosition; + mAccumulatedX += mLastMoveX; + mAccumulatedY += mLastMoveY; + float mAccumulatedXSquared = mAccumulatedX * mAccumulatedX; + float mAccumulatedYSquared = mAccumulatedY * mAccumulatedY; + // Determine if we've moved far enough to send a key press + if (mAccumulatedXSquared > mDistancePerTickSquared || + mAccumulatedYSquared > mDistancePerTickSquared) { + float dominantAxis; + float sign; + boolean isXAxis; + int key; + int repeatCount = 0; + // Determine dominant axis + if (mAccumulatedXSquared > mAccumulatedYSquared) { + dominantAxis = mAccumulatedX; + isXAxis = true; + } else { + dominantAxis = mAccumulatedY; + isXAxis = false; + } + // Determine sign of axis + sign = (dominantAxis > 0) ? 1 : -1; + // Determine key to send + if (isXAxis) { + key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_RIGHT : + KeyEvent.KEYCODE_DPAD_LEFT; + } else { + key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP; + } + // Send key until maximum distance constraint is satisfied + while (dominantAxis * dominantAxis > mDistancePerTickSquared) { + repeatCount++; + dominantAxis -= sign * mDistancePerTick; + if (synthesizeNewKeys) { + viewroot.enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_DOWN, key, 0, event.getMetaState(), + event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK, + event.getSource())); + viewroot.enqueueInputEvent(new KeyEvent(time, time, + KeyEvent.ACTION_UP, key, 0, event.getMetaState(), + event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK, + event.getSource())); + } + } + // Save new axis values + mAccumulatedX = isXAxis ? dominantAxis : 0; + mAccumulatedY = isXAxis ? 0 : dominantAxis; + + mLastKeySent = key; + mKeySendRateMs = (int) ((time - mLastTouchPadKeySendTimeMs) / repeatCount); + mLastTouchPadKeySendTimeMs = time; + } + break; + case MotionEvent.ACTION_UP: + if (time - mLastTouchPadStartTimeMs < MAX_TAP_TIME && mAlwaysInTapRegion) { + if (synthesizeNewKeys) { + viewroot.enqueueInputEvent(new KeyEvent(mLastTouchPadStartTimeMs, time, + KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, + event.getMetaState(), event.getDeviceId(), 0, + KeyEvent.FLAG_FALLBACK, event.getSource())); + viewroot.enqueueInputEvent(new KeyEvent(mLastTouchPadStartTimeMs, time, + KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, + event.getMetaState(), event.getDeviceId(), 0, + KeyEvent.FLAG_FALLBACK, event.getSource())); + } + } else { + float xMoveSquared = mLastMoveX * mLastMoveX; + float yMoveSquared = mLastMoveY * mLastMoveY; + // Determine whether the last gesture was a fling. + if (mMinFlickDistanceSquared <= xMoveSquared + yMoveSquared && + time - mLastTouchPadEventTimeMs <= MAX_TAP_TIME && + mKeySendRateMs <= mMaxRepeatDelay && mKeySendRateMs > 0) { + mLastDeviceId = event.getDeviceId(); + mLastSource = event.getSource(); + mLastMetaState = event.getMetaState(); + + if (synthesizeNewKeys) { + Message message = Message.obtain(mHandler, MSG_FLICK, + mKeySendRateMs, mLastKeySent, viewroot); + message.setAsynchronous(true); + mHandler.sendMessageDelayed(message, mKeySendRateMs); + } + } + } + mEdgeSwipePossible = false; + break; + } + + // Store touch event position and time + mLastTouchPadEventTimeMs = time; + mLastTouchpadXPosition = event.getX(); + mLastTouchpadYPosition = event.getY(); + } +} diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index b6016e9..58e6fbe 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -144,6 +144,8 @@ public final class ViewRootImpl implements ViewParent, final TrackballAxis mTrackballAxisX = new TrackballAxis(); final TrackballAxis mTrackballAxisY = new TrackballAxis(); + final SimulatedDpad mSimulatedDpad; + int mLastJoystickXDirection; int mLastJoystickYDirection; int mLastJoystickXKeyCode; @@ -230,6 +232,13 @@ public final class ViewRootImpl implements ViewParent, int mLastSystemUiVisibility; int mClientWindowLayoutFlags; + /** @hide */ + public static final int EVENT_NOT_HANDLED = 0; + /** @hide */ + public static final int EVENT_HANDLED = 1; + /** @hide */ + public static final int EVENT_IN_PROGRESS = 2; + // Pool of queued input events. private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10; private QueuedInputEvent mQueuedInputEventPool; @@ -383,6 +392,7 @@ public final class ViewRootImpl implements ViewParent, PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mAttachInfo.mScreenOn = powerManager.isScreenOn(); loadSystemProperties(); + mSimulatedDpad = new SimulatedDpad(context); } /** @@ -3154,19 +3164,19 @@ public final class ViewRootImpl implements ViewParent, return false; } - private void deliverInputEvent(QueuedInputEvent q) { + private int deliverInputEvent(QueuedInputEvent q) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent"); try { if (q.mEvent instanceof KeyEvent) { - deliverKeyEvent(q); + return deliverKeyEvent(q); } else { final int source = q.mEvent.getSource(); if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { - deliverPointerEvent(q); + return deliverPointerEvent(q); } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { - deliverTrackballEvent(q); + return deliverTrackballEvent(q); } else { - deliverGenericMotionEvent(q); + return deliverGenericMotionEvent(q); } } } finally { @@ -3174,7 +3184,25 @@ public final class ViewRootImpl implements ViewParent, } } - private void deliverPointerEvent(QueuedInputEvent q) { + private int deliverInputEventPostIme(QueuedInputEvent q) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEventPostIme"); + try { + if (q.mEvent instanceof KeyEvent) { + return deliverKeyEventPostIme(q); + } else { + final int source = q.mEvent.getSource(); + if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { + return deliverTrackballEventPostIme(q); + } else { + return deliverGenericMotionEventPostIme(q); + } + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + } + + private int deliverPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; final boolean isTouchEvent = event.isTouchEvent(); if (mInputEventConsistencyVerifier != null) { @@ -3187,8 +3215,7 @@ public final class ViewRootImpl implements ViewParent, // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { - finishInputEvent(q, false); - return; + return EVENT_NOT_HANDLED; } // Translate the pointer event for compatibility, if needed. @@ -3221,16 +3248,10 @@ public final class ViewRootImpl implements ViewParent, if (MEASURE_LATENCY) { lt.sample("B Dispatched PointerEvents ", System.nanoTime() - event.getEventTimeNano()); } - if (handled) { - finishInputEvent(q, true); - return; - } - - // Pointer event was unhandled. - finishInputEvent(q, false); + return handled ? EVENT_HANDLED : EVENT_NOT_HANDLED; } - private void deliverTrackballEvent(QueuedInputEvent q) { + private int deliverTrackballEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTrackballEvent(event, 0); @@ -3249,24 +3270,25 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_IMF) Log.v(TAG, "Sending trackball event to IME: seq=" + seq + " event=" + event); - imm.dispatchTrackballEvent(mView.getContext(), seq, event, + int result = imm.dispatchTrackballEvent(mView.getContext(), seq, event, mInputMethodCallback); - return; + if (result != EVENT_NOT_HANDLED) { + return result; + } } } } // Not dispatching to IME, continue with post IME actions. - deliverTrackballEventPostIme(q); + return deliverTrackballEventPostIme(q); } - private void deliverTrackballEventPostIme(QueuedInputEvent q) { + private int deliverTrackballEventPostIme(QueuedInputEvent q) { final MotionEvent event = (MotionEvent) q.mEvent; // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { - finishInputEvent(q, false); - return; + return EVENT_NOT_HANDLED; } // Deliver the trackball event to the view. @@ -3276,10 +3298,8 @@ public final class ViewRootImpl implements ViewParent, // event into a key event, touch mode will not exit, so we exit // touch mode here. ensureTouchMode(false); - - finishInputEvent(q, true); mLastTrackballTime = Integer.MIN_VALUE; - return; + return EVENT_HANDLED; } // Translate the trackball event into DPAD keys and try to deliver those. @@ -3387,15 +3407,14 @@ public final class ViewRootImpl implements ViewParent, // Unfortunately we can't tell whether the application consumed the keys, so // we always consider the trackball event handled. - finishInputEvent(q, true); + return EVENT_HANDLED; } - private void deliverGenericMotionEvent(QueuedInputEvent q) { + private int deliverGenericMotionEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onGenericMotionEvent(event, 0); } - if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) { if (LOCAL_LOGV) Log.v(TAG, "Dispatching generic motion " + event + " to " + mView); @@ -3409,47 +3428,56 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_IMF) Log.v(TAG, "Sending generic motion event to IME: seq=" + seq + " event=" + event); - imm.dispatchGenericMotionEvent(mView.getContext(), seq, event, + int result = imm.dispatchGenericMotionEvent(mView.getContext(), seq, event, mInputMethodCallback); - return; + if (result != EVENT_NOT_HANDLED) { + return result; + } } } } // Not dispatching to IME, continue with post IME actions. - deliverGenericMotionEventPostIme(q); + return deliverGenericMotionEventPostIme(q); } - private void deliverGenericMotionEventPostIme(QueuedInputEvent q) { + private int deliverGenericMotionEventPostIme(QueuedInputEvent q) { final MotionEvent event = (MotionEvent) q.mEvent; - final boolean isJoystick = (event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0; + final int source = event.getSource(); + final boolean isJoystick = (source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0; + final boolean isTouchPad = (source & InputDevice.SOURCE_CLASS_POSITION) != 0; // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { if (isJoystick) { updateJoystickDirection(event, false); + } else if (isTouchPad) { + mSimulatedDpad.updateTouchPad(this, event, false); } - finishInputEvent(q, false); - return; + return EVENT_NOT_HANDLED; } // Deliver the event to the view. if (mView.dispatchGenericMotionEvent(event)) { if (isJoystick) { updateJoystickDirection(event, false); + } else if (isTouchPad) { + mSimulatedDpad.updateTouchPad(this, event, false); } - finishInputEvent(q, true); - return; + return EVENT_HANDLED; } if (isJoystick) { // Translate the joystick event into DPAD keys and try to deliver // those. updateJoystickDirection(event, true); - finishInputEvent(q, true); - } else { - finishInputEvent(q, false); + return EVENT_HANDLED; + } + if (isTouchPad) { + mSimulatedDpad.updateTouchPad(this, event, true); + return EVENT_HANDLED; } + return EVENT_NOT_HANDLED; } private void updateJoystickDirection(MotionEvent event, boolean synthesizeNewKeys) { @@ -3593,7 +3621,7 @@ public final class ViewRootImpl implements ViewParent, return false; } - private void deliverKeyEvent(QueuedInputEvent q) { + private int deliverKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 0); @@ -3604,8 +3632,7 @@ public final class ViewRootImpl implements ViewParent, // Perform predispatching before the IME. if (mView.dispatchKeyEventPreIme(event)) { - finishInputEvent(q, true); - return; + return EVENT_HANDLED; } // Dispatch to the IME before propagating down the view hierarchy. @@ -3616,81 +3643,30 @@ public final class ViewRootImpl implements ViewParent, final int seq = event.getSequenceNumber(); if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq=" + seq + " event=" + event); - imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback); - return; + int result = imm.dispatchKeyEvent(mView.getContext(), seq, event, + mInputMethodCallback); + if (result != EVENT_NOT_HANDLED) { + return result; + } } } } // Not dispatching to IME, continue with post IME actions. - deliverKeyEventPostIme(q); + return deliverKeyEventPostIme(q); } - void handleImeFinishedEvent(int seq, boolean handled) { - final QueuedInputEvent q = mCurrentInputEvent; - if (q != null && q.mEvent.getSequenceNumber() == seq) { - if (DEBUG_IMF) { - Log.v(TAG, "IME finished event: seq=" + seq - + " handled=" + handled + " event=" + q); - } - if (handled) { - finishInputEvent(q, true); - } else { - if (q.mEvent instanceof KeyEvent) { - KeyEvent event = (KeyEvent)q.mEvent; - if (event.getAction() != KeyEvent.ACTION_UP) { - // If the window doesn't currently have input focus, then drop - // this event. This could be an event that came back from the - // IME dispatch but the window has lost focus in the meantime. - if (!mAttachInfo.mHasWindowFocus) { - Slog.w(TAG, "Dropping event due to no window focus: " + event); - finishInputEvent(q, true); - return; - } - } - deliverKeyEventPostIme(q); - } else { - MotionEvent event = (MotionEvent)q.mEvent; - if (event.getAction() != MotionEvent.ACTION_CANCEL - && event.getAction() != MotionEvent.ACTION_UP) { - // If the window doesn't currently have input focus, then drop - // this event. This could be an event that came back from the - // IME dispatch but the window has lost focus in the meantime. - if (!mAttachInfo.mHasWindowFocus) { - Slog.w(TAG, "Dropping event due to no window focus: " + event); - finishInputEvent(q, true); - return; - } - } - final int source = q.mEvent.getSource(); - if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { - deliverTrackballEventPostIme(q); - } else { - deliverGenericMotionEventPostIme(q); - } - } - } - } else { - if (DEBUG_IMF) { - Log.v(TAG, "IME finished event: seq=" + seq - + " handled=" + handled + ", event not found!"); - } - } - } - - private void deliverKeyEventPostIme(QueuedInputEvent q) { + private int deliverKeyEventPostIme(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; // If the view went away, then the event will not be handled. if (mView == null || !mAdded) { - finishInputEvent(q, false); - return; + return EVENT_NOT_HANDLED; } // If the key's purpose is to exit touch mode then we consume it and consider it handled. if (checkForLeavingTouchModeAndConsume(event)) { - finishInputEvent(q, true); - return; + return EVENT_HANDLED; } // Make sure the fallback event policy sees all keys that will be delivered to the @@ -3699,8 +3675,7 @@ public final class ViewRootImpl implements ViewParent, // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { - finishInputEvent(q, true); - return; + return EVENT_HANDLED; } // If the Control modifier is held, try to interpret the key as a shortcut. @@ -3709,15 +3684,13 @@ public final class ViewRootImpl implements ViewParent, && event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(event.getKeyCode())) { if (mView.dispatchKeyShortcutEvent(event)) { - finishInputEvent(q, true); - return; + return EVENT_HANDLED; } } // Apply the fallback event policy. if (mFallbackEventHandler.dispatchKeyEvent(event)) { - finishInputEvent(q, true); - return; + return EVENT_HANDLED; } // Handle automatic focus changes. @@ -3770,22 +3743,20 @@ public final class ViewRootImpl implements ViewParent, if (v.requestFocus(direction, mTempRect)) { playSoundEffect(SoundEffectConstants .getContantForFocusDirection(direction)); - finishInputEvent(q, true); - return; + return EVENT_HANDLED; } } // Give the focused view a last chance to handle the dpad key. if (mView.dispatchUnhandledMove(focused, direction)) { - finishInputEvent(q, true); - return; + return EVENT_HANDLED; } } } } // Key was unhandled. - finishInputEvent(q, false); + return EVENT_NOT_HANDLED; } /* drag/drop */ @@ -4289,7 +4260,11 @@ public final class ViewRootImpl implements ViewParent, mFirstPendingInputEvent = q.mNext; q.mNext = null; mCurrentInputEvent = q; - deliverInputEvent(q); + + final int result = deliverInputEvent(q); + if (result != EVENT_IN_PROGRESS) { + finishCurrentInputEvent(result == EVENT_HANDLED); + } } // We are done processing all input events that we can process right now @@ -4300,10 +4275,42 @@ public final class ViewRootImpl implements ViewParent, } } - private void finishInputEvent(QueuedInputEvent q, boolean handled) { - if (q != mCurrentInputEvent) { - throw new IllegalStateException("finished input event out of order"); + void handleImeFinishedEvent(int seq, boolean handled) { + final QueuedInputEvent q = mCurrentInputEvent; + if (q != null && q.mEvent.getSequenceNumber() == seq) { + if (DEBUG_IMF) { + Log.v(TAG, "IME finished event: seq=" + seq + + " handled=" + handled + " event=" + q); + } + + if (!handled) { + // If the window doesn't currently have input focus, then drop + // this event. This could be an event that came back from the + // IME dispatch but the window has lost focus in the meantime. + if (!mAttachInfo.mHasWindowFocus && !isTerminalInputEvent(q.mEvent)) { + Slog.w(TAG, "Dropping event due to no window focus: " + q.mEvent); + } else { + final int result = deliverInputEventPostIme(q); + if (result == EVENT_HANDLED) { + handled = true; + } + } + } + finishCurrentInputEvent(handled); + + // Immediately start processing the next input event. + doProcessInputEvents(); + } else { + if (DEBUG_IMF) { + Log.v(TAG, "IME finished event: seq=" + seq + + " handled=" + handled + ", event not found!"); + } } + } + + private void finishCurrentInputEvent(boolean handled) { + final QueuedInputEvent q = mCurrentInputEvent; + mCurrentInputEvent = null; if (q.mReceiver != null) { q.mReceiver.finishInputEvent(q.mEvent, handled); @@ -4312,10 +4319,18 @@ public final class ViewRootImpl implements ViewParent, } recycleQueuedInputEvent(q); + } - mCurrentInputEvent = null; - if (mFirstPendingInputEvent != null) { - scheduleProcessInputEvents(); + private static boolean isTerminalInputEvent(InputEvent event) { + if (event instanceof KeyEvent) { + final KeyEvent keyEvent = (KeyEvent)event; + return keyEvent.getAction() == KeyEvent.ACTION_UP; + } else { + final MotionEvent motionEvent = (MotionEvent)event; + final int action = motionEvent.getAction(); + return action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_CANCEL + || action == MotionEvent.ACTION_HOVER_EXIT; } } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 4a3f846..d258f4d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -334,7 +334,7 @@ public final class InputMethodManager { class H extends Handler { H(Looper looper) { - super(looper); + super(looper, null, true); } @Override @@ -1565,38 +1565,36 @@ public final class InputMethodManager { /** * @hide */ - public void dispatchKeyEvent(Context context, int seq, KeyEvent key, + public int dispatchKeyEvent(Context context, int seq, KeyEvent key, FinishedEventCallback callback) { - boolean handled = false; synchronized (mH) { if (DEBUG) Log.d(TAG, "dispatchKeyEvent"); if (mCurMethod != null) { if (key.getAction() == KeyEvent.ACTION_DOWN - && key.getKeyCode() == KeyEvent.KEYCODE_SYM) { + && key.getKeyCode() == KeyEvent.KEYCODE_SYM + && key.getRepeatCount() == 0) { showInputMethodPickerLocked(); - handled = true; - } else { - try { - if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod); - final long startTime = SystemClock.uptimeMillis(); - enqueuePendingEventLocked(startTime, seq, mCurId, callback); - mCurMethod.dispatchKeyEvent(seq, key, mInputMethodCallback); - return; - } catch (RemoteException e) { - Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e); - } + return ViewRootImpl.EVENT_HANDLED; + } + try { + if (DEBUG) Log.v(TAG, "DISPATCH KEY: " + mCurMethod); + final long startTime = SystemClock.uptimeMillis(); + enqueuePendingEventLocked(startTime, seq, mCurId, callback); + mCurMethod.dispatchKeyEvent(seq, key, mInputMethodCallback); + return ViewRootImpl.EVENT_IN_PROGRESS; + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId + " dropping: " + key, e); } } } - - callback.finishedEvent(seq, handled); + return ViewRootImpl.EVENT_NOT_HANDLED; } /** * @hide */ - public void dispatchTrackballEvent(Context context, int seq, MotionEvent motion, + public int dispatchTrackballEvent(Context context, int seq, MotionEvent motion, FinishedEventCallback callback) { synchronized (mH) { if (DEBUG) Log.d(TAG, "dispatchTrackballEvent"); @@ -1607,20 +1605,19 @@ public final class InputMethodManager { final long startTime = SystemClock.uptimeMillis(); enqueuePendingEventLocked(startTime, seq, mCurId, callback); mCurMethod.dispatchTrackballEvent(seq, motion, mInputMethodCallback); - return; + return ViewRootImpl.EVENT_IN_PROGRESS; } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId + " dropping trackball: " + motion, e); } } } - - callback.finishedEvent(seq, false); + return ViewRootImpl.EVENT_NOT_HANDLED; } /** * @hide */ - public void dispatchGenericMotionEvent(Context context, int seq, MotionEvent motion, + public int dispatchGenericMotionEvent(Context context, int seq, MotionEvent motion, FinishedEventCallback callback) { synchronized (mH) { if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent"); @@ -1631,14 +1628,13 @@ public final class InputMethodManager { final long startTime = SystemClock.uptimeMillis(); enqueuePendingEventLocked(startTime, seq, mCurId, callback); mCurMethod.dispatchGenericMotionEvent(seq, motion, mInputMethodCallback); - return; + return ViewRootImpl.EVENT_IN_PROGRESS; } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId + " dropping generic motion: " + motion, e); } } } - - callback.finishedEvent(seq, false); + return ViewRootImpl.EVENT_NOT_HANDLED; } void finishedEvent(int seq, boolean handled) { diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java index f1804f8..41ab5f2 100644 --- a/core/java/android/widget/CheckBox.java +++ b/core/java/android/widget/CheckBox.java @@ -20,6 +20,7 @@ import android.content.Context; import android.util.AttributeSet; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.util.ValueModel; /** @@ -55,7 +56,9 @@ import android.view.accessibility.AccessibilityNodeInfo; * {@link android.R.styleable#View View Attributes} * </p> */ -public class CheckBox extends CompoundButton { +public class CheckBox extends CompoundButton implements ValueEditor<Boolean> { + private ValueModel<Boolean> mValueModel = ValueModel.EMPTY; + public CheckBox(Context context) { this(context, null); } @@ -79,4 +82,22 @@ public class CheckBox extends CompoundButton { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(CheckBox.class.getName()); } + + @Override + public ValueModel<Boolean> getValueModel() { + return mValueModel; + } + + @Override + public void setValueModel(ValueModel<Boolean> valueModel) { + mValueModel = valueModel; + setChecked(mValueModel.get()); + } + + @Override + public boolean performClick() { + boolean handled = super.performClick(); + mValueModel.set(isChecked()); + return handled; + } } diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java index 57e51c2..ec81214 100644 --- a/core/java/android/widget/EditText.java +++ b/core/java/android/widget/EditText.java @@ -17,6 +17,7 @@ package android.widget; import android.content.Context; +import android.graphics.Rect; import android.text.Editable; import android.text.Selection; import android.text.Spannable; @@ -24,6 +25,7 @@ import android.text.TextUtils; import android.text.method.ArrowKeyMovementMethod; import android.text.method.MovementMethod; import android.util.AttributeSet; +import android.util.ValueModel; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -47,7 +49,9 @@ import android.view.accessibility.AccessibilityNodeInfo; * {@link android.R.styleable#TextView TextView Attributes}, * {@link android.R.styleable#View View Attributes} */ -public class EditText extends TextView { +public class EditText extends TextView implements ValueEditor<CharSequence> { + private ValueModel<CharSequence> mValueModel = ValueModel.EMPTY; + public EditText(Context context) { this(context, null); } @@ -128,4 +132,21 @@ public class EditText extends TextView { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(EditText.class.getName()); } + + @Override + public ValueModel<CharSequence> getValueModel() { + return mValueModel; + } + + @Override + public void setValueModel(ValueModel<CharSequence> valueModel) { + mValueModel = valueModel; + setText(mValueModel.get()); + } + + @Override + void sendAfterTextChanged(Editable text) { + super.sendAfterTextChanged(text); + mValueModel.set(text); + } } diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 87396fb..26c801f 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -551,7 +551,10 @@ public class ImageView extends View { applied to the drawable, be sure to call setImageMatrix(). */ public Matrix getImageMatrix() { - return mMatrix; + if (mDrawMatrix == null) { + return Matrix.IDENTITY_MATRIX; + } + return mDrawMatrix; } public void setImageMatrix(Matrix matrix) { diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index ac21671..74ded18 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -470,6 +470,11 @@ public class NumberPicker extends LinearLayout { private final PressedStateHelper mPressedStateHelper; /** + * The keycode of the last handled DPAD down event. + */ + private int mLastHandledDownDpadKeyCode = -1; + + /** * Interface to listen for changes of the current value. */ public interface OnValueChangeListener { @@ -936,6 +941,31 @@ public class NumberPicker extends LinearLayout { case KeyEvent.KEYCODE_ENTER: removeAllCallbacks(); break; + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_UP: + if (!mHasSelectorWheel) { + break; + } + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + if (mWrapSelectorWheel || (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) + ? getValue() < getMaxValue() : getValue() > getMinValue()) { + requestFocus(); + mLastHandledDownDpadKeyCode = keyCode; + removeAllCallbacks(); + if (mFlingScroller.isFinished()) { + changeValueByOne(keyCode == KeyEvent.KEYCODE_DPAD_DOWN); + } + return true; + } + break; + case KeyEvent.ACTION_UP: + if (mLastHandledDownDpadKeyCode == keyCode) { + mLastHandledDownDpadKeyCode = -1; + return true; + } + break; + } } return super.dispatchKeyEvent(event); } diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java index 2737f94..a6486a8 100644 --- a/core/java/android/widget/SeekBar.java +++ b/core/java/android/widget/SeekBar.java @@ -18,6 +18,7 @@ package android.widget; import android.content.Context; import android.util.AttributeSet; +import android.util.ValueModel; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; @@ -33,7 +34,7 @@ import android.view.accessibility.AccessibilityNodeInfo; * * @attr ref android.R.styleable#SeekBar_thumb */ -public class SeekBar extends AbsSeekBar { +public class SeekBar extends AbsSeekBar implements ValueEditor<Integer> { /** * A callback that notifies clients when the progress level has been @@ -69,8 +70,9 @@ public class SeekBar extends AbsSeekBar { void onStopTrackingTouch(SeekBar seekBar); } + private ValueModel<Integer> mValueModel = ValueModel.EMPTY; private OnSeekBarChangeListener mOnSeekBarChangeListener; - + public SeekBar(Context context) { this(context, null); } @@ -89,9 +91,23 @@ public class SeekBar extends AbsSeekBar { if (mOnSeekBarChangeListener != null) { mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser); + if (fromUser) { + mValueModel.set(getProgress()); + } } } + @Override + public ValueModel<Integer> getValueModel() { + return mValueModel; + } + + @Override + public void setValueModel(ValueModel<Integer> valueModel) { + mValueModel = valueModel; + setProgress(mValueModel.get()); + } + /** * Sets a listener to receive notifications of changes to the SeekBar's progress level. Also * provides notifications of when the user starts and stops a touch gesture within the SeekBar. diff --git a/core/java/android/widget/ValueEditor.java b/core/java/android/widget/ValueEditor.java new file mode 100755 index 0000000..2b91abf --- /dev/null +++ b/core/java/android/widget/ValueEditor.java @@ -0,0 +1,53 @@ +/* + * 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.widget; + +import android.util.ValueModel; + +/** + * An interface for editors of simple values. Classes implementing this interface are normally + * UI controls (subclasses of {@link android.view.View View}) that can provide a suitable + * user interface to display and edit values of the specified type. This interface is + * intended to describe editors for simple types, like {@code boolean}, {@code int} or + * {@code String}, where the values themselves are immutable. + * <p> + * For example, {@link android.widget.CheckBox CheckBox} implements + * this interface for the Boolean type as it is capable of providing an appropriate + * mechanism for displaying and changing the value of a Boolean property. + * + * @param <T> the value type that this editor supports + */ +public interface ValueEditor<T> { + /** + * Return the last value model that was set. If no value model has been set, the editor + * should return the value {@link android.util.ValueModel#EMPTY}. + * + * @return the value model + */ + public ValueModel<T> getValueModel(); + + /** + * Sets the value model for this editor. When the value model is set, the editor should + * retrieve the value from the value model, using {@link android.util.ValueModel#get()}, + * and set its internal state accordingly. Likewise, when the editor's internal state changes + * it should update the value model by calling {@link android.util.ValueModel#set(T)} + * with the appropriate value. + * + * @param valueModel the new value model for this editor. + */ + public void setValueModel(ValueModel<T> valueModel); +} diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index 84699dc..b442ff5 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -24,38 +24,32 @@ import android.os.Message; public class HandlerCaller { public final Context mContext; - + final Looper mMainLooper; final Handler mH; final Callback mCallback; class MyHandler extends Handler { - MyHandler(Looper looper) { - super(looper); + MyHandler(Looper looper, boolean async) { + super(looper, null, async); } - + @Override public void handleMessage(Message msg) { mCallback.executeMessage(msg); } } - + public interface Callback { public void executeMessage(Message msg); } - - public HandlerCaller(Context context, Callback callback) { - mContext = context; - mMainLooper = context.getMainLooper(); - mH = new MyHandler(mMainLooper); - mCallback = callback; - } - public HandlerCaller(Context context, Looper looper, Callback callback) { + public HandlerCaller(Context context, Looper looper, Callback callback, + boolean asyncHandler) { mContext = context; - mMainLooper = looper; - mH = new MyHandler(mMainLooper); + mMainLooper = looper != null ? looper : context.getMainLooper(); + mH = new MyHandler(mMainLooper, asyncHandler); mCallback = callback; } |