Home | History | Annotate | Download | only in term
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.term;
     18 
     19 import java.io.FileDescriptor;
     20 import java.io.FileInputStream;
     21 import java.io.FileOutputStream;
     22 import java.io.IOException;
     23 import java.util.ArrayList;
     24 
     25 import android.app.Activity;
     26 import android.app.AlertDialog;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.SharedPreferences;
     30 import android.content.res.Configuration;
     31 import android.content.res.Resources;
     32 import android.content.res.TypedArray;
     33 import android.graphics.Bitmap;
     34 import android.graphics.BitmapFactory;
     35 import android.graphics.Canvas;
     36 import android.graphics.ColorMatrixColorFilter;
     37 import android.graphics.Paint;
     38 import android.graphics.PorterDuff;
     39 import android.graphics.PorterDuffXfermode;
     40 import android.graphics.Rect;
     41 import android.graphics.Typeface;
     42 import android.net.Uri;
     43 import android.os.Bundle;
     44 import android.os.Handler;
     45 import android.os.Message;
     46 import android.preference.PreferenceManager;
     47 import android.util.AttributeSet;
     48 import android.util.DisplayMetrics;
     49 import android.util.Log;
     50 import android.view.GestureDetector;
     51 import android.view.KeyEvent;
     52 import android.view.Menu;
     53 import android.view.MenuItem;
     54 import android.view.MotionEvent;
     55 import android.view.View;
     56 import android.view.inputmethod.BaseInputConnection;
     57 import android.view.inputmethod.CompletionInfo;
     58 import android.view.inputmethod.EditorInfo;
     59 import android.view.inputmethod.ExtractedText;
     60 import android.view.inputmethod.ExtractedTextRequest;
     61 import android.view.inputmethod.InputConnection;
     62 
     63 /**
     64  * A terminal emulator activity.
     65  */
     66 
     67 public class Term extends Activity {
     68     /**
     69      * Set to true to add debugging code and logging.
     70      */
     71     public static final boolean DEBUG = false;
     72 
     73     /**
     74      * Set to true to log each character received from the remote process to the
     75      * android log, which makes it easier to debug some kinds of problems with
     76      * emulating escape sequences and control codes.
     77      */
     78     public static final boolean LOG_CHARACTERS_FLAG = DEBUG && false;
     79 
     80     /**
     81      * Set to true to log unknown escape sequences.
     82      */
     83     public static final boolean LOG_UNKNOWN_ESCAPE_SEQUENCES = DEBUG && false;
     84 
     85     /**
     86      * The tag we use when logging, so that our messages can be distinguished
     87      * from other messages in the log. Public because it's used by several
     88      * classes.
     89      */
     90     public static final String LOG_TAG = "Term";
     91 
     92     /**
     93      * Our main view. Displays the emulated terminal screen.
     94      */
     95     private EmulatorView mEmulatorView;
     96 
     97     /**
     98      * The pseudo-teletype (pty) file descriptor that we use to communicate with
     99      * another process, typically a shell.
    100      */
    101     private FileDescriptor mTermFd;
    102 
    103     /**
    104      * Used to send data to the remote process.
    105      */
    106     private FileOutputStream mTermOut;
    107 
    108     /**
    109      * A key listener that tracks the modifier keys and allows the full ASCII
    110      * character set to be entered.
    111      */
    112     private TermKeyListener mKeyListener;
    113 
    114     /**
    115      * The name of our emulator view in the view resource.
    116      */
    117     private static final int EMULATOR_VIEW = R.id.emulatorView;
    118 
    119     private int mFontSize = 9;
    120     private int mColorId = 2;
    121     private int mControlKeyId = 0;
    122 
    123     private static final String FONTSIZE_KEY = "fontsize";
    124     private static final String COLOR_KEY = "color";
    125     private static final String CONTROLKEY_KEY = "controlkey";
    126     private static final String SHELL_KEY = "shell";
    127     private static final String INITIALCOMMAND_KEY = "initialcommand";
    128 
    129     public static final int WHITE = 0xffffffff;
    130     public static final int BLACK = 0xff000000;
    131     public static final int BLUE = 0xff344ebd;
    132 
    133     private static final int[][] COLOR_SCHEMES = {
    134         {BLACK, WHITE}, {WHITE, BLACK}, {WHITE, BLUE}};
    135 
    136     private static final int[] CONTROL_KEY_SCHEMES = {
    137         KeyEvent.KEYCODE_DPAD_CENTER,
    138         KeyEvent.KEYCODE_AT,
    139         KeyEvent.KEYCODE_ALT_LEFT,
    140         KeyEvent.KEYCODE_ALT_RIGHT
    141     };
    142     private static final String[] CONTROL_KEY_NAME = {
    143         "Ball", "@", "Left-Alt", "Right-Alt"
    144     };
    145 
    146     private int mControlKeyCode;
    147 
    148     private final static String DEFAULT_SHELL = "/system/bin/sh -";
    149     private String mShell;
    150 
    151     private final static String DEFAULT_INITIAL_COMMAND =
    152         "export PATH=/data/local/bin:$PATH";
    153     private String mInitialCommand;
    154 
    155     private SharedPreferences mPrefs;
    156 
    157     @Override
    158     public void onCreate(Bundle icicle) {
    159         super.onCreate(icicle);
    160         Log.e(Term.LOG_TAG, "onCreate");
    161         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    162         readPrefs();
    163 
    164         setContentView(R.layout.term_activity);
    165 
    166         mEmulatorView = (EmulatorView) findViewById(EMULATOR_VIEW);
    167 
    168         startListening();
    169 
    170         mKeyListener = new TermKeyListener();
    171 
    172         mEmulatorView.setFocusable(true);
    173         mEmulatorView.setFocusableInTouchMode(true);
    174         mEmulatorView.requestFocus();
    175         mEmulatorView.register(mKeyListener);
    176 
    177         updatePrefs();
    178     }
    179 
    180     @Override
    181     public void onDestroy() {
    182         super.onDestroy();
    183         if (mTermFd != null) {
    184             Exec.close(mTermFd);
    185             mTermFd = null;
    186         }
    187     }
    188 
    189     private void startListening() {
    190         int[] processId = new int[1];
    191 
    192         createSubprocess(processId);
    193         final int procId = processId[0];
    194 
    195         final Handler handler = new Handler() {
    196             @Override
    197             public void handleMessage(Message msg) {
    198             }
    199         };
    200 
    201         Runnable watchForDeath = new Runnable() {
    202 
    203             public void run() {
    204                 Log.i(Term.LOG_TAG, "waiting for: " + procId);
    205                int result = Exec.waitFor(procId);
    206                 Log.i(Term.LOG_TAG, "Subprocess exited: " + result);
    207                 handler.sendEmptyMessage(result);
    208              }
    209 
    210         };
    211         Thread watcher = new Thread(watchForDeath);
    212         watcher.start();
    213 
    214         mTermOut = new FileOutputStream(mTermFd);
    215 
    216         mEmulatorView.initialize(mTermFd, mTermOut);
    217 
    218         sendInitialCommand();
    219     }
    220 
    221     private void sendInitialCommand() {
    222         String initialCommand = mInitialCommand;
    223         if (initialCommand == null || initialCommand.equals("")) {
    224             initialCommand = DEFAULT_INITIAL_COMMAND;
    225         }
    226         if (initialCommand.length() > 0) {
    227             write(initialCommand + '\r');
    228         }
    229     }
    230 
    231     private void restart() {
    232         startActivity(getIntent());
    233         finish();
    234     }
    235 
    236     private void write(String data) {
    237         try {
    238             mTermOut.write(data.getBytes());
    239             mTermOut.flush();
    240         } catch (IOException e) {
    241             // Ignore exception
    242             // We don't really care if the receiver isn't listening.
    243             // We just make a best effort to answer the query.
    244         }
    245     }
    246 
    247     private void createSubprocess(int[] processId) {
    248         String shell = mShell;
    249         if (shell == null || shell.equals("")) {
    250             shell = DEFAULT_SHELL;
    251         }
    252         ArrayList<String> args = parse(shell);
    253         String arg0 = args.get(0);
    254         String arg1 = null;
    255         String arg2 = null;
    256         if (args.size() >= 2) {
    257             arg1 = args.get(1);
    258         }
    259         if (args.size() >= 3) {
    260             arg2 = args.get(2);
    261         }
    262         mTermFd = Exec.createSubprocess(arg0, arg1, arg2, processId);
    263     }
    264 
    265     private ArrayList<String> parse(String cmd) {
    266         final int PLAIN = 0;
    267         final int WHITESPACE = 1;
    268         final int INQUOTE = 2;
    269         int state = WHITESPACE;
    270         ArrayList<String> result =  new ArrayList<String>();
    271         int cmdLen = cmd.length();
    272         StringBuilder builder = new StringBuilder();
    273         for (int i = 0; i < cmdLen; i++) {
    274             char c = cmd.charAt(i);
    275             if (state == PLAIN) {
    276                 if (Character.isWhitespace(c)) {
    277                     result.add(builder.toString());
    278                     builder.delete(0,builder.length());
    279                     state = WHITESPACE;
    280                 } else if (c == '"') {
    281                     state = INQUOTE;
    282                 } else {
    283                     builder.append(c);
    284                 }
    285             } else if (state == WHITESPACE) {
    286                 if (Character.isWhitespace(c)) {
    287                     // do nothing
    288                 } else if (c == '"') {
    289                     state = INQUOTE;
    290                 } else {
    291                     state = PLAIN;
    292                     builder.append(c);
    293                 }
    294             } else if (state == INQUOTE) {
    295                 if (c == '\\') {
    296                     if (i + 1 < cmdLen) {
    297                         i += 1;
    298                         builder.append(cmd.charAt(i));
    299                     }
    300                 } else if (c == '"') {
    301                     state = PLAIN;
    302                 } else {
    303                     builder.append(c);
    304                 }
    305             }
    306         }
    307         if (builder.length() > 0) {
    308             result.add(builder.toString());
    309         }
    310         return result;
    311     }
    312 
    313     private void readPrefs() {
    314         mFontSize = readIntPref(FONTSIZE_KEY, mFontSize, 20);
    315         mColorId = readIntPref(COLOR_KEY, mColorId, COLOR_SCHEMES.length - 1);
    316         mControlKeyId = readIntPref(CONTROLKEY_KEY, mControlKeyId,
    317                 CONTROL_KEY_SCHEMES.length - 1);
    318         {
    319             String newShell = readStringPref(SHELL_KEY, mShell);
    320             if ((newShell == null) || ! newShell.equals(mShell)) {
    321                 if (mShell != null) {
    322                     Log.i(Term.LOG_TAG, "New shell set. Restarting.");
    323                     restart();
    324                 }
    325                 mShell = newShell;
    326             }
    327         }
    328         {
    329             String newInitialCommand = readStringPref(INITIALCOMMAND_KEY,
    330                     mInitialCommand);
    331             if ((newInitialCommand == null)
    332                     || ! newInitialCommand.equals(mInitialCommand)) {
    333                 if (mInitialCommand != null) {
    334                     Log.i(Term.LOG_TAG, "New initial command set. Restarting.");
    335                     restart();
    336                 }
    337                 mInitialCommand = newInitialCommand;
    338             }
    339         }
    340     }
    341 
    342     private void updatePrefs() {
    343         DisplayMetrics metrics = new DisplayMetrics();
    344         getWindowManager().getDefaultDisplay().getMetrics(metrics);
    345         mEmulatorView.setTextSize((int) (mFontSize * metrics.density));
    346         setColors();
    347         mControlKeyCode = CONTROL_KEY_SCHEMES[mControlKeyId];
    348     }
    349 
    350     private int readIntPref(String key, int defaultValue, int maxValue) {
    351         int val;
    352         try {
    353             val = Integer.parseInt(
    354                 mPrefs.getString(key, Integer.toString(defaultValue)));
    355         } catch (NumberFormatException e) {
    356             val = defaultValue;
    357         }
    358         val = Math.max(0, Math.min(val, maxValue));
    359         return val;
    360     }
    361 
    362     private String readStringPref(String key, String defaultValue) {
    363         return mPrefs.getString(key, defaultValue);
    364     }
    365 
    366     @Override
    367     public void onResume() {
    368         super.onResume();
    369         readPrefs();
    370         updatePrefs();
    371     }
    372 
    373     @Override
    374     public void onConfigurationChanged(Configuration newConfig) {
    375         super.onConfigurationChanged(newConfig);
    376 
    377         mEmulatorView.updateSize();
    378     }
    379 
    380     @Override
    381     public boolean onKeyDown(int keyCode, KeyEvent event) {
    382         if (handleControlKey(keyCode, true)) {
    383             return true;
    384         } else if (isSystemKey(keyCode, event)) {
    385             // Don't intercept the system keys
    386             return super.onKeyDown(keyCode, event);
    387         } else if (handleDPad(keyCode, true)) {
    388             return true;
    389         }
    390 
    391         // Translate the keyCode into an ASCII character.
    392         int letter = mKeyListener.keyDown(keyCode, event);
    393 
    394         if (letter >= 0) {
    395             try {
    396                 mTermOut.write(letter);
    397             } catch (IOException e) {
    398                 // Ignore I/O exceptions
    399             }
    400         }
    401         return true;
    402     }
    403 
    404     @Override
    405     public boolean onKeyUp(int keyCode, KeyEvent event) {
    406         if (handleControlKey(keyCode, false)) {
    407             return true;
    408         } else if (isSystemKey(keyCode, event)) {
    409             // Don't intercept the system keys
    410             return super.onKeyUp(keyCode, event);
    411         } else if (handleDPad(keyCode, false)) {
    412             return true;
    413         }
    414 
    415         mKeyListener.keyUp(keyCode);
    416         return true;
    417     }
    418 
    419     private boolean handleControlKey(int keyCode, boolean down) {
    420         if (keyCode == mControlKeyCode) {
    421             mKeyListener.handleControlKey(down);
    422             return true;
    423         }
    424         return false;
    425     }
    426 
    427     /**
    428      * Handle dpad left-right-up-down events. Don't handle
    429      * dpad-center, that's our control key.
    430      * @param keyCode
    431      * @param down
    432      */
    433     private boolean handleDPad(int keyCode, boolean down) {
    434         if (keyCode < KeyEvent.KEYCODE_DPAD_UP ||
    435                 keyCode > KeyEvent.KEYCODE_DPAD_CENTER) {
    436             return false;
    437         }
    438 
    439         if (down) {
    440             try {
    441                 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
    442                     mTermOut.write('\r');
    443                 } else {
    444                     char code;
    445                     switch (keyCode) {
    446                     case KeyEvent.KEYCODE_DPAD_UP:
    447                         code = 'A';
    448                         break;
    449                     case KeyEvent.KEYCODE_DPAD_DOWN:
    450                         code = 'B';
    451                         break;
    452                     case KeyEvent.KEYCODE_DPAD_LEFT:
    453                         code = 'D';
    454                         break;
    455                     default:
    456                     case KeyEvent.KEYCODE_DPAD_RIGHT:
    457                         code = 'C';
    458                         break;
    459                     }
    460                     mTermOut.write(27); // ESC
    461                     if (mEmulatorView.getKeypadApplicationMode()) {
    462                         mTermOut.write('O');
    463                     } else {
    464                         mTermOut.write('[');
    465                     }
    466                     mTermOut.write(code);
    467                 }
    468             } catch (IOException e) {
    469                 // Ignore
    470             }
    471         }
    472         return true;
    473     }
    474 
    475     private boolean isSystemKey(int keyCode, KeyEvent event) {
    476         return event.isSystem();
    477     }
    478 
    479     @Override
    480     public boolean onCreateOptionsMenu(Menu menu) {
    481         getMenuInflater().inflate(R.menu.main, menu);
    482         return true;
    483     }
    484 
    485     @Override
    486     public boolean onOptionsItemSelected(MenuItem item) {
    487         int id = item.getItemId();
    488         if (id == R.id.menu_preferences) {
    489             doPreferences();
    490         } else if (id == R.id.menu_reset) {
    491             doResetTerminal();
    492         } else if (id == R.id.menu_send_email) {
    493             doEmailTranscript();
    494         } else if (id == R.id.menu_special_keys) {
    495             doDocumentKeys();
    496         }
    497         return super.onOptionsItemSelected(item);
    498     }
    499 
    500     private void doPreferences() {
    501         startActivity(new Intent(this, TermPreferences.class));
    502     }
    503 
    504     private void setColors() {
    505         int[] scheme = COLOR_SCHEMES[mColorId];
    506         mEmulatorView.setColors(scheme[0], scheme[1]);
    507     }
    508 
    509     private void doResetTerminal() {
    510         restart();
    511     }
    512 
    513     private void doEmailTranscript() {
    514         // Don't really want to supply an address, but
    515         // currently it's required, otherwise we get an
    516         // exception.
    517         String addr = "user (at) example.com";
    518         Intent intent =
    519                 new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"
    520                         + addr));
    521 
    522         intent.putExtra("body", mEmulatorView.getTranscriptText());
    523         startActivity(intent);
    524     }
    525 
    526     private void doDocumentKeys() {
    527         String controlKey = CONTROL_KEY_NAME[mControlKeyId];
    528         new AlertDialog.Builder(this).
    529             setTitle("Press " + controlKey + " and Key").
    530             setMessage(controlKey + " Space ==> Control-@ (NUL)\n"
    531                     + controlKey + " A..Z ==> Control-A..Z\n"
    532                     + controlKey + " 1 ==> Control-[ (ESC)\n"
    533                     + controlKey + " 5 ==> Control-_\n"
    534                     + controlKey + " . ==> Control-\\\n"
    535                     + controlKey + " 0 ==> Control-]\n"
    536                     + controlKey + " 6 ==> Control-^").
    537             show();
    538      }
    539 }
    540 
    541 
    542 /**
    543  * An abstract screen interface. A terminal screen stores lines of text. (The
    544  * reason to abstract it is to allow different implementations, and to hide
    545  * implementation details from clients.)
    546  */
    547 interface Screen {
    548 
    549     /**
    550      * Set line wrap flag for a given row. Affects how lines are logically
    551      * wrapped when changing screen size or converting to a transcript.
    552      */
    553     void setLineWrap(int row);
    554 
    555     /**
    556      * Store byte b into the screen at location (x, y)
    557      *
    558      * @param x X coordinate (also known as column)
    559      * @param y Y coordinate (also known as row)
    560      * @param b ASCII character to store
    561      * @param foreColor the foreground color
    562      * @param backColor the background color
    563      */
    564     void set(int x, int y, byte b, int foreColor, int backColor);
    565 
    566     /**
    567      * Scroll the screen down one line. To scroll the whole screen of a 24 line
    568      * screen, the arguments would be (0, 24).
    569      *
    570      * @param topMargin First line that is scrolled.
    571      * @param bottomMargin One line after the last line that is scrolled.
    572      */
    573     void scroll(int topMargin, int bottomMargin, int foreColor, int backColor);
    574 
    575     /**
    576      * Block copy characters from one position in the screen to another. The two
    577      * positions can overlap. All characters of the source and destination must
    578      * be within the bounds of the screen, or else an InvalidParemeterException
    579      * will be thrown.
    580      *
    581      * @param sx source X coordinate
    582      * @param sy source Y coordinate
    583      * @param w width
    584      * @param h height
    585      * @param dx destination X coordinate
    586      * @param dy destination Y coordinate
    587      */
    588     void blockCopy(int sx, int sy, int w, int h, int dx, int dy);
    589 
    590     /**
    591      * Block set characters. All characters must be within the bounds of the
    592      * screen, or else and InvalidParemeterException will be thrown. Typically
    593      * this is called with a "val" argument of 32 to clear a block of
    594      * characters.
    595      *
    596      * @param sx source X
    597      * @param sy source Y
    598      * @param w width
    599      * @param h height
    600      * @param val value to set.
    601      * @param foreColor the foreground color
    602      * @param backColor the background color
    603      */
    604     void blockSet(int sx, int sy, int w, int h, int val, int foreColor, int
    605             backColor);
    606 
    607     /**
    608      * Get the contents of the transcript buffer as a text string.
    609      *
    610      * @return the contents of the transcript buffer.
    611      */
    612     String getTranscriptText();
    613 
    614     /**
    615      * Resize the screen
    616      * @param columns
    617      * @param rows
    618      */
    619     void resize(int columns, int rows, int foreColor, int backColor);
    620 }
    621 
    622 
    623 /**
    624  * A TranscriptScreen is a screen that remembers data that's been scrolled. The
    625  * old data is stored in a ring buffer to minimize the amount of copying that
    626  * needs to be done. The transcript does its own drawing, to avoid having to
    627  * expose its internal data structures.
    628  */
    629 class TranscriptScreen implements Screen {
    630 
    631     /**
    632      * The width of the transcript, in characters. Fixed at initialization.
    633      */
    634     private int mColumns;
    635 
    636     /**
    637      * The total number of rows in the transcript and the screen. Fixed at
    638      * initialization.
    639      */
    640     private int mTotalRows;
    641 
    642     /**
    643      * The number of rows in the active portion of the transcript. Doesn't
    644      * include the screen.
    645      */
    646     private int mActiveTranscriptRows;
    647 
    648     /**
    649      * Which row is currently the topmost line of the transcript. Used to
    650      * implement a circular buffer.
    651      */
    652     private int mHead;
    653 
    654     /**
    655      * The number of active rows, includes both the transcript and the screen.
    656      */
    657     private int mActiveRows;
    658 
    659     /**
    660      * The number of rows in the screen.
    661      */
    662     private int mScreenRows;
    663 
    664     /**
    665      * The data for both the screen and the transcript. The first mScreenRows *
    666      * mLineWidth characters are the screen, the rest are the transcript.
    667      * The low byte encodes the ASCII character, the high byte encodes the
    668      * foreground and background colors, plus underline and bold.
    669      */
    670     private char[] mData;
    671 
    672     /**
    673      * The data's stored as color-encoded chars, but the drawing routines require chars, so we
    674      * need a temporary buffer to hold a row's worth of characters.
    675      */
    676     private char[] mRowBuffer;
    677 
    678     /**
    679      * Flags that keep track of whether the current line logically wraps to the
    680      * next line. This is used when resizing the screen and when copying to the
    681      * clipboard or an email attachment
    682      */
    683 
    684     private boolean[] mLineWrap;
    685 
    686     /**
    687      * Create a transcript screen.
    688      *
    689      * @param columns the width of the screen in characters.
    690      * @param totalRows the height of the entire text area, in rows of text.
    691      * @param screenRows the height of just the screen, not including the
    692      *        transcript that holds lines that have scrolled off the top of the
    693      *        screen.
    694      */
    695     public TranscriptScreen(int columns, int totalRows, int screenRows,
    696             int foreColor, int backColor) {
    697         init(columns, totalRows, screenRows, foreColor, backColor);
    698     }
    699 
    700     private void init(int columns, int totalRows, int screenRows, int foreColor, int backColor) {
    701         mColumns = columns;
    702         mTotalRows = totalRows;
    703         mActiveTranscriptRows = 0;
    704         mHead = 0;
    705         mActiveRows = screenRows;
    706         mScreenRows = screenRows;
    707         int totalSize = columns * totalRows;
    708         mData = new char[totalSize];
    709         blockSet(0, 0, mColumns, mScreenRows, ' ', foreColor, backColor);
    710         mRowBuffer = new char[columns];
    711         mLineWrap = new boolean[totalRows];
    712         consistencyCheck();
    713    }
    714 
    715     /**
    716      * Convert a row value from the public external coordinate system to our
    717      * internal private coordinate system. External coordinate system:
    718      * -mActiveTranscriptRows to mScreenRows-1, with the screen being
    719      * 0..mScreenRows-1 Internal coordinate system: 0..mScreenRows-1 rows of
    720      * mData are the visible rows. mScreenRows..mActiveRows - 1 are the
    721      * transcript, stored as a circular buffer.
    722      *
    723      * @param row a row in the external coordinate system.
    724      * @return The row corresponding to the input argument in the private
    725      *         coordinate system.
    726      */
    727     private int externalToInternalRow(int row) {
    728         if (row < -mActiveTranscriptRows || row >= mScreenRows) {
    729             throw new IllegalArgumentException();
    730         }
    731         if (row >= 0) {
    732             return row; // This is a visible row.
    733         }
    734         return mScreenRows
    735                 + ((mHead + mActiveTranscriptRows + row) % mActiveTranscriptRows);
    736     }
    737 
    738     private int getOffset(int externalLine) {
    739         return externalToInternalRow(externalLine) * mColumns;
    740     }
    741 
    742     private int getOffset(int x, int y) {
    743         return getOffset(y) + x;
    744     }
    745 
    746     public void setLineWrap(int row) {
    747         mLineWrap[externalToInternalRow(row)] = true;
    748     }
    749 
    750     /**
    751      * Store byte b into the screen at location (x, y)
    752      *
    753      * @param x X coordinate (also known as column)
    754      * @param y Y coordinate (also known as row)
    755      * @param b ASCII character to store
    756      * @param foreColor the foreground color
    757      * @param backColor the background color
    758      */
    759     public void set(int x, int y, byte b, int foreColor, int backColor) {
    760         mData[getOffset(x, y)] = encode(b, foreColor, backColor);
    761     }
    762 
    763     private char encode(int b, int foreColor, int backColor) {
    764         return (char) ((foreColor << 12) | (backColor << 8) | b);
    765     }
    766 
    767     /**
    768      * Scroll the screen down one line. To scroll the whole screen of a 24 line
    769      * screen, the arguments would be (0, 24).
    770      *
    771      * @param topMargin First line that is scrolled.
    772      * @param bottomMargin One line after the last line that is scrolled.
    773      */
    774     public void scroll(int topMargin, int bottomMargin, int foreColor,
    775             int backColor) {
    776         if (topMargin > bottomMargin - 2 || topMargin > mScreenRows - 2
    777                 || bottomMargin > mScreenRows) {
    778             throw new IllegalArgumentException();
    779         }
    780 
    781         // Adjust the transcript so that the last line of the transcript
    782         // is ready to receive the newly scrolled data
    783         consistencyCheck();
    784         int expansionRows = Math.min(1, mTotalRows - mActiveRows);
    785         int rollRows = 1 - expansionRows;
    786         mActiveRows += expansionRows;
    787         mActiveTranscriptRows += expansionRows;
    788         if (mActiveTranscriptRows > 0) {
    789             mHead = (mHead + rollRows) % mActiveTranscriptRows;
    790         }
    791         consistencyCheck();
    792 
    793         // Block move the scroll line to the transcript
    794         int topOffset = getOffset(topMargin);
    795         int destOffset = getOffset(-1);
    796         System.arraycopy(mData, topOffset, mData, destOffset, mColumns);
    797 
    798         int topLine = externalToInternalRow(topMargin);
    799         int destLine = externalToInternalRow(-1);
    800         System.arraycopy(mLineWrap, topLine, mLineWrap, destLine, 1);
    801 
    802         // Block move the scrolled data up
    803         int numScrollChars = (bottomMargin - topMargin - 1) * mColumns;
    804         System.arraycopy(mData, topOffset + mColumns, mData, topOffset,
    805                 numScrollChars);
    806         int numScrollLines = (bottomMargin - topMargin - 1);
    807         System.arraycopy(mLineWrap, topLine + 1, mLineWrap, topLine,
    808                 numScrollLines);
    809 
    810         // Erase the bottom line of the scroll region
    811         blockSet(0, bottomMargin - 1, mColumns, 1, ' ', foreColor, backColor);
    812         mLineWrap[externalToInternalRow(bottomMargin-1)] = false;
    813     }
    814 
    815     private void consistencyCheck() {
    816         checkPositive(mColumns);
    817         checkPositive(mTotalRows);
    818         checkRange(0, mActiveTranscriptRows, mTotalRows);
    819         if (mActiveTranscriptRows == 0) {
    820             checkEqual(mHead, 0);
    821         } else {
    822             checkRange(0, mHead, mActiveTranscriptRows-1);
    823         }
    824         checkEqual(mScreenRows + mActiveTranscriptRows, mActiveRows);
    825         checkRange(0, mScreenRows, mTotalRows);
    826 
    827         checkEqual(mTotalRows, mLineWrap.length);
    828         checkEqual(mTotalRows*mColumns, mData.length);
    829         checkEqual(mColumns, mRowBuffer.length);
    830     }
    831 
    832     private void checkPositive(int n) {
    833         if (n < 0) {
    834             throw new IllegalArgumentException("checkPositive " + n);
    835         }
    836     }
    837 
    838     private void checkRange(int a, int b, int c) {
    839         if (a > b || b > c) {
    840             throw new IllegalArgumentException("checkRange " + a + " <= " + b + " <= " + c);
    841         }
    842     }
    843 
    844     private void checkEqual(int a, int b) {
    845         if (a != b) {
    846             throw new IllegalArgumentException("checkEqual " + a + " == " + b);
    847         }
    848     }
    849 
    850     /**
    851      * Block copy characters from one position in the screen to another. The two
    852      * positions can overlap. All characters of the source and destination must
    853      * be within the bounds of the screen, or else an InvalidParemeterException
    854      * will be thrown.
    855      *
    856      * @param sx source X coordinate
    857      * @param sy source Y coordinate
    858      * @param w width
    859      * @param h height
    860      * @param dx destination X coordinate
    861      * @param dy destination Y coordinate
    862      */
    863     public void blockCopy(int sx, int sy, int w, int h, int dx, int dy) {
    864         if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows
    865                 || dx < 0 || dx + w > mColumns || dy < 0
    866                 || dy + h > mScreenRows) {
    867             throw new IllegalArgumentException();
    868         }
    869         if (sy <= dy) {
    870             // Move in increasing order
    871             for (int y = 0; y < h; y++) {
    872                 int srcOffset = getOffset(sx, sy + y);
    873                 int dstOffset = getOffset(dx, dy + y);
    874                 System.arraycopy(mData, srcOffset, mData, dstOffset, w);
    875             }
    876         } else {
    877             // Move in decreasing order
    878             for (int y = 0; y < h; y++) {
    879                 int y2 = h - (y + 1);
    880                 int srcOffset = getOffset(sx, sy + y2);
    881                 int dstOffset = getOffset(dx, dy + y2);
    882                 System.arraycopy(mData, srcOffset, mData, dstOffset, w);
    883             }
    884         }
    885     }
    886 
    887     /**
    888      * Block set characters. All characters must be within the bounds of the
    889      * screen, or else and InvalidParemeterException will be thrown. Typically
    890      * this is called with a "val" argument of 32 to clear a block of
    891      * characters.
    892      *
    893      * @param sx source X
    894      * @param sy source Y
    895      * @param w width
    896      * @param h height
    897      * @param val value to set.
    898      */
    899     public void blockSet(int sx, int sy, int w, int h, int val,
    900             int foreColor, int backColor) {
    901         if (sx < 0 || sx + w > mColumns || sy < 0 || sy + h > mScreenRows) {
    902             throw new IllegalArgumentException();
    903         }
    904         char[] data = mData;
    905         char encodedVal = encode(val, foreColor, backColor);
    906         for (int y = 0; y < h; y++) {
    907             int offset = getOffset(sx, sy + y);
    908             for (int x = 0; x < w; x++) {
    909                 data[offset + x] = encodedVal;
    910             }
    911         }
    912     }
    913 
    914     /**
    915      * Draw a row of text. Out-of-bounds rows are blank, not errors.
    916      *
    917      * @param row The row of text to draw.
    918      * @param canvas The canvas to draw to.
    919      * @param x The x coordinate origin of the drawing
    920      * @param y The y coordinate origin of the drawing
    921      * @param renderer The renderer to use to draw the text
    922      * @param cx the cursor X coordinate, -1 means don't draw it
    923      */
    924     public final void drawText(int row, Canvas canvas, float x, float y,
    925             TextRenderer renderer, int cx) {
    926 
    927         // Out-of-bounds rows are blank.
    928         if (row < -mActiveTranscriptRows || row >= mScreenRows) {
    929             return;
    930         }
    931 
    932         // Copy the data from the byte array to a char array so they can
    933         // be drawn.
    934 
    935         int offset = getOffset(row);
    936         char[] rowBuffer = mRowBuffer;
    937         char[] data = mData;
    938         int columns = mColumns;
    939         int lastColors = 0;
    940         int lastRunStart = -1;
    941         final int CURSOR_MASK = 0x10000;
    942         for (int i = 0; i < columns; i++) {
    943             char c = data[offset + i];
    944             int colors = (char) (c & 0xff00);
    945             if (cx == i) {
    946                 // Set cursor background color:
    947                 colors |= CURSOR_MASK;
    948             }
    949             rowBuffer[i] = (char) (c & 0x00ff);
    950             if (colors != lastColors) {
    951                 if (lastRunStart >= 0) {
    952                     renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
    953                             lastRunStart, i - lastRunStart,
    954                             (lastColors & CURSOR_MASK) != 0,
    955                             0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
    956                 }
    957                 lastColors = colors;
    958                 lastRunStart = i;
    959             }
    960         }
    961         if (lastRunStart >= 0) {
    962             renderer.drawTextRun(canvas, x, y, lastRunStart, rowBuffer,
    963                     lastRunStart, columns - lastRunStart,
    964                     (lastColors & CURSOR_MASK) != 0,
    965                     0xf & (lastColors >> 12), 0xf & (lastColors >> 8));
    966         }
    967      }
    968 
    969     /**
    970      * Get the count of active rows.
    971      *
    972      * @return the count of active rows.
    973      */
    974     public int getActiveRows() {
    975         return mActiveRows;
    976     }
    977 
    978     /**
    979      * Get the count of active transcript rows.
    980      *
    981      * @return the count of active transcript rows.
    982      */
    983     public int getActiveTranscriptRows() {
    984         return mActiveTranscriptRows;
    985     }
    986 
    987     public String getTranscriptText() {
    988         return internalGetTranscriptText(true);
    989     }
    990 
    991     private String internalGetTranscriptText(boolean stripColors) {
    992         StringBuilder builder = new StringBuilder();
    993         char[] rowBuffer = mRowBuffer;
    994         char[] data = mData;
    995         int columns = mColumns;
    996         for (int row = -mActiveTranscriptRows; row < mScreenRows; row++) {
    997             int offset = getOffset(row);
    998             int lastPrintingChar = -1;
    999             for (int column = 0; column < columns; column++) {
   1000                 char c = data[offset + column];
   1001                 if (stripColors) {
   1002                     c = (char) (c & 0xff);
   1003                 }
   1004                 if ((c & 0xff) != ' ') {
   1005                     lastPrintingChar = column;
   1006                 }
   1007                 rowBuffer[column] = c;
   1008             }
   1009             if (mLineWrap[externalToInternalRow(row)]) {
   1010                 builder.append(rowBuffer, 0, columns);
   1011             } else {
   1012                 builder.append(rowBuffer, 0, lastPrintingChar + 1);
   1013                 builder.append('\n');
   1014             }
   1015         }
   1016         return builder.toString();
   1017     }
   1018 
   1019     public void resize(int columns, int rows, int foreColor, int backColor) {
   1020         init(columns, mTotalRows, rows, foreColor, backColor);
   1021     }
   1022 }
   1023 
   1024 /**
   1025  * Renders text into a screen. Contains all the terminal-specific knowlege and
   1026  * state. Emulates a subset of the X Window System xterm terminal, which in turn
   1027  * is an emulator for a subset of the Digital Equipment Corporation vt100
   1028  * terminal. Missing functionality: text attributes (bold, underline, reverse
   1029  * video, color) alternate screen cursor key and keypad escape sequences.
   1030  */
   1031 class TerminalEmulator {
   1032 
   1033     /**
   1034      * The cursor row. Numbered 0..mRows-1.
   1035      */
   1036     private int mCursorRow;
   1037 
   1038     /**
   1039      * The cursor column. Numbered 0..mColumns-1.
   1040      */
   1041     private int mCursorCol;
   1042 
   1043     /**
   1044      * The number of character rows in the terminal screen.
   1045      */
   1046     private int mRows;
   1047 
   1048     /**
   1049      * The number of character columns in the terminal screen.
   1050      */
   1051     private int mColumns;
   1052 
   1053     /**
   1054      * Used to send data to the remote process. Needed to implement the various
   1055      * "report" escape sequences.
   1056      */
   1057     private FileOutputStream mTermOut;
   1058 
   1059     /**
   1060      * Stores the characters that appear on the screen of the emulated terminal.
   1061      */
   1062     private Screen mScreen;
   1063 
   1064     /**
   1065      * Keeps track of the current argument of the current escape sequence.
   1066      * Ranges from 0 to MAX_ESCAPE_PARAMETERS-1. (Typically just 0 or 1.)
   1067      */
   1068     private int mArgIndex;
   1069 
   1070     /**
   1071      * The number of parameter arguments. This name comes from the ANSI standard
   1072      * for terminal escape codes.
   1073      */
   1074     private static final int MAX_ESCAPE_PARAMETERS = 16;
   1075 
   1076     /**
   1077      * Holds the arguments of the current escape sequence.
   1078      */
   1079     private int[] mArgs = new int[MAX_ESCAPE_PARAMETERS];
   1080 
   1081     // Escape processing states:
   1082 
   1083     /**
   1084      * Escape processing state: Not currently in an escape sequence.
   1085      */
   1086     private static final int ESC_NONE = 0;
   1087 
   1088     /**
   1089      * Escape processing state: Have seen an ESC character
   1090      */
   1091     private static final int ESC = 1;
   1092 
   1093     /**
   1094      * Escape processing state: Have seen ESC POUND
   1095      */
   1096     private static final int ESC_POUND = 2;
   1097 
   1098     /**
   1099      * Escape processing state: Have seen ESC and a character-set-select char
   1100      */
   1101     private static final int ESC_SELECT_LEFT_PAREN = 3;
   1102 
   1103     /**
   1104      * Escape processing state: Have seen ESC and a character-set-select char
   1105      */
   1106     private static final int ESC_SELECT_RIGHT_PAREN = 4;
   1107 
   1108     /**
   1109      * Escape processing state: ESC [
   1110      */
   1111     private static final int ESC_LEFT_SQUARE_BRACKET = 5;
   1112 
   1113     /**
   1114      * Escape processing state: ESC [ ?
   1115      */
   1116     private static final int ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK = 6;
   1117 
   1118     /**
   1119      * True if the current escape sequence should continue, false if the current
   1120      * escape sequence should be terminated. Used when parsing a single
   1121      * character.
   1122      */
   1123     private boolean mContinueSequence;
   1124 
   1125     /**
   1126      * The current state of the escape sequence state machine.
   1127      */
   1128     private int mEscapeState;
   1129 
   1130     /**
   1131      * Saved state of the cursor row, Used to implement the save/restore cursor
   1132      * position escape sequences.
   1133      */
   1134     private int mSavedCursorRow;
   1135 
   1136     /**
   1137      * Saved state of the cursor column, Used to implement the save/restore
   1138      * cursor position escape sequences.
   1139      */
   1140     private int mSavedCursorCol;
   1141 
   1142     // DecSet booleans
   1143 
   1144     /**
   1145      * This mask indicates 132-column mode is set. (As opposed to 80-column
   1146      * mode.)
   1147      */
   1148     private static final int K_132_COLUMN_MODE_MASK = 1 << 3;
   1149 
   1150     /**
   1151      * This mask indicates that origin mode is set. (Cursor addressing is
   1152      * relative to the absolute screen size, rather than the currently set top
   1153      * and bottom margins.)
   1154      */
   1155     private static final int K_ORIGIN_MODE_MASK = 1 << 6;
   1156 
   1157     /**
   1158      * This mask indicates that wraparound mode is set. (As opposed to
   1159      * stop-at-right-column mode.)
   1160      */
   1161     private static final int K_WRAPAROUND_MODE_MASK = 1 << 7;
   1162 
   1163     /**
   1164      * Holds multiple DECSET flags. The data is stored this way, rather than in
   1165      * separate booleans, to make it easier to implement the save-and-restore
   1166      * semantics. The various k*ModeMask masks can be used to extract and modify
   1167      * the individual flags current states.
   1168      */
   1169     private int mDecFlags;
   1170 
   1171     /**
   1172      * Saves away a snapshot of the DECSET flags. Used to implement save and
   1173      * restore escape sequences.
   1174      */
   1175     private int mSavedDecFlags;
   1176 
   1177     // Modes set with Set Mode / Reset Mode
   1178 
   1179     /**
   1180      * True if insert mode (as opposed to replace mode) is active. In insert
   1181      * mode new characters are inserted, pushing existing text to the right.
   1182      */
   1183     private boolean mInsertMode;
   1184 
   1185     /**
   1186      * Automatic newline mode. Configures whether pressing return on the
   1187      * keyboard automatically generates a return as well. Not currently
   1188      * implemented.
   1189      */
   1190     private boolean mAutomaticNewlineMode;
   1191 
   1192     /**
   1193      * An array of tab stops. mTabStop[i] is true if there is a tab stop set for
   1194      * column i.
   1195      */
   1196     private boolean[] mTabStop;
   1197 
   1198     // The margins allow portions of the screen to be locked.
   1199 
   1200     /**
   1201      * The top margin of the screen, for scrolling purposes. Ranges from 0 to
   1202      * mRows-2.
   1203      */
   1204     private int mTopMargin;
   1205 
   1206     /**
   1207      * The bottom margin of the screen, for scrolling purposes. Ranges from
   1208      * mTopMargin + 2 to mRows. (Defines the first row after the scrolling
   1209      * region.
   1210      */
   1211     private int mBottomMargin;
   1212 
   1213     /**
   1214      * True if the next character to be emitted will be automatically wrapped to
   1215      * the next line. Used to disambiguate the case where the cursor is
   1216      * positioned on column mColumns-1.
   1217      */
   1218     private boolean mAboutToAutoWrap;
   1219 
   1220     /**
   1221      * Used for debugging, counts how many chars have been processed.
   1222      */
   1223     private int mProcessedCharCount;
   1224 
   1225     /**
   1226      * Foreground color, 0..7, mask with 8 for bold
   1227      */
   1228     private int mForeColor;
   1229 
   1230     /**
   1231      * Background color, 0..7, mask with 8 for underline
   1232      */
   1233     private int mBackColor;
   1234 
   1235     private boolean mInverseColors;
   1236 
   1237     private boolean mbKeypadApplicationMode;
   1238 
   1239     private boolean mAlternateCharSet;
   1240 
   1241     /**
   1242      * Construct a terminal emulator that uses the supplied screen
   1243      *
   1244      * @param screen the screen to render characters into.
   1245      * @param columns the number of columns to emulate
   1246      * @param rows the number of rows to emulate
   1247      * @param termOut the output file descriptor that talks to the pseudo-tty.
   1248      */
   1249     public TerminalEmulator(Screen screen, int columns, int rows,
   1250             FileOutputStream termOut) {
   1251         mScreen = screen;
   1252         mRows = rows;
   1253         mColumns = columns;
   1254         mTabStop = new boolean[mColumns];
   1255         mTermOut = termOut;
   1256         reset();
   1257     }
   1258 
   1259     public void updateSize(int columns, int rows) {
   1260         if (mRows == rows && mColumns == columns) {
   1261             return;
   1262         }
   1263         String transcriptText = mScreen.getTranscriptText();
   1264 
   1265         mScreen.resize(columns, rows, mForeColor, mBackColor);
   1266 
   1267         if (mRows != rows) {
   1268             mRows = rows;
   1269             mTopMargin = 0;
   1270             mBottomMargin = mRows;
   1271         }
   1272         if (mColumns != columns) {
   1273             int oldColumns = mColumns;
   1274             mColumns = columns;
   1275             boolean[] oldTabStop = mTabStop;
   1276             mTabStop = new boolean[mColumns];
   1277             int toTransfer = Math.min(oldColumns, columns);
   1278             System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer);
   1279             while (mCursorCol >= columns) {
   1280                 mCursorCol -= columns;
   1281                 mCursorRow = Math.min(mBottomMargin-1, mCursorRow + 1);
   1282             }
   1283         }
   1284         mCursorRow = 0;
   1285         mCursorCol = 0;
   1286         mAboutToAutoWrap = false;
   1287 
   1288         int end = transcriptText.length()-1;
   1289         while ((end >= 0) && transcriptText.charAt(end) == '\n') {
   1290             end--;
   1291         }
   1292         for(int i = 0; i <= end; i++) {
   1293             byte c = (byte) transcriptText.charAt(i);
   1294             if (c == '\n') {
   1295                 setCursorCol(0);
   1296                 doLinefeed();
   1297             } else {
   1298                 emit(c);
   1299             }
   1300         }
   1301     }
   1302 
   1303     /**
   1304      * Get the cursor's current row.
   1305      *
   1306      * @return the cursor's current row.
   1307      */
   1308     public final int getCursorRow() {
   1309         return mCursorRow;
   1310     }
   1311 
   1312     /**
   1313      * Get the cursor's current column.
   1314      *
   1315      * @return the cursor's current column.
   1316      */
   1317     public final int getCursorCol() {
   1318         return mCursorCol;
   1319     }
   1320 
   1321     public final boolean getKeypadApplicationMode() {
   1322         return mbKeypadApplicationMode;
   1323     }
   1324 
   1325     private void setDefaultTabStops() {
   1326         for (int i = 0; i < mColumns; i++) {
   1327             mTabStop[i] = (i & 7) == 0 && i != 0;
   1328         }
   1329     }
   1330 
   1331     /**
   1332      * Accept bytes (typically from the pseudo-teletype) and process them.
   1333      *
   1334      * @param buffer a byte array containing the bytes to be processed
   1335      * @param base the first index of the array to process
   1336      * @param length the number of bytes in the array to process
   1337      */
   1338     public void append(byte[] buffer, int base, int length) {
   1339         for (int i = 0; i < length; i++) {
   1340             byte b = buffer[base + i];
   1341             try {
   1342                 if (Term.LOG_CHARACTERS_FLAG) {
   1343                     char printableB = (char) b;
   1344                     if (b < 32 || b > 126) {
   1345                         printableB = ' ';
   1346                     }
   1347                     Log.w(Term.LOG_TAG, "'" + Character.toString(printableB)
   1348                             + "' (" + Integer.toString(b) + ")");
   1349                 }
   1350                 process(b);
   1351                 mProcessedCharCount++;
   1352             } catch (Exception e) {
   1353                 Log.e(Term.LOG_TAG, "Exception while processing character "
   1354                         + Integer.toString(mProcessedCharCount) + " code "
   1355                         + Integer.toString(b), e);
   1356             }
   1357         }
   1358     }
   1359 
   1360     private void process(byte b) {
   1361         switch (b) {
   1362         case 0: // NUL
   1363             // Do nothing
   1364             break;
   1365 
   1366         case 7: // BEL
   1367             // Do nothing
   1368             break;
   1369 
   1370         case 8: // BS
   1371             setCursorCol(Math.max(0, mCursorCol - 1));
   1372             break;
   1373 
   1374         case 9: // HT
   1375             // Move to next tab stop, but not past edge of screen
   1376             setCursorCol(nextTabStop(mCursorCol));
   1377             break;
   1378 
   1379         case 13:
   1380             setCursorCol(0);
   1381             break;
   1382 
   1383         case 10: // CR
   1384         case 11: // VT
   1385         case 12: // LF
   1386             doLinefeed();
   1387             break;
   1388 
   1389         case 14: // SO:
   1390             setAltCharSet(true);
   1391             break;
   1392 
   1393         case 15: // SI:
   1394             setAltCharSet(false);
   1395             break;
   1396 
   1397 
   1398         case 24: // CAN
   1399         case 26: // SUB
   1400             if (mEscapeState != ESC_NONE) {
   1401                 mEscapeState = ESC_NONE;
   1402                 emit((byte) 127);
   1403             }
   1404             break;
   1405 
   1406         case 27: // ESC
   1407             // Always starts an escape sequence
   1408             startEscapeSequence(ESC);
   1409             break;
   1410 
   1411         case (byte) 0x9b: // CSI
   1412             startEscapeSequence(ESC_LEFT_SQUARE_BRACKET);
   1413             break;
   1414 
   1415         default:
   1416             mContinueSequence = false;
   1417             switch (mEscapeState) {
   1418             case ESC_NONE:
   1419                 if (b >= 32) {
   1420                     emit(b);
   1421                 }
   1422                 break;
   1423 
   1424             case ESC:
   1425                 doEsc(b);
   1426                 break;
   1427 
   1428             case ESC_POUND:
   1429                 doEscPound(b);
   1430                 break;
   1431 
   1432             case ESC_SELECT_LEFT_PAREN:
   1433                 doEscSelectLeftParen(b);
   1434                 break;
   1435 
   1436             case ESC_SELECT_RIGHT_PAREN:
   1437                 doEscSelectRightParen(b);
   1438                 break;
   1439 
   1440             case ESC_LEFT_SQUARE_BRACKET:
   1441                 doEscLeftSquareBracket(b);
   1442                 break;
   1443 
   1444             case ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK:
   1445                 doEscLSBQuest(b);
   1446                 break;
   1447 
   1448             default:
   1449                 unknownSequence(b);
   1450                 break;
   1451             }
   1452             if (!mContinueSequence) {
   1453                 mEscapeState = ESC_NONE;
   1454             }
   1455             break;
   1456         }
   1457     }
   1458 
   1459     private void setAltCharSet(boolean alternateCharSet) {
   1460         mAlternateCharSet = alternateCharSet;
   1461     }
   1462 
   1463     private int nextTabStop(int cursorCol) {
   1464         for (int i = cursorCol; i < mColumns; i++) {
   1465             if (mTabStop[i]) {
   1466                 return i;
   1467             }
   1468         }
   1469         return mColumns - 1;
   1470     }
   1471 
   1472     private void doEscLSBQuest(byte b) {
   1473         int mask = getDecFlagsMask(getArg0(0));
   1474         switch (b) {
   1475         case 'h': // Esc [ ? Pn h - DECSET
   1476             mDecFlags |= mask;
   1477             break;
   1478 
   1479         case 'l': // Esc [ ? Pn l - DECRST
   1480             mDecFlags &= ~mask;
   1481             break;
   1482 
   1483         case 'r': // Esc [ ? Pn r - restore
   1484             mDecFlags = (mDecFlags & ~mask) | (mSavedDecFlags & mask);
   1485             break;
   1486 
   1487         case 's': // Esc [ ? Pn s - save
   1488             mSavedDecFlags = (mSavedDecFlags & ~mask) | (mDecFlags & mask);
   1489             break;
   1490 
   1491         default:
   1492             parseArg(b);
   1493             break;
   1494         }
   1495 
   1496         // 132 column mode
   1497         if ((mask & K_132_COLUMN_MODE_MASK) != 0) {
   1498             // We don't actually set 132 cols, but we do want the
   1499             // side effect of clearing the screen and homing the cursor.
   1500             blockClear(0, 0, mColumns, mRows);
   1501             setCursorRowCol(0, 0);
   1502         }
   1503 
   1504         // origin mode
   1505         if ((mask & K_ORIGIN_MODE_MASK) != 0) {
   1506             // Home the cursor.
   1507             setCursorPosition(0, 0);
   1508         }
   1509     }
   1510 
   1511     private int getDecFlagsMask(int argument) {
   1512         if (argument >= 1 && argument <= 9) {
   1513             return (1 << argument);
   1514         }
   1515 
   1516         return 0;
   1517     }
   1518 
   1519     private void startEscapeSequence(int escapeState) {
   1520         mEscapeState = escapeState;
   1521         mArgIndex = 0;
   1522         for (int j = 0; j < MAX_ESCAPE_PARAMETERS; j++) {
   1523             mArgs[j] = -1;
   1524         }
   1525     }
   1526 
   1527     private void doLinefeed() {
   1528         int newCursorRow = mCursorRow + 1;
   1529         if (newCursorRow >= mBottomMargin) {
   1530             scroll();
   1531             newCursorRow = mBottomMargin - 1;
   1532         }
   1533         setCursorRow(newCursorRow);
   1534     }
   1535 
   1536     private void continueSequence() {
   1537         mContinueSequence = true;
   1538     }
   1539 
   1540     private void continueSequence(int state) {
   1541         mEscapeState = state;
   1542         mContinueSequence = true;
   1543     }
   1544 
   1545     private void doEscSelectLeftParen(byte b) {
   1546         doSelectCharSet(true, b);
   1547     }
   1548 
   1549     private void doEscSelectRightParen(byte b) {
   1550         doSelectCharSet(false, b);
   1551     }
   1552 
   1553     private void doSelectCharSet(boolean isG0CharSet, byte b) {
   1554         switch (b) {
   1555         case 'A': // United Kingdom character set
   1556             break;
   1557         case 'B': // ASCII set
   1558             break;
   1559         case '0': // Special Graphics
   1560             break;
   1561         case '1': // Alternate character set
   1562             break;
   1563         case '2':
   1564             break;
   1565         default:
   1566             unknownSequence(b);
   1567         }
   1568     }
   1569 
   1570     private void doEscPound(byte b) {
   1571         switch (b) {
   1572         case '8': // Esc # 8 - DECALN alignment test
   1573             mScreen.blockSet(0, 0, mColumns, mRows, 'E',
   1574                     getForeColor(), getBackColor());
   1575             break;
   1576 
   1577         default:
   1578             unknownSequence(b);
   1579             break;
   1580         }
   1581     }
   1582 
   1583     private void doEsc(byte b) {
   1584         switch (b) {
   1585         case '#':
   1586             continueSequence(ESC_POUND);
   1587             break;
   1588 
   1589         case '(':
   1590             continueSequence(ESC_SELECT_LEFT_PAREN);
   1591             break;
   1592 
   1593         case ')':
   1594             continueSequence(ESC_SELECT_RIGHT_PAREN);
   1595             break;
   1596 
   1597         case '7': // DECSC save cursor
   1598             mSavedCursorRow = mCursorRow;
   1599             mSavedCursorCol = mCursorCol;
   1600             break;
   1601 
   1602         case '8': // DECRC restore cursor
   1603             setCursorRowCol(mSavedCursorRow, mSavedCursorCol);
   1604             break;
   1605 
   1606         case 'D': // INDEX
   1607             doLinefeed();
   1608             break;
   1609 
   1610         case 'E': // NEL
   1611             setCursorCol(0);
   1612             doLinefeed();
   1613             break;
   1614 
   1615         case 'F': // Cursor to lower-left corner of screen
   1616             setCursorRowCol(0, mBottomMargin - 1);
   1617             break;
   1618 
   1619         case 'H': // Tab set
   1620             mTabStop[mCursorCol] = true;
   1621             break;
   1622 
   1623         case 'M': // Reverse index
   1624             if (mCursorRow == 0) {
   1625                 mScreen.blockCopy(0, mTopMargin + 1, mColumns, mBottomMargin
   1626                         - (mTopMargin + 1), 0, mTopMargin);
   1627                 blockClear(0, mBottomMargin - 1, mColumns);
   1628             } else {
   1629                 mCursorRow--;
   1630             }
   1631 
   1632             break;
   1633 
   1634         case 'N': // SS2
   1635             unimplementedSequence(b);
   1636             break;
   1637 
   1638         case '0': // SS3
   1639             unimplementedSequence(b);
   1640             break;
   1641 
   1642         case 'P': // Device control string
   1643             unimplementedSequence(b);
   1644             break;
   1645 
   1646         case 'Z': // return terminal ID
   1647             sendDeviceAttributes();
   1648             break;
   1649 
   1650         case '[':
   1651             continueSequence(ESC_LEFT_SQUARE_BRACKET);
   1652             break;
   1653 
   1654         case '=': // DECKPAM
   1655             mbKeypadApplicationMode = true;
   1656             break;
   1657 
   1658         case '>' : // DECKPNM
   1659             mbKeypadApplicationMode = false;
   1660             break;
   1661 
   1662         default:
   1663             unknownSequence(b);
   1664             break;
   1665         }
   1666     }
   1667 
   1668     private void doEscLeftSquareBracket(byte b) {
   1669         switch (b) {
   1670         case '@': // ESC [ Pn @ - ICH Insert Characters
   1671         {
   1672             int charsAfterCursor = mColumns - mCursorCol;
   1673             int charsToInsert = Math.min(getArg0(1), charsAfterCursor);
   1674             int charsToMove = charsAfterCursor - charsToInsert;
   1675             mScreen.blockCopy(mCursorCol, mCursorRow, charsToMove, 1,
   1676                     mCursorCol + charsToInsert, mCursorRow);
   1677             blockClear(mCursorCol, mCursorRow, charsToInsert);
   1678         }
   1679             break;
   1680 
   1681         case 'A': // ESC [ Pn A - Cursor Up
   1682             setCursorRow(Math.max(mTopMargin, mCursorRow - getArg0(1)));
   1683             break;
   1684 
   1685         case 'B': // ESC [ Pn B - Cursor Down
   1686             setCursorRow(Math.min(mBottomMargin - 1, mCursorRow + getArg0(1)));
   1687             break;
   1688 
   1689         case 'C': // ESC [ Pn C - Cursor Right
   1690             setCursorCol(Math.min(mColumns - 1, mCursorCol + getArg0(1)));
   1691             break;
   1692 
   1693         case 'D': // ESC [ Pn D - Cursor Left
   1694             setCursorCol(Math.max(0, mCursorCol - getArg0(1)));
   1695             break;
   1696 
   1697         case 'G': // ESC [ Pn G - Cursor Horizontal Absolute
   1698             setCursorCol(Math.min(Math.max(1, getArg0(1)), mColumns) - 1);
   1699             break;
   1700 
   1701         case 'H': // ESC [ Pn ; H - Cursor Position
   1702             setHorizontalVerticalPosition();
   1703             break;
   1704 
   1705         case 'J': // ESC [ Pn J - Erase in Display
   1706             switch (getArg0(0)) {
   1707             case 0: // Clear below
   1708                 blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
   1709                 blockClear(0, mCursorRow + 1, mColumns,
   1710                         mBottomMargin - (mCursorRow + 1));
   1711                 break;
   1712 
   1713             case 1: // Erase from the start of the screen to the cursor.
   1714                 blockClear(0, mTopMargin, mColumns, mCursorRow - mTopMargin);
   1715                 blockClear(0, mCursorRow, mCursorCol + 1);
   1716                 break;
   1717 
   1718             case 2: // Clear all
   1719                 blockClear(0, mTopMargin, mColumns, mBottomMargin - mTopMargin);
   1720                 break;
   1721 
   1722             default:
   1723                 unknownSequence(b);
   1724                 break;
   1725             }
   1726             break;
   1727 
   1728         case 'K': // ESC [ Pn K - Erase in Line
   1729             switch (getArg0(0)) {
   1730             case 0: // Clear to right
   1731                 blockClear(mCursorCol, mCursorRow, mColumns - mCursorCol);
   1732                 break;
   1733 
   1734             case 1: // Erase start of line to cursor (including cursor)
   1735                 blockClear(0, mCursorRow, mCursorCol + 1);
   1736                 break;
   1737 
   1738             case 2: // Clear whole line
   1739                 blockClear(0, mCursorRow, mColumns);
   1740                 break;
   1741 
   1742             default:
   1743                 unknownSequence(b);
   1744                 break;
   1745             }
   1746             break;
   1747 
   1748         case 'L': // Insert Lines
   1749         {
   1750             int linesAfterCursor = mBottomMargin - mCursorRow;
   1751             int linesToInsert = Math.min(getArg0(1), linesAfterCursor);
   1752             int linesToMove = linesAfterCursor - linesToInsert;
   1753             mScreen.blockCopy(0, mCursorRow, mColumns, linesToMove, 0,
   1754                     mCursorRow + linesToInsert);
   1755             blockClear(0, mCursorRow, mColumns, linesToInsert);
   1756         }
   1757             break;
   1758 
   1759         case 'M': // Delete Lines
   1760         {
   1761             int linesAfterCursor = mBottomMargin - mCursorRow;
   1762             int linesToDelete = Math.min(getArg0(1), linesAfterCursor);
   1763             int linesToMove = linesAfterCursor - linesToDelete;
   1764             mScreen.blockCopy(0, mCursorRow + linesToDelete, mColumns,
   1765                     linesToMove, 0, mCursorRow);
   1766             blockClear(0, mCursorRow + linesToMove, mColumns, linesToDelete);
   1767         }
   1768             break;
   1769 
   1770         case 'P': // Delete Characters
   1771         {
   1772             int charsAfterCursor = mColumns - mCursorCol;
   1773             int charsToDelete = Math.min(getArg0(1), charsAfterCursor);
   1774             int charsToMove = charsAfterCursor - charsToDelete;
   1775             mScreen.blockCopy(mCursorCol + charsToDelete, mCursorRow,
   1776                     charsToMove, 1, mCursorCol, mCursorRow);
   1777             blockClear(mCursorCol + charsToMove, mCursorRow, charsToDelete);
   1778         }
   1779             break;
   1780 
   1781         case 'T': // Mouse tracking
   1782             unimplementedSequence(b);
   1783             break;
   1784 
   1785         case '?': // Esc [ ? -- start of a private mode set
   1786             continueSequence(ESC_LEFT_SQUARE_BRACKET_QUESTION_MARK);
   1787             break;
   1788 
   1789         case 'c': // Send device attributes
   1790             sendDeviceAttributes();
   1791             break;
   1792 
   1793         case 'd': // ESC [ Pn d - Vert Position Absolute
   1794             setCursorRow(Math.min(Math.max(1, getArg0(1)), mRows) - 1);
   1795             break;
   1796 
   1797         case 'f': // Horizontal and Vertical Position
   1798             setHorizontalVerticalPosition();
   1799             break;
   1800 
   1801         case 'g': // Clear tab stop
   1802             switch (getArg0(0)) {
   1803             case 0:
   1804                 mTabStop[mCursorCol] = false;
   1805                 break;
   1806 
   1807             case 3:
   1808                 for (int i = 0; i < mColumns; i++) {
   1809                     mTabStop[i] = false;
   1810                 }
   1811                 break;
   1812 
   1813             default:
   1814                 // Specified to have no effect.
   1815                 break;
   1816             }
   1817             break;
   1818 
   1819         case 'h': // Set Mode
   1820             doSetMode(true);
   1821             break;
   1822 
   1823         case 'l': // Reset Mode
   1824             doSetMode(false);
   1825             break;
   1826 
   1827         case 'm': // Esc [ Pn m - character attributes.
   1828             selectGraphicRendition();
   1829             break;
   1830 
   1831         case 'r': // Esc [ Pn ; Pn r - set top and bottom margins
   1832         {
   1833             // The top margin defaults to 1, the bottom margin
   1834             // (unusually for arguments) defaults to mRows.
   1835             //
   1836             // The escape sequence numbers top 1..23, but we
   1837             // number top 0..22.
   1838             // The escape sequence numbers bottom 2..24, and
   1839             // so do we (because we use a zero based numbering
   1840             // scheme, but we store the first line below the
   1841             // bottom-most scrolling line.
   1842             // As a result, we adjust the top line by -1, but
   1843             // we leave the bottom line alone.
   1844             //
   1845             // Also require that top + 2 <= bottom
   1846 
   1847             int top = Math.max(0, Math.min(getArg0(1) - 1, mRows - 2));
   1848             int bottom = Math.max(top + 2, Math.min(getArg1(mRows), mRows));
   1849             mTopMargin = top;
   1850             mBottomMargin = bottom;
   1851 
   1852             // The cursor is placed in the home position
   1853             setCursorRowCol(mTopMargin, 0);
   1854         }
   1855             break;
   1856 
   1857         default:
   1858             parseArg(b);
   1859             break;
   1860         }
   1861     }
   1862 
   1863     private void selectGraphicRendition() {
   1864         for (int i = 0; i <= mArgIndex; i++) {
   1865             int code = mArgs[i];
   1866             if ( code < 0) {
   1867                 if (mArgIndex > 0) {
   1868                     continue;
   1869                 } else {
   1870                     code = 0;
   1871                 }
   1872             }
   1873             if (code == 0) { // reset
   1874                 mInverseColors = false;
   1875                 mForeColor = 7;
   1876                 mBackColor = 0;
   1877             } else if (code == 1) { // bold
   1878                 mForeColor |= 0x8;
   1879             } else if (code == 4) { // underscore
   1880                 mBackColor |= 0x8;
   1881             } else if (code == 7) { // inverse
   1882                 mInverseColors = true;
   1883             } else if (code >= 30 && code <= 37) { // foreground color
   1884                 mForeColor = (mForeColor & 0x8) | (code - 30);
   1885             } else if (code >= 40 && code <= 47) { // background color
   1886                 mBackColor = (mBackColor & 0x8) | (code - 40);
   1887             } else {
   1888                 if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
   1889                     Log.w(Term.LOG_TAG, String.format("SGR unknown code %d", code));
   1890                 }
   1891             }
   1892         }
   1893     }
   1894 
   1895     private void blockClear(int sx, int sy, int w) {
   1896         blockClear(sx, sy, w, 1);
   1897     }
   1898 
   1899     private void blockClear(int sx, int sy, int w, int h) {
   1900         mScreen.blockSet(sx, sy, w, h, ' ', getForeColor(), getBackColor());
   1901     }
   1902 
   1903     private int getForeColor() {
   1904         return mInverseColors ?
   1905                 ((mBackColor & 0x7) | (mForeColor & 0x8)) : mForeColor;
   1906     }
   1907 
   1908     private int getBackColor() {
   1909         return mInverseColors ?
   1910                 ((mForeColor & 0x7) | (mBackColor & 0x8)) : mBackColor;
   1911     }
   1912 
   1913     private void doSetMode(boolean newValue) {
   1914         int modeBit = getArg0(0);
   1915         switch (modeBit) {
   1916         case 4:
   1917             mInsertMode = newValue;
   1918             break;
   1919 
   1920         case 20:
   1921             mAutomaticNewlineMode = newValue;
   1922             break;
   1923 
   1924         default:
   1925             unknownParameter(modeBit);
   1926             break;
   1927         }
   1928     }
   1929 
   1930     private void setHorizontalVerticalPosition() {
   1931 
   1932         // Parameters are Row ; Column
   1933 
   1934         setCursorPosition(getArg1(1) - 1, getArg0(1) - 1);
   1935     }
   1936 
   1937     private void setCursorPosition(int x, int y) {
   1938         int effectiveTopMargin = 0;
   1939         int effectiveBottomMargin = mRows;
   1940         if ((mDecFlags & K_ORIGIN_MODE_MASK) != 0) {
   1941             effectiveTopMargin = mTopMargin;
   1942             effectiveBottomMargin = mBottomMargin;
   1943         }
   1944         int newRow =
   1945                 Math.max(effectiveTopMargin, Math.min(effectiveTopMargin + y,
   1946                         effectiveBottomMargin - 1));
   1947         int newCol = Math.max(0, Math.min(x, mColumns - 1));
   1948         setCursorRowCol(newRow, newCol);
   1949     }
   1950 
   1951     private void sendDeviceAttributes() {
   1952         // This identifies us as a DEC vt100 with advanced
   1953         // video options. This is what the xterm terminal
   1954         // emulator sends.
   1955         byte[] attributes =
   1956                 {
   1957                 /* VT100 */
   1958                  (byte) 27, (byte) '[', (byte) '?', (byte) '1',
   1959                  (byte) ';', (byte) '2', (byte) 'c'
   1960 
   1961                 /* VT220
   1962                 (byte) 27, (byte) '[', (byte) '?', (byte) '6',
   1963                 (byte) '0',  (byte) ';',
   1964                 (byte) '1',  (byte) ';',
   1965                 (byte) '2',  (byte) ';',
   1966                 (byte) '6',  (byte) ';',
   1967                 (byte) '8',  (byte) ';',
   1968                 (byte) '9',  (byte) ';',
   1969                 (byte) '1',  (byte) '5', (byte) ';',
   1970                 (byte) 'c'
   1971                 */
   1972                 };
   1973 
   1974         write(attributes);
   1975     }
   1976 
   1977     /**
   1978      * Send data to the shell process
   1979      * @param data
   1980      */
   1981     private void write(byte[] data) {
   1982         try {
   1983             mTermOut.write(data);
   1984             mTermOut.flush();
   1985         } catch (IOException e) {
   1986             // Ignore exception
   1987             // We don't really care if the receiver isn't listening.
   1988             // We just make a best effort to answer the query.
   1989         }
   1990     }
   1991 
   1992     private void scroll() {
   1993         mScreen.scroll(mTopMargin, mBottomMargin,
   1994                 getForeColor(), getBackColor());
   1995     }
   1996 
   1997     /**
   1998      * Process the next ASCII character of a parameter.
   1999      *
   2000      * @param b The next ASCII character of the paramater sequence.
   2001      */
   2002     private void parseArg(byte b) {
   2003         if (b >= '0' && b <= '9') {
   2004             if (mArgIndex < mArgs.length) {
   2005                 int oldValue = mArgs[mArgIndex];
   2006                 int thisDigit = b - '0';
   2007                 int value;
   2008                 if (oldValue >= 0) {
   2009                     value = oldValue * 10 + thisDigit;
   2010                 } else {
   2011                     value = thisDigit;
   2012                 }
   2013                 mArgs[mArgIndex] = value;
   2014             }
   2015             continueSequence();
   2016         } else if (b == ';') {
   2017             if (mArgIndex < mArgs.length) {
   2018                 mArgIndex++;
   2019             }
   2020             continueSequence();
   2021         } else {
   2022             unknownSequence(b);
   2023         }
   2024     }
   2025 
   2026     private int getArg0(int defaultValue) {
   2027         return getArg(0, defaultValue);
   2028     }
   2029 
   2030     private int getArg1(int defaultValue) {
   2031         return getArg(1, defaultValue);
   2032     }
   2033 
   2034     private int getArg(int index, int defaultValue) {
   2035         int result = mArgs[index];
   2036         if (result < 0) {
   2037             result = defaultValue;
   2038         }
   2039         return result;
   2040     }
   2041 
   2042     private void unimplementedSequence(byte b) {
   2043         if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
   2044             logError("unimplemented", b);
   2045         }
   2046         finishSequence();
   2047     }
   2048 
   2049     private void unknownSequence(byte b) {
   2050         if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
   2051             logError("unknown", b);
   2052         }
   2053         finishSequence();
   2054     }
   2055 
   2056     private void unknownParameter(int parameter) {
   2057         if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
   2058             StringBuilder buf = new StringBuilder();
   2059             buf.append("Unknown parameter");
   2060             buf.append(parameter);
   2061             logError(buf.toString());
   2062         }
   2063     }
   2064 
   2065     private void logError(String errorType, byte b) {
   2066         if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
   2067             StringBuilder buf = new StringBuilder();
   2068             buf.append(errorType);
   2069             buf.append(" sequence ");
   2070             buf.append(" EscapeState: ");
   2071             buf.append(mEscapeState);
   2072             buf.append(" char: '");
   2073             buf.append((char) b);
   2074             buf.append("' (");
   2075             buf.append(b);
   2076             buf.append(")");
   2077             boolean firstArg = true;
   2078             for (int i = 0; i <= mArgIndex; i++) {
   2079                 int value = mArgs[i];
   2080                 if (value >= 0) {
   2081                     if (firstArg) {
   2082                         firstArg = false;
   2083                         buf.append("args = ");
   2084                     }
   2085                     buf.append(String.format("%d; ", value));
   2086                 }
   2087             }
   2088             logError(buf.toString());
   2089         }
   2090     }
   2091 
   2092     private void logError(String error) {
   2093         if (Term.LOG_UNKNOWN_ESCAPE_SEQUENCES) {
   2094             Log.e(Term.LOG_TAG, error);
   2095         }
   2096         finishSequence();
   2097     }
   2098 
   2099     private void finishSequence() {
   2100         mEscapeState = ESC_NONE;
   2101     }
   2102 
   2103     private boolean autoWrapEnabled() {
   2104         // Always enable auto wrap, because it's useful on a small screen
   2105         return true;
   2106         // return (mDecFlags & K_WRAPAROUND_MODE_MASK) != 0;
   2107     }
   2108 
   2109     /**
   2110      * Send an ASCII character to the screen.
   2111      *
   2112      * @param b the ASCII character to display.
   2113      */
   2114     private void emit(byte b) {
   2115         boolean autoWrap = autoWrapEnabled();
   2116 
   2117         if (autoWrap) {
   2118             if (mCursorCol == mColumns - 1 && mAboutToAutoWrap) {
   2119                 mScreen.setLineWrap(mCursorRow);
   2120                 mCursorCol = 0;
   2121                 if (mCursorRow + 1 < mBottomMargin) {
   2122                     mCursorRow++;
   2123                 } else {
   2124                     scroll();
   2125                 }
   2126             }
   2127         }
   2128 
   2129         if (mInsertMode) { // Move character to right one space
   2130             int destCol = mCursorCol + 1;
   2131             if (destCol < mColumns) {
   2132                 mScreen.blockCopy(mCursorCol, mCursorRow, mColumns - destCol,
   2133                         1, destCol, mCursorRow);
   2134             }
   2135         }
   2136 
   2137         mScreen.set(mCursorCol, mCursorRow, b, getForeColor(), getBackColor());
   2138 
   2139         if (autoWrap) {
   2140             mAboutToAutoWrap = (mCursorCol == mColumns - 1);
   2141         }
   2142 
   2143         mCursorCol = Math.min(mCursorCol + 1, mColumns - 1);
   2144     }
   2145 
   2146     private void setCursorRow(int row) {
   2147         mCursorRow = row;
   2148         mAboutToAutoWrap = false;
   2149     }
   2150 
   2151     private void setCursorCol(int col) {
   2152         mCursorCol = col;
   2153         mAboutToAutoWrap = false;
   2154     }
   2155 
   2156     private void setCursorRowCol(int row, int col) {
   2157         mCursorRow = Math.min(row, mRows-1);
   2158         mCursorCol = Math.min(col, mColumns-1);
   2159         mAboutToAutoWrap = false;
   2160     }
   2161 
   2162     /**
   2163      * Reset the terminal emulator to its initial state.
   2164      */
   2165     public void reset() {
   2166         mCursorRow = 0;
   2167         mCursorCol = 0;
   2168         mArgIndex = 0;
   2169         mContinueSequence = false;
   2170         mEscapeState = ESC_NONE;
   2171         mSavedCursorRow = 0;
   2172         mSavedCursorCol = 0;
   2173         mDecFlags = 0;
   2174         mSavedDecFlags = 0;
   2175         mInsertMode = false;
   2176         mAutomaticNewlineMode = false;
   2177         mTopMargin = 0;
   2178         mBottomMargin = mRows;
   2179         mAboutToAutoWrap = false;
   2180         mForeColor = 7;
   2181         mBackColor = 0;
   2182         mInverseColors = false;
   2183         mbKeypadApplicationMode = false;
   2184         mAlternateCharSet = false;
   2185         // mProcessedCharCount is preserved unchanged.
   2186         setDefaultTabStops();
   2187         blockClear(0, 0, mColumns, mRows);
   2188     }
   2189 
   2190     public String getTranscriptText() {
   2191         return mScreen.getTranscriptText();
   2192     }
   2193 }
   2194 
   2195 /**
   2196  * Text renderer interface
   2197  */
   2198 
   2199 interface TextRenderer {
   2200     int getCharacterWidth();
   2201     int getCharacterHeight();
   2202     void drawTextRun(Canvas canvas, float x, float y,
   2203             int lineOffset, char[] text,
   2204             int index, int count, boolean cursor, int foreColor, int backColor);
   2205 }
   2206 
   2207 abstract class BaseTextRenderer implements TextRenderer {
   2208     protected int[] mForePaint = {
   2209             0xff000000, // Black
   2210             0xffff0000, // Red
   2211             0xff00ff00, // green
   2212             0xffffff00, // yellow
   2213             0xff0000ff, // blue
   2214             0xffff00ff, // magenta
   2215             0xff00ffff, // cyan
   2216             0xffffffff  // white -- is overridden by constructor
   2217     };
   2218     protected int[] mBackPaint = {
   2219             0xff000000, // Black -- is overridden by constructor
   2220             0xffcc0000, // Red
   2221             0xff00cc00, // green
   2222             0xffcccc00, // yellow
   2223             0xff0000cc, // blue
   2224             0xffff00cc, // magenta
   2225             0xff00cccc, // cyan
   2226             0xffffffff  // white
   2227     };
   2228     protected final static int mCursorPaint = 0xff808080;
   2229 
   2230     public BaseTextRenderer(int forePaintColor, int backPaintColor) {
   2231         mForePaint[7] = forePaintColor;
   2232         mBackPaint[0] = backPaintColor;
   2233 
   2234     }
   2235 }
   2236 
   2237 class Bitmap4x8FontRenderer extends BaseTextRenderer {
   2238     private final static int kCharacterWidth = 4;
   2239     private final static int kCharacterHeight = 8;
   2240     private Bitmap mFont;
   2241     private int mCurrentForeColor;
   2242     private int mCurrentBackColor;
   2243     private float[] mColorMatrix;
   2244     private Paint mPaint;
   2245     private static final float BYTE_SCALE = 1.0f / 255.0f;
   2246 
   2247     public Bitmap4x8FontRenderer(Resources resources,
   2248             int forePaintColor, int backPaintColor) {
   2249         super(forePaintColor, backPaintColor);
   2250         mFont = BitmapFactory.decodeResource(resources,
   2251                 R.drawable.atari_small);
   2252         mPaint = new Paint();
   2253         mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
   2254     }
   2255 
   2256     public int getCharacterWidth() {
   2257         return kCharacterWidth;
   2258     }
   2259 
   2260     public int getCharacterHeight() {
   2261         return kCharacterHeight;
   2262     }
   2263 
   2264     public void drawTextRun(Canvas canvas, float x, float y,
   2265             int lineOffset, char[] text, int index, int count,
   2266             boolean cursor, int foreColor, int backColor) {
   2267         setColorMatrix(mForePaint[foreColor & 7],
   2268                 cursor ? mCursorPaint : mBackPaint[backColor & 7]);
   2269         int destX = (int) x + kCharacterWidth * lineOffset;
   2270         int destY = (int) y;
   2271         Rect srcRect = new Rect();
   2272         Rect destRect = new Rect();
   2273         destRect.top = (destY - kCharacterHeight);
   2274         destRect.bottom = destY;
   2275         for(int i = 0; i < count; i++) {
   2276             char c = text[i + index];
   2277             if ((cursor || (c != 32)) && (c < 128)) {
   2278                 int cellX = c & 31;
   2279                 int cellY = (c >> 5) & 3;
   2280                 int srcX = cellX * kCharacterWidth;
   2281                 int srcY = cellY * kCharacterHeight;
   2282                 srcRect.set(srcX, srcY,
   2283                         srcX + kCharacterWidth, srcY + kCharacterHeight);
   2284                 destRect.left = destX;
   2285                 destRect.right = destX + kCharacterWidth;
   2286                 canvas.drawBitmap(mFont, srcRect, destRect, mPaint);
   2287             }
   2288             destX += kCharacterWidth;
   2289         }
   2290     }
   2291 
   2292     private void setColorMatrix(int foreColor, int backColor) {
   2293         if ((foreColor != mCurrentForeColor)
   2294                 || (backColor != mCurrentBackColor)
   2295                 || (mColorMatrix == null)) {
   2296             mCurrentForeColor = foreColor;
   2297             mCurrentBackColor = backColor;
   2298             if (mColorMatrix == null) {
   2299                 mColorMatrix = new float[20];
   2300                 mColorMatrix[18] = 1.0f; // Just copy Alpha
   2301             }
   2302             for (int component = 0; component < 3; component++) {
   2303                 int rightShift = (2 - component) << 3;
   2304                 int fore = 0xff & (foreColor >> rightShift);
   2305                 int back = 0xff & (backColor >> rightShift);
   2306                 int delta = back - fore;
   2307                 mColorMatrix[component * 6] = delta * BYTE_SCALE;
   2308                 mColorMatrix[component * 5 + 4] = fore;
   2309             }
   2310             mPaint.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
   2311         }
   2312     }
   2313 }
   2314 
   2315 class PaintRenderer extends BaseTextRenderer {
   2316     public PaintRenderer(int fontSize, int forePaintColor, int backPaintColor) {
   2317         super(forePaintColor, backPaintColor);
   2318         mTextPaint = new Paint();
   2319         mTextPaint.setTypeface(Typeface.MONOSPACE);
   2320         mTextPaint.setAntiAlias(true);
   2321         mTextPaint.setTextSize(fontSize);
   2322 
   2323         mCharHeight = (int) Math.ceil(mTextPaint.getFontSpacing());
   2324         mCharAscent = (int) Math.ceil(mTextPaint.ascent());
   2325         mCharDescent = mCharHeight + mCharAscent;
   2326         mCharWidth = (int) mTextPaint.measureText(EXAMPLE_CHAR, 0, 1);
   2327     }
   2328 
   2329     public void drawTextRun(Canvas canvas, float x, float y, int lineOffset,
   2330             char[] text, int index, int count,
   2331             boolean cursor, int foreColor, int backColor) {
   2332         if (cursor) {
   2333             mTextPaint.setColor(mCursorPaint);
   2334         } else {
   2335             mTextPaint.setColor(mBackPaint[backColor & 0x7]);
   2336         }
   2337         float left = x + lineOffset * mCharWidth;
   2338         canvas.drawRect(left, y + mCharAscent,
   2339                 left + count * mCharWidth, y + mCharDescent,
   2340                 mTextPaint);
   2341         boolean bold = ( foreColor & 0x8 ) != 0;
   2342         boolean underline = (backColor & 0x8) != 0;
   2343         if (bold) {
   2344             mTextPaint.setFakeBoldText(true);
   2345         }
   2346         if (underline) {
   2347             mTextPaint.setUnderlineText(true);
   2348         }
   2349         mTextPaint.setColor(mForePaint[foreColor & 0x7]);
   2350         canvas.drawText(text, index, count, left, y, mTextPaint);
   2351         if (bold) {
   2352             mTextPaint.setFakeBoldText(false);
   2353         }
   2354         if (underline) {
   2355             mTextPaint.setUnderlineText(false);
   2356         }
   2357     }
   2358 
   2359     public int getCharacterHeight() {
   2360         return mCharHeight;
   2361     }
   2362 
   2363     public int getCharacterWidth() {
   2364         return mCharWidth;
   2365     }
   2366 
   2367 
   2368     private Paint mTextPaint;
   2369     private int mCharWidth;
   2370     private int mCharHeight;
   2371     private int mCharAscent;
   2372     private int mCharDescent;
   2373     private static final char[] EXAMPLE_CHAR = {'X'};
   2374     }
   2375 
   2376 /**
   2377  * A multi-thread-safe produce-consumer byte array.
   2378  * Only allows one producer and one consumer.
   2379  */
   2380 
   2381 class ByteQueue {
   2382     public ByteQueue(int size) {
   2383         mBuffer = new byte[size];
   2384     }
   2385 
   2386     public int getBytesAvailable() {
   2387         synchronized(this) {
   2388             return mStoredBytes;
   2389         }
   2390     }
   2391 
   2392     public int read(byte[] buffer, int offset, int length)
   2393         throws InterruptedException {
   2394         if (length + offset > buffer.length) {
   2395             throw
   2396                 new IllegalArgumentException("length + offset > buffer.length");
   2397         }
   2398         if (length < 0) {
   2399             throw
   2400             new IllegalArgumentException("length < 0");
   2401 
   2402         }
   2403         if (length == 0) {
   2404             return 0;
   2405         }
   2406         synchronized(this) {
   2407             while (mStoredBytes == 0) {
   2408                 wait();
   2409             }
   2410             int totalRead = 0;
   2411             int bufferLength = mBuffer.length;
   2412             boolean wasFull = bufferLength == mStoredBytes;
   2413             while (length > 0 && mStoredBytes > 0) {
   2414                 int oneRun = Math.min(bufferLength - mHead, mStoredBytes);
   2415                 int bytesToCopy = Math.min(length, oneRun);
   2416                 System.arraycopy(mBuffer, mHead, buffer, offset, bytesToCopy);
   2417                 mHead += bytesToCopy;
   2418                 if (mHead >= bufferLength) {
   2419                     mHead = 0;
   2420                 }
   2421                 mStoredBytes -= bytesToCopy;
   2422                 length -= bytesToCopy;
   2423                 offset += bytesToCopy;
   2424                 totalRead += bytesToCopy;
   2425             }
   2426             if (wasFull) {
   2427                 notify();
   2428             }
   2429             return totalRead;
   2430         }
   2431     }
   2432 
   2433     public void write(byte[] buffer, int offset, int length)
   2434     throws InterruptedException {
   2435         if (length + offset > buffer.length) {
   2436             throw
   2437                 new IllegalArgumentException("length + offset > buffer.length");
   2438         }
   2439         if (length < 0) {
   2440             throw
   2441             new IllegalArgumentException("length < 0");
   2442 
   2443         }
   2444         if (length == 0) {
   2445             return;
   2446         }
   2447         synchronized(this) {
   2448             int bufferLength = mBuffer.length;
   2449             boolean wasEmpty = mStoredBytes == 0;
   2450             while (length > 0) {
   2451                 while(bufferLength == mStoredBytes) {
   2452                     wait();
   2453                 }
   2454                 int tail = mHead + mStoredBytes;
   2455                 int oneRun;
   2456                 if (tail >= bufferLength) {
   2457                     tail = tail - bufferLength;
   2458                     oneRun = mHead - tail;
   2459                 } else {
   2460                     oneRun = bufferLength - tail;
   2461                 }
   2462                 int bytesToCopy = Math.min(oneRun, length);
   2463                 System.arraycopy(buffer, offset, mBuffer, tail, bytesToCopy);
   2464                 offset += bytesToCopy;
   2465                 mStoredBytes += bytesToCopy;
   2466                 length -= bytesToCopy;
   2467             }
   2468             if (wasEmpty) {
   2469                 notify();
   2470             }
   2471         }
   2472     }
   2473 
   2474     private byte[] mBuffer;
   2475     private int mHead;
   2476     private int mStoredBytes;
   2477 }
   2478 /**
   2479  * A view on a transcript and a terminal emulator. Displays the text of the
   2480  * transcript and the current cursor position of the terminal emulator.
   2481  */
   2482 class EmulatorView extends View implements GestureDetector.OnGestureListener {
   2483 
   2484     /**
   2485      * We defer some initialization until we have been layed out in the view
   2486      * hierarchy. The boolean tracks when we know what our size is.
   2487      */
   2488     private boolean mKnownSize;
   2489 
   2490     /**
   2491      * Our transcript. Contains the screen and the transcript.
   2492      */
   2493     private TranscriptScreen mTranscriptScreen;
   2494 
   2495     /**
   2496      * Number of rows in the transcript.
   2497      */
   2498     private static final int TRANSCRIPT_ROWS = 10000;
   2499 
   2500     /**
   2501      * Total width of each character, in pixels
   2502      */
   2503     private int mCharacterWidth;
   2504 
   2505     /**
   2506      * Total height of each character, in pixels
   2507      */
   2508     private int mCharacterHeight;
   2509 
   2510     /**
   2511      * Used to render text
   2512      */
   2513     private TextRenderer mTextRenderer;
   2514 
   2515     /**
   2516      * Text size. Zero means 4 x 8 font.
   2517      */
   2518     private int mTextSize;
   2519 
   2520     /**
   2521      * Foreground color.
   2522      */
   2523     private int mForeground;
   2524 
   2525     /**
   2526      * Background color.
   2527      */
   2528     private int mBackground;
   2529 
   2530     /**
   2531      * Used to paint the cursor
   2532      */
   2533     private Paint mCursorPaint;
   2534 
   2535     private Paint mBackgroundPaint;
   2536 
   2537     /**
   2538      * Our terminal emulator. We use this to get the current cursor position.
   2539      */
   2540     private TerminalEmulator mEmulator;
   2541 
   2542     /**
   2543      * The number of rows of text to display.
   2544      */
   2545     private int mRows;
   2546 
   2547     /**
   2548      * The number of columns of text to display.
   2549      */
   2550     private int mColumns;
   2551 
   2552     /**
   2553      * The number of columns that are visible on the display.
   2554      */
   2555 
   2556     private int mVisibleColumns;
   2557 
   2558     /**
   2559      * The top row of text to display. Ranges from -activeTranscriptRows to 0
   2560      */
   2561     private int mTopRow;
   2562 
   2563     private int mLeftColumn;
   2564 
   2565     private FileDescriptor mTermFd;
   2566     /**
   2567      * Used to receive data from the remote process.
   2568      */
   2569     private FileInputStream mTermIn;
   2570 
   2571     private FileOutputStream mTermOut;
   2572 
   2573     private ByteQueue mByteQueue;
   2574 
   2575     /**
   2576      * Used to temporarily hold data received from the remote process. Allocated
   2577      * once and used permanently to minimize heap thrashing.
   2578      */
   2579     private byte[] mReceiveBuffer;
   2580 
   2581     /**
   2582      * Our private message id, which we use to receive new input from the
   2583      * remote process.
   2584      */
   2585     private static final int UPDATE = 1;
   2586 
   2587     /**
   2588      * Thread that polls for input from the remote process
   2589      */
   2590 
   2591     private Thread mPollingThread;
   2592 
   2593     private GestureDetector mGestureDetector;
   2594     private float mScrollRemainder;
   2595     private TermKeyListener mKeyListener;
   2596 
   2597     /**
   2598      * Our message handler class. Implements a periodic callback.
   2599      */
   2600     private final Handler mHandler = new Handler() {
   2601         /**
   2602          * Handle the callback message. Call our enclosing class's update
   2603          * method.
   2604          *
   2605          * @param msg The callback message.
   2606          */
   2607         @Override
   2608         public void handleMessage(Message msg) {
   2609             if (msg.what == UPDATE) {
   2610                 update();
   2611             }
   2612         }
   2613     };
   2614 
   2615     public EmulatorView(Context context) {
   2616         super(context);
   2617         commonConstructor(context);
   2618     }
   2619 
   2620     public void register(TermKeyListener listener) {
   2621         mKeyListener = listener;
   2622     }
   2623 
   2624     public void setColors(int foreground, int background) {
   2625         mForeground = foreground;
   2626         mBackground = background;
   2627         updateText();
   2628     }
   2629 
   2630     public String getTranscriptText() {
   2631         return mEmulator.getTranscriptText();
   2632     }
   2633 
   2634     public void resetTerminal() {
   2635         mEmulator.reset();
   2636         invalidate();
   2637     }
   2638 
   2639     @Override
   2640     public boolean onCheckIsTextEditor() {
   2641         return true;
   2642     }
   2643 
   2644     @Override
   2645     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
   2646         return new BaseInputConnection(this, false) {
   2647 
   2648             @Override
   2649             public boolean beginBatchEdit() {
   2650                 return true;
   2651             }
   2652 
   2653             @Override
   2654             public boolean clearMetaKeyStates(int states) {
   2655                 return true;
   2656             }
   2657 
   2658             @Override
   2659             public boolean commitCompletion(CompletionInfo text) {
   2660                 return true;
   2661             }
   2662 
   2663             @Override
   2664             public boolean commitText(CharSequence text, int newCursorPosition) {
   2665                 sendText(text);
   2666                 return true;
   2667             }
   2668 
   2669             @Override
   2670             public boolean deleteSurroundingText(int leftLength, int rightLength) {
   2671                 return true;
   2672             }
   2673 
   2674             @Override
   2675             public boolean endBatchEdit() {
   2676                 return true;
   2677             }
   2678 
   2679             @Override
   2680             public boolean finishComposingText() {
   2681                 return true;
   2682             }
   2683 
   2684             @Override
   2685             public int getCursorCapsMode(int reqModes) {
   2686                 return 0;
   2687             }
   2688 
   2689             @Override
   2690             public ExtractedText getExtractedText(ExtractedTextRequest request,
   2691                     int flags) {
   2692                 return null;
   2693             }
   2694 
   2695             @Override
   2696             public CharSequence getTextAfterCursor(int n, int flags) {
   2697                 return null;
   2698             }
   2699 
   2700             @Override
   2701             public CharSequence getTextBeforeCursor(int n, int flags) {
   2702                 return null;
   2703             }
   2704 
   2705             @Override
   2706             public boolean performEditorAction(int actionCode) {
   2707                 if(actionCode == EditorInfo.IME_ACTION_UNSPECIFIED) {
   2708                     // The "return" key has been pressed on the IME.
   2709                     sendText("\n");
   2710                     return true;
   2711                 }
   2712                 return false;
   2713             }
   2714 
   2715             @Override
   2716             public boolean performContextMenuAction(int id) {
   2717                 return true;
   2718             }
   2719 
   2720             @Override
   2721             public boolean performPrivateCommand(String action, Bundle data) {
   2722                 return true;
   2723             }
   2724 
   2725             @Override
   2726             public boolean sendKeyEvent(KeyEvent event) {
   2727                 if (event.getAction() == KeyEvent.ACTION_DOWN) {
   2728                     switch(event.getKeyCode()) {
   2729                     case KeyEvent.KEYCODE_DEL:
   2730                         sendChar(127);
   2731                         break;
   2732                     }
   2733                 }
   2734                 return true;
   2735             }
   2736 
   2737             @Override
   2738             public boolean setComposingText(CharSequence text, int newCursorPosition) {
   2739                 return true;
   2740             }
   2741 
   2742             @Override
   2743             public boolean setSelection(int start, int end) {
   2744                 return true;
   2745             }
   2746 
   2747             private void sendChar(int c) {
   2748                 try {
   2749                     mapAndSend(c);
   2750                 } catch (IOException ex) {
   2751 
   2752                 }
   2753             }
   2754             private void sendText(CharSequence text) {
   2755                 int n = text.length();
   2756                 try {
   2757                     for(int i = 0; i < n; i++) {
   2758                         char c = text.charAt(i);
   2759                         mapAndSend(c);
   2760                     }
   2761                 } catch (IOException e) {
   2762                 }
   2763             }
   2764 
   2765             private void mapAndSend(int c) throws IOException {
   2766                 mTermOut.write(
   2767                         mKeyListener.mapControlChar(c));
   2768             }
   2769         };
   2770     }
   2771 
   2772     public boolean getKeypadApplicationMode() {
   2773         return mEmulator.getKeypadApplicationMode();
   2774     }
   2775 
   2776     public EmulatorView(Context context, AttributeSet attrs) {
   2777         this(context, attrs, 0);
   2778     }
   2779 
   2780     public EmulatorView(Context context, AttributeSet attrs,
   2781             int defStyle) {
   2782         super(context, attrs, defStyle);
   2783         TypedArray a =
   2784                 context.obtainStyledAttributes(android.R.styleable.View);
   2785         initializeScrollbars(a);
   2786         a.recycle();
   2787         commonConstructor(context);
   2788     }
   2789 
   2790     private void commonConstructor(Context context) {
   2791         mTextRenderer = null;
   2792         mCursorPaint = new Paint();
   2793         mCursorPaint.setARGB(255,128,128,128);
   2794         mBackgroundPaint = new Paint();
   2795         mTopRow = 0;
   2796         mLeftColumn = 0;
   2797         mGestureDetector = new GestureDetector(context, this, null);
   2798         mGestureDetector.setIsLongpressEnabled(false);
   2799         setVerticalScrollBarEnabled(true);
   2800     }
   2801 
   2802     @Override
   2803     protected int computeVerticalScrollRange() {
   2804         return mTranscriptScreen.getActiveRows();
   2805     }
   2806 
   2807     @Override
   2808     protected int computeVerticalScrollExtent() {
   2809         return mRows;
   2810     }
   2811 
   2812     @Override
   2813     protected int computeVerticalScrollOffset() {
   2814         return mTranscriptScreen.getActiveRows() + mTopRow - mRows;
   2815     }
   2816 
   2817     /**
   2818      * Call this to initialize the view.
   2819      *
   2820      * @param termFd the file descriptor
   2821      * @param termOut the output stream for the pseudo-teletype
   2822      */
   2823     public void initialize(FileDescriptor termFd, FileOutputStream termOut) {
   2824         mTermOut = termOut;
   2825         mTermFd = termFd;
   2826         mTextSize = 10;
   2827         mForeground = Term.WHITE;
   2828         mBackground = Term.BLACK;
   2829         updateText();
   2830         mTermIn = new FileInputStream(mTermFd);
   2831         mReceiveBuffer = new byte[4 * 1024];
   2832         mByteQueue = new ByteQueue(4 * 1024);
   2833     }
   2834 
   2835     /**
   2836      * Accept a sequence of bytes (typically from the pseudo-tty) and process
   2837      * them.
   2838      *
   2839      * @param buffer a byte array containing bytes to be processed
   2840      * @param base the index of the first byte in the buffer to process
   2841      * @param length the number of bytes to process
   2842      */
   2843     public void append(byte[] buffer, int base, int length) {
   2844         mEmulator.append(buffer, base, length);
   2845         ensureCursorVisible();
   2846         invalidate();
   2847     }
   2848 
   2849     /**
   2850      * Page the terminal view (scroll it up or down by delta screenfulls.)
   2851      *
   2852      * @param delta the number of screens to scroll. Positive means scroll down,
   2853      *        negative means scroll up.
   2854      */
   2855     public void page(int delta) {
   2856         mTopRow =
   2857                 Math.min(0, Math.max(-(mTranscriptScreen
   2858                         .getActiveTranscriptRows()), mTopRow + mRows * delta));
   2859         invalidate();
   2860     }
   2861 
   2862     /**
   2863      * Page the terminal view horizontally.
   2864      *
   2865      * @param deltaColumns the number of columns to scroll. Positive scrolls to
   2866      *        the right.
   2867      */
   2868     public void pageHorizontal(int deltaColumns) {
   2869         mLeftColumn =
   2870                 Math.max(0, Math.min(mLeftColumn + deltaColumns, mColumns
   2871                         - mVisibleColumns));
   2872         invalidate();
   2873     }
   2874 
   2875     /**
   2876      * Sets the text size, which in turn sets the number of rows and columns
   2877      *
   2878      * @param fontSize the new font size, in pixels.
   2879      */
   2880     public void setTextSize(int fontSize) {
   2881         mTextSize = fontSize;
   2882         updateText();
   2883     }
   2884 
   2885     // Begin GestureDetector.OnGestureListener methods
   2886 
   2887     public boolean onSingleTapUp(MotionEvent e) {
   2888         return true;
   2889     }
   2890 
   2891     public void onLongPress(MotionEvent e) {
   2892     }
   2893 
   2894     public boolean onScroll(MotionEvent e1, MotionEvent e2,
   2895             float distanceX, float distanceY) {
   2896         distanceY += mScrollRemainder;
   2897         int deltaRows = (int) (distanceY / mCharacterHeight);
   2898         mScrollRemainder = distanceY - deltaRows * mCharacterHeight;
   2899         mTopRow =
   2900             Math.min(0, Math.max(-(mTranscriptScreen
   2901                     .getActiveTranscriptRows()), mTopRow + deltaRows));
   2902         invalidate();
   2903 
   2904         return true;
   2905    }
   2906 
   2907     public void onSingleTapConfirmed(MotionEvent e) {
   2908     }
   2909 
   2910     public boolean onJumpTapDown(MotionEvent e1, MotionEvent e2) {
   2911        // Scroll to bottom
   2912        mTopRow = 0;
   2913        invalidate();
   2914        return true;
   2915     }
   2916 
   2917     public boolean onJumpTapUp(MotionEvent e1, MotionEvent e2) {
   2918         // Scroll to top
   2919         mTopRow = -mTranscriptScreen.getActiveTranscriptRows();
   2920         invalidate();
   2921         return true;
   2922     }
   2923 
   2924     public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
   2925             float velocityY) {
   2926         // TODO: add animation man's (non animated) fling
   2927         mScrollRemainder = 0.0f;
   2928         onScroll(e1, e2, 2 * velocityX, -2 * velocityY);
   2929         return true;
   2930     }
   2931 
   2932     public void onShowPress(MotionEvent e) {
   2933     }
   2934 
   2935     public boolean onDown(MotionEvent e) {
   2936         mScrollRemainder = 0.0f;
   2937         return true;
   2938     }
   2939 
   2940     // End GestureDetector.OnGestureListener methods
   2941 
   2942     @Override public boolean onTouchEvent(MotionEvent ev) {
   2943         return mGestureDetector.onTouchEvent(ev);
   2944     }
   2945 
   2946     private void updateText() {
   2947         if (mTextSize > 0) {
   2948             mTextRenderer = new PaintRenderer(mTextSize, mForeground,
   2949                     mBackground);
   2950         }
   2951         else {
   2952             mTextRenderer = new Bitmap4x8FontRenderer(getResources(),
   2953                     mForeground, mBackground);
   2954         }
   2955         mBackgroundPaint.setColor(mBackground);
   2956         mCharacterWidth = mTextRenderer.getCharacterWidth();
   2957         mCharacterHeight = mTextRenderer.getCharacterHeight();
   2958 
   2959         if (mKnownSize) {
   2960             updateSize(getWidth(), getHeight());
   2961         }
   2962     }
   2963 
   2964     @Override
   2965     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
   2966         updateSize(w, h);
   2967         if (!mKnownSize) {
   2968             mKnownSize = true;
   2969 
   2970             // Set up a thread to read input from the
   2971             // pseudo-teletype:
   2972 
   2973             mPollingThread = new Thread(new Runnable() {
   2974 
   2975                 public void run() {
   2976                     try {
   2977                         while(true) {
   2978                             int read = mTermIn.read(mBuffer);
   2979                             mByteQueue.write(mBuffer, 0, read);
   2980                             mHandler.sendMessage(
   2981                                     mHandler.obtainMessage(UPDATE));
   2982                         }
   2983                     } catch (IOException e) {
   2984                     } catch (InterruptedException e) {
   2985                     }
   2986                 }
   2987                 private byte[] mBuffer = new byte[4096];
   2988             });
   2989             mPollingThread.setName("Input reader");
   2990             mPollingThread.start();
   2991         }
   2992     }
   2993 
   2994     private void updateSize(int w, int h) {
   2995         mColumns = w / mCharacterWidth;
   2996         mRows = h / mCharacterHeight;
   2997 
   2998         // Inform the attached pty of our new size:
   2999         Exec.setPtyWindowSize(mTermFd, mRows, mColumns, w, h);
   3000 
   3001 
   3002         if (mTranscriptScreen != null) {
   3003             mEmulator.updateSize(mColumns, mRows);
   3004         } else {
   3005             mTranscriptScreen =
   3006                     new TranscriptScreen(mColumns, TRANSCRIPT_ROWS, mRows, 0, 7);
   3007             mEmulator =
   3008                     new TerminalEmulator(mTranscriptScreen, mColumns, mRows,
   3009                             mTermOut);
   3010         }
   3011 
   3012         // Reset our paging:
   3013         mTopRow = 0;
   3014         mLeftColumn = 0;
   3015 
   3016         invalidate();
   3017     }
   3018 
   3019     void updateSize() {
   3020         if (mKnownSize) {
   3021             updateSize(getWidth(), getHeight());
   3022         }
   3023     }
   3024 
   3025     /**
   3026      * Look for new input from the ptty, send it to the terminal emulator.
   3027      */
   3028     private void update() {
   3029         int bytesAvailable = mByteQueue.getBytesAvailable();
   3030         int bytesToRead = Math.min(bytesAvailable, mReceiveBuffer.length);
   3031         try {
   3032             int bytesRead = mByteQueue.read(mReceiveBuffer, 0, bytesToRead);
   3033             append(mReceiveBuffer, 0, bytesRead);
   3034         } catch (InterruptedException e) {
   3035         }
   3036     }
   3037 
   3038     @Override
   3039     protected void onDraw(Canvas canvas) {
   3040         int w = getWidth();
   3041         int h = getHeight();
   3042         canvas.drawRect(0, 0, w, h, mBackgroundPaint);
   3043         mVisibleColumns = w / mCharacterWidth;
   3044         float x = -mLeftColumn * mCharacterWidth;
   3045         float y = mCharacterHeight;
   3046         int endLine = mTopRow + mRows;
   3047         int cx = mEmulator.getCursorCol();
   3048         int cy = mEmulator.getCursorRow();
   3049         for (int i = mTopRow; i < endLine; i++) {
   3050             int cursorX = -1;
   3051             if (i == cy) {
   3052                 cursorX = cx;
   3053             }
   3054             mTranscriptScreen.drawText(i, canvas, x, y, mTextRenderer, cursorX);
   3055             y += mCharacterHeight;
   3056         }
   3057     }
   3058 
   3059     private void ensureCursorVisible() {
   3060         mTopRow = 0;
   3061         if (mVisibleColumns > 0) {
   3062             int cx = mEmulator.getCursorCol();
   3063             int visibleCursorX = mEmulator.getCursorCol() - mLeftColumn;
   3064             if (visibleCursorX < 0) {
   3065                 mLeftColumn = cx;
   3066             } else if (visibleCursorX >= mVisibleColumns) {
   3067                 mLeftColumn = (cx - mVisibleColumns) + 1;
   3068             }
   3069         }
   3070     }
   3071 }
   3072 
   3073 
   3074 /**
   3075  * An ASCII key listener. Supports control characters and escape. Keeps track of
   3076  * the current state of the alt, shift, and control keys.
   3077  */
   3078 class TermKeyListener {
   3079     /**
   3080      * The state engine for a modifier key. Can be pressed, released, locked,
   3081      * and so on.
   3082      *
   3083      */
   3084     private class ModifierKey {
   3085 
   3086         private int mState;
   3087 
   3088         private static final int UNPRESSED = 0;
   3089 
   3090         private static final int PRESSED = 1;
   3091 
   3092         private static final int RELEASED = 2;
   3093 
   3094         private static final int USED = 3;
   3095 
   3096         private static final int LOCKED = 4;
   3097 
   3098         /**
   3099          * Construct a modifier key. UNPRESSED by default.
   3100          *
   3101          */
   3102         public ModifierKey() {
   3103             mState = UNPRESSED;
   3104         }
   3105 
   3106         public void onPress() {
   3107             switch (mState) {
   3108             case PRESSED:
   3109                 // This is a repeat before use
   3110                 break;
   3111             case RELEASED:
   3112                 mState = LOCKED;
   3113                 break;
   3114             case USED:
   3115                 // This is a repeat after use
   3116                 break;
   3117             case LOCKED:
   3118                 mState = UNPRESSED;
   3119                 break;
   3120             default:
   3121                 mState = PRESSED;
   3122                 break;
   3123             }
   3124         }
   3125 
   3126         public void onRelease() {
   3127             switch (mState) {
   3128             case USED:
   3129                 mState = UNPRESSED;
   3130                 break;
   3131             case PRESSED:
   3132                 mState = RELEASED;
   3133                 break;
   3134             default:
   3135                 // Leave state alone
   3136                 break;
   3137             }
   3138         }
   3139 
   3140         public void adjustAfterKeypress() {
   3141             switch (mState) {
   3142             case PRESSED:
   3143                 mState = USED;
   3144                 break;
   3145             case RELEASED:
   3146                 mState = UNPRESSED;
   3147                 break;
   3148             default:
   3149                 // Leave state alone
   3150                 break;
   3151             }
   3152         }
   3153 
   3154         public boolean isActive() {
   3155             return mState != UNPRESSED;
   3156         }
   3157     }
   3158 
   3159     private ModifierKey mAltKey = new ModifierKey();
   3160 
   3161     private ModifierKey mCapKey = new ModifierKey();
   3162 
   3163     private ModifierKey mControlKey = new ModifierKey();
   3164 
   3165     /**
   3166      * Construct a term key listener.
   3167      *
   3168      */
   3169     public TermKeyListener() {
   3170     }
   3171 
   3172     public void handleControlKey(boolean down) {
   3173         if (down) {
   3174             mControlKey.onPress();
   3175         } else {
   3176             mControlKey.onRelease();
   3177         }
   3178     }
   3179 
   3180     public int mapControlChar(int ch) {
   3181         int result = ch;
   3182         if (mControlKey.isActive()) {
   3183             // Search is the control key.
   3184             if (result >= 'a' && result <= 'z') {
   3185                 result = (char) (result - 'a' + '\001');
   3186             } else if (result == ' ') {
   3187                 result = 0;
   3188             } else if ((result == '[') || (result == '1')) {
   3189                 result = 27;
   3190             } else if ((result == '\\') || (result == '.')) {
   3191                 result = 28;
   3192             } else if ((result == ']') || (result == '0')) {
   3193                 result = 29;
   3194             } else if ((result == '^') || (result == '6')) {
   3195                 result = 30; // control-^
   3196             } else if ((result == '_') || (result == '5')) {
   3197                 result = 31;
   3198             }
   3199         }
   3200 
   3201         if (result > -1) {
   3202             mAltKey.adjustAfterKeypress();
   3203             mCapKey.adjustAfterKeypress();
   3204             mControlKey.adjustAfterKeypress();
   3205         }
   3206         return result;
   3207     }
   3208 
   3209     /**
   3210      * Handle a keyDown event.
   3211      *
   3212      * @param keyCode the keycode of the keyDown event
   3213      * @return the ASCII byte to transmit to the pseudo-teletype, or -1 if this
   3214      *         event does not produce an ASCII byte.
   3215      */
   3216     public int keyDown(int keyCode, KeyEvent event) {
   3217         int result = -1;
   3218         switch (keyCode) {
   3219         case KeyEvent.KEYCODE_ALT_RIGHT:
   3220         case KeyEvent.KEYCODE_ALT_LEFT:
   3221             mAltKey.onPress();
   3222             break;
   3223 
   3224         case KeyEvent.KEYCODE_SHIFT_LEFT:
   3225         case KeyEvent.KEYCODE_SHIFT_RIGHT:
   3226             mCapKey.onPress();
   3227             break;
   3228 
   3229         case KeyEvent.KEYCODE_ENTER:
   3230             // Convert newlines into returns. The vt100 sends a
   3231             // '\r' when the 'Return' key is pressed, but our
   3232             // KeyEvent translates this as a '\n'.
   3233             result = '\r';
   3234             break;
   3235 
   3236         case KeyEvent.KEYCODE_DEL:
   3237             // Convert DEL into 127 (instead of 8)
   3238             result = 127;
   3239             break;
   3240 
   3241         default: {
   3242             result = event.getUnicodeChar(
   3243                    (mCapKey.isActive() ? KeyEvent.META_SHIFT_ON : 0) |
   3244                    (mAltKey.isActive() ? KeyEvent.META_ALT_ON : 0));
   3245             break;
   3246             }
   3247         }
   3248 
   3249         result = mapControlChar(result);
   3250 
   3251         return result;
   3252     }
   3253 
   3254     /**
   3255      * Handle a keyUp event.
   3256      *
   3257      * @param keyCode the keyCode of the keyUp event
   3258      */
   3259     public void keyUp(int keyCode) {
   3260         switch (keyCode) {
   3261         case KeyEvent.KEYCODE_ALT_LEFT:
   3262         case KeyEvent.KEYCODE_ALT_RIGHT:
   3263             mAltKey.onRelease();
   3264             break;
   3265         case KeyEvent.KEYCODE_SHIFT_LEFT:
   3266         case KeyEvent.KEYCODE_SHIFT_RIGHT:
   3267             mCapKey.onRelease();
   3268             break;
   3269         default:
   3270             // Ignore other keyUps
   3271             break;
   3272         }
   3273     }
   3274 }
   3275