add local focus mode and input event injection API to Window

- This enables keyboard navigation for window without focus.
 - FLAG_LOCAL_FOCUS_MODE puts window into local focus mode.
 - Application needs to put window in local focus mode, control focus, and
   inject events to make dpad navigation work.
 - Window in local focus mode does not interact with window manager or ime
   regarding focus related events.
 - Also renamed ViewRootImpl.dispatchKey to dispatchInputEvent to allow both key and touch events injection.

Change-Id: I8e8561f29e0dade3797fb7ae3ee7690e6b7f8895
This commit is contained in:
keunyoung
2013-08-02 14:23:10 -07:00
parent 37ee68fc09
commit 30f420fd6a
7 changed files with 76 additions and 16 deletions

View File

@@ -27827,6 +27827,7 @@ package android.view {
method public final boolean hasChildren();
method public boolean hasFeature(int);
method protected final boolean hasSoftInputMode();
method public void injectInputEvent(android.view.InputEvent);
method public abstract void invalidatePanelMenu(int);
method public final boolean isActive();
method public abstract boolean isFloating();
@@ -27864,6 +27865,7 @@ package android.view {
method public void setGravity(int);
method public void setIcon(int);
method public void setLayout(int, int);
method public void setLocalFocus(boolean, boolean);
method public void setLogo(int);
method public void setSoftInputMode(int);
method public abstract void setTitle(java.lang.CharSequence);
@@ -28002,6 +28004,7 @@ package android.view {
field public static final int FLAG_LAYOUT_IN_OVERSCAN = 33554432; // 0x2000000
field public static final int FLAG_LAYOUT_IN_SCREEN = 256; // 0x100
field public static final int FLAG_LAYOUT_NO_LIMITS = 512; // 0x200
field public static final int FLAG_LOCAL_FOCUS_MODE = 268435456; // 0x10000000
field public static final int FLAG_NOT_FOCUSABLE = 8; // 0x8
field public static final int FLAG_NOT_TOUCHABLE = 16; // 0x10
field public static final int FLAG_NOT_TOUCH_MODAL = 32; // 0x20

View File

@@ -608,6 +608,11 @@ public final class ViewRootImpl implements ViewParent,
}
}
/** Whether the window is in local focus mode or not */
private boolean isInLocalFocusMode() {
return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
}
void destroyHardwareResources() {
if (mAttachInfo.mHardwareRenderer != null) {
if (mAttachInfo.mHardwareRenderer.isEnabled()) {
@@ -1818,7 +1823,7 @@ public final class ViewRootImpl implements ViewParent,
mNewSurfaceNeeded = false;
mViewVisibility = viewVisibility;
if (mAttachInfo.mHasWindowFocus) {
if (mAttachInfo.mHasWindowFocus && !isInLocalFocusMode()) {
final boolean imTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
if (imTarget != mLastWasImTarget) {
@@ -2906,7 +2911,7 @@ public final class ViewRootImpl implements ViewParent,
private final static int MSG_RESIZED = 4;
private final static int MSG_RESIZED_REPORT = 5;
private final static int MSG_WINDOW_FOCUS_CHANGED = 6;
private final static int MSG_DISPATCH_KEY = 7;
private final static int MSG_DISPATCH_INPUT_EVENT = 7;
private final static int MSG_DISPATCH_APP_VISIBILITY = 8;
private final static int MSG_DISPATCH_GET_NEW_SURFACE = 9;
private final static int MSG_DISPATCH_KEY_FROM_IME = 11;
@@ -2941,8 +2946,8 @@ public final class ViewRootImpl implements ViewParent,
return "MSG_RESIZED_REPORT";
case MSG_WINDOW_FOCUS_CHANGED:
return "MSG_WINDOW_FOCUS_CHANGED";
case MSG_DISPATCH_KEY:
return "MSG_DISPATCH_KEY";
case MSG_DISPATCH_INPUT_EVENT:
return "MSG_DISPATCH_INPUT_EVENT";
case MSG_DISPATCH_APP_VISIBILITY:
return "MSG_DISPATCH_APP_VISIBILITY";
case MSG_DISPATCH_GET_NEW_SURFACE:
@@ -3092,7 +3097,8 @@ public final class ViewRootImpl implements ViewParent,
InputMethodManager imm = InputMethodManager.peekInstance();
if (mView != null) {
if (hasWindowFocus && imm != null && mLastWasImTarget) {
if (hasWindowFocus && imm != null && mLastWasImTarget &&
!isInLocalFocusMode()) {
imm.startGettingWindowFocus(mView);
}
mAttachInfo.mKeyDispatchState.reset();
@@ -3103,7 +3109,7 @@ public final class ViewRootImpl implements ViewParent,
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
if (hasWindowFocus) {
if (imm != null && mLastWasImTarget) {
if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
imm.onWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
@@ -3131,8 +3137,8 @@ public final class ViewRootImpl implements ViewParent,
case MSG_DIE:
doDie();
break;
case MSG_DISPATCH_KEY: {
KeyEvent event = (KeyEvent)msg.obj;
case MSG_DISPATCH_INPUT_EVENT: {
InputEvent event = (InputEvent)msg.obj;
enqueueInputEvent(event, null, 0, true);
} break;
case MSG_DISPATCH_KEY_FROM_IME: {
@@ -3222,7 +3228,9 @@ public final class ViewRootImpl implements ViewParent,
// tell the window manager
try {
mWindowSession.setInTouchMode(inTouchMode);
if (!isInLocalFocusMode()) {
mWindowSession.setInTouchMode(inTouchMode);
}
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -3624,7 +3632,7 @@ public final class ViewRootImpl implements ViewParent,
@Override
protected int onProcess(QueuedInputEvent q) {
if (mLastWasImTarget) {
if (mLastWasImTarget && !isInLocalFocusMode()) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
final InputEvent event = q.mEvent;
@@ -5666,8 +5674,8 @@ public final class ViewRootImpl implements ViewParent,
mInvalidateOnAnimationRunnable.removeView(view);
}
public void dispatchKey(KeyEvent event) {
Message msg = mHandler.obtainMessage(MSG_DISPATCH_KEY, event);
public void dispatchInputEvent(InputEvent event) {
Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, event);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
}
@@ -5697,7 +5705,7 @@ public final class ViewRootImpl implements ViewParent,
flags, event.getSource(), null);
fallbackAction.recycle();
dispatchKey(fallbackEvent);
dispatchInputEvent(fallbackEvent);
}
}
}

View File

@@ -1290,4 +1290,18 @@ public abstract class Window {
* @hide
*/
public void setDefaultLogo(int resId) { }
/**
* Set focus locally. The window should have the
* {@link WindowManager.LayoutParams#FLAG_LOCAL_FOCUS_MODE} flag set already.
* @param hasFocus Whether this window has focus or not.
* @param inTouchMode Whether this window is in touch mode or not.
*/
public void setLocalFocus(boolean hasFocus, boolean inTouchMode) { }
/**
* Inject an event to window locally.
* @param event A key or touch event to inject to this window.
*/
public void injectInputEvent(InputEvent event) { }
}

View File

@@ -866,6 +866,15 @@ public interface WindowManager extends ViewManager {
*/
public static final int FLAG_NEEDS_MENU_KEY = 0x08000000;
/**
* Flag for a window in local focus mode.
* Window in local focus mode can control focus independent of window manager using
* {@link Window#setLocalFocus(boolean, boolean)}.
* Usually window in this mode will not get touch/key events from window manager, but will
* get events only via local injection using {@link Window#injectInputEvent(InputEvent)}.
*/
public static final int FLAG_LOCAL_FOCUS_MODE = 0x10000000;
/** Window flag: special flag to limit the size of the window to be
* original size ([320x480] x density). Used to create window for applications
* running under compatibility mode.
@@ -905,6 +914,7 @@ public interface WindowManager extends ViewManager {
* @see #FLAG_DISMISS_KEYGUARD
* @see #FLAG_SPLIT_TOUCH
* @see #FLAG_HARDWARE_ACCELERATED
* @see #FLAG_LOCAL_FOCUS_MODE
*/
@ViewDebug.ExportedProperty(flagMapping = {
@ViewDebug.FlagToString(mask = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, equals = FLAG_ALLOW_LOCK_WHILE_SCREEN_ON,
@@ -956,7 +966,9 @@ public interface WindowManager extends ViewManager {
@ViewDebug.FlagToString(mask = FLAG_SPLIT_TOUCH, equals = FLAG_SPLIT_TOUCH,
name = "FLAG_SPLIT_TOUCH"),
@ViewDebug.FlagToString(mask = FLAG_HARDWARE_ACCELERATED, equals = FLAG_HARDWARE_ACCELERATED,
name = "FLAG_HARDWARE_ACCELERATED")
name = "FLAG_HARDWARE_ACCELERATED"),
@ViewDebug.FlagToString(mask = FLAG_LOCAL_FOCUS_MODE, equals = FLAG_LOCAL_FOCUS_MODE,
name = "FLAG_LOCAL_FOCUS_MODE")
})
public int flags;

View File

@@ -503,7 +503,7 @@ public class ZoomButtonsController implements View.OnTouchListener {
ViewRootImpl viewRoot = mOwnerView.getViewRootImpl();
if (viewRoot != null) {
viewRoot.dispatchKey(event);
viewRoot.dispatchInputEvent(event);
}
// We gave the key to the owner, don't let the container handle this key

View File

@@ -184,7 +184,7 @@ public class PasswordEntryKeyboardHelper implements OnKeyboardActionListener {
KeyEvent event = events[i];
event = KeyEvent.changeFlags(event, event.getFlags()
| KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE);
viewRootImpl.dispatchKey(event);
viewRootImpl.dispatchInputEvent(event);
}
}
}

View File

@@ -67,6 +67,7 @@ import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.IRotationWatcher;
import android.view.IWindowManager;
import android.view.InputEvent;
import android.view.InputQueue;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -79,6 +80,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewManager;
import android.view.ViewParent;
import android.view.ViewRootImpl;
import android.view.ViewStub;
import android.view.Window;
import android.view.WindowManager;
@@ -1459,6 +1461,27 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
}
@Override
public void setLocalFocus(boolean hasFocus, boolean inTouchMode) {
getViewRootImpl().windowFocusChanged(hasFocus, inTouchMode);
}
@Override
public void injectInputEvent(InputEvent event) {
getViewRootImpl().dispatchInputEvent(event);
}
private ViewRootImpl getViewRootImpl() {
if (mDecor != null) {
ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
if (viewRootImpl != null) {
return viewRootImpl;
}
}
throw new IllegalStateException("view not added");
}
/**
* Request that key events come to this activity. Use this if your activity
* has no views with focus, but the activity still wants a chance to process