Home | History | Annotate | Download | only in jni
      1 /*
      2  * Copyright (C) 2013 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 #define LOG_TAG "Terminal"
     18 
     19 #include <utils/Log.h>
     20 #include <utils/Mutex.h>
     21 #include "android_runtime/AndroidRuntime.h"
     22 
     23 #include "forkpty.h"
     24 #include "jni.h"
     25 #include "JNIHelp.h"
     26 #include "ScopedLocalRef.h"
     27 #include "ScopedPrimitiveArray.h"
     28 
     29 #include <fcntl.h>
     30 #include <stdio.h>
     31 #include <termios.h>
     32 #include <unistd.h>
     33 #include <util.h>
     34 #include <utmp.h>
     35 
     36 #include <vterm.h>
     37 
     38 #include <string.h>
     39 
     40 #define USE_TEST_SHELL 0
     41 #define DEBUG_CALLBACKS 0
     42 #define DEBUG_IO 0
     43 #define DEBUG_SCROLLBACK 0
     44 
     45 namespace android {
     46 
     47 /*
     48  * Callback class reference
     49  */
     50 static jclass terminalCallbacksClass;
     51 
     52 /*
     53  * Callback methods
     54  */
     55 static jmethodID damageMethod;
     56 static jmethodID moveRectMethod;
     57 static jmethodID moveCursorMethod;
     58 static jmethodID setTermPropBooleanMethod;
     59 static jmethodID setTermPropIntMethod;
     60 static jmethodID setTermPropStringMethod;
     61 static jmethodID setTermPropColorMethod;
     62 static jmethodID bellMethod;
     63 
     64 /*
     65  * CellRun class
     66  */
     67 static jclass cellRunClass;
     68 static jfieldID cellRunDataField;
     69 static jfieldID cellRunDataSizeField;
     70 static jfieldID cellRunColSizeField;
     71 static jfieldID cellRunFgField;
     72 static jfieldID cellRunBgField;
     73 
     74 typedef short unsigned int dimen_t;
     75 
     76 class ScrollbackLine {
     77 public:
     78     inline ScrollbackLine(dimen_t _cols) : cols(_cols) {
     79         mCells = new VTermScreenCell[cols];
     80     };
     81     inline ~ScrollbackLine() {
     82         delete mCells;
     83     }
     84 
     85     inline dimen_t copyFrom(dimen_t cols, const VTermScreenCell* cells) {
     86         dimen_t n = this->cols > cols ? cols : this->cols;
     87         memcpy(mCells, cells, sizeof(VTermScreenCell) * n);
     88         return n;
     89     }
     90 
     91     inline dimen_t copyTo(dimen_t cols, VTermScreenCell* cells) {
     92         dimen_t n = cols > this->cols ? this->cols : cols;
     93         memcpy(cells, mCells, sizeof(VTermScreenCell) * n);
     94         return n;
     95     }
     96 
     97     inline void getCell(dimen_t col, VTermScreenCell* cell) {
     98         *cell = mCells[col];
     99     }
    100 
    101     const dimen_t cols;
    102 
    103 private:
    104     VTermScreenCell* mCells;
    105 };
    106 
    107 /*
    108  * Terminal session
    109  */
    110 class Terminal {
    111 public:
    112     Terminal(jobject callbacks, dimen_t rows, dimen_t cols);
    113     ~Terminal();
    114 
    115     status_t run();
    116 
    117     size_t write(const char *bytes, size_t len);
    118 
    119     bool dispatchCharacter(int mod, int character);
    120     bool dispatchKey(int mod, int key);
    121     bool flushInput();
    122 
    123     status_t resize(dimen_t rows, dimen_t cols, dimen_t scrollRows);
    124 
    125     status_t onPushline(dimen_t cols, const VTermScreenCell* cells);
    126     status_t onPopline(dimen_t cols, VTermScreenCell* cells);
    127 
    128     void getCellLocked(VTermPos pos, VTermScreenCell* cell);
    129 
    130     dimen_t getRows() const;
    131     dimen_t getCols() const;
    132     dimen_t getScrollRows() const;
    133 
    134     jobject getCallbacks() const;
    135 
    136     // Lock protecting mutations of internal libvterm state
    137     Mutex mLock;
    138 
    139 private:
    140     int mMasterFd;
    141     pid_t mChildPid;
    142     VTerm *mVt;
    143     VTermScreen *mVts;
    144 
    145     jobject mCallbacks;
    146 
    147     dimen_t mRows;
    148     dimen_t mCols;
    149     bool mKilled;
    150 
    151     ScrollbackLine **mScroll;
    152     dimen_t mScrollCur;
    153     dimen_t mScrollSize;
    154 
    155 };
    156 
    157 /*
    158  * VTerm event handlers
    159  */
    160 
    161 static int term_damage(VTermRect rect, void *user) {
    162     Terminal* term = reinterpret_cast<Terminal*>(user);
    163 #if DEBUG_CALLBACKS
    164     ALOGW("term_damage");
    165 #endif
    166 
    167     JNIEnv* env = AndroidRuntime::getJNIEnv();
    168     return env->CallIntMethod(term->getCallbacks(), damageMethod, rect.start_row, rect.end_row,
    169             rect.start_col, rect.end_col);
    170 }
    171 
    172 static int term_moverect(VTermRect dest, VTermRect src, void *user) {
    173     Terminal* term = reinterpret_cast<Terminal*>(user);
    174 #if DEBUG_CALLBACKS
    175     ALOGW("term_moverect");
    176 #endif
    177 
    178     JNIEnv* env = AndroidRuntime::getJNIEnv();
    179     return env->CallIntMethod(term->getCallbacks(), moveRectMethod,
    180             dest.start_row, dest.end_row, dest.start_col, dest.end_col,
    181             src.start_row, src.end_row, src.start_col, src.end_col);
    182 }
    183 
    184 static int term_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) {
    185     Terminal* term = reinterpret_cast<Terminal*>(user);
    186 #if DEBUG_CALLBACKS
    187     ALOGW("term_movecursor");
    188 #endif
    189 
    190     JNIEnv* env = AndroidRuntime::getJNIEnv();
    191     return env->CallIntMethod(term->getCallbacks(), moveCursorMethod, pos.row,
    192             pos.col, oldpos.row, oldpos.col, visible);
    193 }
    194 
    195 static int term_settermprop(VTermProp prop, VTermValue *val, void *user) {
    196     Terminal* term = reinterpret_cast<Terminal*>(user);
    197 #if DEBUG_CALLBACKS
    198     ALOGW("term_settermprop");
    199 #endif
    200 
    201     JNIEnv* env = AndroidRuntime::getJNIEnv();
    202     switch (vterm_get_prop_type(prop)) {
    203     case VTERM_VALUETYPE_BOOL:
    204         return env->CallIntMethod(term->getCallbacks(), setTermPropBooleanMethod,
    205                 static_cast<jboolean>(val->boolean));
    206     case VTERM_VALUETYPE_INT:
    207         return env->CallIntMethod(term->getCallbacks(), setTermPropIntMethod, prop, val->number);
    208     case VTERM_VALUETYPE_STRING:
    209         return env->CallIntMethod(term->getCallbacks(), setTermPropStringMethod, prop,
    210                 env->NewStringUTF(val->string));
    211     case VTERM_VALUETYPE_COLOR:
    212         return env->CallIntMethod(term->getCallbacks(), setTermPropIntMethod, prop, val->color.red,
    213                 val->color.green, val->color.blue);
    214     default:
    215         ALOGE("unknown callback type");
    216         return 0;
    217     }
    218 }
    219 
    220 static int term_setmousefunc(VTermMouseFunc func, void *data, void *user) {
    221     Terminal* term = reinterpret_cast<Terminal*>(user);
    222 #if DEBUG_CALLBACKS
    223     ALOGW("term_setmousefunc");
    224 #endif
    225     return 1;
    226 }
    227 
    228 static int term_bell(void *user) {
    229     Terminal* term = reinterpret_cast<Terminal*>(user);
    230 #if DEBUG_CALLBACKS
    231     ALOGW("term_bell");
    232 #endif
    233 
    234     JNIEnv* env = AndroidRuntime::getJNIEnv();
    235     return env->CallIntMethod(term->getCallbacks(), bellMethod);
    236 }
    237 
    238 static int term_sb_pushline(int cols, const VTermScreenCell *cells, void *user) {
    239     Terminal* term = reinterpret_cast<Terminal*>(user);
    240 #if DEBUG_CALLBACKS
    241     ALOGW("term_sb_pushline");
    242 #endif
    243 
    244     return term->onPushline(cols, cells);
    245 }
    246 
    247 static int term_sb_popline(int cols, VTermScreenCell *cells, void *user) {
    248     Terminal* term = reinterpret_cast<Terminal*>(user);
    249 #if DEBUG_CALLBACKS
    250     ALOGW("term_sb_popline");
    251 #endif
    252 
    253     return term->onPopline(cols, cells);
    254 }
    255 
    256 static VTermScreenCallbacks cb = {
    257     .damage = term_damage,
    258     .moverect = term_moverect,
    259     .movecursor = term_movecursor,
    260     .settermprop = term_settermprop,
    261     .setmousefunc = term_setmousefunc,
    262     .bell = term_bell,
    263     // Resize requests are applied immediately, so callback is ignored
    264     .resize = NULL,
    265     .sb_pushline = term_sb_pushline,
    266     .sb_popline = term_sb_popline,
    267 };
    268 
    269 Terminal::Terminal(jobject callbacks, dimen_t rows, dimen_t cols) :
    270         mCallbacks(callbacks), mRows(rows), mCols(cols), mKilled(false),
    271         mScrollCur(0), mScrollSize(100) {
    272     JNIEnv* env = AndroidRuntime::getJNIEnv();
    273     mCallbacks = env->NewGlobalRef(callbacks);
    274 
    275     mScroll = new ScrollbackLine*[mScrollSize];
    276     memset(mScroll, 0, sizeof(ScrollbackLine*) * mScrollSize);
    277 
    278     /* Create VTerm */
    279     mVt = vterm_new(rows, cols);
    280     vterm_parser_set_utf8(mVt, 1);
    281 
    282     /* Set up screen */
    283     mVts = vterm_obtain_screen(mVt);
    284     vterm_screen_enable_altscreen(mVts, 1);
    285     vterm_screen_set_callbacks(mVts, &cb, this);
    286     vterm_screen_set_damage_merge(mVts, VTERM_DAMAGE_SCROLL);
    287     vterm_screen_reset(mVts, 1);
    288 }
    289 
    290 Terminal::~Terminal() {
    291     close(mMasterFd);
    292     ::kill(mChildPid, SIGHUP);
    293 
    294     vterm_free(mVt);
    295 
    296     delete mScroll;
    297 
    298     JNIEnv *env = AndroidRuntime::getJNIEnv();
    299     env->DeleteGlobalRef(mCallbacks);
    300 }
    301 
    302 status_t Terminal::run() {
    303     struct termios termios = {
    304         .c_iflag = ICRNL|IXON|IUTF8,
    305         .c_oflag = OPOST|ONLCR|NL0|CR0|TAB0|BS0|VT0|FF0,
    306         .c_cflag = CS8|CREAD,
    307         .c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK,
    308         /* c_cc later */
    309     };
    310 
    311     cfsetispeed(&termios, B38400);
    312     cfsetospeed(&termios, B38400);
    313 
    314     termios.c_cc[VINTR]    = 0x1f & 'C';
    315     termios.c_cc[VQUIT]    = 0x1f & '\\';
    316     termios.c_cc[VERASE]   = 0x7f;
    317     termios.c_cc[VKILL]    = 0x1f & 'U';
    318     termios.c_cc[VEOF]     = 0x1f & 'D';
    319     termios.c_cc[VSTART]   = 0x1f & 'Q';
    320     termios.c_cc[VSTOP]    = 0x1f & 'S';
    321     termios.c_cc[VSUSP]    = 0x1f & 'Z';
    322     termios.c_cc[VREPRINT] = 0x1f & 'R';
    323     termios.c_cc[VWERASE]  = 0x1f & 'W';
    324     termios.c_cc[VLNEXT]   = 0x1f & 'V';
    325     termios.c_cc[VMIN]     = 1;
    326     termios.c_cc[VTIME]    = 0;
    327 
    328     struct winsize size = { mRows, mCols, 0, 0 };
    329 
    330     int stderr_save_fd = dup(2);
    331     if (stderr_save_fd < 0) {
    332         ALOGE("failed to dup stderr - %s", strerror(errno));
    333     }
    334 
    335     mChildPid = forkpty(&mMasterFd, NULL, &termios, &size);
    336     if (mChildPid == 0) {
    337         /* Restore the ISIG signals back to defaults */
    338         signal(SIGINT, SIG_DFL);
    339         signal(SIGQUIT, SIG_DFL);
    340         signal(SIGSTOP, SIG_DFL);
    341         signal(SIGCONT, SIG_DFL);
    342 
    343         FILE *stderr_save = fdopen(stderr_save_fd, "a");
    344 
    345         if (!stderr_save) {
    346             ALOGE("failed to open stderr - %s", strerror(errno));
    347         }
    348 
    349         char *shell = "/system/bin/sh"; //getenv("SHELL");
    350 #if USE_TEST_SHELL
    351         char *args[4] = {shell, "-c", "x=1; c=0; while true; do echo -e \"stop \e[00;3${c}mechoing\e[00m yourself! ($x)\"; x=$(( $x + 1 )); c=$((($c+1)%7)); if [ $x -gt 110 ]; then sleep 0.5; fi; done", NULL};
    352 #else
    353         char *args[2] = {shell, NULL};
    354 #endif
    355 
    356         execvp(shell, args);
    357         fprintf(stderr_save, "Cannot exec(%s) - %s\n", shell, strerror(errno));
    358         _exit(1);
    359     }
    360 
    361     ALOGD("entering read() loop");
    362     while (1) {
    363         char buffer[4096];
    364         ssize_t bytes = ::read(mMasterFd, buffer, sizeof buffer);
    365 #if DEBUG_IO
    366         ALOGD("read() returned %d bytes", bytes);
    367 #endif
    368 
    369         if (mKilled) {
    370             ALOGD("kill() requested");
    371             break;
    372         }
    373         if (bytes == 0) {
    374             ALOGD("read() found EOF");
    375             break;
    376         }
    377         if (bytes == -1) {
    378             ALOGE("read() failed: %s", strerror(errno));
    379             return 1;
    380         }
    381 
    382         {
    383             Mutex::Autolock lock(mLock);
    384             vterm_push_bytes(mVt, buffer, bytes);
    385             vterm_screen_flush_damage(mVts);
    386         }
    387     }
    388 
    389     return 0;
    390 }
    391 
    392 size_t Terminal::write(const char *bytes, size_t len) {
    393     return ::write(mMasterFd, bytes, len);
    394 }
    395 
    396 bool Terminal::dispatchCharacter(int mod, int character) {
    397     Mutex::Autolock lock(mLock);
    398     vterm_input_push_char(mVt, static_cast<VTermModifier>(mod), character);
    399     return flushInput();
    400 }
    401 
    402 bool Terminal::dispatchKey(int mod, int key) {
    403     Mutex::Autolock lock(mLock);
    404     vterm_input_push_key(mVt, static_cast<VTermModifier>(mod), static_cast<VTermKey>(key));
    405     return flushInput();
    406 }
    407 
    408 bool Terminal::flushInput() {
    409     size_t len = vterm_output_get_buffer_current(mVt);
    410     if (len) {
    411         char buf[len];
    412         len = vterm_output_bufferread(mVt, buf, len);
    413         return len == write(buf, len);
    414     }
    415     return true;
    416 }
    417 
    418 status_t Terminal::resize(dimen_t rows, dimen_t cols, dimen_t scrollRows) {
    419     Mutex::Autolock lock(mLock);
    420 
    421     ALOGD("resize(%d, %d, %d)", rows, cols, scrollRows);
    422 
    423     mRows = rows;
    424     mCols = cols;
    425     // TODO: resize scrollback
    426 
    427     struct winsize size = { rows, cols, 0, 0 };
    428     ioctl(mMasterFd, TIOCSWINSZ, &size);
    429 
    430     vterm_set_size(mVt, rows, cols);
    431     vterm_screen_flush_damage(mVts);
    432 
    433     return 0;
    434 }
    435 
    436 status_t Terminal::onPushline(dimen_t cols, const VTermScreenCell* cells) {
    437     ScrollbackLine* line = NULL;
    438     if (mScrollCur == mScrollSize) {
    439         /* Recycle old row if it's the right size */
    440         if (mScroll[mScrollCur - 1]->cols == cols) {
    441             line = mScroll[mScrollCur - 1];
    442         } else {
    443             delete mScroll[mScrollCur - 1];
    444         }
    445 
    446         memmove(mScroll + 1, mScroll, sizeof(ScrollbackLine*) * (mScrollCur - 1));
    447     } else if (mScrollCur > 0) {
    448         memmove(mScroll + 1, mScroll, sizeof(ScrollbackLine*) * mScrollCur);
    449     }
    450 
    451     if (line == NULL) {
    452         line = new ScrollbackLine(cols);
    453     }
    454 
    455     mScroll[0] = line;
    456 
    457     if (mScrollCur < mScrollSize) {
    458         mScrollCur++;
    459     }
    460 
    461     line->copyFrom(cols, cells);
    462     return 1;
    463 }
    464 
    465 status_t Terminal::onPopline(dimen_t cols, VTermScreenCell* cells) {
    466     if (mScrollCur == 0) {
    467         return 0;
    468     }
    469 
    470     ScrollbackLine* line = mScroll[0];
    471     mScrollCur--;
    472     memmove(mScroll, mScroll + 1, sizeof(ScrollbackLine*) * mScrollCur);
    473 
    474     dimen_t n = line->copyTo(cols, cells);
    475     for (dimen_t col = n; col < cols; col++) {
    476         cells[col].chars[0] = 0;
    477         cells[col].width = 1;
    478     }
    479 
    480     delete line;
    481     return 1;
    482 }
    483 
    484 void Terminal::getCellLocked(VTermPos pos, VTermScreenCell* cell) {
    485     // The UI may be asking for cell data while the model is changing
    486     // underneath it, so we always fill with meaningful data.
    487 
    488     if (pos.row < 0) {
    489         size_t scrollRow = -pos.row;
    490         if (scrollRow > mScrollCur) {
    491             // Invalid region above current scrollback
    492             cell->width = 1;
    493 #if DEBUG_SCROLLBACK
    494             cell->bg.red = 255;
    495 #endif
    496             return;
    497         }
    498 
    499         ScrollbackLine* line = mScroll[scrollRow - 1];
    500         if ((size_t) pos.col < line->cols) {
    501             // Valid scrollback cell
    502             line->getCell(pos.col, cell);
    503             cell->width = 1;
    504 #if DEBUG_SCROLLBACK
    505             cell->bg.blue = 255;
    506 #endif
    507             return;
    508         } else {
    509             // Extend last scrollback cell into invalid region
    510             line->getCell(line->cols - 1, cell);
    511             cell->width = 1;
    512             cell->chars[0] = ' ';
    513 #if DEBUG_SCROLLBACK
    514             cell->bg.green = 255;
    515 #endif
    516             return;
    517         }
    518     }
    519 
    520     if ((size_t) pos.row >= mRows) {
    521         // Invalid region below screen
    522         cell->width = 1;
    523 #if DEBUG_SCROLLBACK
    524         cell->bg.red = 128;
    525 #endif
    526         return;
    527     }
    528 
    529     // Valid screen cell
    530     vterm_screen_get_cell(mVts, pos, cell);
    531 }
    532 
    533 dimen_t Terminal::getRows() const {
    534     return mRows;
    535 }
    536 
    537 dimen_t Terminal::getCols() const {
    538     return mCols;
    539 }
    540 
    541 dimen_t Terminal::getScrollRows() const {
    542     return mScrollSize;
    543 }
    544 
    545 jobject Terminal::getCallbacks() const {
    546     return mCallbacks;
    547 }
    548 
    549 /*
    550  * JNI glue
    551  */
    552 
    553 static jlong com_android_terminal_Terminal_nativeInit(JNIEnv* env, jclass clazz, jobject callbacks,
    554         jint rows, jint cols) {
    555     return reinterpret_cast<jlong>(new Terminal(callbacks, rows, cols));
    556 }
    557 
    558 static jint com_android_terminal_Terminal_nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
    559     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    560     delete term;
    561     return 0;
    562 }
    563 
    564 static jint com_android_terminal_Terminal_nativeRun(JNIEnv* env, jclass clazz, jlong ptr) {
    565     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    566     return term->run();
    567 }
    568 
    569 static jint com_android_terminal_Terminal_nativeResize(JNIEnv* env,
    570         jclass clazz, jlong ptr, jint rows, jint cols, jint scrollRows) {
    571     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    572     return term->resize(rows, cols, scrollRows);
    573 }
    574 
    575 static inline int toArgb(const VTermColor& color) {
    576     return (0xff << 24 | color.red << 16 | color.green << 8 | color.blue);
    577 }
    578 
    579 static inline bool isCellStyleEqual(const VTermScreenCell& a, const VTermScreenCell& b) {
    580     if (toArgb(a.fg) != toArgb(b.fg)) return false;
    581     if (toArgb(a.bg) != toArgb(b.bg)) return false;
    582 
    583     if (a.attrs.bold != b.attrs.bold) return false;
    584     if (a.attrs.underline != b.attrs.underline) return false;
    585     if (a.attrs.italic != b.attrs.italic) return false;
    586     if (a.attrs.blink != b.attrs.blink) return false;
    587     if (a.attrs.reverse != b.attrs.reverse) return false;
    588     if (a.attrs.strike != b.attrs.strike) return false;
    589     if (a.attrs.font != b.attrs.font) return false;
    590 
    591     return true;
    592 }
    593 
    594 static jint com_android_terminal_Terminal_nativeGetCellRun(JNIEnv* env,
    595         jclass clazz, jlong ptr, jint row, jint col, jobject run) {
    596     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    597     Mutex::Autolock lock(term->mLock);
    598 
    599     jcharArray dataArray = (jcharArray) env->GetObjectField(run, cellRunDataField);
    600     ScopedCharArrayRW data(env, dataArray);
    601     if (data.get() == NULL) {
    602         return -1;
    603     }
    604 
    605     VTermScreenCell firstCell, cell;
    606 
    607     VTermPos pos = {
    608         .row = row,
    609         .col = col,
    610     };
    611 
    612     size_t dataSize = 0;
    613     size_t colSize = 0;
    614     while ((size_t) pos.col < term->getCols()) {
    615         memset(&cell, 0, sizeof(VTermScreenCell));
    616         term->getCellLocked(pos, &cell);
    617 
    618         if (colSize == 0) {
    619             env->SetIntField(run, cellRunFgField, toArgb(cell.fg));
    620             env->SetIntField(run, cellRunBgField, toArgb(cell.bg));
    621             memcpy(&firstCell, &cell, sizeof(VTermScreenCell));
    622         } else {
    623             if (!isCellStyleEqual(cell, firstCell)) {
    624                 break;
    625             }
    626         }
    627 
    628         // Only include cell chars if they fit into run
    629         uint32_t rawCell = cell.chars[0];
    630         size_t size = (rawCell < 0x10000) ? 1 : 2;
    631         if (dataSize + size <= data.size()) {
    632             if (rawCell < 0x10000) {
    633                 data[dataSize++] = rawCell;
    634             } else {
    635                 data[dataSize++] = (((rawCell - 0x10000) >> 10) & 0x3ff) + 0xd800;
    636                 data[dataSize++] = ((rawCell - 0x10000) & 0x3ff) + 0xdc00;
    637             }
    638 
    639             for (int i = 1; i < cell.width; i++) {
    640                 data[dataSize++] = ' ';
    641             }
    642 
    643             colSize += cell.width;
    644             pos.col += cell.width;
    645         } else {
    646             break;
    647         }
    648     }
    649 
    650     env->SetIntField(run, cellRunDataSizeField, dataSize);
    651     env->SetIntField(run, cellRunColSizeField, colSize);
    652 
    653     return 0;
    654 }
    655 
    656 static jint com_android_terminal_Terminal_nativeGetRows(JNIEnv* env, jclass clazz, jlong ptr) {
    657     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    658     return term->getRows();
    659 }
    660 
    661 static jint com_android_terminal_Terminal_nativeGetCols(JNIEnv* env, jclass clazz, jlong ptr) {
    662     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    663     return term->getCols();
    664 }
    665 
    666 static jint com_android_terminal_Terminal_nativeGetScrollRows(JNIEnv* env, jclass clazz, jlong ptr) {
    667     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    668     return term->getScrollRows();
    669 }
    670 
    671 static jboolean com_android_terminal_Terminal_nativeDispatchCharacter(JNIEnv *env, jclass clazz,
    672         jlong ptr, jint mod, jint c) {
    673     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    674     return term->dispatchCharacter(mod, c);
    675 }
    676 
    677 static jboolean com_android_terminal_Terminal_nativeDispatchKey(JNIEnv *env, jclass clazz,
    678         jlong ptr, jint mod, jint c) {
    679     Terminal* term = reinterpret_cast<Terminal*>(ptr);
    680     return term->dispatchKey(mod, c);
    681 }
    682 
    683 static JNINativeMethod gMethods[] = {
    684     { "nativeInit", "(Lcom/android/terminal/TerminalCallbacks;II)L", (void*)com_android_terminal_Terminal_nativeInit },
    685     { "nativeDestroy", "(J)I", (void*)com_android_terminal_Terminal_nativeDestroy },
    686     { "nativeRun", "(J)I", (void*)com_android_terminal_Terminal_nativeRun },
    687     { "nativeResize", "(JIII)I", (void*)com_android_terminal_Terminal_nativeResize },
    688     { "nativeGetCellRun", "(JIILcom/android/terminal/Terminal$CellRun;)I", (void*)com_android_terminal_Terminal_nativeGetCellRun },
    689     { "nativeGetRows", "(J)I", (void*)com_android_terminal_Terminal_nativeGetRows },
    690     { "nativeGetCols", "(J)I", (void*)com_android_terminal_Terminal_nativeGetCols },
    691     { "nativeGetScrollRows", "(J)I", (void*)com_android_terminal_Terminal_nativeGetScrollRows },
    692     { "nativeDispatchCharacter", "(JII)Z", (void*)com_android_terminal_Terminal_nativeDispatchCharacter},
    693     { "nativeDispatchKey", "(JII)Z", (void*)com_android_terminal_Terminal_nativeDispatchKey },
    694 };
    695 
    696 int register_com_android_terminal_Terminal(JNIEnv* env) {
    697     ScopedLocalRef<jclass> localClass(env,
    698             env->FindClass("com/android/terminal/TerminalCallbacks"));
    699 
    700     android::terminalCallbacksClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
    701 
    702     android::damageMethod = env->GetMethodID(terminalCallbacksClass, "damage", "(IIII)I");
    703     android::moveRectMethod = env->GetMethodID(terminalCallbacksClass, "moveRect", "(IIIIIIII)I");
    704     android::moveCursorMethod = env->GetMethodID(terminalCallbacksClass, "moveCursor",
    705             "(IIIII)I");
    706     android::setTermPropBooleanMethod = env->GetMethodID(terminalCallbacksClass,
    707             "setTermPropBoolean", "(IZ)I");
    708     android::setTermPropIntMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropInt",
    709             "(II)I");
    710     android::setTermPropStringMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropString",
    711             "(ILjava/lang/String;)I");
    712     android::setTermPropColorMethod = env->GetMethodID(terminalCallbacksClass, "setTermPropColor",
    713             "(IIII)I");
    714     android::bellMethod = env->GetMethodID(terminalCallbacksClass, "bell", "()I");
    715 
    716     ScopedLocalRef<jclass> cellRunLocal(env,
    717             env->FindClass("com/android/terminal/Terminal$CellRun"));
    718     cellRunClass = reinterpret_cast<jclass>(env->NewGlobalRef(cellRunLocal.get()));
    719     cellRunDataField = env->GetFieldID(cellRunClass, "data", "[C");
    720     cellRunDataSizeField = env->GetFieldID(cellRunClass, "dataSize", "I");
    721     cellRunColSizeField = env->GetFieldID(cellRunClass, "colSize", "I");
    722     cellRunFgField = env->GetFieldID(cellRunClass, "fg", "I");
    723     cellRunBgField = env->GetFieldID(cellRunClass, "bg", "I");
    724 
    725     return jniRegisterNativeMethods(env, "com/android/terminal/Terminal",
    726             gMethods, NELEM(gMethods));
    727 }
    728 
    729 } /* namespace android */
    730