Last big work on #1991910: Make swipes work with capacitive keys

This takes care of allowing us to cancel the back button.  The
back button is a bear because it is strewn all over the place --
everywhere you can close something, there is some code looking
for the back button that now needs to deal with being canceled.

The main things changed are activity (of course), dialog,
input method, search dialog.  There are some other misc places
in the framework (and some I missed here that I will get in a
second pass).

To facility all of this, the key dispatching APIs now provide
a lot more support for dealing with looking for cancelled keys,
and incidentally also provide an actual API for catching long
key presses.  This also helped clean up the code in PhoneWindow
where it deals with all of the combinations of key pressed and
releases.  (And also allows people to override
Activity.onKeyLongPress() to provide a different long press
action for a standard key like search.)

And while I was doing this, I reworked how we detect long
presses by having this be part of the key event delivered by
the window manager.  This should greatly reduce (hopefully
outright eliminate) the problems with long presses being
mis-detected when an application is being slow.

Change-Id: Ia19066b8d588d573df3eee6d96e1c90fdc19f57d
diff --git a/api/current.xml b/api/current.xml
index ee82e00..38490f3 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -16089,6 +16089,17 @@
  visibility="public"
 >
 </method>
+<method name="onBackPressed"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="onChildTitleChanged"
  return="void"
  abstract="false"
@@ -16318,6 +16329,21 @@
 <parameter name="event" type="android.view.KeyEvent">
 </parameter>
 </method>
+<method name="onKeyLongPress"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
 <method name="onKeyMultiple"
  return="boolean"
  abstract="false"
@@ -20085,6 +20111,17 @@
  visibility="public"
 >
 </method>
+<method name="onBackPressed"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="onContentChanged"
  return="void"
  abstract="false"
@@ -20219,6 +20256,21 @@
 <parameter name="event" type="android.view.KeyEvent">
 </parameter>
 </method>
+<method name="onKeyLongPress"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
 <method name="onKeyMultiple"
  return="boolean"
  abstract="false"
@@ -69570,6 +69622,17 @@
  visibility="public"
 >
 </constructor>
+<method name="getKeyDispatcherState"
+ return="android.view.KeyEvent.DispatcherState"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="onBind"
  return="android.os.IBinder"
  abstract="false"
@@ -70315,6 +70378,21 @@
 <parameter name="event" type="android.view.KeyEvent">
 </parameter>
 </method>
+<method name="onKeyLongPress"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
 <method name="onKeyMultiple"
  return="boolean"
  abstract="false"
@@ -144823,7 +144901,7 @@
  type="android.view.KeyEvent"
  static="false"
  final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
  visibility="public"
 >
 <parameter name="origEvent" type="android.view.KeyEvent">
@@ -144880,6 +144958,25 @@
 <parameter name="newRepeat" type="int">
 </parameter>
 </method>
+<method name="changeTimeRepeat"
+ return="android.view.KeyEvent"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+<parameter name="eventTime" type="long">
+</parameter>
+<parameter name="newRepeat" type="int">
+</parameter>
+<parameter name="newFlags" type="int">
+</parameter>
+</method>
 <method name="describeContents"
  return="int"
  abstract="false"
@@ -144898,11 +144995,28 @@
  synchronized="false"
  static="false"
  final="true"
+ deprecated="deprecated"
+ visibility="public"
+>
+<parameter name="receiver" type="android.view.KeyEvent.Callback">
+</parameter>
+</method>
+<method name="dispatch"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
  deprecated="not deprecated"
  visibility="public"
 >
 <parameter name="receiver" type="android.view.KeyEvent.Callback">
 </parameter>
+<parameter name="state" type="android.view.KeyEvent.DispatcherState">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
 </method>
 <method name="getAction"
  return="int"
@@ -145149,6 +145263,17 @@
  visibility="public"
 >
 </method>
+<method name="isLongPress"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isModifierKey"
  return="boolean"
  abstract="false"
@@ -145206,6 +145331,28 @@
  visibility="public"
 >
 </method>
+<method name="isTracking"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="startTracking"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="writeToParcel"
  return="void"
  abstract="false"
@@ -145275,6 +145422,17 @@
  visibility="public"
 >
 </field>
+<field name="FLAG_CANCELED_LONG_PRESS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="256"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FLAG_EDITOR_ACTION"
  type="int"
  transient="false"
@@ -145308,6 +145466,17 @@
  visibility="public"
 >
 </field>
+<field name="FLAG_LONG_PRESS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="128"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FLAG_SOFT_KEYBOARD"
  type="int"
  transient="false"
@@ -145319,6 +145488,17 @@
  visibility="public"
 >
 </field>
+<field name="FLAG_TRACKING"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="512"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="FLAG_VIRTUAL_HARD_KEY"
  type="int"
  transient="false"
@@ -146464,6 +146644,21 @@
 <parameter name="event" type="android.view.KeyEvent">
 </parameter>
 </method>
+<method name="onKeyLongPress"
+ return="boolean"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
 <method name="onKeyMultiple"
  return="boolean"
  abstract="true"
@@ -146497,6 +146692,101 @@
 </parameter>
 </method>
 </interface>
+<class name="KeyEvent.DispatcherState"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="KeyEvent.DispatcherState"
+ type="android.view.KeyEvent.DispatcherState"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="handleUpEvent"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
+<method name="isTracking"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
+<method name="performedLongPress"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
+<method name="reset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="reset"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+</method>
+<method name="startTracking"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+<parameter name="target" type="java.lang.Object">
+</parameter>
+</method>
+</class>
 <class name="LayoutInflater"
  extends="java.lang.Object"
  abstract="true"
