From 1c50d129638e191b8d55cfc17d6ab59956d6b951 Mon Sep 17 00:00:00 2001 From: Aaron Culliney Date: Sat, 19 Dec 2015 13:08:48 -0800 Subject: [PATCH] Source InputManager from AOSP --- .../InputManagerCompat.java | 140 ++++++++++++ .../inputmanagercompat/InputManagerV16.java | 107 +++++++++ .../inputmanagercompat/InputManagerV9.java | 211 ++++++++++++++++++ 3 files changed, 458 insertions(+) create mode 100644 Android/app/src/main/java/com/example/inputmanagercompat/InputManagerCompat.java create mode 100644 Android/app/src/main/java/com/example/inputmanagercompat/InputManagerV16.java create mode 100644 Android/app/src/main/java/com/example/inputmanagercompat/InputManagerV9.java diff --git a/Android/app/src/main/java/com/example/inputmanagercompat/InputManagerCompat.java b/Android/app/src/main/java/com/example/inputmanagercompat/InputManagerCompat.java new file mode 100644 index 00000000..fabc8761 --- /dev/null +++ b/Android/app/src/main/java/com/example/inputmanagercompat/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 com.example.inputmanagercompat; + +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/Android/app/src/main/java/com/example/inputmanagercompat/InputManagerV16.java b/Android/app/src/main/java/com/example/inputmanagercompat/InputManagerV16.java new file mode 100644 index 00000000..d26581e6 --- /dev/null +++ b/Android/app/src/main/java/com/example/inputmanagercompat/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 com.example.inputmanagercompat; + +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 mListeners; + + public InputManagerV16(Context context) { + mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + mListeners = new HashMap(); + } + + @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/Android/app/src/main/java/com/example/inputmanagercompat/InputManagerV9.java b/Android/app/src/main/java/com/example/inputmanagercompat/InputManagerV9.java new file mode 100644 index 00000000..dcd89880 --- /dev/null +++ b/Android/app/src/main/java/com/example/inputmanagercompat/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 com.example.inputmanagercompat; + +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 mDevices; + private final Map mListeners; + private final Handler mDefaultHandler; + + private static class PollingMessageHandler extends Handler { + private final WeakReference mInputManager; + + PollingMessageHandler(InputManagerV9 im) { + mInputManager = new WeakReference(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(); + mListeners = new HashMap(); + 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 sEventQueue = new ArrayDeque(); + + 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); + } + +}