bug 852935 - Add Android gamepad backend. r=snorp, rs=smaug

This commit is contained in:
Ted Mielczarek 2014-04-22 10:53:48 -04:00
parent 5656eb9940
commit dff9bd8e4c
17 changed files with 706 additions and 6 deletions

View File

@ -5949,6 +5949,11 @@ case "$OS_TARGET" in
Darwin|WINNT|Linux)
MOZ_GAMEPAD=1
;;
Android)
if test "$MOZ_WIDGET_TOOLKIT" != "gonk"; then
MOZ_GAMEPAD=1
fi
;;
*)
;;
esac
@ -5973,6 +5978,9 @@ if test "$MOZ_GAMEPAD"; then
fi
MOZ_GAMEPAD_BACKEND=linux
;;
Android)
MOZ_GAMEPAD_BACKEND=android
;;
*)
;;
esac

View File

@ -368,15 +368,15 @@ var interfaceNamesInGlobalScope =
// IMPORTANT: Do not change this list without review from a DOM peer!
"GainNode",
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "Gamepad", desktop: true},
{name: "Gamepad", b2g: false},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "GamepadAxisMoveEvent", desktop: true},
{name: "GamepadAxisMoveEvent", b2g: false},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "GamepadButtonEvent", desktop: true},
{name: "GamepadButtonEvent", b2g: false},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "GamepadButton", desktop: true},
{name: "GamepadButton", b2g: false},
// IMPORTANT: Do not change this list without review from a DOM peer!
{name: "GamepadEvent", desktop: true},
{name: "GamepadEvent", b2g: false},
// IMPORTANT: Do not change this list without review from a DOM peer!
"HashChangeEvent",
// IMPORTANT: Do not change this list without review from a DOM peer!

View File

@ -0,0 +1,27 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Hal.h"
#include "AndroidBridge.h"
using namespace mozilla::hal;
namespace mozilla {
namespace hal_impl {
void
StartMonitoringGamepadStatus()
{
mozilla::widget::android::GeckoAppShell::StartMonitoringGamepad();
}
void
StopMonitoringGamepadStatus()
{
mozilla::widget::android::GeckoAppShell::StopMonitoringGamepad();
}
} // hal_impl
} // mozilla

View File