@@ -150871,6 +151161,17 @@
  visibility="public"
 >
 </method>
+<method name="getKeyDispatcherState"
+ return="android.view.KeyEvent.DispatcherState"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getLayoutParams"
  return="android.view.ViewGroup.LayoutParams"
  abstract="false"
@@ -151997,6 +152298,21 @@
 <parameter name="event" type="android.view.KeyEvent">
 </parameter>
 </method>
+<method name="onKeyLongPress"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="keyCode" type="int">
+</parameter>
+<parameter name="event" type="android.view.KeyEvent">
+</parameter>
+</method>
 <method name="onKeyMultiple"
  return="boolean"
  abstract="false"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 8c10091..be243a5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1752,8 +1752,9 @@
      * 
      * <p>If the focused view didn't want this event, this method is called.
      *
-     * <p>The default implementation handles KEYCODE_BACK to stop the activity
-     * and go back, and other default key handling if configured with {@link #setDefaultKeyMode}.
+     * <p>The default implementation sets up state to call
+     * {@link #onKeyLongPress}, and does other default key handling
+     * if configured with {@link #setDefaultKeyMode}.
      * 
      * @return Return <code>true</code> to prevent this event from being propagated
      * further, or <code>false</code> to indicate that you have not handled 
@@ -1762,16 +1763,19 @@
      * @see android.view.KeyEvent
      */
     public boolean onKeyDown(int keyCode, KeyEvent event)  {
-        if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
-            finish();
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            event.startTracking();
             return true;
         }
         
         if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
             return false;
         } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
-            return getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, 
-                                                    keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE);
+            if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, 
+                    keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
+                return true;
+            }
+            return false;
         } else {
             // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
             boolean clearSpannable = false;
@@ -1780,8 +1784,8 @@
                 clearSpannable = true;
                 handled = false;
             } else {
-                handled = TextKeyListener.getInstance().onKeyDown(null, mDefaultKeySsb, 
-                                                                  keyCode, event);
+                handled = TextKeyListener.getInstance().onKeyDown(
+                        null, mDefaultKeySsb, keyCode, event);
                 if (handled && mDefaultKeySsb.length() > 0) {
                     // something useable has been typed - dispatch it now.
 
@@ -1813,11 +1817,23 @@
     }
 
     /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+     * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
+     * the event).
+     */
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
      * Called when a key was released and not handled by any of the views
      * inside of the activity. So, for example, key presses while the cursor 
      * is inside a TextView will not trigger the event (unless it is a navigation
      * to another object) because TextView handles its own key presses.
      * 
+     * <p>The default implementation handles KEYCODE_BACK to stop the activity
+     * and go back.
+     * 
      * @return Return <code>true</code> to prevent this event from being propagated
      * further, or <code>false</code> to indicate that you have not handled 
      * this event and it should continue to be propagated. 
@@ -1825,6 +1841,11 @@
      * @see KeyEvent
      */
     public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+                && !event.isCanceled()) {
+            onBackPressed();
+            return true;
+        }
         return false;
     }
 
@@ -1838,6 +1859,15 @@
     }
     
     /**
+     * Called when the activity has detected the user's press of the back
+     * key.  The default implementation simply finishes the current activity,
+     * but you can override this to do whatever you want.
+     */
+    public void onBackPressed() {
+        finish();
+    }
+    
+    /**
      * Called when a touch screen event was not handled by any of the views
      * under it.  This is most useful to process touch events that happen
      * outside of your window bounds, where there is no view to receive it.
@@ -1909,9 +1939,10 @@
     /**
      * Called when the current {@link Window} of the activity gains or loses
      * focus.  This is the best indicator of whether this activity is visible
-     * to the user.
+     * to the user.  The default implementation clears the key tracking
+     * state, so should always be called.
      * 
-     * <p>Note that this provides information what global focus state, which
+     * <p>Note that this provides information about global focus state, which
      * is managed independently of activity lifecycles.  As such, while focus
      * changes will generally have some relation to lifecycle changes (an
      * activity that is stopped will not generally get window focus), you
@@ -1988,7 +2019,8 @@
         if (getWindow().superDispatchKeyEvent(event)) {
             return true;
         }
-        return event.dispatch(this);
+        return event.dispatch(this, mDecor != null
+                ? mDecor.getKeyDispatcherState() : null, this);
     }
 
     /**
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 1b96af9..58e8b32 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -481,17 +481,15 @@
      * 
      * <p>If the focused view didn't want this event, this method is called.
      *
-     * <p>The default implementation handles KEYCODE_BACK to close the
-     * dialog.
+     * <p>The default implementation consumed the KEYCODE_BACK to later
+     * handle it in {@link #onKeyUp}.
      *
      * @see #onKeyUp
      * @see android.view.KeyEvent
      */
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
-            if (mCancelable) {
-                cancel();
-            }
+            event.startTracking();
             return true;
         }
 
@@ -499,12 +497,29 @@
     }
 
     /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+     * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
+     * the event).
+     */
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
      * A key was released.
      * 
