OSDN Git Service

The fact that Term.java contains substantially all of the classes of the
authorSteven Luo <steven+android@steven676.net>
Sat, 27 Aug 2011 19:02:31 +0000 (12:02 -0700)
committerJack Palevich <jack.palevich@gmail.com>
Sat, 27 Aug 2011 19:05:31 +0000 (12:05 -0700)
application clashes with Java's model of one class per file, and is
inconvenient when refactoring the code to allow for multiple terminal
sessions in the activity.  This patch splits out all of the classes into
separate files, and groups related classes into packages; no actual
changes to the code are made.

src/jackpal/androidterm/EmulatorView.java [new file with mode: 0644]
src/jackpal/androidterm/Term.java
src/jackpal/androidterm/TermService.java
src/jackpal/androidterm/model/Screen.java [new file with mode: 0644]
src/jackpal/androidterm/model/TextRenderer.java [new file with mode: 0644]
src/jackpal/androidterm/session/TerminalEmulator.java [new file with mode: 0644]
src/jackpal/androidterm/session/TranscriptScreen.java [new file with mode: 0644]
src/jackpal/androidterm/util/ByteQueue.java [new file with mode: 0644]
src/jackpal/androidterm/util/ServiceForegroundCompat.java [moved from src/jackpal/androidterm/ServiceForegroundCompat.java with 99% similarity]

