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 com.example.inputmanagercompat;
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 LOG_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     }
87 
InputManagerV9()88     public InputManagerV9() {
89         mDevices = new SparseArray<long[]>();
90         mListeners = new HashMap<InputDeviceListener, Handler>();
91         mDefaultHandler = new PollingMessageHandler(this);
92         // as a side-effect, populates our collection of watched
93         // input devices
94         getInputDeviceIds();
95     }
96 
97     @Override
getInputDevice(int id)98     public InputDevice getInputDevice(int id) {
99         return InputDevice.getDevice(id);
100     }
101 
102     @Override
getInputDeviceIds()103     public int[] getInputDeviceIds() {
104         // add any hitherto unknown devices to our
105         // collection of watched input devices
106         int[] activeDevices = InputDevice.getDeviceIds();
107         long time = SystemClock.elapsedRealtime();
108         for ( int id : activeDevices ) {
109             long[] lastContact = mDevices.get(id);
110             if ( null == lastContact ) {
111                 // we have a new device
112                 mDevices.put(id, new long[] { time });
113             }
114         }
115         return activeDevices;
116     }
117 
118     @Override
registerInputDeviceListener(InputDeviceListener listener, Handler handler)119     public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
120         mListeners.remove(listener);
121         if (handler == null) {
122             handler = mDefaultHandler;
123         }
124         mListeners.put(listener, handler);
125     }
126 
127     @Override
unregisterInputDeviceListener(InputDeviceListener listener)128     public void unregisterInputDeviceListener(InputDeviceListener listener) {
129         mListeners.remove(listener);
130     }
131 
notifyListeners(int why, int deviceId)132     private void notifyListeners(int why, int deviceId) {
133         // the state of some device has changed
134         if (!mListeners.isEmpty()) {
135             // yes... this will cause an object to get created... hopefully
136             // it won't happen very often
137             for (InputDeviceListener listener : mListeners.keySet()) {
138                 Handler handler = mListeners.get(listener);
139                 DeviceEvent odc = DeviceEvent.getDeviceEvent(why, deviceId, listener);
140                 handler.post(odc);
141             }
142         }
143     }
144 
145     private static class DeviceEvent implements Runnable {
146         private int mMessageType;
147         private int mId;
148         private InputDeviceListener mListener;
149         private static Queue<DeviceEvent> sEventQueue = new ArrayDeque<DeviceEvent>();
150 
DeviceEvent()151         private DeviceEvent() {
152         }
153 
getDeviceEvent(int messageType, int id, InputDeviceListener listener)154         static DeviceEvent getDeviceEvent(int messageType, int id,
155                 InputDeviceListener listener) {
156             DeviceEvent curChanged = sEventQueue.poll();
157             if (null == curChanged) {
158                 curChanged = new DeviceEvent();
159             }
160             curChanged.mMessageType = messageType;
161             curChanged.mId = id;
162             curChanged.mListener = listener;
163             return curChanged;
164         }
165 
166         @Override
run()167         public void run() {
168             switch (mMessageType) {
169                 case ON_DEVICE_ADDED:
170                     mListener.onInputDeviceAdded(mId);
171                     break;
172                 case ON_DEVICE_CHANGED:
173                     mListener.onInputDeviceChanged(mId);
174                     break;
175                 case ON_DEVICE_REMOVED:
176                     mListener.onInputDeviceRemoved(mId);
177                     break;
178                 default:
179                     Log.e(LOG_TAG, "Unknown Message Type");
180                     break;
181             }
182             // dump this runnable back in the queue
183             sEventQueue.offer(this);
184         }
185     }
186 
187     @Override
onGenericMotionEvent(MotionEvent event)188     public void onGenericMotionEvent(MotionEvent event) {
189         // detect new devices
190         int id = event.getDeviceId();
191         long[] timeArray = mDevices.get(id);
192         if (null == timeArray) {
193             notifyListeners(ON_DEVICE_ADDED, id);
194             timeArray = new long[1];
195             mDevices.put(id, timeArray);
196         }
197         long time = SystemClock.elapsedRealtime();
198         timeArray[0] = time;
199     }
200 
201     @Override
onPause()202     public void onPause() {
203         mDefaultHandler.removeMessages(MESSAGE_TEST_FOR_DISCONNECT);
204     }
205 
206     @Override
onResume()207     public void onResume() {
208         mDefaultHandler.sendEmptyMessage(MESSAGE_TEST_FOR_DISCONNECT);
209     }
210 
211 }
212