Back to project page p1keyboard.
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.
/* Copyright (C) 2011, Kenneth Skovhede * http://www.hexad.dk, opensource@hexad.dk * /* w ww. j a va2s. c o m*/ * 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 mobi.omegacentauri.p1keyboard.R; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.inputmethodservice.InputMethodService; import android.os.PowerManager; import android.os.SystemClock; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.widget.Toast; public class BluezIME extends InputMethodService { private static final String SESSION_ID = "mobi.omegacentauri.p1keyboard.ime.controller"; private static final boolean D = false; private static final String LOG_NAME = "BluezIME:BluezInput"; private final String[] m_connectedIds = new String[Preferences.MAX_NO_OF_CONTROLLERS]; private Preferences m_prefs; private NotificationManager m_notificationManager; private Notification m_notification; private int[][] m_keyMappingCache = null; private int[][] m_metaKeyMappingCache = null; private PowerManager.WakeLock m_wakelock = null; private int m_wakelocktype = 0; @Override public void onCreate() { super.onCreate(); m_prefs = new Preferences(this); m_notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); m_notification = new Notification(R.drawable.icon, getString(R.string.app_name), System.currentTimeMillis()); m_keyMappingCache = new int[Preferences.MAX_NO_OF_CONTROLLERS][Math.max(FutureKeyCodes.FUTURE_MAX_KEYCODE, KeyEvent.getMaxKeyCode()) + 1]; m_metaKeyMappingCache = new int[Preferences.MAX_NO_OF_CONTROLLERS][m_keyMappingCache[0].length]; for(int i = 0; i < m_keyMappingCache.length; i++) { for(int j = 0; j < m_keyMappingCache[i].length; j++) { m_keyMappingCache[i][j] = -1; m_metaKeyMappingCache[i][j] = -1; } } setNotificationText(getString(R.string.ime_starting)); acquireWakeLock(); registerReceiver(connectReceiver, new IntentFilter(BluezService.EVENT_CONNECTED)); registerReceiver(connectingReceiver, new IntentFilter(BluezService.EVENT_CONNECTING)); registerReceiver(disconnectReceiver, new IntentFilter(BluezService.EVENT_DISCONNECTED)); registerReceiver(errorReceiver, new IntentFilter(BluezService.EVENT_ERROR)); registerReceiver(preferenceChangedHandler, new IntentFilter(Preferences.PREFERENCES_UPDATED)); registerReceiver(activityHandler, new IntentFilter(BluezService.EVENT_KEYPRESS)); registerReceiver(activityHandler, new IntentFilter(BluezService.EVENT_DIRECTIONALCHANGE)); registerReceiver(bluetoothStateMonitor, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); } private void setNotificationText(CharSequence message) { Intent i = new Intent(this, BluezIMESettings.class); PendingIntent pi = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT); m_notification.setLatestEventInfo(this, getString(R.string.app_name), message, pi); m_notificationManager.notify(1, m_notification); } private void acquireWakeLock() { if (m_wakelock == null) { int wakelocktype = m_prefs.getWakeLock(); if (wakelocktype != Preferences.NO_WAKE_LOCK) { try { m_wakelock = ((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(wakelocktype, "mobi.omegacentauri.p1keyboard.WakeLock"); m_wakelock.acquire(); m_wakelocktype = wakelocktype; } catch (Throwable e) { Log.e(LOG_NAME, e.getMessage()); } } } } private void releaseWakeLock() { if (m_wakelock != null) { m_wakelock.release(); m_wakelock = null; m_wakelocktype = 0; } } private int getConnectedCount() { int count = 0; for(int i = 0; i < m_connectedIds.length; i++) if (m_connectedIds[i] != null) count++; return count; } @Override public View onCreateInputView() { super.onCreateInputView(); return null; } @Override public View onCreateCandidatesView() { super.onCreateCandidatesView(); return null; } @Override public void onStartInputView(EditorInfo info, boolean restarting) { super.onStartInputView(info, restarting); Log.d(LOG_NAME, "Start input view"); if (getConnectedCount() != m_prefs.getControllerCount()) connect(); } @Override public void onStartInput(EditorInfo attribute, boolean restarting) { super.onStartInput(attribute, restarting); //Reconnect if we lost connection if (getConnectedCount() != m_prefs.getControllerCount()) connect(); } private void connect() { Log.d(LOG_NAME, "Connecting"); if (m_prefs.getManageBluetooth()) { try { if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) { //Unfortunately this does not work because there is no view // associated with this IME, and thus we cannot show popup dialogs //ImprovedBluetoothDevice.ActivateBluetooth(this, m_inputView); //NOTE: This violates the guidelines that state that the user should // be asked explicitly before turning BT on BluetoothAdapter.getDefaultAdapter().enable(); Toast.makeText(this, this.getString(R.string.enabling_bluetooth), Toast.LENGTH_SHORT).show(); setNotificationText(this.getString(R.string.enabling_bluetooth)); //We will connect when BT is on return; } } catch (Exception ex) { Log.e(LOG_NAME, "Failed to activate bluetooth: " + ex.getMessage()); } } for(int i = 0; i < m_prefs.getControllerCount(); i++) { String address = m_prefs.getSelectedDeviceAddress(i); String driver = m_prefs.getSelectedDriverName(i); Intent intent = new Intent(this, BluezService.class); intent.setAction(BluezService.REQUEST_CONNECT); intent.putExtra(BluezService.REQUEST_CONNECT_ADDRESS, address); intent.putExtra(BluezService.REQUEST_CONNECT_DRIVER, driver); intent.putExtra(BluezService.SESSION_ID, SESSION_ID + i); intent.putExtra(BluezService.REQUEST_CONNECT_CREATE_NOTIFICATION, false); startService(intent); } } @Override public void onFinishInput() { super.onFinishInput(); Log.d(LOG_NAME, "Finish input view"); } @Override public void onDestroy() { super.onDestroy(); Log.d(LOG_NAME, "Destroy IME"); m_notificationManager.cancel(1); if (getConnectedCount() > 0) { Log.d(LOG_NAME, "Disconnecting"); for(int i = 0; i < m_connectedIds.length; i++) { if (m_connectedIds[i] != null) { Intent intent = new Intent(this, BluezService.class); intent.setAction(BluezService.REQUEST_DISCONNECT); intent.putExtra(BluezService.SESSION_ID, m_connectedIds[i]); startService(intent); } } } releaseWakeLock(); unregisterReceiver(connectReceiver); unregisterReceiver(connectingReceiver); unregisterReceiver(disconnectReceiver); unregisterReceiver(errorReceiver); unregisterReceiver(preferenceChangedHandler); unregisterReceiver(activityHandler); unregisterReceiver(bluetoothStateMonitor); connectReceiver = null; connectingReceiver = null; disconnectReceiver = null; activityHandler = null; errorReceiver = null; preferenceChangedHandler = null; bluetoothStateMonitor = null; if (m_prefs.getManageBluetooth()) { try { if (BluetoothAdapter.getDefaultAdapter().isEnabled()) { Toast.makeText(this, this.getString(R.string.disabling_bluetooth), Toast.LENGTH_SHORT).show(); BluetoothAdapter.getDefaultAdapter().disable(); } } catch (Exception ex) { Log.e(LOG_NAME, "Failed to turn BT off: " + ex.getMessage()); } } } private BroadcastReceiver connectingReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String sid = intent.getStringExtra(BluezService.SESSION_ID); if (sid == null || !sid.startsWith(SESSION_ID)) return; String address = intent.getStringExtra(BluezService.EVENT_CONNECTING_ADDRESS); Toast.makeText(BluezIME.this, String.format(getString(R.string.ime_connecting), address), Toast.LENGTH_SHORT).show(); setNotificationText(String.format(getString(R.string.ime_connecting), address)); } }; private BroadcastReceiver connectReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String sid = intent.getStringExtra(BluezService.SESSION_ID); if (sid == null || !sid.startsWith(SESSION_ID)) return; int controllerNo = -1; try { controllerNo = Integer.parseInt(sid.substring(SESSION_ID.length())); } catch (Throwable t) { if (D) Log.w(LOG_NAME, "Failed to parse connectId: " + sid); } if (controllerNo < 0 || controllerNo >= m_connectedIds.length) return; if (D) Log.d(LOG_NAME, "Connect received"); Toast.makeText(context, String.format(context.getString(R.string.connected_to_device_message), intent.getStringExtra(BluezService.EVENT_CONNECTED_ADDRESS)), Toast.LENGTH_SHORT).show(); m_connectedIds[controllerNo] = sid; setNotificationText(String.format(getString(R.string.ime_connected), intent.getStringExtra(BluezService.EVENT_CONNECTED_ADDRESS))); } }; private BroadcastReceiver disconnectReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String sid = intent.getStringExtra(BluezService.SESSION_ID); if (sid == null || !sid.startsWith(SESSION_ID)) return; Log.d(LOG_NAME, "Disconnect received"); Toast.makeText(context, String.format(context.getString(R.string.disconnected_from_device_message), intent.getStringExtra(BluezService.EVENT_DISCONNECTED_ADDRESS)), Toast.LENGTH_SHORT).show(); for(int i = 0; i < m_connectedIds.length; i++) { if (sid.equals(m_connectedIds[i])) m_connectedIds[i] = null; } setNotificationText(getString(R.string.ime_disconnected)); } }; private BroadcastReceiver errorReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String sid = intent.getStringExtra(BluezService.SESSION_ID); if (sid == null || !sid.startsWith(SESSION_ID)) return; if (D) Log.d(LOG_NAME, "Error received"); Toast.makeText(context, String.format(context.getString(R.string.error_message_generic), intent.getStringExtra(BluezService.EVENT_ERROR_SHORT)), Toast.LENGTH_SHORT).show(); setNotificationText(String.format(getString(R.string.ime_error), intent.getStringExtra(BluezService.EVENT_ERROR_SHORT))); } }; private BroadcastReceiver preferenceChangedHandler = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { //Clear the key mapping cache for(int i = 0; i < m_keyMappingCache.length; i++) { for(int j = 0; j < m_keyMappingCache[i].length; j++) { m_keyMappingCache[i][j] = -1; m_metaKeyMappingCache[i][j] = -1; } } if (getConnectedCount() > 0) connect(); if (m_wakelocktype != m_prefs.getWakeLock()) { releaseWakeLock(); acquireWakeLock(); } } }; private BroadcastReceiver activityHandler = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String sid = intent.getStringExtra(BluezService.SESSION_ID); if (sid == null || !sid.startsWith(SESSION_ID)) return; if (D) Log.d(LOG_NAME, "Update event received"); try { int controllerNo = Integer.parseInt(sid.substring(SESSION_ID.length())); InputConnection ic = getCurrentInputConnection(); long eventTime = SystemClock.uptimeMillis(); if (intent.getAction().equals(BluezService.EVENT_KEYPRESS)) { int action = intent.getIntExtra(BluezService.EVENT_KEYPRESS_ACTION, KeyEvent.ACTION_DOWN); int key = intent.getIntExtra(BluezService.EVENT_KEYPRESS_KEY, 0); int metakey = intent.getIntExtra(BluezService.EVENT_KEYPRESS_MODIFIERS, 0); int special = intent.getIntExtra(BluezService.EVENT_KEYPRESS_SPECIAL, 0); if (special > 0) { switch(special) { case BluezService.SPECIAL_COPY: ic.performContextMenuAction(android.R.id.copy); break; case BluezService.SPECIAL_CUT: ic.performContextMenuAction(android.R.id.cut); break; case BluezService.SPECIAL_PASTE: ic.performContextMenuAction(android.R.id.paste); break; case BluezService.SPECIAL_SELECT_ALL: ic.performContextMenuAction(android.R.id.selectAll); break; case BluezService.SPECIAL_HOME: Intent i = new Intent(Intent.ACTION_MAIN); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.addCategory(Intent.CATEGORY_HOME); startActivity(i); break; case BluezService.SPECIAL_UNICODE: ic.commitText(String.valueOf(Character.toChars(key)),1 ); } return; } //This construct ensures that we can perform lock free // access to m_keyMappingCache and never risk sending -1 // as the keyCode if (controllerNo >= m_keyMappingCache.length || key >= m_keyMappingCache[controllerNo].length) { Log.e(LOG_NAME, "Key reported by driver: " + key + ", size of keymapping array: " + m_keyMappingCache[0].length + ", controller no " + controllerNo + ", expected controllers: " + m_keyMappingCache.length); } else { int translatedKey = m_keyMappingCache[controllerNo][key]; if (translatedKey == -1) { translatedKey = m_prefs.getKeyMapping(key, controllerNo); m_keyMappingCache[controllerNo][key] = translatedKey; } //TODO: This conflicts slightly with keyboard, because we have no way of knowing. // if the mapping is deliberately without a meta key, or just default. //So if we have a case where the controller sends a meta modifier, we // do not apply the user chosen override. //Currently this is not a problem, because only the keyboard HID sends the modifier, // and the user cannot set the modifier anyway if (metakey == 0) { metakey = m_metaKeyMappingCache[controllerNo][key]; if (metakey == -1) { metakey = m_prefs.getMetaKeyMapping(key, controllerNo); m_metaKeyMappingCache[controllerNo][key] = metakey; } } if (D) Log.d(LOG_NAME, "Sending key event: " + (action == KeyEvent.ACTION_DOWN ? "Down" : "Up") + " - " + key + " - " + metakey); ic.sendKeyEvent(new KeyEvent(eventTime, eventTime, action, translatedKey, 0, metakey, 0, 0, KeyEvent.FLAG_SOFT_KEYBOARD)); } } } catch (Exception ex) { Log.e(LOG_NAME, "Failed to send key events: " + ex.toString()); } } }; private BroadcastReceiver bluetoothStateMonitor = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0); if (state == BluetoothAdapter.STATE_ON) { if (getConnectedCount() == 0) connect(); } } }; }