diff --git a/src/jackpal/androidterm/EmulatorView.java b/src/jackpal/androidterm/EmulatorView.java
new file mode 100644 (file)
index 0000000..88a2249
--- /dev/null
@@ -0,0 +1,2078 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.ClipboardManager;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.inputmethod.BaseInputConnection;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+import jackpal.androidterm.model.TextRenderer;
+import jackpal.androidterm.session.TerminalEmulator;
+import jackpal.androidterm.session.TranscriptScreen;
+import jackpal.androidterm.util.ByteQueue;
+
+/**
+ * A view on a transcript and a terminal emulator. Displays the text of the
+ * transcript and the current cursor position of the terminal emulator.
+ */
+public class EmulatorView extends View implements GestureDetector.OnGestureListener {
+
+    private final String TAG = "EmulatorView";
+    private final boolean LOG_KEY_EVENTS = Term.DEBUG && false;
+
+    private Term mTerm;
+
+    /**
+     * We defer some initialization until we have been layed out in the view
+     * hierarchy. The boolean tracks when we know what our size is.
+     */
+    private boolean mKnownSize;
+
+    private int mVisibleWidth;
+    private int mVisibleHeight;
+    private Rect mVisibleRect = new Rect();
+
+    /**
+     * Our transcript. Contains the screen and the transcript.
+     */
+    private TranscriptScreen mTranscriptScreen;
+
+    /**
+     * Number of rows in the transcript.
+     */
+    private static final int TRANSCRIPT_ROWS = 10000;
+
+    /**
+     * Total width of each character, in pixels
+     */
+    private int mCharacterWidth;
+
+    /**
+     * Total height of each character, in pixels
+     */
+    private int mCharacterHeight;
+
+    /**
+     * Used to render text
+     */
+    private TextRenderer mTextRenderer;
+
+    /**
+     * Text size. Zero means 4 x 8 font.
+     */
+    private int mTextSize;
+
+    private int mCursorStyle;
+    private int mCursorBlink;
+
+    /**
+     * Foreground color.
+     */
+    private int mForeground;
+
+    /**
+     * Background color.
+     */
+    private int mBackground;
+
+    /**
+     * Used to paint the cursor
+     */
+    private Paint mCursorPaint;
+
+    private Paint mBackgroundPaint;
+
+    private boolean mUseCookedIme;
+
+    /**
+     * Our terminal emulator. We use this to get the current cursor position.
+     */
+    private TerminalEmulator mEmulator;
+
+    /**
+     * The number of rows of text to display.
+     */
+    private int mRows;
+
+    /**
+     * The number of columns of text to display.
+     */
+    private int mColumns;
+
+    /**
+     * The number of columns that are visible on the display.
+     */
+
+    private int mVisibleColumns;
+
+    /**
+     * The top row of text to display. Ranges from -activeTranscriptRows to 0
+     */
+    private int mTopRow;
+
+    private int mLeftColumn;
+
+    private FileDescriptor mTermFd;
+    /**
+     * Used to receive data from the remote process.
+     */
+    private FileInputStream mTermIn;
+
+    private FileOutputStream mTermOut;
+
+    private ByteQueue mByteQueue;
+
+    /**
+     * Used to temporarily hold data received from the remote process. Allocated
+     * once and used permanently to minimize heap thrashing.
+     */
+    private byte[] mReceiveBuffer;
+
+    /**
+     * Our private message id, which we use to receive new input from the
+     * remote process.
+     */
+    private static final int UPDATE = 1;
+
+    private static final int SCREEN_CHECK_PERIOD = 1000;
+    private static final int CURSOR_BLINK_PERIOD = 1000;
+
+    private boolean mCursorVisible = true;
+
+    private boolean mIsSelectingText = false;
+
+
+    private float mScaledDensity;
+    private static final int SELECT_TEXT_OFFSET_Y = -40;
+    private int mSelXAnchor = -1;
+    private int mSelYAnchor = -1;
+    private int mSelX1 = -1;
+    private int mSelY1 = -1;
+    private int mSelX2 = -1;
+    private int mSelY2 = -1;
+
+    /**
+     * Used to poll if the view has changed size. Wish there was a better way to do this.
+     */
+    private Runnable mCheckSize = new Runnable() {
+
+        public void run() {
+            updateSize(false);
+            mHandler.postDelayed(this, SCREEN_CHECK_PERIOD);
+        }
+    };
+
+    private Runnable mBlinkCursor = new Runnable() {
+        public void run() {
+            if (mCursorBlink != 0) {
+                mCursorVisible = ! mCursorVisible;
+                mHandler.postDelayed(this, CURSOR_BLINK_PERIOD);
+            } else {
+                mCursorVisible = true;
+            }
+            // Perhaps just invalidate the character with the cursor.
+            invalidate();
+        }
+    };
+
+    /**
+     * Thread that polls for input from the remote process
+     */
+
+    private Thread mPollingThread;
+
+    private GestureDetector mGestureDetector;
+    private float mScrollRemainder;
+    private TermKeyListener mKeyListener;
+
+    private String mImeBuffer = "";
+
+    /**
+     * Our message handler class. Implements a periodic callback.
+     */
+    private final Handler mHandler = new Handler() {
+        /**
+         * Handle the callback message. Call our enclosing class's update
+         * method.
+         *
+         * @param msg The callback message.
+         */
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == UPDATE) {
+                update();
+            }
+        }
+    };
+
+    public EmulatorView(Context context) {
+        super(context);
+        commonConstructor();
+    }
+
+    public void setScaledDensity(float scaledDensity) {
+        mScaledDensity = scaledDensity;
+    }
+
+    public void onResume() {
+        updateSize(false);
+        mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD);
+        if (mCursorBlink != 0) {
+            mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
+        }
+    }
+
+    public void onPause() {
+        mHandler.removeCallbacks(mCheckSize);
+        if (mCursorBlink != 0) {
+            mHandler.removeCallbacks(mBlinkCursor);
+        }
+    }
+
+    public void register(Term term, TermKeyListener listener) {
+        mTerm = term;
+        mKeyListener = listener;
+    }
+
+    public void setColors(int foreground, int background) {
+        mForeground = foreground;
+        mBackground = background;
+        updateText();
+    }
+
+    public String getTranscriptText() {
+        return mEmulator.getTranscriptText();
+    }
+
+    public void resetTerminal() {
+        mEmulator.reset();
+        invalidate();
+    }
+
+    @Override
+    public boolean onCheckIsTextEditor() {
+        return true;
+    }
+
+    @Override
+    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        outAttrs.inputType = mUseCookedIme ?
+                EditorInfo.TYPE_CLASS_TEXT :
+                EditorInfo.TYPE_NULL;
+        return new InputConnection() {
+            private boolean mInBatchEdit;
+            /**
+             * Used to handle composing text requests
+             */
+            private int mCursor;
+            private int mComposingTextStart;
+            private int mComposingTextEnd;
+            private int mSelectedTextStart;
+            private int mSelectedTextEnd;
+
+            private void sendChar(int c) {
+                try {
+                    mapAndSend(c);
+                } catch (IOException ex) {
+
+                }
+            }
+
+            private void sendText(CharSequence text) {
+                int n = text.length();
+                try {
+                    for(int i = 0; i < n; i++) {
+                        char c = text.charAt(i);
+                        mapAndSend(c);
+                    }
+                    mTermOut.flush();
+                } catch (IOException e) {
+                    Log.e(TAG, "error writing ", e);
+                }
+            }
+
+            private void mapAndSend(int c) throws IOException {
+                int result = mKeyListener.mapControlChar(c);
+                if (result < TermKeyListener.KEYCODE_OFFSET) {
+                    mTermOut.write(result);
+                } else {
+                    mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, mTermOut, getKeypadApplicationMode());
+                }
+            }
+
+            public boolean beginBatchEdit() {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "beginBatchEdit");
+                }
+                setImeBuffer("");
+                mCursor = 0;
+                mComposingTextStart = 0;
+                mComposingTextEnd = 0;
+                mInBatchEdit = true;
+                return true;
+            }
+
+            public boolean clearMetaKeyStates(int arg0) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "clearMetaKeyStates " + arg0);
+                }
+                return false;
+            }
+
+            public boolean commitCompletion(CompletionInfo arg0) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "commitCompletion " + arg0);
+                }
+                return false;
+            }
+
+            public boolean endBatchEdit() {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "endBatchEdit");
+                }
+                mInBatchEdit = false;
+                return true;
+            }
+
+            public boolean finishComposingText() {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "finishComposingText");
+                }
+                sendText(mImeBuffer);
+                setImeBuffer("");
+                mComposingTextStart = 0;
+                mComposingTextEnd = 0;
+                mCursor = 0;
+                return true;
+            }
+
+            public int getCursorCapsMode(int arg0) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "getCursorCapsMode(" + arg0 + ")");
+                }
+                return 0;
+            }
+
+            public ExtractedText getExtractedText(ExtractedTextRequest arg0,
+                    int arg1) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "getExtractedText" + arg0 + "," + arg1);
+                }
+                return null;
+            }
+
+            public CharSequence getTextAfterCursor(int n, int flags) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")");
+                }
+                int len = Math.min(n, mImeBuffer.length() - mCursor);
+                if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
+                    return "";
+                }
+                return mImeBuffer.substring(mCursor, mCursor + len);
+            }
+
+            public CharSequence getTextBeforeCursor(int n, int flags) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")");
+                }
+                int len = Math.min(n, mCursor);
+                if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
+                    return "";
+                }
+                return mImeBuffer.substring(mCursor-len, mCursor);
+            }
+
+            public boolean performContextMenuAction(int arg0) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "performContextMenuAction" + arg0);
+                }
+                return true;
+            }
+
+            public boolean performPrivateCommand(String arg0, Bundle arg1) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1);
+                }
+                return true;
+            }
+
+            public boolean reportFullscreenMode(boolean arg0) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "reportFullscreenMode" + arg0);
+                }
+                return true;
+            }
+
+            public boolean commitCorrection (CorrectionInfo correctionInfo) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "commitCorrection");
+                }
+                return true;
+            }
+
+            public boolean commitText(CharSequence text, int newCursorPosition) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")");
+                }
+                clearComposingText();
+                sendText(text);
+                setImeBuffer("");
+                mCursor = 0;
+                return true;
+            }
+
+            private void clearComposingText() {
+                setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
+                    mImeBuffer.substring(mComposingTextEnd));
+                if (mCursor < mComposingTextStart) {
+                    // do nothing
+                } else if (mCursor < mComposingTextEnd) {
+                    mCursor = mComposingTextStart;
+                } else {
+                    mCursor -= mComposingTextEnd - mComposingTextStart;
+                }
+                mComposingTextEnd = mComposingTextStart = 0;
+            }
+
+            public boolean deleteSurroundingText(int leftLength, int rightLength) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "deleteSurroundingText(" + leftLength +
+                            "," + rightLength + ")");
+                }
+                if (leftLength > 0) {
+                    for (int i = 0; i < leftLength; i++) {
+                        sendKeyEvent(
+                            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+                    }
+                } else if ((leftLength == 0) && (rightLength == 0)) {
+                    // Delete key held down / repeating
+                    sendKeyEvent(
+                        new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+                }
+                // TODO: handle forward deletes.
+                return true;
+            }
+
+            public boolean performEditorAction(int actionCode) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "performEditorAction(" + actionCode + ")");
+                }
+                if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
+                    // The "return" key has been pressed on the IME.
+                    sendText("\n");
+                }
+                return true;
+            }
+
+            public boolean sendKeyEvent(KeyEvent event) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "sendKeyEvent(" + event + ")");
+                }
+                // Some keys are sent here rather than to commitText.
+                // In particular, del and the digit keys are sent here.
+                // (And I have reports that the HTC Magic also sends Return here.)
+                // As a bit of defensive programming, handle every key.
+                dispatchKeyEvent(event);
+                return true;
+            }
+
+            public boolean setComposingText(CharSequence text, int newCursorPosition) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")");
+                }
+                setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
+                    text + mImeBuffer.substring(mComposingTextEnd));
+                mComposingTextEnd = mComposingTextStart + text.length();
+                mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1
+                        : mComposingTextStart - newCursorPosition;
+                return true;
+            }
+
+            public boolean setSelection(int start, int end) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "setSelection" + start + "," + end);
+                }
+                int length = mImeBuffer.length();
+                if (start == end && start > 0 && start < length) {
+                    mSelectedTextStart = mSelectedTextEnd = 0;
+                    mCursor = start;
+                } else if (start < end && start > 0 && end < length) {
+                    mSelectedTextStart = start;
+                    mSelectedTextEnd = end;
+                    mCursor = start;
+                }
+                return true;
+            }
+
+            public boolean setComposingRegion(int start, int end) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "setComposingRegion " + start + "," + end);
+                }
+                if (start < end && start > 0 && end < mImeBuffer.length()) {
+                    clearComposingText();
+                    mComposingTextStart = start;
+                    mComposingTextEnd = end;
+                }
+                return true;
+            }
+
+            public CharSequence getSelectedText(int flags) {
+                if (Term.LOG_IME) {
+                    Log.w(TAG, "getSelectedText " + flags);
+                }
+                return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1);
+            }
+
+        };
+    }
+
+    private void setImeBuffer(String buffer) {
+        if (!buffer.equals(mImeBuffer)) {
+            invalidate();
+        }
+        mImeBuffer = buffer;
+    }
+
+    public boolean getKeypadApplicationMode() {
+        return mEmulator.getKeypadApplicationMode();
+    }
+
+    public EmulatorView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public EmulatorView(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+        // TypedArray a =
+        //        context.obtainStyledAttributes(android.R.styleable.View);
+        // initializeScrollbars(a);
+        // a.recycle();
+        commonConstructor();
+    }
+
+    private void commonConstructor() {
+        mTextRenderer = null;
+        mCursorPaint = new Paint();
+        mCursorPaint.setARGB(255,128,128,128);
+        mBackgroundPaint = new Paint();
+        mTopRow = 0;
+        mLeftColumn = 0;
+        mGestureDetector = new GestureDetector(this);
+        // mGestureDetector.setIsLongpressEnabled(false);
+        setVerticalScrollBarEnabled(true);
+    }
+
+    @Override
+    protected int computeVerticalScrollRange() {
+        return mTranscriptScreen.getActiveRows();
+    }
+
+    @Override
+    protected int computeVerticalScrollExtent() {
+        return mRows;
+    }
+
+    @Override
+    protected int computeVerticalScrollOffset() {
+        return mTranscriptScreen.getActiveRows() + mTopRow - mRows;
+    }
+
+    /**
+     * Call this to initialize the view.
+     *
+     * @param termFd the file descriptor
+     * @param termOut the output stream for the pseudo-teletype
+     */
+    public void initialize(FileDescriptor termFd, FileOutputStream termOut) {
+        mTermOut = termOut;
+        mTermFd = termFd;
+        mTextSize = 10;
+        mForeground = Term.WHITE;
+        mBackground = Term.BLACK;
+        updateText();
+        mTermIn = new FileInputStream(mTermFd);
+        mReceiveBuffer = new byte[4 * 1024];
+        mByteQueue = new ByteQueue(4 * 1024);
+    }
+
+    /**
+     * Accept a sequence of bytes (typically from the pseudo-tty) and process
+     * them.
+     *
+     * @param buffer a byte array containing bytes to be processed
+     * @param base the index of the first byte in the buffer to process
+     * @param length the number of bytes to process
+     */
+    public void append(byte[] buffer, int base, int length) {
+        mEmulator.append(buffer, base, length);
+        if ( mIsSelectingText ) {
+            int rowShift = mEmulator.getScrollCounter();
+            mSelY1 -= rowShift;
+            mSelY2 -= rowShift;
+            mSelYAnchor -= rowShift;
+        }
+        mEmulator.clearScrollCounter();
+        ensureCursorVisible();
+        invalidate();
+    }
+
+    /**
+     * Page the terminal view (scroll it up or down by delta screenfulls.)
+     *
+     * @param delta the number of screens to scroll. Positive means scroll down,
+     *        negative means scroll up.
+     */
+    public void page(int delta) {
+        mTopRow =
+                Math.min(0, Math.max(-(mTranscriptScreen
+                        .getActiveTranscriptRows()), mTopRow + mRows * delta));
+        invalidate();
+    }
+
+    /**
+     * Page the terminal view horizontally.
+     *
+     * @param deltaColumns the number of columns to scroll. Positive scrolls to
+     *        the right.
+     */
+    public void pageHorizontal(int deltaColumns) {
+        mLeftColumn =
+                Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns
+                        - mVisibleColumns));
+        invalidate();
+    }
+
+    /**
+     * Sets the text size, which in turn sets the number of rows and columns
+     *
+     * @param fontSize the new font size, in pixels.
+     */
+    public void setTextSize(int fontSize) {
+        mTextSize = fontSize;
+        updateText();
+    }
+
+    public void setCursorStyle(int style, int blink) {
+        mCursorStyle = style;
+        if (blink != 0 && mCursorBlink == 0) {
+            mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
+        } else if (blink == 0 && mCursorBlink != 0) {
+            mHandler.removeCallbacks(mBlinkCursor);
+        }
+        mCursorBlink = blink;
+    }
+
+    public void setUseCookedIME(boolean useCookedIME) {
+        mUseCookedIme = useCookedIME;
+    }
+
+    // Begin GestureDetector.OnGestureListener methods
+
+    public boolean onSingleTapUp(MotionEvent e) {
+        return true;
+    }
+
+    public void onLongPress(MotionEvent e) {
+        showContextMenu();
+    }
+
+    public boolean onScroll(MotionEvent e1, MotionEvent e2,
+            float distanceX, float distanceY) {
+        distanceY += mScrollRemainder;
+        int deltaRows = (int) (distanceY / mCharacterHeight);
+        mScrollRemainder = distanceY - deltaRows * mCharacterHeight;
+        mTopRow =
+            Math.min(0, Math.max(-(mTranscriptScreen
+                    .getActiveTranscriptRows()), mTopRow + deltaRows));
+        invalidate();
+
+        return true;
+   }
+
+    public void onSingleTapConfirmed(MotionEvent e) {
+    }
+
+    public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) {
+       // Scroll to bottom
+       mTopRow = 0;
+       invalidate();
+       return true;
+    }
+
+    public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) {
+        // Scroll to top
+        mTopRow = -mTranscriptScreen.getActiveTranscriptRows();
+        invalidate();
+        return true;
+    }
+
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+            float velocityY) {
+        // TODO: add animation man's (non animated) fling
+        mScrollRemainder = 0.0f;
+        onScroll(e1, e2, 2 * velocityX, -2 * velocityY);
+        return true;
+    }
+
+    public void onShowPress(MotionEvent e) {
+    }
+
+    public boolean onDown(MotionEvent e) {
+        mScrollRemainder = 0.0f;
+        return true;
+    }
+
+    // End GestureDetector.OnGestureListener methods
+
+    @Override public boolean onTouchEvent(MotionEvent ev) {
+        if (mIsSelectingText) {
+            return onTouchEventWhileSelectingText(ev);
+        } else {
+            return mGestureDetector.onTouchEvent(ev);
+        }
+    }
+
+    private boolean onTouchEventWhileSelectingText(MotionEvent ev) {
+        int action = ev.getAction();
+        int cx = (int)(ev.getX() / mCharacterWidth);
+        int cy = Math.max(0,
+                (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity)
+                        / mCharacterHeight) + mTopRow);
+        switch (action) {
+        case MotionEvent.ACTION_DOWN:
+            mSelXAnchor = cx;
+            mSelYAnchor = cy;
+            mSelX1 = cx;
+            mSelY1 = cy;
+            mSelX2 = mSelX1;
+            mSelY2 = mSelY1;
+            break;
+        case MotionEvent.ACTION_MOVE:
+        case MotionEvent.ACTION_UP:
+            int minx = Math.min(mSelXAnchor, cx);
+            int maxx = Math.max(mSelXAnchor, cx);
+            int miny = Math.min(mSelYAnchor, cy);
+            int maxy = Math.max(mSelYAnchor, cy);
+            mSelX1 = minx;
+            mSelY1 = miny;
+            mSelX2 = maxx;
+            mSelY2 = maxy;
+            if (action == MotionEvent.ACTION_UP) {
+                ClipboardManager clip = (ClipboardManager)
+                     getContext().getApplicationContext()
+                         .getSystemService(Context.CLIPBOARD_SERVICE);
+                clip.setText(getSelectedText().trim());
+                toggleSelectingText();
+            }
+            invalidate();
+            break;
+        default:
+            toggleSelectingText();
+            invalidate();
+            break;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (LOG_KEY_EVENTS) {
+            Log.w(TAG, "onKeyDown " + keyCode);
+        }
+        if (handleControlKey(keyCode, true)) {
+            return true;
+        } else if (handleFnKey(keyCode, true)) {
+            return true;
+        } else if (isSystemKey(keyCode, event)) {
+            // Don't intercept the system keys
+            return super.onKeyDown(keyCode, event);
+        }
+
+        // Translate the keyCode into an ASCII character.
+
+        try {
+            mKeyListener.keyDown(keyCode, event, mTermOut,
+                    getKeypadApplicationMode());
+        } catch (IOException e) {
+            // Ignore I/O exceptions
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (LOG_KEY_EVENTS) {
+            Log.w(TAG, "onKeyUp " + keyCode);
+        }
+        if (handleControlKey(keyCode, false)) {
+            return true;
+        } else if (handleFnKey(keyCode, false)) {
+            return true;
+        } else if (isSystemKey(keyCode, event)) {
+            // Don't intercept the system keys
+            return super.onKeyUp(keyCode, event);
+        }
+
+        mKeyListener.keyUp(keyCode);
+        return true;
+    }
+
+
+    private boolean handleControlKey(int keyCode, boolean down) {
+        if (keyCode == mTerm.getControlKeyCode()) {
+            if (LOG_KEY_EVENTS) {
+                Log.w(TAG, "handleControlKey " + keyCode);
+            }
+            mKeyListener.handleControlKey(down);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean handleFnKey(int keyCode, boolean down) {
+        if (keyCode == mTerm.getFnKeyCode()) {
+            if (LOG_KEY_EVENTS) {
+                Log.w(TAG, "handleFnKey " + keyCode);
+            }
+            mKeyListener.handleFnKey(down);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isSystemKey(int keyCode, KeyEvent event) {
+        return event.isSystem();
+    }
+
+    private void updateText() {
+        if (mTextSize > 0) {
+            mTextRenderer = new PaintRenderer(mTextSize, mForeground,
+                    mBackground);
+        }
+        else {
+            mTextRenderer = new Bitmap4x8FontRenderer(getResources(),
+                    mForeground, mBackground);
+        }
+        mBackgroundPaint.setColor(mBackground);
+        mCharacterWidth = mTextRenderer.getCharacterWidth();
+        mCharacterHeight = mTextRenderer.getCharacterHeight();
+
+        updateSize(true);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        boolean oldKnownSize = mKnownSize;
+        if (!mKnownSize) {
+            mKnownSize = true;
+        }
+        updateSize(false);
+        if (!oldKnownSize) {
+            // Set up a thread to read input from the
+            // pseudo-teletype:
+
+            mPollingThread = new Thread(new Runnable() {
+
+                public void run() {
+                    try {
+                        while(true) {
+                            int read = mTermIn.read(mBuffer);
+                            mByteQueue.write(mBuffer, 0, read);
+                            mHandler.sendMessage(
+                                    mHandler.obtainMessage(UPDATE));
+                        }
+                    } catch (IOException e) {
+                    } catch (InterruptedException e) {
+                    }
+                }
+                private byte[] mBuffer = new byte[4096];
+            });
+            mPollingThread.setName("Input reader");
+            mPollingThread.start();
+        }
+    }
+
+    private void updateSize(int w, int h) {
+        mColumns = Math.max(1, w / mCharacterWidth);
+        mRows = Math.max(1, h / mCharacterHeight);
+        mVisibleColumns = mVisibleWidth / mCharacterWidth;
+
+        // Inform the attached pty of our new size:
+        Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h);
+
+
+        if (mTranscriptScreen != null) {
+            mEmulator.updateSize(mColumns, mRows);
+        } else {
+            mTranscriptScreen =
+                    new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7);
+            mEmulator =
+                    new TerminalEmulator(mTranscriptScreen, mColumns, mRows,
+                            mTermOut);
+        }
+
+        // Reset our paging:
+        mTopRow = 0;
+        mLeftColumn = 0;
+
+        invalidate();
+    }
+
+    void updateSize(boolean force) {
+        if (mKnownSize) {
+            getWindowVisibleDisplayFrame(mVisibleRect);
+            int w = mVisibleRect.width();
+            int h = mVisibleRect.height();
+            // Log.w("Term", "(" + w + ", " + h + ")");
+            if (force || w != mVisibleWidth || h != mVisibleHeight) {
+                mVisibleWidth = w;
+                mVisibleHeight = h;
+                updateSize(mVisibleWidth, mVisibleHeight);
+            }
+        }
+    }
+
+    /**
+     * Look for new input from the ptty, send it to the terminal emulator.
+     */
+    private void update() {
+        int bytesAvailable = mByteQueue.getBytesAvailable();
+        int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
+        try {
+            int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
+            append(mReceiveBuffer, 0, bytesRead);
+        } catch (InterruptedException e) {
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        updateSize(false);
+        int w = getWidth();
+        int h = getHeight();
+        canvas.drawRect(0, 0, w, h, mBackgroundPaint);
+        float x = -mLeftColumn * mCharacterWidth;
+        float y = mCharacterHeight;
+        int endLine = mTopRow + mRows;
+        int cx = mEmulator.getCursorCol();
+        int cy = mEmulator.getCursorRow();
+        for (int i = mTopRow; i < endLine; i++) {
+            int cursorX = -1;
+            if (i == cy && mCursorVisible) {
+                cursorX = cx;
+            }
+            int selx1 = -1;
+            int selx2 = -1;
+            if ( i >= mSelY1 && i <= mSelY2 ) {
+                if ( i == mSelY1 ) {
+                    selx1 = mSelX1;
+                }
+                if ( i == mSelY2 ) {
+                    selx2 = mSelX2;
+                } else {
+                    selx2 = mColumns;
+                }
+            }
+            mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer);
+            y += mCharacterHeight;
+        }
+    }
+
+    private void ensureCursorVisible() {
+        mTopRow = 0;
+        if (mVisibleColumns > 0) {
+            int cx = mEmulator.getCursorCol();
+            int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn;
+            if (visibleCursorX < 0) {
+                mLeftColumn = cx;
+            } else if (visibleCursorX >= mVisibleColumns) {
+                mLeftColumn = (cx - mVisibleColumns) + 1;
+            }
+        }
+    }
+
+    public void toggleSelectingText() {
+        mIsSelectingText = ! mIsSelectingText;
+        setVerticalScrollBarEnabled( ! mIsSelectingText );
+        if ( ! mIsSelectingText ) {
+            mSelX1 = -1;
+            mSelY1 = -1;
+            mSelX2 = -1;
+            mSelY2 = -1;
+        }
+    }
+
+    public boolean getSelectingText() {
+        return mIsSelectingText;
+    }
+
+    public String getSelectedText() {
+        return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2);
+    }
+}
+
+abstract class BaseTextRenderer implements TextRenderer {
+    protected int[] mForePaint = {
+            0xff000000, // Black
+            0xffff0000, // Red
+            0xff00ff00, // green
+            0xffffff00, // yellow
+            0xff0000ff, // blue
+            0xffff00ff, // magenta
+            0xff00ffff, // cyan
+            0xffffffff  // white -- is overridden by constructor
+    };
+    protected int[] mBackPaint = {
+            0xff000000, // Black -- is overridden by constructor
+            0xffcc0000, // Red
+            0xff00cc00, // green
+            0xffcccc00, // yellow
+            0xff0000cc, // blue
+            0xffff00cc, // magenta
+            0xff00cccc, // cyan
+            0xffffffff  // white
+    };
+    protected final static int mCursorPaint = 0xff808080;
+
+    public BaseTextRenderer(int forePaintColor, int backPaintColor) {
+        mForePaint[7] = forePaintColor;
+        mBackPaint[0] = backPaintColor;
+
+    }
+}
+
+class Bitmap4x8FontRenderer extends BaseTextRenderer {
+    private final static int kCharacterWidth = 4;
+    private final static int kCharacterHeight = 8;
+    private Bitmap mFont;
+    private int mCurrentForeColor;
+    private int mCurrentBackColor;
+    private float[] mColorMatrix;
+    private Paint mPaint;
+    private static final float BYTE_SCALE = 1.0f / 255.0f;
+
+    public Bitmap4x8FontRenderer(Resources resources,
+            int forePaintColor, int backPaintColor) {
+        super(forePaintColor, backPaintColor);
+        mFont = BitmapFactory.decodeResource(resources,
+                R.drawable.atari_small);
+        mPaint = new Paint();
+        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+    }
+
+    public int getCharacterWidth() {
+        return kCharacterWidth;
+    }
+
+    public int getCharacterHeight() {
+        return kCharacterHeight;
+    }
+
+    public void drawTextRun(Canvas canvas, float x, float y,
+            int lineOffset, char[] text, int index, int count,
+            boolean cursor, int foreColor, int backColor) {
+        setColorMatrix(mForePaint[foreColor & 7],
+                cursor ? mCursorPaint : mBackPaint[backColor & 7]);
+        int destX = (int) x + kCharacterWidth * lineOffset;
+        int destY = (int) y;
+        Rect srcRect = new Rect();
+        Rect destRect = new Rect();
+        destRect.top = (destY - kCharacterHeight);
+        destRect.bottom = destY;
+        for(int i = 0; i < count; i++) {
+            char c = text[i + index];
+            if ((cursor || (c != 32)) && (c < 128)) {
+                int cellX = c & 31;
+                int cellY = (c >> 5) & 3;
+                int srcX = cellX * kCharacterWidth;
+                int srcY = cellY * kCharacterHeight;
+                srcRect.set(srcX, srcY,
+                        srcX + kCharacterWidth, srcY + kCharacterHeight);
+                destRect.left = destX;
+                destRect.right = destX + kCharacterWidth;
+                canvas.drawBitmap(mFont, srcRect, destRect, mPaint);
+            }
+            destX += kCharacterWidth;
+        }
+    }
+
+    private void setColorMatrix(int foreColor, int backColor) {
+        if ((foreColor != mCurrentForeColor)
+                || (backColor != mCurrentBackColor)
+                || (mColorMatrix == null)) {
+            mCurrentForeColor = foreColor;
+            mCurrentBackColor = backColor;
+            if (mColorMatrix == null) {
+                mColorMatrix = new float[20];
+                mColorMatrix[18] = 1.0f; // Just copy Alpha
+            }
+            for (int component = 0; component < 3; component++) {
+                int rightShift = (2 - component) << 3;
+                int fore = 0xff & (foreColor >> rightShift);
+                int back = 0xff & (backColor >> rightShift);
+                int delta = back - fore;
+                mColorMatrix[component * 6] = delta * BYTE_SCALE;
+                mColorMatrix[component * 5 + 4] = fore;
+            }
+            mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
+        }
+    }
+}
+
+class PaintRenderer extends BaseTextRenderer {
+    public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) {
+        super(forePaintColor, backPaintColor);
+        mTextPaint = new Paint();
+        mTextPaint.setTypeface(Typeface.MONOSPACE);
+        mTextPaint.setAntiAlias(true);
+        mTextPaint.setTextSize(fontSize);
+
+        mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing());
+        mCharAscent = (int) Math.ceil(mTextPaint.ascent());
+        mCharDescent = mCharHeight + mCharAscent;
+        mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1);
+    }
+
+    public void drawTextRun(Canvas canvas, float x, float y, int lineOffset,
+            char[] text, int index, int count,
+            boolean cursor, int foreColor, int backColor) {
+        if (cursor) {
+            mTextPaint.setColor(mCursorPaint);
+        } else {
+            mTextPaint.setColor(mBackPaint[backColor & 0x7]);
+        }
+        float left = x + lineOffset * mCharWidth;
+        canvas.drawRect(left, y + mCharAscent,
+                left + count * mCharWidth, y + mCharDescent,
+                mTextPaint);
+        boolean bold = ( foreColor & 0x8 ) != 0;
+        boolean underline = (backColor & 0x8) != 0;
+        if (bold) {
+            mTextPaint.setFakeBoldText(true);
+        }
+        if (underline) {
+            mTextPaint.setUnderlineText(true);
+        }
+        mTextPaint.setColor(mForePaint[foreColor & 0x7]);
+        canvas.drawText(text, index, count, left, y, mTextPaint);
+        if (bold) {
+            mTextPaint.setFakeBoldText(false);
+        }
+        if (underline) {
+            mTextPaint.setUnderlineText(false);
+        }
+    }
+
+    public int getCharacterHeight() {
+        return mCharHeight;
+    }
+
+    public int getCharacterWidth() {
+        return mCharWidth;
+    }
+
+
+    private Paint mTextPaint;
+    private int mCharWidth;
+    private int mCharHeight;
+    private int mCharAscent;
+    private int mCharDescent;
+    private static final char[] EXAMPLE_CHAR = {'X'};
+    }
+
+/**
+ * An ASCII key listener. Supports control characters and escape. Keeps track of
+ * the current state of the alt, shift, and control keys.
+ */
+class TermKeyListener {
+    /**
+     * Android key codes that are defined in the Android 2.3 API.
+     * We want to recognize these codes, because they will be sent to our
+     * app when we run on Android 2.3 systems.
+     * But we don't want to accidentally use 2.3-specific APIs.
+     * So we compile against the Android 1.6 APIs, and have a copy of the codes here.
+     */
+
+    /** Key code constant: Unknown key code. */
+    public static final int KEYCODE_UNKNOWN         = 0;
+    /** Key code constant: Soft Left key.
+     * Usually situated below the display on phones and used as a multi-function
+     * feature key for selecting a software defined function shown on the bottom left
+     * of the display. */
+    public static final int KEYCODE_SOFT_LEFT       = 1;
+    /** Key code constant: Soft Right key.
+     * Usually situated below the display on phones and used as a multi-function
+     * feature key for selecting a software defined function shown on the bottom right
+     * of the display. */
+    public static final int KEYCODE_SOFT_RIGHT      = 2;
+    /** Key code constant: Home key.
+     * This key is handled by the framework and is never delivered to applications. */
+    public static final int KEYCODE_HOME            = 3;
+    /** Key code constant: Back key. */
+    public static final int KEYCODE_BACK            = 4;
+    /** Key code constant: Call key. */
+    public static final int KEYCODE_CALL            = 5;
+    /** Key code constant: End Call key. */
+    public static final int KEYCODE_ENDCALL         = 6;
+    /** Key code constant: '0' key. */
+    public static final int KEYCODE_0               = 7;
+    /** Key code constant: '1' key. */
+    public static final int KEYCODE_1               = 8;
+    /** Key code constant: '2' key. */
+    public static final int KEYCODE_2               = 9;
+    /** Key code constant: '3' key. */
+    public static final int KEYCODE_3               = 10;
+    /** Key code constant: '4' key. */
+    public static final int KEYCODE_4               = 11;
+    /** Key code constant: '5' key. */
+    public static final int KEYCODE_5               = 12;
+    /** Key code constant: '6' key. */
+    public static final int KEYCODE_6               = 13;
+    /** Key code constant: '7' key. */
+    public static final int KEYCODE_7               = 14;
+    /** Key code constant: '8' key. */
+    public static final int KEYCODE_8               = 15;
+    /** Key code constant: '9' key. */
+    public static final int KEYCODE_9               = 16;
+    /** Key code constant: '*' key. */
+    public static final int KEYCODE_STAR            = 17;
+    /** Key code constant: '#' key. */
+    public static final int KEYCODE_POUND           = 18;
+    /** Key code constant: Directional Pad Up key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_UP         = 19;
+    /** Key code constant: Directional Pad Down key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_DOWN       = 20;
+    /** Key code constant: Directional Pad Left key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_LEFT       = 21;
+    /** Key code constant: Directional Pad Right key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_RIGHT      = 22;
+    /** Key code constant: Directional Pad Center key.
+     * May also be synthesized from trackball motions. */
+    public static final int KEYCODE_DPAD_CENTER     = 23;
+    /** Key code constant: Volume Up key.
+     * Adjusts the speaker volume up. */
+    public static final int KEYCODE_VOLUME_UP       = 24;
+    /** Key code constant: Volume Down key.
+     * Adjusts the speaker volume down. */
+    public static final int KEYCODE_VOLUME_DOWN     = 25;
+    /** Key code constant: Power key. */
+    public static final int KEYCODE_POWER           = 26;
+    /** Key code constant: Camera key.
+     * Used to launch a camera application or take pictures. */
+    public static final int KEYCODE_CAMERA          = 27;
+    /** Key code constant: Clear key. */
+    public static final int KEYCODE_CLEAR           = 28;
+    /** Key code constant: 'A' key. */
+    public static final int KEYCODE_A               = 29;
+    /** Key code constant: 'B' key. */
+    public static final int KEYCODE_B               = 30;
+    /** Key code constant: 'C' key. */
+    public static final int KEYCODE_C               = 31;
+    /** Key code constant: 'D' key. */
+    public static final int KEYCODE_D               = 32;
+    /** Key code constant: 'E' key. */
+    public static final int KEYCODE_E               = 33;
+    /** Key code constant: 'F' key. */
+    public static final int KEYCODE_F               = 34;
+    /** Key code constant: 'G' key. */
+    public static final int KEYCODE_G               = 35;
+    /** Key code constant: 'H' key. */
+    public static final int KEYCODE_H               = 36;
+    /** Key code constant: 'I' key. */
+    public static final int KEYCODE_I               = 37;
+    /** Key code constant: 'J' key. */
+    public static final int KEYCODE_J               = 38;
+    /** Key code constant: 'K' key. */
+    public static final int KEYCODE_K               = 39;
+    /** Key code constant: 'L' key. */
+    public static final int KEYCODE_L               = 40;
+    /** Key code constant: 'M' key. */
+    public static final int KEYCODE_M               = 41;
+    /** Key code constant: 'N' key. */
+    public static final int KEYCODE_N               = 42;
+    /** Key code constant: 'O' key. */
+    public static final int KEYCODE_O               = 43;
+    /** Key code constant: 'P' key. */
+    public static final int KEYCODE_P               = 44;
+    /** Key code constant: 'Q' key. */
+    public static final int KEYCODE_Q               = 45;
+    /** Key code constant: 'R' key. */
+    public static final int KEYCODE_R               = 46;
+    /** Key code constant: 'S' key. */
+    public static final int KEYCODE_S               = 47;
+    /** Key code constant: 'T' key. */
+    public static final int KEYCODE_T               = 48;
+    /** Key code constant: 'U' key. */
+    public static final int KEYCODE_U               = 49;
+    /** Key code constant: 'V' key. */
+    public static final int KEYCODE_V               = 50;
+    /** Key code constant: 'W' key. */
+    public static final int KEYCODE_W               = 51;
+    /** Key code constant: 'X' key. */
+    public static final int KEYCODE_X               = 52;
+    /** Key code constant: 'Y' key. */
+    public static final int KEYCODE_Y               = 53;
+    /** Key code constant: 'Z' key. */
+    public static final int KEYCODE_Z               = 54;
+    /** Key code constant: ',' key. */
+    public static final int KEYCODE_COMMA           = 55;
+    /** Key code constant: '.' key. */
+    public static final int KEYCODE_PERIOD          = 56;
+    /** Key code constant: Left Alt modifier key. */
+    public static final int KEYCODE_ALT_LEFT        = 57;
+    /** Key code constant: Right Alt modifier key. */
+    public static final int KEYCODE_ALT_RIGHT       = 58;
+    /** Key code constant: Left Shift modifier key. */
+    public static final int KEYCODE_SHIFT_LEFT      = 59;
+    /** Key code constant: Right Shift modifier key. */
+    public static final int KEYCODE_SHIFT_RIGHT     = 60;
+    /** Key code constant: Tab key. */
+    public static final int KEYCODE_TAB             = 61;
+    /** Key code constant: Space key. */
+    public static final int KEYCODE_SPACE           = 62;
+    /** Key code constant: Symbol modifier key.
+     * Used to enter alternate symbols. */
+    public static final int KEYCODE_SYM             = 63;
+    /** Key code constant: Explorer special function key.
+     * Used to launch a browser application. */
+    public static final int KEYCODE_EXPLORER        = 64;
+    /** Key code constant: Envelope special function key.
+     * Used to launch a mail application. */
+    public static final int KEYCODE_ENVELOPE        = 65;
+    /** Key code constant: Enter key. */
+    public static final int KEYCODE_ENTER           = 66;
+    /** Key code constant: Backspace key.
+     * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
+    public static final int KEYCODE_DEL             = 67;
+    /** Key code constant: '`' (backtick) key. */
+    public static final int KEYCODE_GRAVE           = 68;
+    /** Key code constant: '-'. */
+    public static final int KEYCODE_MINUS           = 69;
+    /** Key code constant: '=' key. */
+    public static final int KEYCODE_EQUALS          = 70;
+    /** Key code constant: '[' key. */
+    public static final int KEYCODE_LEFT_BRACKET    = 71;
+    /** Key code constant: ']' key. */
+    public static final int KEYCODE_RIGHT_BRACKET   = 72;
+    /** Key code constant: '\' key. */
+    public static final int KEYCODE_BACKSLASH       = 73;
+    /** Key code constant: ';' key. */
+    public static final int KEYCODE_SEMICOLON       = 74;
+    /** Key code constant: ''' (apostrophe) key. */
+    public static final int KEYCODE_APOSTROPHE      = 75;
+    /** Key code constant: '/' key. */
+    public static final int KEYCODE_SLASH           = 76;
+    /** Key code constant: '@' key. */
+    public static final int KEYCODE_AT              = 77;
+    /** Key code constant: Number modifier key.
+     * Used to enter numeric symbols.
+     * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
+     * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
+    public static final int KEYCODE_NUM             = 78;
+    /** Key code constant: Headset Hook key.
+     * Used to hang up calls and stop media. */
+    public static final int KEYCODE_HEADSETHOOK     = 79;
+    /** Key code constant: Camera Focus key.
+     * Used to focus the camera. */
+    public static final int KEYCODE_FOCUS           = 80;   // *Camera* focus
+    /** Key code constant: '+' key. */
+    public static final int KEYCODE_PLUS            = 81;
+    /** Key code constant: Menu key. */
+    public static final int KEYCODE_MENU            = 82;
+    /** Key code constant: Notification key. */
+    public static final int KEYCODE_NOTIFICATION    = 83;
+    /** Key code constant: Search key. */
+    public static final int KEYCODE_SEARCH          = 84;
+    /** Key code constant: Play/Pause media key. */
+    public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
+    /** Key code constant: Stop media key. */
+    public static final int KEYCODE_MEDIA_STOP      = 86;
+    /** Key code constant: Play Next media key. */
+    public static final int KEYCODE_MEDIA_NEXT      = 87;
+    /** Key code constant: Play Previous media key. */
+    public static final int KEYCODE_MEDIA_PREVIOUS  = 88;
+    /** Key code constant: Rewind media key. */
+    public static final int KEYCODE_MEDIA_REWIND    = 89;
+    /** Key code constant: Fast Forward media key. */
+    public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
+    /** Key code constant: Mute key.
+     * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
+    public static final int KEYCODE_MUTE            = 91;
+    /** Key code constant: Page Up key. */
+    public static final int KEYCODE_PAGE_UP         = 92;
+    /** Key code constant: Page Down key. */
+    public static final int KEYCODE_PAGE_DOWN       = 93;
+    /** Key code constant: Picture Symbols modifier key.
+     * Used to switch symbol sets (Emoji, Kao-moji). */
+    public static final int KEYCODE_PICTSYMBOLS     = 94;   // switch symbol-sets (Emoji,Kao-moji)
+    /** Key code constant: Switch Charset modifier key.
+     * Used to switch character sets (Kanji, Katakana). */
+    public static final int KEYCODE_SWITCH_CHARSET  = 95;   // switch char-sets (Kanji,Katakana)
+    /** Key code constant: A Button key.
+     * On a game controller, the A button should be either the button labeled A
+     * or the first button on the upper row of controller buttons. */
+    public static final int KEYCODE_BUTTON_A        = 96;
+    /** Key code constant: B Button key.
+     * On a game controller, the B button should be either the button labeled B
+     * or the second button on the upper row of controller buttons. */
+    public static final int KEYCODE_BUTTON_B        = 97;
+    /** Key code constant: C Button key.
+     * On a game controller, the C button should be either the button labeled C
+     * or the third button on the upper row of controller buttons. */
+    public static final int KEYCODE_BUTTON_C        = 98;
+    /** Key code constant: X Button key.
+     * On a game controller, the X button should be either the button labeled X
+     * or the first button on the lower row of controller buttons. */
+    public static final int KEYCODE_BUTTON_X        = 99;
+    /** Key code constant: Y Button key.
+     * On a game controller, the Y button should be either the button labeled Y
+     * or the second button on the lower row of controller buttons. */
+    public static final int KEYCODE_BUTTON_Y        = 100;
+    /** Key code constant: Z Button key.
+     * On a game controller, the Z button should be either the button labeled Z
+     * or the third button on the lower row of controller buttons. */
+    public static final int KEYCODE_BUTTON_Z        = 101;
+    /** Key code constant: L1 Button key.
+     * On a game controller, the L1 button should be either the button labeled L1 (or L)
+     * or the top left trigger button. */
+    public static final int KEYCODE_BUTTON_L1       = 102;
+    /** Key code constant: R1 Button key.
+     * On a game controller, the R1 button should be either the button labeled R1 (or R)
+     * or the top right trigger button. */
+    public static final int KEYCODE_BUTTON_R1       = 103;
+    /** Key code constant: L2 Button key.
+     * On a game controller, the L2 button should be either the button labeled L2
+     * or the bottom left trigger button. */
+    public static final int KEYCODE_BUTTON_L2       = 104;
+    /** Key code constant: R2 Button key.
+     * On a game controller, the R2 button should be either the button labeled R2
+     * or the bottom right trigger button. */
+    public static final int KEYCODE_BUTTON_R2       = 105;
+    /** Key code constant: Left Thumb Button key.
+     * On a game controller, the left thumb button indicates that the left (or only)
+     * joystick is pressed. */
+    public static final int KEYCODE_BUTTON_THUMBL   = 106;
+    /** Key code constant: Right Thumb Button key.
+     * On a game controller, the right thumb button indicates that the right
+     * joystick is pressed. */
+    public static final int KEYCODE_BUTTON_THUMBR   = 107;
+    /** Key code constant: Start Button key.
+     * On a game controller, the button labeled Start. */
+    public static final int KEYCODE_BUTTON_START    = 108;
+    /** Key code constant: Select Button key.
+     * On a game controller, the button labeled Select. */
+    public static final int KEYCODE_BUTTON_SELECT   = 109;
+    /** Key code constant: Mode Button key.
+     * On a game controller, the button labeled Mode. */
+    public static final int KEYCODE_BUTTON_MODE     = 110;
+    /** Key code constant: Escape key. */
+    public static final int KEYCODE_ESCAPE          = 111;
+    /** Key code constant: Forward Delete key.
+     * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
+    public static final int KEYCODE_FORWARD_DEL     = 112;
+    /** Key code constant: Left Control modifier key. */
+    public static final int KEYCODE_CTRL_LEFT       = 113;
+    /** Key code constant: Right Control modifier key. */
+    public static final int KEYCODE_CTRL_RIGHT      = 114;
+    /** Key code constant: Caps Lock modifier key. */
+    public static final int KEYCODE_CAPS_LOCK       = 115;
+    /** Key code constant: Scroll Lock key. */
+    public static final int KEYCODE_SCROLL_LOCK     = 116;
+    /** Key code constant: Left Meta modifier key. */
+    public static final int KEYCODE_META_LEFT       = 117;
+    /** Key code constant: Right Meta modifier key. */
+    public static final int KEYCODE_META_RIGHT      = 118;
+    /** Key code constant: Function modifier key. */
+    public static final int KEYCODE_FUNCTION        = 119;
+    /** Key code constant: System Request / Print Screen key. */
+    public static final int KEYCODE_SYSRQ           = 120;
+    /** Key code constant: Break / Pause key. */
+    public static final int KEYCODE_BREAK           = 121;
+    /** Key code constant: Home Movement key.
+     * Used for scrolling or moving the cursor around to the start of a line
+     * or to the top of a list. */
+    public static final int KEYCODE_MOVE_HOME       = 122;
+    /** Key code constant: End Movement key.
+     * Used for scrolling or moving the cursor around to the end of a line
+     * or to the bottom of a list. */
+    public static final int KEYCODE_MOVE_END        = 123;
+    /** Key code constant: Insert key.
+     * Toggles insert / overwrite edit mode. */
+    public static final int KEYCODE_INSERT          = 124;
+    /** Key code constant: Forward key.
+     * Navigates forward in the history stack.  Complement of {@link #KEYCODE_BACK}. */
+    public static final int KEYCODE_FORWARD         = 125;
+    /** Key code constant: Play media key. */
+    public static final int KEYCODE_MEDIA_PLAY      = 126;
+    /** Key code constant: Pause media key. */
+    public static final int KEYCODE_MEDIA_PAUSE     = 127;
+    /** Key code constant: Close media key.
+     * May be used to close a CD tray, for example. */
+    public static final int KEYCODE_MEDIA_CLOSE     = 128;
+    /** Key code constant: Eject media key.
+     * May be used to eject a CD tray, for example. */
+    public static final int KEYCODE_MEDIA_EJECT     = 129;
+    /** Key code constant: Record media key. */
+    public static final int KEYCODE_MEDIA_RECORD    = 130;
+    /** Key code constant: F1 key. */
+    public static final int KEYCODE_F1              = 131;
+    /** Key code constant: F2 key. */
+    public static final int KEYCODE_F2              = 132;
+    /** Key code constant: F3 key. */
+    public static final int KEYCODE_F3              = 133;
+    /** Key code constant: F4 key. */
+    public static final int KEYCODE_F4              = 134;
+    /** Key code constant: F5 key. */
+    public static final int KEYCODE_F5              = 135;
+    /** Key code constant: F6 key. */
+    public static final int KEYCODE_F6              = 136;
+    /** Key code constant: F7 key. */
+    public static final int KEYCODE_F7              = 137;
+    /** Key code constant: F8 key. */
+    public static final int KEYCODE_F8              = 138;
+    /** Key code constant: F9 key. */
+    public static final int KEYCODE_F9              = 139;
+    /** Key code constant: F10 key. */
+    public static final int KEYCODE_F10             = 140;
+    /** Key code constant: F11 key. */
+    public static final int KEYCODE_F11             = 141;
+    /** Key code constant: F12 key. */
+    public static final int KEYCODE_F12             = 142;
+    /** Key code constant: Num Lock modifier key.
+     * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
+     * This key generally modifies the behavior of other keys on the numeric keypad. */
+    public static final int KEYCODE_NUM_LOCK        = 143;
+    /** Key code constant: Numeric keypad '0' key. */
+    public static final int KEYCODE_NUMPAD_0        = 144;
+    /** Key code constant: Numeric keypad '1' key. */
+    public static final int KEYCODE_NUMPAD_1        = 145;
+    /** Key code constant: Numeric keypad '2' key. */
+    public static final int KEYCODE_NUMPAD_2        = 146;
+    /** Key code constant: Numeric keypad '3' key. */
+    public static final int KEYCODE_NUMPAD_3        = 147;
+    /** Key code constant: Numeric keypad '4' key. */
+    public static final int KEYCODE_NUMPAD_4        = 148;
+    /** Key code constant: Numeric keypad '5' key. */
+    public static final int KEYCODE_NUMPAD_5        = 149;
+    /** Key code constant: Numeric keypad '6' key. */
+    public static final int KEYCODE_NUMPAD_6        = 150;
+    /** Key code constant: Numeric keypad '7' key. */
+    public static final int KEYCODE_NUMPAD_7        = 151;
+    /** Key code constant: Numeric keypad '8' key. */
+    public static final int KEYCODE_NUMPAD_8        = 152;
+    /** Key code constant: Numeric keypad '9' key. */
+    public static final int KEYCODE_NUMPAD_9        = 153;
+    /** Key code constant: Numeric keypad '/' key (for division). */
+    public static final int KEYCODE_NUMPAD_DIVIDE   = 154;
+    /** Key code constant: Numeric keypad '*' key (for multiplication). */
+    public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
+    /** Key code constant: Numeric keypad '-' key (for subtraction). */
+    public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
+    /** Key code constant: Numeric keypad '+' key (for addition). */
+    public static final int KEYCODE_NUMPAD_ADD      = 157;
+    /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
+    public static final int KEYCODE_NUMPAD_DOT      = 158;
+    /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
+    public static final int KEYCODE_NUMPAD_COMMA    = 159;
+    /** Key code constant: Numeric keypad Enter key. */
+    public static final int KEYCODE_NUMPAD_ENTER    = 160;
+    /** Key code constant: Numeric keypad '=' key. */
+    public static final int KEYCODE_NUMPAD_EQUALS   = 161;
+    /** Key code constant: Numeric keypad '(' key. */
+    public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
+    /** Key code constant: Numeric keypad ')' key. */
+    public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
+    /** Key code constant: Volume Mute key.
+     * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
+     * This key should normally be implemented as a toggle such that the first press
+     * mutes the speaker and the second press restores the original volume. */
+    public static final int KEYCODE_VOLUME_MUTE     = 164;
+    /** Key code constant: Info key.
+     * Common on TV remotes to show additional information related to what is
+     * currently being viewed. */
+    public static final int KEYCODE_INFO            = 165;
+    /** Key code constant: Channel up key.
+     * On TV remotes, increments the television channel. */
+    public static final int KEYCODE_CHANNEL_UP      = 166;
+    /** Key code constant: Channel down key.
+     * On TV remotes, decrements the television channel. */
+    public static final int KEYCODE_CHANNEL_DOWN    = 167;
+    /** Key code constant: Zoom in key. */
+    public static final int KEYCODE_ZOOM_IN         = 168;
+    /** Key code constant: Zoom out key. */
+    public static final int KEYCODE_ZOOM_OUT        = 169;
+    /** Key code constant: TV key.
+     * On TV remotes, switches to viewing live TV. */
+    public static final int KEYCODE_TV              = 170;
+    /** Key code constant: Window key.
+     * On TV remotes, toggles picture-in-picture mode or other windowing functions. */
+    public static final int KEYCODE_WINDOW          = 171;
+    /** Key code constant: Guide key.
+     * On TV remotes, shows a programming guide. */
+    public static final int KEYCODE_GUIDE           = 172;
+    /** Key code constant: DVR key.
+     * On some TV remotes, switches to a DVR mode for recorded shows. */
+    public static final int KEYCODE_DVR             = 173;
+    /** Key code constant: Bookmark key.
+     * On some TV remotes, bookmarks content or web pages. */
+    public static final int KEYCODE_BOOKMARK        = 174;
+    /** Key code constant: Toggle captions key.
+     * Switches the mode for closed-captioning text, for example during television shows. */
+    public static final int KEYCODE_CAPTIONS        = 175;
+    /** Key code constant: Settings key.
+     * Starts the system settings activity. */
+    public static final int KEYCODE_SETTINGS        = 176;
+    /** Key code constant: TV power key.
+     * On TV remotes, toggles the power on a television screen. */
+    public static final int KEYCODE_TV_POWER        = 177;
+    /** Key code constant: TV input key.
+     * On TV remotes, switches the input on a television screen. */
+    public static final int KEYCODE_TV_INPUT        = 178;
+    /** Key code constant: Set-top-box power key.
+     * On TV remotes, toggles the power on an external Set-top-box. */
+    public static final int KEYCODE_STB_POWER       = 179;
+    /** Key code constant: Set-top-box input key.
+     * On TV remotes, switches the input mode on an external Set-top-box. */
+    public static final int KEYCODE_STB_INPUT       = 180;
+    /** Key code constant: A/V Receiver power key.
+     * On TV remotes, toggles the power on an external A/V Receiver. */
+    public static final int KEYCODE_AVR_POWER       = 181;
+    /** Key code constant: A/V Receiver input key.
+     * On TV remotes, switches the input mode on an external A/V Receiver. */
+    public static final int KEYCODE_AVR_INPUT       = 182;
+    /** Key code constant: Red "programmable" key.
+     * On TV remotes, acts as a contextual/programmable key. */
+    public static final int KEYCODE_PROG_RED        = 183;
+    /** Key code constant: Green "programmable" key.
+     * On TV remotes, actsas a contextual/programmable key. */
+    public static final int KEYCODE_PROG_GREEN      = 184;
+    /** Key code constant: Yellow "programmable" key.
+     * On TV remotes, acts as a contextual/programmable key. */
+    public static final int KEYCODE_PROG_YELLOW     = 185;
+    /** Key code constant: Blue "programmable" key.
+     * On TV remotes, acts as a contextual/programmable key. */
+    public static final int KEYCODE_PROG_BLUE       = 186;
+
+    private static final int LAST_KEYCODE           = KEYCODE_PROG_BLUE;
+
+    private String[] mKeyCodes = new String[256];
+    private String[] mAppKeyCodes = new String[256];
+
+    private void initKeyCodes() {
+        mKeyCodes[KEYCODE_DPAD_CENTER] = "\015";
+        mKeyCodes[KEYCODE_DPAD_UP] = "\033[A";
+        mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B";
+        mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C";
+        mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D";
+        mKeyCodes[KEYCODE_F1] = "\033[OP";
+        mKeyCodes[KEYCODE_F2] = "\033[OQ";
+        mKeyCodes[KEYCODE_F3] = "\033[OR";
+        mKeyCodes[KEYCODE_F4] = "\033[OS";
+        mKeyCodes[KEYCODE_F5] = "\033[15~";
+        mKeyCodes[KEYCODE_F6] = "\033[17~";
+        mKeyCodes[KEYCODE_F7] = "\033[18~";
+        mKeyCodes[KEYCODE_F8] = "\033[19~";
+        mKeyCodes[KEYCODE_F9] = "\033[20~";
+        mKeyCodes[KEYCODE_F10] = "\033[21~";
+        mKeyCodes[KEYCODE_F11] = "\033[23~";
+        mKeyCodes[KEYCODE_F12] = "\033[24~";
+        mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print
+        // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~";
+        mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break
+
+        mKeyCodes[KEYCODE_TAB] = "\011";
+        mKeyCodes[KEYCODE_ENTER] = "\015";
+        mKeyCodes[KEYCODE_ESCAPE] = "\033";
+
+        mKeyCodes[KEYCODE_INSERT] = "\033[2~";
+        mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~";
+        mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~";
+        mKeyCodes[KEYCODE_MOVE_END] = "\033[4~";
+        mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~";
+        mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~";
+        mKeyCodes[KEYCODE_DEL]= "\177";
+        mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP";
+        mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/";
+        mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*";
+        mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-";
+        mKeyCodes[KEYCODE_NUMPAD_ADD] = "+";
+        mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015";
+        mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "=";
+        mKeyCodes[KEYCODE_NUMPAD_DOT] = ".";
+        mKeyCodes[KEYCODE_NUMPAD_COMMA] = ",";
+        mKeyCodes[KEYCODE_NUMPAD_0] = "0";
+        mKeyCodes[KEYCODE_NUMPAD_1] = "1";
+        mKeyCodes[KEYCODE_NUMPAD_2] = "2";
+        mKeyCodes[KEYCODE_NUMPAD_3] = "3";
+        mKeyCodes[KEYCODE_NUMPAD_4] = "4";
+        mKeyCodes[KEYCODE_NUMPAD_5] = "5";
+        mKeyCodes[KEYCODE_NUMPAD_6] = "6";
+        mKeyCodes[KEYCODE_NUMPAD_7] = "7";
+        mKeyCodes[KEYCODE_NUMPAD_8] = "8";
+        mKeyCodes[KEYCODE_NUMPAD_9] = "9";
+
+        mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA";
+        mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB";
+        mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC";
+        mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD";
+        mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo";
+        mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj";
+        mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om";
+        mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok";
+        mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM";
+        mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX";
+        mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On";
+        mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol";
+        mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op";
+        mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq";
+        mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or";
+        mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os";
+        mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot";
+        mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou";
+        mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov";
+        mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow";
+        mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox";
+        mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy";
+    }
+
+    /**
+     * The state engine for a modifier key. Can be pressed, released, locked,
+     * and so on.
+     *
+     */
+    private class ModifierKey {
+
+        private int mState;
+
+        private static final int UNPRESSED = 0;
+
+        private static final int PRESSED = 1;
+
+        private static final int RELEASED = 2;
+
+        private static final int USED = 3;
+
+        private static final int LOCKED = 4;
+
+        /**
+         * Construct a modifier key. UNPRESSED by default.
+         *
+         */
+        public ModifierKey() {
+            mState = UNPRESSED;
+        }
+
+        public void onPress() {
+            switch (mState) {
+            case PRESSED:
+                // This is a repeat before use
+                break;
+            case RELEASED:
+                mState = LOCKED;
+                break;
+            case USED:
+                // This is a repeat after use
+                break;
+            case LOCKED:
+                mState = UNPRESSED;
+                break;
+            default:
+                mState = PRESSED;
+                break;
+            }
+        }
+
+        public void onRelease() {
+            switch (mState) {
+            case USED:
+                mState = UNPRESSED;
+                break;
+            case PRESSED:
+                mState = RELEASED;
+                break;
+            default:
+                // Leave state alone
+                break;
+            }
+        }
+
+        public void adjustAfterKeypress() {
+            switch (mState) {
+            case PRESSED:
+                mState = USED;
+                break;
+            case RELEASED:
+                mState = UNPRESSED;
+                break;
+            default:
+                // Leave state alone
+                break;
+            }
+        }
+
+        public boolean isActive() {
+            return mState != UNPRESSED;
+        }
+    }
+
+    private ModifierKey mAltKey = new ModifierKey();
+
+    private ModifierKey mCapKey = new ModifierKey();
+
+    private ModifierKey mControlKey = new ModifierKey();
+
+    private ModifierKey mFnKey = new ModifierKey();
+
+    private boolean mCapsLock;
+
+    static public final int KEYCODE_OFFSET = 1000;
+
+    /**
+     * Construct a term key listener.
+     *
+     */
+    public TermKeyListener() {
+        initKeyCodes();
+    }
+
+    public void handleControlKey(boolean down) {
+        if (down) {
+            mControlKey.onPress();
+        } else {
+            mControlKey.onRelease();
+        }
+    }
+
+    public void handleFnKey(boolean down) {
+        if (down) {
+            mFnKey.onPress();
+        } else {
+            mFnKey.onRelease();
+        }
+    }
+
+    public int mapControlChar(int ch) {
+        int result = ch;
+        if (mControlKey.isActive()) {
+            // Search is the control key.
+            if (result >= 'a' && result <= 'z') {
+                result = (char) (result - 'a' + '\001');
+            } else if (result >= 'A' && result <= 'Z') {
+                result = (char) (result - 'A' + '\001');
+            } else if (result == ' ' || result == '2') {
+                result = 0;
+            } else if (result == '[' || result == '3') {
+                result = 27; // ^[ (Esc)
+            } else if (result == '\\' || result == '4') {
+                result = 28;
+            } else if (result == ']' || result == '5') {
+                result = 29;
+            } else if (result == '^' || result == '6') {
+                result = 30; // control-^
+            } else if (result == '_' || result == '7') {
+                result = 31;
+            } else if (result == '8') {
+                result = 127; // DEL
+            } else if (result == '9') {
+                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11;
+            } else if (result == '0') {
+                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12;
+            }
+        } else if (mFnKey.isActive()) {
+            if (result == 'w' || result == 'W') {
+                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP;
+            } else if (result == 'a' || result == 'A') {
+                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT;
+            } else if (result == 's' || result == 'S') {
+                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN;
+            } else if (result == 'd' || result == 'D') {
+                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT;
+            } else if (result == 'p' || result == 'P') {
+                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP;
+            } else if (result == 'n' || result == 'N') {
+                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN;
+            } else if (result == 't' || result == 'T') {
+                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB;
+            } else if (result == 'l' || result == 'L') {
+                result = '|';
+            } else if (result == 'u' || result == 'U') {
+                result = '_';
+            } else if (result == 'e' || result == 'E') {
+                result = 27; // ^[ (Esc)
+            } else if (result == '.') {
+                result = 28; // ^\
+            } else if (result > '0' && result <= '9') {
+                // F1-F9
+                result = (char)(result + KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 - 1);
+            } else if (result == '0') {
+                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10;
+            } else if (result == 'i' || result == 'I') {
+                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_INSERT;
+            } else if (result == 'x' || result == 'X') {
+                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL;
+            } else if (result == 'h' || result == 'H') {
+                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_HOME;
+            } else if (result == 'f' || result == 'F') {
+                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_END;
+            }
+        }
+
+        if (result > -1) {
+            mAltKey.adjustAfterKeypress();
+            mCapKey.adjustAfterKeypress();
+            mControlKey.adjustAfterKeypress();
+            mFnKey.adjustAfterKeypress();
+        }
+
+        return result;
+    }
+
+    /**
+     * Handle a keyDown event.
+     *
+     * @param keyCode the keycode of the keyDown event
+     *
+     */
+    public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException {
+        if (handleKeyCode(keyCode, out, appMode)) {
+            return;
+        }
+        int result = -1;
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_ALT_RIGHT:
+        case KeyEvent.KEYCODE_ALT_LEFT:
+            mAltKey.onPress();
+            break;
+
+        case KeyEvent.KEYCODE_SHIFT_LEFT:
+        case KeyEvent.KEYCODE_SHIFT_RIGHT:
+            mCapKey.onPress();
+            break;
+
+        case KEYCODE_CTRL_LEFT:
+        case KEYCODE_CTRL_RIGHT:
+            mControlKey.onPress();
+            break;
+
+        case KEYCODE_CAPS_LOCK:
+            if (event.getRepeatCount() == 0) {
+                mCapsLock = !mCapsLock;
+            }
+            break;
+
+        default: {
+            result = event.getUnicodeChar(
+                   (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) |
+                   (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0));
+            break;
+            }
+        }
+
+        result = mapControlChar(result);
+
+        if (result >= KEYCODE_OFFSET) {
+            handleKeyCode(result - KEYCODE_OFFSET, out, appMode);
+        } else if (result >= 0) {
+            out.write(result);
+        }
+    }
+
+    public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException {
+        if (keyCode >= 0 && keyCode < mKeyCodes.length) {
+            String code = null;
+            if (appMode) {
+                code = mAppKeyCodes[keyCode];
+            }
+            if (code == null) {
+                code = mKeyCodes[keyCode];
+            }
+            if (code != null) {
+                int length = code.length();
+                for (int i = 0; i < length; i++) {
+                    out.write(code.charAt(i));
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Handle a keyUp event.
+     *
+     * @param keyCode the keyCode of the keyUp event
+     */
+    public void keyUp(int keyCode) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_ALT_LEFT:
+        case KeyEvent.KEYCODE_ALT_RIGHT:
+            mAltKey.onRelease();
+            break;
+        case KeyEvent.KEYCODE_SHIFT_LEFT:
+        case KeyEvent.KEYCODE_SHIFT_RIGHT:
+            mCapKey.onRelease();
+            break;
+
+        case KEYCODE_CTRL_LEFT:
+        case KEYCODE_CTRL_RIGHT:
+            mControlKey.onRelease();
+            break;
+
+        default:
+            // Ignore other keyUps
+            break;
+        }
+    }
+}
index fabe014..584eaf7 100644 (file)
 package jackpal.androidterm;
 
 import java.io.FileDescriptor;
-import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 
@@ -31,27 +29,18 @@ import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.Typeface;
 import android.net.Uri;
+import android.net.wifi.WifiManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
+import android.os.PowerManager;
 import android.preference.PreferenceManager;
 import android.text.ClipboardManager;
-import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
-import android.view.GestureDetector;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
@@ -59,24 +48,7 @@ import android.view.MotionEvent;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.inputmethod.BaseInputConnection;
-import android.view.inputmethod.CompletionInfo;
-import android.view.inputmethod.CorrectionInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.TextView;
-
-import android.content.ComponentName;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Binder;
-import android.os.IBinder;
-
-import android.net.wifi.WifiManager;
-import android.os.PowerManager;
 
 /**
  * A terminal emulator activity.
@@ -712,3882 +684,3 @@ public class Term extends Activity {
         }
     }
 }
-
-
-/**
- * An abstract screen interface. A terminal screen stores lines of text. (The
- * reason to abstract it is to allow different implementations, and to hide
- * implementation details from clients.)
- */
-interface Screen {
-
-    /**
-     * Set line wrap flag for a given row. Affects how lines are logically
-     * wrapped when changing screen size or converting to a transcript.
-     */
-    void setLineWrap(int row);
-
-    /**
-     * Store byte b into the screen at location (x, y)
-     *
-     * @param x X coordinate (also known as column)
-     * @param y Y coordinate (also known as row)
-     * @param b ASCII character to store
-     * @param foreColor the foreground color
-     * @param backColor the background color
-     */
-    void set(int x, int y, byte b, int foreColor, int backColor);
-
-    /**
-     * Scroll the screen down one line. To scroll the whole screen of a 24 line
-     * screen, the arguments would be (0, 24).
-     *
-     * @param topMargin First line that is scrolled.
-     * @param bottomMargin One line after the last line that is scrolled.
-     */
-    void scroll(int topMargin, int bottomMargin, int foreColor, int backColor);
-
-    /**
-     * Block copy characters from one position in the screen to another. The two
-     * positions can overlap. All characters of the source and destination must
-     * be within the bounds of the screen, or else an InvalidParemeterException
-     * will be thrown.
-     *
-     * @param sx source X coordinate
-     * @param sy source Y coordinate
-     * @param w width
-     * @param h height
-     * @param dx destination X coordinate
-     * @param dy destination Y coordinate
-     */
-    void blockCopy(int sx, int sy, int w, int h, int dx, int dy);
-
-    /**
-     * Block set characters. All characters must be within the bounds of the
-     * screen, or else and InvalidParemeterException will be thrown. Typically
-     * this is called with a "val" argument of 32 to clear a block of
-     * characters.
-     *
-     * @param sx source X
-     * @param sy source Y
-     * @param w width
-     * @param h height
-     * @param val value to set.
-     * @param foreColor the foreground color
-     * @param backColor the background color
-     */
-    void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int
-            backColor);
-
-    /**
-     * Get the contents of the transcript buffer as a text string.
-     *
-     * @return the contents of the transcript buffer.
-     */
-    String getTranscriptText();
-
-    /**
-     * Get the selected text inside transcript buffer as a text string.
-     * @param x1 Selection start
-     * @param y1 Selection start
-     * @param x2 Selection end
-     * @param y2 Selection end
-     * @return the contents of the transcript buffer.
-     */
-    String getSelectedText(int x1, int y1, int x2, int y2);
-
-    /**
-     * Resize the screen
-     * @param columns
-     * @param rows
-     */
-    void resize(int columns, int rows, int foreColor, int backColor);
-}
-
-
-/**
- * A TranscriptScreen is a screen that remembers data that's been scrolled. The
- * old data is stored in a ring buffer to minimize the amount of copying that
- * needs to be done. The transcript does its own drawing, to avoid having to
- * expose its internal data structures.
- */
-class TranscriptScreen implements Screen {
-    private static final String TAG = "TranscriptScreen";
-
-    /**
-     * The width of the transcript, in characters. Fixed at initialization.
-     */
-    private int mColumns;
-
-    /**
-     * The total number of rows in the transcript and the screen. Fixed at
-     * initialization.
-     */
-    private int mTotalRows;
-
-    /**
-     * The number of rows in the active portion of the transcript. Doesn't
-     * include the screen.
-     */
-    private int mActiveTranscriptRows;
-
-    /**
-     * Which row is currently the topmost line of the transcript. Used to
-     * implement a circular buffer.
-     */
-    private int mHead;
-
-    /**
-     * The number of active rows, includes both the transcript and the screen.
-     */
-    private int mActiveRows;
-
-    /**
-     * The number of rows in the screen.
-     */
-    private int mScreenRows;
-
-    /**
-     * The data for both the screen and the transcript. The first mScreenRows *
-     * mLineWidth characters are the screen, the rest are the transcript.
-     * The low byte encodes the ASCII character, the high byte encodes the
-     * foreground and background colors, plus underline and bold.
-     */
-    private char[] mData;
-
-    /**
-     * The data's stored as color-encoded chars, but the drawing routines require chars, so we
-     * need a temporary buffer to hold a row's worth of characters.
-     */
-    private char[] mRowBuffer;
-
-    /**
-     * Flags that keep track of whether the current line logically wraps to the
-     * next line. This is used when resizing the screen and when copying to the
-     * clipboard or an email attachment
-     */
-
-    private boolean[] mLineWrap;
-
-    /**
-     * Create a transcript screen.
-     *
-     * @param columns the width of the screen in characters.
-     * @param totalRows the height of the entire text area, in rows of text.
-     * @param screenRows the height of just the screen, not including the
-     *        transcript that holds lines that have scrolled off the top of the
-     *        screen.
-     */
-    public TranscriptScreen(int columns, int totalRows, int screenRows,
-            int foreColor, int backColor) {
-        init(columns, totalRows, screenRows, foreColor, backColor);
-    }
-
-    private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) {
-        mColumns = columns;
-        mTotalRows = totalRows;
-        mActiveTranscriptRows = 0;
-        mHead = 0;
-        mActiveRows = screenRows;
-        mScreenRows = screenRows;
-        int totalSize = columns * totalRows;
-        mData = new char[totalSize];
-        blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor);
-        mRowBuffer = new char[columns];
-        mLineWrap = new boolean[totalRows];
-        consistencyCheck();
-   }
-
-    /**
-     * Convert a row value from the public external coordinate system to our
-     * internal private coordinate system. External coordinate system:
-     * -mActiveTranscriptRows to mScreenRows-1, with the screen being
-     * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of
-     * mData are the visible rows. mScreenRows..mActiveRows - 1 are the
-     * transcript, stored as a circular buffer.
-     *
-     * @param row a row in the external coordinate system.
-     * @return The row corresponding to the input argument in the private
-     *         coordinate system.
-     */
-    private int externalToInternalRow(int row) {
-        if (row < -mActiveTranscriptRows || row >= mScreenRows) {
-            String errorMessage = "externalToInternalRow "+ row +
-                " " + mActiveTranscriptRows + " " + mScreenRows;
-            Log.e(TAG, errorMessage);
-            throw new IllegalArgumentException(errorMessage);
-        }
-        if (row >= 0) {
-            return row; // This is a visible row.
-        }
-        return mScreenRows
-                + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows);
-    }
-
-    private int getOffset(int externalLine) {
-        return externalToInternalRow(externalLine) * mColumns;
-    }
-
-    private int getOffset(int x, int y) {
-        return getOffset(y) + x;
-    }
-
-    public void setLineWrap(int row) {
-        mLineWrap[externalToInternalRow(row)] = true;
-    }
-
-    /**
-     * Store byte b into the screen at location (x, y)
-     *
-     * @param x X coordinate (also known as column)
-     * @param y Y coordinate (also known as row)
-     * @param b ASCII character to store
-     * @param foreColor the foreground color
-     * @param backColor the background color
-     */
-    public void set(int x, int y, byte b, int foreColor, int backColor) {
-        mData[getOffset(x, y)] = encode(b, foreColor, backColor);
-    }
-
-    private char encode(int b, int foreColor, int backColor) {
-        return (char) ((foreColor << 12) | (backColor << 8) | b);
-    }
-
-    /**
-     * Scroll the screen down one line. To scroll the whole screen of a 24 line
-     * screen, the arguments would be (0, 24).
-     *
-     * @param topMargin First line that is scrolled.
-     * @param bottomMargin One line after the last line that is scrolled.
-     */
-    public void scroll(int topMargin, int bottomMargin, int foreColor,
-            int backColor) {
-        // Separate out reasons so that stack crawls help us
-        // figure out which condition was violated.
-        if (topMargin > bottomMargin - 1) {
-            throw new IllegalArgumentException();
-        }
-
-        if (topMargin > mScreenRows - 1) {
-            throw new IllegalArgumentException();
-        }
-
-        if (bottomMargin > mScreenRows) {
-            throw new IllegalArgumentException();
-        }
-
-        // Adjust the transcript so that the last line of the transcript
-        // is ready to receive the newly scrolled data
-        consistencyCheck();
-        int expansionRows = Math.min(1, mTotalRows - mActiveRows);
-        int rollRows = 1 - expansionRows;
-        mActiveRows += expansionRows;
-        mActiveTranscriptRows += expansionRows;
-        if (mActiveTranscriptRows > 0) {
-            mHead = (mHead + rollRows) % mActiveTranscriptRows;
-        }
-        consistencyCheck();
-
-        // Block move the scroll line to the transcript
-        int topOffset = getOffset(topMargin);
-        int destOffset = getOffset(-1);
-        System.arraycopy(mData, topOffset, mData, destOffset, mColumns);
-
-        int topLine = externalToInternalRow(topMargin);
-        int destLine = externalToInternalRow(-1);
-        System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1);
-
-        // Block move the scrolled data up
-        int numScrollChars = (bottomMargin - topMargin - 1) * mColumns;
-        System.arraycopy(mData, topOffset + mColumns, mData, topOffset,
-                numScrollChars);
-        int numScrollLines = (bottomMargin - topMargin - 1);
-        System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine,
-                numScrollLines);
-
-        // Erase the bottom line of the scroll region
-        blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor);
-        mLineWrap[externalToInternalRow(bottomMargin-1)] = false;
-    }
-
-    private void consistencyCheck() {
-        checkPositive(mColumns);
-        checkPositive(mTotalRows);
-        checkRange(0, mActiveTranscriptRows, mTotalRows);
-        if (mActiveTranscriptRows == 0) {
-            checkEqual(mHead, 0);
-        } else {
-            checkRange(0, mHead, mActiveTranscriptRows-1);
-        }
-        checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows);
-        checkRange(0, mScreenRows, mTotalRows);
-
-        checkEqual(mTotalRows, mLineWrap.length);
-        checkEqual(mTotalRows*mColumns, mData.length);
-        checkEqual(mColumns, mRowBuffer.length);
-    }
-
-    private void checkPositive(int n) {
-        if (n < 0) {
-            throw new IllegalArgumentException("checkPositive " + n);
-        }
-    }
-
-    private void checkRange(int a, int b, int c) {
-        if (a > b || b > c) {
-            throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c);
-        }
-    }
-
-    private void checkEqual(int a, int b) {
-        if (a != b) {
-            throw new IllegalArgumentException("checkEqual " + a + " == " + b);
-        }
-    }
-
-    /**
-     * Block copy characters from one position in the screen to another. The two
-     * positions can overlap. All characters of the source and destination must
-     * be within the bounds of the screen, or else an InvalidParemeterException
-     * will be thrown.
-     *
-     * @param sx source X coordinate
-     * @param sy source Y coordinate
-     * @param w width
-     * @param h height
-     * @param dx destination X coordinate
-     * @param dy destination Y coordinate
-     */
-    public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
-        if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows
-                || dx < 0 || dx + w > mColumns || dy < 0
-                || dy + h > mScreenRows) {
-            throw new IllegalArgumentException();
-        }
-        if (sy > dy) {
-            // Move in increasing order
-            for (int y = 0; y < h; y++) {
-                int srcOffset = getOffset(sx, sy + y);
-                int dstOffset = getOffset(dx, dy + y);
-                System.arraycopy(mData, srcOffset, mData, dstOffset, w);
-            }
-        } else {
-            // Move in decreasing order
-            for (int y = 0; y < h; y++) {
-                int y2 = h - (y + 1);
-                int srcOffset = getOffset(sx, sy + y2);
-                int dstOffset = getOffset(dx, dy + y2);
-                System.arraycopy(mData, srcOffset, mData, dstOffset, w);
-            }
-        }
-    }
-
-    /**
-     * Block set characters. All characters must be within the bounds of the
-     * screen, or else and InvalidParemeterException will be thrown. Typically
-     * this is called with a "val" argument of 32 to clear a block of
-     * characters.
-     *
-     * @param sx source X
-     * @param sy source Y
-     * @param w width
-     * @param h height
-     * @param val value to set.
-     */
-    public void blockSet(int sx, int sy, int w, int h, int val,
-            int foreColor, int backColor) {
-        if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
-            throw new IllegalArgumentException();
-        }
-        char[] data = mData;
-        char encodedVal = encode(val, foreColor, backColor);
-        for (int y = 0; y < h; y++) {
-            int offset = getOffset(sx, sy + y);
-            for (int x = 0; x < w; x++) {
-                data[offset + x] = encodedVal;
-            }
-        }
-    }
-
-    /**
-     * Draw a row of text. Out-of-bounds rows are blank, not errors.
-     *
-     * @param row The row of text to draw.
-     * @param canvas The canvas to draw to.
-     * @param x The x coordinate origin of the drawing
-     * @param y The y coordinate origin of the drawing
-     * @param renderer The renderer to use to draw the text
-     * @param cx the cursor X coordinate, -1 means don't draw it
-     * @param selx1 the text selection start X coordinate
-     * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection
-     * @param imeText current IME text, to be rendered at cursor
-     */
-    public final void drawText(int row, Canvas canvas, float x, float y,
-            TextRenderer renderer, int cx, int selx1, int selx2, String imeText) {
-
-        // Out-of-bounds rows are blank.
-        if (row < -mActiveTranscriptRows || row >= mScreenRows) {
-            return;
-        }
-
-        // Copy the data from the byte array to a char array so they can
-        // be drawn.
-
-        int offset = getOffset(row);
-        char[] rowBuffer = mRowBuffer;
-        char[] data = mData;
-        int columns = mColumns;
-        int lastColors = 0;
-        int lastRunStart = -1;
-        final int CURSOR_MASK = 0x10000;
-        for (int i = 0; i < columns; i++) {
-            char c = data[offset + i];
-            int colors = (char) (c & 0xff00);
-            if (cx == i || (i >= selx1 && i <= selx2)) {
-                // Set cursor background color:
-                colors |= CURSOR_MASK;
-            }
-            rowBuffer[i] = (char) (c & 0x00ff);
-            if (colors != lastColors) {
-                if (lastRunStart >= 0) {
-                    renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
-                            lastRunStart, i - lastRunStart,
-                            (lastColors & CURSOR_MASK) != 0,
-                            0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
-                }
-                lastColors = colors;
-                lastRunStart = i;
-            }
-        }
-        if (lastRunStart >= 0) {
-            renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
-                    lastRunStart, columns - lastRunStart,
-                    (lastColors & CURSOR_MASK) != 0,
-                    0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
-        }
-
-        if (cx >= 0 && imeText.length() > 0) {
-            int imeLength = Math.min(columns, imeText.length());
-            int imeOffset = imeText.length() - imeLength;
-            int imePosition = Math.min(cx, columns - imeLength);
-            renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(),
-                    imeOffset, imeLength, true, 0x0f, 0x00);
-        }
-     }
-
-    /**
-     * Get the count of active rows.
-     *
-     * @return the count of active rows.
-     */
-    public int getActiveRows() {
-        return mActiveRows;
-    }
-
-    /**
-     * Get the count of active transcript rows.
-     *
-     * @return the count of active transcript rows.
-     */
-    public int getActiveTranscriptRows() {
-        return mActiveTranscriptRows;
-    }
-
-    public String getTranscriptText() {
-        return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows);
-    }
-
-    public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
-        return internalGetTranscriptText(true, selX1, selY1, selX2, selY2);
-    }
-
-    private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) {
-        StringBuilder builder = new StringBuilder();
-        char[] rowBuffer = mRowBuffer;
-        char[] data = mData;
-        int columns = mColumns;
-        for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) {
-            int offset = getOffset(row);
-            int lastPrintingChar = -1;
-            for (int column = 0; column < columns; column++) {
-                char c = data[offset + column];
-                if (stripColors) {
-                    c = (char) (c & 0xff);
-                }
-                if ((c & 0xff) != ' ') {
-                    lastPrintingChar = column;
-                }
-                rowBuffer[column] = c;
-            }
-            if ( row >= selY1 && row <= selY2 ) {
-                int x1 = 0;
-                int x2 = 0;
-                if ( row == selY1 ) {
-                    x1 = selX1;
-                }
-                if ( row == selY2 ) {
-                    x2 = selX2;
-                } else {
-                    x2 = columns;
-                }
-                if (mLineWrap[externalToInternalRow(row)]) {
-                    builder.append(rowBuffer, x1, x2 - x1);
-                } else {
-                    builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1)));
-                    builder.append('\n');
-                }
-            }
-        }
-        return builder.toString();
-    }
-
-    public void resize(int columns, int rows, int foreColor, int backColor) {
-        init(columns, mTotalRows, rows, foreColor, backColor);
-    }
-}
-
-/**
- * Renders text into a screen. Contains all the terminal-specific knowlege and
- * state. Emulates a subset of the X Window System xterm terminal, which in turn
- * is an emulator for a subset of the Digital Equipment Corporation vt100
- * terminal. Missing functionality: text attributes (bold, underline, reverse
- * video, color) alternate screen cursor key and keypad escape sequences.
- */
-class TerminalEmulator {
-
-    /**
-     * The cursor row. Numbered 0..mRows-1.
-     */
-    private int mCursorRow;
-
-    /**
-     * The cursor column. Numbered 0..mColumns-1.
-     */
-    private int mCursorCol;
-
-    /**
-     * The number of character rows in the terminal screen.
-     */
-    private int mRows;
-
-    /**
-     * The number of character columns in the terminal screen.
-     */
-    private int mColumns;
-
-    /**
-     * Used to send data to the remote process. Needed to implement the various
-     * "report" escape sequences.
-     */
-    private FileOutputStream mTermOut;
-
-    /**
-     * Stores the characters that appear on the screen of the emulated terminal.
-     */
-    private Screen mScreen;
-
-    /**
-     * Keeps track of the current argument of the current escape sequence.
-     * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.)
-     */
-    private int mArgIndex;
-
-    /**
-     * The number of parameter arguments. This name comes from the ANSI standard
-     * for terminal escape codes.
-     */
-    private static final int MAX_ESCAPE_PARAMETERS = 16;
-
-    /**
-     * Holds the arguments of the current escape sequence.
-     */
-    private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
-
-    // Escape processing states:
-
-    /**
-     * Escape processing state: Not currently in an escape sequence.
-     */
-    private static final int ESC_NONE = 0;
-
-    /**
-     * Escape processing state: Have seen an ESC character
-     */
-    private static final int ESC = 1;
-
-    /**
-     * Escape processing state: Have seen ESC POUND
-     */
-    private static final int ESC_POUND = 2;
-
-    /**
-     * Escape processing state: Have seen ESC and a character-set-select char
-     */
-    private static final int ESC_SELECT_LEFT_PAREN = 3;
-
-    /**
-     * Escape processing state: Have seen ESC and a character-set-select char
-     */
-    private static final int ESC_SELECT_RIGHT_PAREN = 4;
-
-    /**
-     * Escape processing state: ESC [
-     */
-    private static final int ESC_LEFT_SQUARE_BRACKET = 5;
-
-    /**
-     * Escape processing state: ESC [ ?
-     */
-    private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6;
-
-    /**
-     * True if the current escape sequence should continue, false if the current
-     * escape sequence should be terminated. Used when parsing a single
-     * character.
-     */
-    private boolean mContinueSequence;
-
-    /**
-     * The current state of the escape sequence state machine.
-     */
-    private int mEscapeState;
-
-    /**
-     * Saved state of the cursor row, Used to implement the save/restore cursor
-     * position escape sequences.
-     */
-    private int mSavedCursorRow;
-
-    /**
-     * Saved state of the cursor column, Used to implement the save/restore
-     * cursor position escape sequences.
-     */
-    private int mSavedCursorCol;
-
-    // DecSet booleans
-
-    /**
-     * This mask indicates 132-column mode is set. (As opposed to 80-column
-     * mode.)
-     */
-    private static final int K_132_COLUMN_MODE_MASK = 1 << 3;
-
-    /**
-     * This mask indicates that origin mode is set. (Cursor addressing is
-     * relative to the absolute screen size, rather than the currently set top
-     * and bottom margins.)
-     */
-    private static final int K_ORIGIN_MODE_MASK = 1 << 6;
-
-    /**
-     * This mask indicates that wraparound mode is set. (As opposed to
-     * stop-at-right-column mode.)
-     */
-    private static final int K_WRAPAROUND_MODE_MASK = 1 << 7;
-
-    /**
-     * Holds multiple DECSET flags. The data is stored this way, rather than in
-     * separate booleans, to make it easier to implement the save-and-restore
-     * semantics. The various k*ModeMask masks can be used to extract and modify
-     * the individual flags current states.
-     */
-    private int mDecFlags;
-
-    /**
-     * Saves away a snapshot of the DECSET flags. Used to implement save and
-     * restore escape sequences.
-     */
-    private int mSavedDecFlags;
-
-    // Modes set with Set Mode / Reset Mode
-
-    /**
-     * True if insert mode (as opposed to replace mode) is active. In insert
-     * mode new characters are inserted, pushing existing text to the right.
-     */
-    private boolean mInsertMode;
-
-    /**
-     * Automatic newline mode. Configures whether pressing return on the
-     * keyboard automatically generates a return as well. Not currently
-     * implemented.
-     */
-    private boolean mAutomaticNewlineMode;
-
-    /**
-     * An array of tab stops. mTabStop[i] is true if there is a tab stop set for
-     * column i.
-     */
-    private boolean[] mTabStop;
-
-    // The margins allow portions of the screen to be locked.
-
-    /**
-     * The top margin of the screen, for scrolling purposes. Ranges from 0 to
-     * mRows-2.
-     */
-    private int mTopMargin;
-
-    /**
-     * The bottom margin of the screen, for scrolling purposes. Ranges from
-     * mTopMargin + 2 to mRows. (Defines the first row after the scrolling
-     * region.
-     */
-    private int mBottomMargin;
-
-    /**
-     * True if the next character to be emitted will be automatically wrapped to
-     * the next line. Used to disambiguate the case where the cursor is
-     * positioned on column mColumns-1.
-     */
-    private boolean mAboutToAutoWrap;
-
-    /**
-     * Used for debugging, counts how many chars have been processed.
-     */
-    private int mProcessedCharCount;
-
-    /**
-     * Foreground color, 0..7, mask with 8 for bold
-     */
-    private int mForeColor;
-
-    /**
-     * Background color, 0..7, mask with 8 for underline
-     */
-    private int mBackColor;
-
-    private boolean mInverseColors;
-
-    private boolean mbKeypadApplicationMode;
-
-    private boolean mAlternateCharSet;
-
-    /**
-     * Used for moving selection up along with the scrolling text
-     */
-    private int mScrollCounter = 0;
-
-    /**
-     * Construct a terminal emulator that uses the supplied screen
-     *
-     * @param screen the screen to render characters into.
-     * @param columns the number of columns to emulate
-     * @param rows the number of rows to emulate
-     * @param termOut the output file descriptor that talks to the pseudo-tty.
-     */
-    public TerminalEmulator(Screen screen, int columns, int rows,
-            FileOutputStream termOut) {
-        mScreen = screen;
-        mRows = rows;
-        mColumns = columns;
-        mTabStop = new boolean[mColumns];
-        mTermOut = termOut;
-        reset();
-    }
-
-    public void updateSize(int columns, int rows) {
-        if (mRows == rows && mColumns == columns) {
-            return;
-        }
-        if (columns <= 0) {
-            throw new IllegalArgumentException("rows:" + columns);
-        }
-
-        if (rows <= 0) {
-            throw new IllegalArgumentException("rows:" + rows);
-        }
-
-        String transcriptText = mScreen.getTranscriptText();
-
-        mScreen.resize(columns, rows, mForeColor, mBackColor);
-
-        if (mRows != rows) {
-            mRows = rows;
-            mTopMargin = 0;
-            mBottomMargin = mRows;
-        }
-        if (mColumns != columns) {
-            int oldColumns = mColumns;
-            mColumns = columns;
-            boolean[] oldTabStop = mTabStop;
-            mTabStop = new boolean[mColumns];
-            int toTransfer = Math.min(oldColumns, columns);
-            System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
-            while (mCursorCol >= columns) {
-                mCursorCol -= columns;
-                mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1);
-            }
-        }
-        mCursorRow = 0;
-        mCursorCol = 0;
-        mAboutToAutoWrap = false;
-
-        int end = transcriptText.length()-1;
-        while ((end >= 0) && transcriptText.charAt(end) == '\n') {
-            end--;
-        }
-        for(int i = 0; i <= end; i++) {
-            byte c = (byte) transcriptText.charAt(i);
-            if (c == '\n') {
-                setCursorCol(0);
-                doLinefeed();
-            } else {
-                emit(c);
-            }
-        }
-    }
-
-    /**
-     * Get the cursor's current row.
-     *
-     * @return the cursor's current row.
-     */
-    public final int getCursorRow() {
-        return mCursorRow;
-    }
-
-    /**
-     * Get the cursor's current column.
-     *
-     * @return the cursor's current column.
-     */
-    public final int getCursorCol() {
-        return mCursorCol;
-    }
-
-    public final boolean getKeypadApplicationMode() {
-        return mbKeypadApplicationMode;
-    }
-
-    private void setDefaultTabStops() {
-        for (int i = 0; i < mColumns; i++) {
-            mTabStop[i] = (i & 7) == 0 && i != 0;
-        }
-    }
-
-    /**
-     * Accept bytes (typically from the pseudo-teletype) and process them.
-     *
-     * @param buffer a byte array containing the bytes to be processed
-     * @param base the first index of the array to process
-     * @param length the number of bytes in the array to process
-     */
-    public void append(byte[] buffer, int base, int length) {
-        for (int i = 0; i < length; i++) {
-            byte b = buffer[base + i];
-            try {
-                if (Term.LOG_CHARACTERS_FLAG) {
-                    char printableB = (char) b;
-                    if (b < 32 || b > 126) {
-                        printableB = ' ';
-                    }
-                    Log.w(Term.LOG_TAG, "'" + Character.toString(printableB)
-                            + "' (" + Integer.toString(b) + ")");
-                }
-                process(b);
-                mProcessedCharCount++;
-            } catch (Exception e) {
-                Log.e(Term.LOG_TAG, "Exception while processing character "
-                        + Integer.toString(mProcessedCharCount) + " code "
-                        + Integer.toString(b), e);
-            }
-        }
-    }
-
-    private void process(byte b) {
-        switch (b) {
-        case 0: // NUL
-            // Do nothing
-            break;
-
-        case 7: // BEL
-            // Do nothing
-            break;
-
-        case 8: // BS
-            setCursorCol(Math.max(0, mCursorCol - 1));
-            break;
-
-        case 9: // HT
-            // Move to next tab stop, but not past edge of screen
-            setCursorCol(nextTabStop(mCursorCol));
-            break;
-
-        case 13:
-            setCursorCol(0);
-            break;
-
-        case 10: // CR
-        case 11: // VT
-        case 12: // LF
-            doLinefeed();
-            break;
-
-        case 14: // SO:
-            setAltCharSet(true);
-            break;
-
-        case 15: // SI:
-            setAltCharSet(false);
-            break;
-
-
-        case 24: // CAN
-        case 26: // SUB
-            if (mEscapeState != ESC_NONE) {
-                mEscapeState = ESC_NONE;
-                emit((byte) 127);
-            }
-            break;
-
-        case 27: // ESC
-            // Always starts an escape sequence
-            startEscapeSequence(ESC);
-            break;
-
-        case (byte) 0x9b: // CSI
-            startEscapeSequence(ESC_LEFT_SQUARE_BRACKET);
-            break;
-
-        default:
-            mContinueSequence = false;
-            switch (mEscapeState) {
-            case ESC_NONE:
-                if (b >= 32) {
-                    emit(b);
-                }
-                break;
-
-            case ESC:
-                doEsc(b);
-                break;
-
-            case ESC_POUND:
-                doEscPound(b);
-                break;
-
-            case ESC_SELECT_LEFT_PAREN:
-                doEscSelectLeftParen(b);
-                break;
-
-            case ESC_SELECT_RIGHT_PAREN:
-                doEscSelectRightParen(b);
-                break;
-
-            case ESC_LEFT_SQUARE_BRACKET:
-                doEscLeftSquareBracket(b);
-                break;
-
-            case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
-                doEscLSBQuest(b);
-                break;
-
-            default:
-                unknownSequence(b);
-                break;
-            }
-            if (!mContinueSequence) {
-                mEscapeState = ESC_NONE;
-            }
-            break;
-        }
-    }
-
-    private void setAltCharSet(boolean alternateCharSet) {
-        mAlternateCharSet = alternateCharSet;
-    }
-
-    private int nextTabStop(int cursorCol) {
-        for (int i = cursorCol; i < mColumns; i++) {
-            if (mTabStop[i]) {
-                return i;
-            }
-        }
-        return mColumns - 1;
-    }
-
-    private void doEscLSBQuest(byte b) {
-        int mask = getDecFlagsMask(getArg0(0));
-        switch (b) {
-        case 'h': // Esc [ ? Pn h - DECSET
-            mDecFlags |= mask;
-            break;
-
-        case 'l': // Esc [ ? Pn l - DECRST
-            mDecFlags &= ~mask;
-            break;
-
-        case 'r': // Esc [ ? Pn r - restore
-            mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask);
-            break;
-
-        case 's': // Esc [ ? Pn s - save
-            mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask);
-            break;
-
-        default:
-            parseArg(b);
-            break;
-        }
-
-        // 132 column mode
-        if ((mask & K_132_COLUMN_MODE_MASK) != 0) {
-            // We don't actually set 132 cols, but we do want the
-            // side effect of clearing the screen and homing the cursor.
-            blockClear(0, 0, mColumns, mRows);
-            setCursorRowCol(0, 0);
-        }
-
-        // origin mode
-        if ((mask & K_ORIGIN_MODE_MASK) != 0) {
-            // Home the cursor.
-            setCursorPosition(0, 0);
-        }
-    }
-
-    private int getDecFlagsMask(int argument) {
-        if (argument >= 1 && argument <= 9) {
-            return (1 << argument);
-        }
-
-        return 0;
-    }
-
-    private void startEscapeSequence(int escapeState) {
-        mEscapeState = escapeState;
-        mArgIndex = 0;
-        for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) {
-            mArgs[j] = -1;
-        }
-    }
-
-    private void doLinefeed() {
-        int newCursorRow = mCursorRow + 1;
-        if (newCursorRow >= mBottomMargin) {
-            scroll();
-            newCursorRow = mBottomMargin - 1;
-        }
-        setCursorRow(newCursorRow);
-    }
-
-    private void continueSequence() {
-        mContinueSequence = true;
-    }
-
-    private void continueSequence(int state) {
-        mEscapeState = state;
-        mContinueSequence = true;
-    }
-
-    private void doEscSelectLeftParen(byte b) {
-        doSelectCharSet(true, b);
-    }
-
-    private void doEscSelectRightParen(byte b) {
-        doSelectCharSet(false, b);
-    }
-
-    private void doSelectCharSet(boolean isG0CharSet, byte b) {
-        switch (b) {
-        case 'A': // United Kingdom character set
-            break;
-        case 'B': // ASCII set
-            break;
-        case '0': // Special Graphics
-            break;
-        case '1': // Alternate character set
-            break;
-        case '2':
-            break;
-        default:
-            unknownSequence(b);
-        }
-    }
-
-    private void doEscPound(byte b) {
-        switch (b) {
-        case '8': // Esc # 8 - DECALN alignment test
-            mScreen.blockSet(0, 0, mColumns, mRows, 'E',
-                    getForeColor(), getBackColor());
-            break;
-
-        default:
-            unknownSequence(b);
-            break;
-        }
-    }
-
-    private void doEsc(byte b) {
-        switch (b) {
-        case '#':
-            continueSequence(ESC_POUND);
-            break;
-
-        case '(':
-            continueSequence(ESC_SELECT_LEFT_PAREN);
-            break;
-
-        case ')':
-            continueSequence(ESC_SELECT_RIGHT_PAREN);
-            break;
-
-        case '7': // DECSC save cursor
-            mSavedCursorRow = mCursorRow;
-            mSavedCursorCol = mCursorCol;
-            break;
-
-        case '8': // DECRC restore cursor
-            setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
-            break;
-
-        case 'D': // INDEX
-            doLinefeed();
-            break;
-
-        case 'E': // NEL
-            setCursorCol(0);
-            doLinefeed();
-            break;
-
-        case 'F': // Cursor to lower-left corner of screen
-            setCursorRowCol(0, mBottomMargin - 1);
-            break;
-
-        case 'H': // Tab set
-            mTabStop[mCursorCol] = true;
-            break;
-
-        case 'M': // Reverse index
-            if (mCursorRow <= mTopMargin) {
-                mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin
-                        - (mTopMargin + 1), 0, mTopMargin + 1);
-                blockClear(0, mTopMargin, mColumns);
-            } else {
-                mCursorRow--;
-            }
-
-            break;
-
-        case 'N': // SS2
-            unimplementedSequence(b);
-            break;
-
-        case '0': // SS3
-            unimplementedSequence(b);
-            break;
-
-        case 'P': // Device control string
-            unimplementedSequence(b);
-            break;
-
-        case 'Z': // return terminal ID
-            sendDeviceAttributes();
-            break;
-
-        case '[':
-            continueSequence(ESC_LEFT_SQUARE_BRACKET);
-            break;
-
-        case '=': // DECKPAM
-            mbKeypadApplicationMode = true;
-            break;
-
-        case '>' : // DECKPNM
-            mbKeypadApplicationMode = false;
-            break;
-
-        default:
-            unknownSequence(b);
-            break;
-        }
-    }
-
-    private void doEscLeftSquareBracket(byte b) {
-        switch (b) {
-        case '@': // ESC [ Pn @ - ICH Insert Characters
-        {
-            int charsAfterCursor = mColumns - mCursorCol;
-            int charsToInsert = Math.min(getArg0(1), charsAfterCursor);
-            int charsToMove = charsAfterCursor - charsToInsert;
-            mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1,
-                    mCursorCol + charsToInsert, mCursorRow);
-            blockClear(mCursorCol, mCursorRow, charsToInsert);
-        }
-            break;
-
-        case 'A': // ESC [ Pn A - Cursor Up
-            setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
-            break;
-
-        case 'B': // ESC [ Pn B - Cursor Down
-            setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
-            break;
-
-        case 'C': // ESC [ Pn C - Cursor Right
-            setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1)));
-            break;
-
-        case 'D': // ESC [ Pn D - Cursor Left
-            setCursorCol(Math.max(0, mCursorCol - getArg0(1)));
-            break;
-
-        case 'G': // ESC [ Pn G - Cursor Horizontal Absolute
-            setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
-            break;
-
-        case 'H': // ESC [ Pn ; H - Cursor Position
-            setHorizontalVerticalPosition();
-            break;
-
-        case 'J': // ESC [ Pn J - Erase in Display
-            switch (getArg0(0)) {
-            case 0: // Clear below
-                blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
-                blockClear(0, mCursorRow + 1, mColumns,
-                        mBottomMargin - (mCursorRow + 1));
-                break;
-
-            case 1: // Erase from the start of the screen to the cursor.
-                blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin);
-                blockClear(0, mCursorRow, mCursorCol + 1);
-                break;
-
-            case 2: // Clear all
-                blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin);
-                break;
-
-            default:
-                unknownSequence(b);
-                break;
-            }
-            break;
-
-        case 'K': // ESC [ Pn K - Erase in Line
-            switch (getArg0(0)) {
-            case 0: // Clear to right
-                blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
-                break;
-
-            case 1: // Erase start of line to cursor (including cursor)
-                blockClear(0, mCursorRow, mCursorCol + 1);
-                break;
-
-            case 2: // Clear whole line
-                blockClear(0, mCursorRow, mColumns);
-                break;
-
-            default:
-                unknownSequence(b);
-                break;
-            }
-            break;
-
-        case 'L': // Insert Lines
-        {
-            int linesAfterCursor = mBottomMargin - mCursorRow;
-            int linesToInsert = Math.min(getArg0(1), linesAfterCursor);
-            int linesToMove = linesAfterCursor - linesToInsert;
-            mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0,
-                    mCursorRow + linesToInsert);
-            blockClear(0, mCursorRow, mColumns, linesToInsert);
-        }
-            break;
-
-        case 'M': // Delete Lines
-        {
-            int linesAfterCursor = mBottomMargin - mCursorRow;
-            int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
-            int linesToMove = linesAfterCursor - linesToDelete;
-            mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns,
-                    linesToMove, 0, mCursorRow);
-            blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete);
-        }
-            break;
-
-        case 'P': // Delete Characters
-        {
-            int charsAfterCursor = mColumns - mCursorCol;
-            int charsToDelete = Math.min(getArg0(1), charsAfterCursor);
-            int charsToMove = charsAfterCursor - charsToDelete;
-            mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow,
-                    charsToMove, 1, mCursorCol, mCursorRow);
-            blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete);
-        }
-            break;
-
-        case 'T': // Mouse tracking
-            unimplementedSequence(b);
-            break;
-
-        case '?': // Esc [ ? -- start of a private mode set
-            continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK);
-            break;
-
-        case 'c': // Send device attributes
-            sendDeviceAttributes();
-            break;
-
-        case 'd': // ESC [ Pn d - Vert Position Absolute
-            setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
-            break;
-
-        case 'f': // Horizontal and Vertical Position
-            setHorizontalVerticalPosition();
-            break;
-
-        case 'g': // Clear tab stop
-            switch (getArg0(0)) {
-            case 0:
-                mTabStop[mCursorCol] = false;
-                break;
-
-            case 3:
-                for (int i = 0; i < mColumns; i++) {
-                    mTabStop[i] = false;
-                }
-                break;
-
-            default:
-                // Specified to have no effect.
-                break;
-            }
-            break;
-
-        case 'h': // Set Mode
-            doSetMode(true);
-            break;
-
-        case 'l': // Reset Mode
-            doSetMode(false);
-            break;
-
-        case 'm': // Esc [ Pn m - character attributes.
-            selectGraphicRendition();
-            break;
-
-        case 'r': // Esc [ Pn ; Pn r - set top and bottom margins
-        {
-            // The top margin defaults to 1, the bottom margin
-            // (unusually for arguments) defaults to mRows.
-            //
-            // The escape sequence numbers top 1..23, but we
-            // number top 0..22.
-            // The escape sequence numbers bottom 2..24, and
-            // so do we (because we use a zero based numbering
-            // scheme, but we store the first line below the
-            // bottom-most scrolling line.
-            // As a result, we adjust the top line by -1, but
-            // we leave the bottom line alone.
-            //
-            // Also require that top + 2 <= bottom
-
-            int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
-            int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows));
-            mTopMargin = top;
-            mBottomMargin = bottom;
-
-            // The cursor is placed in the home position
-            setCursorRowCol(mTopMargin, 0);
-        }
-            break;
-
-        default:
-            parseArg(b);
-            break;
-        }
-    }
-
-    private void selectGraphicRendition() {
-        for (int i = 0; i <= mArgIndex; i++) {
-            int code = mArgs[i];
-            if ( code < 0) {
-                if (mArgIndex > 0) {
-                    continue;
-                } else {
-                    code = 0;
-                }
-            }
-
-            // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
-
-            if (code == 0) { // reset
-                mInverseColors = false;
-                mForeColor = 7;
-                mBackColor = 0;
-            } else if (code == 1) { // bold
-                mForeColor |= 0x8;
-            } else if (code == 4) { // underscore
-                mBackColor |= 0x8;
-            } else if (code == 7) { // inverse
-                mInverseColors = true;
-            } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint
-                mForeColor &= 0x7;
-            } else if (code == 24) { // underline: none
-                mBackColor &= 0x7;
-            } else if (code == 27) { // image: positive
-                mInverseColors = false;
-            } else if (code >= 30 && code <= 37) { // foreground color
-                mForeColor = (mForeColor & 0x8) | (code - 30);
-            } else if (code == 39) { // set default text color
-                mForeColor = 7;
-                mBackColor = mBackColor & 0x7;
-            } else if (code >= 40 && code <= 47) { // background color
-                mBackColor = (mBackColor & 0x8) | (code - 40);
-            } else if (code == 49) { // set default background color
-                mBackColor = mBackColor & 0x8; // color 0, but preserve underscore.
-            } else {
-                if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
-                    Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code));
-                }
-            }
-        }
-    }
-
-    private void blockClear(int sx, int sy, int w) {
-        blockClear(sx, sy, w, 1);
-    }
-
-    private void blockClear(int sx, int sy, int w, int h) {
-        mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor());
-    }
-
-    private int getForeColor() {
-        return mInverseColors ?
-                ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor;
-    }
-
-    private int getBackColor() {
-        return mInverseColors ?
-                ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor;
-    }
-
-    private void doSetMode(boolean newValue) {
-        int modeBit = getArg0(0);
-        switch (modeBit) {
-        case 4:
-            mInsertMode = newValue;
-            break;
-
-        case 20:
-            mAutomaticNewlineMode = newValue;
-            break;
-
-        default:
-            unknownParameter(modeBit);
-            break;
-        }
-    }
-
-    private void setHorizontalVerticalPosition() {
-
-        // Parameters are Row ; Column
-
-        setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
-    }
-
-    private void setCursorPosition(int x, int y) {
-        int effectiveTopMargin = 0;
-        int effectiveBottomMargin = mRows;
-        if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) {
-            effectiveTopMargin = mTopMargin;
-            effectiveBottomMargin = mBottomMargin;
-        }
-        int newRow =
-                Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y,
-                        effectiveBottomMargin - 1));
-        int newCol = Math.max(0, Math.min(x, mColumns - 1));
-        setCursorRowCol(newRow, newCol);
-    }
-
-    private void sendDeviceAttributes() {
-        // This identifies us as a DEC vt100 with advanced
-        // video options. This is what the xterm terminal
-        // emulator sends.
-        byte[] attributes =
-                {
-                /* VT100 */
-                 (byte) 27, (byte) '[', (byte) '?', (byte) '1',
-                 (byte) ';', (byte) '2', (byte) 'c'
-
-                /* VT220
-                (byte) 27, (byte) '[', (byte) '?', (byte) '6',
-                (byte) '0',  (byte) ';',
-                (byte) '1',  (byte) ';',
-                (byte) '2',  (byte) ';',
-                (byte) '6',  (byte) ';',
-                (byte) '8',  (byte) ';',
-                (byte) '9',  (byte) ';',
-                (byte) '1',  (byte) '5', (byte) ';',
-                (byte) 'c'
-                */
-                };
-
-        write(attributes);
-    }
-
-    /**
-     * Send data to the shell process
-     * @param data
-     */
-    private void write(byte[] data) {
-        try {
-            mTermOut.write(data);
-            mTermOut.flush();
-        } catch (IOException e) {
-            // Ignore exception
-            // We don't really care if the receiver isn't listening.
-            // We just make a best effort to answer the query.
-        }
-    }
-
-    private void scroll() {
-        //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin);
-        mScrollCounter ++;
-        mScreen.scroll(mTopMargin, mBottomMargin,
-                getForeColor(), getBackColor());
-    }
-
-    /**
-     * Process the next ASCII character of a parameter.
-     *
-     * @param b The next ASCII character of the paramater sequence.
-     */
-    private void parseArg(byte b) {
-        if (b >= '0' && b <= '9') {
-            if (mArgIndex < mArgs.length) {
-                int oldValue = mArgs[mArgIndex];
-                int thisDigit = b - '0';
-                int value;
-                if (oldValue >= 0) {
-                    value = oldValue * 10 + thisDigit;
-                } else {
-                    value = thisDigit;
-                }
-                mArgs[mArgIndex] = value;
-            }
-            continueSequence();
-        } else if (b == ';') {
-            if (mArgIndex < mArgs.length) {
-                mArgIndex++;
-            }
-            continueSequence();
-        } else {
-            unknownSequence(b);
-        }
-    }
-
-    private int getArg0(int defaultValue) {
-        return getArg(0, defaultValue);
-    }
-
-    private int getArg1(int defaultValue) {
-        return getArg(1, defaultValue);
-    }
-
-    private int getArg(int index, int defaultValue) {
-        int result = mArgs[index];
-        if (result < 0) {
-            result = defaultValue;
-        }
-        return result;
-    }
-
-    private void unimplementedSequence(byte b) {
-        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
-            logError("unimplemented", b);
-        }
-        finishSequence();
-    }
-
-    private void unknownSequence(byte b) {
-        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
-            logError("unknown", b);
-        }
-        finishSequence();
-    }
-
-    private void unknownParameter(int parameter) {
-        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
-            StringBuilder buf = new StringBuilder();
-            buf.append("Unknown parameter");
-            buf.append(parameter);
-            logError(buf.toString());
-        }
-    }
-
-    private void logError(String errorType, byte b) {
-        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
-            StringBuilder buf = new StringBuilder();
-            buf.append(errorType);
-            buf.append(" sequence ");
-            buf.append(" EscapeState: ");
-            buf.append(mEscapeState);
-            buf.append(" char: '");
-            buf.append((char) b);
-            buf.append("' (");
-            buf.append(b);
-            buf.append(")");
-            boolean firstArg = true;
-            for (int i = 0; i <= mArgIndex; i++) {
-                int value = mArgs[i];
-                if (value >= 0) {
-                    if (firstArg) {
-                        firstArg = false;
-                        buf.append("args = ");
-                    }
-                    buf.append(String.format("%d; ", value));
-                }
-            }
-            logError(buf.toString());
-        }
-    }
-
-    private void logError(String error) {
-        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
-            Log.e(Term.LOG_TAG, error);
-        }
-        finishSequence();
-    }
-
-    private void finishSequence() {
-        mEscapeState = ESC_NONE;
-    }
-
-    private boolean autoWrapEnabled() {
-        // Always enable auto wrap, because it's useful on a small screen
-        return true;
-        // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0;
-    }
-
-    /**
-     * Send an ASCII character to the screen.
-     *
-     * @param b the ASCII character to display.
-     */
-    private void emit(byte b) {
-        boolean autoWrap = autoWrapEnabled();
-
-        if (autoWrap) {
-            if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) {
-                mScreen.setLineWrap(mCursorRow);
-                mCursorCol = 0;
-                if (mCursorRow + 1 < mBottomMargin) {
-                    mCursorRow++;
-                } else {
-                    scroll();
-                }
-            }
-        }
-
-        if (mInsertMode) { // Move character to right one space
-            int destCol = mCursorCol + 1;
-            if (destCol < mColumns) {
-                mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol,
-                        1, destCol, mCursorRow);
-            }
-        }
-
-        mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor());
-
-        if (autoWrap) {
-            mAboutToAutoWrap = (mCursorCol == mColumns - 1);
-        }
-
-        mCursorCol = Math.min(mCursorCol + 1, mColumns - 1);
-    }
-
-    private void setCursorRow(int row) {
-        mCursorRow = row;
-        mAboutToAutoWrap = false;
-    }
-
-    private void setCursorCol(int col) {
-        mCursorCol = col;
-        mAboutToAutoWrap = false;
-    }
-
-    private void setCursorRowCol(int row, int col) {
-        mCursorRow = Math.min(row, mRows-1);
-        mCursorCol = Math.min(col, mColumns-1);
-        mAboutToAutoWrap = false;
-    }
-
-    public int getScrollCounter() {
-        return mScrollCounter;
-    }
-
-    public void clearScrollCounter() {
-        mScrollCounter = 0;
-    }
-
-    /**
-     * Reset the terminal emulator to its initial state.
-     */
-    public void reset() {
-        mCursorRow = 0;
-        mCursorCol = 0;
-        mArgIndex = 0;
-        mContinueSequence = false;
-        mEscapeState = ESC_NONE;
-        mSavedCursorRow = 0;
-        mSavedCursorCol = 0;
-        mDecFlags = 0;
-        mSavedDecFlags = 0;
-        mInsertMode = false;
-        mAutomaticNewlineMode = false;
-        mTopMargin = 0;
-        mBottomMargin = mRows;
-        mAboutToAutoWrap = false;
-        mForeColor = 7;
-        mBackColor = 0;
-        mInverseColors = false;
-        mbKeypadApplicationMode = false;
-        mAlternateCharSet = false;
-        // mProcessedCharCount is preserved unchanged.
-        setDefaultTabStops();
-        blockClear(0, 0, mColumns, mRows);
-    }
-
-    public String getTranscriptText() {
-        return mScreen.getTranscriptText();
-    }
-    public String getSelectedText(int x1, int y1, int x2, int y2) {
-        return mScreen.getSelectedText(x1, y1, x2, y2);
-    }
-}
-
-/**
- * Text renderer interface
- */
-
-interface TextRenderer {
-    int getCharacterWidth();
-    int getCharacterHeight();
-    void drawTextRun(Canvas canvas, float x, float y,
-            int lineOffset, char[] text,
-            int index, int count, boolean cursor, int foreColor, int backColor);
-}
-
-abstract class BaseTextRenderer implements TextRenderer {
-    protected int[] mForePaint = {
-            0xff000000, // Black
-            0xffff0000, // Red
-            0xff00ff00, // green
-            0xffffff00, // yellow
-            0xff0000ff, // blue
-            0xffff00ff, // magenta
-            0xff00ffff, // cyan
-            0xffffffff  // white -- is overridden by constructor
-    };
-    protected int[] mBackPaint = {
-            0xff000000, // Black -- is overridden by constructor
-            0xffcc0000, // Red
-            0xff00cc00, // green
-            0xffcccc00, // yellow
-            0xff0000cc, // blue
-            0xffff00cc, // magenta
-            0xff00cccc, // cyan
-            0xffffffff  // white
-    };
-    protected final static int mCursorPaint = 0xff808080;
-
-    public BaseTextRenderer(int forePaintColor, int backPaintColor) {
-        mForePaint[7] = forePaintColor;
-        mBackPaint[0] = backPaintColor;
-
-    }
-}
-
-class Bitmap4x8FontRenderer extends BaseTextRenderer {
-    private final static int kCharacterWidth = 4;
-    private final static int kCharacterHeight = 8;
-    private Bitmap mFont;
-    private int mCurrentForeColor;
-    private int mCurrentBackColor;
-    private float[] mColorMatrix;
-    private Paint mPaint;
-    private static final float BYTE_SCALE = 1.0f / 255.0f;
-
-    public Bitmap4x8FontRenderer(Resources resources,
-            int forePaintColor, int backPaintColor) {
-        super(forePaintColor, backPaintColor);
-        mFont = BitmapFactory.decodeResource(resources,
-                R.drawable.atari_small);
-        mPaint = new Paint();
-        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
-    }
-
-    public int getCharacterWidth() {
-        return kCharacterWidth;
-    }
-
-    public int getCharacterHeight() {
-        return kCharacterHeight;
-    }
-
-    public void drawTextRun(Canvas canvas, float x, float y,
-            int lineOffset, char[] text, int index, int count,
-            boolean cursor, int foreColor, int backColor) {
-        setColorMatrix(mForePaint[foreColor & 7],
-                cursor ? mCursorPaint : mBackPaint[backColor & 7]);
-        int destX = (int) x + kCharacterWidth * lineOffset;
-        int destY = (int) y;
-        Rect srcRect = new Rect();
-        Rect destRect = new Rect();
-        destRect.top = (destY - kCharacterHeight);
-        destRect.bottom = destY;
-        for(int i = 0; i < count; i++) {
-            char c = text[i + index];
-            if ((cursor || (c != 32)) && (c < 128)) {
-                int cellX = c & 31;
-                int cellY = (c >> 5) & 3;
-                int srcX = cellX * kCharacterWidth;
-                int srcY = cellY * kCharacterHeight;
-                srcRect.set(srcX, srcY,
-                        srcX + kCharacterWidth, srcY + kCharacterHeight);
-                destRect.left = destX;
-                destRect.right = destX + kCharacterWidth;
-                canvas.drawBitmap(mFont, srcRect, destRect, mPaint);
-            }
-            destX += kCharacterWidth;
-        }
-    }
-
-    private void setColorMatrix(int foreColor, int backColor) {
-        if ((foreColor != mCurrentForeColor)
-                || (backColor != mCurrentBackColor)
-                || (mColorMatrix == null)) {
-            mCurrentForeColor = foreColor;
-            mCurrentBackColor = backColor;
-            if (mColorMatrix == null) {
-                mColorMatrix = new float[20];
-                mColorMatrix[18] = 1.0f; // Just copy Alpha
-            }
-            for (int component = 0; component < 3; component++) {
-                int rightShift = (2 - component) << 3;
-                int fore = 0xff & (foreColor >> rightShift);
-                int back = 0xff & (backColor >> rightShift);
-                int delta = back - fore;
-                mColorMatrix[component * 6] = delta * BYTE_SCALE;
-                mColorMatrix[component * 5 + 4] = fore;
-            }
-            mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
-        }
-    }
-}
-
-class PaintRenderer extends BaseTextRenderer {
-    public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) {
-        super(forePaintColor, backPaintColor);
-        mTextPaint = new Paint();
-        mTextPaint.setTypeface(Typeface.MONOSPACE);
-        mTextPaint.setAntiAlias(true);
-        mTextPaint.setTextSize(fontSize);
-
-        mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing());
-        mCharAscent = (int) Math.ceil(mTextPaint.ascent());
-        mCharDescent = mCharHeight + mCharAscent;
-        mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1);
-    }
-
-    public void drawTextRun(Canvas canvas, float x, float y, int lineOffset,
-            char[] text, int index, int count,
-            boolean cursor, int foreColor, int backColor) {
-        if (cursor) {
-            mTextPaint.setColor(mCursorPaint);
-        } else {
-            mTextPaint.setColor(mBackPaint[backColor & 0x7]);
-        }
-        float left = x + lineOffset * mCharWidth;
-        canvas.drawRect(left, y + mCharAscent,
-                left + count * mCharWidth, y + mCharDescent,
-                mTextPaint);
-        boolean bold = ( foreColor & 0x8 ) != 0;
-        boolean underline = (backColor & 0x8) != 0;
-        if (bold) {
-            mTextPaint.setFakeBoldText(true);
-        }
-        if (underline) {
-            mTextPaint.setUnderlineText(true);
-        }
-        mTextPaint.setColor(mForePaint[foreColor & 0x7]);
-        canvas.drawText(text, index, count, left, y, mTextPaint);
-        if (bold) {
-            mTextPaint.setFakeBoldText(false);
-        }
-        if (underline) {
-            mTextPaint.setUnderlineText(false);
-        }
-    }
-
-    public int getCharacterHeight() {
-        return mCharHeight;
-    }
-
-    public int getCharacterWidth() {
-        return mCharWidth;
-    }
-
-
-    private Paint mTextPaint;
-    private int mCharWidth;
-    private int mCharHeight;
-    private int mCharAscent;
-    private int mCharDescent;
-    private static final char[] EXAMPLE_CHAR = {'X'};
-    }
-
-/**
- * A multi-thread-safe produce-consumer byte array.
- * Only allows one producer and one consumer.
- */
-
-class ByteQueue {
-    public ByteQueue(int size) {
-        mBuffer = new byte[size];
-    }
-
-    public int getBytesAvailable() {
-        synchronized(this) {
-            return mStoredBytes;
-        }
-    }
-
-    public int read(byte[] buffer, int offset, int length)
-        throws InterruptedException {
-        if (length + offset > buffer.length) {
-            throw
-                new IllegalArgumentException("length + offset > buffer.length");
-        }
-        if (length < 0) {
-            throw
-            new IllegalArgumentException("length < 0");
-
-        }
-        if (length == 0) {
-            return 0;
-        }
-        synchronized(this) {
-            while (mStoredBytes == 0) {
-                wait();
-            }
-            int totalRead = 0;
-            int bufferLength = mBuffer.length;
-            boolean wasFull = bufferLength == mStoredBytes;
-            while (length > 0 && mStoredBytes > 0) {
-                int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
-                int bytesToCopy = Math.min(length, oneRun);
-                System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
-                mHead += bytesToCopy;
-                if (mHead >= bufferLength) {
-                    mHead = 0;
-                }
-                mStoredBytes -= bytesToCopy;
-                length -= bytesToCopy;
-                offset += bytesToCopy;
-                totalRead += bytesToCopy;
-            }
-            if (wasFull) {
-                notify();
-            }
-            return totalRead;
-        }
-    }
-
-    public void write(byte[] buffer, int offset, int length)
-    throws InterruptedException {
-        if (length + offset > buffer.length) {
-            throw
-                new IllegalArgumentException("length + offset > buffer.length");
-        }
-        if (length < 0) {
-            throw
-            new IllegalArgumentException("length < 0");
-
-        }
-        if (length == 0) {
-            return;
-        }
-        synchronized(this) {
-            int bufferLength = mBuffer.length;
-            boolean wasEmpty = mStoredBytes == 0;
-            while (length > 0) {
-                while(bufferLength == mStoredBytes) {
-                    wait();
-                }
-                int tail = mHead + mStoredBytes;
-                int oneRun;
-                if (tail >= bufferLength) {
-                    tail = tail - bufferLength;
-                    oneRun = mHead - tail;
-                } else {
-                    oneRun = bufferLength - tail;
-                }
-                int bytesToCopy = Math.min(oneRun, length);
-                System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
-                offset += bytesToCopy;
-                mStoredBytes += bytesToCopy;
-                length -= bytesToCopy;
-            }
-            if (wasEmpty) {
-                notify();
-            }
-        }
-    }
-
-    private byte[] mBuffer;
-    private int mHead;
-    private int mStoredBytes;
-}
-/**
- * A view on a transcript and a terminal emulator. Displays the text of the
- * transcript and the current cursor position of the terminal emulator.
- */
-class EmulatorView extends View implements GestureDetector.OnGestureListener {
-
-    private final String TAG = "EmulatorView";
-    private final boolean LOG_KEY_EVENTS = Term.DEBUG && false;
-
-    private Term mTerm;
-
-    /**
-     * We defer some initialization until we have been layed out in the view
-     * hierarchy. The boolean tracks when we know what our size is.
-     */
-    private boolean mKnownSize;
-
-    private int mVisibleWidth;
-    private int mVisibleHeight;
-    private Rect mVisibleRect = new Rect();
-
-    /**
-     * Our transcript. Contains the screen and the transcript.
-     */
-    private TranscriptScreen mTranscriptScreen;
-
-    /**
-     * Number of rows in the transcript.
-     */
-    private static final int TRANSCRIPT_ROWS = 10000;
-
-    /**
-     * Total width of each character, in pixels
-     */
-    private int mCharacterWidth;
-
-    /**
-     * Total height of each character, in pixels
-     */
-    private int mCharacterHeight;
-
-    /**
-     * Used to render text
-     */
-    private TextRenderer mTextRenderer;
-
-    /**
-     * Text size. Zero means 4 x 8 font.
-     */
-    private int mTextSize;
-
-    private int mCursorStyle;
-    private int mCursorBlink;
-
-    /**
-     * Foreground color.
-     */
-    private int mForeground;
-
-    /**
-     * Background color.
-     */
-    private int mBackground;
-
-    /**
-     * Used to paint the cursor
-     */
-    private Paint mCursorPaint;
-
-    private Paint mBackgroundPaint;
-
-    private boolean mUseCookedIme;
-
-    /**
-     * Our terminal emulator. We use this to get the current cursor position.
-     */
-    private TerminalEmulator mEmulator;
-
-    /**
-     * The number of rows of text to display.
-     */
-    private int mRows;
-
-    /**
-     * The number of columns of text to display.
-     */
-    private int mColumns;
-
-    /**
-     * The number of columns that are visible on the display.
-     */
-
-    private int mVisibleColumns;
-
-    /**
-     * The top row of text to display. Ranges from -activeTranscriptRows to 0
-     */
-    private int mTopRow;
-
-    private int mLeftColumn;
-
-    private FileDescriptor mTermFd;
-    /**
-     * Used to receive data from the remote process.
-     */
-    private FileInputStream mTermIn;
-
-    private FileOutputStream mTermOut;
-
-    private ByteQueue mByteQueue;
-
-    /**
-     * Used to temporarily hold data received from the remote process. Allocated
-     * once and used permanently to minimize heap thrashing.
-     */
-    private byte[] mReceiveBuffer;
-
-    /**
-     * Our private message id, which we use to receive new input from the
-     * remote process.
-     */
-    private static final int UPDATE = 1;
-
-    private static final int SCREEN_CHECK_PERIOD = 1000;
-    private static final int CURSOR_BLINK_PERIOD = 1000;
-
-    private boolean mCursorVisible = true;
-
-    private boolean mIsSelectingText = false;
-
-
-    private float mScaledDensity;
-    private static final int SELECT_TEXT_OFFSET_Y = -40;
-    private int mSelXAnchor = -1;
-    private int mSelYAnchor = -1;
-    private int mSelX1 = -1;
-    private int mSelY1 = -1;
-    private int mSelX2 = -1;
-    private int mSelY2 = -1;
-
-    /**
-     * Used to poll if the view has changed size. Wish there was a better way to do this.
-     */
-    private Runnable mCheckSize = new Runnable() {
-
-        public void run() {
-            updateSize(false);
-            mHandler.postDelayed(this, SCREEN_CHECK_PERIOD);
-        }
-    };
-
-    private Runnable mBlinkCursor = new Runnable() {
-        public void run() {
-            if (mCursorBlink != 0) {
-                mCursorVisible = ! mCursorVisible;
-                mHandler.postDelayed(this, CURSOR_BLINK_PERIOD);
-            } else {
-                mCursorVisible = true;
-            }
-            // Perhaps just invalidate the character with the cursor.
-            invalidate();
-        }
-    };
-
-    /**
-     * Thread that polls for input from the remote process
-     */
-
-    private Thread mPollingThread;
-
-    private GestureDetector mGestureDetector;
-    private float mScrollRemainder;
-    private TermKeyListener mKeyListener;
-
-    private String mImeBuffer = "";
-
-    /**
-     * Our message handler class. Implements a periodic callback.
-     */
-    private final Handler mHandler = new Handler() {
-        /**
-         * Handle the callback message. Call our enclosing class's update
-         * method.
-         *
-         * @param msg The callback message.
-         */
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == UPDATE) {
-                update();
-            }
-        }
-    };
-
-    public EmulatorView(Context context) {
-        super(context);
-        commonConstructor();
-    }
-
-    public void setScaledDensity(float scaledDensity) {
-        mScaledDensity = scaledDensity;
-    }
-
-    public void onResume() {
-        updateSize(false);
-        mHandler.postDelayed(mCheckSize, SCREEN_CHECK_PERIOD);
-        if (mCursorBlink != 0) {
-            mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
-        }
-    }
-
-    public void onPause() {
-        mHandler.removeCallbacks(mCheckSize);
-        if (mCursorBlink != 0) {
-            mHandler.removeCallbacks(mBlinkCursor);
-        }
-    }
-
-    public void register(Term term, TermKeyListener listener) {
-        mTerm = term;
-        mKeyListener = listener;
-    }
-
-    public void setColors(int foreground, int background) {
-        mForeground = foreground;
-        mBackground = background;
-        updateText();
-    }
-
-    public String getTranscriptText() {
-        return mEmulator.getTranscriptText();
-    }
-
-    public void resetTerminal() {
-        mEmulator.reset();
-        invalidate();
-    }
-
-    @Override
-    public boolean onCheckIsTextEditor() {
-        return true;
-    }
-
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-        outAttrs.inputType = mUseCookedIme ?
-                EditorInfo.TYPE_CLASS_TEXT :
-                EditorInfo.TYPE_NULL;
-        return new InputConnection() {
-            private boolean mInBatchEdit;
-            /**
-             * Used to handle composing text requests
-             */
-            private int mCursor;
-            private int mComposingTextStart;
-            private int mComposingTextEnd;
-            private int mSelectedTextStart;
-            private int mSelectedTextEnd;
-
-            private void sendChar(int c) {
-                try {
-                    mapAndSend(c);
-                } catch (IOException ex) {
-
-                }
-            }
-
-            private void sendText(CharSequence text) {
-                int n = text.length();
-                try {
-                    for(int i = 0; i < n; i++) {
-                        char c = text.charAt(i);
-                        mapAndSend(c);
-                    }
-                    mTermOut.flush();
-                } catch (IOException e) {
-                    Log.e(TAG, "error writing ", e);
-                }
-            }
-
-            private void mapAndSend(int c) throws IOException {
-                int result = mKeyListener.mapControlChar(c);
-                if (result < TermKeyListener.KEYCODE_OFFSET) {
-                    mTermOut.write(result);
-                } else {
-                    mKeyListener.handleKeyCode(result - TermKeyListener.KEYCODE_OFFSET, mTermOut, getKeypadApplicationMode());
-                }
-            }
-
-            public boolean beginBatchEdit() {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "beginBatchEdit");
-                }
-                setImeBuffer("");
-                mCursor = 0;
-                mComposingTextStart = 0;
-                mComposingTextEnd = 0;
-                mInBatchEdit = true;
-                return true;
-            }
-
-            public boolean clearMetaKeyStates(int arg0) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "clearMetaKeyStates " + arg0);
-                }
-                return false;
-            }
-
-            public boolean commitCompletion(CompletionInfo arg0) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "commitCompletion " + arg0);
-                }
-                return false;
-            }
-
-            public boolean endBatchEdit() {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "endBatchEdit");
-                }
-                mInBatchEdit = false;
-                return true;
-            }
-
-            public boolean finishComposingText() {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "finishComposingText");
-                }
-                sendText(mImeBuffer);
-                setImeBuffer("");
-                mComposingTextStart = 0;
-                mComposingTextEnd = 0;
-                mCursor = 0;
-                return true;
-            }
-
-            public int getCursorCapsMode(int arg0) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "getCursorCapsMode(" + arg0 + ")");
-                }
-                return 0;
-            }
-
-            public ExtractedText getExtractedText(ExtractedTextRequest arg0,
-                    int arg1) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "getExtractedText" + arg0 + "," + arg1);
-                }
-                return null;
-            }
-
-            public CharSequence getTextAfterCursor(int n, int flags) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "getTextAfterCursor(" + n + "," + flags + ")");
-                }
-                int len = Math.min(n, mImeBuffer.length() - mCursor);
-                if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
-                    return "";
-                }
-                return mImeBuffer.substring(mCursor, mCursor + len);
-            }
-
-            public CharSequence getTextBeforeCursor(int n, int flags) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "getTextBeforeCursor(" + n + "," + flags + ")");
-                }
-                int len = Math.min(n, mCursor);
-                if (len <= 0 || mCursor < 0 || mCursor >= mImeBuffer.length()) {
-                    return "";
-                }
-                return mImeBuffer.substring(mCursor-len, mCursor);
-            }
-
-            public boolean performContextMenuAction(int arg0) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "performContextMenuAction" + arg0);
-                }
-                return true;
-            }
-
-            public boolean performPrivateCommand(String arg0, Bundle arg1) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "performPrivateCommand" + arg0 + "," + arg1);
-                }
-                return true;
-            }
-
-            public boolean reportFullscreenMode(boolean arg0) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "reportFullscreenMode" + arg0);
-                }
-                return true;
-            }
-
-            public boolean commitCorrection (CorrectionInfo correctionInfo) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "commitCorrection");
-                }
-                return true;
-            }
-
-            public boolean commitText(CharSequence text, int newCursorPosition) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "commitText(\"" + text + "\", " + newCursorPosition + ")");
-                }
-                clearComposingText();
-                sendText(text);
-                setImeBuffer("");
-                mCursor = 0;
-                return true;
-            }
-
-            private void clearComposingText() {
-                setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
-                    mImeBuffer.substring(mComposingTextEnd));
-                if (mCursor < mComposingTextStart) {
-                    // do nothing
-                } else if (mCursor < mComposingTextEnd) {
-                    mCursor = mComposingTextStart;
-                } else {
-                    mCursor -= mComposingTextEnd - mComposingTextStart;
-                }
-                mComposingTextEnd = mComposingTextStart = 0;
-            }
-
-            public boolean deleteSurroundingText(int leftLength, int rightLength) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "deleteSurroundingText(" + leftLength +
-                            "," + rightLength + ")");
-                }
-                if (leftLength > 0) {
-                    for (int i = 0; i < leftLength; i++) {
-                        sendKeyEvent(
-                            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
-                    }
-                } else if ((leftLength == 0) && (rightLength == 0)) {
-                    // Delete key held down / repeating
-                    sendKeyEvent(
-                        new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
-                }
-                // TODO: handle forward deletes.
-                return true;
-            }
-
-            public boolean performEditorAction(int actionCode) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "performEditorAction(" + actionCode + ")");
-                }
-                if (actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
-                    // The "return" key has been pressed on the IME.
-                    sendText("\n");
-                }
-                return true;
-            }
-
-            public boolean sendKeyEvent(KeyEvent event) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "sendKeyEvent(" + event + ")");
-                }
-                // Some keys are sent here rather than to commitText.
-                // In particular, del and the digit keys are sent here.
-                // (And I have reports that the HTC Magic also sends Return here.)
-                // As a bit of defensive programming, handle every key.
-                dispatchKeyEvent(event);
-                return true;
-            }
-
-            public boolean setComposingText(CharSequence text, int newCursorPosition) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "setComposingText(\"" + text + "\", " + newCursorPosition + ")");
-                }
-                setImeBuffer(mImeBuffer.substring(0, mComposingTextStart) +
-                    text + mImeBuffer.substring(mComposingTextEnd));
-                mComposingTextEnd = mComposingTextStart + text.length();
-                mCursor = newCursorPosition > 0 ? mComposingTextEnd + newCursorPosition - 1
-                        : mComposingTextStart - newCursorPosition;
-                return true;
-            }
-
-            public boolean setSelection(int start, int end) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "setSelection" + start + "," + end);
-                }
-                int length = mImeBuffer.length();
-                if (start == end && start > 0 && start < length) {
-                    mSelectedTextStart = mSelectedTextEnd = 0;
-                    mCursor = start;
-                } else if (start < end && start > 0 && end < length) {
-                    mSelectedTextStart = start;
-                    mSelectedTextEnd = end;
-                    mCursor = start;
-                }
-                return true;
-            }
-
-            public boolean setComposingRegion(int start, int end) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "setComposingRegion " + start + "," + end);
-                }
-                if (start < end && start > 0 && end < mImeBuffer.length()) {
-                    clearComposingText();
-                    mComposingTextStart = start;
-                    mComposingTextEnd = end;
-                }
-                return true;
-            }
-
-            public CharSequence getSelectedText(int flags) {
-                if (Term.LOG_IME) {
-                    Log.w(TAG, "getSelectedText " + flags);
-                }
-                return mImeBuffer.substring(mSelectedTextStart, mSelectedTextEnd+1);
-            }
-            
-        };
-    }
-
-    private void setImeBuffer(String buffer) {
-        if (!buffer.equals(mImeBuffer)) {
-            invalidate();
-        }
-        mImeBuffer = buffer;
-    }
-
-    public boolean getKeypadApplicationMode() {
-        return mEmulator.getKeypadApplicationMode();
-    }
-
-    public EmulatorView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public EmulatorView(Context context, AttributeSet attrs,
-            int defStyle) {
-        super(context, attrs, defStyle);
-        // TypedArray a =
-        //        context.obtainStyledAttributes(android.R.styleable.View);
-        // initializeScrollbars(a);
-        // a.recycle();
-        commonConstructor();
-    }
-
-    private void commonConstructor() {
-        mTextRenderer = null;
-        mCursorPaint = new Paint();
-        mCursorPaint.setARGB(255,128,128,128);
-        mBackgroundPaint = new Paint();
-        mTopRow = 0;
-        mLeftColumn = 0;
-        mGestureDetector = new GestureDetector(this);
-        // mGestureDetector.setIsLongpressEnabled(false);
-        setVerticalScrollBarEnabled(true);
-    }
-
-    @Override
-    protected int computeVerticalScrollRange() {
-        return mTranscriptScreen.getActiveRows();
-    }
-
-    @Override
-    protected int computeVerticalScrollExtent() {
-        return mRows;
-    }
-
-    @Override
-    protected int computeVerticalScrollOffset() {
-        return mTranscriptScreen.getActiveRows() + mTopRow - mRows;
-    }
-
-    /**
-     * Call this to initialize the view.
-     *
-     * @param termFd the file descriptor
-     * @param termOut the output stream for the pseudo-teletype
-     */
-    public void initialize(FileDescriptor termFd, FileOutputStream termOut) {
-        mTermOut = termOut;
-        mTermFd = termFd;
-        mTextSize = 10;
-        mForeground = Term.WHITE;
-        mBackground = Term.BLACK;
-        updateText();
-        mTermIn = new FileInputStream(mTermFd);
-        mReceiveBuffer = new byte[4 * 1024];
-        mByteQueue = new ByteQueue(4 * 1024);
-    }
-
-    /**
-     * Accept a sequence of bytes (typically from the pseudo-tty) and process
-     * them.
-     *
-     * @param buffer a byte array containing bytes to be processed
-     * @param base the index of the first byte in the buffer to process
-     * @param length the number of bytes to process
-     */
-    public void append(byte[] buffer, int base, int length) {
-        mEmulator.append(buffer, base, length);
-        if ( mIsSelectingText ) {
-            int rowShift = mEmulator.getScrollCounter();
-            mSelY1 -= rowShift;
-            mSelY2 -= rowShift;
-            mSelYAnchor -= rowShift;
-        }
-        mEmulator.clearScrollCounter();
-        ensureCursorVisible();
-        invalidate();
-    }
-
-    /**
-     * Page the terminal view (scroll it up or down by delta screenfulls.)
-     *
-     * @param delta the number of screens to scroll. Positive means scroll down,
-     *        negative means scroll up.
-     */
-    public void page(int delta) {
-        mTopRow =
-                Math.min(0, Math.max(-(mTranscriptScreen
-                        .getActiveTranscriptRows()), mTopRow + mRows * delta));
-        invalidate();
-    }
-
-    /**
-     * Page the terminal view horizontally.
-     *
-     * @param deltaColumns the number of columns to scroll. Positive scrolls to
-     *        the right.
-     */
-    public void pageHorizontal(int deltaColumns) {
-        mLeftColumn =
-                Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns
-                        - mVisibleColumns));
-        invalidate();
-    }
-
-    /**
-     * Sets the text size, which in turn sets the number of rows and columns
-     *
-     * @param fontSize the new font size, in pixels.
-     */
-    public void setTextSize(int fontSize) {
-        mTextSize = fontSize;
-        updateText();
-    }
-
-    public void setCursorStyle(int style, int blink) {
-        mCursorStyle = style;
-        if (blink != 0 && mCursorBlink == 0) {
-            mHandler.postDelayed(mBlinkCursor, CURSOR_BLINK_PERIOD);
-        } else if (blink == 0 && mCursorBlink != 0) {
-            mHandler.removeCallbacks(mBlinkCursor);
-        }
-        mCursorBlink = blink;
-    }
-
-    public void setUseCookedIME(boolean useCookedIME) {
-        mUseCookedIme = useCookedIME;
-    }
-
-    // Begin GestureDetector.OnGestureListener methods
-
-    public boolean onSingleTapUp(MotionEvent e) {
-        return true;
-    }
-
-    public void onLongPress(MotionEvent e) {
-        showContextMenu();
-    }
-
-    public boolean onScroll(MotionEvent e1, MotionEvent e2,
-            float distanceX, float distanceY) {
-        distanceY += mScrollRemainder;
-        int deltaRows = (int) (distanceY / mCharacterHeight);
-        mScrollRemainder = distanceY - deltaRows * mCharacterHeight;
-        mTopRow =
-            Math.min(0, Math.max(-(mTranscriptScreen
-                    .getActiveTranscriptRows()), mTopRow + deltaRows));
-        invalidate();
-
-        return true;
-   }
-
-    public void onSingleTapConfirmed(MotionEvent e) {
-    }
-
-    public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) {
-       // Scroll to bottom
-       mTopRow = 0;
-       invalidate();
-       return true;
-    }
-
-    public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) {
-        // Scroll to top
-        mTopRow = -mTranscriptScreen.getActiveTranscriptRows();
-        invalidate();
-        return true;
-    }
-
-    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
-            float velocityY) {
-        // TODO: add animation man's (non animated) fling
-        mScrollRemainder = 0.0f;
-        onScroll(e1, e2, 2 * velocityX, -2 * velocityY);
-        return true;
-    }
-
-    public void onShowPress(MotionEvent e) {
-    }
-
-    public boolean onDown(MotionEvent e) {
-        mScrollRemainder = 0.0f;
-        return true;
-    }
-
-    // End GestureDetector.OnGestureListener methods
-
-    @Override public boolean onTouchEvent(MotionEvent ev) {
-        if (mIsSelectingText) {
-            return onTouchEventWhileSelectingText(ev);
-        } else {
-            return mGestureDetector.onTouchEvent(ev);
-        }
-    }
-
-    private boolean onTouchEventWhileSelectingText(MotionEvent ev) {
-        int action = ev.getAction();
-        int cx = (int)(ev.getX() / mCharacterWidth);
-        int cy = Math.max(0,
-                (int)((ev.getY() + SELECT_TEXT_OFFSET_Y * mScaledDensity)
-                        / mCharacterHeight) + mTopRow);
-        switch (action) {
-        case MotionEvent.ACTION_DOWN:
-            mSelXAnchor = cx;
-            mSelYAnchor = cy;
-            mSelX1 = cx;
-            mSelY1 = cy;
-            mSelX2 = mSelX1;
-            mSelY2 = mSelY1;
-            break;
-        case MotionEvent.ACTION_MOVE:
-        case MotionEvent.ACTION_UP:
-            int minx = Math.min(mSelXAnchor, cx);
-            int maxx = Math.max(mSelXAnchor, cx);
-            int miny = Math.min(mSelYAnchor, cy);
-            int maxy = Math.max(mSelYAnchor, cy);
-            mSelX1 = minx;
-            mSelY1 = miny;
-            mSelX2 = maxx;
-            mSelY2 = maxy;
-            if (action == MotionEvent.ACTION_UP) {
-                ClipboardManager clip = (ClipboardManager)
-                     getContext().getApplicationContext()
-                         .getSystemService(Context.CLIPBOARD_SERVICE);
-                clip.setText(getSelectedText().trim());
-                toggleSelectingText();
-            }
-            invalidate();
-            break;
-        default:
-            toggleSelectingText();
-            invalidate();
-            break;
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onKeyDown(int keyCode, KeyEvent event) {
-        if (LOG_KEY_EVENTS) {
-            Log.w(TAG, "onKeyDown " + keyCode);
-        }
-        if (handleControlKey(keyCode, true)) {
-            return true;
-        } else if (handleFnKey(keyCode, true)) {
-            return true;
-        } else if (isSystemKey(keyCode, event)) {
-            // Don't intercept the system keys
-            return super.onKeyDown(keyCode, event);
-        }
-
-        // Translate the keyCode into an ASCII character.
-
-        try {
-            mKeyListener.keyDown(keyCode, event, mTermOut,
-                    getKeypadApplicationMode());
-        } catch (IOException e) {
-            // Ignore I/O exceptions
-        }
-        return true;
-    }
-
-    @Override
-    public boolean onKeyUp(int keyCode, KeyEvent event) {
-        if (LOG_KEY_EVENTS) {
-            Log.w(TAG, "onKeyUp " + keyCode);
-        }
-        if (handleControlKey(keyCode, false)) {
-            return true;
-        } else if (handleFnKey(keyCode, false)) {
-            return true;
-        } else if (isSystemKey(keyCode, event)) {
-            // Don't intercept the system keys
-            return super.onKeyUp(keyCode, event);
-        }
-
-        mKeyListener.keyUp(keyCode);
-        return true;
-    }
-
-
-    private boolean handleControlKey(int keyCode, boolean down) {
-        if (keyCode == mTerm.getControlKeyCode()) {
-            if (LOG_KEY_EVENTS) {
-                Log.w(TAG, "handleControlKey " + keyCode);
-            }
-            mKeyListener.handleControlKey(down);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean handleFnKey(int keyCode, boolean down) {
-        if (keyCode == mTerm.getFnKeyCode()) {
-            if (LOG_KEY_EVENTS) {
-                Log.w(TAG, "handleFnKey " + keyCode);
-            }
-            mKeyListener.handleFnKey(down);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean isSystemKey(int keyCode, KeyEvent event) {
-        return event.isSystem();
-    }
-
-    private void updateText() {
-        if (mTextSize > 0) {
-            mTextRenderer = new PaintRenderer(mTextSize, mForeground,
-                    mBackground);
-        }
-        else {
-            mTextRenderer = new Bitmap4x8FontRenderer(getResources(),
-                    mForeground, mBackground);
-        }
-        mBackgroundPaint.setColor(mBackground);
-        mCharacterWidth = mTextRenderer.getCharacterWidth();
-        mCharacterHeight = mTextRenderer.getCharacterHeight();
-
-        updateSize(true);
-    }
-
-    @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        boolean oldKnownSize = mKnownSize;
-        if (!mKnownSize) {
-            mKnownSize = true;
-        }
-        updateSize(false);
-        if (!oldKnownSize) {
-            // Set up a thread to read input from the
-            // pseudo-teletype:
-
-            mPollingThread = new Thread(new Runnable() {
-
-                public void run() {
-                    try {
-                        while(true) {
-                            int read = mTermIn.read(mBuffer);
-                            mByteQueue.write(mBuffer, 0, read);
-                            mHandler.sendMessage(
-                                    mHandler.obtainMessage(UPDATE));
-                        }
-                    } catch (IOException e) {
-                    } catch (InterruptedException e) {
-                    }
-                }
-                private byte[] mBuffer = new byte[4096];
-            });
-            mPollingThread.setName("Input reader");
-            mPollingThread.start();
-        }
-    }
-
-    private void updateSize(int w, int h) {
-        mColumns = Math.max(1, w / mCharacterWidth);
-        mRows = Math.max(1, h / mCharacterHeight);
-        mVisibleColumns = mVisibleWidth / mCharacterWidth;
-
-        // Inform the attached pty of our new size:
-        Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h);
-
-
-        if (mTranscriptScreen != null) {
-            mEmulator.updateSize(mColumns, mRows);
-        } else {
-            mTranscriptScreen =
-                    new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7);
-            mEmulator =
-                    new TerminalEmulator(mTranscriptScreen, mColumns, mRows,
-                            mTermOut);
-        }
-
-        // Reset our paging:
-        mTopRow = 0;
-        mLeftColumn = 0;
-
-        invalidate();
-    }
-
-    void updateSize(boolean force) {
-        if (mKnownSize) {
-            getWindowVisibleDisplayFrame(mVisibleRect);
-            int w = mVisibleRect.width();
-            int h = mVisibleRect.height();
-            // Log.w("Term", "(" + w + ", " + h + ")");
-            if (force || w != mVisibleWidth || h != mVisibleHeight) {
-                mVisibleWidth = w;
-                mVisibleHeight = h;
-                updateSize(mVisibleWidth, mVisibleHeight);
-            }
-        }
-    }
-
-    /**
-     * Look for new input from the ptty, send it to the terminal emulator.
-     */
-    private void update() {
-        int bytesAvailable = mByteQueue.getBytesAvailable();
-        int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
-        try {
-            int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
-            append(mReceiveBuffer, 0, bytesRead);
-        } catch (InterruptedException e) {
-        }
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        updateSize(false);
-        int w = getWidth();
-        int h = getHeight();
-        canvas.drawRect(0, 0, w, h, mBackgroundPaint);
-        float x = -mLeftColumn * mCharacterWidth;
-        float y = mCharacterHeight;
-        int endLine = mTopRow + mRows;
-        int cx = mEmulator.getCursorCol();
-        int cy = mEmulator.getCursorRow();
-        for (int i = mTopRow; i < endLine; i++) {
-            int cursorX = -1;
-            if (i == cy && mCursorVisible) {
-                cursorX = cx;
-            }
-            int selx1 = -1;
-            int selx2 = -1;
-            if ( i >= mSelY1 && i <= mSelY2 ) {
-                if ( i == mSelY1 ) {
-                    selx1 = mSelX1;
-                }
-                if ( i == mSelY2 ) {
-                    selx2 = mSelX2;
-                } else {
-                    selx2 = mColumns;
-                }
-            }
-            mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX, selx1, selx2, mImeBuffer);
-            y += mCharacterHeight;
-        }
-    }
-
-    private void ensureCursorVisible() {
-        mTopRow = 0;
-        if (mVisibleColumns > 0) {
-            int cx = mEmulator.getCursorCol();
-            int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn;
-            if (visibleCursorX < 0) {
-                mLeftColumn = cx;
-            } else if (visibleCursorX >= mVisibleColumns) {
-                mLeftColumn = (cx - mVisibleColumns) + 1;
-            }
-        }
-    }
-
-    public void toggleSelectingText() {
-        mIsSelectingText = ! mIsSelectingText;
-        setVerticalScrollBarEnabled( ! mIsSelectingText );
-        if ( ! mIsSelectingText ) {
-            mSelX1 = -1;
-            mSelY1 = -1;
-            mSelX2 = -1;
-            mSelY2 = -1;
-        }
-    }
-
-    public boolean getSelectingText() {
-        return mIsSelectingText;
-    }
-
-    public String getSelectedText() {
-        return mEmulator.getSelectedText(mSelX1, mSelY1, mSelX2, mSelY2);
-    }
-}
-
-
-/**
- * An ASCII key listener. Supports control characters and escape. Keeps track of
- * the current state of the alt, shift, and control keys.
- */
-class TermKeyListener {
-    /**
-     * Android key codes that are defined in the Android 2.3 API.
-     * We want to recognize these codes, because they will be sent to our
-     * app when we run on Android 2.3 systems.
-     * But we don't want to accidentally use 2.3-specific APIs.
-     * So we compile against the Android 1.6 APIs, and have a copy of the codes here.
-     */
-
-    /** Key code constant: Unknown key code. */
-    public static final int KEYCODE_UNKNOWN         = 0;
-    /** Key code constant: Soft Left key.
-     * Usually situated below the display on phones and used as a multi-function
-     * feature key for selecting a software defined function shown on the bottom left
-     * of the display. */
-    public static final int KEYCODE_SOFT_LEFT       = 1;
-    /** Key code constant: Soft Right key.
-     * Usually situated below the display on phones and used as a multi-function
-     * feature key for selecting a software defined function shown on the bottom right
-     * of the display. */
-    public static final int KEYCODE_SOFT_RIGHT      = 2;
-    /** Key code constant: Home key.
-     * This key is handled by the framework and is never delivered to applications. */
-    public static final int KEYCODE_HOME            = 3;
-    /** Key code constant: Back key. */
-    public static final int KEYCODE_BACK            = 4;
-    /** Key code constant: Call key. */
-    public static final int KEYCODE_CALL            = 5;
-    /** Key code constant: End Call key. */
-    public static final int KEYCODE_ENDCALL         = 6;
-    /** Key code constant: '0' key. */
-    public static final int KEYCODE_0               = 7;
-    /** Key code constant: '1' key. */
-    public static final int KEYCODE_1               = 8;
-    /** Key code constant: '2' key. */
-    public static final int KEYCODE_2               = 9;
-    /** Key code constant: '3' key. */
-    public static final int KEYCODE_3               = 10;
-    /** Key code constant: '4' key. */
-    public static final int KEYCODE_4               = 11;
-    /** Key code constant: '5' key. */
-    public static final int KEYCODE_5               = 12;
-    /** Key code constant: '6' key. */
-    public static final int KEYCODE_6               = 13;
-    /** Key code constant: '7' key. */
-    public static final int KEYCODE_7               = 14;
-    /** Key code constant: '8' key. */
-    public static final int KEYCODE_8               = 15;
-    /** Key code constant: '9' key. */
-    public static final int KEYCODE_9               = 16;
-    /** Key code constant: '*' key. */
-    public static final int KEYCODE_STAR            = 17;
-    /** Key code constant: '#' key. */
-    public static final int KEYCODE_POUND           = 18;
-    /** Key code constant: Directional Pad Up key.
-     * May also be synthesized from trackball motions. */
-    public static final int KEYCODE_DPAD_UP         = 19;
-    /** Key code constant: Directional Pad Down key.
-     * May also be synthesized from trackball motions. */
-    public static final int KEYCODE_DPAD_DOWN       = 20;
-    /** Key code constant: Directional Pad Left key.
-     * May also be synthesized from trackball motions. */
-    public static final int KEYCODE_DPAD_LEFT       = 21;
-    /** Key code constant: Directional Pad Right key.
-     * May also be synthesized from trackball motions. */
-    public static final int KEYCODE_DPAD_RIGHT      = 22;
-    /** Key code constant: Directional Pad Center key.
-     * May also be synthesized from trackball motions. */
-    public static final int KEYCODE_DPAD_CENTER     = 23;
-    /** Key code constant: Volume Up key.
-     * Adjusts the speaker volume up. */
-    public static final int KEYCODE_VOLUME_UP       = 24;
-    /** Key code constant: Volume Down key.
-     * Adjusts the speaker volume down. */
-    public static final int KEYCODE_VOLUME_DOWN     = 25;
-    /** Key code constant: Power key. */
-    public static final int KEYCODE_POWER           = 26;
-    /** Key code constant: Camera key.
-     * Used to launch a camera application or take pictures. */
-    public static final int KEYCODE_CAMERA          = 27;
-    /** Key code constant: Clear key. */
-    public static final int KEYCODE_CLEAR           = 28;
-    /** Key code constant: 'A' key. */
-    public static final int KEYCODE_A               = 29;
-    /** Key code constant: 'B' key. */
-    public static final int KEYCODE_B               = 30;
-    /** Key code constant: 'C' key. */
-    public static final int KEYCODE_C               = 31;
-    /** Key code constant: 'D' key. */
-    public static final int KEYCODE_D               = 32;
-    /** Key code constant: 'E' key. */
-    public static final int KEYCODE_E               = 33;
-    /** Key code constant: 'F' key. */
-    public static final int KEYCODE_F               = 34;
-    /** Key code constant: 'G' key. */
-    public static final int KEYCODE_G               = 35;
-    /** Key code constant: 'H' key. */
-    public static final int KEYCODE_H               = 36;
-    /** Key code constant: 'I' key. */
-    public static final int KEYCODE_I               = 37;
-    /** Key code constant: 'J' key. */
-    public static final int KEYCODE_J               = 38;
-    /** Key code constant: 'K' key. */
-    public static final int KEYCODE_K               = 39;
-    /** Key code constant: 'L' key. */
-    public static final int KEYCODE_L               = 40;
-    /** Key code constant: 'M' key. */
-    public static final int KEYCODE_M               = 41;
-    /** Key code constant: 'N' key. */
-    public static final int KEYCODE_N               = 42;
-    /** Key code constant: 'O' key. */
-    public static final int KEYCODE_O               = 43;
-    /** Key code constant: 'P' key. */
-    public static final int KEYCODE_P               = 44;
-    /** Key code constant: 'Q' key. */
-    public static final int KEYCODE_Q               = 45;
-    /** Key code constant: 'R' key. */
-    public static final int KEYCODE_R               = 46;
-    /** Key code constant: 'S' key. */
-    public static final int KEYCODE_S               = 47;
-    /** Key code constant: 'T' key. */
-    public static final int KEYCODE_T               = 48;
-    /** Key code constant: 'U' key. */
-    public static final int KEYCODE_U               = 49;
-    /** Key code constant: 'V' key. */
-    public static final int KEYCODE_V               = 50;
-    /** Key code constant: 'W' key. */
-    public static final int KEYCODE_W               = 51;
-    /** Key code constant: 'X' key. */
-    public static final int KEYCODE_X               = 52;
-    /** Key code constant: 'Y' key. */
-    public static final int KEYCODE_Y               = 53;
-    /** Key code constant: 'Z' key. */
-    public static final int KEYCODE_Z               = 54;
-    /** Key code constant: ',' key. */
-    public static final int KEYCODE_COMMA           = 55;
-    /** Key code constant: '.' key. */
-    public static final int KEYCODE_PERIOD          = 56;
-    /** Key code constant: Left Alt modifier key. */
-    public static final int KEYCODE_ALT_LEFT        = 57;
-    /** Key code constant: Right Alt modifier key. */
-    public static final int KEYCODE_ALT_RIGHT       = 58;
-    /** Key code constant: Left Shift modifier key. */
-    public static final int KEYCODE_SHIFT_LEFT      = 59;
-    /** Key code constant: Right Shift modifier key. */
-    public static final int KEYCODE_SHIFT_RIGHT     = 60;
-    /** Key code constant: Tab key. */
-    public static final int KEYCODE_TAB             = 61;
-    /** Key code constant: Space key. */
-    public static final int KEYCODE_SPACE           = 62;
-    /** Key code constant: Symbol modifier key.
-     * Used to enter alternate symbols. */
-    public static final int KEYCODE_SYM             = 63;
-    /** Key code constant: Explorer special function key.
-     * Used to launch a browser application. */
-    public static final int KEYCODE_EXPLORER        = 64;
-    /** Key code constant: Envelope special function key.
-     * Used to launch a mail application. */
-    public static final int KEYCODE_ENVELOPE        = 65;
-    /** Key code constant: Enter key. */
-    public static final int KEYCODE_ENTER           = 66;
-    /** Key code constant: Backspace key.
-     * Deletes characters before the insertion point, unlike {@link #KEYCODE_FORWARD_DEL}. */
-    public static final int KEYCODE_DEL             = 67;
-    /** Key code constant: '`' (backtick) key. */
-    public static final int KEYCODE_GRAVE           = 68;
-    /** Key code constant: '-'. */
-    public static final int KEYCODE_MINUS           = 69;
-    /** Key code constant: '=' key. */
-    public static final int KEYCODE_EQUALS          = 70;
-    /** Key code constant: '[' key. */
-    public static final int KEYCODE_LEFT_BRACKET    = 71;
-    /** Key code constant: ']' key. */
-    public static final int KEYCODE_RIGHT_BRACKET   = 72;
-    /** Key code constant: '\' key. */
-    public static final int KEYCODE_BACKSLASH       = 73;
-    /** Key code constant: ';' key. */
-    public static final int KEYCODE_SEMICOLON       = 74;
-    /** Key code constant: ''' (apostrophe) key. */
-    public static final int KEYCODE_APOSTROPHE      = 75;
-    /** Key code constant: '/' key. */
-    public static final int KEYCODE_SLASH           = 76;
-    /** Key code constant: '@' key. */
-    public static final int KEYCODE_AT              = 77;
-    /** Key code constant: Number modifier key.
-     * Used to enter numeric symbols.
-     * This key is not Num Lock; it is more like {@link #KEYCODE_ALT_LEFT} and is
-     * interpreted as an ALT key by {@link android.text.method.MetaKeyKeyListener}. */
-    public static final int KEYCODE_NUM             = 78;
-    /** Key code constant: Headset Hook key.
-     * Used to hang up calls and stop media. */
-    public static final int KEYCODE_HEADSETHOOK     = 79;
-    /** Key code constant: Camera Focus key.
-     * Used to focus the camera. */
-    public static final int KEYCODE_FOCUS           = 80;   // *Camera* focus
-    /** Key code constant: '+' key. */
-    public static final int KEYCODE_PLUS            = 81;
-    /** Key code constant: Menu key. */
-    public static final int KEYCODE_MENU            = 82;
-    /** Key code constant: Notification key. */
-    public static final int KEYCODE_NOTIFICATION    = 83;
-    /** Key code constant: Search key. */
-    public static final int KEYCODE_SEARCH          = 84;
-    /** Key code constant: Play/Pause media key. */
-    public static final int KEYCODE_MEDIA_PLAY_PAUSE= 85;
-    /** Key code constant: Stop media key. */
-    public static final int KEYCODE_MEDIA_STOP      = 86;
-    /** Key code constant: Play Next media key. */
-    public static final int KEYCODE_MEDIA_NEXT      = 87;
-    /** Key code constant: Play Previous media key. */
-    public static final int KEYCODE_MEDIA_PREVIOUS  = 88;
-    /** Key code constant: Rewind media key. */
-    public static final int KEYCODE_MEDIA_REWIND    = 89;
-    /** Key code constant: Fast Forward media key. */
-    public static final int KEYCODE_MEDIA_FAST_FORWARD = 90;
-    /** Key code constant: Mute key.
-     * Mutes the microphone, unlike {@link #KEYCODE_VOLUME_MUTE}. */
-    public static final int KEYCODE_MUTE            = 91;
-    /** Key code constant: Page Up key. */
-    public static final int KEYCODE_PAGE_UP         = 92;
-    /** Key code constant: Page Down key. */
-    public static final int KEYCODE_PAGE_DOWN       = 93;
-    /** Key code constant: Picture Symbols modifier key.
-     * Used to switch symbol sets (Emoji, Kao-moji). */
-    public static final int KEYCODE_PICTSYMBOLS     = 94;   // switch symbol-sets (Emoji,Kao-moji)
-    /** Key code constant: Switch Charset modifier key.
-     * Used to switch character sets (Kanji, Katakana). */
-    public static final int KEYCODE_SWITCH_CHARSET  = 95;   // switch char-sets (Kanji,Katakana)
-    /** Key code constant: A Button key.
-     * On a game controller, the A button should be either the button labeled A
-     * or the first button on the upper row of controller buttons. */
-    public static final int KEYCODE_BUTTON_A        = 96;
-    /** Key code constant: B Button key.
-     * On a game controller, the B button should be either the button labeled B
-     * or the second button on the upper row of controller buttons. */
-    public static final int KEYCODE_BUTTON_B        = 97;
-    /** Key code constant: C Button key.
-     * On a game controller, the C button should be either the button labeled C
-     * or the third button on the upper row of controller buttons. */
-    public static final int KEYCODE_BUTTON_C        = 98;
-    /** Key code constant: X Button key.
-     * On a game controller, the X button should be either the button labeled X
-     * or the first button on the lower row of controller buttons. */
-    public static final int KEYCODE_BUTTON_X        = 99;
-    /** Key code constant: Y Button key.
-     * On a game controller, the Y button should be either the button labeled Y
-     * or the second button on the lower row of controller buttons. */
-    public static final int KEYCODE_BUTTON_Y        = 100;
-    /** Key code constant: Z Button key.
-     * On a game controller, the Z button should be either the button labeled Z
-     * or the third button on the lower row of controller buttons. */
-    public static final int KEYCODE_BUTTON_Z        = 101;
-    /** Key code constant: L1 Button key.
-     * On a game controller, the L1 button should be either the button labeled L1 (or L)
-     * or the top left trigger button. */
-    public static final int KEYCODE_BUTTON_L1       = 102;
-    /** Key code constant: R1 Button key.
-     * On a game controller, the R1 button should be either the button labeled R1 (or R)
-     * or the top right trigger button. */
-    public static final int KEYCODE_BUTTON_R1       = 103;
-    /** Key code constant: L2 Button key.
-     * On a game controller, the L2 button should be either the button labeled L2
-     * or the bottom left trigger button. */
-    public static final int KEYCODE_BUTTON_L2       = 104;
-    /** Key code constant: R2 Button key.
-     * On a game controller, the R2 button should be either the button labeled R2
-     * or the bottom right trigger button. */
-    public static final int KEYCODE_BUTTON_R2       = 105;
-    /** Key code constant: Left Thumb Button key.
-     * On a game controller, the left thumb button indicates that the left (or only)
-     * joystick is pressed. */
-    public static final int KEYCODE_BUTTON_THUMBL   = 106;
-    /** Key code constant: Right Thumb Button key.
-     * On a game controller, the right thumb button indicates that the right
-     * joystick is pressed. */
-    public static final int KEYCODE_BUTTON_THUMBR   = 107;
-    /** Key code constant: Start Button key.
-     * On a game controller, the button labeled Start. */
-    public static final int KEYCODE_BUTTON_START    = 108;
-    /** Key code constant: Select Button key.
-     * On a game controller, the button labeled Select. */
-    public static final int KEYCODE_BUTTON_SELECT   = 109;
-    /** Key code constant: Mode Button key.
-     * On a game controller, the button labeled Mode. */
-    public static final int KEYCODE_BUTTON_MODE     = 110;
-    /** Key code constant: Escape key. */
-    public static final int KEYCODE_ESCAPE          = 111;
-    /** Key code constant: Forward Delete key.
-     * Deletes characters ahead of the insertion point, unlike {@link #KEYCODE_DEL}. */
-    public static final int KEYCODE_FORWARD_DEL     = 112;
-    /** Key code constant: Left Control modifier key. */
-    public static final int KEYCODE_CTRL_LEFT       = 113;
-    /** Key code constant: Right Control modifier key. */
-    public static final int KEYCODE_CTRL_RIGHT      = 114;
-    /** Key code constant: Caps Lock modifier key. */
-    public static final int KEYCODE_CAPS_LOCK       = 115;
-    /** Key code constant: Scroll Lock key. */
-    public static final int KEYCODE_SCROLL_LOCK     = 116;
-    /** Key code constant: Left Meta modifier key. */
-    public static final int KEYCODE_META_LEFT       = 117;
-    /** Key code constant: Right Meta modifier key. */
-    public static final int KEYCODE_META_RIGHT      = 118;
-    /** Key code constant: Function modifier key. */
-    public static final int KEYCODE_FUNCTION        = 119;
-    /** Key code constant: System Request / Print Screen key. */
-    public static final int KEYCODE_SYSRQ           = 120;
-    /** Key code constant: Break / Pause key. */
-    public static final int KEYCODE_BREAK           = 121;
-    /** Key code constant: Home Movement key.
-     * Used for scrolling or moving the cursor around to the start of a line
-     * or to the top of a list. */
-    public static final int KEYCODE_MOVE_HOME       = 122;
-    /** Key code constant: End Movement key.
-     * Used for scrolling or moving the cursor around to the end of a line
-     * or to the bottom of a list. */
-    public static final int KEYCODE_MOVE_END        = 123;
-    /** Key code constant: Insert key.
-     * Toggles insert / overwrite edit mode. */
-    public static final int KEYCODE_INSERT          = 124;
-    /** Key code constant: Forward key.
-     * Navigates forward in the history stack.  Complement of {@link #KEYCODE_BACK}. */
-    public static final int KEYCODE_FORWARD         = 125;
-    /** Key code constant: Play media key. */
-    public static final int KEYCODE_MEDIA_PLAY      = 126;
-    /** Key code constant: Pause media key. */
-    public static final int KEYCODE_MEDIA_PAUSE     = 127;
-    /** Key code constant: Close media key.
-     * May be used to close a CD tray, for example. */
-    public static final int KEYCODE_MEDIA_CLOSE     = 128;
-    /** Key code constant: Eject media key.
-     * May be used to eject a CD tray, for example. */
-    public static final int KEYCODE_MEDIA_EJECT     = 129;
-    /** Key code constant: Record media key. */
-    public static final int KEYCODE_MEDIA_RECORD    = 130;
-    /** Key code constant: F1 key. */
-    public static final int KEYCODE_F1              = 131;
-    /** Key code constant: F2 key. */
-    public static final int KEYCODE_F2              = 132;
-    /** Key code constant: F3 key. */
-    public static final int KEYCODE_F3              = 133;
-    /** Key code constant: F4 key. */
-    public static final int KEYCODE_F4              = 134;
-    /** Key code constant: F5 key. */
-    public static final int KEYCODE_F5              = 135;
-    /** Key code constant: F6 key. */
-    public static final int KEYCODE_F6              = 136;
-    /** Key code constant: F7 key. */
-    public static final int KEYCODE_F7              = 137;
-    /** Key code constant: F8 key. */
-    public static final int KEYCODE_F8              = 138;
-    /** Key code constant: F9 key. */
-    public static final int KEYCODE_F9              = 139;
-    /** Key code constant: F10 key. */
-    public static final int KEYCODE_F10             = 140;
-    /** Key code constant: F11 key. */
-    public static final int KEYCODE_F11             = 141;
-    /** Key code constant: F12 key. */
-    public static final int KEYCODE_F12             = 142;
-    /** Key code constant: Num Lock modifier key.
-     * This is the Num Lock key; it is different from {@link #KEYCODE_NUM}.
-     * This key generally modifies the behavior of other keys on the numeric keypad. */
-    public static final int KEYCODE_NUM_LOCK        = 143;
-    /** Key code constant: Numeric keypad '0' key. */
-    public static final int KEYCODE_NUMPAD_0        = 144;
-    /** Key code constant: Numeric keypad '1' key. */
-    public static final int KEYCODE_NUMPAD_1        = 145;
-    /** Key code constant: Numeric keypad '2' key. */
-    public static final int KEYCODE_NUMPAD_2        = 146;
-    /** Key code constant: Numeric keypad '3' key. */
-    public static final int KEYCODE_NUMPAD_3        = 147;
-    /** Key code constant: Numeric keypad '4' key. */
-    public static final int KEYCODE_NUMPAD_4        = 148;
-    /** Key code constant: Numeric keypad '5' key. */
-    public static final int KEYCODE_NUMPAD_5        = 149;
-    /** Key code constant: Numeric keypad '6' key. */
-    public static final int KEYCODE_NUMPAD_6        = 150;
-    /** Key code constant: Numeric keypad '7' key. */
-    public static final int KEYCODE_NUMPAD_7        = 151;
-    /** Key code constant: Numeric keypad '8' key. */
-    public static final int KEYCODE_NUMPAD_8        = 152;
-    /** Key code constant: Numeric keypad '9' key. */
-    public static final int KEYCODE_NUMPAD_9        = 153;
-    /** Key code constant: Numeric keypad '/' key (for division). */
-    public static final int KEYCODE_NUMPAD_DIVIDE   = 154;
-    /** Key code constant: Numeric keypad '*' key (for multiplication). */
-    public static final int KEYCODE_NUMPAD_MULTIPLY = 155;
-    /** Key code constant: Numeric keypad '-' key (for subtraction). */
-    public static final int KEYCODE_NUMPAD_SUBTRACT = 156;
-    /** Key code constant: Numeric keypad '+' key (for addition). */
-    public static final int KEYCODE_NUMPAD_ADD      = 157;
-    /** Key code constant: Numeric keypad '.' key (for decimals or digit grouping). */
-    public static final int KEYCODE_NUMPAD_DOT      = 158;
-    /** Key code constant: Numeric keypad ',' key (for decimals or digit grouping). */
-    public static final int KEYCODE_NUMPAD_COMMA    = 159;
-    /** Key code constant: Numeric keypad Enter key. */
-    public static final int KEYCODE_NUMPAD_ENTER    = 160;
-    /** Key code constant: Numeric keypad '=' key. */
-    public static final int KEYCODE_NUMPAD_EQUALS   = 161;
-    /** Key code constant: Numeric keypad '(' key. */
-    public static final int KEYCODE_NUMPAD_LEFT_PAREN = 162;
-    /** Key code constant: Numeric keypad ')' key. */
-    public static final int KEYCODE_NUMPAD_RIGHT_PAREN = 163;
-    /** Key code constant: Volume Mute key.
-     * Mutes the speaker, unlike {@link #KEYCODE_MUTE}.
-     * This key should normally be implemented as a toggle such that the first press
-     * mutes the speaker and the second press restores the original volume. */
-    public static final int KEYCODE_VOLUME_MUTE     = 164;
-    /** Key code constant: Info key.
-     * Common on TV remotes to show additional information related to what is
-     * currently being viewed. */
-    public static final int KEYCODE_INFO            = 165;
-    /** Key code constant: Channel up key.
-     * On TV remotes, increments the television channel. */
-    public static final int KEYCODE_CHANNEL_UP      = 166;
-    /** Key code constant: Channel down key.
-     * On TV remotes, decrements the television channel. */
-    public static final int KEYCODE_CHANNEL_DOWN    = 167;
-    /** Key code constant: Zoom in key. */
-    public static final int KEYCODE_ZOOM_IN         = 168;
-    /** Key code constant: Zoom out key. */
-    public static final int KEYCODE_ZOOM_OUT        = 169;
-    /** Key code constant: TV key.
-     * On TV remotes, switches to viewing live TV. */
-    public static final int KEYCODE_TV              = 170;
-    /** Key code constant: Window key.
-     * On TV remotes, toggles picture-in-picture mode or other windowing functions. */
-    public static final int KEYCODE_WINDOW          = 171;
-    /** Key code constant: Guide key.
-     * On TV remotes, shows a programming guide. */
-    public static final int KEYCODE_GUIDE           = 172;
-    /** Key code constant: DVR key.
-     * On some TV remotes, switches to a DVR mode for recorded shows. */
-    public static final int KEYCODE_DVR             = 173;
-    /** Key code constant: Bookmark key.
-     * On some TV remotes, bookmarks content or web pages. */
-    public static final int KEYCODE_BOOKMARK        = 174;
-    /** Key code constant: Toggle captions key.
-     * Switches the mode for closed-captioning text, for example during television shows. */
-    public static final int KEYCODE_CAPTIONS        = 175;
-    /** Key code constant: Settings key.
-     * Starts the system settings activity. */
-    public static final int KEYCODE_SETTINGS        = 176;
-    /** Key code constant: TV power key.
-     * On TV remotes, toggles the power on a television screen. */
-    public static final int KEYCODE_TV_POWER        = 177;
-    /** Key code constant: TV input key.
-     * On TV remotes, switches the input on a television screen. */
-    public static final int KEYCODE_TV_INPUT        = 178;
-    /** Key code constant: Set-top-box power key.
-     * On TV remotes, toggles the power on an external Set-top-box. */
-    public static final int KEYCODE_STB_POWER       = 179;
-    /** Key code constant: Set-top-box input key.
-     * On TV remotes, switches the input mode on an external Set-top-box. */
-    public static final int KEYCODE_STB_INPUT       = 180;
-    /** Key code constant: A/V Receiver power key.
-     * On TV remotes, toggles the power on an external A/V Receiver. */
-    public static final int KEYCODE_AVR_POWER       = 181;
-    /** Key code constant: A/V Receiver input key.
-     * On TV remotes, switches the input mode on an external A/V Receiver. */
-    public static final int KEYCODE_AVR_INPUT       = 182;
-    /** Key code constant: Red "programmable" key.
-     * On TV remotes, acts as a contextual/programmable key. */
-    public static final int KEYCODE_PROG_RED        = 183;
-    /** Key code constant: Green "programmable" key.
-     * On TV remotes, actsas a contextual/programmable key. */
-    public static final int KEYCODE_PROG_GREEN      = 184;
-    /** Key code constant: Yellow "programmable" key.
-     * On TV remotes, acts as a contextual/programmable key. */
-    public static final int KEYCODE_PROG_YELLOW     = 185;
-    /** Key code constant: Blue "programmable" key.
-     * On TV remotes, acts as a contextual/programmable key. */
-    public static final int KEYCODE_PROG_BLUE       = 186;
-
-    private static final int LAST_KEYCODE           = KEYCODE_PROG_BLUE;
-
-    private String[] mKeyCodes = new String[256];
-    private String[] mAppKeyCodes = new String[256];
-
-    private void initKeyCodes() {
-        mKeyCodes[KEYCODE_DPAD_CENTER] = "\015";
-        mKeyCodes[KEYCODE_DPAD_UP] = "\033[A";
-        mKeyCodes[KEYCODE_DPAD_DOWN] = "\033[B";
-        mKeyCodes[KEYCODE_DPAD_RIGHT] = "\033[C";
-        mKeyCodes[KEYCODE_DPAD_LEFT] = "\033[D";
-        mKeyCodes[KEYCODE_F1] = "\033[OP";
-        mKeyCodes[KEYCODE_F2] = "\033[OQ";
-        mKeyCodes[KEYCODE_F3] = "\033[OR";
-        mKeyCodes[KEYCODE_F4] = "\033[OS";
-        mKeyCodes[KEYCODE_F5] = "\033[15~";
-        mKeyCodes[KEYCODE_F6] = "\033[17~";
-        mKeyCodes[KEYCODE_F7] = "\033[18~";
-        mKeyCodes[KEYCODE_F8] = "\033[19~";
-        mKeyCodes[KEYCODE_F9] = "\033[20~";
-        mKeyCodes[KEYCODE_F10] = "\033[21~";
-        mKeyCodes[KEYCODE_F11] = "\033[23~";
-        mKeyCodes[KEYCODE_F12] = "\033[24~";
-        mKeyCodes[KEYCODE_SYSRQ] = "\033[32~"; // Sys Request / Print
-        // Is this Scroll lock? mKeyCodes[Cancel] = "\033[33~";
-        mKeyCodes[KEYCODE_BREAK] = "\033[34~"; // Pause/Break
-
-        mKeyCodes[KEYCODE_TAB] = "\011";
-        mKeyCodes[KEYCODE_ENTER] = "\015";
-        mKeyCodes[KEYCODE_ESCAPE] = "\033";
-
-        mKeyCodes[KEYCODE_INSERT] = "\033[2~";
-        mKeyCodes[KEYCODE_FORWARD_DEL] = "\033[3~";
-        mKeyCodes[KEYCODE_MOVE_HOME] = "\033[1~";
-        mKeyCodes[KEYCODE_MOVE_END] = "\033[4~";
-        mKeyCodes[KEYCODE_PAGE_UP] = "\033[5~";
-        mKeyCodes[KEYCODE_PAGE_DOWN] = "\033[6~";
-        mKeyCodes[KEYCODE_DEL]= "\177";
-        mKeyCodes[KEYCODE_NUM_LOCK] = "\033OP";
-        mKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "/";
-        mKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "*";
-        mKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "-";
-        mKeyCodes[KEYCODE_NUMPAD_ADD] = "+";
-        mKeyCodes[KEYCODE_NUMPAD_ENTER] = "\015";
-        mKeyCodes[KEYCODE_NUMPAD_EQUALS] = "=";
-        mKeyCodes[KEYCODE_NUMPAD_DOT] = ".";
-        mKeyCodes[KEYCODE_NUMPAD_COMMA] = ",";
-        mKeyCodes[KEYCODE_NUMPAD_0] = "0";
-        mKeyCodes[KEYCODE_NUMPAD_1] = "1";
-        mKeyCodes[KEYCODE_NUMPAD_2] = "2";
-        mKeyCodes[KEYCODE_NUMPAD_3] = "3";
-        mKeyCodes[KEYCODE_NUMPAD_4] = "4";
-        mKeyCodes[KEYCODE_NUMPAD_5] = "5";
-        mKeyCodes[KEYCODE_NUMPAD_6] = "6";
-        mKeyCodes[KEYCODE_NUMPAD_7] = "7";
-        mKeyCodes[KEYCODE_NUMPAD_8] = "8";
-        mKeyCodes[KEYCODE_NUMPAD_9] = "9";
-
-        mAppKeyCodes[KEYCODE_DPAD_UP] = "\033OA";
-        mAppKeyCodes[KEYCODE_DPAD_DOWN] = "\033OB";
-        mAppKeyCodes[KEYCODE_DPAD_RIGHT] = "\033OC";
-        mAppKeyCodes[KEYCODE_DPAD_LEFT] = "\033OD";
-        mAppKeyCodes[KEYCODE_NUMPAD_DIVIDE] = "\033Oo";
-        mAppKeyCodes[KEYCODE_NUMPAD_MULTIPLY] = "\033Oj";
-        mAppKeyCodes[KEYCODE_NUMPAD_SUBTRACT] = "\033Om";
-        mAppKeyCodes[KEYCODE_NUMPAD_ADD] = "\033Ok";
-        mAppKeyCodes[KEYCODE_NUMPAD_ENTER] = "\033OM";
-        mAppKeyCodes[KEYCODE_NUMPAD_EQUALS] = "\033OX";
-        mAppKeyCodes[KEYCODE_NUMPAD_DOT] = "\033On";
-        mAppKeyCodes[KEYCODE_NUMPAD_COMMA] = "\033Ol";
-        mAppKeyCodes[KEYCODE_NUMPAD_0] = "\033Op";
-        mAppKeyCodes[KEYCODE_NUMPAD_1] = "\033Oq";
-        mAppKeyCodes[KEYCODE_NUMPAD_2] = "\033Or";
-        mAppKeyCodes[KEYCODE_NUMPAD_3] = "\033Os";
-        mAppKeyCodes[KEYCODE_NUMPAD_4] = "\033Ot";
-        mAppKeyCodes[KEYCODE_NUMPAD_5] = "\033Ou";
-        mAppKeyCodes[KEYCODE_NUMPAD_6] = "\033Ov";
-        mAppKeyCodes[KEYCODE_NUMPAD_7] = "\033Ow";
-        mAppKeyCodes[KEYCODE_NUMPAD_8] = "\033Ox";
-        mAppKeyCodes[KEYCODE_NUMPAD_9] = "\033Oy";
-    }
-
-    /**
-     * The state engine for a modifier key. Can be pressed, released, locked,
-     * and so on.
-     *
-     */
-    private class ModifierKey {
-
-        private int mState;
-
-        private static final int UNPRESSED = 0;
-
-        private static final int PRESSED = 1;
-
-        private static final int RELEASED = 2;
-
-        private static final int USED = 3;
-
-        private static final int LOCKED = 4;
-
-        /**
-         * Construct a modifier key. UNPRESSED by default.
-         *
-         */
-        public ModifierKey() {
-            mState = UNPRESSED;
-        }
-
-        public void onPress() {
-            switch (mState) {
-            case PRESSED:
-                // This is a repeat before use
-                break;
-            case RELEASED:
-                mState = LOCKED;
-                break;
-            case USED:
-                // This is a repeat after use
-                break;
-            case LOCKED:
-                mState = UNPRESSED;
-                break;
-            default:
-                mState = PRESSED;
-                break;
-            }
-        }
-
-        public void onRelease() {
-            switch (mState) {
-            case USED:
-                mState = UNPRESSED;
-                break;
-            case PRESSED:
-                mState = RELEASED;
-                break;
-            default:
-                // Leave state alone
-                break;
-            }
-        }
-
-        public void adjustAfterKeypress() {
-            switch (mState) {
-            case PRESSED:
-                mState = USED;
-                break;
-            case RELEASED:
-                mState = UNPRESSED;
-                break;
-            default:
-                // Leave state alone
-                break;
-            }
-        }
-
-        public boolean isActive() {
-            return mState != UNPRESSED;
-        }
-    }
-
-    private ModifierKey mAltKey = new ModifierKey();
-
-    private ModifierKey mCapKey = new ModifierKey();
-
-    private ModifierKey mControlKey = new ModifierKey();
-
-    private ModifierKey mFnKey = new ModifierKey();
-
-    private boolean mCapsLock;
-
-    static public final int KEYCODE_OFFSET = 1000;
-
-    /**
-     * Construct a term key listener.
-     *
-     */
-    public TermKeyListener() {
-        initKeyCodes();
-    }
-
-    public void handleControlKey(boolean down) {
-        if (down) {
-            mControlKey.onPress();
-        } else {
-            mControlKey.onRelease();
-        }
-    }
-
-    public void handleFnKey(boolean down) {
-        if (down) {
-            mFnKey.onPress();
-        } else {
-            mFnKey.onRelease();
-        }
-    }
-
-    public int mapControlChar(int ch) {
-        int result = ch;
-        if (mControlKey.isActive()) {
-            // Search is the control key.
-            if (result >= 'a' && result <= 'z') {
-                result = (char) (result - 'a' + '\001');
-            } else if (result >= 'A' && result <= 'Z') {
-                result = (char) (result - 'A' + '\001');
-            } else if (result == ' ' || result == '2') {
-                result = 0;
-            } else if (result == '[' || result == '3') {
-                result = 27; // ^[ (Esc)
-            } else if (result == '\\' || result == '4') {
-                result = 28;
-            } else if (result == ']' || result == '5') {
-                result = 29;
-            } else if (result == '^' || result == '6') {
-                result = 30; // control-^
-            } else if (result == '_' || result == '7') {
-                result = 31;
-            } else if (result == '8') {
-                result = 127; // DEL
-            } else if (result == '9') {
-                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F11;
-            } else if (result == '0') {
-                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F12;
-            }
-        } else if (mFnKey.isActive()) {
-            if (result == 'w' || result == 'W') {
-                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_UP;
-            } else if (result == 'a' || result == 'A') {
-                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_LEFT;
-            } else if (result == 's' || result == 'S') {
-                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_DOWN;
-            } else if (result == 'd' || result == 'D') {
-                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_DPAD_RIGHT;
-            } else if (result == 'p' || result == 'P') {
-                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_UP;
-            } else if (result == 'n' || result == 'N') {
-                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_PAGE_DOWN;
-            } else if (result == 't' || result == 'T') {
-                result = KEYCODE_OFFSET + KeyEvent.KEYCODE_TAB;
-            } else if (result == 'l' || result == 'L') {
-                result = '|';
-            } else if (result == 'u' || result == 'U') {
-                result = '_';
-            } else if (result == 'e' || result == 'E') {
-                result = 27; // ^[ (Esc)
-            } else if (result == '.') {
-                result = 28; // ^\
-            } else if (result > '0' && result <= '9') {
-                // F1-F9
-                result = (char)(result + KEYCODE_OFFSET + TermKeyListener.KEYCODE_F1 - 1);
-            } else if (result == '0') {
-                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_F10;
-            } else if (result == 'i' || result == 'I') {
-                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_INSERT;
-            } else if (result == 'x' || result == 'X') {
-                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_FORWARD_DEL;
-            } else if (result == 'h' || result == 'H') {
-                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_HOME;
-            } else if (result == 'f' || result == 'F') {
-                result = KEYCODE_OFFSET + TermKeyListener.KEYCODE_MOVE_END;
-            }
-        }
-
-        if (result > -1) {
-            mAltKey.adjustAfterKeypress();
-            mCapKey.adjustAfterKeypress();
-            mControlKey.adjustAfterKeypress();
-            mFnKey.adjustAfterKeypress();
-        }
-
-        return result;
-    }
-
-    /**
-     * Handle a keyDown event.
-     *
-     * @param keyCode the keycode of the keyDown event
-     *
-     */
-    public void keyDown(int keyCode, KeyEvent event, OutputStream out, boolean appMode) throws IOException {
-        if (handleKeyCode(keyCode, out, appMode)) {
-            return;
-        }
-        int result = -1;
-        switch (keyCode) {
-        case KeyEvent.KEYCODE_ALT_RIGHT:
-        case KeyEvent.KEYCODE_ALT_LEFT:
-            mAltKey.onPress();
-            break;
-
-        case KeyEvent.KEYCODE_SHIFT_LEFT:
-        case KeyEvent.KEYCODE_SHIFT_RIGHT:
-            mCapKey.onPress();
-            break;
-
-        case KEYCODE_CTRL_LEFT:
-        case KEYCODE_CTRL_RIGHT:
-            mControlKey.onPress();
-            break;
-
-        case KEYCODE_CAPS_LOCK:
-            if (event.getRepeatCount() == 0) {
-                mCapsLock = !mCapsLock;
-            }
-            break;
-
-        default: {
-            result = event.getUnicodeChar(
-                   (mCapKey.isActive() || mCapsLock ? KeyEvent.META_SHIFT_ON : 0) |
-                   (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0));
-            break;
-            }
-        }
-
-        result = mapControlChar(result);
-
-        if (result >= KEYCODE_OFFSET) {
-            handleKeyCode(result - KEYCODE_OFFSET, out, appMode);
-        } else if (result >= 0) {
-            out.write(result);
-        }
-    }
-
-    public boolean handleKeyCode(int keyCode, OutputStream out, boolean appMode) throws IOException {
-        if (keyCode >= 0 && keyCode < mKeyCodes.length) {
-            String code = null;
-            if (appMode) {
-                code = mAppKeyCodes[keyCode];
-            }
-            if (code == null) {
-                code = mKeyCodes[keyCode];
-            }
-            if (code != null) {
-                int length = code.length();
-                for (int i = 0; i < length; i++) {
-                    out.write(code.charAt(i));
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Handle a keyUp event.
-     *
-     * @param keyCode the keyCode of the keyUp event
-     */
-    public void keyUp(int keyCode) {
-        switch (keyCode) {
-        case KeyEvent.KEYCODE_ALT_LEFT:
-        case KeyEvent.KEYCODE_ALT_RIGHT:
-            mAltKey.onRelease();
-            break;
-        case KeyEvent.KEYCODE_SHIFT_LEFT:
-        case KeyEvent.KEYCODE_SHIFT_RIGHT:
-            mCapKey.onRelease();
-            break;
-
-        case KEYCODE_CTRL_LEFT:
-        case KEYCODE_CTRL_RIGHT:
-            mControlKey.onRelease();
-            break;
-
-        default:
-            // Ignore other keyUps
-            break;
-        }
-    }
-}
index 00b53e8..060ffad 100644 (file)
@@ -24,6 +24,8 @@ import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 
+import jackpal.androidterm.util.ServiceForegroundCompat;
+
 public class TermService extends Service
 {
     /* Parallels the value of START_STICKY on API Level >= 5 */
diff --git a/src/jackpal/androidterm/model/Screen.java b/src/jackpal/androidterm/model/Screen.java
new file mode 100644 (file)
index 0000000..6ceb452
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.model;
+
+/**
+ * An abstract screen interface. A terminal screen stores lines of text. (The
+ * reason to abstract it is to allow different implementations, and to hide
+ * implementation details from clients.)
+ */
+public interface Screen {
+
+    /**
+     * Set line wrap flag for a given row. Affects how lines are logically
+     * wrapped when changing screen size or converting to a transcript.
+     */
+    void setLineWrap(int row);
+
+    /**
+     * Store byte b into the screen at location (x, y)
+     *
+     * @param x X coordinate (also known as column)
+     * @param y Y coordinate (also known as row)
+     * @param b ASCII character to store
+     * @param foreColor the foreground color
+     * @param backColor the background color
+     */
+    void set(int x, int y, byte b, int foreColor, int backColor);
+
+    /**
+     * Scroll the screen down one line. To scroll the whole screen of a 24 line
+     * screen, the arguments would be (0, 24).
+     *
+     * @param topMargin First line that is scrolled.
+     * @param bottomMargin One line after the last line that is scrolled.
+     */
+    void scroll(int topMargin, int bottomMargin, int foreColor, int backColor);
+
+    /**
+     * Block copy characters from one position in the screen to another. The two
+     * positions can overlap. All characters of the source and destination must
+     * be within the bounds of the screen, or else an InvalidParemeterException
+     * will be thrown.
+     *
+     * @param sx source X coordinate
+     * @param sy source Y coordinate
+     * @param w width
+     * @param h height
+     * @param dx destination X coordinate
+     * @param dy destination Y coordinate
+     */
+    void blockCopy(int sx, int sy, int w, int h, int dx, int dy);
+
+    /**
+     * Block set characters. All characters must be within the bounds of the
+     * screen, or else and InvalidParemeterException will be thrown. Typically
+     * this is called with a "val" argument of 32 to clear a block of
+     * characters.
+     *
+     * @param sx source X
+     * @param sy source Y
+     * @param w width
+     * @param h height
+     * @param val value to set.
+     * @param foreColor the foreground color
+     * @param backColor the background color
+     */
+    void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int
+            backColor);
+
+    /**
+     * Get the contents of the transcript buffer as a text string.
+     *
+     * @return the contents of the transcript buffer.
+     */
+    String getTranscriptText();
+
+    /**
+     * Get the selected text inside transcript buffer as a text string.
+     * @param x1 Selection start
+     * @param y1 Selection start
+     * @param x2 Selection end
+     * @param y2 Selection end
+     * @return the contents of the transcript buffer.
+     */
+    String getSelectedText(int x1, int y1, int x2, int y2);
+
+    /**
+     * Resize the screen
+     * @param columns
+     * @param rows
+     */
+    void resize(int columns, int rows, int foreColor, int backColor);
+}
diff --git a/src/jackpal/androidterm/model/TextRenderer.java b/src/jackpal/androidterm/model/TextRenderer.java
new file mode 100644 (file)
index 0000000..f4e0a7b
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.model;
+
+import android.graphics.Canvas;
+
+/**
+ * Text renderer interface
+ */
+
+public interface TextRenderer {
+    int getCharacterWidth();
+    int getCharacterHeight();
+    void drawTextRun(Canvas canvas, float x, float y,
+            int lineOffset, char[] text,
+            int index, int count, boolean cursor, int foreColor, int backColor);
+}
diff --git a/src/jackpal/androidterm/session/TerminalEmulator.java b/src/jackpal/androidterm/session/TerminalEmulator.java
new file mode 100644 (file)
index 0000000..7b5e559
--- /dev/null
@@ -0,0 +1,1236 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.session;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import android.util.Log;
+
+import jackpal.androidterm.Term;
+import jackpal.androidterm.model.Screen;
+
+/**
+ * Renders text into a screen. Contains all the terminal-specific knowlege and
+ * state. Emulates a subset of the X Window System xterm terminal, which in turn
+ * is an emulator for a subset of the Digital Equipment Corporation vt100
+ * terminal. Missing functionality: text attributes (bold, underline, reverse
+ * video, color) alternate screen cursor key and keypad escape sequences.
+ */
+public class TerminalEmulator {
+
+    /**
+     * The cursor row. Numbered 0..mRows-1.
+     */
+    private int mCursorRow;
+
+    /**
+     * The cursor column. Numbered 0..mColumns-1.
+     */
+    private int mCursorCol;
+
+    /**
+     * The number of character rows in the terminal screen.
+     */
+    private int mRows;
+
+    /**
+     * The number of character columns in the terminal screen.
+     */
+    private int mColumns;
+
+    /**
+     * Used to send data to the remote process. Needed to implement the various
+     * "report" escape sequences.
+     */
+    private FileOutputStream mTermOut;
+
+    /**
+     * Stores the characters that appear on the screen of the emulated terminal.
+     */
+    private Screen mScreen;
+
+    /**
+     * Keeps track of the current argument of the current escape sequence.
+     * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.)
+     */
+    private int mArgIndex;
+
+    /**
+     * The number of parameter arguments. This name comes from the ANSI standard
+     * for terminal escape codes.
+     */
+    private static final int MAX_ESCAPE_PARAMETERS = 16;
+
+    /**
+     * Holds the arguments of the current escape sequence.
+     */
+    private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
+
+    // Escape processing states:
+
+    /**
+     * Escape processing state: Not currently in an escape sequence.
+     */
+    private static final int ESC_NONE = 0;
+
+    /**
+     * Escape processing state: Have seen an ESC character
+     */
+    private static final int ESC = 1;
+
+    /**
+     * Escape processing state: Have seen ESC POUND
+     */
+    private static final int ESC_POUND = 2;
+
+    /**
+     * Escape processing state: Have seen ESC and a character-set-select char
+     */
+    private static final int ESC_SELECT_LEFT_PAREN = 3;
+
+    /**
+     * Escape processing state: Have seen ESC and a character-set-select char
+     */
+    private static final int ESC_SELECT_RIGHT_PAREN = 4;
+
+    /**
+     * Escape processing state: ESC [
+     */
+    private static final int ESC_LEFT_SQUARE_BRACKET = 5;
+
+    /**
+     * Escape processing state: ESC [ ?
+     */
+    private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6;
+
+    /**
+     * True if the current escape sequence should continue, false if the current
+     * escape sequence should be terminated. Used when parsing a single
+     * character.
+     */
+    private boolean mContinueSequence;
+
+    /**
+     * The current state of the escape sequence state machine.
+     */
+    private int mEscapeState;
+
+    /**
+     * Saved state of the cursor row, Used to implement the save/restore cursor
+     * position escape sequences.
+     */
+    private int mSavedCursorRow;
+
+    /**
+     * Saved state of the cursor column, Used to implement the save/restore
+     * cursor position escape sequences.
+     */
+    private int mSavedCursorCol;
+
+    // DecSet booleans
+
+    /**
+     * This mask indicates 132-column mode is set. (As opposed to 80-column
+     * mode.)
+     */
+    private static final int K_132_COLUMN_MODE_MASK = 1 << 3;
+
+    /**
+     * This mask indicates that origin mode is set. (Cursor addressing is
+     * relative to the absolute screen size, rather than the currently set top
+     * and bottom margins.)
+     */
+    private static final int K_ORIGIN_MODE_MASK = 1 << 6;
+
+    /**
+     * This mask indicates that wraparound mode is set. (As opposed to
+     * stop-at-right-column mode.)
+     */
+    private static final int K_WRAPAROUND_MODE_MASK = 1 << 7;
+
+    /**
+     * Holds multiple DECSET flags. The data is stored this way, rather than in
+     * separate booleans, to make it easier to implement the save-and-restore
+     * semantics. The various k*ModeMask masks can be used to extract and modify
+     * the individual flags current states.
+     */
+    private int mDecFlags;
+
+    /**
+     * Saves away a snapshot of the DECSET flags. Used to implement save and
+     * restore escape sequences.
+     */
+    private int mSavedDecFlags;
+
+    // Modes set with Set Mode / Reset Mode
+
+    /**
+     * True if insert mode (as opposed to replace mode) is active. In insert
+     * mode new characters are inserted, pushing existing text to the right.
+     */
+    private boolean mInsertMode;
+
+    /**
+     * Automatic newline mode. Configures whether pressing return on the
+     * keyboard automatically generates a return as well. Not currently
+     * implemented.
+     */
+    private boolean mAutomaticNewlineMode;
+
+    /**
+     * An array of tab stops. mTabStop[i] is true if there is a tab stop set for
+     * column i.
+     */
+    private boolean[] mTabStop;
+
+    // The margins allow portions of the screen to be locked.
+
+    /**
+     * The top margin of the screen, for scrolling purposes. Ranges from 0 to
+     * mRows-2.
+     */
+    private int mTopMargin;
+
+    /**
+     * The bottom margin of the screen, for scrolling purposes. Ranges from
+     * mTopMargin + 2 to mRows. (Defines the first row after the scrolling
+     * region.
+     */
+    private int mBottomMargin;
+
+    /**
+     * True if the next character to be emitted will be automatically wrapped to
+     * the next line. Used to disambiguate the case where the cursor is
+     * positioned on column mColumns-1.
+     */
+    private boolean mAboutToAutoWrap;
+
+    /**
+     * Used for debugging, counts how many chars have been processed.
+     */
+    private int mProcessedCharCount;
+
+    /**
+     * Foreground color, 0..7, mask with 8 for bold
+     */
+    private int mForeColor;
+
+    /**
+     * Background color, 0..7, mask with 8 for underline
+     */
+    private int mBackColor;
+
+    private boolean mInverseColors;
+
+    private boolean mbKeypadApplicationMode;
+
+    private boolean mAlternateCharSet;
+
+    /**
+     * Used for moving selection up along with the scrolling text
+     */
+    private int mScrollCounter = 0;
+
+    /**
+     * Construct a terminal emulator that uses the supplied screen
+     *
+     * @param screen the screen to render characters into.
+     * @param columns the number of columns to emulate
+     * @param rows the number of rows to emulate
+     * @param termOut the output file descriptor that talks to the pseudo-tty.
+     */
+    public TerminalEmulator(Screen screen, int columns, int rows,
+            FileOutputStream termOut) {
+        mScreen = screen;
+        mRows = rows;
+        mColumns = columns;
+        mTabStop = new boolean[mColumns];
+        mTermOut = termOut;
+        reset();
+    }
+
+    public void updateSize(int columns, int rows) {
+        if (mRows == rows && mColumns == columns) {
+            return;
+        }
+        if (columns <= 0) {
+            throw new IllegalArgumentException("rows:" + columns);
+        }
+
+        if (rows <= 0) {
+            throw new IllegalArgumentException("rows:" + rows);
+        }
+
+        String transcriptText = mScreen.getTranscriptText();
+
+        mScreen.resize(columns, rows, mForeColor, mBackColor);
+
+        if (mRows != rows) {
+            mRows = rows;
+            mTopMargin = 0;
+            mBottomMargin = mRows;
+        }
+        if (mColumns != columns) {
+            int oldColumns = mColumns;
+            mColumns = columns;
+            boolean[] oldTabStop = mTabStop;
+            mTabStop = new boolean[mColumns];
+            int toTransfer = Math.min(oldColumns, columns);
+            System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
+            while (mCursorCol >= columns) {
+                mCursorCol -= columns;
+                mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1);
+            }
+        }
+        mCursorRow = 0;
+        mCursorCol = 0;
+        mAboutToAutoWrap = false;
+
+        int end = transcriptText.length()-1;
+        while ((end >= 0) && transcriptText.charAt(end) == '\n') {
+            end--;
+        }
+        for(int i = 0; i <= end; i++) {
+            byte c = (byte) transcriptText.charAt(i);
+            if (c == '\n') {
+                setCursorCol(0);
+                doLinefeed();
+            } else {
+                emit(c);
+            }
+        }
+    }
+
+    /**
+     * Get the cursor's current row.
+     *
+     * @return the cursor's current row.
+     */
+    public final int getCursorRow() {
+        return mCursorRow;
+    }
+
+    /**
+     * Get the cursor's current column.
+     *
+     * @return the cursor's current column.
+     */
+    public final int getCursorCol() {
+        return mCursorCol;
+    }
+
+    public final boolean getKeypadApplicationMode() {
+        return mbKeypadApplicationMode;
+    }
+
+    private void setDefaultTabStops() {
+        for (int i = 0; i < mColumns; i++) {
+            mTabStop[i] = (i & 7) == 0 && i != 0;
+        }
+    }
+
+    /**
+     * Accept bytes (typically from the pseudo-teletype) and process them.
+     *
+     * @param buffer a byte array containing the bytes to be processed
+     * @param base the first index of the array to process
+     * @param length the number of bytes in the array to process
+     */
+    public void append(byte[] buffer, int base, int length) {
+        for (int i = 0; i < length; i++) {
+            byte b = buffer[base + i];
+            try {
+                if (Term.LOG_CHARACTERS_FLAG) {
+                    char printableB = (char) b;
+                    if (b < 32 || b > 126) {
+                        printableB = ' ';
+                    }
+                    Log.w(Term.LOG_TAG, "'" + Character.toString(printableB)
+                            + "' (" + Integer.toString(b) + ")");
+                }
+                process(b);
+                mProcessedCharCount++;
+            } catch (Exception e) {
+                Log.e(Term.LOG_TAG, "Exception while processing character "
+                        + Integer.toString(mProcessedCharCount) + " code "
+                        + Integer.toString(b), e);
+            }
+        }
+    }
+
+    private void process(byte b) {
+        switch (b) {
+        case 0: // NUL
+            // Do nothing
+            break;
+
+        case 7: // BEL
+            // Do nothing
+            break;
+
+        case 8: // BS
+            setCursorCol(Math.max(0, mCursorCol - 1));
+            break;
+
+        case 9: // HT
+            // Move to next tab stop, but not past edge of screen
+            setCursorCol(nextTabStop(mCursorCol));
+            break;
+
+        case 13:
+            setCursorCol(0);
+            break;
+
+        case 10: // CR
+        case 11: // VT
+        case 12: // LF
+            doLinefeed();
+            break;
+
+        case 14: // SO:
+            setAltCharSet(true);
+            break;
+
+        case 15: // SI:
+            setAltCharSet(false);
+            break;
+
+
+        case 24: // CAN
+        case 26: // SUB
+            if (mEscapeState != ESC_NONE) {
+                mEscapeState = ESC_NONE;
+                emit((byte) 127);
+            }
+            break;
+
+        case 27: // ESC
+            // Always starts an escape sequence
+            startEscapeSequence(ESC);
+            break;
+
+        case (byte) 0x9b: // CSI
+            startEscapeSequence(ESC_LEFT_SQUARE_BRACKET);
+            break;
+
+        default:
+            mContinueSequence = false;
+            switch (mEscapeState) {
+            case ESC_NONE:
+                if (b >= 32) {
+                    emit(b);
+                }
+                break;
+
+            case ESC:
+                doEsc(b);
+                break;
+
+            case ESC_POUND:
+                doEscPound(b);
+                break;
+
+            case ESC_SELECT_LEFT_PAREN:
+                doEscSelectLeftParen(b);
+                break;
+
+            case ESC_SELECT_RIGHT_PAREN:
+                doEscSelectRightParen(b);
+                break;
+
+            case ESC_LEFT_SQUARE_BRACKET:
+                doEscLeftSquareBracket(b);
+                break;
+
+            case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
+                doEscLSBQuest(b);
+                break;
+
+            default:
+                unknownSequence(b);
+                break;
+            }
+            if (!mContinueSequence) {
+                mEscapeState = ESC_NONE;
+            }
+            break;
+        }
+    }
+
+    private void setAltCharSet(boolean alternateCharSet) {
+        mAlternateCharSet = alternateCharSet;
+    }
+
+    private int nextTabStop(int cursorCol) {
+        for (int i = cursorCol; i < mColumns; i++) {
+            if (mTabStop[i]) {
+                return i;
+            }
+        }
+        return mColumns - 1;
+    }
+
+    private void doEscLSBQuest(byte b) {
+        int mask = getDecFlagsMask(getArg0(0));
+        switch (b) {
+        case 'h': // Esc [ ? Pn h - DECSET
+            mDecFlags |= mask;
+            break;
+
+        case 'l': // Esc [ ? Pn l - DECRST
+            mDecFlags &= ~mask;
+            break;
+
+        case 'r': // Esc [ ? Pn r - restore
+            mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask);
+            break;
+
+        case 's': // Esc [ ? Pn s - save
+            mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask);
+            break;
+
+        default:
+            parseArg(b);
+            break;
+        }
+
+        // 132 column mode
+        if ((mask & K_132_COLUMN_MODE_MASK) != 0) {
+            // We don't actually set 132 cols, but we do want the
+            // side effect of clearing the screen and homing the cursor.
+            blockClear(0, 0, mColumns, mRows);
+            setCursorRowCol(0, 0);
+        }
+
+        // origin mode
+        if ((mask & K_ORIGIN_MODE_MASK) != 0) {
+            // Home the cursor.
+            setCursorPosition(0, 0);
+        }
+    }
+
+    private int getDecFlagsMask(int argument) {
+        if (argument >= 1 && argument <= 9) {
+            return (1 << argument);
+        }
+
+        return 0;
+    }
+
+    private void startEscapeSequence(int escapeState) {
+        mEscapeState = escapeState;
+        mArgIndex = 0;
+        for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) {
+            mArgs[j] = -1;
+        }
+    }
+
+    private void doLinefeed() {
+        int newCursorRow = mCursorRow + 1;
+        if (newCursorRow >= mBottomMargin) {
+            scroll();
+            newCursorRow = mBottomMargin - 1;
+        }
+        setCursorRow(newCursorRow);
+    }
+
+    private void continueSequence() {
+        mContinueSequence = true;
+    }
+
+    private void continueSequence(int state) {
+        mEscapeState = state;
+        mContinueSequence = true;
+    }
+
+    private void doEscSelectLeftParen(byte b) {
+        doSelectCharSet(true, b);
+    }
+
+    private void doEscSelectRightParen(byte b) {
+        doSelectCharSet(false, b);
+    }
+
+    private void doSelectCharSet(boolean isG0CharSet, byte b) {
+        switch (b) {
+        case 'A': // United Kingdom character set
+            break;
+        case 'B': // ASCII set
+            break;
+        case '0': // Special Graphics
+            break;
+        case '1': // Alternate character set
+            break;
+        case '2':
+            break;
+        default:
+            unknownSequence(b);
+        }
+    }
+
+    private void doEscPound(byte b) {
+        switch (b) {
+        case '8': // Esc # 8 - DECALN alignment test
+            mScreen.blockSet(0, 0, mColumns, mRows, 'E',
+                    getForeColor(), getBackColor());
+            break;
+
+        default:
+            unknownSequence(b);
+            break;
+        }
+    }
+
+    private void doEsc(byte b) {
+        switch (b) {
+        case '#':
+            continueSequence(ESC_POUND);
+            break;
+
+        case '(':
+            continueSequence(ESC_SELECT_LEFT_PAREN);
+            break;
+
+        case ')':
+            continueSequence(ESC_SELECT_RIGHT_PAREN);
+            break;
+
+        case '7': // DECSC save cursor
+            mSavedCursorRow = mCursorRow;
+            mSavedCursorCol = mCursorCol;
+            break;
+
+        case '8': // DECRC restore cursor
+            setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
+            break;
+
+        case 'D': // INDEX
+            doLinefeed();
+            break;
+
+        case 'E': // NEL
+            setCursorCol(0);
+            doLinefeed();
+            break;
+
+        case 'F': // Cursor to lower-left corner of screen
+            setCursorRowCol(0, mBottomMargin - 1);
+            break;
+
+        case 'H': // Tab set
+            mTabStop[mCursorCol] = true;
+            break;
+
+        case 'M': // Reverse index
+            if (mCursorRow <= mTopMargin) {
+                mScreen.blockCopy(0, mTopMargin, mColumns, mBottomMargin
+                        - (mTopMargin + 1), 0, mTopMargin + 1);
+                blockClear(0, mTopMargin, mColumns);
+            } else {
+                mCursorRow--;
+            }
+
+            break;
+
+        case 'N': // SS2
+            unimplementedSequence(b);
+            break;
+
+        case '0': // SS3
+            unimplementedSequence(b);
+            break;
+
+        case 'P': // Device control string
+            unimplementedSequence(b);
+            break;
+
+        case 'Z': // return terminal ID
+            sendDeviceAttributes();
+            break;
+
+        case '[':
+            continueSequence(ESC_LEFT_SQUARE_BRACKET);
+            break;
+
+        case '=': // DECKPAM
+            mbKeypadApplicationMode = true;
+            break;
+
+        case '>' : // DECKPNM
+            mbKeypadApplicationMode = false;
+            break;
+
+        default:
+            unknownSequence(b);
+            break;
+        }
+    }
+
+    private void doEscLeftSquareBracket(byte b) {
+        switch (b) {
+        case '@': // ESC [ Pn @ - ICH Insert Characters
+        {
+            int charsAfterCursor = mColumns - mCursorCol;
+            int charsToInsert = Math.min(getArg0(1), charsAfterCursor);
+            int charsToMove = charsAfterCursor - charsToInsert;
+            mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1,
+                    mCursorCol + charsToInsert, mCursorRow);
+            blockClear(mCursorCol, mCursorRow, charsToInsert);
+        }
+            break;
+
+        case 'A': // ESC [ Pn A - Cursor Up
+            setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
+            break;
+
+        case 'B': // ESC [ Pn B - Cursor Down
+            setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
+            break;
+
+        case 'C': // ESC [ Pn C - Cursor Right
+            setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1)));
+            break;
+
+        case 'D': // ESC [ Pn D - Cursor Left
+            setCursorCol(Math.max(0, mCursorCol - getArg0(1)));
+            break;
+
+        case 'G': // ESC [ Pn G - Cursor Horizontal Absolute
+            setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
+            break;
+
+        case 'H': // ESC [ Pn ; H - Cursor Position
+            setHorizontalVerticalPosition();
+            break;
+
+        case 'J': // ESC [ Pn J - Erase in Display
+            switch (getArg0(0)) {
+            case 0: // Clear below
+                blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
+                blockClear(0, mCursorRow + 1, mColumns,
+                        mBottomMargin - (mCursorRow + 1));
+                break;
+
+            case 1: // Erase from the start of the screen to the cursor.
+                blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin);
+                blockClear(0, mCursorRow, mCursorCol + 1);
+                break;
+
+            case 2: // Clear all
+                blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin);
+                break;
+
+            default:
+                unknownSequence(b);
+                break;
+            }
+            break;
+
+        case 'K': // ESC [ Pn K - Erase in Line
+            switch (getArg0(0)) {
+            case 0: // Clear to right
+                blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
+                break;
+
+            case 1: // Erase start of line to cursor (including cursor)
+                blockClear(0, mCursorRow, mCursorCol + 1);
+                break;
+
+            case 2: // Clear whole line
+                blockClear(0, mCursorRow, mColumns);
+                break;
+
+            default:
+                unknownSequence(b);
+                break;
+            }
+            break;
+
+        case 'L': // Insert Lines
+        {
+            int linesAfterCursor = mBottomMargin - mCursorRow;
+            int linesToInsert = Math.min(getArg0(1), linesAfterCursor);
+            int linesToMove = linesAfterCursor - linesToInsert;
+            mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0,
+                    mCursorRow + linesToInsert);
+            blockClear(0, mCursorRow, mColumns, linesToInsert);
+        }
+            break;
+
+        case 'M': // Delete Lines
+        {
+            int linesAfterCursor = mBottomMargin - mCursorRow;
+            int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
+            int linesToMove = linesAfterCursor - linesToDelete;
+            mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns,
+                    linesToMove, 0, mCursorRow);
+            blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete);
+        }
+            break;
+
+        case 'P': // Delete Characters
+        {
+            int charsAfterCursor = mColumns - mCursorCol;
+            int charsToDelete = Math.min(getArg0(1), charsAfterCursor);
+            int charsToMove = charsAfterCursor - charsToDelete;
+            mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow,
+                    charsToMove, 1, mCursorCol, mCursorRow);
+            blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete);
+        }
+            break;
+
+        case 'T': // Mouse tracking
+            unimplementedSequence(b);
+            break;
+
+        case '?': // Esc [ ? -- start of a private mode set
+            continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK);
+            break;
+
+        case 'c': // Send device attributes
+            sendDeviceAttributes();
+            break;
+
+        case 'd': // ESC [ Pn d - Vert Position Absolute
+            setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
+            break;
+
+        case 'f': // Horizontal and Vertical Position
+            setHorizontalVerticalPosition();
+            break;
+
+        case 'g': // Clear tab stop
+            switch (getArg0(0)) {
+            case 0:
+                mTabStop[mCursorCol] = false;
+                break;
+
+            case 3:
+                for (int i = 0; i < mColumns; i++) {
+                    mTabStop[i] = false;
+                }
+                break;
+
+            default:
+                // Specified to have no effect.
+                break;
+            }
+            break;
+
+        case 'h': // Set Mode
+            doSetMode(true);
+            break;
+
+        case 'l': // Reset Mode
+            doSetMode(false);
+            break;
+
+        case 'm': // Esc [ Pn m - character attributes.
+            selectGraphicRendition();
+            break;
+
+        case 'r': // Esc [ Pn ; Pn r - set top and bottom margins
+        {
+            // The top margin defaults to 1, the bottom margin
+            // (unusually for arguments) defaults to mRows.
+            //
+            // The escape sequence numbers top 1..23, but we
+            // number top 0..22.
+            // The escape sequence numbers bottom 2..24, and
+            // so do we (because we use a zero based numbering
+            // scheme, but we store the first line below the
+            // bottom-most scrolling line.
+            // As a result, we adjust the top line by -1, but
+            // we leave the bottom line alone.
+            //
+            // Also require that top + 2 <= bottom
+
+            int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
+            int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows));
+            mTopMargin = top;
+            mBottomMargin = bottom;
+
+            // The cursor is placed in the home position
+            setCursorRowCol(mTopMargin, 0);
+        }
+            break;
+
+        default:
+            parseArg(b);
+            break;
+        }
+    }
+
+    private void selectGraphicRendition() {
+        for (int i = 0; i <= mArgIndex; i++) {
+            int code = mArgs[i];
+            if ( code < 0) {
+                if (mArgIndex > 0) {
+                    continue;
+                } else {
+                    code = 0;
+                }
+            }
+
+            // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
+
+            if (code == 0) { // reset
+                mInverseColors = false;
+                mForeColor = 7;
+                mBackColor = 0;
+            } else if (code == 1) { // bold
+                mForeColor |= 0x8;
+            } else if (code == 4) { // underscore
+                mBackColor |= 0x8;
+            } else if (code == 7) { // inverse
+                mInverseColors = true;
+            } else if (code == 22) { // Normal color or intensity, neither bright, bold nor faint
+                mForeColor &= 0x7;
+            } else if (code == 24) { // underline: none
+                mBackColor &= 0x7;
+            } else if (code == 27) { // image: positive
+                mInverseColors = false;
+            } else if (code >= 30 && code <= 37) { // foreground color
+                mForeColor = (mForeColor & 0x8) | (code - 30);
+            } else if (code == 39) { // set default text color
+                mForeColor = 7;
+                mBackColor = mBackColor & 0x7;
+            } else if (code >= 40 && code <= 47) { // background color
+                mBackColor = (mBackColor & 0x8) | (code - 40);
+            } else if (code == 49) { // set default background color
+                mBackColor = mBackColor & 0x8; // color 0, but preserve underscore.
+            } else {
+                if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+                    Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code));
+                }
+            }
+        }
+    }
+
+    private void blockClear(int sx, int sy, int w) {
+        blockClear(sx, sy, w, 1);
+    }
+
+    private void blockClear(int sx, int sy, int w, int h) {
+        mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor());
+    }
+
+    private int getForeColor() {
+        return mInverseColors ?
+                ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor;
+    }
+
+    private int getBackColor() {
+        return mInverseColors ?
+                ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor;
+    }
+
+    private void doSetMode(boolean newValue) {
+        int modeBit = getArg0(0);
+        switch (modeBit) {
+        case 4:
+            mInsertMode = newValue;
+            break;
+
+        case 20:
+            mAutomaticNewlineMode = newValue;
+            break;
+
+        default:
+            unknownParameter(modeBit);
+            break;
+        }
+    }
+
+    private void setHorizontalVerticalPosition() {
+
+        // Parameters are Row ; Column
+
+        setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
+    }
+
+    private void setCursorPosition(int x, int y) {
+        int effectiveTopMargin = 0;
+        int effectiveBottomMargin = mRows;
+        if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) {
+            effectiveTopMargin = mTopMargin;
+            effectiveBottomMargin = mBottomMargin;
+        }
+        int newRow =
+                Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y,
+                        effectiveBottomMargin - 1));
+        int newCol = Math.max(0, Math.min(x, mColumns - 1));
+        setCursorRowCol(newRow, newCol);
+    }
+
+    private void sendDeviceAttributes() {
+        // This identifies us as a DEC vt100 with advanced
+        // video options. This is what the xterm terminal
+        // emulator sends.
+        byte[] attributes =
+                {
+                /* VT100 */
+                 (byte) 27, (byte) '[', (byte) '?', (byte) '1',
+                 (byte) ';', (byte) '2', (byte) 'c'
+
+                /* VT220
+                (byte) 27, (byte) '[', (byte) '?', (byte) '6',
+                (byte) '0',  (byte) ';',
+                (byte) '1',  (byte) ';',
+                (byte) '2',  (byte) ';',
+                (byte) '6',  (byte) ';',
+                (byte) '8',  (byte) ';',
+                (byte) '9',  (byte) ';',
+                (byte) '1',  (byte) '5', (byte) ';',
+                (byte) 'c'
+                */
+                };
+
+        write(attributes);
+    }
+
+    /**
+     * Send data to the shell process
+     * @param data
+     */
+    private void write(byte[] data) {
+        try {
+            mTermOut.write(data);
+            mTermOut.flush();
+        } catch (IOException e) {
+            // Ignore exception
+            // We don't really care if the receiver isn't listening.
+            // We just make a best effort to answer the query.
+        }
+    }
+
+    private void scroll() {
+        //System.out.println("Scroll(): mTopMargin " + mTopMargin + " mBottomMargin " + mBottomMargin);
+        mScrollCounter ++;
+        mScreen.scroll(mTopMargin, mBottomMargin,
+                getForeColor(), getBackColor());
+    }
+
+    /**
+     * Process the next ASCII character of a parameter.
+     *
+     * @param b The next ASCII character of the paramater sequence.
+     */
+    private void parseArg(byte b) {
+        if (b >= '0' && b <= '9') {
+            if (mArgIndex < mArgs.length) {
+                int oldValue = mArgs[mArgIndex];
+                int thisDigit = b - '0';
+                int value;
+                if (oldValue >= 0) {
+                    value = oldValue * 10 + thisDigit;
+                } else {
+                    value = thisDigit;
+                }
+                mArgs[mArgIndex] = value;
+            }
+            continueSequence();
+        } else if (b == ';') {
+            if (mArgIndex < mArgs.length) {
+                mArgIndex++;
+            }
+            continueSequence();
+        } else {
+            unknownSequence(b);
+        }
+    }
+
+    private int getArg0(int defaultValue) {
+        return getArg(0, defaultValue);
+    }
+
+    private int getArg1(int defaultValue) {
+        return getArg(1, defaultValue);
+    }
+
+    private int getArg(int index, int defaultValue) {
+        int result = mArgs[index];
+        if (result < 0) {
+            result = defaultValue;
+        }
+        return result;
+    }
+
+    private void unimplementedSequence(byte b) {
+        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+            logError("unimplemented", b);
+        }
+        finishSequence();
+    }
+
+    private void unknownSequence(byte b) {
+        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+            logError("unknown", b);
+        }
+        finishSequence();
+    }
+
+    private void unknownParameter(int parameter) {
+        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+            StringBuilder buf = new StringBuilder();
+            buf.append("Unknown parameter");
+            buf.append(parameter);
+            logError(buf.toString());
+        }
+    }
+
+    private void logError(String errorType, byte b) {
+        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+            StringBuilder buf = new StringBuilder();
+            buf.append(errorType);
+            buf.append(" sequence ");
+            buf.append(" EscapeState: ");
+            buf.append(mEscapeState);
+            buf.append(" char: '");
+            buf.append((char) b);
+            buf.append("' (");
+            buf.append(b);
+            buf.append(")");
+            boolean firstArg = true;
+            for (int i = 0; i <= mArgIndex; i++) {
+                int value = mArgs[i];
+                if (value >= 0) {
+                    if (firstArg) {
+                        firstArg = false;
+                        buf.append("args = ");
+                    }
+                    buf.append(String.format("%d; ", value));
+                }
+            }
+            logError(buf.toString());
+        }
+    }
+
+    private void logError(String error) {
+        if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
+            Log.e(Term.LOG_TAG, error);
+        }
+        finishSequence();
+    }
+
+    private void finishSequence() {
+        mEscapeState = ESC_NONE;
+    }
+
+    private boolean autoWrapEnabled() {
+        // Always enable auto wrap, because it's useful on a small screen
+        return true;
+        // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0;
+    }
+
+    /**
+     * Send an ASCII character to the screen.
+     *
+     * @param b the ASCII character to display.
+     */
+    private void emit(byte b) {
+        boolean autoWrap = autoWrapEnabled();
+
+        if (autoWrap) {
+            if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) {
+                mScreen.setLineWrap(mCursorRow);
+                mCursorCol = 0;
+                if (mCursorRow + 1 < mBottomMargin) {
+                    mCursorRow++;
+                } else {
+                    scroll();
+                }
+            }
+        }
+
+        if (mInsertMode) { // Move character to right one space
+            int destCol = mCursorCol + 1;
+            if (destCol < mColumns) {
+                mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol,
+                        1, destCol, mCursorRow);
+            }
+        }
+
+        mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor());
+
+        if (autoWrap) {
+            mAboutToAutoWrap = (mCursorCol == mColumns - 1);
+        }
+
+        mCursorCol = Math.min(mCursorCol + 1, mColumns - 1);
+    }
+
+    private void setCursorRow(int row) {
+        mCursorRow = row;
+        mAboutToAutoWrap = false;
+    }
+
+    private void setCursorCol(int col) {
+        mCursorCol = col;
+        mAboutToAutoWrap = false;
+    }
+
+    private void setCursorRowCol(int row, int col) {
+        mCursorRow = Math.min(row, mRows-1);
+        mCursorCol = Math.min(col, mColumns-1);
+        mAboutToAutoWrap = false;
+    }
+
+    public int getScrollCounter() {
+        return mScrollCounter;
+    }
+
+    public void clearScrollCounter() {
+        mScrollCounter = 0;
+    }
+
+    /**
+     * Reset the terminal emulator to its initial state.
+     */
+    public void reset() {
+        mCursorRow = 0;
+        mCursorCol = 0;
+        mArgIndex = 0;
+        mContinueSequence = false;
+        mEscapeState = ESC_NONE;
+        mSavedCursorRow = 0;
+        mSavedCursorCol = 0;
+        mDecFlags = 0;
+        mSavedDecFlags = 0;
+        mInsertMode = false;
+        mAutomaticNewlineMode = false;
+        mTopMargin = 0;
+        mBottomMargin = mRows;
+        mAboutToAutoWrap = false;
+        mForeColor = 7;
+        mBackColor = 0;
+        mInverseColors = false;
+        mbKeypadApplicationMode = false;
+        mAlternateCharSet = false;
+        // mProcessedCharCount is preserved unchanged.
+        setDefaultTabStops();
+        blockClear(0, 0, mColumns, mRows);
+    }
+
+    public String getTranscriptText() {
+        return mScreen.getTranscriptText();
+    }
+    public String getSelectedText(int x1, int y1, int x2, int y2) {
+        return mScreen.getSelectedText(x1, y1, x2, y2);
+    }
+}
diff --git a/src/jackpal/androidterm/session/TranscriptScreen.java b/src/jackpal/androidterm/session/TranscriptScreen.java
new file mode 100644 (file)
index 0000000..c79e403
--- /dev/null
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.session;
+
+import android.graphics.Canvas;
+import android.util.Log;
+
+import jackpal.androidterm.model.Screen;
+import jackpal.androidterm.model.TextRenderer;
+
+/**
+ * A TranscriptScreen is a screen that remembers data that's been scrolled. The
+ * old data is stored in a ring buffer to minimize the amount of copying that
+ * needs to be done. The transcript does its own drawing, to avoid having to
+ * expose its internal data structures.
+ */
+public class TranscriptScreen implements Screen {
+    private static final String TAG = "TranscriptScreen";
+
+    /**
+     * The width of the transcript, in characters. Fixed at initialization.
+     */
+    private int mColumns;
+
+    /**
+     * The total number of rows in the transcript and the screen. Fixed at
+     * initialization.
+     */
+    private int mTotalRows;
+
+    /**
+     * The number of rows in the active portion of the transcript. Doesn't
+     * include the screen.
+     */
+    private int mActiveTranscriptRows;
+
+    /**
+     * Which row is currently the topmost line of the transcript. Used to
+     * implement a circular buffer.
+     */
+    private int mHead;
+
+    /**
+     * The number of active rows, includes both the transcript and the screen.
+     */
+    private int mActiveRows;
+
+    /**
+     * The number of rows in the screen.
+     */
+    private int mScreenRows;
+
+    /**
+     * The data for both the screen and the transcript. The first mScreenRows *
+     * mLineWidth characters are the screen, the rest are the transcript.
+     * The low byte encodes the ASCII character, the high byte encodes the
+     * foreground and background colors, plus underline and bold.
+     */
+    private char[] mData;
+
+    /**
+     * The data's stored as color-encoded chars, but the drawing routines require chars, so we
+     * need a temporary buffer to hold a row's worth of characters.
+     */
+    private char[] mRowBuffer;
+
+    /**
+     * Flags that keep track of whether the current line logically wraps to the
+     * next line. This is used when resizing the screen and when copying to the
+     * clipboard or an email attachment
+     */
+
+    private boolean[] mLineWrap;
+
+    /**
+     * Create a transcript screen.
+     *
+     * @param columns the width of the screen in characters.
+     * @param totalRows the height of the entire text area, in rows of text.
+     * @param screenRows the height of just the screen, not including the
+     *        transcript that holds lines that have scrolled off the top of the
+     *        screen.
+     */
+    public TranscriptScreen(int columns, int totalRows, int screenRows,
+            int foreColor, int backColor) {
+        init(columns, totalRows, screenRows, foreColor, backColor);
+    }
+
+    private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) {
+        mColumns = columns;
+        mTotalRows = totalRows;
+        mActiveTranscriptRows = 0;
+        mHead = 0;
+        mActiveRows = screenRows;
+        mScreenRows = screenRows;
+        int totalSize = columns * totalRows;
+        mData = new char[totalSize];
+        blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor);
+        mRowBuffer = new char[columns];
+        mLineWrap = new boolean[totalRows];
+        consistencyCheck();
+   }
+
+    /**
+     * Convert a row value from the public external coordinate system to our
+     * internal private coordinate system. External coordinate system:
+     * -mActiveTranscriptRows to mScreenRows-1, with the screen being
+     * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of
+     * mData are the visible rows. mScreenRows..mActiveRows - 1 are the
+     * transcript, stored as a circular buffer.
+     *
+     * @param row a row in the external coordinate system.
+     * @return The row corresponding to the input argument in the private
+     *         coordinate system.
+     */
+    private int externalToInternalRow(int row) {
+        if (row < -mActiveTranscriptRows || row >= mScreenRows) {
+            String errorMessage = "externalToInternalRow "+ row +
+                " " + mActiveTranscriptRows + " " + mScreenRows;
+            Log.e(TAG, errorMessage);
+            throw new IllegalArgumentException(errorMessage);
+        }
+        if (row >= 0) {
+            return row; // This is a visible row.
+        }
+        return mScreenRows
+                + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows);
+    }
+
+    private int getOffset(int externalLine) {
+        return externalToInternalRow(externalLine) * mColumns;
+    }
+
+    private int getOffset(int x, int y) {
+        return getOffset(y) + x;
+    }
+
+    public void setLineWrap(int row) {
+        mLineWrap[externalToInternalRow(row)] = true;
+    }
+
+    /**
+     * Store byte b into the screen at location (x, y)
+     *
+     * @param x X coordinate (also known as column)
+     * @param y Y coordinate (also known as row)
+     * @param b ASCII character to store
+     * @param foreColor the foreground color
+     * @param backColor the background color
+     */
+    public void set(int x, int y, byte b, int foreColor, int backColor) {
+        mData[getOffset(x, y)] = encode(b, foreColor, backColor);
+    }
+
+    private char encode(int b, int foreColor, int backColor) {
+        return (char) ((foreColor << 12) | (backColor << 8) | b);
+    }
+
+    /**
+     * Scroll the screen down one line. To scroll the whole screen of a 24 line
+     * screen, the arguments would be (0, 24).
+     *
+     * @param topMargin First line that is scrolled.
+     * @param bottomMargin One line after the last line that is scrolled.
+     */
+    public void scroll(int topMargin, int bottomMargin, int foreColor,
+            int backColor) {
+        // Separate out reasons so that stack crawls help us
+        // figure out which condition was violated.
+        if (topMargin > bottomMargin - 1) {
+            throw new IllegalArgumentException();
+        }
+
+        if (topMargin > mScreenRows - 1) {
+            throw new IllegalArgumentException();
+        }
+
+        if (bottomMargin > mScreenRows) {
+            throw new IllegalArgumentException();
+        }
+
+        // Adjust the transcript so that the last line of the transcript
+        // is ready to receive the newly scrolled data
+        consistencyCheck();
+        int expansionRows = Math.min(1, mTotalRows - mActiveRows);
+        int rollRows = 1 - expansionRows;
+        mActiveRows += expansionRows;
+        mActiveTranscriptRows += expansionRows;
+        if (mActiveTranscriptRows > 0) {
+            mHead = (mHead + rollRows) % mActiveTranscriptRows;
+        }
+        consistencyCheck();
+
+        // Block move the scroll line to the transcript
+        int topOffset = getOffset(topMargin);
+        int destOffset = getOffset(-1);
+        System.arraycopy(mData, topOffset, mData, destOffset, mColumns);
+
+        int topLine = externalToInternalRow(topMargin);
+        int destLine = externalToInternalRow(-1);
+        System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1);
+
+        // Block move the scrolled data up
+        int numScrollChars = (bottomMargin - topMargin - 1) * mColumns;
+        System.arraycopy(mData, topOffset + mColumns, mData, topOffset,
+                numScrollChars);
+        int numScrollLines = (bottomMargin - topMargin - 1);
+        System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine,
+                numScrollLines);
+
+        // Erase the bottom line of the scroll region
+        blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor);
+        mLineWrap[externalToInternalRow(bottomMargin-1)] = false;
+    }
+
+    private void consistencyCheck() {
+        checkPositive(mColumns);
+        checkPositive(mTotalRows);
+        checkRange(0, mActiveTranscriptRows, mTotalRows);
+        if (mActiveTranscriptRows == 0) {
+            checkEqual(mHead, 0);
+        } else {
+            checkRange(0, mHead, mActiveTranscriptRows-1);
+        }
+        checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows);
+        checkRange(0, mScreenRows, mTotalRows);
+
+        checkEqual(mTotalRows, mLineWrap.length);
+        checkEqual(mTotalRows*mColumns, mData.length);
+        checkEqual(mColumns, mRowBuffer.length);
+    }
+
+    private void checkPositive(int n) {
+        if (n < 0) {
+            throw new IllegalArgumentException("checkPositive " + n);
+        }
+    }
+
+    private void checkRange(int a, int b, int c) {
+        if (a > b || b > c) {
+            throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c);
+        }
+    }
+
+    private void checkEqual(int a, int b) {
+        if (a != b) {
+            throw new IllegalArgumentException("checkEqual " + a + " == " + b);
+        }
+    }
+
+    /**
+     * Block copy characters from one position in the screen to another. The two
+     * positions can overlap. All characters of the source and destination must
+     * be within the bounds of the screen, or else an InvalidParemeterException
+     * will be thrown.
+     *
+     * @param sx source X coordinate
+     * @param sy source Y coordinate
+     * @param w width
+     * @param h height
+     * @param dx destination X coordinate
+     * @param dy destination Y coordinate
+     */
+    public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
+        if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows
+                || dx < 0 || dx + w > mColumns || dy < 0
+                || dy + h > mScreenRows) {
+            throw new IllegalArgumentException();
+        }
+        if (sy > dy) {
+            // Move in increasing order
+            for (int y = 0; y < h; y++) {
+                int srcOffset = getOffset(sx, sy + y);
+                int dstOffset = getOffset(dx, dy + y);
+                System.arraycopy(mData, srcOffset, mData, dstOffset, w);
+            }
+        } else {
+            // Move in decreasing order
+            for (int y = 0; y < h; y++) {
+                int y2 = h - (y + 1);
+                int srcOffset = getOffset(sx, sy + y2);
+                int dstOffset = getOffset(dx, dy + y2);
+                System.arraycopy(mData, srcOffset, mData, dstOffset, w);
+            }
+        }
+    }
+
+    /**
+     * Block set characters. All characters must be within the bounds of the
+     * screen, or else and InvalidParemeterException will be thrown. Typically
+     * this is called with a "val" argument of 32 to clear a block of
+     * characters.
+     *
+     * @param sx source X
+     * @param sy source Y
+     * @param w width
+     * @param h height
+     * @param val value to set.
+     */
+    public void blockSet(int sx, int sy, int w, int h, int val,
+            int foreColor, int backColor) {
+        if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
+            throw new IllegalArgumentException();
+        }
+        char[] data = mData;
+        char encodedVal = encode(val, foreColor, backColor);
+        for (int y = 0; y < h; y++) {
+            int offset = getOffset(sx, sy + y);
+            for (int x = 0; x < w; x++) {
+                data[offset + x] = encodedVal;
+            }
+        }
+    }
+
+    /**
+     * Draw a row of text. Out-of-bounds rows are blank, not errors.
+     *
+     * @param row The row of text to draw.
+     * @param canvas The canvas to draw to.
+     * @param x The x coordinate origin of the drawing
+     * @param y The y coordinate origin of the drawing
+     * @param renderer The renderer to use to draw the text
+     * @param cx the cursor X coordinate, -1 means don't draw it
+     * @param selx1 the text selection start X coordinate
+     * @param selx2 the text selection end X coordinate, if equals to selx1 don't draw selection
+     * @param imeText current IME text, to be rendered at cursor
+     */
+    public final void drawText(int row, Canvas canvas, float x, float y,
+            TextRenderer renderer, int cx, int selx1, int selx2, String imeText) {
+
+        // Out-of-bounds rows are blank.
+        if (row < -mActiveTranscriptRows || row >= mScreenRows) {
+            return;
+        }
+
+        // Copy the data from the byte array to a char array so they can
+        // be drawn.
+
+        int offset = getOffset(row);
+        char[] rowBuffer = mRowBuffer;
+        char[] data = mData;
+        int columns = mColumns;
+        int lastColors = 0;
+        int lastRunStart = -1;
+        final int CURSOR_MASK = 0x10000;
+        for (int i = 0; i < columns; i++) {
+            char c = data[offset + i];
+            int colors = (char) (c & 0xff00);
+            if (cx == i || (i >= selx1 && i <= selx2)) {
+                // Set cursor background color:
+                colors |= CURSOR_MASK;
+            }
+            rowBuffer[i] = (char) (c & 0x00ff);
+            if (colors != lastColors) {
+                if (lastRunStart >= 0) {
+                    renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
+                            lastRunStart, i - lastRunStart,
+                            (lastColors & CURSOR_MASK) != 0,
+                            0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
+                }
+                lastColors = colors;
+                lastRunStart = i;
+            }
+        }
+        if (lastRunStart >= 0) {
+            renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
+                    lastRunStart, columns - lastRunStart,
+                    (lastColors & CURSOR_MASK) != 0,
+                    0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
+        }
+
+        if (cx >= 0 && imeText.length() > 0) {
+            int imeLength = Math.min(columns, imeText.length());
+            int imeOffset = imeText.length() - imeLength;
+            int imePosition = Math.min(cx, columns - imeLength);
+            renderer.drawTextRun(canvas, x, y, imePosition, imeText.toCharArray(),
+                    imeOffset, imeLength, true, 0x0f, 0x00);
+        }
+     }
+
+    /**
+     * Get the count of active rows.
+     *
+     * @return the count of active rows.
+     */
+    public int getActiveRows() {
+        return mActiveRows;
+    }
+
+    /**
+     * Get the count of active transcript rows.
+     *
+     * @return the count of active transcript rows.
+     */
+    public int getActiveTranscriptRows() {
+        return mActiveTranscriptRows;
+    }
+
+    public String getTranscriptText() {
+        return internalGetTranscriptText(true, 0, -mActiveTranscriptRows, mColumns, mScreenRows);
+    }
+
+    public String getSelectedText(int selX1, int selY1, int selX2, int selY2) {
+        return internalGetTranscriptText(true, selX1, selY1, selX2, selY2);
+    }
+
+    private String internalGetTranscriptText(boolean stripColors, int selX1, int selY1, int selX2, int selY2) {
+        StringBuilder builder = new StringBuilder();
+        char[] rowBuffer = mRowBuffer;
+        char[] data = mData;
+        int columns = mColumns;
+        for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) {
+            int offset = getOffset(row);
+            int lastPrintingChar = -1;
+            for (int column = 0; column < columns; column++) {
+                char c = data[offset + column];
+                if (stripColors) {
+                    c = (char) (c & 0xff);
+                }
+                if ((c & 0xff) != ' ') {
+                    lastPrintingChar = column;
+                }
+                rowBuffer[column] = c;
+            }
+            if ( row >= selY1 && row <= selY2 ) {
+                int x1 = 0;
+                int x2 = 0;
+                if ( row == selY1 ) {
+                    x1 = selX1;
+                }
+                if ( row == selY2 ) {
+                    x2 = selX2;
+                } else {
+                    x2 = columns;
+                }
+                if (mLineWrap[externalToInternalRow(row)]) {
+                    builder.append(rowBuffer, x1, x2 - x1);
+                } else {
+                    builder.append(rowBuffer, x1, Math.max(0, Math.min(x2 - x1 + 1, lastPrintingChar + 1 - x1)));
+                    builder.append('\n');
+                }
+            }
+        }
+        return builder.toString();
+    }
+
+    public void resize(int columns, int rows, int foreColor, int backColor) {
+        init(columns, mTotalRows, rows, foreColor, backColor);
+    }
+}
diff --git a/src/jackpal/androidterm/util/ByteQueue.java b/src/jackpal/androidterm/util/ByteQueue.java
new file mode 100644 (file)
index 0000000..debee07
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 jackpal.androidterm.util;
+
+/**
+ * A multi-thread-safe produce-consumer byte array.
+ * Only allows one producer and one consumer.
+ */
+
+public class ByteQueue {
+    public ByteQueue(int size) {
+        mBuffer = new byte[size];
+    }
+
+    public int getBytesAvailable() {
+        synchronized(this) {
+            return mStoredBytes;
+        }
+    }
+
+    public int read(byte[] buffer, int offset, int length)
+        throws InterruptedException {
+        if (length + offset > buffer.length) {
+            throw
+                new IllegalArgumentException("length + offset > buffer.length");
+        }
+        if (length < 0) {
+            throw
+            new IllegalArgumentException("length < 0");
+
+        }
+        if (length == 0) {
+            return 0;
+        }
+        synchronized(this) {
+            while (mStoredBytes == 0) {
+                wait();
+            }
+            int totalRead = 0;
+            int bufferLength = mBuffer.length;
+            boolean wasFull = bufferLength == mStoredBytes;
+            while (length > 0 && mStoredBytes > 0) {
+                int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
+                int bytesToCopy = Math.min(length, oneRun);
+                System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
+                mHead += bytesToCopy;
+                if (mHead >= bufferLength) {
+                    mHead = 0;
+                }
+                mStoredBytes -= bytesToCopy;
+                length -= bytesToCopy;
+                offset += bytesToCopy;
+                totalRead += bytesToCopy;
+            }
+            if (wasFull) {
+                notify();
+            }
+            return totalRead;
+        }
+    }
+
+    public void write(byte[] buffer, int offset, int length)
+    throws InterruptedException {
+        if (length + offset > buffer.length) {
+            throw
+                new IllegalArgumentException("length + offset > buffer.length");
+        }
+        if (length < 0) {
+            throw
+            new IllegalArgumentException("length < 0");
+
+        }
+        if (length == 0) {
+            return;
+        }
+        synchronized(this) {
+            int bufferLength = mBuffer.length;
+            boolean wasEmpty = mStoredBytes == 0;
+            while (length > 0) {
+                while(bufferLength == mStoredBytes) {
+                    wait();
+                }
+                int tail = mHead + mStoredBytes;
+                int oneRun;
+                if (tail >= bufferLength) {
+                    tail = tail - bufferLength;
+                    oneRun = mHead - tail;
+                } else {
+                    oneRun = bufferLength - tail;
+                }
+                int bytesToCopy = Math.min(oneRun, length);
+                System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
+                offset += bytesToCopy;
+                mStoredBytes += bytesToCopy;
+                length -= bytesToCopy;
+            }
+            if (wasEmpty) {
+                notify();
+            }
+        }
+    }
+
+    private byte[] mBuffer;
+    private int mHead;
+    private int mStoredBytes;
+}
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package jackpal.androidterm;
+package jackpal.androidterm.util;
 
 import java.lang.reflect.Method;
 import java.lang.reflect.InvocationTargetException;