1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.godotengine.godot.input;
18 
19 import android.os.Handler;
20 import android.os.Message;
21 import android.os.SystemClock;
22 import android.util.Log;
23 import android.util.SparseArray;
24 import android.view.InputDevice;
25 import android.view.MotionEvent;
26 
27 import java.lang.ref.WeakReference;
28 import java.util.ArrayDeque;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.Queue;
32 
33 public class InputManagerV9 implements InputManagerCompat {
34 	private static final String TAG = "InputManagerV9";
35 	private static final int MESSAGE_TEST_FOR_DISCONNECT = 101;
36 	private static final long CHECK_ELAPSED_TIME = 3000L;
37 
38 	private static final int ON_DEVICE_ADDED = 0;
39 	private static final int ON_DEVICE_CHANGED = 1;
40 	private static final int ON_DEVICE_REMOVED = 2;
41 
42 	private final SparseArray<long[]> mDevices;
43 	private final Map<InputDeviceListener, Handler> mListeners;
44 	private final Handler mDefaultHandler;
45 
46 	private static class PollingMessageHandler extends Handler {
47 		private final WeakReference<InputManagerV9> mInputManager;
48 
PollingMessageHandler(InputManagerV9 im)49 		PollingMessageHandler(InputManagerV9 im) {
50 			mInputManager = new WeakReference<InputManagerV9>(im);
51 		}
52 
53 		@Override
handleMessage(Message msg)54 		public void handleMessage(Message msg) {
55 			super.handleMessage(msg);
56 			switch (msg.what) {
57 				case MESSAGE_TEST_FOR_DISCONNECT:
58 					InputManagerV9 imv = mInputManager.get();
59 					if (null != imv) {
60 						long time = SystemClock.elapsedRealtime();
61 						int size = imv.mDevices.size();
62 						for (int i = 0; i < size; i++) {
63 							long[] lastContact = imv.mDevices.valueAt(i);
64 							if (null != lastContact) {
65 								if (time - lastContact[0] > CHECK_ELAPSED_TIME) {
66 									// check to see if the device has been
67 									// disconnected
68 									int id = imv.mDevices.keyAt(i);
69 									if (null == InputDevice.getDevice(id)) {
70 										// disconnected!
71 										imv.notifyListeners(ON_DEVICE_REMOVED, id);
72 										imv.mDevices.remove(id);
73 									} else {
74 										lastContact[0] = time;
75 									}
76 								}
77 							}
78 						}
79 						sendEmptyMessageDelayed(MESSAGE_TEST_FOR_DISCONNECT,
80 								CHECK_ELAPSED_TIME);
81 					}
82 					break;
83 			}
84 		}
85 	}
86 
InputManagerV9()87 	public InputManagerV9() {
88 		mDevices = new SparseArray<long[]>();
89 		mListeners = new HashMap<InputDeviceListener, Handler>();
90 		mDefaultHandler = new PollingMessageHandler(this);
91 		// as a side-effect, populates our collection of watched
92 		// input devices
93 		getInputDeviceIds();
94 	}
95 
96 	@Override
getInputDevice(int id)97 	public InputDevice getInputDevice(int id) {
98 		return InputDevice.getDevice(id);
99 	}
100 
101 	@Override
getInputDeviceIds()102 	public int[] getInputDeviceIds() {
103 		// add any hitherto unknown devices to our
104 		// collection of watched input devices
105 		int[] activeDevices = InputDevice.getDeviceIds();
106 		long time = SystemClock.elapsedRealtime();
107 		for (int id : activeDevices) {
108 			long[] lastContact = mDevices.get(id);
109 			if (null == lastContact) {
110 				// we have a new device
111 				mDevices.put(id, new long[] { time });
112 			}
113 		}
114 		return activeDevices;
115 	}
116 
117 	@Override
registerInputDeviceListener(InputDeviceListener listener, Handler handler)118 	public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
119 		mListeners.remove(listener);
120 		if (handler == null) {
121 			handler = mDefaultHandler;
122 		}
123 		mListeners.put(listener, handler);
124 	}
125 
126 	@Override
unregisterInputDeviceListener(InputDeviceListener listener)127 	public void unregisterInputDeviceListener(InputDeviceListener listener) {
128 		mListeners.remove(listener);
129 	}
130 
notifyListeners(int why, int deviceId)131 	private void notifyListeners(int why, int deviceId) {
132 		// the state of some device has changed
133 		if (!mListeners.isEmpty()) {
134 			// yes... this will cause an object to get created... hopefully
135 			// it won't happen very often
136 			for (InputDeviceListener listener : mListeners.keySet()) {
137 				Handler handler = mListeners.get(listener);
138 				DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId, listener);
139 				handler.post(odc);
140 			}
141 		}
142 	}
143 
144 	private static class DeviceEvent implements Runnable {
145 		private int mMessageType;
146 		private int mId;
147 		private InputDeviceListener mListener;
148 		private static Queue<DeviceEvent> sEventQueue = new ArrayDeque<DeviceEvent>();
149 
DeviceEvent()150 		private DeviceEvent() {
151 		}
152 
getDeviceEvent(int messageType, int id, InputDeviceListener listener)153 		static DeviceEvent getDeviceEvent(int messageType, int id,
154 				InputDeviceListener listener) {
155 			DeviceEvent curChanged = sEventQueue.poll();
156 			if (null == curChanged) {
157 				curChanged = new DeviceEvent();
158 			}
159 			curChanged.mMessageType = messageType;
160 			curChanged.mId = id;
161 			curChanged.mListener = listener;
162 			return curChanged;
163 		}
164 
165 		@Override
run()166 		public void run() {
167 			switch (mMessageType) {
168 				case ON_DEVICE_ADDED:
169 					mListener.onInputDeviceAdded(mId);
170 					break;
171 				case ON_DEVICE_CHANGED:
172 					mListener.onInputDeviceChanged(mId);
173 					break;
174 				case ON_DEVICE_REMOVED:
175 					mListener.onInputDeviceRemoved(mId);
176 					break;
177 				default:
178 					Log.e(TAG, "Unknown Message Type");
179 					break;
180 			}
181 			// dump this runnable back in the queue
182 			sEventQueue.offer(this);
183 		}
184 	}
185 
186 	@Override
onGenericMotionEvent(MotionEvent event)187 	public void onGenericMotionEvent(MotionEvent event) {
188 		// detect new devices
189 		int id = event.getDeviceId();
190 		long[] timeArray = mDevices.get(id);
191 		if (null == timeArray) {
192 			notifyListeners(ON_DEVICE_ADDED, id);
193 			timeArray = new long[1];
194 			mDevices.put(id, timeArray);
195 		}
196 		long time = SystemClock.elapsedRealtime();
197 		timeArray[0] = time;
198 	}
199 
200 	@Override
onPause()201 	public void onPause() {
202 		mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
203 	}
204 
205 	@Override
onResume()206 	public void onResume() {
207 		mDefaultHandler.sendEmptyMessage(MESSAGE_TEST_FOR_DISCONNECT);
208 	}
209 }
210