OSDN Git Service

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