diff options
author | Sven Gothel <[email protected]> | 2013-03-21 20:40:41 +0100 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2013-03-21 20:40:41 +0100 |
commit | 58ebd43a78491281e2c3b012666220ed47634c7e (patch) | |
tree | 8f092d3dbf158c8173d9d95024df63ca37a59d19 | |
parent | 98f6f99ddc6643cfa540d6c85c64c7f8510cc1ea (diff) |
NEWT/Android: Fix BACK button implementation, use different KeyCode mappings and allowing native action to be suppressed.
- Don't trust soft-kbd visibility state, but perform invisible action. If the latter
was successful - soft-kbd was visible before.
- Map BACK to VK_KEYBOARD_INVISIBLE and propagate it,
if soft-kbd was visible before.
No native default action is performed.
- Map BACK to VK_ESCAPE event and propagate it,
if soft-kbd was invisible _and_ an activity was registered via registerActivity(Activity),
i.e. by NewtBaseActivity.
Otherwise proceed w/ default action (-> activity.finish()).
- If the KeyListener consumed the [EVENT_KEY_RELEASED, VK_ESCAPE] event,
it will be suppressed and no default action performed.
This allows applications to have a custom 'ESCAPE' or 'BACK' handling.
Otherwise (not consumed) the default action is performed.
5 files changed, 146 insertions, 24 deletions
diff --git a/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java b/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java index 67bc73652..12a2e3dc2 100644 --- a/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java +++ b/src/newt/classes/com/jogamp/newt/event/NEWTEvent.java @@ -137,7 +137,7 @@ public class NEWTEvent extends java.util.EventObject { if(null == sb) { sb = new StringBuilder(); } - return sb.append("NEWTEvent[source:").append(getSource().getClass().getName()).append(", when:").append(getWhen()).append(" d ").append((System.currentTimeMillis()-getWhen())).append("ms]"); + return sb.append("NEWTEvent[source:").append(getSource().getClass().getName()).append("consumed ").append(isConsumed()).append(", when:").append(getWhen()).append(" d ").append((System.currentTimeMillis()-getWhen())).append("ms]"); } public static String toHexString(short hex) { diff --git a/src/newt/classes/jogamp/newt/WindowImpl.java b/src/newt/classes/jogamp/newt/WindowImpl.java index e8dc0d7d7..e1636ffcd 100644 --- a/src/newt/classes/jogamp/newt/WindowImpl.java +++ b/src/newt/classes/jogamp/newt/WindowImpl.java @@ -1625,6 +1625,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer windowHandle = handle; } + @Override public void runOnEDTIfAvail(boolean wait, final Runnable task) { if( windowLock.isOwner( Thread.currentThread() ) ) { task.run(); @@ -1650,14 +1651,17 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } }; + @Override public final boolean hasFocus() { return hasFocus; } + @Override public void requestFocus() { requestFocus(true); } + @Override public void requestFocus(boolean wait) { requestFocus(wait /* wait */, false /* skipFocusAction */, brokenFocusChange /* force */); } @@ -1680,6 +1684,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } } + @Override public void setFocusAction(FocusRunnable focusAction) { this.focusAction = focusAction; } @@ -1704,6 +1709,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer brokenFocusChange = v; } + @Override public void setKeyboardFocusHandler(KeyListener l) { keyboardFocusHandler = l; } @@ -1737,11 +1743,13 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } } + @Override public void setPosition(int x, int y) { autoPosition = false; runOnEDTIfAvail(true, new SetPositionAction(x, y)); } + @Override public void setTopLevelPosition(int x, int y) { setPosition(x + getInsets().getLeftWidth(), y + getInsets().getTopHeight()); } @@ -1848,6 +1856,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } private final FullScreenAction fullScreenAction = new FullScreenAction(); + @Override public boolean setFullscreen(boolean fullscreen) { synchronized(fullScreenAction) { if( fullScreenAction.init(fullscreen) ) { @@ -1921,12 +1930,14 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer // Child Window Management // + @Override public final boolean removeChild(NativeWindow win) { synchronized(childWindowsLock) { return childWindows.remove(win); } } + @Override public final boolean addChild(NativeWindow win) { if (win == null) { return false; @@ -1952,12 +1963,14 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } } + @Override public void enqueueEvent(boolean wait, com.jogamp.newt.event.NEWTEvent event) { if(isNativeValid()) { ((DisplayImpl)screen.getDisplay()).enqueueEvent(wait, event); } } + @Override public boolean consumeEvent(NEWTEvent e) { switch(e.getEventType()) { // special repaint treatment @@ -2009,18 +2022,22 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer // // SurfaceUpdatedListener Support // + @Override public void addSurfaceUpdatedListener(SurfaceUpdatedListener l) { surfaceUpdatedHelper.addSurfaceUpdatedListener(l); } + @Override public void addSurfaceUpdatedListener(int index, SurfaceUpdatedListener l) throws IndexOutOfBoundsException { surfaceUpdatedHelper.addSurfaceUpdatedListener(index, l); } + @Override public void removeSurfaceUpdatedListener(SurfaceUpdatedListener l) { surfaceUpdatedHelper.removeSurfaceUpdatedListener(l); } + @Override public void surfaceUpdated(Object updater, NativeSurface ns, long when) { surfaceUpdatedHelper.surfaceUpdated(updater, ns, when); } @@ -2149,11 +2166,12 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } } - + @Override public void addMouseListener(MouseListener l) { addMouseListener(-1, l); } + @Override public void addMouseListener(int index, MouseListener l) { if(l == null) { return; @@ -2167,6 +2185,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer mouseListeners = clonedListeners; } + @Override public void removeMouseListener(MouseListener l) { if (l == null) { return; @@ -2177,6 +2196,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer mouseListeners = clonedListeners; } + @Override public MouseListener getMouseListener(int index) { @SuppressWarnings("unchecked") ArrayList<MouseListener> clonedListeners = (ArrayList<MouseListener>) mouseListeners.clone(); @@ -2186,6 +2206,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer return clonedListeners.get(index); } + @Override public MouseListener[] getMouseListeners() { return mouseListeners.toArray(new MouseListener[mouseListeners.size()]); } @@ -2267,26 +2288,31 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer enqueueEvent(wait, new KeyEvent(eventType, this, System.currentTimeMillis(), modifiers | mouseButtonModMask, keyCode, keySym, keyChar) ); } - public void addKeyListener(KeyListener l) { - addKeyListener(-1, l); - } - + @Override public final void setKeyboardVisible(boolean visible) { if(isNativeValid()) { // We don't skip the impl. if it seems that there is no state change, // since we cannot assume the impl. reliably gives us it's current state. - final boolean n = setKeyboardVisibleImpl(visible); + final boolean ok = setKeyboardVisibleImpl(visible); if(DEBUG_IMPLEMENTATION || DEBUG_KEY_EVENT) { - System.err.println("setKeyboardVisible(native): visible "+keyboardVisible+" -> "+visible +" -> "+n); + System.err.println("setKeyboardVisible(native): visible "+keyboardVisible+" -- op[visible:"+visible +", ok "+ok+"] -> "+(visible && ok)); } - keyboardVisible = n; + keyboardVisibilityChanged( visible && ok ); } else { - keyboardVisible = visible; // earmark for creation + keyboardVisibilityChanged( visible ); // earmark for creation } } + @Override public final boolean isKeyboardVisible() { return keyboardVisible; - } + } + /** + * Returns <code>true</code> if operation was successful, otherwise <code>false</code>. + * <p> + * We assume that a failed invisible operation is due to an already invisible keyboard, + * hence even if an invisible operation failed, the keyboard is considered invisible! + * </p> + */ protected boolean setKeyboardVisibleImpl(boolean visible) { return false; // nop } @@ -2301,6 +2327,12 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer } protected boolean keyboardVisible = false; + @Override + public void addKeyListener(KeyListener l) { + addKeyListener(-1, l); + } + + @Override public void addKeyListener(int index, KeyListener l) { if(l == null) { return; @@ -2314,6 +2346,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer keyListeners = clonedListeners; } + @Override public void removeKeyListener(KeyListener l) { if (l == null) { return; @@ -2324,6 +2357,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer keyListeners = clonedListeners; } + @Override public KeyListener getKeyListener(int index) { @SuppressWarnings("unchecked") ArrayList<KeyListener> clonedListeners = (ArrayList<KeyListener>) keyListeners.clone(); @@ -2333,6 +2367,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer return clonedListeners.get(index); } + @Override public KeyListener[] getKeyListeners() { return keyListeners.toArray(new KeyListener[keyListeners.size()]); } @@ -2404,6 +2439,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer // // WindowListener/Event Support // + @Override public void sendWindowEvent(int eventType) { consumeWindowEvent( new WindowEvent((short)eventType, this, System.currentTimeMillis()) ); // FIXME } @@ -2412,10 +2448,12 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer enqueueEvent( wait, new WindowEvent((short)eventType, this, System.currentTimeMillis()) ); // FIXME } + @Override public void addWindowListener(WindowListener l) { addWindowListener(-1, l); } + @Override public void addWindowListener(int index, WindowListener l) throws IndexOutOfBoundsException { @@ -2431,6 +2469,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer windowListeners = clonedListeners; } + @Override public final void removeWindowListener(WindowListener l) { if (l == null) { return; @@ -2441,6 +2480,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer windowListeners = clonedListeners; } + @Override public WindowListener getWindowListener(int index) { @SuppressWarnings("unchecked") ArrayList<WindowListener> clonedListeners = (ArrayList<WindowListener>) windowListeners.clone(); @@ -2450,6 +2490,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer return clonedListeners.get(index); } + @Override public WindowListener[] getWindowListeners() { return windowListeners.toArray(new WindowListener[windowListeners.size()]); } @@ -2676,6 +2717,7 @@ public abstract class WindowImpl implements Window, NEWTEventConsumer return destroyed; } + @Override public void windowRepaint(int x, int y, int width, int height) { windowRepaint(false, x, y, width, height); } diff --git a/src/newt/classes/jogamp/newt/driver/android/NewtBaseActivity.java b/src/newt/classes/jogamp/newt/driver/android/NewtBaseActivity.java index 6bc81a555..726793768 100644 --- a/src/newt/classes/jogamp/newt/driver/android/NewtBaseActivity.java +++ b/src/newt/classes/jogamp/newt/driver/android/NewtBaseActivity.java @@ -126,6 +126,9 @@ public class NewtBaseActivity extends Activity { * @see #addContentView(android.view.Window, Window, android.view.ViewGroup.LayoutParams) */ public void registerNEWTWindow(Window newtWindow) { + newtWindow = newtWindow.getDelegatedWindow(); + WindowDriver newtAWindow = (WindowDriver)newtWindow; + newtAWindow.registerActivity(getActivity()); newtWindows.add(newtWindow); } diff --git a/src/newt/classes/jogamp/newt/driver/android/WindowDriver.java b/src/newt/classes/jogamp/newt/driver/android/WindowDriver.java index c5371ae9c..0a253ff9e 100644 --- a/src/newt/classes/jogamp/newt/driver/android/WindowDriver.java +++ b/src/newt/classes/jogamp/newt/driver/android/WindowDriver.java @@ -47,12 +47,15 @@ import com.jogamp.common.os.AndroidVersion; import com.jogamp.nativewindow.egl.EGLGraphicsDevice; import com.jogamp.newt.Screen; import com.jogamp.newt.ScreenMode; +import com.jogamp.newt.event.NEWTEvent; import jogamp.opengl.egl.EGL; import jogamp.opengl.egl.EGLGraphicsConfiguration; import jogamp.opengl.egl.EGLGraphicsConfigurationFactory; +import android.app.Activity; import android.content.Context; +import android.content.Intent; import android.graphics.PixelFormat; import android.os.Bundle; import android.os.IBinder; @@ -205,6 +208,11 @@ public class WindowDriver extends jogamp.newt.WindowImpl implements Callback2 { public WindowDriver() { reset(); } + + public void registerActivity(Activity activity) { + this.activity = activity; + } + protected Activity activity = null; private final void reset() { added2StaticViewGroup = false; @@ -499,14 +507,15 @@ public class WindowDriver extends jogamp.newt.WindowImpl implements Callback2 { if(null != androidView) { final InputMethodManager imm = (InputMethodManager) getAndroidView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE); final IBinder winid = getAndroidView().getWindowToken(); + final boolean result; if(visible) { // Show soft-keyboard: - imm.showSoftInput(androidView, 0, keyboardVisibleReceiver); + result = imm.showSoftInput(androidView, 0, keyboardVisibleReceiver); } else { // hide keyboard : - imm.hideSoftInputFromWindow(winid, 0, keyboardVisibleReceiver); + result = imm.hideSoftInputFromWindow(winid, 0, keyboardVisibleReceiver); } - return visible; + return result; } else { return false; // nop } @@ -588,25 +597,74 @@ public class WindowDriver extends jogamp.newt.WindowImpl implements Callback2 { protected boolean handleKeyCodeBack(KeyEvent.DispatcherState state, android.view.KeyEvent event) { if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + Log.d(MD.TAG, "handleKeyCodeBack.0 : "+event); state.startTracking(event, this); } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled() && state.isTracking(event)) { - if( isKeyboardVisible() ) { - keyboardVisibilityChanged(false); - enqueueAKey2NKeyUpDown(event); + // Since we cannot trust the visibility state 'completly', + // assume an already invisible state if the invisible operation fails. + final boolean wasVisible = setKeyboardVisibleImpl(false); + Log.d(MD.TAG, "handleKeyCodeBack.1 : wasVisible "+wasVisible+": "+event); + keyboardVisibilityChanged(false); + if( wasVisible ) { + // event processed, just send invisible event, no activity.finished() + enqueueAKey2NKeyUpDown(event, com.jogamp.newt.event.KeyEvent.VK_KEYBOARD_INVISIBLE); + return true; + } else if( null != activity ) { + // process event on our own, since we have an activity to call finish() + // and decide in overriden consumeKeyEvent(..) whether we suppress or proceed w/ activity.finish(). + enqueueAKey2NKeyUpDown(event, com.jogamp.newt.event.KeyEvent.VK_ESCAPE); + return true; } else { - Log.d(MD.TAG, "handleKeyCodeBack : "+event); + Log.d(MD.TAG, "handleKeyCodeBack.X1 : "+event); windowDestroyNotify(true); + // -> default BACK action, usually activity.finish() } } - return false; // cont. processing + return false; // continue w/ further processing + } + protected boolean handleKeyCodeHome(KeyEvent.DispatcherState state, android.view.KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { + Log.d(MD.TAG, "handleKeyCodeHome.0 : "+event); + state.startTracking(event, this); + } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled() && state.isTracking(event)) { + Log.d(MD.TAG, "handleKeyCodeHome.1 : "+event); + enqueueAKey2NKeyUpDown(event, com.jogamp.newt.event.KeyEvent.VK_HOME); + return true; // we handle further processing + } + return false; // continue w/ further processing } - private void enqueueAKey2NKeyUpDown(android.view.KeyEvent aEvent) { - final com.jogamp.newt.event.KeyEvent eDown = AndroidNewtEventFactory.createKeyEvent(aEvent, com.jogamp.newt.event.KeyEvent.EVENT_KEY_PRESSED, this, true); - final com.jogamp.newt.event.KeyEvent eUp = AndroidNewtEventFactory.createKeyEvent(aEvent, com.jogamp.newt.event.KeyEvent.EVENT_KEY_RELEASED, this, true); + private void enqueueAKey2NKeyUpDown(android.view.KeyEvent aEvent, short newtKeyCode) { + final com.jogamp.newt.event.KeyEvent eDown = AndroidNewtEventFactory.createKeyEvent(aEvent, newtKeyCode, com.jogamp.newt.event.KeyEvent.EVENT_KEY_PRESSED, this); + final com.jogamp.newt.event.KeyEvent eUp = AndroidNewtEventFactory.createKeyEvent(aEvent, newtKeyCode, com.jogamp.newt.event.KeyEvent.EVENT_KEY_RELEASED, this); enqueueEvent(false, eDown); enqueueEvent(false, eUp); } + // private GLEventListenerState glels = null; + + @Override + protected void consumeKeyEvent(com.jogamp.newt.event.KeyEvent e) { + super.consumeKeyEvent(e); // consume event, i.e. call all KeyListener + if( com.jogamp.newt.event.KeyEvent.EVENT_KEY_RELEASED == e.getEventType() && !e.isConsumed() ) { + if( com.jogamp.newt.event.KeyEvent.VK_ESCAPE == e.getKeyCode() ) { + Log.d(MD.TAG, "handleKeyCodeBack.X2 : "+e); + activity.finish(); + } else if( com.jogamp.newt.event.KeyEvent.VK_HOME == e.getKeyCode() ) { + Log.d(MD.TAG, "handleKeyCodeHome.X2 : "+e); + triggerHome(); + } + } + } + private void triggerHome() { + Context ctx = StaticContext.getContext(); + if(null == ctx) { + throw new NativeWindowException("No static [Application] Context has been set. Call StaticContext.setContext(Context) first."); + } + Intent showOptions = new Intent(Intent.ACTION_MAIN); + showOptions.addCategory(Intent.CATEGORY_HOME); + ctx.startActivity(showOptions); + } + private boolean added2StaticViewGroup; private MSurfaceView androidView; private int nativeFormat; // chosen current native PixelFormat (suitable for EGL) @@ -625,11 +683,17 @@ public class WindowDriver extends jogamp.newt.WindowImpl implements Callback2 { @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { + Log.d(MD.TAG, "onKeyPreIme : "+event); if ( event.getKeyCode() == KeyEvent.KEYCODE_BACK ) { final KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { return handleKeyCodeBack(state, event); } + } else if ( event.getKeyCode() == KeyEvent.KEYCODE_HOME ) { + final KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (state != null) { + return handleKeyCodeHome(state, event); + } } return false; // cont. processing } diff --git a/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventFactory.java b/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventFactory.java index 1f78bd578..5d2333c1f 100644 --- a/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventFactory.java +++ b/src/newt/classes/jogamp/newt/driver/android/event/AndroidNewtEventFactory.java @@ -110,7 +110,7 @@ public class AndroidNewtEventFactory { case android.view.KeyEvent.KEYCODE_TAB: return com.jogamp.newt.event.KeyEvent.VK_TAB; case android.view.KeyEvent.KEYCODE_SPACE: return com.jogamp.newt.event.KeyEvent.VK_SPACE; case android.view.KeyEvent.KEYCODE_ENTER: return com.jogamp.newt.event.KeyEvent.VK_ENTER; - case android.view.KeyEvent.KEYCODE_DEL: return com.jogamp.newt.event.KeyEvent.VK_DELETE; + case android.view.KeyEvent.KEYCODE_DEL: return com.jogamp.newt.event.KeyEvent.VK_BACK_SPACE; case android.view.KeyEvent.KEYCODE_MINUS: return com.jogamp.newt.event.KeyEvent.VK_MINUS; case android.view.KeyEvent.KEYCODE_EQUALS: return com.jogamp.newt.event.KeyEvent.VK_EQUALS; case android.view.KeyEvent.KEYCODE_LEFT_BRACKET: return com.jogamp.newt.event.KeyEvent.VK_LEFT_PARENTHESIS; @@ -128,11 +128,16 @@ public class AndroidNewtEventFactory { case android.view.KeyEvent.KEYCODE_CTRL_RIGHT: return com.jogamp.newt.event.KeyEvent.VK_CONTROL; // ?? case android.view.KeyEvent.KEYCODE_BACK: if( inclSysKeys ) { - return com.jogamp.newt.event.KeyEvent.VK_KEYBOARD_INVISIBLE; + // Note that manual mapping is performed, based on the keyboard state. + // I.e. we map to VK_KEYBOARD_INVISIBLE if keyboard was visible and now becomes invisible! + // Otherwise we map to VK_ESCAPE, and if not consumed by user, the application will be terminated. + return com.jogamp.newt.event.KeyEvent.VK_ESCAPE; } break; case android.view.KeyEvent.KEYCODE_HOME: if( inclSysKeys ) { + // If not consumed by user, the application will be 'paused', + // i.e. resources (GLEventListener) pulled before surface gets destroyed! return com.jogamp.newt.event.KeyEvent.VK_HOME; } break; @@ -184,6 +189,14 @@ public class AndroidNewtEventFactory { return res; } + public static com.jogamp.newt.event.KeyEvent createKeyEvent(android.view.KeyEvent aEvent, short newtKeyCode, short newtType, com.jogamp.newt.Window newtSource) { + final com.jogamp.newt.event.KeyEvent res = createKeyEventImpl(aEvent, newtType, newtKeyCode, newtSource); + if(Window.DEBUG_KEY_EVENT) { + System.err.println("createKeyEvent2: newtType "+NEWTEvent.toHexString(newtType)+", "+aEvent+" -> "+res); + } + return res; + } + private static com.jogamp.newt.event.KeyEvent createKeyEventImpl(android.view.KeyEvent aEvent, short newtType, short newtKeyCode, com.jogamp.newt.Window newtSource) { if( (short)0 != newtType && (short)0 != newtKeyCode ) { final Object src = null==newtSource ? null : newtSource; |