+     * <p>The default implementation handles KEYCODE_BACK to close the
+     * dialog.
+     *
      * @see #onKeyDown
      * @see KeyEvent
      */
     public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+                && !event.isCanceled()) {
+            onBackPressed();
+            return true;
+        }
         return false;
     }
 
@@ -518,6 +533,17 @@
     }
     
     /**
+     * Called when the dialog has detected the user's press of the back
+     * key.  The default implementation simply cancels the dialog (only if
+     * it is cancelable), but you can override this to do whatever you want.
+     */
+    public void onBackPressed() {
+        if (mCancelable) {
+            cancel();
+        }
+    }
+    
+    /**
      * Called when a touch screen event was not handled by any of the views
      * under it. This is most useful to process touch events that happen outside
      * of your window bounds, where there is no view to receive it.
@@ -599,7 +625,8 @@
         if (mWindow.superDispatchKeyEvent(event)) {
             return true;
         }
-        return event.dispatch(this);
+        return event.dispatch(this, mDecor != null
+                ? mDecor.getKeyDispatcherState() : null, this);
     }
 
     /**
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 75e4669..62dc9b2 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -745,11 +745,9 @@
             return true;
         }
         
-        if (keyCode == KeyEvent.KEYCODE_SEARCH && event.getRepeatCount() < 1) {
-            // If the search key is pressed, toggle between global and in-app search. If we are
-            // currently doing global search and there is no in-app search context to toggle to,
-            // just don't do anything.
-            return toggleGlobalSearch();
+        if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+            // Consume search key for later use.
+            return true;
         }
 
         // if it's an action specified by the searchable activity, launch the
@@ -763,6 +761,29 @@
         return false;
     }
     
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (DBG) Log.d(LOG_TAG, "onKeyUp(" + keyCode + "," + event + ")");
+        if (mSearchable == null) {
+            return false;
+        }
+
+        // handle back key to go back to previous searchable, etc.
+        if (handleBackKey(keyCode, event)) {
+            return true;
+        }
+        
+        if (keyCode == KeyEvent.KEYCODE_SEARCH && event.isTracking()
+                && !event.isCanceled()) {
+            // If the search key is pressed, toggle between global and in-app search. If we are
+            // currently doing global search and there is no in-app search context to toggle to,
+            // just don't do anything.
+            return toggleGlobalSearch();
+        }
+        
+        return false;
+    }
+    
     /**
      * Callback to watch the textedit field for empty/non-empty
      */
@@ -1500,21 +1521,24 @@
      * 
      * @return <code>true</code> if there was a previous component that we could go back to.
      */
