Android Open Source - p1keyboard Wiimote Reader






From Project

Back to project page p1keyboard.

License

The source code is released under:

GNU Lesser General Public License

If you think the Android project p1keyboard listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/* Copyright (C) 2011, Kenneth Skovhede
 * http://www.hexad.dk, opensource@hexad.dk
 * //w  w  w  .ja v a 2s .  c  om
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
package mobi.omegacentauri.p1keyboard;

import java.io.IOException;
import java.util.Hashtable;

import mobi.omegacentauri.p1keyboard.R;

import android.content.Context;
import android.util.Log;
import android.view.KeyEvent;

public class WiimoteReader extends HIDReaderBase {

  private static final boolean D = false; //General debug info
  private static final boolean D2 = false; //Detailed (packages) debug info
  private static final boolean D3 = false; //Classic Controller debug info
  
  public static final String DRIVER_NAME = "wiimote";
  public static final String DISPLAY_NAME = "Wiimote (HID)";

  //These are from API level 9
  public static final int KEYCODE_BUTTON_A = 0x60;
  public static final int KEYCODE_BUTTON_B = 0x61;
  public static final int KEYCODE_BUTTON_X = 0x63;
  public static final int KEYCODE_BUTTON_Y = 0x64;
  public static final int KEYCODE_BUTTON_L1 = 0x66;
  public static final int KEYCODE_BUTTON_R1 = 0x67;
  public static final int KEYCODE_BUTTON_L2 = 0x68;
  public static final int KEYCODE_BUTTON_R2 = 0x69;
  public static final int KEYCODE_BUTTON_START = 0x6c; 
  public static final int KEYCODE_BUTTON_SELECT = 0x6d; 
  
  //Value for keys we do not care about
  public static final int KEYCODE_UNUSED = 0x0;

  //Accelerometer axis (x,y,z) is reported as accelerometer (0,1,2)
  private static final int CORE_ACCELEROMETER_AXIS_OFFSET = 0;

  //Nunchuck accelerometer axis (x,y,z) is reported as accelerometer (3,4,5)
  private static final int NUNCHUCK_ACCELEROMETER_AXIS_OFFSET = 3;

  //The max value an analog control can have can report
  private static int ANALOG_MAX_VALUE = 127;
  //How large the analog value must be for it to issue an emulated keypress
  private static int ANALOG_THRESHOLD = ANALOG_MAX_VALUE / 2;
  
  //The number of messages to read before probing for extensions
  private static int EXTENSION_PROBETHRESHOLD = 15;

  //Mappings from bit-index to keycode for core buttons
  private static final int[] CORE_KEYS = new int[] {
    KeyEvent.KEYCODE_2,       //Byte 1, bit 0
    KeyEvent.KEYCODE_1,        //Byte 1, bit 1
    KeyEvent.KEYCODE_B,        //Byte 1, bit 2
    KeyEvent.KEYCODE_A,        //Byte 1, bit 3
    KeyEvent.KEYCODE_MINUS,      //Byte 1, bit 4
    KEYCODE_UNUSED,         //Byte 1, bit 5
    KEYCODE_UNUSED,         //Byte 1, bit 6
    KeyEvent.KEYCODE_H,        //Byte 1, bit 7
    KeyEvent.KEYCODE_DPAD_LEFT,   //Byte 0, bit 0
    KeyEvent.KEYCODE_DPAD_RIGHT,   //Byte 0, bit 1
    KeyEvent.KEYCODE_DPAD_DOWN,   //Byte 0, bit 2
    KeyEvent.KEYCODE_DPAD_UP,     //Byte 0, bit 3
    KeyEvent.KEYCODE_PLUS,       //Byte 0, bit 4
    KEYCODE_UNUSED,         //Byte 0, bit 5
    KEYCODE_UNUSED,         //Byte 0, bit 6
    KEYCODE_UNUSED,         //Byte 0, bit 7
  };

  //Mappings from bit-index to keycode for classic keys
  private static final int[] CLASSIC_KEYS = new int[] {
    KEYCODE_UNUSED,         //Byte 4, bit 0
    KEYCODE_BUTTON_R2,        //Byte 4, bit 1
    KeyEvent.KEYCODE_PLUS,      //Byte 4, bit 2
    KeyEvent.KEYCODE_H,        //Byte 4, bit 3
    KeyEvent.KEYCODE_MINUS,      //Byte 4, bit 4
    KEYCODE_BUTTON_L2,         //Byte 4, bit 5
    KeyEvent.KEYCODE_DPAD_DOWN,   //Byte 4, bit 6
    KeyEvent.KEYCODE_DPAD_RIGHT,  //Byte 4, bit 7
    KeyEvent.KEYCODE_DPAD_UP,     //Byte 5, bit 0
    KeyEvent.KEYCODE_DPAD_LEFT,   //Byte 5, bit 1
    KEYCODE_BUTTON_R1,         //Byte 5, bit 2
    KEYCODE_BUTTON_X,         //Byte 5, bit 3
    KEYCODE_BUTTON_A,         //Byte 5, bit 4
    KEYCODE_BUTTON_Y,         //Byte 5, bit 5
    KEYCODE_BUTTON_B,         //Byte 5, bit 6
    KEYCODE_BUTTON_L1,         //Byte 5, bit 7
  };
  
  //Mappings for Nunchuck buttons
  private static final int[] NUNCHUCK_KEYS = new int[] {
    KeyEvent.KEYCODE_C,    //C Button
    KeyEvent.KEYCODE_Z,    //Z Button
  };

  //Emulated keypress values for core accelerometer
  private static final int[] CORE_ACCELEROMETER_KEYS = new int[] {
    KeyEvent.KEYCODE_N,       //Accelerometer X up
    KeyEvent.KEYCODE_M,       //Accelerometer X down
    KeyEvent.KEYCODE_J,        //Accelerometer Y up
    KeyEvent.KEYCODE_K,        //Accelerometer Y down
    KeyEvent.KEYCODE_I,        //Accelerometer Z up
    KeyEvent.KEYCODE_O,        //Accelerometer Z down
  };

  //Emulated keypress values for classic thumbsticks
  private static final int[] CLASSIC_ANALOG_KEYS = new int[] {
    KeyEvent.KEYCODE_Q,     //Classic, Left stick left
    KeyEvent.KEYCODE_D,     //Classic, Left stick right
    KeyEvent.KEYCODE_W,     //Classic, Left stick up
    KeyEvent.KEYCODE_S,     //Classic, Left stick down
    KeyEvent.KEYCODE_4,     //Classic, Right stick left
    KeyEvent.KEYCODE_6,     //Classic, Right stick right
    KeyEvent.KEYCODE_8,     //Classic, Right stick up
    KeyEvent.KEYCODE_5,     //Classic, Right stick down
    KEYCODE_UNUSED,       //Classic, L2 up
    KEYCODE_UNUSED,       //Classic, L2 down
    KEYCODE_UNUSED,       //Classic, R2 up
    KEYCODE_UNUSED,       //Classic, R2 down
  };

  //Emulated keypress values for nunchuck accelerometer
  private static final int[] NUNCHUCK_ACCELEROMETER_KEYS = new int[] {
    KeyEvent.KEYCODE_V,     //Nunchuck, Accelerometer X up
    KeyEvent.KEYCODE_B,     //Nunchuck, Accelerometer X down
    KeyEvent.KEYCODE_G,     //Nunchuck, Accelerometer Y up
    KeyEvent.KEYCODE_H,     //Nunchuck, Accelerometer Y down
    KeyEvent.KEYCODE_Y,     //Nunchuck, Accelerometer Z up
    KeyEvent.KEYCODE_U,     //Nunchuck, Accelerometer Z down
  };
  
  //Emulated keypress values for nunchuck thumbsticks
  private static final int[] NUNCHUCK_ANALOG_KEYS = new int[] {  
    KeyEvent.KEYCODE_D,     //Nunchuck, Thumbstick right
    KeyEvent.KEYCODE_Q,     //Nunchuck, Thumbstick left
    KeyEvent.KEYCODE_S,     //Nunchuck, Thumbstick down
    KeyEvent.KEYCODE_W,     //Nunchuck, Thumbstick up
  };

  //TODO: Figure out if it is a parsing bug that makes the first byte differ
  // for the extension ID or it is safe to ignore the first byte?
  
  //The ID for a Wii Classic Controller
  private static final byte[] CLASSIC_DEVICE_ID = new byte[] {
    0x00, 0x00, (byte)0xA4, 0x20, 0x01, 0x01
  };

  //The ID for a Wii Classic Controller (not documented, but reported)
  private static final byte[] CLASSIC_DEVICE_ID_ALT = new byte[] {
    0x01, 0x00, (byte)0xA4, 0x20, 0x01, 0x01
  };

  //The ID for a Wii Nunchuck Controller
  private static final byte[] NUNCHUCK_DEVICE_ID = new byte[] {
    0x00, 0x00, (byte)0xA4, 0x20, 0x00, 0x00
  };

  //The ID for a Wii Nunchuck Controller (not documented, but reported)
  private static final byte[] NUNCHUCK_DEVICE_ID_ALT = new byte[] {
    (byte)0xFF, 0x00, (byte)0xA4, 0x20, 0x00, 0x00
  };

  //Enumeration for states required for extension initialization
  private static final int EXTENSION_INIT_STATE_NONE = 0;
  private static final int EXTENSION_INIT_STATE_SENT_FIRST = 1;
  private static final int EXTENSION_INIT_STATE_SENT_SECOND = 2;
  private static final int EXTENSION_INIT_STATE_SENT_READ = 3;
  
  //Offsets into the control messages
  private static int EXTENSION_ADR_OFFSET = 5;
  private static int EXTENSION_VAL_OFFSET = 7;
  private static int REPORTMODE_OFFSET = 3;
  private static int SET_LED_OFFSET = 2;
  
  //Command for writing an extension command, kept here to avoid allocations
  private byte[] m_extensionWriteData = new byte[] {
    0x52, 0x16, 0x04, (byte)0xA4, 0x00, 0x00, 0x01, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  };

  //Command for reading extension data, kept here to avoid allocations
  private byte[] m_extensionReadData = new byte[] {
    0x52, 0x17, 0x04, (byte)0xA4, 0x00, 0x00, 0x00, 0x00
  };

  //The command used to request status, kept here to avoid allocations
  private final byte[] m_requestStatus = new byte[] {
    0x52, 0x15, 0x00  
  };

  //The command used to request status, kept here to avoid allocations
  private final byte[] m_setLEDStatus = new byte[] {
    0x52, 0x11, 0x00  
  };

  //The command used to set report mode, kept here to avoid allocations
  private byte[] m_setReportMode = new byte[] {
    0x52, 0x12, 0x00, 0x00  
  };

  //A buffer for reading memory data, kept here to avoid allocations
  private byte[] m_memoryDataBuffer = new byte[16];
  
  //A flag that tells if the Wii Classic Controller is connected
  private boolean m_isClassicConnected = false;

  //A flag that tells if the Wii Nunchuck Controller is connected
  private boolean m_isNunchuckConnected = false;

  //A state counter, used to keep track of states in the extension initialization sequence
  private int m_extensionInitState = EXTENSION_INIT_STATE_NONE;
  
  //The state of all core buttons, 1 means down, 0 means up
  private int[] m_coreButtons = new int[16];
  
  //The core accelerometer values for x,y,z
  private int[] m_coreAccelerometerValues = new int[3];

  //State of emulated direction keys for core accelerometer
  private boolean[] m_coreEmulatedAccelerometerButtons = new boolean[m_coreAccelerometerValues.length * 2];

  //The state of all classic controller buttons, 1 means down, 0 means up
  private int[] m_classicButtons = new int[16];

  //The analog values from the classic controller thumbsticks
  private int[] m_classicAnalogValues = new int[6];

  //State of emulated direction keys for classic controller thumbsticks
  private boolean[] m_classicEmulatedButtons = new boolean[m_classicAnalogValues.length * 2];

  //The accelerometer values for x,y,z
  private int[] m_nunchuckAccelerometerValues = new int[3];
  
  //The analog values for the nunchuck thumbstick
  private int[] m_nunchuckAnalogValues = new int[2];
  
  //State of emulated direction keys for nunchuck accelerometer
  private boolean[] m_nunchuckEmulatedAccelerometerButtons = new boolean[m_nunchuckAccelerometerValues.length * 2];

  //State of emulated direction keys for nunchuck thumbstick
  private boolean[] m_nunchuckEmulatedButtons = new boolean[m_nunchuckAnalogValues.length * 2]; 
  
  //The state of the nunchuck buttons
  private boolean[] m_nunchuckButtons = new boolean[2];
  
  //A temporary holder for analog values
  private int[] m_tmpAnalogValues = new int[16];
  
  //A flag that keeps the rumble state
  private boolean m_rumbleOn = false;
  
  //The state of the LEDs
  private byte m_LEDstate = 0x10;
  
  //A flag indicating if the accelerometer is active
  private boolean m_useAccelerometer = false;
  
  //A counter used to probe for extensions
  private int m_probeExtension = 0;
  
  //A flag that indicates an update needs to be performed,
  //This flag reduces the number of times a synchronized method is called
  private volatile boolean m_dirtyUpdateFlag;

  //The variables keep an intent to change, and are processed in synchronized methods
  private boolean m_updateRequestRumble = m_rumbleOn;
  private byte m_updateRequestLEDState = m_LEDstate;
  private boolean m_updateRequestAccelerometer = m_useAccelerometer;
  
  private static class ClassicCalibrationDataAxis {
    public int min;
    public int max;
    public int center;
    
    public void ResetAsLeft() {
      //6 bits = 0 - 63
      min = 0;
      center = 31;
      max = 63;
      
      //min = 5;
      //center = 30;
      //max = 55;
    }

    public void ResetAsRight() {
      //5 bits = 0 - 31
      min = 0;
      center = 15;
      max = 31;
      
      //min = 0;
      //center = 15;
      //max = 30;
    }
    
    public int NormalizedValue(int raw) {
      if (raw == center) {
        return 0;
      } else if (raw < center) {
        return ((byte)(((raw - min) / (float)(center - min)) * 127)) - 127;
      } else {
        return (byte)(((raw - center) / (float)(max - center)) * 127);
      }
    }
  }
  
  private static class ClassicCalibrationDataStick {
    public final ClassicCalibrationDataAxis x = new ClassicCalibrationDataAxis();
    public final ClassicCalibrationDataAxis y = new ClassicCalibrationDataAxis();
    private final boolean m_isLeft;
  
    public ClassicCalibrationDataStick(boolean isLeft) {
      m_isLeft = isLeft;
      Reset();
    }
    
    public void Reset() {
      if (m_isLeft)
        ResetAsLeft();
      else
        ResetAsRight();
    }
    
    public void ResetAsLeft() {
      x.ResetAsLeft();
      y.ResetAsLeft();
    }

    public void ResetAsRight() {
      x.ResetAsRight();
      y.ResetAsRight();
    }
  }
  
  private final ClassicCalibrationDataStick m_classic_calibration_left = new ClassicCalibrationDataStick(true);
  private final ClassicCalibrationDataStick m_classic_calibration_right = new ClassicCalibrationDataStick(false);
  
  //We keep a copy to prevent repeated allocations
  private Hashtable<Byte, Integer> m_reportCodes = null;
  
  @Override
  protected Hashtable<Byte, Integer> getSupportedReportCodes() {
    //TODO: This should be handled by SDP inquiry
    
    if (m_reportCodes == null) {
      Hashtable<Byte, Integer> results = new Hashtable<Byte, Integer>();
      
      results.put((byte)0x20, 6); //Status information 
      results.put((byte)0x21, 21); //Read register data
      results.put((byte)0x22, 4); //Acknowledge report
      
      //Data reports
      results.put((byte)0x30, 2);
      results.put((byte)0x31, 5);
      results.put((byte)0x32, 10);
      results.put((byte)0x33, 17);
      results.put((byte)0x34, 21);
      results.put((byte)0x35, 21);
      results.put((byte)0x36, 21);
      results.put((byte)0x37, 21);
      results.put((byte)0x3d, 21);
      
      m_reportCodes = results;
    }
    
    return m_reportCodes;
  }
  
  public WiimoteReader(String address, String sessionId, Context context, boolean startnotification) throws Exception {
    super(address, sessionId, context, startnotification);

    super.m_useInsecureChannel = true;
    
    super.doConnect();
  }

  @Override
  public String getDriverName() {
    return DRIVER_NAME;
  }

  @Override
  protected void verifyHIDDevice() throws Exception {
    //When this is called, we are connected, 
    // so we set up the current state
    
    //Bugfix, there seems to be some cases where the controller does not respond immediately
    try { Thread.sleep(500); }
    catch (InterruptedException iex) {}
    
    //Set the LEDs to indicate we are now connected
    setLEDs(true, false, false, false);
    
    //Set the report mode
    updateReportMode();
    
    //Request a status report so we detect extensions immediately
    requestStatus();
  }
  
  @Override
  protected void handleHIDMessage(byte hidType, byte reportId, byte[] data) throws Exception {
    if (D2) Log.d(DRIVER_NAME, "Got Wii message " + reportId + ": " + getHexString(data, 0, data.length));
    
    //Core buttons are always present in the status reports
    handleCoreButtons(data[0], data[1]);

    //If the accelerometer is active, we handle it here
    if (m_useAccelerometer) {
      switch(reportId) {
        case (byte)0x31: //Core buttons + accelerometer
        case (byte)0x33: //Core buttons + accelerometer + 12 IR bytes
        case (byte)0x35: //Core buttons + accelerometer + 16 extension bytes
        case (byte)0x37: //Core buttons + accelerometer + 10 IR bytes + 6 extension bytes
          handleAccelerometerData(data[2], data[3], data[4]);
          break;
      }
    }
    
    switch(reportId) {
      case (byte)0x20: //Status report
        handleStatusReport(data[2], data[5]);
        break;
      case (byte)0x21: //Read memory data
        if ((data[2] & 0xf) != 0) {
          if (D) Log.e(DRIVER_NAME, "Failed to read extension type");
          m_extensionInitState = EXTENSION_INIT_STATE_NONE;
        } else {
          byte size = (byte)((byte)((data[2] & 0xff) >>> 4) + 1);
          int dataoffset = (data[3] << 8) | (((int)data[4]) &0xff);
          System.arraycopy(data, 5, m_memoryDataBuffer, 0, size);
          handleExtensionDataRead(dataoffset, size, m_memoryDataBuffer);
        }
        break;
      case (byte)0x22: //Acknowledge report
        handleAcknowledgeReport(data[2], data[3]);
        break;
      case (byte)0x32: //Core buttons + 8 extension bytes
        handleExtensionData(data, 2);
        break;
      case (byte)0x34: //Core buttons + 19 extension bytes
        handleExtensionData(data, 4);
        break;
      case (byte)0x35: //Core buttons + accelerometer + 16 extension bytes
        handleExtensionData(data, 5);
        break;
      case (byte)0x36: //Core buttons + 10 IR bytes + 9 extension bytes
        handleExtensionData(data, 12);
        break;
      case (byte)0x37: //Core buttons + accelerometer + 10 IR bytes + 6 extension bytes
        handleExtensionData(data, 15);
        break;

      case (byte)0x30: //Core buttons
      case (byte)0x31: //Core buttons + accelerometer
      case (byte)0x33: //Core buttons + accelerometer + 12 IR bytes
        //Do not report any unexpected message for these
        break;

      default:
        if (D) Log.w(DRIVER_NAME, "Got unexpected wii message: " + data[1]);
        break;
    }

    //If we have not yet seen an extension, lets force activation
    if (m_probeExtension == EXTENSION_PROBETHRESHOLD && !(m_isClassicConnected || m_isNunchuckConnected)) {
      if (D) Log.d(DRIVER_NAME, "Probing for extensions");
      //These will activate the extension without encryption
      writeExtensionRegister((byte)0xf0, (byte)0x55);
      m_extensionInitState = EXTENSION_INIT_STATE_SENT_FIRST;
    }

    //Count until we hit the threshold
    if (m_probeExtension <= EXTENSION_PROBETHRESHOLD)
      m_probeExtension++;

    //We only call the synchronized method when the flag is set
    //which reduces the number of times we need to obtain the lock
    if (m_dirtyUpdateFlag)
      processUpdateRequest();
  }

  private void handleExtensionDataRead(int offset, byte size, byte[] data) throws IOException {
    
    //Special report, read calibration data from classic controller
    if (offset == 0x0020) {
      
      if (D || D3) Log.d(DRIVER_NAME, "Got classic controller calibration report: " + getHexString(data, 0, data.length));

      //TODO: The Nunchuck can also deliver calibration data
      if ((data[0] & 0xff) != 0xff && data[0] != 0x00 && size >= 12 && m_isClassicConnected) {
        if (D || D3) Log.d(DRIVER_NAME, "Classic controller calibration data seems valid, setting up ranges");

        m_classic_calibration_left.x.max = data[0] / 4;
        m_classic_calibration_left.x.min = data[1] / 4;
        m_classic_calibration_left.x.center = data[2] / 4;
        m_classic_calibration_left.y.max = data[3] / 4;
        m_classic_calibration_left.y.min = data[4] / 4;
        m_classic_calibration_left.y.center = data[5] / 4;

        m_classic_calibration_left.x.max = data[6] / 8;
        m_classic_calibration_left.x.min = data[7] / 8;
        m_classic_calibration_left.x.center = data[8] / 8;
        m_classic_calibration_left.y.max = data[9] / 8;
        m_classic_calibration_left.y.min = data[10] / 8;
        m_classic_calibration_left.y.center = data[11] / 8;
      } else {
        if (D || D3) Log.d(DRIVER_NAME, "Classic Controller calibration data was not valid ignoring");
      }
    } else  if (offset != 0x00fa || size != CLASSIC_DEVICE_ID.length) {
      Log.e(DRIVER_NAME, "Unexpected data read: " + getHexString(data, 0, size));
    } else if (m_extensionInitState != EXTENSION_INIT_STATE_SENT_READ) {
      Log.e(DRIVER_NAME, "Got extension data but state was: " + m_extensionInitState);      
    } else {
      
      if (D) Log.d(DRIVER_NAME, "Extension device id: " + getHexString(data, 0, size));
      
      m_extensionInitState = EXTENSION_INIT_STATE_NONE;
      boolean classic = true;
      for(int i = 0; i < size; i++)
        classic &= data[i] == CLASSIC_DEVICE_ID[i];

      boolean classic_alt = true;
      for(int i = 0; i < size; i++)
        classic_alt &= data[i] == CLASSIC_DEVICE_ID_ALT[i];

      boolean nunchuck = true;
      for(int i = 0; i < size; i++)
        nunchuck &= data[i] == NUNCHUCK_DEVICE_ID[i];

      boolean nunchuck_alt = true;
      for(int i = 0; i < size; i++)
        nunchuck_alt &= data[i] == NUNCHUCK_DEVICE_ID_ALT[i];
      
      if (classic || classic_alt) {
        if (D) Log.d(DRIVER_NAME, "Wii Classic Controller Extension connected");
        
        //Clear any previous states
        for(int i = 0; i < m_classicButtons.length; i++)
          m_classicButtons[i] = 0;
        for(int i = 0; i < m_classicAnalogValues.length; i++)
          m_classicAnalogValues[i] = 0;
        for(int i = 0; i < m_classicEmulatedButtons.length; i++)
          m_classicEmulatedButtons[i] = false;
        
        m_isClassicConnected = true;
        m_classic_calibration_left.Reset();
        m_classic_calibration_right.Reset();
        
        //Ask for calibration data, if we do not get it, we just use the default data
        if (D || D3) Log.d(DRIVER_NAME, "Sending Classic Controller Calibration data request");
        readExtensionRegisters((byte)0x20, (byte)16);
        
        updateReportMode();
      } else if (nunchuck | nunchuck_alt) {
        if (D) Log.d(DRIVER_NAME, "Wii Nunchuck Controller Extension connected");
        
        //Clear any previous states
        for(int i = 0; i < m_nunchuckButtons.length; i++)
          m_nunchuckButtons[i] = false;
        for(int i = 0; i < m_nunchuckAccelerometerValues.length; i++)
          m_nunchuckAccelerometerValues[i] = 0;
        for(int i = 0; i < m_nunchuckAnalogValues.length; i++)
          m_nunchuckAnalogValues[i] = 0;
        for(int i = 0; i < m_nunchuckEmulatedAccelerometerButtons.length; i++)
          m_nunchuckEmulatedAccelerometerButtons[i] = false;
        for(int i = 0; i < m_nunchuckEmulatedButtons.length; i++)
          m_nunchuckEmulatedButtons[i] = false;

        m_isNunchuckConnected = true;
        updateReportMode();
      } else {
        Log.d(DRIVER_NAME, "Unknown extension device id: " + getHexString(data, 0, size));
      }
    }
  }
  
  //Method that provides a thread-safe way of updating the LED state
  public synchronized void request_SetLEDState(boolean l1, boolean l2, boolean l3, boolean l4) {
    //TODO: The request is not detected until the next 
    //report is sent from the wiimote, which may cause a large delay
    
    m_dirtyUpdateFlag = true;
    m_updateRequestLEDState = (byte)
      ((l1 ? 0x10 : 0x00) |
      (l2 ? 0x20 : 0x00) |
      (l3 ? 0x40 : 0x00) |
      (l4 ? 0x80 : 0x00));
  }

  //Method that provides a thread-safe way of setting the rumble state
  public synchronized void request_SetRumble(boolean active) {
    //TODO: The request is not detected until the next 
    //report is sent from the wiimote, which may cause a large delay

    m_dirtyUpdateFlag = true;
    m_updateRequestRumble = active;
  }
  
  //Method that provides a thread-safe way of setting the accelerometer state
  public synchronized void request_UseAccelerometer(boolean active) {
    //TODO: The request is not detected until the next 
    //report is sent from the wiimote, which may cause a large delay

    m_dirtyUpdateFlag = true;
    m_updateRequestAccelerometer = active;
  }

  //This method is called when the volatile update flag is set, and processes all pending updates
  private synchronized void processUpdateRequest() throws Exception {
    m_dirtyUpdateFlag = false;
    
    if (m_LEDstate != m_updateRequestLEDState || m_rumbleOn != m_updateRequestRumble) {
      m_LEDstate = m_updateRequestLEDState;
      m_rumbleOn = m_updateRequestRumble;
      
      updateLEDStates();
    }
    
    if (m_useAccelerometer != m_updateRequestAccelerometer) {
      m_useAccelerometer = m_updateRequestAccelerometer;
      
      setAccelerometerActive(m_useAccelerometer);
    }
  }
  
  private void setLEDs(boolean l1, boolean l2, boolean l3, boolean l4) throws Exception {
    m_LEDstate = (byte)
      ((l1 ? 0x10 : 0x00) |
      (l2 ? 0x20 : 0x00) |
      (l3 ? 0x40 : 0x00) |
      (l4 ? 0x80 : 0x00));
    
    updateLEDStates();
  }
  
  private void setAccelerometerActive(boolean active) throws IOException {
    if (active != m_useAccelerometer) {
      m_useAccelerometer = active;
      
      if (m_useAccelerometer) {
        
        //Reset data
        for(int i = 0; i < m_coreAccelerometerValues.length; i++)
          m_coreAccelerometerValues[i] = 0;
        for(int i = 0; i < m_coreEmulatedAccelerometerButtons.length; i++)
          m_coreEmulatedAccelerometerButtons[i] = false;
        
        if (m_isNunchuckConnected) {
          for(int i = 0; i < m_nunchuckEmulatedAccelerometerButtons.length; i++)
            m_nunchuckEmulatedAccelerometerButtons[i] = false;
          for(int i = 0; i < m_nunchuckAccelerometerValues.length; i++)
            m_nunchuckAccelerometerValues[i] = 0;
        }
      }
      
      updateReportMode();
    }
  }
  
  @SuppressWarnings("unused")
  private void setRumble(boolean rumbleOn) throws Exception {
    m_rumbleOn = rumbleOn;
    
    updateLEDStates();
  }

  private void updateLEDStates() throws IOException {
    m_setLEDStatus[SET_LED_OFFSET] = (byte)((m_LEDstate & 0xf0) | (m_rumbleOn ? 0x01 : 0x00));
    m_control.write(m_setLEDStatus);
    m_control.flush();
  }

  
  private void requestStatus() throws IOException {
    m_control.write(m_requestStatus);
    m_control.flush();
  }
  
  private void updateReportMode() throws IOException {
    byte mode;
    
    if (m_isClassicConnected || m_isNunchuckConnected)   
      mode = (byte)(m_useAccelerometer ? 0x35 : 0x32);
    else
      mode = (byte)(m_useAccelerometer ? 0x31 : 0x30);

    
    m_setReportMode[REPORTMODE_OFFSET] = mode;
    m_control.write(m_setReportMode);
    m_control.flush();
  }

  private void writeExtensionRegister(byte address, byte value) throws IOException {
    m_extensionWriteData[EXTENSION_ADR_OFFSET] = address;
    m_extensionWriteData[EXTENSION_VAL_OFFSET] = value;
    
    m_control.write(m_extensionWriteData);
    m_control.flush();
  }
  
  private void readExtensionRegisters(byte address, byte size) throws IOException {
    m_extensionReadData[EXTENSION_ADR_OFFSET] = address;
    m_extensionReadData[EXTENSION_VAL_OFFSET] = size;
    
    m_control.write(m_extensionReadData);
    m_control.flush();
  }
  
  private void handleAcknowledgeReport(byte reportId, byte result) throws IOException {
    
    if (reportId == 0x16) {
      
      if (result != 0x0) {
        Log.w(DRIVER_NAME, "Got NACK for write register report");
      }
      
      switch (m_extensionInitState) {
        case EXTENSION_INIT_STATE_SENT_FIRST:
          //Confirm extension initialization
          writeExtensionRegister((byte)0xfb, (byte)0x00);
          m_extensionInitState = EXTENSION_INIT_STATE_SENT_SECOND;
          break;
        case EXTENSION_INIT_STATE_SENT_SECOND:
          //This will read the extension device ID
          readExtensionRegisters((byte)0xfa, (byte)0x6);
          m_extensionInitState = EXTENSION_INIT_STATE_SENT_READ;
          break;
      }
    }
  }

  private void handleStatusReport(byte state, byte voltage) throws IOException {
    //boolean batteryNearlyDone  = (state & 0x1) != 0;
    boolean extensionConnected = (state & 0x2) != 0;
    //boolean speakerEnabled     = (state & 0x4) != 0;
    //boolean irEnabled          = (state & 0x8) != 0;

    //If we get this message, there is no need to probe
    if (extensionConnected)
      m_probeExtension = EXTENSION_PROBETHRESHOLD + 1;
    
    //If we have an extension connection change, examine it
    if (extensionConnected != (m_isClassicConnected || m_isNunchuckConnected)) {

      //If a new extension is connected, examine it to see what type it is
      if (extensionConnected) {
        if (D && m_extensionInitState != EXTENSION_INIT_STATE_NONE)
          Log.e(DRIVER_NAME, "Started extension init seqence but state was: " + m_extensionInitState);
        
        //These will activate the extension without encryption
        writeExtensionRegister((byte)0xf0, (byte)0x55);
        m_extensionInitState = EXTENSION_INIT_STATE_SENT_FIRST;
      } else {
        if (D) Log.d(DRIVER_NAME, "Extension disconnected");
        
        m_isClassicConnected = false;
        m_isNunchuckConnected = false;
        
        //Disable extension data
        updateReportMode();
      }
    }
  }

  private void handleDigitalButtons(int bitmask, int[] states, int[] keys) {
    for(int i = 0; i < 16; i++) {
      if ((bitmask & 1) != states[i]) {
        states[i] = (bitmask & 1);
        
        if (keys[i] != KEYCODE_UNUSED) {
          if (D || (D3 && keys == CLASSIC_KEYS)) Log.d(getDriverName(), "Button " + i + " changed to: " + (states[i] == 1 ? "down" : "up"));

          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ACTION, states[i] == 1 ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP);
          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_KEY, keys[i]);
          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_MODIFIERS, 0);
          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ANALOG_EMULATED, false);
          m_context.sendBroadcast(keypressBroadcast);
        }
      }
      bitmask = bitmask >>> 1;
    }  
  }
  
  private void handleAnalogValues(int[] newValues, int[] prev, boolean[] buttonstates, int[] keys, int report_axis_offset, boolean isAccelerometer) {
    
    for(int i = 0; i < prev.length; i++) {
      //Make sure the values are always in range
      newValues[i] = Math.max(-127, Math.min(127, newValues[i]));
      
      if (prev[i] != newValues[i]) {
        
        if (D && Math.abs(prev[i] - newValues[i]) > 10) Log.d(getDriverName(), "Axis " + (i + report_axis_offset) + " changed from " + prev[i] + " to: " + newValues[i]);
        
        boolean up = newValues[i] >= ANALOG_THRESHOLD;
        boolean down = newValues[i] <= -ANALOG_THRESHOLD;
        
        prev[i] = newValues[i];
        
        if (isAccelerometer) {
          accelerometerBroadcast.putExtra(BluezService.EVENT_ACCELEROMETERCHANGE_AXIS, i + report_axis_offset);
          accelerometerBroadcast.putExtra(BluezService.EVENT_ACCELEROMETERCHANGE_VALUE, prev[i]);
          m_context.sendBroadcast(accelerometerBroadcast);
        } else {
          directionBroadcast.putExtra(BluezService.EVENT_DIRECTIONALCHANGE_DIRECTION, i + report_axis_offset);
          directionBroadcast.putExtra(BluezService.EVENT_DIRECTIONALCHANGE_VALUE, prev[i]);
          m_context.sendBroadcast(directionBroadcast);
        }
        
        if (up != buttonstates[i*2]) {
          buttonstates[i*2] = up;
          if (keys[i*2] != KEYCODE_UNUSED) {
            keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ACTION, up ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP);
            keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_KEY, keys[i*2]);
            keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_MODIFIERS, 0);
            keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ANALOG_EMULATED, true);
            m_context.sendBroadcast(keypressBroadcast);
          }
        }
        
        if (down != buttonstates[(i*2) + 1]) {
          buttonstates[i*2 + 1] = down;
          if (keys[i*2 + 1] != KEYCODE_UNUSED) {
            keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ACTION, down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP);
            keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_KEY, keys[(i*2) + 1]);
            keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_MODIFIERS, 0);
            keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ANALOG_EMULATED, true);
            m_context.sendBroadcast(keypressBroadcast);
          }
        }
      }
    }        
  }
  
  private void handleCoreButtons(int a, int b) {
    a = a & 0xff;
    b = b & 0xff;
    
    handleDigitalButtons((a << 8) | b, m_coreButtons, CORE_KEYS);
  }

  private void handleAccelerometerData(byte x, byte y, byte z) {

    //Fix the lame Java signed byte representation
    int raw_x = ((int)x) & 0xff;
    int raw_y = ((int)y) & 0xff;
    int raw_z = ((int)z) & 0xff;
    
    //Assign the values and normalize them from 
    // [0 -> 0x80 -> 0xff] to [-127, 0, +127] 
    m_tmpAnalogValues[0] = raw_x - 0x80; 
    m_tmpAnalogValues[1] = raw_y - 0x80;
    m_tmpAnalogValues[2] = raw_z - 0x80;
    
    handleAnalogValues(m_tmpAnalogValues, m_coreAccelerometerValues, m_coreEmulatedAccelerometerButtons, CORE_ACCELEROMETER_KEYS, CORE_ACCELEROMETER_AXIS_OFFSET, true);
  }

  private void handleExtensionData(byte[] data, int offset) {

    if (m_isClassicConnected) {

      if (D3) Log.d(DRIVER_NAME, "Got Classic data: " + getHexString(data, offset, offset + 6));
      
      //These report inverse of the core buttons, 0=pressed, 1=unpressed
      int byteA = (~data[offset + 4]) & 0xff;
      int byteB = (~data[offset + 5]) & 0xff;
      
      handleDigitalButtons((byteB << 8) | byteA, m_classicButtons, CLASSIC_KEYS);
      
      m_tmpAnalogValues[0] = data[offset] & 0x3f; //Left X
      m_tmpAnalogValues[1] = data[offset + 1] & 0x3f; //Left Y
  
      m_tmpAnalogValues[2] = (((data[offset + 2] >>> 7) & 0x1) | ((data[offset + 1] >>> 5) & 0x6) | ((data[offset] >>> 3) & 0x18)) & 0x1f; //Right X
      m_tmpAnalogValues[3] = data[offset + 2] & 0x1f; //Right Y
  
      m_tmpAnalogValues[4] = (((data[offset + 3] >>> 5) & 0x7) | ((data[offset + 2] >>> 2) & 0x18)) & 0x1f; //Left trigger
      m_tmpAnalogValues[5] = data[offset + 3] & 0x1f; //Right trigger
      
      if (D3) Log.d(DRIVER_NAME, "Raw classic analog values: " 
          + m_tmpAnalogValues[0] + ", " 
          + m_tmpAnalogValues[1] + ", "
          + m_tmpAnalogValues[2] + ", "
          + m_tmpAnalogValues[3] + ", "
          + m_tmpAnalogValues[4] + ", "
          + m_tmpAnalogValues[5] + ", ");

      //We scale up the values so they are all in the -127/+127 range
      // if we have some calibration data, we also apply that
      m_tmpAnalogValues[0] = m_classic_calibration_left.x.NormalizedValue(m_tmpAnalogValues[0]);
      m_tmpAnalogValues[1] = m_classic_calibration_left.y.NormalizedValue(m_tmpAnalogValues[1]);
      m_tmpAnalogValues[2] = m_classic_calibration_right.x.NormalizedValue(m_tmpAnalogValues[2]);
      m_tmpAnalogValues[3] = m_classic_calibration_right.y.NormalizedValue(m_tmpAnalogValues[3]);
            
      //The L/R triggers have 5 bits, but only reports in 0-127,
      // as you can only push it in one direction
      m_tmpAnalogValues[4] = ((byte)(m_tmpAnalogValues[4] << 2));
      m_tmpAnalogValues[5] = ((byte)(m_tmpAnalogValues[5] << 2));
  
      //Invert the Y axis
      m_tmpAnalogValues[1] *= -1;
      m_tmpAnalogValues[3] *= -1;
      
      if (D3) Log.d(DRIVER_NAME, "Normalized classic values: " 
          + m_tmpAnalogValues[0] + ", " 
          + m_tmpAnalogValues[1] + ", "
          + m_tmpAnalogValues[2] + ", "
          + m_tmpAnalogValues[3] + ", "
          + m_tmpAnalogValues[4] + ", "
          + m_tmpAnalogValues[5] + ", ");
            
      handleAnalogValues(m_tmpAnalogValues, m_classicAnalogValues, m_classicEmulatedButtons, CLASSIC_ANALOG_KEYS, 0, false);
    
    } else if (m_isNunchuckConnected) {
      
      //The buttons report 0 for pressed
      boolean isCPressed = (data[offset + 5] & 0x2) == 0;
      boolean isZPressed = (data[offset + 5] & 0x1) == 0;
      if (m_nunchuckButtons[0] != isCPressed) {
        m_nunchuckButtons[0] = isCPressed;
        if (NUNCHUCK_KEYS[0] != KEYCODE_UNUSED) {
          if (D) Log.d(getDriverName(), "Button C changed to: " + (isCPressed ? "down" : "up"));

          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ACTION, isCPressed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP);
          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_KEY, NUNCHUCK_KEYS[0]);
          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_MODIFIERS, 0);
          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ANALOG_EMULATED, false);
          m_context.sendBroadcast(keypressBroadcast);
        }
      }

      if (m_nunchuckButtons[1] != isZPressed) {
        m_nunchuckButtons[1] = isZPressed;
        if (NUNCHUCK_KEYS[1] != KEYCODE_UNUSED) {
          if (D) Log.d(getDriverName(), "Button Z changed to: " + (isZPressed ? "down" : "up"));

          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ACTION, isZPressed ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP);
          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_KEY, NUNCHUCK_KEYS[1]);
          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_MODIFIERS, 0);
          keypressBroadcast.putExtra(BluezService.EVENT_KEYPRESS_ANALOG_EMULATED, false);
          m_context.sendBroadcast(keypressBroadcast);
        }
      }

      if (m_useAccelerometer) {
        m_tmpAnalogValues[0] = ((((int)data[offset + 2] & 0xff) << 2) | ((((int)data[offset + 5] & 0xff) >>> 6) & 0x3)) & 0x3ff; //Accelerometer X 
        m_tmpAnalogValues[1] = ((((int)data[offset + 3] & 0xff) << 2) | ((((int)data[offset + 5] & 0xff) >>> 4) & 0x3)) & 0x3ff; //Accelerometer Y 
        m_tmpAnalogValues[2] = ((((int)data[offset + 4] & 0xff) << 2) | ((((int)data[offset + 5] & 0xff) >>> 2) & 0x3)) & 0x3ff; //Accelerometer Z
  
        //We scale the values so they are all in the -127/+127 range
        m_tmpAnalogValues[0] = (m_tmpAnalogValues[0] >>> 2) - 0x80; 
        m_tmpAnalogValues[1] = (m_tmpAnalogValues[1] >>> 2) - 0x80; 
        m_tmpAnalogValues[2] = (m_tmpAnalogValues[2] >>> 2) - 0x80;

        handleAnalogValues(m_tmpAnalogValues, m_nunchuckAccelerometerValues, m_nunchuckEmulatedAccelerometerButtons, NUNCHUCK_ACCELEROMETER_KEYS, NUNCHUCK_ACCELEROMETER_AXIS_OFFSET, true);
      }
      
      m_tmpAnalogValues[0] = (((int)data[offset]) & 0xff); //Thumbstick up/down
      m_tmpAnalogValues[1] = (((int)data[offset + 1]) & 0xff); //Thumbstick left/right
      
      //We scale the values so they are all in the -127/+127 range
      m_tmpAnalogValues[0] = (int)((m_tmpAnalogValues[0] - 0x78) * 1.27);
      m_tmpAnalogValues[1] = (int)((m_tmpAnalogValues[1] - 0x81) * 1.3);
      
      //Bugfix, invert the Y-axis values:
      m_tmpAnalogValues[1] *= -1;
      
      handleAnalogValues(m_tmpAnalogValues, m_nunchuckAnalogValues, m_nunchuckEmulatedButtons, NUNCHUCK_ANALOG_KEYS, 0, false);
    }
  }
  
  public static int[] getButtonCodes() {
    return new int[] { 
        //Core/regular buttons
        KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, 
        KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_1, KeyEvent.KEYCODE_2, 
        KeyEvent.KEYCODE_PLUS, KeyEvent.KEYCODE_MINUS, KeyEvent.KEYCODE_H,
        
        //Classic buttons
        KEYCODE_BUTTON_A, KEYCODE_BUTTON_B, KEYCODE_BUTTON_X, KEYCODE_BUTTON_Y,
        KEYCODE_BUTTON_L1, KEYCODE_BUTTON_R1, KEYCODE_BUTTON_L2, KEYCODE_BUTTON_R2,
        
        //Nunchuck/classic thumbstick keys
        KeyEvent.KEYCODE_W, KeyEvent.KEYCODE_Q, KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_D,
        KeyEvent.KEYCODE_8, KeyEvent.KEYCODE_4, KeyEvent.KEYCODE_5, KeyEvent.KEYCODE_6,
        
        //Nunchuck buttons
        KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_Z,
        
        //Core accelerometer keys
        //KeyEvent.KEYCODE_N, KeyEvent.KEYCODE_M,
        //KeyEvent.KEYCODE_J, KeyEvent.KEYCODE_K,
        //KeyEvent.KEYCODE_I, KeyEvent.KEYCODE_O,
        
        //Nunchuck accelerometer keys
        //KeyEvent.KEYCODE_V, KeyEvent.KEYCODE_B,
        //KeyEvent.KEYCODE_G, KeyEvent.KEYCODE_H,
        //KeyEvent.KEYCODE_Y, KeyEvent.KEYCODE_U,
    };
  }
  
  public static int[] getButtonNames() {
    return new int[] {
        //Core/regular buttons
        R.string.wiimote_dpad_left, R.string.wiimote_dpad_right, R.string.wiimote_dpad_up, R.string.wiimote_dpad_down,  
        R.string.wiimote_core_a, R.string.wiimote_core_b, R.string.wiimote_core_1, R.string.wiimote_core_2,
        R.string.wiimote_plus, R.string.wiimote_minus, R.string.wiimote_home,  
        
        //Classic buttons
        R.string.wiimote_classic_a, R.string.wiimote_classic_b, R.string.wiimote_classic_x, R.string.wiimote_classic_y,
        R.string.wiimote_classic_l1, R.string.wiimote_classic_r1, R.string.wiimote_classic_l2, R.string.wiimote_classic_r2,
        
        //Nunchuck/classic thumbstick keys
        R.string.wiimote_thumb1_up, R.string.wiimote_thumb1_left, R.string.wiimote_thumb1_down, R.string.wiimote_thumb1_right,
        R.string.wiimote_thumb2_up, R.string.wiimote_thumb2_left, R.string.wiimote_thumb2_down, R.string.wiimote_thumb2_right,
        
        //Nunchuck buttons
        R.string.wiimote_nunchuck_c, R.string.wiimote_nunchuck_z,

    };
  }
  
}




Java Source Code List

mobi.omegacentauri.p1keyboard.BGP100Reader.java
mobi.omegacentauri.p1keyboard.BluezDriverInterface.java
mobi.omegacentauri.p1keyboard.BluezForegroundService.java
mobi.omegacentauri.p1keyboard.BluezIMESettings.java
mobi.omegacentauri.p1keyboard.BluezIME.java
mobi.omegacentauri.p1keyboard.BluezService.java
mobi.omegacentauri.p1keyboard.ButtonConfiguration.java
mobi.omegacentauri.p1keyboard.DataDumpReader.java
mobi.omegacentauri.p1keyboard.DeviceScanActivity.java
mobi.omegacentauri.p1keyboard.FutureKeyCodes.java
mobi.omegacentauri.p1keyboard.GameStopReader.java
mobi.omegacentauri.p1keyboard.HIDKeyboard.java
mobi.omegacentauri.p1keyboard.HIDReaderBase.java
mobi.omegacentauri.p1keyboard.HIDipega.java
mobi.omegacentauri.p1keyboard.ImprovedBluetoothDevice.java
mobi.omegacentauri.p1keyboard.PalmOneWirelessKeyboardReader.java
mobi.omegacentauri.p1keyboard.PhonejoyReader.java
mobi.omegacentauri.p1keyboard.Preferences.java
mobi.omegacentauri.p1keyboard.RfcommReader.java
mobi.omegacentauri.p1keyboard.WiimoteReader.java
mobi.omegacentauri.p1keyboard.ZeemoteReader.java
mobi.omegacentauri.p1keyboard.iCadeReader.java
mobi.omegacentauri.p1keyboard.iControlPadReader.java