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