-    private boolean backToPreviousComponent() {
+    private boolean backToPreviousComponent(boolean doIt) {
         ComponentName previous = popPreviousComponent();
         if (previous == null) {
             return false;
         }
-        if (!show(previous, mAppSearchData, false)) {
-            Log.w(LOG_TAG, "Failed to switch to source " + previous);
-            return false;
-        }
         
-        // must touch text to trigger suggestions
-        // TODO: should this be the text as it was when the user left
-        // the source that we are now going back to?
-        String query = mSearchAutoComplete.getText().toString();
-        setUserQuery(query);
+        if (doIt) {
+            if (!show(previous, mAppSearchData, false)) {
+                Log.w(LOG_TAG, "Failed to switch to source " + previous);
+                return false;
+            }
+            
+            // must touch text to trigger suggestions
+            // TODO: should this be the text as it was when the user left
+            // the source that we are now going back to?
+            String query = mSearchAutoComplete.getText().toString();
+            setUserQuery(query);
+        }
         
         return true;
     }
@@ -1660,6 +1684,7 @@
     public static class SearchAutoComplete extends AutoCompleteTextView {
 
         private int mThreshold;
+        private int mLastKeyDown;
         private SearchDialog mSearchDialog;
         
         public SearchAutoComplete(Context context) {
@@ -1740,27 +1765,44 @@
          */
         @Override
         public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+            mLastKeyDown = keyCode;
             if (mSearchDialog.mSearchable == null) {
                 return false;
             }
-            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
-                if (mSearchDialog.backToPreviousComponent()) {
-                    return true;
+            if (keyCode == KeyEvent.KEYCODE_BACK) {
+                if (event.getAction() == KeyEvent.ACTION_DOWN
+                        && event.getRepeatCount() == 0) {
+                    // We releae the back key, might we want to do
+                    // something before the IME?
+                    if (mSearchDialog.backToPreviousComponent(false)) {
+                        return true;
+                    }
+                    if (isInputMethodNotNeeded() ||
+                            (isEmpty() && getDropDownChildCount() >= getAdapterCount())) {
+                        return true;
+                    }
+                    mLastKeyDown = 0;
+                    return false; // will dismiss soft keyboard if necessary
+                } else if (event.getAction() == KeyEvent.ACTION_UP
+                        && mLastKeyDown == keyCode && !event.isCanceled()) {
+                    if (mSearchDialog.backToPreviousComponent(true)) {
+                        return true;
+                    }
+                    // If the drop-down obscures the keyboard, the user wouldn't see anything
+                    // happening when pressing back, so we dismiss the entire dialog instead.
+                    //
+                    // also: if there is no text entered, we also want to dismiss the whole dialog,
+                    // not just the soft keyboard.  the exception to this is if there are shortcuts
+                    // that aren't displayed (e.g are being obscured by the soft keyboard); in that
+                    // case we want to dismiss the soft keyboard so the user can see the rest of the
+                    // shortcuts.
+                    if (isInputMethodNotNeeded() ||
+                            (isEmpty() && getDropDownChildCount() >= getAdapterCount())) {
+                        mSearchDialog.cancel();
+                        return true;
+                    }
+                    return false; // will dismiss soft keyboard if necessary
                 }
-                // If the drop-down obscures the keyboard, the user wouldn't see anything
-                // happening when pressing back, so we dismiss the entire dialog instead.
-                //
-                // also: if there is no text entered, we also want to dismiss the whole dialog,
-                // not just the soft keyboard.  the exception to this is if there are shortcuts
-                // that aren't displayed (e.g are being obscured by the soft keyboard); in that
-                // case we want to dismiss the soft keyboard so the user can see the rest of the
-                // shortcuts.
-                if (isInputMethodNotNeeded() ||
-                        (isEmpty() && getDropDownChildCount() >= getAdapterCount())) {
-                    mSearchDialog.cancel();
-                    return true;
-                }
-                return false; // will dismiss soft keyboard if necessary
             }
             return false;
         }
@@ -1772,11 +1814,18 @@
     }
     
     protected boolean handleBackKey(int keyCode, KeyEvent event) {
-        if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
-            if (backToPreviousComponent()) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                // Consume the event, to get an up at which point we execute.
                 return true;
             }
-            cancel();
+            if (event.getAction() == KeyEvent.ACTION_UP && event.isTracking()
+                    && !event.isCanceled()) {
+                if (backToPreviousComponent(true)) {
+                    return true;
+                }
+                cancel();
+            }
             return true;
         }
         return false;
diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java
index eedcc35..3619653 100644
--- a/core/java/android/inputmethodservice/AbstractInputMethodService.java
+++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java
@@ -45,6 +45,9 @@
         implements KeyEvent.Callback {
     private InputMethod mInputMethod;
     
+    final KeyEvent.DispatcherState mDispatcherState
+            = new KeyEvent.DispatcherState();
+
     /**
      * Base class for derived classes to implement their {@link InputMethod}
      * interface.  This takes care of basic maintenance of the input method,
@@ -129,7 +132,8 @@
          * callbacks on the service, and tell the client when this is done.
          */
         public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback) {
-            boolean handled = event.dispatch(AbstractInputMethodService.this);
+            boolean handled = event.dispatch(AbstractInputMethodService.this,
+                    mDispatcherState, this);
             if (callback != null) {
                 callback.finishedEvent(seq, handled);
             }
@@ -148,6 +152,16 @@
     }
     
     /**
+     * Return the global {@link KeyEvent.DispatcherState KeyEvent.DispatcherState}
+     * for used for processing events from the target application.
+     * Normally you will not need to use this directly, but
+     * just use the standard high-level event callbacks like {@link #onKeyDown}.
+     */
+    public KeyEvent.DispatcherState getKeyDispatcherState() {
+        return mDispatcherState;
+    }
+    
+    /**
      * Called by the framework during initialization, when the InputMethod
      * interface for this service needs to be created.
      */
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 1f640ea..5499bba 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -554,7 +554,7 @@
         mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
         mInflater = (LayoutInflater)getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
-        mWindow = new SoftInputWindow(this, mTheme);
+        mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
         initViews();
         mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT);
     }
@@ -1557,6 +1557,28 @@
         mImm.showSoftInputFromInputMethod(mToken, flags);
     }
     
+    private boolean handleBack(boolean doIt) {
+        if (mShowInputRequested) {
+            // If the soft input area is shown, back closes it and we
+            // consume the back key.
+            if (doIt) requestHideSelf(0);
+            return true;
+        } else if (mWindowVisible) {
+            if (mCandidatesVisibility == View.VISIBLE) {
+                // If we are showing candidates even if no input area, then
+                // hide them.
+                if (doIt) setCandidatesViewShown(false);
+            } else {
+                // If we have the window visible for some other reason --
+                // most likely to show candidates -- then just get rid
+                // of it.  This really shouldn't happen, but just in case...
+                if (doIt) hideWindow();
+            }
+            return true;
+        }
+        return false;
+    }
+    
     /**
      * Override this to intercept key down events before they are processed by the
      * application.  If you return true, the application will not itself
@@ -1564,38 +1586,33 @@
      * will occur as if the IME had not seen the event at all.
      * 
      * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
-     * KeyEvent.KEYCODE_BACK} to hide the current IME UI if it is shown.  In
-     * additional, in fullscreen mode only, it will consume DPAD movement
+     * KeyEvent.KEYCODE_BACK} if the IME is currently shown, to
+     * possibly hide it when the key goes up (if not canceled or long pressed).  In
+     * addition, in fullscreen mode only, it will consume DPAD movement
      * events to move the cursor in the extracted text view, not allowing
      * them to perform navigation in the underlying application.
      */
     public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
-                && event.getRepeatCount() == 0) {
-            if (mShowInputRequested) {
-                // If the soft input area is shown, back closes it and we
-                // consume the back key.
-                requestHideSelf(0);
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+            if (handleBack(false)) {
+                event.startTracking();
                 return true;
-            } else if (mWindowVisible) {
-                if (mCandidatesVisibility == View.VISIBLE) {
-                    // If we are showing candidates even if no input area, then
-                    // hide them.
-                    setCandidatesViewShown(false);
-                    return true;
-                } else {
-                    // If we have the window visible for some other reason --
-                    // most likely to show candidates -- then just get rid
-                    // of it.  This really shouldn't happen, but just in case...
-                    hideWindow();
-                    return true;
-                }
             }
+            return false;
         }
         return doMovementKey(keyCode, event, MOVEMENT_DOWN);
     }
 
     /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+     * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