@ -48,6 +48,10 @@ elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'linux':
UNIFIED_SOURCES += [
'linux/LinuxGamepad.cpp'
]
elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'android':
UNIFIED_SOURCES += [
'android/AndroidGamepad.cpp'
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
UNIFIED_SOURCES += [

View File

@ -0,0 +1,395 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.util.GamepadUtils;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Build;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import java.lang.Math;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class AndroidGamepadManager {
// This is completely arbitrary.
private static final float TRIGGER_PRESSED_THRESHOLD = 0.25f;
private static final long POLL_TIMER_PERIOD = 1000; // milliseconds
private static enum Axis {
X(MotionEvent.AXIS_X),
Y(MotionEvent.AXIS_Y),
Z(MotionEvent.AXIS_Z),
RZ(MotionEvent.AXIS_RZ);
public final int axis;
private Axis(int axis) {
this.axis = axis;
}
};
// A list of gamepad button mappings. Axes are determined at
// runtime, as they vary by Android version.
private static enum Trigger {
Left(6),
Right(7);
public final int button;
private Trigger(int button) {
this.button = button;
}
};
private static final int FIRST_DPAD_BUTTON = 12;
// A list of axis number, gamepad button mappings for negative, positive.
// Button mappings are added to FIRST_DPAD_BUTTON.
private static enum DpadAxis {
UpDown(MotionEvent.AXIS_HAT_Y, 0, 1),
LeftRight(MotionEvent.AXIS_HAT_X, 2, 3);
public final int axis;
public final int negativeButton;
public final int positiveButton;
private DpadAxis(int axis, int negativeButton, int positiveButton) {
this.axis = axis;
this.negativeButton = negativeButton;
this.positiveButton = positiveButton;
}
};
private static enum Button {
A(KeyEvent.KEYCODE_BUTTON_A),
B(KeyEvent.KEYCODE_BUTTON_B),
X(KeyEvent.KEYCODE_BUTTON_X),
Y(KeyEvent.KEYCODE_BUTTON_Y),
L1(KeyEvent.KEYCODE_BUTTON_L1),
R1(KeyEvent.KEYCODE_BUTTON_R1),
L2(KeyEvent.KEYCODE_BUTTON_L2),
R2(KeyEvent.KEYCODE_BUTTON_R2),
SELECT(KeyEvent.KEYCODE_BUTTON_SELECT),
START(KeyEvent.KEYCODE_BUTTON_START),
THUMBL(KeyEvent.KEYCODE_BUTTON_THUMBL),
THUMBR(KeyEvent.KEYCODE_BUTTON_THUMBR),
DPAD_UP(KeyEvent.KEYCODE_DPAD_UP),
DPAD_DOWN(KeyEvent.KEYCODE_DPAD_DOWN),
DPAD_LEFT(KeyEvent.KEYCODE_DPAD_LEFT),
DPAD_RIGHT(KeyEvent.KEYCODE_DPAD_RIGHT);
public final int button;
private Button(int button) {
this.button = button;
}
};
private static class Gamepad {
// ID from GamepadService
public int id;
// Retain axis state so we can determine changes.
public float axes[];
public boolean dpad[];
public int triggerAxes[];
public float triggers[];
public Gamepad(int serviceId, int deviceId) {
id = serviceId;
axes = new float[Axis.values().length];
dpad = new boolean[4];
triggers = new float[2];
InputDevice device = InputDevice.getDevice(deviceId);
if (device != null) {
// LTRIGGER/RTRIGGER don't seem to be exposed on older
// versions of Android.
if (device.getMotionRange(MotionEvent.AXIS_LTRIGGER) != null && device.getMotionRange(MotionEvent.AXIS_RTRIGGER) != null) {
triggerAxes = new int[]{MotionEvent.AXIS_LTRIGGER,
MotionEvent.AXIS_RTRIGGER};
} else if (device.getMotionRange(MotionEvent.AXIS_BRAKE) != null && device.getMotionRange(MotionEvent.AXIS_GAS) != null) {
triggerAxes = new int[]{MotionEvent.AXIS_BRAKE,
MotionEvent.AXIS_GAS};
} else {
triggerAxes = null;
}
}
}
}
private static boolean sStarted = false;
private static HashMap<Integer, Gamepad> sGamepads = null;
private static HashMap<Integer, List<KeyEvent>> sPendingGamepads = null;
private static InputManager.InputDeviceListener sListener = null;
private static Timer sPollTimer = null;
private AndroidGamepadManager() {
}
public static void startup() {
ThreadUtils.assertOnUiThread();
if (!sStarted) {
sGamepads = new HashMap<Integer, Gamepad>();
sPendingGamepads = new HashMap<Integer, List<KeyEvent>>();
scanForGamepads();
addDeviceListener();
sStarted = true;
}
}
public static void shutdown() {
ThreadUtils.assertOnUiThread();
if (sStarted) {
removeDeviceListener();
sPendingGamepads = null;
sGamepads = null;
sStarted = false;
}
}
public static void gamepadAdded(int deviceId, int serviceId) {
ThreadUtils.assertOnUiThread();
if (!sStarted) {
return;
}
if (!sPendingGamepads.containsKey(deviceId)) {
removeGamepad(deviceId);
return;
}
List<KeyEvent> pending = sPendingGamepads.get(deviceId);
sPendingGamepads.remove(deviceId);
sGamepads.put(deviceId, new Gamepad(serviceId, deviceId));
// Handle queued KeyEvents
for (KeyEvent ev : pending) {
handleKeyEvent(ev);
}
}
private static float deadZone(MotionEvent ev, int axis) {
if (GamepadUtils.isValueInDeadZone(ev, axis)) {
return 0.0f;
}
return ev.getAxisValue(axis);
}
private static void mapDpadAxis(Gamepad gamepad,
boolean pressed,
float value,
int which) {
if (pressed != gamepad.dpad[which]) {
gamepad.dpad[which] = pressed;
GeckoAppShell.sendEventToGecko(GeckoEvent.createGamepadButtonEvent(gamepad.id, FIRST_DPAD_BUTTON + which, pressed, Math.abs(value)));
}
}
public static boolean handleMotionEvent(MotionEvent ev) {
ThreadUtils.assertOnUiThread();
if (!sStarted) {
return false;
}
if (!sGamepads.containsKey(ev.getDeviceId())) {
// Not a device we care about.
return false;
}
Gamepad gamepad = sGamepads.get(ev.getDeviceId());
// First check the analog stick axes
boolean[] valid = new boolean[Axis.values().length];
float[] axes = new float[Axis.values().length];
boolean anyValidAxes = false;
for (Axis axis : Axis.values()) {
float value = deadZone(ev, axis.axis);
int i = axis.ordinal();
if (value != gamepad.axes[i]) {
axes[i] = value;
gamepad.axes[i] = value;
valid[i] = true;
anyValidAxes = true;
}
}
if (anyValidAxes) {
// Send an axismove event.
GeckoAppShell.sendEventToGecko(GeckoEvent.createGamepadAxisEvent(gamepad.id, valid, axes));
}
// Map triggers to buttons.
if (gamepad.triggerAxes != null) {
for (Trigger trigger : Trigger.values()) {
int i = trigger.ordinal();
int axis = gamepad.triggerAxes[i];
float value = deadZone(ev, axis);
if (value != gamepad.triggers[i]) {
gamepad.triggers[i] = value;
boolean pressed = value > TRIGGER_PRESSED_THRESHOLD;
GeckoAppShell.sendEventToGecko(GeckoEvent.createGamepadButtonEvent(gamepad.id, trigger.button, pressed, value));
}
}
}
// Map d-pad to buttons.
for (DpadAxis dpadaxis : DpadAxis.values()) {
float value = deadZone(ev, dpadaxis.axis);
mapDpadAxis(gamepad, value < 0.0f, value, dpadaxis.negativeButton);
mapDpadAxis(gamepad, value > 0.0f, value, dpadaxis.positiveButton);
}
return true;
}
public static boolean handleKeyEvent(KeyEvent ev) {
ThreadUtils.assertOnUiThread();
if (!sStarted) {
return false;
}
int deviceId = ev.getDeviceId();
if (sPendingGamepads.containsKey(deviceId)) {
// Queue up key events for pending devices.
sPendingGamepads.get(deviceId).add(ev);
return true;
} else if (!sGamepads.containsKey(deviceId)) {
InputDevice device = ev.getDevice();
if (device != null &&
(device.getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
// This is a gamepad we haven't seen yet.
addGamepad(device);
sPendingGamepads.get(deviceId).add(ev);
return true;
}
// Not a device we care about.
return false;
}
int key = -1;
for (Button button : Button.values()) {
if (button.button == ev.getKeyCode()) {
key = button.ordinal();
break;
}
}
if (key == -1) {
// Not a key we know how to handle.
return false;
}
if (ev.getRepeatCount() > 0) {
// We would handle this key, but we're not interested in
// repeats. Eat it.
return true;
}
Gamepad gamepad = sGamepads.get(deviceId);
boolean pressed = ev.getAction() == KeyEvent.ACTION_DOWN;
GeckoAppShell.sendEventToGecko(GeckoEvent.createGamepadButtonEvent(gamepad.id, key, pressed, pressed ? 1.0f : 0.0f));
return true;
}
private static void scanForGamepads() {
if (Build.VERSION.SDK_INT < 9) {
return;
}
int[] deviceIds = InputDevice.getDeviceIds();
if (deviceIds == null) {
return;
}
for (int i=0; i < deviceIds.length; i++) {
InputDevice device = InputDevice.getDevice(deviceIds[i]);
if (device == null) {
continue;
}
if ((device.getSources() & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD) {
continue;
}
addGamepad(device);
}
}
private static void addGamepad(InputDevice device) {
//TODO: when we're using a newer SDK version, use these.
//if (Build.VERSION.SDK_INT >= 12) {
//int vid = device.getVendorId();
//int pid = device.getProductId();
//}
sPendingGamepads.put(device.getId(), new ArrayList<KeyEvent>());
GeckoAppShell.sendEventToGecko(GeckoEvent.createGamepadAddRemoveEvent(device.getId(), true));
}
private static void removeGamepad(int deviceId) {
Gamepad gamepad = sGamepads.get(deviceId);
GeckoAppShell.sendEventToGecko(GeckoEvent.createGamepadAddRemoveEvent(gamepad.id, false));
sGamepads.remove(deviceId);
}
private static void addDeviceListener() {
if (Build.VERSION.SDK_INT < 16) {
// Poll known gamepads to see if they've disappeared.
sPollTimer = new Timer();
sPollTimer.scheduleAtFixedRate(new TimerTask() {
public void run() {
for (Integer deviceId : sGamepads.keySet()) {
if (InputDevice.getDevice(deviceId) == null) {
removeGamepad(deviceId);
}
}
}
}, POLL_TIMER_PERIOD, POLL_TIMER_PERIOD);
return;
}
sListener = new InputManager.InputDeviceListener() {
public void onInputDeviceAdded(int deviceId) {
InputDevice device = InputDevice.getDevice(deviceId);
if (device == null) {
return;
}
if ((device.getSources() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) {
addGamepad(device);
}
}
public void onInputDeviceRemoved(int deviceId) {
if (sPendingGamepads.containsKey(deviceId)) {
// Got removed before Gecko's ack reached us.
// gamepadAdded will deal with it.
sPendingGamepads.remove(deviceId);
return;
}
if (sGamepads.containsKey(deviceId)) {
removeGamepad(deviceId);
}
}
public void onInputDeviceChanged(int deviceId) {
}
};
((InputManager)GeckoAppShell.getContext().getSystemService(Context.INPUT_SERVICE)).registerInputDeviceListener(sListener, ThreadUtils.getUiHandler());
}
private static void removeDeviceListener() {
if (Build.VERSION.SDK_INT < 16) {
if (sPollTimer != null) {
sPollTimer.cancel();
sPollTimer = null;
}
return;
}
((InputManager)GeckoAppShell.getContext().getSystemService(Context.INPUT_SERVICE)).unregisterInputDeviceListener(sListener);
sListener = null;
}
}

View File

@ -14,6 +14,7 @@ import java.util.Vector;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.AndroidGamepadManager;
import org.mozilla.gecko.DynamicToolbar.PinReason;
import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
@ -268,6 +269,10 @@ abstract public class BrowserApp extends GeckoApp
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (AndroidGamepadManager.handleKeyEvent(event)) {
return true;
}
// Global onKey handler. This is called if the focused UI doesn't
// handle the key event, and before Gecko swallows the events.
if (event.getAction() != KeyEvent.ACTION_DOWN) {
@ -357,6 +362,14 @@ abstract public class BrowserApp extends GeckoApp
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (AndroidGamepadManager.handleKeyEvent(event)) {
return true;
}
return super.onKeyUp(keyCode, event);
}
void handleReaderListStatusRequest(final String url) {
ThreadUtils.postToBackgroundThread(new Runnable() {
@Override

View File

@ -698,6 +698,43 @@ public class GeckoAppShell
}
}
@WrapElementForJNI
public static void startMonitoringGamepad() {
if (Build.VERSION.SDK_INT < 9) {
return;
}
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
AndroidGamepadManager.startup();
}
});
}
@WrapElementForJNI
public static void stopMonitoringGamepad() {
if (Build.VERSION.SDK_INT < 9) {
return;
}
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
AndroidGamepadManager.shutdown();
}
});
}
@WrapElementForJNI
public static void gamepadAdded(final int device_id, final int service_id) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
AndroidGamepadManager.gamepadAdded(device_id, service_id);
}
});
}
@WrapElementForJNI
public static void moveTaskToBack() {
if (getGeckoInterface() != null)

View File

@ -109,7 +109,9 @@ public class GeckoEvent {
PREFERENCES_REMOVE_OBSERVERS(41),
TELEMETRY_UI_SESSION_START(42),
TELEMETRY_UI_SESSION_STOP(43),
TELEMETRY_UI_EVENT(44);
TELEMETRY_UI_EVENT(44),
GAMEPAD_ADDREMOVE(45),
GAMEPAD_DATA(46);
public final int value;
@ -179,6 +181,12 @@ public class GeckoEvent {
public static final int ACTION_MAGNIFY = 12;
public static final int ACTION_MAGNIFY_END = 13;
public static final int ACTION_GAMEPAD_ADDED = 1;
public static final int ACTION_GAMEPAD_REMOVED = 2;
public static final int ACTION_GAMEPAD_BUTTON = 1;
public static final int ACTION_GAMEPAD_AXES = 2;
private final int mType;
private int mAction;
private boolean mAckNeeded;
@ -231,6 +239,12 @@ public class GeckoEvent {
private int mWidth;
private int mHeight;
private int mID;
private int mGamepadButton;
private boolean mGamepadButtonPressed;
private float mGamepadButtonValue;
private float[] mGamepadValues;
private String[] mPrefNames;
private GeckoEvent(NativeGeckoEvent event) {
@ -813,6 +827,47 @@ public class GeckoEvent {
return event;
}
public static GeckoEvent createGamepadAddRemoveEvent(int id, boolean added) {
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.GAMEPAD_ADDREMOVE);
event.mID = id;
event.mAction = added ? ACTION_GAMEPAD_ADDED : ACTION_GAMEPAD_REMOVED;
return event;
}
private static int boolArrayToBitfield(boolean[] array) {
int bits = 0;
for (int i = 0; i < array.length; i++) {
if (array[i]) {
bits |= 1<<i;
}
}
return bits;
}
public static GeckoEvent createGamepadButtonEvent(int id,
int which,
boolean pressed,
float value) {
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.GAMEPAD_DATA);
event.mID = id;
event.mAction = ACTION_GAMEPAD_BUTTON;
event.mGamepadButton = which;
event.mGamepadButtonPressed = pressed;
event.mGamepadButtonValue = value;
return event;
}
public static GeckoEvent createGamepadAxisEvent(int id, boolean[] valid,
float[] values) {
GeckoEvent event = GeckoEvent.get(NativeGeckoEvent.GAMEPAD_DATA);
event.mID = id;
event.mAction = ACTION_GAMEPAD_AXES;
event.mFlags = boolArrayToBitfield(valid);
event.mCount = values.length;
event.mGamepadValues = values;
return event;
}
public void setAckNeeded(boolean ackNeeded) {
mAckNeeded = ackNeeded;
}

View File

@ -5,6 +5,7 @@
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.AndroidGamepadManager;
import org.mozilla.gecko.GeckoAccessibility;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
@ -291,6 +292,9 @@ public class LayerView extends FrameLayout implements Tabs.OnTabsChangedListener
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if (AndroidGamepadManager.handleMotionEvent(event)) {
return true;
}
if (mPanZoomController != null && mPanZoomController.onMotionEvent(event)) {
return true;
}

View File

@ -109,6 +109,7 @@ gbjar.sources += [
'ActionModeCompatView.java',
'ActivityHandlerHelper.java',
'AlertNotification.java',
'AndroidGamepadManager.java',
'animation/AnimationUtils.java',
'animation/AnimatorProxy.java',
'animation/HeightChangeAnimation.java',

View File

@ -169,6 +169,9 @@
@BINPATH@/components/dom_xbl.xpt
@BINPATH@/components/dom_xpath.xpt
@BINPATH@/components/dom_xul.xpt
#ifdef MOZ_GAMEPAD
@BINPATH@/components/dom_gamepad.xpt
#endif
@BINPATH@/components/downloads.xpt
@BINPATH@/components/editor.xpt
@BINPATH@/components/embed_base.xpt

View File

@ -62,6 +62,11 @@ jfieldID AndroidGeckoEvent::jScreenOrientationField = 0;
jfieldID AndroidGeckoEvent::jByteBufferField = 0;
jfieldID AndroidGeckoEvent::jWidthField = 0;
jfieldID AndroidGeckoEvent::jHeightField = 0;
jfieldID AndroidGeckoEvent::jIDField = 0;
jfieldID AndroidGeckoEvent::jGamepadButtonField = 0;
jfieldID AndroidGeckoEvent::jGamepadButtonPressedField = 0;
jfieldID AndroidGeckoEvent::jGamepadButtonValueField = 0;
jfieldID AndroidGeckoEvent::jGamepadValuesField = 0;
jfieldID AndroidGeckoEvent::jPrefNamesField = 0;
jclass AndroidGeckoEvent::jDomKeyLocationClass = 0;
@ -167,6 +172,11 @@ AndroidGeckoEvent::InitGeckoEventClass(JNIEnv *jEnv)
jByteBufferField = getField("mBuffer", "Ljava/nio/ByteBuffer;");
jWidthField = getField("mWidth", "I");
jHeightField = getField("mHeight", "I");
jIDField = getField("mID", "I");
jGamepadButtonField = getField("mGamepadButton", "I");
jGamepadButtonPressedField = getField("mGamepadButtonPressed", "Z");
jGamepadButtonValueField = getField("mGamepadButtonValue", "F");
jGamepadValuesField = getField("mGamepadValues", "[F");
jPrefNamesField = getField("mPrefNames", "[Ljava/lang/String;");
// Init GeckoEvent.DomKeyLocation enum
@ -586,6 +596,26 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jobject jobj)
break;
}
case GAMEPAD_ADDREMOVE: {
mID = jenv->GetIntField(jobj, jIDField);
break;
}
case GAMEPAD_DATA: {
mID = jenv->GetIntField(jobj, jIDField);
if (mAction == ACTION_GAMEPAD_BUTTON) {
mGamepadButton = jenv->GetIntField(jobj, jGamepadButtonField);
mGamepadButtonPressed = jenv->GetBooleanField(jobj, jGamepadButtonPressedField);
mGamepadButtonValue = jenv->GetFloatField(jobj, jGamepadButtonValueField);
} else if (mAction == ACTION_GAMEPAD_AXES) {
// Flags is a bitfield of valid entries in gamepadvalues
mFlags = jenv->GetIntField(jobj, jFlagsField);
mCount = jenv->GetIntField(jobj, jCountField);
ReadFloatArray(mGamepadValues, jenv, jGamepadValuesField, mCount);
}
break;
}
case PREFERENCES_OBSERVE:
case PREFERENCES_GET: {
ReadStringArray(mPrefNames, jenv, jPrefNamesField);

View File

@ -537,6 +537,11 @@ public:
RefCountedJavaObject* ByteBuffer() { return mByteBuffer; }
int Width() { return mWidth; }
int Height() { return mHeight; }
int ID() { return mID; }
int GamepadButton() { return mGamepadButton; }
bool GamepadButtonPressed() { return mGamepadButtonPressed; }
float GamepadButtonValue() { return mGamepadButtonValue; }
const nsTArray<float>& GamepadValues() { return mGamepadValues; }
int RequestId() { return mCount; } // for convenience
WidgetTouchEvent MakeTouchEvent(nsIWidget* widget);
MultiTouchInput MakeMultiTouchInput(nsIWidget* widget);
@ -574,6 +579,11 @@ protected:
short mScreenOrientation;
nsRefPtr<RefCountedJavaObject> mByteBuffer;
int mWidth, mHeight;
int mID;
int mGamepadButton;
bool mGamepadButtonPressed;
float mGamepadButtonValue;
nsTArray<float> mGamepadValues;
nsCOMPtr<nsIObserver> mObserver;
nsTArray<nsString> mPrefNames;
@ -652,6 +662,12 @@ protected:
static jfieldID jWidthField;
static jfieldID jHeightField;
static jfieldID jIDField;
static jfieldID jGamepadButtonField;
static jfieldID jGamepadButtonPressedField;
static jfieldID jGamepadButtonValueField;
static jfieldID jGamepadValuesField;
static jclass jDomKeyLocationClass;
static jfieldID jDomKeyLocationValueField;
@ -693,6 +709,8 @@ public:
TELEMETRY_UI_SESSION_START = 42,
TELEMETRY_UI_SESSION_STOP = 43,
TELEMETRY_UI_EVENT = 44,
GAMEPAD_ADDREMOVE = 45,
GAMEPAD_DATA = 46,
dummy_java_enum_list_end
};
@ -719,6 +737,16 @@ public:
IME_ACKNOWLEDGE_FOCUS = 6,
dummy_ime_enum_list_end
};
enum {
ACTION_GAMEPAD_ADDED = 1,
ACTION_GAMEPAD_REMOVED = 2
};
enum {
ACTION_GAMEPAD_BUTTON = 1,
ACTION_GAMEPAD_AXES = 2
};
};
class nsJNIString : public nsString

