OSDN Git Service

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