Fix mouse grab behavior on Android (#16203)

* Add grab_mouse interface for Android
Makes mouse grabbing and 'Game Focus' work on Android with a real mouse
Properly handle relative mouse motion events on Android (SDK 28 and newer)

* Enable workflow_dispatch on CI Android

* Update android_mouse_calculate_deltas callsites

* Add RETRO_DEVICE_MOUSE to android_input_get_capabilities

* Use Handler to trigger UI events (toggle mouse, immersive mode) with 300ms delay

* Enable input_auto_mouse_grab by default for Android

* Handle RARCH_DEVICE_MOUSE_SCREEN in Android input driver

* Add android.hardware.type.pc to manifest

* Don't attempt to set pointer speed via scaling in android_mouse_calculate_deltas

* Keep x/y values within viewport resolution for screen mouse

* Use video_driver_get_size to get width/height

---------

Co-authored-by: Bernhard Schelling <14200249+schellingb@users.noreply.github.com>
This commit is contained in:
Patrick Stankard 2024-03-19 08:33:02 -04:00 committed by GitHub
parent 338c9a4fe4
commit 5452999b2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 283 additions and 117 deletions

View file

@ -3,8 +3,10 @@ name: CI Android
on:
push:
pull_request:
workflow_dispatch:
repository_dispatch:
types: [run_build]
permissions:
contents: read

View file

@ -1552,6 +1552,14 @@
#define DEFAULT_TURBO_DEFAULT_BTN RETRO_DEVICE_ID_JOYPAD_B
#define DEFAULT_ALLOW_TURBO_DPAD false
/* Enable automatic mouse grab by default
* only on Android */
#if defined(ANDROID)
#define DEFAULT_INPUT_AUTO_MOUSE_GRAB true
#else
#define DEFAULT_INPUT_AUTO_MOUSE_GRAB false
#endif
#if TARGET_OS_IPHONE
#define DEFAULT_INPUT_KEYBOARD_GAMEPAD_ENABLE false
#else

View file

@ -2086,7 +2086,7 @@ static struct config_bool_setting *populate_settings_bool(
SETTING_BOOL("keyboard_gamepad_enable", &settings->bools.input_keyboard_gamepad_enable, true, DEFAULT_INPUT_KEYBOARD_GAMEPAD_ENABLE, false);
SETTING_BOOL("input_autodetect_enable", &settings->bools.input_autodetect_enable, true, DEFAULT_INPUT_AUTODETECT_ENABLE, false);
SETTING_BOOL("input_allow_turbo_dpad", &settings->bools.input_allow_turbo_dpad, true, DEFAULT_ALLOW_TURBO_DPAD, false);
SETTING_BOOL("input_auto_mouse_grab", &settings->bools.input_auto_mouse_grab, true, false, false);
SETTING_BOOL("input_auto_mouse_grab", &settings->bools.input_auto_mouse_grab, true, DEFAULT_INPUT_AUTO_MOUSE_GRAB, false);
SETTING_BOOL("input_remap_binds_enable", &settings->bools.input_remap_binds_enable, true, true, false);
SETTING_BOOL("input_hotkey_device_merge", &settings->bools.input_hotkey_device_merge, true, DEFAULT_INPUT_HOTKEY_DEVICE_MERGE, false);
SETTING_BOOL("all_users_control_menu", &settings->bools.input_all_users_control_menu, true, DEFAULT_ALL_USERS_CONTROL_MENU, false);

View file

@ -2090,6 +2090,8 @@ static void frontend_unix_init(void *data)
"getVolumeCount", "()I");
GET_METHOD_ID(env, android_app->getVolumePath, class,
"getVolumePath", "(Ljava/lang/String;)Ljava/lang/String;");
GET_METHOD_ID(env, android_app->inputGrabMouse, class,
"inputGrabMouse", "(Z)V");
GET_OBJECT_CLASS(env, class, obj);
GET_METHOD_ID(env, android_app->getStringExtra, class,

View file

@ -178,6 +178,7 @@ struct android_app
jmethodID getVolumeCount;
jmethodID getVolumePath;
jmethodID inputGrabMouse;
struct
{

View file

@ -61,11 +61,17 @@ enum {
AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
AMOTION_EVENT_AXIS_VSCROLL = 9,
AMOTION_EVENT_ACTION_HOVER_MOVE = 7,
AINPUT_SOURCE_STYLUS = 0x00004002,
AINPUT_SOURCE_STYLUS = 0x00004000 | AINPUT_SOURCE_CLASS_POINTER,
AMOTION_EVENT_BUTTON_STYLUS_PRIMARY = 1 << 5,
AMOTION_EVENT_BUTTON_STYLUS_SECONDARY = 1 << 6
};
#endif
/* If using an NDK lower than 16b then add missing definition */
#ifndef __ANDROID_API_O_MR1__
enum {
AINPUT_SOURCE_MOUSE_RELATIVE = 0x00020000 | AINPUT_SOURCE_CLASS_NAVIGATION
};
#endif
/* If using an SDK lower than 24 then add missing relative axis codes */
#ifndef AMOTION_EVENT_AXIS_RELATIVE_X
@ -144,6 +150,7 @@ typedef struct android_input
{
int64_t quick_tap_time;
state_device_t pad_states[MAX_USERS]; /* int alignment */
int mouse_x, mouse_y;
int mouse_x_delta, mouse_y_delta;
int mouse_l, mouse_r, mouse_m, mouse_wu, mouse_wd;
unsigned pads_connected;
@ -638,53 +645,77 @@ static int android_check_quick_tap(android_input_t *android)
}
static INLINE void android_mouse_calculate_deltas(android_input_t *android,
AInputEvent *event,size_t motion_ptr)
AInputEvent *event,size_t motion_ptr,int source)
{
/* Adjust mouse speed based on ratio
* between core resolution and system resolution */
float x = 0, y = 0;
float x_scale = 1;
float y_scale = 1;
settings_t *settings = config_get_ptr();
video_driver_state_t *video_st = video_state_get_ptr();
struct retro_system_av_info *av_info = &video_st->av_info;
unsigned video_width, video_height;
video_driver_get_size(&video_width, &video_height);
if (av_info)
float x = 0;
float x_delta = 0;
float x_min = 0;
float x_max = (float)video_width;
float y = 0;
float y_delta = 0;
float y_min = 0;
float y_max = (float)video_height;
/* AINPUT_SOURCE_MOUSE_RELATIVE is available on Oreo (SDK 26) and newer,
* it passes the relative coordinates in the regular X and Y parts.
* NOTE: AINPUT_SOURCE_* defines have multiple bits set so do full check */
if ((source & AINPUT_SOURCE_MOUSE_RELATIVE) == AINPUT_SOURCE_MOUSE_RELATIVE)
{
video_viewport_t *custom_vp = &settings->video_viewport_custom;
const struct retro_game_geometry *geom = (const struct retro_game_geometry*)&av_info->geometry;
x_scale = 2 * (float)geom->base_width / (float)custom_vp->width;
y_scale = 2 * (float)geom->base_height / (float)custom_vp->height;
x_delta = AMotionEvent_getX(event, motion_ptr);
y_delta = AMotionEvent_getY(event, motion_ptr);
}
else
{
/* This axis is only available on Android Nougat or on
* Android devices with NVIDIA extensions */
if (p_AMotionEvent_getAxisValue)
{
x_delta = AMotionEvent_getAxisValue(event,AMOTION_EVENT_AXIS_RELATIVE_X,
motion_ptr);
y_delta = AMotionEvent_getAxisValue(event,AMOTION_EVENT_AXIS_RELATIVE_Y,
motion_ptr);
}
/* If AXIS_RELATIVE had 0 values it might be because we're not
* running Android Nougat or on a device
* with NVIDIA extension, so re-calculate deltas based on
* AXIS_X and AXIS_Y. This has limitations
* compared to AXIS_RELATIVE because once the Android mouse cursor
* hits the edge of the screen it is
* not possible to move the in-game mouse any further in that direction.
*/
if (!x_delta && !y_delta)
{
x = AMotionEvent_getX(event, motion_ptr);
y = AMotionEvent_getY(event, motion_ptr);
x_delta = (x_delta - android->mouse_x_prev);
y_delta = (y_delta - android->mouse_y_prev);
android->mouse_x_prev = x;
android->mouse_y_prev = y;
}
}
/* This axis is only available on Android Nougat and on
* Android devices with NVIDIA extensions */
if (p_AMotionEvent_getAxisValue)
{
x = AMotionEvent_getAxisValue(event,AMOTION_EVENT_AXIS_RELATIVE_X,
motion_ptr);
y = AMotionEvent_getAxisValue(event,AMOTION_EVENT_AXIS_RELATIVE_Y,
motion_ptr);
}
android->mouse_x_delta = x_delta;
android->mouse_y_delta = y_delta;
/* If AXIS_RELATIVE had 0 values it might be because we're not
* running Android Nougat or on a device
* with NVIDIA extension, so re-calculate deltas based on
* AXIS_X and AXIS_Y. This has limitations
* compared to AXIS_RELATIVE because once the Android mouse cursor
* hits the edge of the screen it is
* not possible to move the in-game mouse any further in that direction.
*/
if (!x && !y)
{
x = (AMotionEvent_getX(event, motion_ptr) - android->mouse_x_prev);
y = (AMotionEvent_getY(event, motion_ptr) - android->mouse_y_prev);
android->mouse_x_prev = AMotionEvent_getX(event, motion_ptr);
android->mouse_y_prev = AMotionEvent_getY(event, motion_ptr);
}
if (!x) x = android->mouse_x + android->mouse_x_delta;
if (!y) y = android->mouse_y + android->mouse_y_delta;
android->mouse_x_delta = ceil(x) * x_scale;
android->mouse_y_delta = ceil(y) * y_scale;
/* x and y are used for the screen mouse, so we want
* to avoid values outside of the viewport resolution */
if (x < x_min) x = x_min;
else if (x > x_max) x = x_max;
if (y < y_min) y = y_min;
else if (y > y_max) y = y_max;
android->mouse_x = x;
android->mouse_y = y;
}
static INLINE void android_input_poll_event_type_motion(
@ -697,13 +728,13 @@ static INLINE void android_input_poll_event_type_motion(
bool keyup = (
action == AMOTION_EVENT_ACTION_UP
|| action == AMOTION_EVENT_ACTION_CANCEL
|| action == AMOTION_EVENT_ACTION_POINTER_UP)
|| (source == AINPUT_SOURCE_MOUSE &&
action != AMOTION_EVENT_ACTION_DOWN);
|| action == AMOTION_EVENT_ACTION_POINTER_UP);
/* If source is mouse then calculate button state
* and mouse deltas and don't process as touchscreen event */
if (source == AINPUT_SOURCE_MOUSE)
* and mouse deltas and don't process as touchscreen event.
* NOTE: AINPUT_SOURCE_* defines have multiple bits set so do full check */
if ( (source & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE
|| (source & AINPUT_SOURCE_MOUSE_RELATIVE) == AINPUT_SOURCE_MOUSE_RELATIVE)
{
/* getButtonState requires API level 14 */
if (p_AMotionEvent_getButtonState)
@ -732,7 +763,7 @@ static INLINE void android_input_poll_event_type_motion(
android->mouse_l = 0;
}
android_mouse_calculate_deltas(android,event,motion_ptr);
android_mouse_calculate_deltas(android,event,motion_ptr,source);
return;
}
@ -785,7 +816,7 @@ static INLINE void android_input_poll_event_type_motion(
if (( action == AMOTION_EVENT_ACTION_MOVE
|| action == AMOTION_EVENT_ACTION_HOVER_MOVE)
&& ENABLE_TOUCH_SCREEN_MOUSE)
android_mouse_calculate_deltas(android,event,motion_ptr);
android_mouse_calculate_deltas(android,event,motion_ptr,source);
for (motion_ptr = 0; motion_ptr < pointer_max; motion_ptr++)
{
@ -850,7 +881,7 @@ static INLINE void android_input_poll_event_type_motion_stylus(
android->mouse_l = 0;
}
android_mouse_calculate_deltas(android,event,motion_ptr);
android_mouse_calculate_deltas(android,event,motion_ptr,source);
}
if (action == AMOTION_EVENT_ACTION_MOVE) {
@ -893,7 +924,7 @@ static INLINE void android_input_poll_event_type_motion_stylus(
{
android->mouse_l = 0;
android_mouse_calculate_deltas(android,event,motion_ptr);
android_mouse_calculate_deltas(android,event,motion_ptr,source);
}
// pointer was already released during AMOTION_EVENT_ACTION_HOVER_MOVE
@ -967,7 +998,7 @@ static int android_input_get_id_port(android_input_t *android, int id,
unsigned i;
int ret = -1;
if (source & (AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_MOUSE |
AINPUT_SOURCE_TOUCHPAD))
AINPUT_SOURCE_MOUSE_RELATIVE | AINPUT_SOURCE_TOUCHPAD))
ret = 0; /* touch overlay is always user 1 */
for (i = 0; i < android->pads_connected; i++)
@ -1565,7 +1596,10 @@ static void android_input_poll_input_default(android_input_t *android)
else if ((source & AINPUT_SOURCE_STYLUS) == AINPUT_SOURCE_STYLUS)
android_input_poll_event_type_motion_stylus(android, event,
port, source);
else if ((source & (AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_MOUSE)))
/* Only handle events from a touchscreen or mouse */
else if ((source & (AINPUT_SOURCE_TOUCHSCREEN
| AINPUT_SOURCE_MOUSE
| AINPUT_SOURCE_MOUSE_RELATIVE)))
android_input_poll_event_type_motion(android, event,
port, source);
else
@ -1774,6 +1808,7 @@ static int16_t android_input_state(
case RETRO_DEVICE_KEYBOARD:
return (id && id < RETROK_LAST) && BIT_GET(android_key_state[ANDROID_KEYBOARD_PORT], rarch_keysym_lut[id]);
case RETRO_DEVICE_MOUSE:
case RARCH_DEVICE_MOUSE_SCREEN:
{
int val = 0;
if (port > 0)
@ -1788,11 +1823,17 @@ static int16_t android_input_state(
case RETRO_DEVICE_ID_MOUSE_MIDDLE:
return android->mouse_m;
case RETRO_DEVICE_ID_MOUSE_X:
if (device == RARCH_DEVICE_MOUSE_SCREEN)
return android->mouse_x;
val = android->mouse_x_delta;
android->mouse_x_delta = 0;
/* flush delta after it has been read */
return val;
case RETRO_DEVICE_ID_MOUSE_Y:
if (device == RARCH_DEVICE_MOUSE_SCREEN)
return android->mouse_y;
val = android->mouse_y_delta;
android->mouse_y_delta = 0;
/* flush delta after it has been read */
@ -1907,6 +1948,7 @@ static uint64_t android_input_get_capabilities(void *data)
return
(1 << RETRO_DEVICE_JOYPAD)
| (1 << RETRO_DEVICE_POINTER)
| (1 << RETRO_DEVICE_MOUSE)
| (1 << RETRO_DEVICE_KEYBOARD)
| (1 << RETRO_DEVICE_LIGHTGUN)
| (1 << RETRO_DEVICE_ANALOG);
@ -2056,6 +2098,18 @@ static float android_input_get_sensor_input(void *data,
return 0.0f;
}
static void android_input_grab_mouse(void *data, bool state)
{
JNIEnv *env = jni_thread_getenv();
if (!env || !g_android)
return;
if (g_android->inputGrabMouse)
CALL_VOID_METHOD_PARAM(env, g_android->activity->clazz,
g_android->inputGrabMouse, state);
}
static void android_input_keypress_vibrate()
{
static const int keyboard_press = 3;
@ -2077,8 +2131,7 @@ input_driver_t input_android = {
android_input_get_sensor_input,
android_input_get_capabilities,
"android",
NULL, /* grab_mouse */
android_input_grab_mouse,
NULL,
android_input_keypress_vibrate
};

View file

@ -15272,7 +15272,7 @@ static bool setting_append_list(
&settings->bools.input_auto_mouse_grab,
MENU_ENUM_LABEL_INPUT_AUTO_MOUSE_GRAB,
MENU_ENUM_LABEL_VALUE_INPUT_AUTO_MOUSE_GRAB,
false,
DEFAULT_INPUT_AUTO_MOUSE_GRAB,
MENU_ENUM_LABEL_VALUE_OFF,
MENU_ENUM_LABEL_VALUE_ON,
&group_info,

View file

@ -6,6 +6,7 @@
android:versionName="1.17.0"
android:installLocation="internalOnly">
<uses-feature android:glEsVersion="0x00020000" />
<uses-feature android:name="android.hardware.type.pc" android:required="false"/>
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.gamepad" android:required="false"/>

View file

@ -1,12 +1,17 @@
package com.retroarch.browser.retroactivity;
import android.util.Log;
import android.view.PointerIcon;
import android.view.View;
import android.view.WindowManager;
import android.content.Intent;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.retroarch.browser.preferences.util.ConfigFile;
import com.retroarch.browser.preferences.util.UserPreferences;
import java.lang.reflect.InvocationTargetException;
@ -14,58 +19,69 @@ import java.lang.reflect.Method;
public final class RetroActivityFuture extends RetroActivityCamera {
// If set to true then Retroarch will completely exit when it loses focus
// If set to true then RetroArch will completely exit when it loses focus
private boolean quitfocus = false;
// Top-level window decor view
private View mDecorView;
// Constants used for Handler messages
private static final int HANDLER_WHAT_TOGGLE_IMMERSIVE = 1;
private static final int HANDLER_WHAT_TOGGLE_POINTER_CAPTURE = 2;
private static final int HANDLER_WHAT_TOGGLE_POINTER_NVIDIA = 3;
private static final int HANDLER_WHAT_TOGGLE_POINTER_ICON = 4;
private static final int HANDLER_ARG_TRUE = 1;
private static final int HANDLER_ARG_FALSE = 0;
private static final int HANDLER_MESSAGE_DELAY_DEFAULT_MS = 300;
// Handler used for UI events
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
boolean state = (msg.arg1 == HANDLER_ARG_TRUE) ? true : false;
if (msg.what == HANDLER_WHAT_TOGGLE_IMMERSIVE) {
attemptToggleImmersiveMode(state);
} else if (msg.what == HANDLER_WHAT_TOGGLE_POINTER_CAPTURE) {
attemptTogglePointerCapture(state);
} else if (msg.what == HANDLER_WHAT_TOGGLE_POINTER_NVIDIA) {
attemptToggleNvidiaCursorVisibility(state);
} else if (msg.what == HANDLER_WHAT_TOGGLE_POINTER_ICON) {
attemptTogglePointerIcon(state);
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDecorView = getWindow().getDecorView();
// If QUITFOCUS parameter is provided then enable that Retroarch quits when focus is lost
quitfocus = getIntent().hasExtra("QUITFOCUS");
}
@Override
public void onResume() {
super.onResume();
setSustainedPerformanceMode(sustainedPerformanceMode);
if (Build.VERSION.SDK_INT >= 19) {
// Immersive mode
// Check for Android UI specific parameters
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String refresh = getIntent().getStringExtra("REFRESH");
// Constants from API > 14
final int API_SYSTEM_UI_FLAG_LAYOUT_STABLE = 0x00000100;
final int API_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200;
final int API_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400;
final int API_SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004;
final int API_SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000;
View thisView = getWindow().getDecorView();
thisView.setSystemUiVisibility(API_SYSTEM_UI_FLAG_LAYOUT_STABLE
| API_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| API_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| API_SYSTEM_UI_FLAG_FULLSCREEN
| API_SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
// Check for Android UI specific parameters
Intent retro = getIntent();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
String refresh = retro.getStringExtra("REFRESH");
// If REFRESH parameter is provided then try to set refreshrate accordingly
if(refresh != null) {
WindowManager.LayoutParams params = getWindow().getAttributes();
params.preferredRefreshRate = Integer.parseInt(refresh);
getWindow().setAttributes(params);
}
// If REFRESH parameter is provided then try to set refreshrate accordingly
if (refresh != null) {
WindowManager.LayoutParams params = getWindow().getAttributes();
params.preferredRefreshRate = Integer.parseInt(refresh);
getWindow().setAttributes(params);
}
// If QUITFOCUS parameter is provided then enable that Retroarch quits when focus is lost
quitfocus = retro.hasExtra("QUITFOCUS");
// If HIDEMOUSE parameters is provided then hide the mourse cursor
// This requires NVIDIA Android extensions (available on NVIDIA Shield), if they are not
// available then nothing will be done
if (retro.hasExtra("HIDEMOUSE")) hideMouseCursor();
}
//Checks if Android versions is above 9.0 (28) and enable the screen to write over notch if the user desires
if (Build.VERSION.SDK_INT >= 28) {
// Checks if Android versions is above 9.0 (28) and enable the screen to write over notch if the user desires
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ConfigFile configFile = new ConfigFile(UserPreferences.getDefaultConfigPath(this));
try {
if (configFile.getBoolean("video_notch_write_over_enable")) {
@ -77,31 +93,114 @@ public final class RetroActivityFuture extends RetroActivityCamera {
}
}
public void hideMouseCursor() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
// Check for NVIDIA extensions and minimum SDK version
Method mInputManager_setCursorVisibility;
try { mInputManager_setCursorVisibility =
InputManager.class.getMethod("setCursorVisibility", boolean.class);
}
catch (NoSuchMethodException ex) {
return; // Extensions were not available so do nothing
}
// Hide the mouse cursor
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
try { mInputManager_setCursorVisibility.invoke(inputManager, false); }
catch (InvocationTargetException ite) { }
catch (IllegalAccessException iae) { }
}
}
@Override
public void onStop() {
super.onStop();
// If QUITFOCUS parameter was set then completely exit Retroarch when focus is lost
// If QUITFOCUS parameter was set then completely exit RetroArch when focus is lost
if (quitfocus) System.exit(0);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
mHandlerSendUiMessage(HANDLER_WHAT_TOGGLE_IMMERSIVE, hasFocus);
try {
ConfigFile configFile = new ConfigFile(UserPreferences.getDefaultConfigPath(this));
if (configFile.getBoolean("input_auto_mouse_grab")) {
inputGrabMouse(hasFocus);
}
} catch (Exception e) {
Log.w("[onWindowFocusChanged] exception thrown:", e.getMessage());
}
}
private void mHandlerSendUiMessage(int what, boolean state) {
int arg1 = (state ? HANDLER_ARG_TRUE : HANDLER_ARG_FALSE);
int arg2 = -1;
Message message = mHandler.obtainMessage(what, arg1, arg2);
mHandler.sendMessageDelayed(message, HANDLER_MESSAGE_DELAY_DEFAULT_MS);
}
public void inputGrabMouse(boolean state) {
mHandlerSendUiMessage(HANDLER_WHAT_TOGGLE_POINTER_CAPTURE, state);
mHandlerSendUiMessage(HANDLER_WHAT_TOGGLE_POINTER_NVIDIA, state);
mHandlerSendUiMessage(HANDLER_WHAT_TOGGLE_POINTER_ICON, state);
}
private void attemptToggleImmersiveMode(boolean state) {
// Attempt to toggle "Immersive Mode" for Android 4.4 (19) and up
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
if (state) {
mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_IMMERSIVE);
} else {
mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
} catch (Exception e) {
Log.w("[attemptToggleImmersiveMode] exception thrown:", e.getMessage());
}
}
}
private void attemptTogglePointerCapture(boolean state) {
// Attempt requestPointerCapture for Android 8.0 (26) and up
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
if (state) {
mDecorView.requestPointerCapture();
} else {
mDecorView.releasePointerCapture();
}
} catch (Exception e) {
Log.w("[attemptTogglePointerCapture] exception thrown:", e.getMessage());
}
}
}
private void attemptToggleNvidiaCursorVisibility(boolean state) {
// Attempt setCursorVisibility for Android 4.1 (16) and up
// only works if NVIDIA Android extensions for NVIDIA Shield are available
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
try {
boolean cursorVisibility = !state;
Method mInputManager_setCursorVisibility = InputManager.class.getMethod("setCursorVisibility", boolean.class);
InputManager inputManager = (InputManager) getSystemService(Context.INPUT_SERVICE);
mInputManager_setCursorVisibility.invoke(inputManager, cursorVisibility);
} catch (NoSuchMethodException e) {
// Extensions were not available so do nothing
} catch (Exception e) {
Log.w("[attemptToggleNvidiaCursorVisibility] exception thrown:", e.getMessage());
}
}
}
private void attemptTogglePointerIcon(boolean state) {
// Attempt setPointerIcon for Android 7.x (24, 25) only
// For Android 8.0+, requestPointerCapture is used
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
try {
if (state) {
PointerIcon nullPointerIcon = PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL);
mDecorView.setPointerIcon(nullPointerIcon);
} else {
// Restore the pointer icon to it's default value
mDecorView.setPointerIcon(null);
}
} catch (Exception e) {
Log.w("[attemptTogglePointerIcon] exception thrown:", e.getMessage());
}
}
}
}