Skip to content

Commit

Permalink
Add support for predictive back gestures (libgdx#7429)
Browse files Browse the repository at this point in the history
* Remove back/menu methods (deprecated 5 years ago)

* Add enableOnBackInvokedCallback to gdx-tests manifest

* Add back invoke support and test

* Update CHANGES

* Apply formatter

* Realised where I went wrong with inner class

* No-one else is using @nullable so I guess I shouldn't either

* Suppose it covers GWT as well

* Undo constructor nonsense

* Fix malformed Javadoc

* Apply formatter

* Continuation of e074acc reversion, nearly forgot

* Revisit BackTest

* Apply formatter

* Make test exclusive to Android

It doesn't make sense for different platforms. Input.Keys.BACK doesn't catch the browser back button on GWT.

* Less ambiguous class name

* Apply formatter

* Less dumb-dumb anchor

* final PredictiveBackHandler

* Apply formatter

* Mostly revert 5b2d072

* Remove unneeded `if` nesting

---------

Co-authored-by: GitHub Action <[email protected]>
  • Loading branch information
Frosty-J and actions-user authored Sep 7, 2024
1 parent c856c16 commit 892dee6
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 112 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
- Optimization of SpriteBatch in GL30 default modes, as indices do not need to be updated
- Desktop: Added support for 8/32/64-bit PCM and MP3 WAVs
- Desktop: Improved support for surround sound audio files (#6792)
- Android: Added support for predictive back gesture (requires android:enableOnBackInvokedCallback="true" in manifest)
- API Removal: Removed deprecated back and menu key methods. Use `setCatchKey` and `isCatchKey` instead.
- Cache packed color on sprite to improve performance
- Improve JsonReader, add JsonSkimmer, JsonString
- Add proper glTexImage2D support on GWT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.badlogic.gdx.backends.android;

import android.animation.Animator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
Expand All @@ -27,6 +28,7 @@
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.os.Handler;
import android.text.*;
import android.text.InputFilter.LengthFilter;
Expand All @@ -41,10 +43,14 @@
import android.view.inputmethod.InputMethodManager;
import android.widget.*;
import android.widget.TextView.OnEditorActionListener;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;

import com.badlogic.gdx.AbstractInput;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Graphics.DisplayMode;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.backends.android.keyboardheight.KeyboardHeightObserver;
import com.badlogic.gdx.backends.android.keyboardheight.KeyboardHeightProvider;
Expand All @@ -60,8 +66,8 @@

/** An implementation of the {@link Input} interface for Android.
*
* @author mzechner */
/** @author jshapcot */
* @author mzechner
* @author jshapcot */
public class DefaultAndroidInput extends AbstractInput implements AndroidInput, KeyboardHeightObserver {

static class KeyEvent {
Expand Down Expand Up @@ -144,6 +150,7 @@ protected TouchEvent newObject () {
private final AndroidApplicationConfiguration config;
protected final Orientation nativeOrientation;
private long currentEventTimeStamp = 0;
private PredictiveBackHandler predictiveBackHandler;

private SensorEventListener accelerometerListener;
private SensorEventListener gyroscopeListener;
Expand Down Expand Up @@ -179,6 +186,10 @@ public DefaultAndroidInput (Application activity, Context context, Object view,

haptics = new AndroidHaptics(context);

if (Build.VERSION.SDK_INT >= 33) {
this.predictiveBackHandler = new PredictiveBackHandler();
}

int rotation = getRotation();
DisplayMode mode = app.getGraphics().getDisplayMode();
if (((rotation == 0 || rotation == 180) && (mode.width >= mode.height))
Expand Down Expand Up @@ -1423,6 +1434,44 @@ public void onDreamingStopped () {
unregisterSensorListeners();
}

@Override
public void setCatchKey (int keycode, boolean catchKey) {
super.setCatchKey(keycode, catchKey);
if (keycode == Keys.BACK && predictiveBackHandler != null) {
if (catchKey)
predictiveBackHandler.register();
else
predictiveBackHandler.unregister();
}
}

/** Handle predictive back gestures on Android 13 and newer, replacing the <code>BACK</code> key event for exiting the
* activity.
* @see <a href="https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture">Add support for the
* predictive back gesture - Android Developers</a> */
@TargetApi(33)
private class PredictiveBackHandler {

private final OnBackInvokedDispatcher dispatcher = ((Activity)app).getOnBackInvokedDispatcher();
private final OnBackInvokedCallback callback = new OnBackInvokedCallback() {
@Override
public void onBackInvoked () {
if (processor != null) {
processor.keyDown(Keys.BACK);
}
}
};

private void register () {
dispatcher.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, callback);
}

private void unregister () {
dispatcher.unregisterOnBackInvokedCallback(callback);
}

}

/** Our implementation of SensorEventListener. Because Android doesn't like it when we register more than one Sensor to a
* single SensorEventListener, we add one of these for each Sensor. Could use an anonymous class, but I don't see any harm in
* explicitly defining it here. Correct me if I am wrong. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,26 +222,6 @@ public long getCurrentEventTime () {
return 0;
}

@Override
public void setCatchBackKey (boolean catchBack) {

}

@Override
public boolean isCatchBackKey () {
return false;
}

@Override
public void setCatchMenuKey (boolean catchMenu) {

}

@Override
public boolean isCatchMenuKey () {
return false;
}

@Override
public void setCatchKey (int keycode, boolean catchKey) {

Expand Down
20 changes: 0 additions & 20 deletions gdx/src/com/badlogic/gdx/AbstractInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,6 @@ public boolean isKeyJustPressed (int key) {
return justPressedKeys[key];
}

@Override
public boolean isCatchBackKey () {
return keysToCatch.contains(Keys.BACK);
}

@Override
public void setCatchBackKey (boolean catchBack) {
setCatchKey(Keys.BACK, catchBack);
}

@Override
public boolean isCatchMenuKey () {
return keysToCatch.contains(Keys.MENU);
}

@Override
public void setCatchMenuKey (boolean catchMenu) {
setCatchKey(Keys.MENU, catchMenu);
}

@Override
public void setCatchKey (int keycode, boolean catchKey) {
if (!catchKey) {
Expand Down
28 changes: 0 additions & 28 deletions gdx/src/com/badlogic/gdx/Input.java
Original file line number Diff line number Diff line change
Expand Up @@ -890,34 +890,6 @@ public enum VibrationType {
/** @return the time of the event currently reported to the {@link InputProcessor}. */
public long getCurrentEventTime ();

/** @deprecated use {@link Input#setCatchKey(int keycode, boolean catchKey)} instead
*
* Sets whether the BACK button on Android should be caught. This will prevent the app from being paused. Will have
* no effect on the desktop.
*
* @param catchBack whether to catch the back button */
@Deprecated
public void setCatchBackKey (boolean catchBack);

/** @deprecated use {@link Input#isCatchKey(int keycode)} instead
* @return whether the back button is currently being caught */
@Deprecated
public boolean isCatchBackKey ();

/** @deprecated use {@link Input#setCatchKey(int keycode, boolean catchKey)} instead
*
* Sets whether the MENU button on Android should be caught. This will prevent the onscreen keyboard to show up.
* Will have no effect on the desktop.
*
* @param catchMenu whether to catch the menu button */
@Deprecated
public void setCatchMenuKey (boolean catchMenu);

/** @deprecated use {@link Input#isCatchKey(int keycode)} instead
* @return whether the menu button is currently being caught */
@Deprecated
public boolean isCatchMenuKey ();

/** Sets whether the given key on Android or GWT should be caught. No effect on other platforms. All keys that are not caught
* may be handled by other apps or background processes on Android, or may trigger default browser behaviour on GWT. For
* example, media or volume buttons are handled by background media players if present, or Space key triggers a scroll. All
Expand Down
20 changes: 0 additions & 20 deletions gdx/src/com/badlogic/gdx/input/RemoteInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -511,26 +511,6 @@ public float getRoll () {
return compass[2];
}

@Override
public void setCatchBackKey (boolean catchBack) {

}

@Override
public boolean isCatchBackKey () {
return false;
}

@Override
public void setCatchMenuKey (boolean catchMenu) {

}

@Override
public boolean isCatchMenuKey () {
return false;
}

@Override
public void setCatchKey (int keycode, boolean catchKey) {

Expand Down
3 changes: 2 additions & 1 deletion tests/gdx-tests-android/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:allowBackup="false"
tools:ignore="GoogleAppIndexingWarning">
android:enableOnBackInvokedCallback="true"
tools:ignore="UnusedAttribute, GoogleAppIndexingWarning">
<activity android:name=".AndroidTestStarter" android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|screenLayout"
android:exported="true">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import android.widget.ArrayAdapter;
import android.widget.ListView;

import com.badlogic.gdx.tests.BackTest;
import com.badlogic.gdx.tests.utils.GdxTests;

public class AndroidTestStarter extends ListActivity {
Expand All @@ -36,8 +37,9 @@ public class AndroidTestStarter extends ListActivity {
@Override
public void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GdxTests.tests.add(MatrixTest.class);
if (!GdxTests.tests.contains(MatrixTest.class)) GdxTests.tests.add(MatrixTest.class);
if (!GdxTests.tests.contains(APKExpansionTest.class)) GdxTests.tests.add(APKExpansionTest.class);
if (!GdxTests.tests.contains(BackTest.class)) GdxTests.tests.add(BackTest.class);
List<String> testNames = GdxTests.getNames();
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, testNames));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,26 +360,6 @@ public long getCurrentEventTime () {
return input.getCurrentEventTime();
}

@Override
public void setCatchBackKey (boolean catchBack) {
input.setCatchBackKey(catchBack);
}

@Override
public boolean isCatchBackKey () {
return input.isCatchBackKey();
}

@Override
public void setCatchMenuKey (boolean catchMenu) {
input.setCatchMenuKey(catchMenu);
}

@Override
public boolean isCatchMenuKey () {
return input.isCatchMenuKey();
}

@Override
public void setCatchKey (int keycode, boolean catchKey) {
input.setCatchKey(keycode, catchKey);
Expand Down
69 changes: 69 additions & 0 deletions tests/gdx-tests/src/com/badlogic/gdx/tests/BackTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@

package com.badlogic.gdx.tests;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.tests.utils.GdxTest;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;

/** Check if predictive back gesture works, loosely modeled upon Android's back stack. Tap the screen to increment the counter. Go
* back to decrement the counter. If the counter is 0, the test will be exited. */
public class BackTest extends GdxTest {

private SpriteBatch batch;
private BitmapFont font;
private final Viewport viewport = new FitViewport(160, 90);

private int stackDepth;

@Override
public void create () {
batch = new SpriteBatch();
font = new BitmapFont();
Gdx.input.setInputProcessor(new InputAdapter() {

@Override
public boolean touchDown (int screenX, int screenY, int pointer, int button) {
int screenWidth = Gdx.graphics.getBackBufferWidth();
float safeZone = screenWidth * .1f;
if (screenX >= safeZone && screenX < screenWidth - safeZone) {
stackDepth++;
Gdx.input.setCatchKey(Input.Keys.BACK, stackDepth > 0);
return true;
}
return false;
}

@Override
public boolean keyDown (int keycode) {
if (keycode == Input.Keys.BACK) {
stackDepth--;
Gdx.input.setCatchKey(Input.Keys.BACK, stackDepth > 0);
return true;
}
return false;
}
});
}

@Override
public void render () {
ScreenUtils.clear(Color.BLACK);
batch.begin();
font.draw(batch, "Stack depth: " + stackDepth, 20, 50);
batch.end();
}

@Override
public void resize (int width, int height) {
viewport.update(width, height, true);
batch.setProjectionMatrix(viewport.getCamera().combined);
}

}

0 comments on commit 892dee6

Please sign in to comment.