+     * the event).
+     */
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
      * Override this to intercept special key multiple events before they are
      * processed by the
      * application.  If you return true, the application will not itself
@@ -1617,12 +1634,18 @@
      * process the event.  If you return true, the normal application processing
      * will occur as if the IME had not seen the event at all.
      * 
-     * <p>The default implementation always returns false, except when
-     * in fullscreen mode, where it will consume DPAD movement
+     * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
+     * KeyEvent.KEYCODE_BACK} to hide the current IME UI if it is shown.  In
+     * addition, in fullscreen mode only, it will consume DPAD movement
      * events to move the cursor in the extracted text view, not allowing
      * them to perform navigation in the underlying application.
      */
     public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.isTracking()
+                && !event.isCanceled()) {
+            return handleBack(true);
+        }
+        
         return doMovementKey(keyCode, event, MOVEMENT_UP);
     }
 
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index d91ace6..6a54846 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -21,6 +21,7 @@
 import android.content.pm.ActivityInfo;
 import android.os.IBinder;
 import android.view.Gravity;
+import android.view.KeyEvent;
 import android.view.WindowManager;
 
 /**
@@ -30,7 +31,8 @@
  * always visible.
  */
 class SoftInputWindow extends Dialog {
-
+    final KeyEvent.DispatcherState mDispatcherState;
+    
     public void setToken(IBinder token) {
         WindowManager.LayoutParams lp = getWindow().getAttributes();
         lp.token = token;
@@ -49,11 +51,19 @@
      *        using styles. This theme is applied on top of the current theme in
      *        <var>context</var>. If 0, the default dialog theme will be used.
      */
-    public SoftInputWindow(Context context, int theme) {
+    public SoftInputWindow(Context context, int theme,
+            KeyEvent.DispatcherState dispatcherState) {
         super(context, theme);
+        mDispatcherState = dispatcherState;
         initDockWindow();
     }
 
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        mDispatcherState.reset();
+    }
+
     /**
      * Get the size of the DockWindow.
      * 
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index f9b16fc..daa4b29 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -18,6 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.SparseIntArray;
 import android.view.KeyCharacterMap;
 import android.view.KeyCharacterMap.KeyData;
 
@@ -277,6 +278,32 @@
     public static final int FLAG_VIRTUAL_HARD_KEY = 0x40;
     
     /**
+     * This flag is set for the first key repeat that occurs after the
+     * long press timeout.
+     */
+    public static final int FLAG_LONG_PRESS = 0x80;
+    
+    /**
+     * Set when a key event has {@link #FLAG_CANCELED} set because a long
+     * press action was executed while it was down. 
+     */
+    public static final int FLAG_CANCELED_LONG_PRESS = 0x100;
+    
+    /**
+     * Set for {@link #ACTION_UP} when this event's key code is still being
+     * tracked from its initial down.  That is, somebody requested that tracking
+     * started on the key down and a long press has not caused
+     * the tracking to be canceled.
+     */
+    public static final int FLAG_TRACKING = 0x200;
+    
+    /**
+     * Private control to determine when an app is tracking a key sequence.
+     * @hide
+     */
+    public static final int FLAG_START_TRACKING = 0x40000000;
+    
+    /**
      * Returns the maximum keycode.
      */
     public static int getMaxKeyCode() {
@@ -305,7 +332,11 @@
 
     public interface Callback {
         /**
-         * Called when a key down event has occurred.
+         * Called when a key down event has occurred.  If you return true,
+         * you can first call {@link KeyEvent#startTracking()
+         * KeyEvent.startTracking()} to have the framework track the event
+         * through its {@link #onKeyUp(int, KeyEvent)} and also call your
+         * {@link #onKeyLongPress(int, KeyEvent)} if it occurs.
          * 
          * @param keyCode The value in event.getKeyCode().
          * @param event Description of the key event.
@@ -316,6 +347,22 @@
         boolean onKeyDown(int keyCode, KeyEvent event);
 
         /**
+         * Called when a long press has occurred.  If you return true,
+         * the final key up will have {@link KeyEvent#FLAG_CANCELED} and
+         * {@link KeyEvent#FLAG_CANCELED_LONG_PRESS} set.  Note that in
+         * order to receive this callback, someone in the event change
+         * <em>must</em> return true from {@link #onKeyDown} <em>and</em>
+         * call {@link KeyEvent#startTracking()} on the event.
+         * 
+         * @param keyCode The value in event.getKeyCode().
+         * @param event Description of the key event.
+         * 
+         * @return If you handled the event, return true.  If you want to allow
+         *         the event to be handled by the next receiver, return false.
+         */
+        boolean onKeyLongPress(int keyCode, KeyEvent event);
+
+        /**
          * Called when a key up event has occurred.
          * 
          * @param keyCode The value in event.getKeyCode().
@@ -500,11 +547,15 @@
     /**
      * Copy an existing key event, modifying its time and repeat count.
      * 
+     * @deprecated Use {@link #changeTimeRepeat(KeyEvent, long, int)}
+     * instead.
+     * 
      * @param origEvent The existing event to be copied.
      * @param eventTime The new event time
      * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
      * @param newRepeat The new repeat count of the event.
      */
+    @Deprecated
     public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) {
         mDownTime = origEvent.mDownTime;
         mEventTime = eventTime;
@@ -533,6 +584,26 @@
     }
     
     /**
+     * Create a new key event that is the same as the given one, but whose
+     * event time and repeat count are replaced with the given value.
+     * 
+     * @param event The existing event to be copied.  This is not modified.
+     * @param eventTime The new event time
+     * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
+     * @param newRepeat The new repeat count of the event.
+     * @param newFlags New flags for the event, replacing the entire value
+     * in the original event.
+     */
+    public static KeyEvent changeTimeRepeat(KeyEvent event, long eventTime,
+            int newRepeat, int newFlags) {
+        KeyEvent ret = new KeyEvent(event);
+        ret.mEventTime = eventTime;
+        ret.mRepeatCount = newRepeat;
+        ret.mFlags = newFlags;
+        return ret;
+    }
+    
+    /**
      * Copy an existing key event, modifying its action.
      * 
      * @param origEvent The existing event to be copied.
@@ -721,6 +792,34 @@
     }
     
     /**
+     * Call this during {@link Callback#onKeyDown} to have the system track
+     * the key through its final up (possibly including a long press).  Note
+     * that only one key can be tracked at a time -- if another key down
+     * event is received while a previous one is being tracked, tracking is
+     * stopped on the previous event.
+     */
+    public final void startTracking() {
+        mFlags |= FLAG_START_TRACKING;
+    }
+    
+    /**
+     * For {@link #ACTION_UP} events, indicates that the event is still being
+     * tracked from its initial down event as per
+     * {@link #FLAG_TRACKING}.
+     */
+    public final boolean isTracking() {
+        return (mFlags&FLAG_TRACKING) != 0;
+    }
+    
+    /**
+     * For {@link #ACTION_DOWN} events, indicates that the event has been
+     * canceled as per {@link #FLAG_LONG_PRESS}.
+     */
+    public final boolean isLongPress() {
+        return (mFlags&FLAG_LONG_PRESS) != 0;
+    }
+    
+    /**
      * Retrieve the key code of the key event.  This is the physical key that
      * was pressed, <em>not</em> the Unicode character.
      * 
@@ -906,19 +1005,49 @@
     }
     
     /**
+     * @deprecated Use {@link #dispatch(Callback, DispatcherState, Object)} instead.
+     */
+    @Deprecated
+    public final boolean dispatch(Callback receiver) {
+        return dispatch(receiver, null, null);
+    }
+    
+    /**
      * Deliver this key event to a {@link Callback} interface.  If this is
      * an ACTION_MULTIPLE event and it is not handled, then an attempt will
      * be made to deliver a single normal event.
      * 
      * @param receiver The Callback that will be given the event.
+     * @param state State information retained across events.
+     * @param target The target of the dispatch, for use in tracking.
      * 
      * @return The return value from the Callback method that was called.
      */
-    public final boolean dispatch(Callback receiver) {
+    public final boolean dispatch(Callback receiver, DispatcherState state,
+            Object target) {
         switch (mAction) {
-            case ACTION_DOWN:
-                return receiver.onKeyDown(mKeyCode, this);
+            case ACTION_DOWN: {
+                mFlags &= ~FLAG_START_TRACKING;
+                boolean res = receiver.onKeyDown(mKeyCode, this);
+                if (state != null) {
+                    if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
+                        state.startTracking(this, target);
+                    } else if (isLongPress() && state.isTracking(this)) {
+                        try {
+                            if (receiver.onKeyLongPress(mKeyCode, this)) {
+                                state.performedLongPress(this);
+                                res = true;
+                            }
+                        } catch (AbstractMethodError e) {
+                        }
+                    }
+                }
+                return res;
+            }
             case ACTION_UP:
+                if (state != null) {
+                    state.handleUpEvent(this);
+                }
                 return receiver.onKeyUp(mKeyCode, this);
             case ACTION_MULTIPLE:
                 final int count = mRepeatCount;
@@ -938,10 +1067,97 @@
                     mRepeatCount = count;
                     return handled;
                 }
+                return false;
         }
         return false;
     }
 
+    /**
+     * Use with {@link KeyEvent#dispatch(Callback, DispatcherState, Object)}
+     * for more advanced key dispatching, such as long presses.
+     */
+    public static class DispatcherState {
+        int mDownKeyCode;
+        Object mDownTarget;
+        SparseIntArray mActiveLongPresses = new SparseIntArray();
+        
+        /**
+         * Reset back to initial state.
+         */
+        public void reset() {
+            mDownKeyCode = 0;
+            mDownTarget = null;
+            mActiveLongPresses.clear();
+        }
+        
+        /**
+         * Stop any tracking associated with this target.
+         */
+        public void reset(Object target) {
+            if (mDownTarget == target) {
+                mDownKeyCode = 0;
+                mDownTarget = null;
+            }
+        }
+        
+        /**
+         * Start tracking the key code associated with the given event.  This
+         * can only be called on a key down.  It will allow you to see any
+         * long press associated with the key, and will result in
+         * {@link KeyEvent#isTracking} return true on the long press and up
+         * events.
+         * 
+         * <p>This is only needed if you are directly dispatching events, rather
+         * than handling them in {@link Callback#onKeyDown}.
+         */
+        public void startTracking(KeyEvent event, Object target) {
+            if (event.getAction() != ACTION_DOWN) {
+                throw new IllegalArgumentException(
+                        "Can only start tracking on a down event");
+            }
+            mDownKeyCode = event.getKeyCode();
+            mDownTarget = target;
+        }
+        
+        /**
+         * Return true if the key event is for a key code that is currently
+         * being tracked by the dispatcher.
+         */
+        public boolean isTracking(KeyEvent event) {
+            return mDownKeyCode == event.getKeyCode();
+        }
+        
+        /**
+         * Keep track of the given event's key code as having performed an
+         * action with a long press, so no action should occur on the up.
+         * <p>This is only needed if you are directly dispatching events, rather
+         * than handling them in {@link Callback#onKeyLongPress}.
+         */
+        public void performedLongPress(KeyEvent event) {
+            mActiveLongPresses.put(event.getKeyCode(), 1);
+        }
+        
+        /**
+         * Handle key up event to stop tracking.  This resets the dispatcher state,
+         * and updates the key event state based on it.
+         * <p>This is only needed if you are directly dispatching events, rather
+         * than handling them in {@link Callback#onKeyUp}.
+         */
+        public void handleUpEvent(KeyEvent event) {
+            final int keyCode = event.getKeyCode();
+            int index = mActiveLongPresses.indexOfKey(keyCode);
+            if (index >= 0) {
+                event.mFlags |= FLAG_CANCELED | FLAG_CANCELED_LONG_PRESS;
+                mActiveLongPresses.removeAt(index);
+            }
+            if (mDownKeyCode == keyCode) {
+                event.mFlags |= FLAG_TRACKING;
+                mDownKeyCode = 0;
+                mDownTarget = null;
+            }
+        }
+    }
+    
     public String toString() {
         return "KeyEvent{action=" + mAction + " code=" + mKeyCode
             + " repeat=" + mRepeatCount
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 2cc243e..6ff0fc8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2600,6 +2600,10 @@
         if (mOnFocusChangeListener != null) {
             mOnFocusChangeListener.onFocusChange(this, gainFocus);
         }
+        
+        if (mAttachInfo != null) {
+            mAttachInfo.mKeyDispatchState.reset(this);
+        }
     }
 
     /**
@@ -3609,6 +3613,16 @@
     }
 
     /**
+     * Return the global {@link KeyEvent.DispatcherState KeyEvent.DispatcherState}
+     * for this view's window.  Returns null if the view is not currently attached
+     * to the window.  Normally you will not need to use this directly, but
+     * just use the standard high-level event callbacks like {@link #onKeyDown}.
+     */
+    public KeyEvent.DispatcherState getKeyDispatcherState() {
+        return mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null;
+    }
+    
+    /**
      * Dispatch a key event before it is processed by any input method
      * associated with the view hierarchy.  This can be used to intercept
      * key events in special situations before the IME consumes them; a
@@ -3645,7 +3659,8 @@
             return true;
         }
 
-        return event.dispatch(this);
+        return event.dispatch(this, mAttachInfo != null
+                ? mAttachInfo.mKeyDispatchState : null, this);
     }
 
     /**
@@ -3910,6 +3925,15 @@
     }
 
     /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+     * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
+     * the event).
+     */
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
      * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
      * KeyEvent.Callback.onKeyMultiple()}: perform clicking of the view
      * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or
@@ -8572,6 +8596,9 @@
          */
         final ArrayList<View> mScrollContainers = new ArrayList<View>();
 
+        final KeyEvent.DispatcherState mKeyDispatchState
+                = new KeyEvent.DispatcherState();
+
         /**
          * Indicates whether the view's window currently has the focus.
          */
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 6748ade..c6937a3 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -1812,6 +1812,7 @@
                     if (hasWindowFocus && imm != null && mLastWasImTarget) {
                         imm.startGettingWindowFocus(mView);
                     }
+                    mAttachInfo.mKeyDispatchState.reset();
                     mView.dispatchWindowFocusChanged(hasWindowFocus);
                 }
 
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 2f292d5..bed2a7a 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2945,10 +2945,14 @@
             okToSend = false;
             break;
         case KeyEvent.KEYCODE_BACK:
-            if (mFiltered && mPopup != null && mPopup.isShowing() &&
-                    event.getAction() == KeyEvent.ACTION_DOWN) {
-                handled = true;
-                mTextFilter.setText("");
+            if (mFiltered && mPopup != null && mPopup.isShowing()) {
+                if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                    handled = true;
+                } else if (event.getAction() == KeyEvent.ACTION_UP
+                        && event.isTracking() && !event.isCanceled()) {
+                    handled = true;
+                    mTextFilter.setText("");
+                }
             }
             okToSend = false;
             break;
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index d821a7d..7891d3c 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -132,6 +132,8 @@
 
     private AutoCompleteTextView.PassThroughClickListener mPassThroughClickListener;
 
+    private int mDownKeyCode;
+    
     public AutoCompleteTextView(Context context) {
         this(context, null);
     }
@@ -603,12 +605,19 @@
 
     @Override
     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_DOWN
+                && event.getRepeatCount() == 0) {
+            mDownKeyCode = keyCode;
+        }
         if (isPopupShowing()) {
             // special case for the back key, we do not even try to send it
             // to the drop down list but instead, consume it immediately
             if (keyCode == KeyEvent.KEYCODE_BACK && !mDropDownAlwaysVisible) {
-                dismissDropDown();
-                return true;
+                if (event.getAction() == KeyEvent.ACTION_UP
+                        && mDownKeyCode == keyCode && !event.isCanceled()) {
+                    dismissDropDown();
+                    return true;
+                }
             }
         }
         return super.onKeyPreIme(keyCode, event);
