Merge change 24850 into eclair

* changes:
  Last big work on #1991910: Make swipes work with capacitive keys
This commit is contained in:
Android (Google) Code Review
2009-09-14 20:56:46 -04:00
14 changed files with 872 additions and 96 deletions

View File

@@ -16089,6 +16089,17 @@
visibility="public" visibility="public"
> >
</method> </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" <method name="onChildTitleChanged"
return="void" return="void"
abstract="false" abstract="false"
@@ -16318,6 +16329,21 @@
<parameter name="event" type="android.view.KeyEvent"> <parameter name="event" type="android.view.KeyEvent">
</parameter> </parameter>
</method> </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" <method name="onKeyMultiple"
return="boolean" return="boolean"
abstract="false" abstract="false"
@@ -20085,6 +20111,17 @@
visibility="public" visibility="public"
> >
</method> </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" <method name="onContentChanged"
return="void" return="void"
abstract="false" abstract="false"
@@ -20219,6 +20256,21 @@
<parameter name="event" type="android.view.KeyEvent"> <parameter name="event" type="android.view.KeyEvent">
</parameter> </parameter>
</method> </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" <method name="onKeyMultiple"
return="boolean" return="boolean"
abstract="false" abstract="false"
@@ -69570,6 +69622,17 @@
visibility="public" visibility="public"
> >
</constructor> </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" <method name="onBind"
return="android.os.IBinder" return="android.os.IBinder"
abstract="false" abstract="false"
@@ -70315,6 +70378,21 @@
<parameter name="event" type="android.view.KeyEvent"> <parameter name="event" type="android.view.KeyEvent">
</parameter> </parameter>
</method> </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" <method name="onKeyMultiple"
return="boolean" return="boolean"
abstract="false" abstract="false"
@@ -144823,7 +144901,7 @@
type="android.view.KeyEvent" type="android.view.KeyEvent"
static="false" static="false"
final="false" final="false"
deprecated="not deprecated" deprecated="deprecated"
visibility="public" visibility="public"
> >
<parameter name="origEvent" type="android.view.KeyEvent"> <parameter name="origEvent" type="android.view.KeyEvent">
@@ -144880,6 +144958,25 @@
<parameter name="newRepeat" type="int"> <parameter name="newRepeat" type="int">
</parameter> </parameter>
</method> </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" <method name="describeContents"
return="int" return="int"
abstract="false" abstract="false"
@@ -144891,6 +144988,19 @@
visibility="public" visibility="public"
> >
</method> </method>
<method name="dispatch"
return="boolean"
abstract="false"
native="false"
synchronized="false"
static="false"
final="true"
deprecated="deprecated"
visibility="public"
>
<parameter name="receiver" type="android.view.KeyEvent.Callback">
</parameter>
</method>
<method name="dispatch" <method name="dispatch"
return="boolean" return="boolean"
abstract="false" abstract="false"
@@ -144903,6 +145013,10 @@
> >
<parameter name="receiver" type="android.view.KeyEvent.Callback"> <parameter name="receiver" type="android.view.KeyEvent.Callback">
</parameter> </parameter>
<parameter name="state" type="android.view.KeyEvent.DispatcherState">
</parameter>
<parameter name="target" type="java.lang.Object">
</parameter>
</method> </method>
<method name="getAction" <method name="getAction"
return="int" return="int"
@@ -145149,6 +145263,17 @@
visibility="public" visibility="public"
> >
</method> </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" <method name="isModifierKey"
return="boolean" return="boolean"
abstract="false" abstract="false"
@@ -145206,6 +145331,28 @@
visibility="public" visibility="public"
> >
</method> </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" <method name="writeToParcel"
return="void" return="void"
abstract="false" abstract="false"
@@ -145275,6 +145422,17 @@
visibility="public" visibility="public"
> >
</field> </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" <field name="FLAG_EDITOR_ACTION"
type="int" type="int"
transient="false" transient="false"
@@ -145308,6 +145466,17 @@
visibility="public" visibility="public"
> >
</field> </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" <field name="FLAG_SOFT_KEYBOARD"
type="int" type="int"
transient="false" transient="false"
@@ -145319,6 +145488,17 @@
visibility="public" visibility="public"
> >
</field> </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" <field name="FLAG_VIRTUAL_HARD_KEY"
type="int" type="int"
transient="false" transient="false"
@@ -146464,6 +146644,21 @@
<parameter name="event" type="android.view.KeyEvent"> <parameter name="event" type="android.view.KeyEvent">
</parameter> </parameter>
</method> </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" <method name="onKeyMultiple"
return="boolean" return="boolean"
abstract="true" abstract="true"
@@ -146497,6 +146692,101 @@
</parameter> </parameter>
</method> </method>
</interface> </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" <class name="LayoutInflater"
extends="java.lang.Object" extends="java.lang.Object"
abstract="true" abstract="true"
@@ -150871,6 +151161,17 @@
visibility="public" visibility="public"
> >
</method> </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" <method name="getLayoutParams"
return="android.view.ViewGroup.LayoutParams" return="android.view.ViewGroup.LayoutParams"
abstract="false" abstract="false"
@@ -151997,6 +152298,21 @@
<parameter name="event" type="android.view.KeyEvent"> <parameter name="event" type="android.view.KeyEvent">
</parameter> </parameter>
</method> </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" <method name="onKeyMultiple"
return="boolean" return="boolean"
abstract="false" abstract="false"

