jogamp.newt.driver.android.WindowDriver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jogl-all Show documentation
Show all versions of jogl-all Show documentation
Java™ Binding for the OpenGL® API
/**
* Copyright 2011 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
* of conditions and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package jogamp.newt.driver.android;
import jogamp.common.os.android.StaticContext;
import jogamp.newt.WindowImpl;
import jogamp.newt.driver.android.event.AndroidNewtEventFactory;
import jogamp.newt.driver.android.event.AndroidNewtEventTranslator;
import javax.media.nativewindow.AbstractGraphicsScreen;
import javax.media.nativewindow.Capabilities;
import javax.media.nativewindow.CapabilitiesImmutable;
import javax.media.nativewindow.DefaultGraphicsScreen;
import javax.media.nativewindow.NativeWindowException;
import javax.media.nativewindow.VisualIDHolder;
import javax.media.nativewindow.util.Insets;
import javax.media.nativewindow.util.Point;
import javax.media.nativewindow.util.RectangleImmutable;
import javax.media.opengl.GLCapabilitiesChooser;
import javax.media.opengl.GLCapabilitiesImmutable;
import javax.media.opengl.GLException;
import com.jogamp.common.os.AndroidVersion;
import com.jogamp.nativewindow.egl.EGLGraphicsDevice;
import com.jogamp.newt.MonitorDevice;
import jogamp.opengl.egl.EGL;
import jogamp.opengl.egl.EGLDisplayUtil;
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;
import android.os.Looper;
import android.os.ResultReceiver;
import android.util.Log;
import android.view.Gravity;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback2;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.view.SurfaceView;
import android.view.KeyEvent;
public class WindowDriver extends jogamp.newt.WindowImpl implements Callback2 {
static {
DisplayDriver.initSingleton();
}
/**
* First stage of selecting an Android PixelFormat,
* at construction via {@link SurfaceHolder#setFormat(int)}
* before native realization!
*
* @param rCaps requested Capabilities
* @return An Android PixelFormat number suitable for {@link SurfaceHolder#setFormat(int)}.
*/
public static final int getSurfaceHolderFormat(CapabilitiesImmutable rCaps) {
int fmt = PixelFormat.UNKNOWN;
if( !rCaps.isBackgroundOpaque() ) {
fmt = PixelFormat.TRANSLUCENT;
} else if( rCaps.getRedBits()<=5 &&
rCaps.getGreenBits()<=6 &&
rCaps.getBlueBits()<=5 &&
rCaps.getAlphaBits()==0 ) {
fmt = PixelFormat.RGB_565;
} else if( rCaps.getAlphaBits()==0 ) {
fmt = PixelFormat.RGB_888;
} else {
fmt = PixelFormat.RGBA_8888;
}
Log.d(MD.TAG, "getSurfaceHolderFormat: requested: "+rCaps);
Log.d(MD.TAG, "getSurfaceHolderFormat: returned: "+fmt);
return fmt;
}
public static final int NATIVE_WINDOW_FORMAT_RGBA_8888 = 1;
public static final int NATIVE_WINDOW_FORMAT_RGBX_8888 = 2;
public static final int NATIVE_WINDOW_FORMAT_RGB_565 = 4;
/**
* Second stage of selecting an Android PixelFormat,
* at right after native (surface) realization at {@link Callback2#surfaceChanged(SurfaceHolder, int, int, int)}.
* Selection happens via {@link #setSurfaceVisualID0(long, int)} before native EGL creation.
*
* @param androidPixelFormat An Android PixelFormat delivered via {@link Callback2#surfaceChanged(SurfaceHolder, int, int, int)} params.
* @return A native Android PixelFormat number suitable for {@link #setSurfaceVisualID0(long, int)}.
*/
public static final int getANativeWindowFormat(int androidPixelFormat) {
final int nativePixelFormat;
switch(androidPixelFormat) {
case PixelFormat.RGBA_8888:
case PixelFormat.RGBA_5551:
case PixelFormat.RGBA_4444:
nativePixelFormat = NATIVE_WINDOW_FORMAT_RGBA_8888;
break;
case PixelFormat.RGBX_8888:
case PixelFormat.RGB_888:
nativePixelFormat = NATIVE_WINDOW_FORMAT_RGBX_8888;
break;
case PixelFormat.RGB_565:
case PixelFormat.RGB_332:
nativePixelFormat = NATIVE_WINDOW_FORMAT_RGB_565;
break;
default: nativePixelFormat = NATIVE_WINDOW_FORMAT_RGBA_8888;
}
Log.d(MD.TAG, "getANativeWindowFormat: android: "+androidPixelFormat+" -> native "+nativePixelFormat);
return nativePixelFormat;
}
/**
* Final stage of Android PixelFormat operation,
* match the requested Capabilities w/ Android PixelFormat number.
* This is done at native realization @ {@link Callback2#surfaceChanged(SurfaceHolder, int, int, int)}.
*
* @param matchFormatPrecise
* @param format
* @param rCaps requested Capabilities
* @return The fixed Capabilities
*/
public static final CapabilitiesImmutable fixCaps(boolean matchFormatPrecise, int format, CapabilitiesImmutable rCaps) {
PixelFormat pf = new PixelFormat();
PixelFormat.getPixelFormatInfo(format, pf);
final CapabilitiesImmutable res;
int r, g, b, a;
switch(format) {
case PixelFormat.RGBA_8888: r=8; g=8; b=8; a=8; break; // NATIVE_WINDOW_FORMAT_RGBA_8888
case PixelFormat.RGBX_8888: r=8; g=8; b=8; a=0; break; // NATIVE_WINDOW_FORMAT_RGBX_8888
case PixelFormat.RGB_888: r=8; g=8; b=8; a=0; break;
case PixelFormat.RGB_565: r=5; g=6; b=5; a=0; break; // NATIVE_WINDOW_FORMAT_RGB_565
case PixelFormat.RGBA_5551: r=5; g=5; b=5; a=1; break;
case PixelFormat.RGBA_4444: r=4; g=4; b=4; a=4; break;
case PixelFormat.RGB_332: r=3; g=3; b=2; a=0; break;
default: throw new InternalError("Unhandled pixelformat: "+format);
}
final boolean change = matchFormatPrecise ||
rCaps.getRedBits() > r &&
rCaps.getGreenBits() > g &&
rCaps.getBlueBits() > b &&
rCaps.getAlphaBits() > a ;
if(change) {
Capabilities nCaps = (Capabilities) rCaps.cloneMutable();
nCaps.setRedBits(r);
nCaps.setGreenBits(g);
nCaps.setBlueBits(b);
nCaps.setAlphaBits(a);
res = nCaps;
} else {
res = rCaps;
}
Log.d(MD.TAG, "fixCaps: format: "+format);
Log.d(MD.TAG, "fixCaps: requested: "+rCaps);
Log.d(MD.TAG, "fixCaps: chosen: "+res);
return res;
}
public static final boolean isAndroidFormatTransparent(int aFormat) {
switch (aFormat) {
case PixelFormat.TRANSLUCENT:
case PixelFormat.TRANSPARENT:
return true;
}
return false;
}
public static Class>[] getCustomConstructorArgumentTypes() {
return new Class>[] { Context.class } ;
}
public WindowDriver() {
reset();
}
public void registerActivity(Activity activity) {
this.activity = activity;
}
protected Activity activity = null;
private final void reset() {
added2StaticViewGroup = false;
androidView = null;
nativeFormat = VisualIDHolder.VID_UNDEFINED;
androidFormat = VisualIDHolder.VID_UNDEFINED;
capsByFormat = null;
surface = null;
surfaceHandle = 0;
eglSurface = 0;
definePosition(0, 0); // default to 0/0
defineSize(0, 0); // default size -> TBD !
setBrokenFocusChange(true);
}
private final void setupInputListener(final boolean enable) {
Log.d(MD.TAG, "setupInputListener(enable "+enable+") - "+Thread.currentThread().getName());
final AndroidNewtEventTranslator eventTranslator =
enable ? new AndroidNewtEventTranslator(this, androidView.getContext(), androidView.getHandler()) : null;
androidView.setOnTouchListener(eventTranslator);
androidView.setOnKeyListener(eventTranslator);
androidView.setOnFocusChangeListener(eventTranslator);
if(AndroidVersion.SDK_INT >= 12) { // API Level 12
Log.d(MD.TAG, "setupInputListener - enable GenericMotionListener - "+Thread.currentThread().getName());
androidView.setOnGenericMotionListener(eventTranslator);
}
if( enable ) {
androidView.post(new Runnable() {
public void run() {
androidView.setClickable(false);
androidView.setFocusable(true);
androidView.setFocusableInTouchMode(true);
} } );
}
}
private final void setupAndroidView(Context ctx) {
androidView = new MSurfaceView(ctx);
final SurfaceHolder sh = androidView.getHolder();
sh.addCallback(WindowDriver.this);
sh.setFormat(getSurfaceHolderFormat(getRequestedCapabilities()));
}
public final SurfaceView getAndroidView() { return androidView; }
@Override
protected final void instantiationFinished() {
Log.d(MD.TAG, "instantiationFinished() - "+Thread.currentThread().getName());
final Context ctx = StaticContext.getContext();
if(null == ctx) {
throw new NativeWindowException("No static [Application] Context has been set. Call StaticContext.setContext(Context) first.");
}
if( null != Looper.myLooper() ) {
setupAndroidView(ctx);
}
}
@Override
protected final boolean canCreateNativeImpl() {
Log.d(MD.TAG, "canCreateNativeImpl.0: surfaceHandle ready "+(0!=surfaceHandle)+" - on thread "+Thread.currentThread().getName());
if(WindowImpl.DEBUG_IMPLEMENTATION) {
Thread.dumpStack();
}
if( isFullscreen() ) {
final MonitorDevice mainMonitor = getMainMonitor();
final RectangleImmutable viewport = mainMonitor.getViewport();
definePosition(viewport.getX(), viewport.getY());
defineSize(viewport.getWidth(), viewport.getHeight());
}
final boolean b;
if( 0 == surfaceHandle ) {
// Static ViewGroup, i.e. self contained main code
final ViewGroup viewGroup = StaticContext.getContentViewGroup();
Log.d(MD.TAG, "canCreateNativeImpl: viewGroup "+viewGroup);
if( null != viewGroup && !added2StaticViewGroup ) {
added2StaticViewGroup = true;
viewGroup.post(new Runnable() {
public void run() {
if(null == androidView) {
setupAndroidView( StaticContext.getContext() );
}
viewGroup.addView(androidView, new android.widget.FrameLayout.LayoutParams(getWidth(), getHeight(), Gravity.BOTTOM|Gravity.RIGHT));
Log.d(MD.TAG, "canCreateNativeImpl: added to static ViewGroup - on thread "+Thread.currentThread().getName());
} });
for(long sleep = TIMEOUT_NATIVEWINDOW; 0
* Accessible protected method!
*
*
* {@inheritDoc}
*/
@Override
public final void focusChanged(boolean defer, boolean focusGained) {
super.focusChanged(defer, focusGained);
}
@Override
protected final void requestFocusImpl(boolean reparented) {
if(null != androidView) {
Log.d(MD.TAG, "requestFocusImpl: reparented "+reparented);
androidView.post(new Runnable() {
public void run() {
androidView.requestFocus();
androidView.bringToFront();
}
});
}
}
@Override
protected final boolean reconfigureWindowImpl(int x, int y, int width, int height, int flags) {
boolean res = true;
if( 0 != ( FLAG_CHANGE_FULLSCREEN & flags) ) {
Log.d(MD.TAG, "reconfigureWindowImpl.setFullscreen post creation (setContentView()) n/a");
return false;
}
if(getWidth() != width || getHeight() != height) {
if(0!=getWindowHandle()) {
Log.d(MD.TAG, "reconfigureWindowImpl.setSize n/a");
res = false;
} else {
defineSize(width, height);
}
}
if(getX() != x || getY() != y) {
if(0!=getWindowHandle()) {
Log.d(MD.TAG, "reconfigureWindowImpl.setPos n/a");
res = false;
} else {
definePosition(x, y);
}
}
if( 0 != ( FLAG_CHANGE_VISIBILITY & flags) ) {
visibleChanged(false, 0 != ( FLAG_IS_VISIBLE & flags));
}
return res;
}
@Override
protected final Point getLocationOnScreenImpl(int x, int y) {
return new Point(x,y);
}
@Override
protected final void updateInsetsImpl(Insets insets) {
// nop ..
}
//----------------------------------------------------------------------
// Virtual On-Screen Keyboard / SoftInput
//
private class KeyboardVisibleReceiver extends ResultReceiver {
public KeyboardVisibleReceiver() {
super(null);
}
@Override
public void onReceiveResult(int r, Bundle data) {
boolean v = false;
switch(r) {
case InputMethodManager.RESULT_UNCHANGED_SHOWN:
case InputMethodManager.RESULT_SHOWN:
v = true;
break;
case InputMethodManager.RESULT_HIDDEN:
case InputMethodManager.RESULT_UNCHANGED_HIDDEN:
v = false;
break;
}
Log.d(MD.TAG, "keyboardVisible: "+v);
keyboardVisibilityChanged(v);
}
}
private KeyboardVisibleReceiver keyboardVisibleReceiver = new KeyboardVisibleReceiver();
@Override
protected final boolean setKeyboardVisibleImpl(boolean visible) {
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:
result = imm.showSoftInput(androidView, 0, keyboardVisibleReceiver);
} else {
// hide keyboard :
result = imm.hideSoftInputFromWindow(winid, 0, keyboardVisibleReceiver);
}
return result;
} else {
return false; // nop
}
}
//----------------------------------------------------------------------
// Surface Callbacks
//
@Override
public final void surfaceCreated(SurfaceHolder holder) {
Log.d(MD.TAG, "surfaceCreated: "+getX()+"/"+getY()+" "+getWidth()+"x"+getHeight()+" - on thread "+Thread.currentThread().getName());
}
@Override
public final void surfaceChanged(SurfaceHolder aHolder, int aFormat, int aWidth, int aHeight) {
Log.d(MD.TAG, "surfaceChanged: f "+nativeFormat+" -> "+aFormat+", "+aWidth+"x"+aHeight+", current surfaceHandle: 0x"+Long.toHexString(surfaceHandle)+" - on thread "+Thread.currentThread().getName());
if(WindowImpl.DEBUG_IMPLEMENTATION) {
Thread.dumpStack();
}
if(0!=surfaceHandle && androidFormat != aFormat ) {
// re-create
Log.d(MD.TAG, "surfaceChanged (destroy old)");
if(!windowDestroyNotify(true)) {
destroy();
}
surfaceHandle = 0;
surface=null;
}
if(getScreen().isNativeValid()) {
// if MonitorMode changed .. trigger MonitorMode event
final MonitorDevice mainMonitor = getMainMonitor();
mainMonitor.queryCurrentMode();
}
if(0>getX() || 0>getY()) {
positionChanged(false, 0, 0);
}
if(0 == surfaceHandle) {
androidFormat = aFormat;
surface = aHolder.getSurface();
surfaceHandle = getSurfaceHandle0(surface);
acquire0(surfaceHandle);
final int aNativeWindowFormat = getANativeWindowFormat(androidFormat);
setSurfaceVisualID0(surfaceHandle, aNativeWindowFormat);
nativeFormat = getSurfaceVisualID0(surfaceHandle);
Log.d(MD.TAG, "surfaceChanged: androidFormat "+androidFormat+" -- (set-native "+aNativeWindowFormat+") --> nativeFormat "+nativeFormat);
final int nWidth = getWidth0(surfaceHandle);
final int nHeight = getHeight0(surfaceHandle);
capsByFormat = (GLCapabilitiesImmutable) fixCaps(true /* matchFormatPrecise */, nativeFormat, getRequestedCapabilities());
sizeChanged(false, nWidth, nHeight, false);
Log.d(MD.TAG, "surfaceRealized: isValid: "+surface.isValid()+
", new surfaceHandle 0x"+Long.toHexString(surfaceHandle)+
", format [a "+androidFormat+"/n "+nativeFormat+"], "+
getX()+"/"+getY()+" "+nWidth+"x"+nHeight+", visible: "+isVisible());
if(isVisible()) {
setVisible(false, true);
}
}
sizeChanged(false, aWidth, aHeight, false);
windowRepaint(0, 0, aWidth, aHeight);
Log.d(MD.TAG, "surfaceChanged: X");
}
@Override
public final void surfaceDestroyed(SurfaceHolder holder) {
Log.d(MD.TAG, "surfaceDestroyed - on thread "+Thread.currentThread().getName());
windowDestroyNotify(true); // actually too late .. however ..
Thread.dumpStack();
}
@Override
public final void surfaceRedrawNeeded(SurfaceHolder holder) {
Log.d(MD.TAG, "surfaceRedrawNeeded - on thread "+Thread.currentThread().getName());
windowRepaint(0, 0, getWidth(), getHeight());
}
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)) {
// 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.X1 : "+event);
windowDestroyNotify(true);
// -> default BACK action, usually activity.finish()
}
}
return false; // continue w/ further processing
}
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);
}
@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)
private int androidFormat; // chosen current android PixelFormat (-1, -2 ..)
private GLCapabilitiesImmutable capsByFormat; // fixed requestedCaps by PixelFormat
private Surface surface;
private volatile long surfaceHandle;
private long eglSurface;
class MSurfaceView extends SurfaceView {
public MSurfaceView (Context ctx) {
super(ctx);
setBackgroundDrawable(null);
// setBackgroundColor(Color.TRANSPARENT);
}
@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);
}
}
return false; // cont. processing
}
}
//----------------------------------------------------------------------
// Internals only
//
protected static native boolean initIDs0();
protected static native long getSurfaceHandle0(Surface surface);
/** Return the native window format via ANativeWindow_getFormat(..)
. */
protected static native int getSurfaceVisualID0(long surfaceHandle);
/** Set the native window format via ANativeWindow_setBuffersGeometry(..)
. */
protected static native void setSurfaceVisualID0(long surfaceHandle, int nativeVisualID);
protected static native int getWidth0(long surfaceHandle);
protected static native int getHeight0(long surfaceHandle);
protected static native void acquire0(long surfaceHandle);
protected static native void release0(long surfaceHandle);
}