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