View File

@@ -1752,8 +1752,9 @@ public class Activity extends ContextThemeWrapper
* *
* <p>If the focused view didn't want this event, this method is called. * <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 * <p>The default implementation sets up state to call
* and go back, and other default key handling if configured with {@link #setDefaultKeyMode}. * {@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 * @return Return <code>true</code> to prevent this event from being propagated
* further, or <code>false</code> to indicate that you have not handled * further, or <code>false</code> to indicate that you have not handled
@@ -1762,16 +1763,19 @@ public class Activity extends ContextThemeWrapper
* @see android.view.KeyEvent * @see android.view.KeyEvent
*/ */
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { if (keyCode == KeyEvent.KEYCODE_BACK) {
finish(); event.startTracking();
return true; return true;
} }
if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) { if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
return false; return false;
} else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) { } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
return getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,
keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE); keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
return true;
}
return false;
} else { } else {
// Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_* // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
boolean clearSpannable = false; boolean clearSpannable = false;
@@ -1780,8 +1784,8 @@ public class Activity extends ContextThemeWrapper
clearSpannable = true; clearSpannable = true;
handled = false; handled = false;
} else { } else {
handled = TextKeyListener.getInstance().onKeyDown(null, mDefaultKeySsb, handled = TextKeyListener.getInstance().onKeyDown(
keyCode, event); null, mDefaultKeySsb, keyCode, event);
if (handled && mDefaultKeySsb.length() > 0) { if (handled && mDefaultKeySsb.length() > 0) {
// something useable has been typed - dispatch it now. // something useable has been typed - dispatch it now.
@@ -1812,12 +1816,24 @@ public class Activity extends ContextThemeWrapper
} }
} }
/**
* 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 * 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 * 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 * is inside a TextView will not trigger the event (unless it is a navigation
* to another object) because TextView handles its own key presses. * 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 * @return Return <code>true</code> to prevent this event from being propagated
* further, or <code>false</code> to indicate that you have not handled * further, or <code>false</code> to indicate that you have not handled
* this event and it should continue to be propagated. * this event and it should continue to be propagated.
@@ -1825,6 +1841,11 @@ public class Activity extends ContextThemeWrapper
* @see KeyEvent * @see KeyEvent
*/ */
public boolean onKeyUp(int keyCode, KeyEvent event) { public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
}
return false; return false;
} }
@@ -1837,6 +1858,15 @@ public class Activity extends ContextThemeWrapper
return false; return false;
} }
/**
* 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 * 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 * under it. This is most useful to process touch events that happen
@@ -1909,9 +1939,10 @@ public class Activity extends ContextThemeWrapper
/** /**
* Called when the current {@link Window} of the activity gains or loses * Called when the current {@link Window} of the activity gains or loses
* focus. This is the best indicator of whether this activity is visible * 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 * is managed independently of activity lifecycles. As such, while focus
* changes will generally have some relation to lifecycle changes (an * changes will generally have some relation to lifecycle changes (an
* activity that is stopped will not generally get window focus), you * activity that is stopped will not generally get window focus), you
@@ -1988,7 +2019,8 @@ public class Activity extends ContextThemeWrapper
if (getWindow().superDispatchKeyEvent(event)) { if (getWindow().superDispatchKeyEvent(event)) {
return true; return true;
} }
return event.dispatch(this); return event.dispatch(this, mDecor != null
? mDecor.getKeyDispatcherState() : null, this);
} }
/** /**

View File

@@ -481,30 +481,45 @@ public class Dialog implements DialogInterface, Window.Callback,
* *
* <p>If the focused view didn't want this event, this method is called. * <p>If the focused view didn't want this event, this method is called.
* *
* <p>The default implementation handles KEYCODE_BACK to close the * <p>The default implementation consumed the KEYCODE_BACK to later
* dialog. * handle it in {@link #onKeyUp}.
* *
* @see #onKeyUp * @see #onKeyUp
* @see android.view.KeyEvent * @see android.view.KeyEvent
*/ */
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mCancelable) { event.startTracking();
cancel();
}
return true; return true;
} }
return false; return false;
} }
/**
* 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. * A key was released.
* *
* <p>The default implementation handles KEYCODE_BACK to close the
* dialog.
*
* @see #onKeyDown * @see #onKeyDown
* @see KeyEvent * @see KeyEvent
*/ */
public boolean onKeyUp(int keyCode, KeyEvent event) { public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
}
return false; return false;
} }
@@ -517,6 +532,17 @@ public class Dialog implements DialogInterface, Window.Callback,
return false; return false;
} }
/**
* 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 * 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 * under it. This is most useful to process touch events that happen outside
@@ -599,7 +625,8 @@ public class Dialog implements DialogInterface, Window.Callback,
if (mWindow.superDispatchKeyEvent(event)) { if (mWindow.superDispatchKeyEvent(event)) {
return true; return true;
} }
return event.dispatch(this); return event.dispatch(this, mDecor != null
? mDecor.getKeyDispatcherState() : null, this);
} }
/** /**

View File

@@ -745,11 +745,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return true; return true;
} }
if (keyCode == KeyEvent.KEYCODE_SEARCH && event.getRepeatCount() < 1) { if (keyCode == KeyEvent.KEYCODE_SEARCH) {
// If the search key is pressed, toggle between global and in-app search. If we are // Consume search key for later use.
// currently doing global search and there is no in-app search context to toggle to, return true;
// just don't do anything.
return toggleGlobalSearch();
} }
// if it's an action specified by the searchable activity, launch the // if it's an action specified by the searchable activity, launch the
@@ -763,6 +761,29 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return false; 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 * Callback to watch the textedit field for empty/non-empty
*/ */
@@ -1500,21 +1521,24 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
* *
* @return <code>true</code> if there was a previous component that we could go back to. * @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(); ComponentName previous = popPreviousComponent();
if (previous == null) { if (previous == null) {
return false; 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 if (doIt) {
// TODO: should this be the text as it was when the user left if (!show(previous, mAppSearchData, false)) {
// the source that we are now going back to? Log.w(LOG_TAG, "Failed to switch to source " + previous);
String query = mSearchAutoComplete.getText().toString(); return false;
setUserQuery(query); }
// 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; return true;
} }
@@ -1660,6 +1684,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
public static class SearchAutoComplete extends AutoCompleteTextView { public static class SearchAutoComplete extends AutoCompleteTextView {
private int mThreshold; private int mThreshold;
private int mLastKeyDown;
private SearchDialog mSearchDialog; private SearchDialog mSearchDialog;
public SearchAutoComplete(Context context) { public SearchAutoComplete(Context context) {
@@ -1740,27 +1765,44 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
*/ */
@Override @Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) { public boolean onKeyPreIme(int keyCode, KeyEvent event) {
mLastKeyDown = keyCode;
if (mSearchDialog.mSearchable == null) { if (mSearchDialog.mSearchable == null) {
return false; return false;
} }
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mSearchDialog.backToPreviousComponent()) { if (event.getAction() == KeyEvent.ACTION_DOWN
return true; && 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; return false;
} }
@@ -1772,11 +1814,18 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
} }
protected boolean handleBackKey(int keyCode, KeyEvent event) { protected boolean handleBackKey(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { if (keyCode == KeyEvent.KEYCODE_BACK) {
if (backToPreviousComponent()) { if (event.getAction() == KeyEvent.ACTION_DOWN) {
// Consume the event, to get an up at which point we execute.
return true; return true;
} }
cancel(); if (event.getAction() == KeyEvent.ACTION_UP && event.isTracking()
&& !event.isCanceled()) {
if (backToPreviousComponent(true)) {
return true;
}
cancel();
}
return true; return true;
} }
return false; return false;

View File

@@ -45,6 +45,9 @@ public abstract class AbstractInputMethodService extends Service
implements KeyEvent.Callback { implements KeyEvent.Callback {
private InputMethod mInputMethod; private InputMethod mInputMethod;
final KeyEvent.DispatcherState mDispatcherState
= new KeyEvent.DispatcherState();
/** /**
* Base class for derived classes to implement their {@link InputMethod} * Base class for derived classes to implement their {@link InputMethod}
* interface. This takes care of basic maintenance of the input method, * interface. This takes care of basic maintenance of the input method,
@@ -129,7 +132,8 @@ public abstract class AbstractInputMethodService extends Service
* callbacks on the service, and tell the client when this is done. * callbacks on the service, and tell the client when this is done.
*/ */
public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback) { 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) { if (callback != null) {
callback.finishedEvent(seq, handled); callback.finishedEvent(seq, handled);
} }
@@ -147,6 +151,16 @@ public abstract class AbstractInputMethodService extends Service
} }
} }
/**
* 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 * Called by the framework during initialization, when the InputMethod
* interface for this service needs to be created. * interface for this service needs to be created.

View File

@@ -554,7 +554,7 @@ public class InputMethodService extends AbstractInputMethodService {
mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
mInflater = (LayoutInflater)getSystemService( mInflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE); Context.LAYOUT_INFLATER_SERVICE);
mWindow = new SoftInputWindow(this, mTheme); mWindow = new SoftInputWindow(this, mTheme, mDispatcherState);
initViews(); initViews();
mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT); mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT);
} }
@@ -1557,6 +1557,28 @@ public class InputMethodService extends AbstractInputMethodService {
mImm.showSoftInputFromInputMethod(mToken, flags); 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 * Override this to intercept key down events before they are processed by the
* application. If you return true, the application will not itself * application. If you return true, the application will not itself
@@ -1564,37 +1586,32 @@ public class InputMethodService extends AbstractInputMethodService {
* will occur as if the IME had not seen the event at all. * will occur as if the IME had not seen the event at all.
* *
* <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
* KeyEvent.KEYCODE_BACK} to hide the current IME UI if it is shown. In * KeyEvent.KEYCODE_BACK} if the IME is currently shown, to
* additional, in fullscreen mode only, it will consume DPAD movement * 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 * events to move the cursor in the extracted text view, not allowing
* them to perform navigation in the underlying application. * them to perform navigation in the underlying application.
*/ */
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
&& event.getRepeatCount() == 0) { if (handleBack(false)) {
if (mShowInputRequested) { event.startTracking();
// If the soft input area is shown, back closes it and we
// consume the back key.
requestHideSelf(0);
return true; 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); 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 * Override this to intercept special key multiple events before they are
* processed by the * processed by the
@@ -1617,12 +1634,18 @@ public class InputMethodService extends AbstractInputMethodService {
* process the event. If you return true, the normal application processing * process the event. If you return true, the normal application processing
* will occur as if the IME had not seen the event at all. * will occur as if the IME had not seen the event at all.
* *
* <p>The default implementation always returns false, except when * <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
* in fullscreen mode, where it will consume DPAD movement * 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 * events to move the cursor in the extracted text view, not allowing
* them to perform navigation in the underlying application. * them to perform navigation in the underlying application.
*/ */
public boolean onKeyUp(int keyCode, KeyEvent event) { 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); return doMovementKey(keyCode, event, MOVEMENT_UP);
} }

View File

@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.os.IBinder; import android.os.IBinder;
import android.view.Gravity; import android.view.Gravity;
import android.view.KeyEvent;
import android.view.WindowManager; import android.view.WindowManager;
/** /**
@@ -30,7 +31,8 @@ import android.view.WindowManager;
* always visible. * always visible.
*/ */
class SoftInputWindow extends Dialog { class SoftInputWindow extends Dialog {
final KeyEvent.DispatcherState mDispatcherState;
public void setToken(IBinder token) { public void setToken(IBinder token) {
WindowManager.LayoutParams lp = getWindow().getAttributes(); WindowManager.LayoutParams lp = getWindow().getAttributes();
lp.token = token; lp.token = token;
@@ -49,11 +51,19 @@ class SoftInputWindow extends Dialog {
* using styles. This theme is applied on top of the current theme in * 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. * <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); super(context, theme);
mDispatcherState = dispatcherState;
initDockWindow(); initDockWindow();
} }
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
mDispatcherState.reset();
}
/** /**
* Get the size of the DockWindow. * Get the size of the DockWindow.
* *

View File

@@ -18,6 +18,7 @@ package android.view;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.SparseIntArray;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
import android.view.KeyCharacterMap.KeyData; import android.view.KeyCharacterMap.KeyData;
@@ -276,6 +277,32 @@ public class KeyEvent implements Parcelable {
*/ */
public static final int FLAG_VIRTUAL_HARD_KEY = 0x40; 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. * Returns the maximum keycode.
*/ */
@@ -305,7 +332,11 @@ public class KeyEvent implements Parcelable {
public interface Callback { 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 keyCode The value in event.getKeyCode().
* @param event Description of the key event. * @param event Description of the key event.
@@ -315,6 +346,22 @@ public class KeyEvent implements Parcelable {
*/ */
boolean onKeyDown(int keyCode, KeyEvent event); 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. * Called when a key up event has occurred.
* *
@@ -500,11 +547,15 @@ public class KeyEvent implements Parcelable {
/** /**
* Copy an existing key event, modifying its time and repeat count. * 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 origEvent The existing event to be copied.
* @param eventTime The new event time * @param eventTime The new event time
* (in {@link android.os.SystemClock#uptimeMillis}) of the event. * (in {@link android.os.SystemClock#uptimeMillis}) of the event.
* @param newRepeat The new repeat count of the event. * @param newRepeat The new repeat count of the event.
*/ */
@Deprecated
public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) { public KeyEvent(KeyEvent origEvent, long eventTime, int newRepeat) {
mDownTime = origEvent.mDownTime; mDownTime = origEvent.mDownTime;
mEventTime = eventTime; mEventTime = eventTime;
@@ -532,6 +583,26 @@ public class KeyEvent implements Parcelable {
return new KeyEvent(event, eventTime, newRepeat); return new KeyEvent(event, eventTime, newRepeat);
} }
/**
* 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. * Copy an existing key event, modifying its action.
* *
@@ -720,6 +791,34 @@ public class KeyEvent implements Parcelable {
return (mFlags&FLAG_CANCELED) != 0; return (mFlags&FLAG_CANCELED) != 0;
} }
/**
* 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 * Retrieve the key code of the key event. This is the physical key that
* was pressed, <em>not</em> the Unicode character. * was pressed, <em>not</em> the Unicode character.
@@ -905,20 +1004,50 @@ public class KeyEvent implements Parcelable {
return KeyCharacterMap.load(mDeviceId).isPrintingKey(mKeyCode); return KeyCharacterMap.load(mDeviceId).isPrintingKey(mKeyCode);
} }
/**
* @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 * 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 * an ACTION_MULTIPLE event and it is not handled, then an attempt will
* be made to deliver a single normal event. * be made to deliver a single normal event.
* *
* @param receiver The Callback that will be given the 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. * @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) { switch (mAction) {
case ACTION_DOWN: case ACTION_DOWN: {
return receiver.onKeyDown(mKeyCode, this); 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: case ACTION_UP:
if (state != null) {
state.handleUpEvent(this);
}
return receiver.onKeyUp(mKeyCode, this); return receiver.onKeyUp(mKeyCode, this);
case ACTION_MULTIPLE: case ACTION_MULTIPLE:
final int count = mRepeatCount; final int count = mRepeatCount;
@@ -938,10 +1067,97 @@ public class KeyEvent implements Parcelable {
mRepeatCount = count; mRepeatCount = count;
return handled; return handled;
} }
return false;
} }
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() { public String toString() {
return "KeyEvent{action=" + mAction + " code=" + mKeyCode return "KeyEvent{action=" + mAction + " code=" + mKeyCode
+ " repeat=" + mRepeatCount + " repeat=" + mRepeatCount

View File

@@ -2600,6 +2600,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
if (mOnFocusChangeListener != null) { if (mOnFocusChangeListener != null) {
mOnFocusChangeListener.onFocusChange(this, gainFocus); mOnFocusChangeListener.onFocusChange(this, gainFocus);
} }
if (mAttachInfo != null) {
mAttachInfo.mKeyDispatchState.reset(this);
}
} }
/** /**
@@ -3608,6 +3612,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
ViewDebug.dumpCapturedView(subTag, v); ViewDebug.dumpCapturedView(subTag, v);
} }
/**
* 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 * Dispatch a key event before it is processed by any input method
* associated with the view hierarchy. This can be used to intercept * associated with the view hierarchy. This can be used to intercept
@@ -3645,7 +3659,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
return true; return true;
} }
return event.dispatch(this); return event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this);
} }
/** /**
@@ -3909,6 +3924,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
return result; return result;
} }
/**
* 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) * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
* KeyEvent.Callback.onKeyMultiple()}: perform clicking of the view * KeyEvent.Callback.onKeyMultiple()}: perform clicking of the view
@@ -8572,6 +8596,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/ */
final ArrayList<View> mScrollContainers = new ArrayList<View>(); final ArrayList<View> mScrollContainers = new ArrayList<View>();
final KeyEvent.DispatcherState mKeyDispatchState
= new KeyEvent.DispatcherState();
/** /**
* Indicates whether the view's window currently has the focus. * Indicates whether the view's window currently has the focus.
*/ */

View File

@@ -1812,6 +1812,7 @@ public final class ViewRoot extends Handler implements ViewParent,
if (hasWindowFocus && imm != null && mLastWasImTarget) { if (hasWindowFocus && imm != null && mLastWasImTarget) {
imm.startGettingWindowFocus(mView); imm.startGettingWindowFocus(mView);
} }
mAttachInfo.mKeyDispatchState.reset();
mView.dispatchWindowFocusChanged(hasWindowFocus); mView.dispatchWindowFocusChanged(hasWindowFocus);
} }

View File

@@ -2945,10 +2945,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
okToSend = false; okToSend = false;
break; break;
case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_BACK:
if (mFiltered && mPopup != null && mPopup.isShowing() && if (mFiltered && mPopup != null && mPopup.isShowing()) {
event.getAction() == KeyEvent.ACTION_DOWN) { if (event.getAction() == KeyEvent.ACTION_DOWN) {
handled = true; handled = true;
mTextFilter.setText(""); } else if (event.getAction() == KeyEvent.ACTION_UP
&& event.isTracking() && !event.isCanceled()) {
handled = true;
mTextFilter.setText("");
}
} }
okToSend = false; okToSend = false;
break; break;

View File

@@ -132,6 +132,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
private AutoCompleteTextView.PassThroughClickListener mPassThroughClickListener; private AutoCompleteTextView.PassThroughClickListener mPassThroughClickListener;
private int mDownKeyCode;
public AutoCompleteTextView(Context context) { public AutoCompleteTextView(Context context) {
this(context, null); this(context, null);
} }
@@ -603,12 +605,19 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
@Override @Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) { public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0) {
mDownKeyCode = keyCode;
}
if (isPopupShowing()) { if (isPopupShowing()) {
// special case for the back key, we do not even try to send it // special case for the back key, we do not even try to send it
// to the drop down list but instead, consume it immediately // to the drop down list but instead, consume it immediately
if (keyCode == KeyEvent.KEYCODE_BACK && !mDropDownAlwaysVisible) { if (keyCode == KeyEvent.KEYCODE_BACK && !mDropDownAlwaysVisible) {
dismissDropDown(); if (event.getAction() == KeyEvent.ACTION_UP
return true; && mDownKeyCode == keyCode && !event.isCanceled()) {
dismissDropDown();
return true;
}
} }
} }
return super.onKeyPreIme(keyCode, event); return super.onKeyPreIme(keyCode, event);
@@ -1017,6 +1026,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
@Override @Override
public void onWindowFocusChanged(boolean hasWindowFocus) { public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus); super.onWindowFocusChanged(hasWindowFocus);
mDownKeyCode = 0;
performValidation(); performValidation();
if (!hasWindowFocus && !mDropDownAlwaysVisible) { if (!hasWindowFocus && !mDropDownAlwaysVisible) {
dismissDropDown(); dismissDropDown();
@@ -1026,6 +1036,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe
@Override @Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect); super.onFocusChanged(focused, direction, previouslyFocusedRect);
mDownKeyCode = 0;
performValidation(); performValidation();
if (!focused && !mDropDownAlwaysVisible) { if (!focused && !mDropDownAlwaysVisible) {
dismissDropDown(); dismissDropDown();

View File

@@ -981,6 +981,29 @@ public abstract class KeyInputQueue {
} }
} }
/**
* 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) { void recycleEvent(QueuedEvent ev) {
synchronized (mFirst) { synchronized (mFirst) {
//Log.i(TAG, "Recycle event: " + ev); //Log.i(TAG, "Recycle event: " + ev);

View File

@@ -99,6 +99,7 @@ import android.view.RawInputEvent;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceSession; import android.view.SurfaceSession;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.WindowManagerImpl; import android.view.WindowManagerImpl;
@@ -151,9 +152,6 @@ public class WindowManagerService extends IWindowManager.Stub
static final int LOG_WM_NO_SURFACE_MEMORY = 31000; 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 */ /** How long to wait for subsequent key repeats, in milliseconds */
static final int KEY_REPEAT_DELAY = 50; static final int KEY_REPEAT_DELAY = 50;
@@ -4900,6 +4898,16 @@ public class WindowManagerService extends IWindowManager.Stub
return INJECT_SUCCEEDED; 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; WindowState focus = (WindowState)focusObj;
if (DEBUG_INPUT) Log.v( if (DEBUG_INPUT) Log.v(
@@ -6018,6 +6026,7 @@ public class WindowManagerService extends IWindowManager.Stub
// Last keydown time for auto-repeating keys // Last keydown time for auto-repeating keys
long lastKeyTime = SystemClock.uptimeMillis(); long lastKeyTime = SystemClock.uptimeMillis();
long nextKeyTime = lastKeyTime+LONG_WAIT; long nextKeyTime = lastKeyTime+LONG_WAIT;
long downTime = 0;
// How many successive repeats we generated // How many successive repeats we generated
int keyRepeatCount = 0; int keyRepeatCount = 0;
@@ -6088,15 +6097,17 @@ public class WindowManagerService extends IWindowManager.Stub
KeyEvent ke = (KeyEvent)ev.event; KeyEvent ke = (KeyEvent)ev.event;
if (ke.isDown()) { if (ke.isDown()) {
lastKey = ke; lastKey = ke;
downTime = curTime;
keyRepeatCount = 0; keyRepeatCount = 0;
lastKeyTime = curTime; lastKeyTime = curTime;
nextKeyTime = lastKeyTime nextKeyTime = lastKeyTime
+ KEY_REPEAT_FIRST_DELAY; + ViewConfiguration.getLongPressTimeout();
if (DEBUG_INPUT) Log.v( if (DEBUG_INPUT) Log.v(
TAG, "Received key down: first repeat @ " TAG, "Received key down: first repeat @ "
+ nextKeyTime); + nextKeyTime);
} else { } else {
lastKey = null; lastKey = null;
downTime = 0;
// Arbitrary long timeout. // Arbitrary long timeout.
lastKeyTime = curTime; lastKeyTime = curTime;
nextKeyTime = curTime + LONG_WAIT; nextKeyTime = curTime + LONG_WAIT;
@@ -6144,7 +6155,19 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_INPUT) Log.v( if (DEBUG_INPUT) Log.v(
TAG, "Key repeat: count=" + keyRepeatCount TAG, "Key repeat: count=" + keyRepeatCount
+ ", next @ " + nextKeyTime); + ", 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 { } else {
curTime = SystemClock.uptimeMillis(); curTime = SystemClock.uptimeMillis();