1 package org.scummvm.scummvm;
2 
3 import android.content.Context;
4 import android.os.Handler;
5 import android.os.Message;
6 import android.util.Log;
7 import android.view.MotionEvent;
8 import android.view.InputDevice;
9 
10 import androidx.annotation.NonNull;
11 
12 import java.lang.ref.WeakReference;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.Comparator;
16 import java.util.List;
17 
18 // A class that extends the basic ScummVMEventsBase, supporting Android APIs > HONEYCOMB_MR1 (API 12)
19 public class ScummVMEventsModern extends ScummVMEventsBase {
20 
21 	private static final int MSG_REPEAT = 3;
22 	private static final int REPEAT_INTERVAL = 20; // ~50 keys per second
23 	private static final int REPEAT_START_DELAY = 40;
24 
ScummVMEventsModern(Context context, ScummVM scummvm, MouseHelper mouseHelper)25 	public ScummVMEventsModern(Context context, ScummVM scummvm, MouseHelper mouseHelper) {
26 		super(context, scummvm, mouseHelper);
27 	}
28 
29 	// Custom handler code (to avoid mem leaks, see warning "This Handler Class Should Be Static Or Leaks Might Occur”) based on:
30 	// https://stackoverflow.com/a/27826094
31 	public static class ScummVMEventsModernHandler extends Handler {
32 
33 		private final WeakReference<ScummVMEventsModern> mListenerReference;
34 
ScummVMEventsModernHandler(ScummVMEventsModern listener)35 		public ScummVMEventsModernHandler(ScummVMEventsModern listener) {
36 			mListenerReference = new WeakReference<>(listener);
37 		}
38 
39 		@Override
handleMessage(@onNull Message msg)40 		public synchronized void handleMessage(@NonNull Message msg) {
41 			ScummVMEventsModern listener = mListenerReference.get();
42 			if(listener != null) {
43 				switch (msg.what) {
44 					case MSG_REPEAT:
45 						if (listener.repeatMove()) {
46 							Message repeat = Message.obtain(this, MSG_REPEAT);
47 							sendMessageDelayed(repeat, REPEAT_INTERVAL);
48 						}
49 						break;
50 				}
51 			}
52 		}
53 
clear()54 		public void clear() {
55 			this.removeCallbacksAndMessages(null);
56 		}
57 	}
58 
59 	@Override
clearEventHandler()60 	public void clearEventHandler() {
61 		super.clearEventHandler();
62 		mHandler.clear();
63 	}
64 
65 	private ScummVMEventsModernHandler mHandler = new ScummVMEventsModernHandler(this);
66 	private float repeatingX = 0.0f;
67 	private float repeatingY = 0.0f;
68 
getCenteredAxis(MotionEvent event, InputDevice device, int axis, int historyPos)69 	private static float getCenteredAxis(MotionEvent event, InputDevice device, int axis, int historyPos) {
70 		final InputDevice.MotionRange range = device.getMotionRange(axis, event.getSource());
71 		final int actionPointerIndex = event.getActionIndex();
72 
73 		// A joystick at rest does not always report an absolute position of
74 		// (0,0). Use the getFlat() method to determine the range of values
75 		// bounding the joystick axis center.
76 		if (range != null) {
77 			final float flat = range.getFlat();
78 
79 //			if (axis == MotionEvent.AXIS_X
80 //				|| axis == MotionEvent.AXIS_HAT_X
81 //				|| axis == MotionEvent.AXIS_Z) {
82 //				Log.d(ScummVM.LOG_TAG, "Flat X= " + flat);
83 //			} else {
84 //				Log.d(ScummVM.LOG_TAG, "Flat Y= " + flat);
85 //			}
86 
87 			float axisVal = (historyPos < 0) ? event.getAxisValue( range.getAxis(), actionPointerIndex) : event.getHistoricalAxisValue( range.getAxis(), actionPointerIndex, historyPos);
88 			// Normalize
89 			final float value =  (axisVal - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
90 
91 			// Ignore axis values that are within the 'flat' region of the
92 			// joystick axis center.
93 			if (Math.abs(value) > flat) {
94 				return value;
95 			}
96 		}
97 		return 0;
98 	}
99 
removeMessages()100 	private void removeMessages() {
101 		if (mHandler != null) {
102 			mHandler.removeMessages(MSG_REPEAT);
103 		}
104 	}
105 
repeatMove()106 	private boolean repeatMove() {
107 		_scummvm.pushEvent(JE_JOYSTICK, MotionEvent.ACTION_MOVE,
108 			(int) (repeatingX * 100),
109 			(int) (repeatingY * 100),
110 			0, 0, 0);
111 		return true;
112 	}
113 
processJoystickInput(MotionEvent event, int historyPos)114 	private void processJoystickInput(MotionEvent event, int historyPos) {
115 
116 		InputDevice inputDevice = event.getDevice();
117 
118 		// Calculate the horizontal distance to move by
119 		// using the input value from one of these physical controls:
120 		// the left control stick, hat axis, or the right control stick.
121 		float x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_X, historyPos);
122 		//Log.d(ScummVM.LOG_TAG, "JOYSTICK - LEFT: x= " +x);
123 		if (x == 0) {
124 			x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_X, historyPos);
125 			//Log.d(ScummVM.LOG_TAG, "JOYSTICK - HAT: x= " +x);
126 		}
127 		if (x == 0) {
128 			x = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Z, historyPos);
129 			//Log.d(ScummVM.LOG_TAG, "JOYSTICK - RIGHT: x= " +x);
130 		}
131 
132 		// Calculate the vertical distance to move by
133 		// using the input value from one of these physical controls:
134 		// the left control stick, hat switch, or the right control stick.
135 		float y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_Y, historyPos);
136 		//Log.d(ScummVM.LOG_TAG, "JOYSTICK - LEFT: y= " +y);
137 		if (y == 0) {
138 			y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_HAT_Y, historyPos);
139 			//Log.d(ScummVM.LOG_TAG, "JOYSTICK - HAT: y= " +y);
140 		}
141 		if (y == 0) {
142 			y = getCenteredAxis(event, inputDevice, MotionEvent.AXIS_RZ, historyPos);
143 			//Log.d(ScummVM.LOG_TAG, "JOYSTICK - RIGHT: y= " +y);
144 		}
145 
146 		// extra filter to stop repetition in order to avoid cases when android does not send onGenericMotionEvent()
147 		// for small x or y (while abs is still larger than range.getflat())
148 		// In such case we would end up with a slow moving "mouse" cursor - so we need this extra filter
149 		if (Math.abs(x * 100) < 20.0f && Math.abs(y * 100) < 20.0f) {
150 			//Log.d(ScummVM.LOG_TAG, "JOYSTICK - pushEvent(): STOPPED: " + (int)(x * 100) + " y= " + (int)(y * 100));
151 			removeMessages();
152 			// do the move anyway, just don't repeat
153 			repeatMove();
154 			repeatingX = 0.0f;
155 			repeatingY = 0.0f;
156 		} else {
157 			//Log.d(ScummVM.LOG_TAG, "JOYSTICK - pushEvent(): x= " + (int)(x * 100) + " y= " + (int)(y * 100));
158 			if (repeatingX != 0.0f || repeatingY != 0.0f) {
159 				// already repeating - just update the movement co-ords
160 				repeatingX = x;
161 				repeatingY = y;
162 			} else {
163 				// start repeating
164 				//removeMessages();
165 				repeatingX = x;
166 				repeatingY = y;
167 				Message msg = mHandler.obtainMessage(MSG_REPEAT);
168 				mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY);
169 				repeatMove();
170 			}
171 		}
172 	}
173 
174 	@Override
onGenericMotionEvent(MotionEvent event)175 	public boolean onGenericMotionEvent(MotionEvent event) {
176 		// Check that the event came from a joystick
177 		if (((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
178 			 || (event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0)) {
179 			int action = event.getActionMasked();
180 			if (action == MotionEvent.ACTION_MOVE) {
181 
182 				// Process all historical movement samples in the batch
183 				final int historySize = event.getHistorySize();
184 
185 				// Process the movements starting from the
186 				// earliest historical position in the batch
187 				for (int i = 0; i < historySize; i++) {
188 					// Process the event at historical position i
189 					//Log.d(ScummVM.LOG_TAG, "JOYSTICK - onGenericMotionEvent(m) hist: ");
190 					processJoystickInput(event, i);
191 				}
192 
193 				// Process the current movement sample in the batch (position -1)
194 				//Log.d(ScummVM.LOG_TAG, "JOYSTICK - onGenericMotionEvent(m): "  );
195 				processJoystickInput(event, -1);
196 				return true;
197 			}
198 		}
199 		// this basically returns false since the super just returns false
200 		return super.onGenericMotionEvent(event);
201 	}
202 }
203