diff --git a/apps/Term/Android.mk b/apps/Term/Android.mk index 843aec59b..9ff6c0de4 100644 --- a/apps/Term/Android.mk +++ b/apps/Term/Android.mk @@ -1,3 +1,26 @@ +# +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# This makefile shows how to build a shared library and an activity that +# bundles the shared library and calls it using JNI. + +TOP_LOCAL_PATH:= $(call my-dir) + +# Build activity + LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) @@ -7,4 +30,11 @@ LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := Term +LOCAL_JNI_SHARED_LIBRARIES := libterm + include $(BUILD_PACKAGE) + +# ============================================================ + +# Also build all of the sub-targets under this one: the shared library. +include $(call all-makefiles-under,$(LOCAL_PATH)) \ No newline at end of file diff --git a/apps/Term/jni/Android.mk b/apps/Term/jni/Android.mk new file mode 100644 index 000000000..2fe4a751d --- /dev/null +++ b/apps/Term/jni/Android.mk @@ -0,0 +1,54 @@ +# +# Copyright (C) 2008 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# This makefile supplies the rules for building a library of JNI code for +# use by our example of how to bundle a shared library with an APK. + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := eng + +# This is the target being built. +LOCAL_MODULE:= libterm + + +# All of the source files that we will compile. +LOCAL_SRC_FILES:= \ + termExec.cpp + +# All of the shared libraries we link against. +LOCAL_SHARED_LIBRARIES := \ + libutils + +# No static libraries. +LOCAL_STATIC_LIBRARIES := + +# Also need the JNI headers. +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) + +# No special compiler flags. +LOCAL_CFLAGS += + +# Don't prelink this library. For more efficient code, you may want +# to add this library to the prelink map and set this to true. However, +# it's difficult to do this for applications that are not supplied as +# part of a system image. + +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) diff --git a/apps/Term/jni/termExec.cpp b/apps/Term/jni/termExec.cpp new file mode 100644 index 000000000..968bb90e9 --- /dev/null +++ b/apps/Term/jni/termExec.cpp @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Exec" + +#include "jni.h" +#include "utils/Log.h" +#include "utils/misc.h" +#include "android_runtime/AndroidRuntime.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static jclass class_fileDescriptor; +static jfieldID field_fileDescriptor_descriptor; +static jmethodID method_fileDescriptor_init; + + +class String8 { +public: + String8() { + mString = 0; + } + + ~String8() { + if (mString) { + free(mString); + } + } + + void set(const char16_t* o, size_t numChars) { + mString = (char*) malloc(numChars + 1); + for (size_t i = 0; i < numChars; i++) { + mString[i] = (char) o[i]; + } + mString[numChars] = '\0'; + } + + const char* string() { + return mString; + } +private: + char* mString; +}; + +static int create_subprocess(const char *cmd, const char *arg0, const char *arg1, + int* pProcessId) +{ + char *devname; + int ptm; + pid_t pid; + + ptm = open("/dev/ptmx", O_RDWR); // | O_NOCTTY); + if(ptm < 0){ + LOGE("[ cannot open /dev/ptmx - %s ]\n",strerror(errno)); + return -1; + } + fcntl(ptm, F_SETFD, FD_CLOEXEC); + + if(grantpt(ptm) || unlockpt(ptm) || + ((devname = (char*) ptsname(ptm)) == 0)){ + LOGE("[ trouble with /dev/ptmx - %s ]\n", strerror(errno)); + return -1; + } + + pid = fork(); + if(pid < 0) { + LOGE("- fork failed: %s -\n", strerror(errno)); + return -1; + } + + if(pid == 0){ + close(ptm); + + int pts; + + setsid(); + + pts = open(devname, O_RDWR); + if(pts < 0) exit(-1); + + dup2(pts, 0); + dup2(pts, 1); + dup2(pts, 2); + + execl(cmd, cmd, arg0, arg1, NULL); + exit(-1); + } else { + *pProcessId = (int) pid; + return ptm; + } +} + + +static jobject android_os_Exec_createSubProcess(JNIEnv *env, jobject clazz, + jstring cmd, jstring arg0, jstring arg1, jintArray processIdArray) +{ + const jchar* str = cmd ? env->GetStringCritical(cmd, 0) : 0; + String8 cmd_8; + if (str) { + cmd_8.set(str, env->GetStringLength(cmd)); + env->ReleaseStringCritical(cmd, str); + } + + str = arg0 ? env->GetStringCritical(arg0, 0) : 0; + const char* arg0Str = 0; + String8 arg0_8; + if (str) { + arg0_8.set(str, env->GetStringLength(arg0)); + env->ReleaseStringCritical(arg0, str); + arg0Str = arg0_8.string(); + } + + str = arg1 ? env->GetStringCritical(arg1, 0) : 0; + const char* arg1Str = 0; + String8 arg1_8; + if (str) { + arg1_8.set(str, env->GetStringLength(arg1)); + env->ReleaseStringCritical(arg1, str); + arg1Str = arg1_8.string(); + } + + int procId; + int ptm = create_subprocess(cmd_8.string(), arg0Str, arg1Str, &procId); + + if (processIdArray) { + int procIdLen = env->GetArrayLength(processIdArray); + if (procIdLen > 0) { + jboolean isCopy; + + int* pProcId = (int*) env->GetPrimitiveArrayCritical(processIdArray, &isCopy); + if (pProcId) { + *pProcId = procId; + env->ReleasePrimitiveArrayCritical(processIdArray, pProcId, 0); + } + } + } + + jobject result = env->NewObject(class_fileDescriptor, method_fileDescriptor_init); + + if (!result) { + LOGE("Couldn't create a FileDescriptor."); + } + else { + env->SetIntField(result, field_fileDescriptor_descriptor, ptm); + } + + return result; +} + + +static void android_os_Exec_setPtyWindowSize(JNIEnv *env, jobject clazz, + jobject fileDescriptor, jint row, jint col, jint xpixel, jint ypixel) +{ + int fd; + struct winsize sz; + + fd = env->GetIntField(fileDescriptor, field_fileDescriptor_descriptor); + + if (env->ExceptionOccurred() != NULL) { + return; + } + + sz.ws_row = row; + sz.ws_col = col; + sz.ws_xpixel = xpixel; + sz.ws_ypixel = ypixel; + + ioctl(fd, TIOCSWINSZ, &sz); +} + +static int android_os_Exec_waitFor(JNIEnv *env, jobject clazz, + jint procId) { + int status; + waitpid(procId, &status, 0); + int result = 0; + if (WIFEXITED(status)) { + result = WEXITSTATUS(status); + } + return result; +} + +static int register_FileDescriptor(JNIEnv *env) +{ + class_fileDescriptor = env->FindClass("java/io/FileDescriptor"); + + if (class_fileDescriptor == NULL) { + LOGE("Can't find java/io/FileDescriptor"); + return -1; + } + + field_fileDescriptor_descriptor = env->GetFieldID(class_fileDescriptor, "descriptor", "I"); + + if (field_fileDescriptor_descriptor == NULL) { + LOGE("Can't find FileDescriptor.descriptor"); + return -1; + } + + method_fileDescriptor_init = env->GetMethodID(class_fileDescriptor, "", "()V"); + if (method_fileDescriptor_init == NULL) { + LOGE("Can't find FileDescriptor.init"); + return -1; + } + return 0; +} + + +static const char *classPathName = "com/android/term/Exec"; + +static JNINativeMethod method_table[] = { + { "createSubprocess", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I)Ljava/io/FileDescriptor;", + (void*) android_os_Exec_createSubProcess }, + { "setPtyWindowSize", "(Ljava/io/FileDescriptor;IIII)V", + (void*) android_os_Exec_setPtyWindowSize}, + { "waitFor", "(I)I", + (void*) android_os_Exec_waitFor} +}; + +/* + * Register several native methods for one class. + */ +static int registerNativeMethods(JNIEnv* env, const char* className, + JNINativeMethod* gMethods, int numMethods) +{ + jclass clazz; + + clazz = env->FindClass(className); + if (clazz == NULL) { + LOGE("Native registration unable to find class '%s'", className); + return JNI_FALSE; + } + if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) { + LOGE("RegisterNatives failed for '%s'", className); + return JNI_FALSE; + } + + return JNI_TRUE; +} + +/* + * Register native methods for all classes we know about. + * + * returns JNI_TRUE on success. + */ +static int registerNatives(JNIEnv* env) +{ + if (!registerNativeMethods(env, classPathName, method_table, + sizeof(method_table) / sizeof(method_table[0]))) { + return JNI_FALSE; + } + + return JNI_TRUE; +} + + +// ---------------------------------------------------------------------------- + +/* + * This is called by the VM when the shared library is first loaded. + */ + +typedef union { + JNIEnv* env; + void* venv; +} UnionJNIEnvToVoid; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + UnionJNIEnvToVoid uenv; + uenv.venv = NULL; + jint result = -1; + JNIEnv* env = NULL; + + LOGI("JNI_OnLoad"); + + if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) { + LOGE("ERROR: GetEnv failed"); + goto bail; + } + env = uenv.env; + + if ((result = register_FileDescriptor(env)) < 0) { + LOGE("ERROR: registerFileDescriptor failed"); + goto bail; + } + + if (registerNatives(env) != JNI_TRUE) { + LOGE("ERROR: registerNatives failed"); + goto bail; + } + + result = JNI_VERSION_1_4; + +bail: + return result; +} diff --git a/apps/Term/src/com/android/term/Exec.java b/apps/Term/src/com/android/term/Exec.java new file mode 100644 index 000000000..f84e7c127 --- /dev/null +++ b/apps/Term/src/com/android/term/Exec.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.term; + +import java.io.FileDescriptor; + +/** + * @hide + * Tools for executing commands. Not for public consumption. + */ + +public class Exec +{ + static { + System.loadLibrary("term"); + } + + /** + * @param cmd The command to execute + * @param arg0 The first argument to the command, may be null + * @param arg1 the second argument to the command, may be null + * @return the file descriptor of the started process. + * + */ + public static FileDescriptor createSubprocess( + String cmd, String arg0, String arg1) { + return createSubprocess(cmd, arg0, arg1, null); + } + + /** + * @param cmd The command to execute + * @param arg0 The first argument to the command, may be null + * @param arg1 the second argument to the command, may be null + * @param processId A one-element array to which the process ID of the + * started process will be written. + * @return the file descriptor of the started process. + * + */ + public static native FileDescriptor createSubprocess( + String cmd, String arg0, String arg1, int[] processId); + + public static native void setPtyWindowSize(FileDescriptor fd, + int row, int col, int xpixel, int ypixel); + /** + * Causes the calling thread to wait for the process associated with the + * receiver to finish executing. + * + * @return The exit value of the Process being waited on + * + */ + public static native int waitFor(int processId); +} + diff --git a/apps/Term/src/com/android/term/Term.java b/apps/Term/src/com/android/term/Term.java index 1f43843a3..ae83a8988 100644 --- a/apps/Term/src/com/android/term/Term.java +++ b/apps/Term/src/com/android/term/Term.java @@ -16,6 +16,12 @@ package com.android.term; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; + import android.app.Activity; import android.app.AlertDialog; import android.content.Context; @@ -35,10 +41,8 @@ import android.graphics.Rect; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; -import android.os.Exec; import android.os.Handler; import android.os.Message; -import android.os.SystemClock; import android.preference.PreferenceManager; import android.util.AttributeSet; import android.util.Log; @@ -54,13 +58,6 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputMethodManager; - -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; /** * A terminal emulator activity. @@ -98,10 +95,7 @@ public class Term extends Activity { /** * The pseudo-teletype (pty) file descriptor that we use to communicate with - * another process, typically a shell. Currently we just use this to get the - * mTermIn / mTermOut file descriptors, but when we implement resizing of - * the terminal we will need it to issue the ioctl to inform the other - * process that we've changed the terminal size. + * another process, typically a shell. */ private FileDescriptor mTermFd;