/* * Copyright (C) 2007 The Android Open Source Project * Copyright (C) 2011 John Pritchard, Syntelos * * 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 ob.droid.term; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import java.util.ArrayList; import ob.droid.Connection; import ob.droid.R; /** * A terminal emulator activity. */ public abstract class Term extends Activity { /** * Set to true to add debugging code and logging. */ public static final boolean DEBUG = false; /** * Set to true to log each character received from the remote process to the * android log, which makes it easier to debug some kinds of problems with * emulating escape sequences and control codes. */ public static final boolean LOG_CHARACTERS_FLAG = DEBUG && false; /** * Set to true to log unknown escape sequences. */ public static final boolean LOG_UNKNOWN_ESCAPE_SEQUENCES = DEBUG && false; /** * The name of our emulator view in the view resource. */ private static final int EMULATOR_VIEW = R.id.emulatorView; private static final String FONTSIZE_KEY = "fontsize"; private static final String COLOR_KEY = "color"; private static final String CONTROLKEY_KEY = "controlkey"; private static final String INITIALCOMMAND_KEY = "initialcommand"; public static final int WHITE = 0xffffffff; public static final int BLACK = 0xff000000; public static final int BLUE = 0xff344ebd; private static final int[][] COLOR_SCHEMES = { {BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}}; private static final int[] CONTROL_KEY_SCHEMES = { KeyEvent.KEYCODE_DPAD_CENTER, KeyEvent.KEYCODE_AT, KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT }; private static final String[] CONTROL_KEY_NAME = { "Ball", "@", "Left-Alt", "Right-Alt" }; /** * The tag we use when logging, so that our messages can be distinguished * from other messages in the log. Public because it's used by several * classes. */ public static final String LOG_TAG = "Term"; private final static String DEFAULT_INITIAL_COMMAND = null; /** * Our main view. Displays the emulated terminal screen. */ private EmulatorView emulatorView; /** * A key listener that tracks the modifier keys and allows the full ASCII * character set to be entered. */ private TermKeyListener keyListener; private int fontSize = 9; private int colorId = 2; private int controlKeyId = 0; private int controlKeyCode; private String initialCommand; private SharedPreferences prefs; protected abstract Connection createConnection(); @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); Log.e(Term.LOG_TAG, "onCreate"); this.prefs = PreferenceManager.getDefaultSharedPreferences(this); this.readPrefs(); this.setContentView(R.layout.main); this.emulatorView = (EmulatorView) findViewById(EMULATOR_VIEW); this.connection = this.createConnection(); this.emulatorView.init(this.connection); this.sendInitialCommand(); this.keyListener = new TermKeyListener(); this.emulatorView.setFocusable(true); this.emulatorView.setFocusableInTouchMode(true); this.emulatorView.requestFocus(); this.emulatorView.register(this.keyListener); this.updatePrefs(); } @Override public void onDestroy() { super.onDestroy(); if (mTermFd != null) { /**************************************************** * Exec.close(mTermFd); * ****************************************************/ mTermFd = null; } } private void sendInitialCommand() { String initialCommand = mInitialCommand; if (initialCommand == null || 0 == initialCommand.length()) { initialCommand = DEFAULT_INITIAL_COMMAND; } if (null != initialCommand && 0 < initialCommand.length()) { this.send(initialCommand + '\n'); } } private void restart() { this.startActivity(getIntent()); this.finish(); } private void send(String data) { } private ArrayList<String> parse(String cmd) { final int PLAIN = 0; final int WHITESPACE = 1; final int INQUOTE = 2; int state = WHITESPACE; ArrayList<String> result = new ArrayList<String>(); int cmdLen = cmd.length(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < cmdLen; i++) { char c = cmd.charAt(i); if (state == PLAIN) { if (Character.isWhitespace(c)) { result.add(builder.toString()); builder.delete(0,builder.length()); state = WHITESPACE; } else if (c == '"') { state = INQUOTE; } else { builder.append(c); } } else if (state == WHITESPACE) { if (Character.isWhitespace(c)) { // do nothing } else if (c == '"') { state = INQUOTE; } else { state = PLAIN; builder.append(c); } } else if (state == INQUOTE) { if (c == '\\') { if (i + 1 < cmdLen) { i += 1; builder.append(cmd.charAt(i)); } } else if (c == '"') { state = PLAIN; } else { builder.append(c); } } } if (builder.length() > 0) { result.add(builder.toString()); } return result; } private void readPrefs() { mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 20); mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1); mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId, CONTROL_KEY_SCHEMES.length - 1); { String newInitialCommand = readStringPref(INITIALCOMMAND_KEY, mInitialCommand); if ((newInitialCommand == null) || ! newInitialCommand.equals(mInitialCommand)) { if (mInitialCommand != null) { Log.i(Term.LOG_TAG, "New initial command set. Restarting."); restart(); } mInitialCommand = newInitialCommand; } } } private void updatePrefs() { DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); mEmulatorView.setTextSize((int) (mFontSize * metrics.density)); setColors(); mControlKeyCode = CONTROL_KEY_SCHEMES[mControlKeyId]; } private int readIntPref(String key, int defaultValue, int maxValue) { int val; try { val = Integer.parseInt( mPrefs.getString(key, Integer.toString(defaultValue))); } catch (NumberFormatException e) { val = defaultValue; } val = Math.max(0, Math.min(val, maxValue)); return val; } private String readStringPref(String key, String defaultValue) { return mPrefs.getString(key, defaultValue); } @Override public void onResume() { super.onResume(); readPrefs(); updatePrefs(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mEmulatorView.updateSize(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (handleControlKey(keyCode, true)) { return true; } else if (isSystemKey(keyCode, event)) { // Don't intercept the system keys return super.onKeyDown(keyCode, event); } else if (handleDPad(keyCode, true)) { return true; } // Translate the keyCode into an ASCII character. int letter = mKeyListener.keyDown(keyCode, event); if (letter >= 0) { try { mTermOut.write(letter); } catch (IOException e) { // Ignore I/O exceptions } } return true; } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (handleControlKey(keyCode, false)) { return true; } else if (isSystemKey(keyCode, event)) { // Don't intercept the system keys return super.onKeyUp(keyCode, event); } else if (handleDPad(keyCode, false)) { return true; } mKeyListener.keyUp(keyCode); return true; } private boolean handleControlKey(int keyCode, boolean down) { if (keyCode == mControlKeyCode) { mKeyListener.handleControlKey(down); return true; } return false; } /** * Handle dpad left-right-up-down events. Don't handle * dpad-center, that's our control key. * @param keyCode * @param down */ private boolean handleDPad(int keyCode, boolean down) { if (keyCode < KeyEvent.KEYCODE_DPAD_UP || keyCode > KeyEvent.KEYCODE_DPAD_CENTER) { return false; } if (down) { try { if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { mTermOut.write('\r'); } else { char code; switch (keyCode) { case KeyEvent.KEYCODE_DPAD_UP: code = 'A'; break; case KeyEvent.KEYCODE_DPAD_DOWN: code = 'B'; break; case KeyEvent.KEYCODE_DPAD_LEFT: code = 'D'; break; default: case KeyEvent.KEYCODE_DPAD_RIGHT: code = 'C'; break; } mTermOut.write(27); // ESC if (mEmulatorView.getKeypadApplicationMode()) { mTermOut.write('O'); } else { mTermOut.write('['); } mTermOut.write(code); } } catch (IOException e) { // Ignore } } return true; } private boolean isSystemKey(int keyCode, KeyEvent event) { return event.isSystem(); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.menu_preferences) { doPreferences(); } else if (id == R.id.menu_reset) { doResetTerminal(); } return super.onOptionsItemSelected(item); } protected abstract void doPreferences(); protected void doResetTerminal() { restart(); } private void setColors() { int[] scheme = COLOR_SCHEMES[mColorId]; mEmulatorView.setColors(scheme[0], scheme[1]); } }