View File

@ -33,6 +33,7 @@ jmethodID GeckoAppShell::jEnableLocationHighAccuracy = 0;
jmethodID GeckoAppShell::jEnableNetworkNotifications = 0;
jmethodID GeckoAppShell::jEnableScreenOrientationNotifications = 0;
jmethodID GeckoAppShell::jEnableSensor = 0;
jmethodID GeckoAppShell::jGamepadAdded = 0;
jmethodID GeckoAppShell::jGetContext = 0;
jmethodID GeckoAppShell::jGetCurrentBatteryInformationWrapper = 0;
jmethodID GeckoAppShell::jGetCurrentNetworkInformationWrapper = 0;
@ -82,6 +83,8 @@ jmethodID GeckoAppShell::jSetKeepScreenOn = 0;
jmethodID GeckoAppShell::jSetURITitle = 0;
jmethodID GeckoAppShell::jShowAlertNotificationWrapper = 0;
jmethodID GeckoAppShell::jShowInputMethodPicker = 0;
jmethodID GeckoAppShell::jStartMonitoringGamepad = 0;
jmethodID GeckoAppShell::jStopMonitoringGamepad = 0;
jmethodID GeckoAppShell::jUnlockProfile = 0;
jmethodID GeckoAppShell::jUnlockScreenOrientation = 0;
jmethodID GeckoAppShell::jUnregisterSurfaceTextureFrameListener = 0;
@ -112,6 +115,7 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
jEnableNetworkNotifications = getStaticMethod("enableNetworkNotifications", "()V");
jEnableScreenOrientationNotifications = getStaticMethod("enableScreenOrientationNotifications", "()V");
jEnableSensor = getStaticMethod("enableSensor", "(I)V");
jGamepadAdded = getStaticMethod("gamepadAdded", "(II)V");
jGetContext = getStaticMethod("getContext", "()Landroid/content/Context;");
jGetCurrentBatteryInformationWrapper = getStaticMethod("getCurrentBatteryInformation", "()[D");
jGetCurrentNetworkInformationWrapper = getStaticMethod("getCurrentNetworkInformation", "()[D");
@ -161,6 +165,8 @@ void GeckoAppShell::InitStubs(JNIEnv *jEnv) {
jSetURITitle = getStaticMethod("setUriTitle", "(Ljava/lang/String;Ljava/lang/String;)V");
jShowAlertNotificationWrapper = getStaticMethod("showAlertNotification", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
jShowInputMethodPicker = getStaticMethod("showInputMethodPicker", "()V");
jStartMonitoringGamepad = getStaticMethod("startMonitoringGamepad", "()V");
jStopMonitoringGamepad = getStaticMethod("stopMonitoringGamepad", "()V");
jUnlockProfile = getStaticMethod("unlockProfile", "()Z");
jUnlockScreenOrientation = getStaticMethod("unlockScreenOrientation", "()V");
jUnregisterSurfaceTextureFrameListener = getStaticMethod("unregisterSurfaceTextureFrameListener", "(Ljava/lang/Object;)V");
@ -460,6 +466,18 @@ void GeckoAppShell::EnableSensor(int32_t a0) {
env->PopLocalFrame(nullptr);
}
void GeckoAppShell::GamepadAdded(int32_t a0, int32_t a1) {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(0) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_CRASH("Exception should have caused crash.");
}
env->CallStaticVoidMethod(mGeckoAppShellClass, jGamepadAdded, a0, a1);
AndroidBridge::HandleUncaughtException(env);
env->PopLocalFrame(nullptr);
}
jobject GeckoAppShell::GetContext() {
JNIEnv *env = GetJNIForThread();
if (env->PushLocalFrame(1) != 0) {
@ -1139,6 +1157,30 @@ void GeckoAppShell::ShowInputMethodPicker() {
env->PopLocalFrame(nullptr);
}
void GeckoAppShell::StartMonitoringGamepad() {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(0) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_CRASH("Exception should have caused crash.");
}
env->CallStaticVoidMethod(mGeckoAppShellClass, jStartMonitoringGamepad);
AndroidBridge::HandleUncaughtException(env);
env->PopLocalFrame(nullptr);
}
void GeckoAppShell::StopMonitoringGamepad() {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(0) != 0) {
AndroidBridge::HandleUncaughtException(env);
MOZ_CRASH("Exception should have caused crash.");
}
env->CallStaticVoidMethod(mGeckoAppShellClass, jStopMonitoringGamepad);
AndroidBridge::HandleUncaughtException(env);
env->PopLocalFrame(nullptr);
}
bool GeckoAppShell::UnlockProfile() {
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (env->PushLocalFrame(0) != 0) {

View File

@ -40,6 +40,7 @@ public:
static void EnableNetworkNotifications();
static void EnableScreenOrientationNotifications();
static void EnableSensor(int32_t a0);
static void GamepadAdded(int32_t a0, int32_t a1);
static jobject GetContext();
static jdoubleArray GetCurrentBatteryInformationWrapper();
static jdoubleArray GetCurrentNetworkInformationWrapper();
@ -89,6 +90,8 @@ public:
static void SetURITitle(const nsAString& a0, const nsAString& a1);
static void ShowAlertNotificationWrapper(const nsAString& a0, const nsAString& a1, const nsAString& a2, const nsAString& a3, const nsAString& a4);
static void ShowInputMethodPicker();
static void StartMonitoringGamepad();
static void StopMonitoringGamepad();
static bool UnlockProfile();
static void UnlockScreenOrientation();
static void UnregisterSurfaceTextureFrameListener(jobject a0);
@ -118,6 +121,7 @@ protected:
static jmethodID jEnableNetworkNotifications;
static jmethodID jEnableScreenOrientationNotifications;
static jmethodID jEnableSensor;
static jmethodID jGamepadAdded;
static jmethodID jGetContext;
static jmethodID jGetCurrentBatteryInformationWrapper;
static jmethodID jGetCurrentNetworkInformationWrapper;
@ -167,6 +171,8 @@ protected:
static jmethodID jSetURITitle;
static jmethodID jShowAlertNotificationWrapper;
static jmethodID jShowInputMethodPicker;
static jmethodID jStartMonitoringGamepad;
static jmethodID jStopMonitoringGamepad;
static jmethodID jUnlockProfile;
static jmethodID jUnlockScreenOrientation;
static jmethodID jUnregisterSurfaceTextureFrameListener;

View File

@ -49,6 +49,7 @@ FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/docshell/base',
'/dom/base',
'/dom/system/android',
'/netwerk/cache',
'/widget/android/android',

View File

@ -39,6 +39,9 @@
#include <wchar.h>
#include "mozilla/dom/ScreenOrientation.h"
#ifdef MOZ_GAMEPAD
#include "mozilla/dom/GamepadService.h"
#endif
#include "GeckoProfiler.h"
#ifdef MOZ_ANDROID_HISTORY
@ -590,6 +593,49 @@ nsAppShell::ProcessNextNativeEvent(bool mayWait)
curEvent->Count());
break;
case AndroidGeckoEvent::GAMEPAD_ADDREMOVE: {
#ifdef MOZ_GAMEPAD
nsRefPtr<mozilla::dom::GamepadService> svc =
mozilla::dom::GamepadService::GetService();
if (svc) {
if (curEvent->Action() == AndroidGeckoEvent::ACTION_GAMEPAD_ADDED) {
int svc_id = svc->AddGamepad("android",
mozilla::dom::StandardMapping,
mozilla::dom::kStandardGamepadButtons,
mozilla::dom::kStandardGamepadAxes);
mozilla::widget::android::GeckoAppShell::GamepadAdded(curEvent->ID(),
svc_id);
} else if (curEvent->Action() == AndroidGeckoEvent::ACTION_GAMEPAD_REMOVED) {
svc->RemoveGamepad(curEvent->ID());
}
}
#endif
break;
}
case AndroidGeckoEvent::GAMEPAD_DATA: {
#ifdef MOZ_GAMEPAD
nsRefPtr<mozilla::dom::GamepadService> svc =
mozilla::dom::GamepadService::GetService();
if (svc) {
int id = curEvent->ID();
if (curEvent->Action() == AndroidGeckoEvent::ACTION_GAMEPAD_BUTTON) {
svc->NewButtonEvent(id, curEvent->GamepadButton(),
curEvent->GamepadButtonPressed(),
curEvent->GamepadButtonValue());
} else if (curEvent->Action() == AndroidGeckoEvent::ACTION_GAMEPAD_AXES) {
int valid = curEvent->Flags();
const nsTArray<float>& values = curEvent->GamepadValues();
for (unsigned i = 0; i < values.Length(); i++) {
if (valid & (1<<i)) {
svc->NewAxisMoveEvent(id, i, values[i]);
}
}
}
}
#endif
break;
}
case AndroidGeckoEvent::NOOP:
break;