/* * ConnectBot: simple, powerful, open-source SSH client for Android * Copyright 2010 Kenny Root, Jeffrey Sharkey * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.iiordanov.bssh.service; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.List; import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.res.Configuration; import android.net.Uri; import android.preference.PreferenceManager; import android.text.ClipboardManager; import android.text.Editable; import android.text.method.CharacterPickerDialog; import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.View; import android.view.View.OnKeyListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import com.iiordanov.bssh.R; import com.iiordanov.bssh.TerminalView; import com.iiordanov.bssh.bean.SelectionArea; import com.iiordanov.bssh.util.PreferenceConstants; import de.mud.terminal.VDUBuffer; import de.mud.terminal.vt320; /** * @author kenny * */ @SuppressWarnings("deprecation") // for ClipboardManager public class TerminalKeyListener implements OnKeyListener, OnSharedPreferenceChangeListener { private static final String TAG = "ConnectBot.OnKeyListener"; public final static int META_CTRL_ON = 0x01; public final static int META_CTRL_LOCK = 0x02; public final static int META_ALT_ON = 0x04; public final static int META_ALT_LOCK = 0x08; public final static int META_SHIFT_ON = 0x10; public final static int META_SHIFT_LOCK = 0x20; public final static int META_SLASH = 0x40; public final static int META_TAB = 0x80; // The bit mask of momentary and lock states for each public final static int META_CTRL_MASK = META_CTRL_ON | META_CTRL_LOCK; public final static int META_ALT_MASK = META_ALT_ON | META_ALT_LOCK; public final static int META_SHIFT_MASK = META_SHIFT_ON | META_SHIFT_LOCK; // backport constants from api level 11 public final static int KEYCODE_ESCAPE = 111; public final static int HC_META_CTRL_ON = 4096; public final static int KEYCODE_PAGE_UP = 92; public final static int KEYCODE_PAGE_DOWN = 93; // All the transient key codes public final static int META_TRANSIENT = META_CTRL_ON | META_ALT_ON | META_SHIFT_ON; public final static int SCAN_ESC = 1; public final static int SCAN_LEFTCTRL = 29; public final static int SCAN_RIGHTCTRL = 97; public final static int SCAN_F1 = 59; public final static int SCAN_F10 = 68; public final static int SCAN_HOME = 102; public final static int SCAN_END = 107; private final TerminalManager manager; private final TerminalBridge bridge; private final VDUBuffer buffer; private String keymode = null; private boolean hardKeyboard = false; private String customKeyboard = null; private int metaState = 0; private boolean hardCtrlPressed = false; private int mDeadKey = 0; // TODO add support for the new API. private ClipboardManager clipboard = null; private boolean selectingForCopy = false; private final SelectionArea selectionArea; private String encoding; private final SharedPreferences prefs; private Toast debugToast = null; public TerminalKeyListener(TerminalManager manager, TerminalBridge bridge, VDUBuffer buffer, String encoding) { this.manager = manager; this.bridge = bridge; this.buffer = buffer; this.encoding = encoding; selectionArea = new SelectionArea(); prefs = PreferenceManager.getDefaultSharedPreferences(manager); prefs.registerOnSharedPreferenceChangeListener(this); hardKeyboard = (manager.res.getConfiguration().keyboard == Configuration.KEYBOARD_QWERTY); updateKeymode(); updateCustomKeymap(); } /** * Handle onKey() events coming down from a {@link TerminalView} above us. * Modify the keys to make more sense to a host then pass it to the transport. */ public boolean onKey(View v, int keyCode, KeyEvent event) { Log.e(TAG, "onKey: " + keyCode + " " + event.getUnicodeChar()); try { final boolean hardKeyboardHidden = manager.hardKeyboardHidden; // Ignore all key-up events except for the special keys if (event.getAction() == KeyEvent.ACTION_UP) { // Look for external keyboard scancodes. switch (event.getScanCode()) { case SCAN_LEFTCTRL: case SCAN_RIGHTCTRL: metaState &= ~META_CTRL_ON; hardCtrlPressed = false; bridge.redraw(); return true; } // There's nothing here for virtual keyboard users. if (!hardKeyboard || (hardKeyboard && hardKeyboardHidden)) return false; // skip keys if we aren't connected yet or have been disconnected if (bridge.isDisconnected() || bridge.transport == null) return false; // if keycode debugging enabled, log and print the pressed key if (prefs.getBoolean(PreferenceConstants.DEBUG_KEYCODES, false)) { String keyCodeString = String.format(": %d", keyCode); String toastText = v.getContext().getString(R.string.keycode_pressed) + keyCodeString; if (debugToast == null) debugToast = Toast.makeText(v.getContext(), toastText, Toast.LENGTH_LONG); else debugToast.setText(toastText); debugToast.show(); } if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) { if (keyCode == KeyEvent.KEYCODE_ALT_RIGHT && (metaState & META_SLASH) != 0) { metaState &= ~(META_SLASH | META_TRANSIENT); bridge.transport.write('/'); return true; } else if (keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT && (metaState & META_TAB) != 0) { metaState &= ~(META_TAB | META_TRANSIENT); bridge.transport.write(0x09); return true; } } else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) { if (keyCode == KeyEvent.KEYCODE_ALT_LEFT && (metaState & META_SLASH) != 0) { metaState &= ~(META_SLASH | META_TRANSIENT); bridge.transport.write('/'); return true; } else if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT && (metaState & META_TAB) != 0) { metaState &= ~(META_TAB | META_TRANSIENT); bridge.transport.write(0x09); return true; } } return false; } // check for terminal resizing keys if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { bridge.increaseFontSize(); return true; } else if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { bridge.decreaseFontSize(); return true; } // skip keys if we aren't connected yet or have been disconnected if (bridge.isDisconnected() || bridge.transport == null) return false; bridge.resetScrollPosition(); if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) { byte[] input = event.getCharacters().getBytes(encoding); bridge.transport.write(input); return true; } int curMetaState = event.getMetaState(); final int orgMetaState = curMetaState; if ((metaState & META_SHIFT_MASK) != 0) { curMetaState |= KeyEvent.META_SHIFT_ON; } if ((metaState & META_ALT_MASK) != 0) { curMetaState |= KeyEvent.META_ALT_ON; } int key = event.getUnicodeChar(curMetaState); // no hard keyboard? ALT-k should pass through to below if ((orgMetaState & KeyEvent.META_ALT_ON) != 0 && (!hardKeyboard || hardKeyboardHidden)) { key = 0; } if ((key & KeyCharacterMap.COMBINING_ACCENT) != 0) { mDeadKey = key & KeyCharacterMap.COMBINING_ACCENT_MASK; return true; } if (mDeadKey != 0) { key = KeyCharacterMap.getDeadChar(mDeadKey, keyCode); mDeadKey = 0; } final boolean printing = (key != 0 && keyCode != KeyEvent.KEYCODE_ENTER); if (v != null) { //Show up the CharacterPickerDialog when the SYM key is pressed if( (keyCode == KeyEvent.KEYCODE_SYM || keyCode == KeyEvent.KEYCODE_PICTSYMBOLS || key == KeyCharacterMap.PICKER_DIALOG_INPUT)) { bridge.showCharPickerDialog(); if(metaState == 4) { // reset fn-key state metaState = 0; bridge.redraw(); } return true; } else if(keyCode == KeyEvent.KEYCODE_SEARCH) { //Show up the URL scan dialog when the search key is pressed urlScan(v); return true; } } // handle customized keymaps if (customKeymapAction(keyCode)) return true; // otherwise pass through to existing session // print normal keys if (printing) { metaState &= ~(META_SLASH | META_TAB); // Remove shift and alt modifiers final int lastMetaState = metaState; metaState &= ~(META_SHIFT_ON | META_ALT_ON); if (metaState != lastMetaState) { bridge.redraw(); } if ((metaState & META_CTRL_MASK) != 0) { if (!hardCtrlPressed) { metaState &= ~META_CTRL_ON; bridge.redraw(); } // If there is no hard keyboard or there is a hard keyboard currently hidden, // CTRL-1 through CTRL-9 will send F1 through F9 if ((!hardKeyboard || (hardKeyboard && hardKeyboardHidden)) && sendFunctionKey(keyCode)) return true; key = keyAsControl(key); } // handle pressing f-keys if ((hardKeyboard && !hardKeyboardHidden) && (curMetaState & KeyEvent.META_ALT_ON) != 0 && (curMetaState & KeyEvent.META_SHIFT_ON) != 0 && sendFunctionKey(keyCode)) return true; if (key < 0x80) bridge.transport.write(key); else // TODO write encoding routine that doesn't allocate each time bridge.transport.write(new String(Character.toChars(key)) .getBytes(encoding)); return true; } // send ctrl and meta-keys as appropriate if (!hardKeyboard || hardKeyboardHidden) { int k = event.getUnicodeChar(0); int k0 = k; boolean sendCtrl = false; boolean sendMeta = false; if (k != 0) { if ((orgMetaState & HC_META_CTRL_ON) != 0) { k = keyAsControl(k); if (k != k0) sendCtrl = true; // send F1-F10 via CTRL-1 through CTRL-0 if (!sendCtrl && sendFunctionKey(keyCode)) return true; } else if ((orgMetaState & KeyEvent.META_ALT_ON) != 0) { sendMeta = true; sendEscape(); } if (sendMeta || sendCtrl) { bridge.transport.write(k); return true; } } } // try handling keymode shortcuts if (hardKeyboard && !hardKeyboardHidden && event.getRepeatCount() == 0) { if (PreferenceConstants.KEYMODE_RIGHT.equals(keymode)) { switch (keyCode) { case KeyEvent.KEYCODE_ALT_RIGHT: metaState |= META_SLASH; return true; case KeyEvent.KEYCODE_SHIFT_RIGHT: metaState |= META_TAB; return true; case KeyEvent.KEYCODE_SHIFT_LEFT: metaPress(META_SHIFT_ON); return true; case KeyEvent.KEYCODE_ALT_LEFT: metaPress(META_ALT_ON); return true; } } else if (PreferenceConstants.KEYMODE_LEFT.equals(keymode)) { switch (keyCode) { case KeyEvent.KEYCODE_ALT_LEFT: metaState |= META_SLASH; return true; case KeyEvent.KEYCODE_SHIFT_LEFT: metaState |= META_TAB; return true; case KeyEvent.KEYCODE_SHIFT_RIGHT: metaPress(META_SHIFT_ON); return true; case KeyEvent.KEYCODE_ALT_RIGHT: metaPress(META_ALT_ON); return true; } } else { switch (keyCode) { case KeyEvent.KEYCODE_ALT_RIGHT: case KeyEvent.KEYCODE_ALT_LEFT: metaPress(META_ALT_ON); return true; case KeyEvent.KEYCODE_SHIFT_LEFT: case KeyEvent.KEYCODE_SHIFT_RIGHT: metaPress(META_SHIFT_ON); return true; } } } // look for special chars switch(keyCode) { case KEYCODE_ESCAPE: sendEscape(); return true; case KeyEvent.KEYCODE_TAB: bridge.transport.write(0x09); return true; case KEYCODE_PAGE_DOWN: ((vt320)buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', getStateForBuffer()); metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); return true; case KEYCODE_PAGE_UP: ((vt320)buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', getStateForBuffer()); metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); return true; case 122: //KeyEvent.KEYCODE_MOVE_HOME: ((vt320) buffer).keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer()); metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); return true; case 123: //KeyEvent.KEYCODE_MOVE_END: ((vt320) buffer).keyPressed(vt320.KEY_END, ' ', getStateForBuffer()); metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); return true; case KeyEvent.KEYCODE_CAMERA: // check to see which shortcut the camera button triggers String camera = manager.prefs.getString( PreferenceConstants.CAMERA, PreferenceConstants.CAMERA_SCREEN_CAPTURE); if(PreferenceConstants.CAMERA_SCREEN_CAPTURE.equals(camera)) { bridge.captureScreen(); } else if (PreferenceConstants.CAMERA_CTRLA_SPACE.equals(camera)) { bridge.transport.write(0x01); bridge.transport.write(' '); } else if(PreferenceConstants.CAMERA_CTRLA.equals(camera)) { bridge.transport.write(0x01); } else if(PreferenceConstants.CAMERA_ESC.equals(camera)) { ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); } else if(PreferenceConstants.CAMERA_ESC_A.equals(camera)) { ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); bridge.transport.write('a'); } break; case KeyEvent.KEYCODE_DEL: if ((metaState & META_ALT_MASK) != 0) { ((vt320) buffer).keyPressed(vt320.KEY_INSERT, ' ', getStateForBuffer()); } else { ((vt320) buffer).keyPressed(vt320.KEY_BACK_SPACE, ' ', getStateForBuffer()); } metaState &= ~META_TRANSIENT; return true; case KeyEvent.KEYCODE_ENTER: ((vt320)buffer).keyTyped(vt320.KEY_ENTER, ' ', 0); metaState &= ~META_TRANSIENT; return true; case KeyEvent.KEYCODE_DPAD_LEFT: if (selectingForCopy) { selectionArea.decrementColumn(); bridge.redraw(); } else { if ((metaState & META_ALT_MASK) != 0) { ((vt320) buffer).keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer()); } else { ((vt320) buffer).keyPressed(vt320.KEY_LEFT, ' ', getStateForBuffer()); } metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); } return true; case KeyEvent.KEYCODE_DPAD_UP: if (selectingForCopy) { selectionArea.decrementRow(); bridge.redraw(); } else { if ((metaState & META_ALT_MASK) != 0) { ((vt320)buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', getStateForBuffer()); } else { ((vt320) buffer).keyPressed(vt320.KEY_UP, ' ', getStateForBuffer()); } metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); } return true; case KeyEvent.KEYCODE_DPAD_DOWN: if (selectingForCopy) { selectionArea.incrementRow(); bridge.redraw(); } else { if ((metaState & META_ALT_MASK) != 0) { ((vt320)buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', getStateForBuffer()); } else { ((vt320) buffer).keyPressed(vt320.KEY_DOWN, ' ', getStateForBuffer()); } metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); } return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (selectingForCopy) { selectionArea.incrementColumn(); bridge.redraw(); } else { if ((metaState & META_ALT_MASK) != 0) { ((vt320) buffer).keyPressed(vt320.KEY_END, ' ', getStateForBuffer()); } else { ((vt320) buffer).keyPressed(vt320.KEY_RIGHT, ' ', getStateForBuffer()); } metaState &= ~META_TRANSIENT; bridge.tryKeyVibrate(); } return true; case KeyEvent.KEYCODE_DPAD_CENTER: metaPress(META_CTRL_ON); //ctrlKeySpecial(); return true; } // Look for scancodes from external keyboards switch (event.getScanCode()) { case SCAN_ESC: sendEscape(); return true; case SCAN_LEFTCTRL: case SCAN_RIGHTCTRL: metaState |= META_CTRL_ON; hardCtrlPressed = true; return true; case SCAN_HOME: return true; case SCAN_END: return true; default: // Handle F1-F10 which come in row int scancode = event.getScanCode(); if (scancode >= SCAN_F1 && scancode <= SCAN_F10) { int k = scancode - SCAN_F1 + KeyEvent.KEYCODE_1; // F10 corresponds to 0 sendFunctionKey(k > KeyEvent.KEYCODE_9 ? KeyEvent.KEYCODE_0 : k); return true; } return false; } } catch (IOException e) { Log.e(TAG, "Problem while trying to handle an onKey() event", e); try { bridge.transport.flush(); } catch (IOException ioe) { Log.d(TAG, "Our transport was closed, dispatching disconnect event"); bridge.dispatchDisconnect(false); } } catch (NullPointerException npe) { Log.d(TAG, "Input before connection established ignored."); return true; } return false; } public int keyAsControl(int key) { Log.e(TAG, "keyAsControl"); // Support CTRL-a through CTRL-z if (key >= 0x61 && key <= 0x7A) key -= 0x60; // Support CTRL-A through CTRL-_ else if (key >= 0x41 && key <= 0x5F) key -= 0x40; // CTRL-space sends NULL else if (key == 0x20) key = 0x00; // CTRL-? sends DEL else if (key == 0x3F) key = 0x7F; return key; } public void sendTab () { try { bridge.transport.write(0x09); } catch (IOException e) { e.printStackTrace(); } } public void sendEscape() { ((vt320)buffer).keyTyped(vt320.KEY_ESCAPE, ' ', 0); } public void sendUp() { if ((metaState & META_ALT_MASK) != 0) { ((vt320)buffer).keyPressed(vt320.KEY_PAGE_UP, ' ', getStateForBuffer()); } else { ((vt320) buffer).keyPressed(vt320.KEY_UP, ' ', getStateForBuffer()); } metaState &= ~META_TRANSIENT; } public void sendLeft() { if ((metaState & META_ALT_MASK) != 0) { ((vt320) buffer).keyPressed(vt320.KEY_HOME, ' ', getStateForBuffer()); } else { ((vt320) buffer).keyPressed(vt320.KEY_LEFT, ' ', getStateForBuffer()); } metaState &= ~META_TRANSIENT; } public void sendDown() { if ((metaState & META_ALT_MASK) != 0) { ((vt320)buffer).keyPressed(vt320.KEY_PAGE_DOWN, ' ', getStateForBuffer()); } else { ((vt320) buffer).keyPressed(vt320.KEY_DOWN, ' ', getStateForBuffer()); } metaState &= ~META_TRANSIENT; } public void sendRight() { if ((metaState & META_ALT_MASK) != 0) { ((vt320) buffer).keyPressed(vt320.KEY_END, ' ', getStateForBuffer()); } else { ((vt320) buffer).keyPressed(vt320.KEY_RIGHT, ' ', getStateForBuffer()); } metaState &= ~META_TRANSIENT; } /** * @param key * @return successful */ private boolean sendFunctionKey(int keyCode) { switch (keyCode) { case KeyEvent.KEYCODE_1: ((vt320) buffer).keyPressed(vt320.KEY_F1, ' ', 0); return true; case KeyEvent.KEYCODE_2: ((vt320) buffer).keyPressed(vt320.KEY_F2, ' ', 0); return true; case KeyEvent.KEYCODE_3: ((vt320) buffer).keyPressed(vt320.KEY_F3, ' ', 0); return true; case KeyEvent.KEYCODE_4: ((vt320) buffer).keyPressed(vt320.KEY_F4, ' ', 0); return true; case KeyEvent.KEYCODE_5: ((vt320) buffer).keyPressed(vt320.KEY_F5, ' ', 0); return true; case KeyEvent.KEYCODE_6: ((vt320) buffer).keyPressed(vt320.KEY_F6, ' ', 0); return true; case KeyEvent.KEYCODE_7: ((vt320) buffer).keyPressed(vt320.KEY_F7, ' ', 0); return true; case KeyEvent.KEYCODE_8: ((vt320) buffer).keyPressed(vt320.KEY_F8, ' ', 0); return true; case KeyEvent.KEYCODE_9: ((vt320) buffer).keyPressed(vt320.KEY_F9, ' ', 0); return true; case KeyEvent.KEYCODE_0: ((vt320) buffer).keyPressed(vt320.KEY_F10, ' ', 0); return true; default: return false; } } /** * Handle meta key presses where the key can be locked on. * <p> * 1st press: next key to have meta state<br /> * 2nd press: meta state is locked on<br /> * 3rd press: disable meta state * * @param code */ public void metaPress(int code) { if ((metaState & (code << 1)) != 0) { metaState &= ~(code << 1); } else if ((metaState & code) != 0) { metaState &= ~code; metaState |= code << 1; } else metaState |= code; bridge.redraw(); } public void setTerminalKeyMode(String keymode) { this.keymode = keymode; } private int getStateForBuffer() { int bufferState = 0; if ((metaState & META_CTRL_MASK) != 0) bufferState |= vt320.KEY_CONTROL; if ((metaState & META_SHIFT_MASK) != 0) bufferState |= vt320.KEY_SHIFT; if ((metaState & META_ALT_MASK) != 0) bufferState |= vt320.KEY_ALT; return bufferState; } public int getMetaState() { return metaState; } public int getDeadKey() { return mDeadKey; } public void setClipboardManager(ClipboardManager clipboard) { this.clipboard = clipboard; } public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (PreferenceConstants.KEYMODE.equals(key)) { updateKeymode(); } else if (PreferenceConstants.CUSTOM_KEYMAP.equals(key)) { updateCustomKeymap(); } } private void updateKeymode() { keymode = prefs.getString(PreferenceConstants.KEYMODE, PreferenceConstants.KEYMODE_RIGHT); } private void updateCustomKeymap() { customKeyboard = prefs.getString(PreferenceConstants.CUSTOM_KEYMAP, PreferenceConstants.CUSTOM_KEYMAP_DISABLED); } public void setCharset(String encoding) { this.encoding = encoding; } private void ctrlKeySpecial() { if (selectingForCopy) { if (selectionArea.isSelectingOrigin()) selectionArea.finishSelectingOrigin(); else { if (clipboard != null) { // copy selected area to clipboard String copiedText = selectionArea.copyFrom(buffer); clipboard.setText(copiedText); // XXX STOPSHIP // manager.notifyUser(manager.getString( // R.string.console_copy_done, // copiedText.length())); selectingForCopy = false; selectionArea.reset(); } } } else { if ((metaState & META_CTRL_ON) != 0) { sendEscape(); metaState &= ~META_CTRL_ON; } else metaPress(META_CTRL_ON); } bridge.redraw(); } private boolean customKeymapAction(int keyCode) { if (bridge == null || customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_DISABLED)) return false; byte c = 0x00; if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SE_XPPRO)) { // Sony Ericsson Xperia pro (MK16i) and Xperia mini Pro (SK17i) // Language key acts as CTRL if (keyCode == KeyEvent.KEYCODE_SWITCH_CHARSET) { ctrlKeySpecial(); return true; } if ((metaState & META_ALT_MASK) != 0) { if ((metaState & META_SHIFT_MASK) != 0) { // ALT + shift + key switch (keyCode) { case KeyEvent.KEYCODE_U: c = 0x5B; break; case KeyEvent.KEYCODE_I: c = 0x5D; break; case KeyEvent.KEYCODE_O: c = 0x7B; break; case KeyEvent.KEYCODE_P: c = 0x7D; break; } } else { // ALT + key switch (keyCode) { case KeyEvent.KEYCODE_S: c = 0x7c; break; case KeyEvent.KEYCODE_Z: c = 0x5c; break; } } } else if ((metaState & META_SHIFT_MASK) != 0) { // shift + key switch (keyCode) { case KeyEvent.KEYCODE_AT: c = 0x3c; break; case KeyEvent.KEYCODE_COMMA: c = 0x3e; break; case KeyEvent.KEYCODE_PERIOD: c = 0x5e; break; case KeyEvent.KEYCODE_GRAVE: c = 0x60; break; case KeyEvent.KEYCODE_APOSTROPHE: c = 0x7e; break; } } } else if (customKeyboard.equals(PreferenceConstants.CUSTOM_KEYMAP_SGH_I927)) { // Samsung Captivate Glide (SGH-i927) if (keyCode == 115) { // .com key = ESC sendEscape(); return true; } if (keyCode == 116) { // Microphone key = TAB c = 0x09; } else if ((metaState & META_ALT_MASK) != 0 && (metaState & META_SHIFT_MASK) != 0) { switch (keyCode) { case KeyEvent.KEYCODE_O: c = 0x5B; break; case KeyEvent.KEYCODE_P: c = 0x5D; break; case KeyEvent.KEYCODE_A: c = 0x3C; break; case KeyEvent.KEYCODE_D: c = 0x3E; break; } } } if (c != 0x00) { try { bridge.transport.write(c); } catch (IOException e) { Log.e(TAG, "Problem while trying to handle a custom onKey() event", e); } metaState &= ~(META_SHIFT_ON | META_ALT_ON); bridge.redraw(); return true; } return false; } public void urlScan(View v) { //final TerminalView terminalView = (TerminalView) findCurrentView(R.id.console_flip); List<String> urls = bridge.scanForURLs(); Dialog urlDialog = new Dialog(v.getContext()); urlDialog.setTitle(R.string.console_menu_urlscan); ListView urlListView = new ListView(v.getContext()); URLItemListener urlListener = new URLItemListener(v.getContext()); urlListView.setOnItemClickListener(urlListener); urlListView.setAdapter(new ArrayAdapter<String>(v.getContext(), android.R.layout.simple_list_item_1, urls)); urlDialog.setContentView(urlListView); urlDialog.show(); } private class URLItemListener implements OnItemClickListener { private WeakReference<Context> contextRef; URLItemListener(Context context) { this.contextRef = new WeakReference<Context>(context); } public void onItemClick(AdapterView<?> arg0, View view, int position, long id) { Context context = contextRef.get(); if (context == null) return; try { TextView urlView = (TextView) view; String url = urlView.getText().toString(); if (url.indexOf("://") < 0) url = "http://" + url; Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); context.startActivity(intent); } catch (Exception e) { Log.e(TAG, "couldn't open URL", e); // We should probably tell the user that we couldn't find a // handler... } } } }