diff options
| author | hondres <liu.gam3@gmail.com> | 2016-01-24 05:11:59 +0100 | 
|---|---|---|
| committer | hondres <liu.gam3@gmail.com> | 2016-01-24 05:29:09 +0100 | 
| commit | e7c920fdbabd65a86864ec9610f895bee82f05ba (patch) | |
| tree | d97aa6da8b4ff05a1164d0bdeadff4c83b3be1cd /platform/android/java/src | |
| parent | 6c27df8df609337867c108c5adb66174393e190b (diff) | |
support gamepad remapping on android
Diffstat (limited to 'platform/android/java/src')
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); +    } + +}  |