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