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