@@ -1017,6 +1026,7 @@
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         super.onWindowFocusChanged(hasWindowFocus);
+        mDownKeyCode = 0;
         performValidation();
         if (!hasWindowFocus && !mDropDownAlwaysVisible) {
             dismissDropDown();
@@ -1026,6 +1036,7 @@
     @Override
     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(focused, direction, previouslyFocusedRect);
+        mDownKeyCode = 0;
         performValidation();
         if (!focused && !mDropDownAlwaysVisible) {
             dismissDropDown();
diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java
index 244e136..d9f4c9c 100644
--- a/services/java/com/android/server/KeyInputQueue.java
+++ b/services/java/com/android/server/KeyInputQueue.java
@@ -981,6 +981,29 @@
         }
     }
 
+    /**
+     * Return true if the queue has an up event pending that corresponds
+     * to the same key as the given key event.
+     */
+    boolean hasKeyUpEvent(KeyEvent origEvent) {
+        synchronized (mFirst) {
+            final int keyCode = origEvent.getKeyCode();
+            QueuedEvent cur = mLast.prev;
+            while (cur.prev != null) {
+                if (cur.classType == RawInputEvent.CLASS_KEYBOARD) {
+                    KeyEvent ke = (KeyEvent)cur.event;
+                    if (ke.getAction() == KeyEvent.ACTION_UP
+                            && ke.getKeyCode() == keyCode) {
+                        return true;
+                    }
+                }
+                cur = cur.prev;
+            }
+        }
+        
+        return false;
+    }
+    
     void recycleEvent(QueuedEvent ev) {
         synchronized (mFirst) {
             //Log.i(TAG, "Recycle event: " + ev);
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index c844de2..8e85a6a 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -99,6 +99,7 @@
 import android.view.Surface;
 import android.view.SurfaceSession;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.WindowManagerImpl;
@@ -151,9 +152,6 @@
 
     static final int LOG_WM_NO_SURFACE_MEMORY = 31000;
 
-    /** How long to wait for first key repeat, in milliseconds */
-    static final int KEY_REPEAT_FIRST_DELAY = 750;
-
     /** How long to wait for subsequent key repeats, in milliseconds */
     static final int KEY_REPEAT_DELAY = 50;
 
@@ -4900,6 +4898,16 @@
             return INJECT_SUCCEEDED;
         }
 
+        // Okay we have finished waiting for the last event to be processed.
+        // First off, if this is a repeat event, check to see if there is
+        // a corresponding up event in the queue.  If there is, we will
+        // just drop the repeat, because it makes no sense to repeat after
+        // the user has released a key.  (This is especially important for
+        // long presses.)
+        if (event.getRepeatCount() > 0 && mQueue.hasKeyUpEvent(event)) {
+            return INJECT_SUCCEEDED;
+        }
+        
         WindowState focus = (WindowState)focusObj;
 
         if (DEBUG_INPUT) Log.v(
@@ -6018,6 +6026,7 @@
             // Last keydown time for auto-repeating keys
             long lastKeyTime = SystemClock.uptimeMillis();
             long nextKeyTime = lastKeyTime+LONG_WAIT;
+            long downTime = 0;
 
             // How many successive repeats we generated
             int keyRepeatCount = 0;
@@ -6088,15 +6097,17 @@
                                 KeyEvent ke = (KeyEvent)ev.event;
                                 if (ke.isDown()) {
                                     lastKey = ke;
+                                    downTime = curTime;
                                     keyRepeatCount = 0;
                                     lastKeyTime = curTime;
                                     nextKeyTime = lastKeyTime
-                                            + KEY_REPEAT_FIRST_DELAY;
+                                            + ViewConfiguration.getLongPressTimeout();
                                     if (DEBUG_INPUT) Log.v(
                                         TAG, "Received key down: first repeat @ "
                                         + nextKeyTime);
                                 } else {
                                     lastKey = null;
+                                    downTime = 0;
                                     // Arbitrary long timeout.
                                     lastKeyTime = curTime;
                                     nextKeyTime = curTime + LONG_WAIT;
@@ -6144,7 +6155,19 @@
                         if (DEBUG_INPUT) Log.v(
                             TAG, "Key repeat: count=" + keyRepeatCount
                             + ", next @ " + nextKeyTime);
-                        dispatchKey(KeyEvent.changeTimeRepeat(lastKey, curTime, keyRepeatCount), 0, 0);
+                        KeyEvent newEvent;
+                        if (downTime != 0 && (downTime
+                                + ViewConfiguration.getLongPressTimeout())
+                                <= curTime) {
+                            newEvent = KeyEvent.changeTimeRepeat(lastKey,
+                                    curTime, keyRepeatCount,
+                                    lastKey.getFlags() | KeyEvent.FLAG_LONG_PRESS);
+                            downTime = 0;
+                        } else {
+                            newEvent = KeyEvent.changeTimeRepeat(lastKey,
+                                    curTime, keyRepeatCount);
+                        }
+                        dispatchKey(newEvent, 0, 0);
 
                     } else {
                         curTime = SystemClock.uptimeMillis();