Is there a way to intercept input in a NativeActivity before it gets dispatched to the AInputQueue in native code? The reason I need to intercept input in Java is to support gamepad/joystick events that I can't capture using any of the android/input.h functions, ie. MotionEvent.getAxisValue(MotionEvent.AXIS_RZ).
This following does not work (my manifest correctly points to my derived NativeActivity class):
public class CustomNativeActivity extends NativeActivity
{
private View.OnTouchListener touchListener = new View.OnTouchListener() {
public boolean onTouch (View v, MotionEvent event)
{
// This is never called!
System.out.println("onTouch");
return false;
}
};
public void setContentView(View view)
{
// This method is called, but registering a touch listener does nothing!
view.setOnTouchListener(touchListener);
super.setContentView(view);
}
public boolean dispatchTouchEvent(MotionEvent ev)
{
// This is never called either!
System.out.println("dispatchTouchEvent!");
return super.dispatchTouchEvent(ev);
}
}
Related
I am trying to make a 3D app for Android using OpenGL ES 3.0 but I have an issue when going from one state to another (in a state machine). When switching state, the OpenGL context seems to be lost in computer memory. This only happens when I push a button that will trigger state switching.
I think it is due to thread creation when switching state in Android (even though in a desktop java application, I don't recall seeing such an issue). Correct me if I'm wrong.
The exact message that appears (in red) in the debugger is :
E/libEGL: call to OpenGL ES API with no current context (logged once
per thread)
I made it so that if I don't press any key, the state will be switched after 20 sec. In that case the message does not appear (i.e. no red message if the user is not the one triggering the switch). But after the automatic switch, if I press a button I get a (normally colored) message in the debugger that states the following :
D/CompatibilityChangeReporter: Compat change id reported: 147798919;
UID 10060; state: ENABLED
Not sure if it's an issue. All I know is that if a use a workaround to track which keys are pressed (array of boolean describing each key state, with each index of the element of the table being used to represent the keycode), the keys being pressed after the switch of state get stuck on pressed (values in the table for the keys that are pressed are true).
Apologies in advance for the (probably) long code that I have to post. I tried my best to strip away and test so that the issue remains as much as I could. That's also why I wrote the whole explaination in the beginning
code-listing 1: The GLSurfaceView
public class OpenGLView extends GLSurfaceView
{
private OpenGLRenderer renderer;
public OpenGLView(Context context)
{
super(context);
init(context);
}
public OpenGLView(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
private void init(Context context)
{
setEGLContextClientVersion(3);
setPreserveEGLContextOnPause(true);
renderer = new OpenGLRenderer();
setRenderer(renderer);
Display.getInstance().setContext(context);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
renderer.onKeyDown(keyCode, event);
return super.onKeyDown(keyCode, event);
}
#Override
public boolean onKeyUp(int keyCode, KeyEvent event)
{
renderer.onKeyUp(keyCode, event);
return super.onKeyUp(keyCode, event);
}
}
code-listing 2: the GLSurfaceView.Renderer
public class OpenGLRenderer implements GLSurfaceView.Renderer
{
private final StateMachine gameStateMachine = StateMachine.getInstance();
public void onSurfaceCreated (GL10 glUnused, EGLConfig config ) {/* empty */}
public void onSurfaceChanged ( GL10 glUnused, int width, int height )
{
gameStateMachine.pushState(new HomeState(gameStateMachine));
}
public void onDrawFrame ( GL10 glUnused )
{
gameStateMachine.update();
gameStateMachine.render();
}
public void onKeyDown(int keyCode, KeyEvent event)
{
gameStateMachine.handleEvent(EventType.KEY_PRESSED, keyCode);
}
public void onKeyUp(int keyCode, KeyEvent event)
{
gameStateMachine.handleEvent(EventType.KEY_PRESSED, keyCode);
}
}
code-listing 3: HomeState.java (first state in the game state machine)
public class HomeState extends State {
public HomeState(StateMachine gsm) { super(gsm); }
#Override
public void onEnter() {/* empty */}
#Override
public void onExit() {/* empty */}
#Override
public void handleEvent(EventType type, int keyCode)
{
if(type == EventType.KEY_PRESSED)
{
switch (keyCode)
{
case KeyEvent.KEYCODE_DPAD_LEFT:
gameStateMachine.pushState(new InGameState(gameStateMachine));
}
}
}
long sometime = SystemClock.uptimeMillis();
#Override
public void update()
{
Log.d("DEBUG", ""+(SystemClock.uptimeMillis() - sometime));
if(SystemClock.uptimeMillis() - sometime > 20000)
{
gameStateMachine.pushState(new InGameState(gameStateMachine));
}
}
#Override
public void render() {/* empty */}
}
code-listing 4: inGameState.java (state one is switching to)
public class InGameState extends State {
public InGameState(StateMachine gsm) { super(gsm); }
#Override
public void onEnter()
{
glCreateShader(GL_VERTEX_SHADER);
}
#Override
public void onExit() {/* empty */}
#Override
public void handleEvent(EventType type, int keyCode)
{
if(type == EventType.KEY_PRESSED)
{
Toast.makeText(Display.getInstance().getContext(), "key pressed in InGameState!", Toast.LENGTH_SHORT).show();
}
}
#Override
public void update() {/* empty */}
#Override
public void render() {/* empty */}
}
Note that when there is no OpenGL call in the second game state, the red message doesn't appear. But as soon as I make any OpenGL call in the second game state, the red message appears when the user triggers the switch.
Also note that, like I mentioned previously, the red message only appears when I directly check the keyCode/KeyEvent given by Android-Java. If I do something like the following code, the red message will not appear but key pressed will be stuck (through software).
code-listing 5: workaround GLSurfaceView.Renderer
public class OpenGLRenderer implements GLSurfaceView.Renderer
{
private final StateMachine gameStateMachine = StateMachine.getInstance();
private final boolean[] keyStates = new boolean[1024];
public void onSurfaceCreated (GL10 glUnused, EGLConfig config )
{
for(boolean b : keyStates)
{
b = false;
}
}
/* see code-listing 2 */
public void onDrawFrame ( GL10 glUnused )
{
gameStateMachine.handleEvent(EventType.KEY_PRESSED, keyStates);
gameStateMachine.update();
gameStateMachine.render();
}
public void onKeyDown(int keyCode, KeyEvent event)
{
keyStates[keyCode] = true;
}
public void onKeyUp(int keyCode, KeyEvent event)
{
keyStates[keyCode] = false;
}
}
When you're using GLSurfaceView, all of your interactions with OpenGLES should be downstream from OpenGLRenderer's onDrawFrame, as that's called on the thread with the OpenGLES context bound.
onKeyDown is called from some other Android thread with no OpenGLES context. If I'm following your code correctly, then it eventually invokes InGameState's onEnter function which calls glCreateShader which fails because there's no OpenGLES context.
Your workaround seems like the right sort of idea. I would recommend that you have onKeyDown and onKeyUp append to a queue of input events, which can be processed in your onDrawFrame. I think that's a more robust approach than your array of boolean keyStates as you can process input in order, and easily extend the system to have touch events when you need to.
I have two views which are siblings of each other. They both cover the full screen. So one is behind the other. If the upper one gets touched (onTouch), I delegate the touch events to the one underneath it (with dispatchTouchEvent).
But sometimes I want to delay that delegation, till the next time onTouch gets called. But somehow that does not work.
An example to clarify:
To viewA - which is in front of viewB - I have applied the following (simplified) code:
viewA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
int touchType = event.getActionMasked();
if (touchType == MotionEvent.ACTION_DOWN) {
savedEvent = event;
return true; // I also tried returning false here
} else {
if (savedEvent != null) {
viewB.dispatchTouchEvent(savedEvent);
savedEvent = null;
}
viewB.dispatchTouchEvent(event);
return true; // I also tried returning false here
}
}
});
To test the dispatchTouchEvent call, I have the following code for viewB
viewB.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View arg0, MotionEvent event) {
Log.d("test", "test"); // this code gets logged, so it is being called, but the view seems to not execute any of the touch events
return true;
}
});
When I change to code for viewA to this:
viewA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
viewB.dispatchTouchEvent(event);
return true;
}
});
everything works just fine.
But the thing is that for my use case, I sometimes have to call the dispatchTouchEvent method with the event parameter outside its originating onTouch method, so to speak.
Is this even possible? And if yes, how?
So I found out what prevents it from working. If you manually call dispatchTouchEvent you should pass through an event that you created yourself with MotionEvent.obtain().
Working example:
viewA.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
int touchType = event.getActionMasked();
if (touchType == MotionEvent.ACTION_DOWN) {
// do not do this, somehow passing on a reference of the event directly does not work:
// savedEvent = event;
// this works though:
savedEvent = MotionEvent.obtain(event);
return true;
} else {
if (savedEvent != null) {
viewB.dispatchTouchEvent(savedEvent);
savedEvent = null;
}
viewB.dispatchTouchEvent(event);
return true;
}
}
});
Although I don't understand why that does work and just passing the event reference directly does not, I tested this and it does work perfectly.
I would like to implement changes in the functionality of GestureDetector.java. The easy way should be just to create a custom GestureDetector that extends from the original one, and to implement the changes:
public class CustomGestureDetector extends GestureDetector {
public CustomGestureDetector(OnGestureListener listener, Handler handler) {
super(listener, handler);
}
public CustomGestureDetector(OnGestureListener listener) {
super(listener);
}
public CustomGestureDetector(Context context, OnGestureListener listener) {
super(context, listener);
}
public CustomGestureDetector(Context context, OnGestureListener listener, Handler handler) {
super(context, listener, handler);
}
public CustomGestureDetector(Context context, OnGestureListener listener, Handler handler, boolean unused) {
super(context, listener, handler, unused);
}
#Override
public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {
super.setOnDoubleTapListener(onDoubleTapListener);
}
#Override
public void setContextClickListener(OnContextClickListener onContextClickListener) {
super.setContextClickListener(onContextClickListener);
}
#Override
public void setIsLongpressEnabled(boolean isLongpressEnabled) {
super.setIsLongpressEnabled(isLongpressEnabled);
}
#Override
public boolean isLongpressEnabled() {
return super.isLongpressEnabled();
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
return super.onTouchEvent(ev);
}
#Override
public boolean onGenericMotionEvent(MotionEvent ev) {
return super.onGenericMotionEvent(ev);
}
}
But I would like to override the function onTouchEvent and implement very specific changes so I cannot just implement them here and then call the super class, nor take the whole code for this function and copy it here because it relies on other variables.
In such case, should I create an alternative GestureDetector and then import the custom one in my project, instead of the regular one?
If it matters, the default behavior of the detector is to disable the scrolling function when long pressed it triggered:
case MotionEvent.ACTION_MOVE:
if (mInLongPress || mInContextClick) {
break;
}
But I would like it to still fire while the user is long pressing, and then scrolling.
Also, another problem is that I cannot just copy the code from GestureDetector.java to my own class in my project, since it actually contains many errors and then it won't compile anymore.
I'm a student of Yonsei graduate school, Korea.
I want to make a simple application that measures an time interval - between
touching the screen and display-updating.
I found that following method catch the touching event.
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if(event.getAction() == MotionEvent.ACTION_DOWN ){
...
Now I'm searching the Android APIs, but I coudn't find the method which catches
display-updating event. If you have any information about this problem,
please show mercy to me. Thank you.
Try adding a ViewTreeObserver.onDrawListener to a view in your Activity's content view. You can make a class that implements the that interface. When you get the touch event, call a method on your draw listener to record the time of the next draw event.
private MyDrawListener myDrawListener = new MyDrawListener();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(...);
findViewById(...).getViewTreeObserver().addOnDrawListener(myDrawListener);
}
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
myDrawListener.recordNextDrawTime();
}
}
public static class MyDrawListener implements ViewTreeObserver.OnDrawListener {
private boolean recordNextDrawTime;
public void recordNextDrawTime() {
recordNextDrawTime = true;
}
#Override
public void onDraw() {
if (recordNextDrawTime) {
Log.d("MyDrawListener", "Draw time = " + System.currentTimeMillis());
recordNextDrawTime = false;
}
}
}
I have an Osmdroid MapView. Even though I have set
mapView.setClickable(false);
mapView.setFocusable(false);
the map can still be moved around. Is there a simple way to disable all interactions with the map view?
A simple solution is to do like #Schrieveslaach but with the mapView:
mapView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return true;
}
});
My solution is similar to #schrieveslaach and #sagix, but I just extend base MapView class and add new functionality:
class DisabledMapView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MapView(context, attrs) {
private var isUserInteractionEnabled = true
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
if (isUserInteractionEnabled.not()) {
return false
}
return super.dispatchTouchEvent(event)
}
fun setUserInteractionEnabled(isUserInteractionEnabled: Boolean) {
this.isUserInteractionEnabled = isUserInteractionEnabled
}
}
I've found a solution. You need to handle the touch events directly by setting a OnTouchListener. For example,
public class MapViewLayout extends RelativeLayout {
private MapView mapView;
/**
* #see #setDetachedMode(boolean)
*/
private boolean detachedMode;
// implement initialization of your layout...
private void setUpMapView() {
mapView.setOnTouchListener(new OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (detachedMode) {
if (event.getAction() == MotionEvent.ACTION_UP) {
// if you want to fire another event
}
// Is detached mode is active all other touch handler
// should not be invoked, so just return true
return true;
}
return false;
}
});
}
/**
* Sets the detached mode. In detached mode no interactions will be passed to the map, the map
* will be static (no movement, no zooming, etc).
*
* #param detachedMode
*/
public void setDetachedMode(boolean detachedMode) {
this.detachedMode = detachedMode;
}
}
You could try:
mapView.setEnabled(false);
Which should disable all interactions with the map view