summaryrefslogtreecommitdiff
path: root/platform/android/java
diff options
context:
space:
mode:
authorhondres <liu.gam3@gmail.com>2016-01-24 05:11:59 +0100
committerhondres <liu.gam3@gmail.com>2016-01-24 05:29:09 +0100
commite7c920fdbabd65a86864ec9610f895bee82f05ba (patch)
treed97aa6da8b4ff05a1164d0bdeadff4c83b3be1cd /platform/android/java
parent6c27df8df609337867c108c5adb66174393e190b (diff)
support gamepad remapping on android
Diffstat (limited to 'platform/android/java')
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotLib.java2
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotView.java237
-rw-r--r--platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java140
-rw-r--r--platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java107
-rw-r--r--platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java211
5 files changed, 566 insertions, 131 deletions
diff --git a/platform/android/java/src/org/godotengine/godot/GodotLib.java b/platform/android/java/src/org/godotengine/godot/GodotLib.java
index aef6591864..7c5ac33c85 100644
--- a/platform/android/java/src/org/godotengine/godot/GodotLib.java
+++ b/platform/android/java/src/org/godotengine/godot/GodotLib.java
@@ -54,6 +54,8 @@ public class GodotLib {
public static native void key(int p_scancode, int p_unicode_char, boolean p_pressed);
public static native void joybutton(int p_device, int p_but, boolean p_pressed);
public static native void joyaxis(int p_device, int p_axis, float p_value);
+ public static native void joyhat(int p_device, int p_hat_x, int p_hat_y);
+ public static native void joyconnectionchanged(int p_device, boolean p_connected, String p_name);
public static native void focusin();
public static native void focusout();
public static native void audio();
diff --git a/platform/android/java/src/org/godotengine/godot/GodotView.java b/platform/android/java/src/org/godotengine/godot/GodotView.java
index 492eb4cb54..e210161e8b 100644
--- a/platform/android/java/src/org/godotengine/godot/GodotView.java
+++ b/platform/android/java/src/org/godotengine/godot/GodotView.java
@@ -36,14 +36,21 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.content.ContextWrapper;
import android.view.InputDevice;
+import android.hardware.input.InputManager;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.opengles.GL10;
+import org.godotengine.godot.input.InputManagerCompat;
+import org.godotengine.godot.input.InputManagerCompat.InputDeviceListener;
/**
* A simple GLSurfaceView sub-class that demonstrate how to perform
* OpenGL ES 2.0 rendering into a GL Surface. Note the following important
@@ -62,7 +69,7 @@ import javax.microedition.khronos.opengles.GL10;
* that matches it exactly (with regards to red/green/blue/alpha channels
* bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
*/
-public class GodotView extends GLSurfaceView {
+public class GodotView extends GLSurfaceView implements InputDeviceListener {
private static String TAG = "GodotView";
private static final boolean DEBUG = false;
@@ -75,6 +82,8 @@ public class GodotView extends GLSurfaceView {
private Godot activity;
+
+ private InputManagerCompat mInputManager;
public GodotView(Context context,GodotIO p_io,boolean p_use_gl2, boolean p_use_32_bits, Godot p_activity) {
super(context);
ctx=context;
@@ -88,7 +97,8 @@ public class GodotView extends GLSurfaceView {
//will only work on SDK 11+!!
setPreserveEGLContextOnPause(true);
}
-
+ mInputManager = InputManagerCompat.Factory.getInputManager(this.getContext());
+ mInputManager.registerInputDeviceListener(this, null);
init(false, 16, 0);
}
@@ -119,50 +129,112 @@ public class GodotView extends GLSurfaceView {
button = 3;
break;
case KeyEvent.KEYCODE_BUTTON_L1:
- button = 4;
+ button = 9;
break;
case KeyEvent.KEYCODE_BUTTON_L2:
- button = 6;
+ button = 15;
break;
case KeyEvent.KEYCODE_BUTTON_R1:
- button = 5;
+ button = 10;
break;
case KeyEvent.KEYCODE_BUTTON_R2:
- button = 7;
+ button = 16;
break;
case KeyEvent.KEYCODE_BUTTON_SELECT:
- button = 10;
+ button = 4;
break;
case KeyEvent.KEYCODE_BUTTON_START:
- button = 11;
+ button = 6;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBL:
- button = 8;
+ button = 7;
break;
case KeyEvent.KEYCODE_BUTTON_THUMBR:
- button = 9;
+ button = 8;
break;
case KeyEvent.KEYCODE_DPAD_UP:
- button = 12;
+ button = 11;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
- button = 13;
+ button = 12;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
- button = 14;
+ button = 13;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
- button = 15;
+ button = 14;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_C:
+ button = 17;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_Z:
+ button = 18;
break;
default:
- button = keyCode - KeyEvent.KEYCODE_BUTTON_1;
+ button = keyCode - KeyEvent.KEYCODE_BUTTON_1 + 20;
break;
};
-
return button;
};
+ private static class joystick {
+ public int device_id;
+ public String name;
+ public ArrayList<InputDevice.MotionRange> axes;
+ public ArrayList<InputDevice.MotionRange> hats;
+ }
+
+ private static class RangeComparator implements Comparator<InputDevice.MotionRange> {
+ @Override
+ public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
+ return arg0.getAxis() - arg1.getAxis();
+ }
+ }
+
+ ArrayList<joystick> joy_devices = new ArrayList<joystick>();
+
+ private int find_joy_device(int device_id) {
+ for (int i=0; i<joy_devices.size(); i++) {
+ if (joy_devices.get(i).device_id == device_id) {
+ return i;
+ }
+ }
+ onInputDeviceAdded(device_id);
+ return joy_devices.size() - 1;
+ }
+
+ @Override public void onInputDeviceAdded(int deviceId) {
+ joystick joy = new joystick();
+ joy.device_id = deviceId;
+ int id = joy_devices.size();
+ InputDevice device = mInputManager.getInputDevice(deviceId);
+ joy.name = device.getName();
+ joy.axes = new ArrayList<InputDevice.MotionRange>();
+ joy.hats = new ArrayList<InputDevice.MotionRange>();
+ List<InputDevice.MotionRange> ranges = device.getMotionRanges();
+ Collections.sort(ranges, new RangeComparator());
+ for (InputDevice.MotionRange range : ranges) {
+ if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
+ joy.hats.add(range);
+ }
+ else {
+ joy.axes.add(range);
+ }
+ }
+ joy_devices.add(joy);
+ GodotLib.joyconnectionchanged(id, true, joy.name);
+ }
+
+ @Override public void onInputDeviceRemoved(int deviceId) {
+ int id = find_joy_device(deviceId);
+ joy_devices.remove(id);
+ GodotLib.joyconnectionchanged(id, false, "");
+ }
+
+ @Override public void onInputDeviceChanged(int deviceId) {
+
+ }
@Override public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
@@ -177,7 +249,7 @@ public class GodotView extends GLSurfaceView {
if ((source & InputDevice.SOURCE_JOYSTICK) != 0 || (source & InputDevice.SOURCE_DPAD) != 0 || (source & InputDevice.SOURCE_GAMEPAD) != 0) {
int button = get_godot_button(keyCode);
- int device = event.getDeviceId();
+ int device = find_joy_device(event.getDeviceId());
GodotLib.joybutton(device, button, false);
return true;
@@ -209,7 +281,8 @@ public class GodotView extends GLSurfaceView {
if (event.getRepeatCount() > 0) // ignore key echo
return true;
int button = get_godot_button(keyCode);
- int device = event.getDeviceId();
+ int device = find_joy_device(event.getDeviceId());
+
//Log.e(TAG, String.format("joy button down! button %x, %d, device %d", keyCode, button, device));
GodotLib.joybutton(device, button, true);
@@ -221,125 +294,27 @@ public class GodotView extends GLSurfaceView {
return super.onKeyDown(keyCode, event);
}
- public float axis_value(MotionEvent p_event, InputDevice p_device, int p_axis, int p_pos) {
-
- final InputDevice.MotionRange range = p_device.getMotionRange(p_axis, p_event.getSource());
- if (range == null)
- return 0;
-
- //Log.e(TAG, String.format("axis ranges %f, %f, %f", range.getRange(), range.getMin(), range.getMax()));
-
- final float flat = range.getFlat();
- final float value =
- p_pos < 0 ? p_event.getAxisValue(p_axis):
- p_event.getHistoricalAxisValue(p_axis, p_pos);
-
- final float absval = Math.abs(value);
- if (absval <= flat) {
- return 0;
- };
-
- final float ret = (value - range.getMin()) / range.getRange() * 2 - 1.0f;
-
- return ret;
- };
-
- float[] last_axis_values = { 0, 0, 0, 0, -1, -1 };
- boolean[] last_axis_buttons = { false, false, false, false, false, false }; // dpad up down left right, ltrigger, rtrigger
-
- public void process_axis_state(MotionEvent p_event, int p_pos) {
-
- int device_id = p_event.getDeviceId();
- InputDevice device = p_event.getDevice();
- float val;
-
- val = axis_value(p_event, device, MotionEvent.AXIS_X, p_pos);
- if (val != last_axis_values[0]) {
- last_axis_values[0] = val;
- //Log.e(TAG, String.format("axis moved! axis %d, value %f", 0, val));
- GodotLib.joyaxis(device_id, 0, val);
- };
-
- val = axis_value(p_event, device, MotionEvent.AXIS_Y, p_pos);
- if (val != last_axis_values[1]) {
- last_axis_values[1] = val;
- //Log.e(TAG, String.format("axis moved! axis %d, value %f", 1, val));
- GodotLib.joyaxis(device_id, 1, val);
- };
-
- val = axis_value(p_event, device, MotionEvent.AXIS_Z, p_pos);
- if (val != last_axis_values[2]) {
- last_axis_values[2] = val;
- //Log.e(TAG, String.format("axis moved! axis %d, value %f", 2, val));
- GodotLib.joyaxis(device_id, 2, val);
- };
-
- val = axis_value(p_event, device, MotionEvent.AXIS_RZ, p_pos);
- if (val != last_axis_values[3]) {
- last_axis_values[3] = val;
- //Log.e(TAG, String.format("axis moved! axis %d, value %f", 3, val));
- GodotLib.joyaxis(device_id, 3, val);
- };
-
- val = axis_value(p_event, device, MotionEvent.AXIS_LTRIGGER, p_pos);
- if (val != last_axis_values[4]) {
- last_axis_values[4] = val;
- if ((val != 0) != (last_axis_buttons[4])) {
- last_axis_buttons[4] = (val != 0);
- GodotLib.joybutton(device_id, 6, (val != 0));
- };
- };
-
- val = axis_value(p_event, device, MotionEvent.AXIS_RTRIGGER, p_pos);
- if (val != last_axis_values[5]) {
- last_axis_values[5] = val;
- if ((val != 0) != (last_axis_buttons[5])) {
- last_axis_buttons[5] = (val != 0);
- GodotLib.joybutton(device_id, 7, (val != 0));
- };
- };
-
- val = axis_value(p_event, device, MotionEvent.AXIS_HAT_Y, p_pos);
-
- if (last_axis_buttons[0] != (val > 0)) {
- last_axis_buttons[0] = val > 0;
- GodotLib.joybutton(device_id, 12, val > 0);
- };
- if (last_axis_buttons[1] != (val < 0)) {
- last_axis_buttons[1] = val < 0;
- GodotLib.joybutton(device_id, 13, val > 0);
- };
-
- val = axis_value(p_event, device, MotionEvent.AXIS_HAT_X, p_pos);
- if (last_axis_buttons[2] != (val < 0)) {
- last_axis_buttons[2] = val < 0;
- GodotLib.joybutton(device_id, 14, val < 0);
- };
- if (last_axis_buttons[3] != (val > 0)) {
- last_axis_buttons[3] = val > 0;
- GodotLib.joybutton(device_id, 15, val > 0);
- };
- };
-
@Override public boolean onGenericMotionEvent(MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
- // Process all historical movement samples in the batch
- final int historySize = event.getHistorySize();
+ int device_id = find_joy_device(event.getDeviceId());
+ joystick joy = joy_devices.get(device_id);
- // Process the movements starting from the
- // earliest historical position in the batch
- for (int i = 0; i < historySize; i++) {
- // Process the event at historical position i
- process_axis_state(event, i);
+ for (int i = 0; i < joy.axes.size(); i++) {
+ InputDevice.MotionRange range = joy.axes.get(i);
+ float value = (event.getAxisValue(range.getAxis()) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
+ //Log.e(TAG, String.format("axis event: %d, value %f", i, value));
+ GodotLib.joyaxis(device_id, i, value);
}
- // Process the current movement sample in the batch (position -1)
- process_axis_state(event, -1);
+ for (int i = 0; i < joy.hats.size(); i+=2) {
+ int hatX = Math.round(event.getAxisValue(joy.hats.get(i).getAxis()));
+ int hatY = Math.round(event.getAxisValue(joy.hats.get(i+1).getAxis()));
+ //Log.e(TAG, String.format("HAT EVENT %d, %d", hatX, hatY));
+ GodotLib.joyhat(device_id, hatX, hatY);
+ }
return true;
-
-
};
return super.onGenericMotionEvent(event);
@@ -413,12 +388,12 @@ public class GodotView extends GLSurfaceView {
/* Fallback if 32bit View is not supported*/
private static class FallbackConfigChooser extends ConfigChooser {
private ConfigChooser fallback;
-
+
public FallbackConfigChooser(int r, int g, int b, int a, int depth, int stencil, ConfigChooser fallback) {
super(r, g, b, a, depth, stencil);
this.fallback = fallback;
}
-
+
@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
EGLConfig ec = super.chooseConfig(egl, display, configs);
diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java b/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java
new file mode 100644
index 0000000000..4615d2fbb5
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/input/InputManagerCompat.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.godotengine.godot.input;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+
+public interface InputManagerCompat {
+ /**
+ * Gets information about the input device with the specified id.
+ *
+ * @param id The device id
+ * @return The input device or null if not found
+ */
+ public InputDevice getInputDevice(int id);
+
+ /**
+ * Gets the ids of all input devices in the system.
+ *
+ * @return The input device ids.
+ */
+ public int[] getInputDeviceIds();
+
+ /**
+ * Registers an input device listener to receive notifications about when
+ * input devices are added, removed or changed.
+ *
+ * @param listener The listener to register.
+ * @param handler The handler on which the listener should be invoked, or
+ * null if the listener should be invoked on the calling thread's
+ * looper.
+ */
+ public void registerInputDeviceListener(InputManagerCompat.InputDeviceListener listener,
+ Handler handler);
+
+ /**
+ * Unregisters an input device listener.
+ *
+ * @param listener The listener to unregister.
+ */
+ public void unregisterInputDeviceListener(InputManagerCompat.InputDeviceListener listener);
+
+ /*
+ * The following three calls are to simulate V16 behavior on pre-Jellybean
+ * devices. If you don't call them, your callback will never be called
+ * pre-API 16.
+ */
+
+ /**
+ * Pass the motion events to the InputManagerCompat. This is used to
+ * optimize for polling for controllers. If you do not pass these events in,
+ * polling will cause regular object creation.
+ *
+ * @param event the motion event from the app
+ */
+ public void onGenericMotionEvent(MotionEvent event);
+
+ /**
+ * Tell the V9 input manager that it should stop polling for disconnected
+ * devices. You can call this during onPause in your activity, although you
+ * might want to call it whenever your game is not active (or whenever you
+ * don't care about being notified of new input devices)
+ */
+ public void onPause();
+
+ /**
+ * Tell the V9 input manager that it should start polling for disconnected
+ * devices. You can call this during onResume in your activity, although you
+ * might want to call it less often (only when the gameplay is actually
+ * active)
+ */
+ public void onResume();
+
+ public interface InputDeviceListener {
+ /**
+ * Called whenever the input manager detects that a device has been
+ * added. This will only be called in the V9 version when a motion event
+ * is detected.
+ *
+ * @param deviceId The id of the input device that was added.
+ */
+ void onInputDeviceAdded(int deviceId);
+
+ /**
+ * Called whenever the properties of an input device have changed since
+ * they were last queried. This will not be called for the V9 version of
+ * the API.
+ *
+ * @param deviceId The id of the input device that changed.
+ */
+ void onInputDeviceChanged(int deviceId);
+
+ /**
+ * Called whenever the input manager detects that a device has been
+ * removed. For the V9 version, this can take some time depending on the
+ * poll rate.
+ *
+ * @param deviceId The id of the input device that was removed.
+ */
+ void onInputDeviceRemoved(int deviceId);
+ }
+
+ /**
+ * Use this to construct a compatible InputManager.
+ */
+ public static class Factory {
+
+ /**
+ * Constructs and returns a compatible InputManger
+ *
+ * @param context the Context that will be used to get the system
+ * service from
+ * @return a compatible implementation of InputManager
+ */
+ public static InputManagerCompat getInputManager(Context context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ return new InputManagerV16(context);
+ } else {
+ return new InputManagerV9();
+ }
+ }
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java b/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java
new file mode 100644
index 0000000000..f05701f455
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/input/InputManagerV16.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.godotengine.godot.input;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Build;
+import android.os.Handler;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+public class InputManagerV16 implements InputManagerCompat {
+
+ private final InputManager mInputManager;
+ private final Map<InputManagerCompat.InputDeviceListener, V16InputDeviceListener> mListeners;
+
+ public InputManagerV16(Context context) {
+ mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
+ mListeners = new HashMap<InputManagerCompat.InputDeviceListener, V16InputDeviceListener>();
+ }
+
+ @Override
+ public InputDevice getInputDevice(int id) {
+ return mInputManager.getInputDevice(id);
+ }
+
+ @Override
+ public int[] getInputDeviceIds() {
+ return mInputManager.getInputDeviceIds();
+ }
+
+ static class V16InputDeviceListener implements InputManager.InputDeviceListener {
+ final InputManagerCompat.InputDeviceListener mIDL;
+
+ public V16InputDeviceListener(InputDeviceListener idl) {
+ mIDL = idl;
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ mIDL.onInputDeviceAdded(deviceId);
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ mIDL.onInputDeviceChanged(deviceId);
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ mIDL.onInputDeviceRemoved(deviceId);
+ }
+
+ }
+
+ @Override
+ public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+ V16InputDeviceListener v16Listener = new V16InputDeviceListener(listener);
+ mInputManager.registerInputDeviceListener(v16Listener, handler);
+ mListeners.put(listener, v16Listener);
+ }
+
+ @Override
+ public void unregisterInputDeviceListener(InputDeviceListener listener) {
+ V16InputDeviceListener curListener = mListeners.remove(listener);
+ if (null != curListener)
+ {
+ mInputManager.unregisterInputDeviceListener(curListener);
+ }
+
+ }
+
+ @Override
+ public void onGenericMotionEvent(MotionEvent event) {
+ // unused in V16
+ }
+
+ @Override
+ public void onPause() {
+ // unused in V16
+ }
+
+ @Override
+ public void onResume() {
+ // unused in V16
+ }
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java b/platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java
new file mode 100644
index 0000000000..0334c00997
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/input/InputManagerV9.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.godotengine.godot.input;
+
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayDeque;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Queue;
+
+public class InputManagerV9 implements InputManagerCompat {
+ private static final String LOG_TAG = "InputManagerV9";
+ private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
+ private static final long CHECK_ELAPSED_TIME = 3000L;
+
+ private static final int ON_DEVICE_ADDED = 0;
+ private static final int ON_DEVICE_CHANGED = 1;
+ private static final int ON_DEVICE_REMOVED = 2;
+
+ private final SparseArray<long[]> mDevices;
+ private final Map<InputDeviceListener, Handler> mListeners;
+ private final Handler mDefaultHandler;
+
+ private static class PollingMessageHandler extends Handler {
+ private final WeakReference<InputManagerV9> mInputManager;
+
+ PollingMessageHandler(InputManagerV9 im) {
+ mInputManager = new WeakReference<InputManagerV9>(im);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ super.handleMessage(msg);
+ switch (msg.what) {
+ case MESSAGE_TEST_FOR_DISCONNECT:
+ InputManagerV9 imv = mInputManager.get();
+ if (null != imv) {
+ long time = SystemClock.elapsedRealtime();
+ int size = imv.mDevices.size();
+ for (int i = 0; i < size; i++) {
+ long[] lastContact = imv.mDevices.valueAt(i);
+ if (null != lastContact) {
+ if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
+ // check to see if the device has been
+ // disconnected
+ int id = imv.mDevices.keyAt(i);
+ if (null == InputDevice.getDevice(id)) {
+ // disconnected!
+ imv.notifyListeners(ON_DEVICE_REMOVED, id);
+ imv.mDevices.remove(id);
+ } else {
+ lastContact[0] = time;
+ }
+ }
+ }
+ }
+ sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
+ CHECK_ELAPSED_TIME);
+ }
+ break;
+ }
+ }
+
+ }
+
+ public InputManagerV9() {
+ mDevices = new SparseArray<long[]>();
+ mListeners = new HashMap<InputDeviceListener, Handler>();
+ mDefaultHandler = new PollingMessageHandler(this);
+ // as a side-effect, populates our collection of watched
+ // input devices
+ getInputDeviceIds();
+ }
+
+ @Override
+ public InputDevice getInputDevice(int id) {
+ return InputDevice.getDevice(id);
+ }
+
+ @Override
+ public int[] getInputDeviceIds() {
+ // add any hitherto unknown devices to our
+ // collection of watched input devices
+ int[] activeDevices = InputDevice.getDeviceIds();
+ long time = SystemClock.elapsedRealtime();
+ for ( int id : activeDevices ) {
+ long[] lastContact = mDevices.get(id);
+ if ( null == lastContact ) {
+ // we have a new device
+ mDevices.put(id, new long[] { time });
+ }
+ }
+ return activeDevices;
+ }
+
+ @Override
+ public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+ mListeners.remove(listener);
+ if (handler == null) {
+ handler = mDefaultHandler;
+ }
+ mListeners.put(listener, handler);
+ }
+
+ @Override
+ public void unregisterInputDeviceListener(InputDeviceListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private void notifyListeners(int why, int deviceId) {
+ // the state of some device has changed
+ if (!mListeners.isEmpty()) {
+ // yes... this will cause an object to get created... hopefully
+ // it won't happen very often
+ for (InputDeviceListener listener : mListeners.keySet()) {
+ Handler handler = mListeners.get(listener);
+ DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId, listener);
+ handler.post(odc);
+ }
+ }
+ }
+
+ private static class DeviceEvent implements Runnable {
+ private int mMessageType;
+ private int mId;
+ private InputDeviceListener mListener;
+ private static Queue<DeviceEvent> sEventQueue = new ArrayDeque<DeviceEvent>();
+
+ private DeviceEvent() {
+ }
+
+ static DeviceEvent getDeviceEvent(int messageType, int id,
+ InputDeviceListener listener) {
+ DeviceEvent curChanged = sEventQueue.poll();
+ if (null == curChanged) {
+ curChanged = new DeviceEvent();
+ }
+ curChanged.mMessageType = messageType;
+ curChanged.mId = id;
+ curChanged.mListener = listener;
+ return curChanged;
+ }
+
+ @Override
+ public void run() {
+ switch (mMessageType) {
+ case ON_DEVICE_ADDED:
+ mListener.onInputDeviceAdded(mId);
+ break;
+ case ON_DEVICE_CHANGED:
+ mListener.onInputDeviceChanged(mId);
+ break;
+ case ON_DEVICE_REMOVED:
+ mListener.onInputDeviceRemoved(mId);
+ break;
+ default:
+ Log.e(LOG_TAG, "Unknown Message Type");
+ break;
+ }
+ // dump this runnable back in the queue
+ sEventQueue.offer(this);
+ }
+ }
+
+ @Override
+ public void onGenericMotionEvent(MotionEvent event) {
+ // detect new devices
+ int id = event.getDeviceId();
+ long[] timeArray = mDevices.get(id);
+ if (null == timeArray) {
+ notifyListeners(ON_DEVICE_ADDED, id);
+ timeArray = new long[1];
+ mDevices.put(id, timeArray);
+ }
+ long time = SystemClock.elapsedRealtime();
+ timeArray[0] = time;
+ }
+
+ @Override
+ public void onPause() {
+ mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
+ }
+
+ @Override
+ public void onResume() {
+ mDefaultHandler.sendEmptyMessage(MESSAGE_TEST_FOR_DISCONNECT);
+ }
+
+}