New Serial Manager API:

SerialManager: provides access to serial ports
SerialPort: for reading and writing data to and from serial ports

IO with both array based and direct ByteBuffers is supported.

Accessing serial ports requires android.permission.SERIAL_PORT permission

Each platform must configure list of supported serial ports in the
config_serialPorts resource overlay
(this is needed to prevent apps from accidentally accessing the bluetooth
or other system UARTs).

In addition, the platform uevent.rc file must set the owner to the
/dev/tty* files to "system" so the framework can access the port.

Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/Android.mk b/Android.mk
index 5acfb86..bda3d1d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -110,6 +110,7 @@
 	core/java/android/content/pm/IPackageMoveObserver.aidl \
 	core/java/android/content/pm/IPackageStatsObserver.aidl \
 	core/java/android/database/IContentObserver.aidl \
+	core/java/android/hardware/ISerialManager.aidl \
 	core/java/android/hardware/usb/IUsbManager.aidl \
 	core/java/android/net/IConnectivityManager.aidl \
 	core/java/android/net/INetworkManagementEventObserver.aidl \
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index db5113e..37900b6 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -42,7 +42,9 @@
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
+import android.hardware.ISerialManager;
 import android.hardware.SensorManager;
+import android.hardware.SerialManager;
 import android.hardware.usb.IUsbManager;
 import android.hardware.usb.UsbManager;
 import android.location.CountryDetector;
@@ -428,6 +430,12 @@
                     return new UsbManager(ctx, IUsbManager.Stub.asInterface(b));
                 }});
 
+        registerService(SERIAL_SERVICE, new ServiceFetcher() {
+                public Object createService(ContextImpl ctx) {
+                    IBinder b = ServiceManager.getService(SERIAL_SERVICE);
+                    return new SerialManager(ctx, ISerialManager.Stub.asInterface(b));
+                }});
+
         registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
                 public Object createService(ContextImpl ctx) {
                     return new Vibrator();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index bfbd0ac..3d4e354 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1789,6 +1789,17 @@
     public static final String USB_SERVICE = "usb";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.hardware.SerialManager} for access to serial ports.
+     *
+     * @see #getSystemService
+     * @see android.harware.SerialManager
+     *
+     * @hide
+     */
+    public static final String SERIAL_SERVICE = "serial";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/hardware/ISerialManager.aidl b/core/java/android/hardware/ISerialManager.aidl
new file mode 100644
index 0000000..74d30f7
--- /dev/null
+++ b/core/java/android/hardware/ISerialManager.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 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 android.hardware;
+
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+interface ISerialManager
+{
+    /* Returns a list of all available serial ports */
+    String[] getSerialPorts();
+
+    /* Returns a file descriptor for the serial port. */
+    ParcelFileDescriptor openSerialPort(String name);
+}
diff --git a/core/java/android/hardware/SerialManager.java b/core/java/android/hardware/SerialManager.java
new file mode 100644
index 0000000..c5e1c2b
--- /dev/null
+++ b/core/java/android/hardware/SerialManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2011 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 android.hardware;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * @hide
+ */
+public class SerialManager {
+    private static final String TAG = "SerialManager";
+
+    private final Context mContext;
+    private final ISerialManager mService;
+
+    /**
+     * {@hide}
+     */
+    public SerialManager(Context context, ISerialManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Returns a string array containing the names of available serial ports
+     *
+     * @return names of available serial ports
+     */
+    public String[] getSerialPorts() {
+        try {
+            return mService.getSerialPorts();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in getSerialPorts", e);
+            return null;
+        }
+    }
+
+    /**
+     * Opens and returns the {@link android.hardware.SerialPort} with the given name.
+     * The speed of the serial port must be one of:
+     * 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600,
+     * 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000,
+     * 1500000, 2000000, 2500000, 3000000, 3500000 or 4000000
+     *
+     * @param name of the serial port
+     * @param speed at which to open the serial port
+     * @return the serial port
+     */
+    public SerialPort openSerialPort(String name, int speed) throws IOException {
+        try {
+            ParcelFileDescriptor pfd = mService.openSerialPort(name);
+            if (pfd != null) {
+                SerialPort port = new SerialPort(name);
+                port.open(pfd, speed);
+                return port;
+            } else {
+                throw new IOException("Could not open serial port " + name);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "exception in UsbManager.openDevice", e);
+        }
+        return null;
+    }
+}
diff --git a/core/java/android/hardware/SerialPort.java b/core/java/android/hardware/SerialPort.java
new file mode 100644
index 0000000..0889790
--- /dev/null
+++ b/core/java/android/hardware/SerialPort.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2011 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 android.hardware;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.nio.ByteBuffer;
+
+/**
+ * @hide
+ */
+public class SerialPort {
+
+    private static final String TAG = "SerialPort";
+
+    // used by the JNI code
+    private int mNativeContext;
+    private final String mName;
+    private ParcelFileDescriptor mFileDescriptor;
+
+    /**
+     * SerialPort should only be instantiated by SerialManager
+     * @hide
+     */
+    public SerialPort(String name) {
+        mName = name;
+    }
+
+    /**
+     * SerialPort should only be instantiated by SerialManager
+     * Speed must be one of 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600,
+     * 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000,
+     * 1500000, 2000000, 2500000, 3000000, 3500000, 4000000
+     *
+     * @hide
+     */
+    public void open(ParcelFileDescriptor pfd, int speed) throws IOException {
+        native_open(pfd.getFileDescriptor(), speed);
+        mFileDescriptor = pfd;
+    }
+
+    /**
+     * Closes the serial port
+     */
+    public void close() throws IOException {
+        if (mFileDescriptor != null) {
+            mFileDescriptor.close();
+            mFileDescriptor = null;
+        }
+        native_close();
+    }
+
+    /**
+     * Returns the name of the serial port
+     *
+     * @return the serial port's name
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Reads data into the provided buffer
+     *
+     * @param buffer to read into
+     * @return number of bytes read
+     */
+    public int read(ByteBuffer buffer) throws IOException {
+        if (buffer.isDirect()) {
+            return native_read_direct(buffer, buffer.remaining());
+        } else if (buffer.hasArray()) {
+            return native_read_array(buffer.array(), buffer.remaining());
+        } else {
+            throw new IllegalArgumentException("buffer is not direct and has no array");
+        }
+    }
+
+    /**
+     * Writes data from provided buffer
+     *
+     * @param buffer to write
+     * @param length number of bytes to write
+     */
+    public void write(ByteBuffer buffer, int length) throws IOException {
+        if (buffer.isDirect()) {
+            native_write_direct(buffer, length);
+        } else if (buffer.hasArray()) {
+            native_write_array(buffer.array(), length);
+        } else {
+            throw new IllegalArgumentException("buffer is not direct and has no array");
+        }
+    }
+
+    private native void native_open(FileDescriptor pfd, int speed) throws IOException;
+    private native void native_close();
+    private native int native_read_array(byte[] buffer, int length) throws IOException;
+    private native int native_read_direct(ByteBuffer buffer, int length) throws IOException;
+    private native void native_write_array(byte[] buffer, int length) throws IOException;
+    private native void native_write_direct(ByteBuffer buffer, int length) throws IOException;
+}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 39b84bb..78e8df3 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -126,6 +126,7 @@
 	android_media_ToneGenerator.cpp \
 	android_hardware_Camera.cpp \
 	android_hardware_SensorManager.cpp \
+	android_hardware_SerialPort.cpp \
 	android_hardware_UsbDevice.cpp \
 	android_hardware_UsbDeviceConnection.cpp \
 	android_hardware_UsbRequest.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 8a3063f..3067e75 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -76,6 +76,7 @@
 
 extern int register_android_hardware_Camera(JNIEnv *env);
 extern int register_android_hardware_SensorManager(JNIEnv *env);
+extern int register_android_hardware_SerialPort(JNIEnv *env);
 extern int register_android_hardware_UsbDevice(JNIEnv *env);
 extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
 extern int register_android_hardware_UsbRequest(JNIEnv *env);
@@ -1157,6 +1158,7 @@
     REG_JNI(register_com_android_internal_os_ZygoteInit),
     REG_JNI(register_android_hardware_Camera),
     REG_JNI(register_android_hardware_SensorManager),
+    REG_JNI(register_android_hardware_SerialPort),
     REG_JNI(register_android_hardware_UsbDevice),
     REG_JNI(register_android_hardware_UsbDeviceConnection),
     REG_JNI(register_android_hardware_UsbRequest),
diff --git a/core/jni/android_hardware_SerialPort.cpp b/core/jni/android_hardware_SerialPort.cpp
new file mode 100644
index 0000000..e67153b
--- /dev/null
+++ b/core/jni/android_hardware_SerialPort.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2011 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 "SerialPortJNI"
+
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+
+using namespace android;
+
+static jfieldID field_context;
+
+static void
+android_hardware_SerialPort_open(JNIEnv *env, jobject thiz, jobject fileDescriptor, jint speed)
+{
+    switch (speed) {
+        case 50:
+            speed = B50;
+            break;
+        case 75:
+            speed = B75;
+            break;
+        case 110:
+            speed = B110;
+            break;
+        case 134:
+            speed = B134;
+            break;
+        case 150:
+            speed = B150;
+            break;
+        case 200:
+            speed = B200;
+            break;
+        case 300:
+            speed = B300;
+            break;
+        case 600:
+            speed = B600;
+            break;
+        case 1200:
+            speed = B1200;
+            break;
+        case 1800:
+            speed = B1800;
+            break;
+        case 2400:
+            speed = B2400;
+            break;
+        case 4800:
+            speed = B4800;
+            break;
+        case 9600:
+            speed = B9600;
+            break;
+        case 19200:
+            speed = B19200;
+            break;
+        case 38400:
+            speed = B38400;
+            break;
+        case 57600:
+            speed = B57600;
+            break;
+        case 115200:
+            speed = B115200;
+            break;
+        case 230400:
+            speed = B230400;
+            break;
+        case 460800:
+            speed = B460800;
+            break;
+        case 500000:
+            speed = B500000;
+            break;
+        case 576000:
+            speed = B576000;
+            break;
+        case 921600:
+            speed = B921600;
+            break;
+        case 1000000:
+            speed = B1000000;
+            break;
+        case 1152000:
+            speed = B1152000;
+            break;
+        case 1500000:
+            speed = B1500000;
+            break;
+        case 2000000:
+            speed = B2000000;
+            break;
+        case 2500000:
+            speed = B2500000;
+            break;
+        case 3000000:
+            speed = B3000000;
+            break;
+        case 3500000:
+            speed = B3500000;
+            break;
+        case 4000000:
+            speed = B4000000;
+            break;
+        default:
+            jniThrowException(env, "java/lang/IllegalArgumentException",
+                              "Unsupported serial port speed");
+            return;
+    }
+
+    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+    // duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copy
+    fd = dup(fd);
+    if (fd < 0) {
+        jniThrowException(env, "java/io/IOException", "Could not open serial port");
+        return;
+    }
+    env->SetIntField(thiz, field_context, fd);
+
+    struct termios tio;
+    if (tcgetattr(fd, &tio))
+        memset(&tio, 0, sizeof(tio));
+
+    tio.c_cflag =  speed | CS8 | CLOCAL | CREAD;
+    tio.c_iflag = IGNPAR;
+    tio.c_lflag = 0; /* turn of CANON, ECHO*, etc */
+    /* no timeout but request at least one character per read */
+    tio.c_cc[VTIME] = 0;
+    tio.c_cc[VMIN] = 1;
+    tcsetattr(fd, TCSANOW, &tio);
+    tcflush(fd, TCIFLUSH);
+}
+
+static void
+android_hardware_SerialPort_close(JNIEnv *env, jobject thiz)
+{
+    int fd = env->GetIntField(thiz, field_context);
+    close(fd);
+    env->SetIntField(thiz, field_context, -1);
+}
+
+static jint
+android_hardware_SerialPort_read_array(JNIEnv *env, jobject thiz, jbyteArray buffer, jint length)
+{
+    int fd = env->GetIntField(thiz, field_context);
+    jbyte* buf = (jbyte *)malloc(length);
+    if (!buf) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return -1;
+    }
+
+    int ret = read(fd, buf, length);
+    if (ret > 0) {
+        // copy data from native buffer to Java buffer
+        env->SetByteArrayRegion(buffer, 0, ret, buf);
+    }
+
+    free(buf);
+    if (ret < 0)
+        jniThrowException(env, "java/io/IOException", NULL);
+    return ret;
+}
+
+static jint
+android_hardware_SerialPort_read_direct(JNIEnv *env, jobject thiz, jobject buffer, jint length)
+{
+    int fd = env->GetIntField(thiz, field_context);
+
+    jbyte* buf = (jbyte *)env->GetDirectBufferAddress(buffer);
+    if (!buf) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "ByteBuffer not direct");
+        return -1;
+    }
+
+    int ret = read(fd, buf, length);
+    if (ret < 0)
+        jniThrowException(env, "java/io/IOException", NULL);
+    return ret;
+}
+
+static void
+android_hardware_SerialPort_write_array(JNIEnv *env, jobject thiz, jbyteArray buffer, jint length)
+{
+    int fd = env->GetIntField(thiz, field_context);
+    jbyte* buf = (jbyte *)malloc(length);
+    if (!buf) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
+        return;
+    }
+    env->GetByteArrayRegion(buffer, 0, length, buf);
+
+    jint ret = write(fd, buf, length);
+    free(buf);
+    if (ret < 0)
+        jniThrowException(env, "java/io/IOException", NULL);
+}
+
+static void
+android_hardware_SerialPort_write_direct(JNIEnv *env, jobject thiz, jobject buffer, jint length)
+{
+    int fd = env->GetIntField(thiz, field_context);
+
+    jbyte* buf = (jbyte *)env->GetDirectBufferAddress(buffer);
+    if (!buf) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "ByteBuffer not direct");
+        return;
+    }
+    int ret = write(fd, buf, length);
+    if (ret < 0)
+        jniThrowException(env, "java/io/IOException", NULL);
+}
+
+static JNINativeMethod method_table[] = {
+    {"native_open",             "(Ljava/io/FileDescriptor;I)V",
+                                        (void *)android_hardware_SerialPort_open},
+    {"native_close",            "()V",  (void *)android_hardware_SerialPort_close},
+    {"native_read_array",       "([BI)I",
+                                        (void *)android_hardware_SerialPort_read_array},
+    {"native_read_direct",      "(Ljava/nio/ByteBuffer;I)I",
+                                        (void *)android_hardware_SerialPort_read_direct},
+    {"native_write_array",      "([BI)V",
+                                        (void *)android_hardware_SerialPort_write_array},
+    {"native_write_direct",     "(Ljava/nio/ByteBuffer;I)V",
+                                        (void *)android_hardware_SerialPort_write_direct},
+};
+
+int register_android_hardware_SerialPort(JNIEnv *env)
+{
+    jclass clazz = env->FindClass("android/hardware/SerialPort");
+    if (clazz == NULL) {
+        ALOGE("Can't find android/hardware/SerialPort");
+        return -1;
+    }
+    field_context = env->GetFieldID(clazz, "mNativeContext", "I");
+    if (field_context == NULL) {
+        ALOGE("Can't find SerialPort.mNativeContext");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env, "android/hardware/SerialPort",
+            method_table, NELEM(method_table));
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1199cf7..68c919e 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1514,6 +1514,13 @@
         android:description="@string/permdesc_bindPackageVerifier"
         android:protectionLevel="signature" />
 
+    <!-- Allows applications to access serial ports via the SerialManager.
+         @hide -->
+    <permission android:name="android.permission.SERIAL_PORT"
+        android:label="@string/permlab_serialPort"
+        android:description="@string/permdesc_serialPort"
+        android:protectionLevel="normal" />
+
     <!-- The system process is explicitly the only one allowed to launch the
          confirmation UI for full backup/restore -->
     <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index adde69b..9bdacf4 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -368,6 +368,12 @@
     <string-array name="config_usbHostBlacklist" translatable="false">
     </string-array>
 
+    <!-- List of paths to serial ports that are available to the serial manager.
+         for example, /dev/ttyUSB0
+    -->
+    <string-array translatable="false" name="config_serialPorts">
+    </string-array>
+
     <!-- Vibrator pattern for feedback about a long screen/key press -->
     <integer-array name="config_longPressVibePattern">
         <item>0</item>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 19e852e..fe56434 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1289,6 +1289,7 @@
   <java-symbol type="array" name="config_tether_usb_regexs" />
   <java-symbol type="array" name="config_tether_wifi_regexs" />
   <java-symbol type="array" name="config_usbHostBlacklist" />
+  <java-symbol type="array" name="config_serialPorts" />
   <java-symbol type="array" name="radioAttributes" />
   <java-symbol type="bool" name="config_animateScreenLights" />
   <java-symbol type="bool" name="config_automatic_brightness_available" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 701782c..4995d2c 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2243,6 +2243,11 @@
     <string name="permdesc_bindPackageVerifier">Allows the holder to make requests of
         package verifiers. Should never be needed for normal apps.</string>
 
+    <!-- Title of an application permission which allows the application to access serial ports via the SerialManager. [CHAR LIMIT=40] -->
+    <string name="permlab_serialPort">access serial ports</string>
+    <!-- Description of an application permission which allows the application access serial ports via the SerialManager. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_serialPort">Allows the holder to access serial ports using the SerialManager API.</string>
+
     <!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Text in the save password dialog, asking if the browser should remember a password. -->
     <string name="save_password_message">Do you want the browser to remember this password?</string>
     <!-- If the user enters a password in a form on a website, a dialog will come up asking if they want to save the password. Button in the save password dialog, saying not to remember this password. -->
diff --git a/services/java/com/android/server/SerialService.java b/services/java/com/android/server/SerialService.java
new file mode 100644
index 0000000..5d2b2a0
--- /dev/null
+++ b/services/java/com/android/server/SerialService.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 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 an
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.hardware.ISerialManager;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class SerialService extends ISerialManager.Stub {
+
+    private final Context mContext;
+    private final String[] mSerialPorts;
+
+    public SerialService(Context context) {
+        mContext = context;
+        mSerialPorts = context.getResources().getStringArray(
+                com.android.internal.R.array.config_serialPorts);
+    }
+
+    public String[] getSerialPorts() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
+
+        ArrayList<String> ports = new ArrayList<String>();
+        for (int i = 0; i < mSerialPorts.length; i++) {
+            String path = mSerialPorts[i];
+            if (new File(path).exists()) {
+                ports.add(path);
+            }
+        }
+        String[] result = new String[ports.size()];
+        ports.toArray(result);
+        return result;
+    }
+
+    public ParcelFileDescriptor openSerialPort(String path) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
+        return native_open(path);
+    }
+
+    private native ParcelFileDescriptor native_open(String path);
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 762acbb..d132949 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -126,6 +126,7 @@
         BluetoothA2dpService bluetoothA2dp = null;
         DockObserver dock = null;
         UsbService usb = null;
+        SerialService serial = null;
         UiModeManagerService uiMode = null;
         RecognitionManagerService recognition = null;
         ThrottleService throttle = null;
@@ -497,6 +498,15 @@
             }
 
             try {
+                Slog.i(TAG, "Serial Service");
+                // Serial port support
+                serial = new SerialService(context);
+                ServiceManager.addService(Context.SERIAL_SERVICE, serial);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting SerialService", e);
+            }
+
+            try {
                 Slog.i(TAG, "UI Mode Manager Service");
                 // Listen for UI mode changes
                 uiMode = new UiModeManagerService(context);
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index 6fa5dfa..c63b84d 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -9,6 +9,7 @@
     com_android_server_InputWindowHandle.cpp \
     com_android_server_LightsService.cpp \
     com_android_server_PowerManagerService.cpp \
+    com_android_server_SerialService.cpp \
     com_android_server_SystemServer.cpp \
     com_android_server_UsbDeviceManager.cpp \
     com_android_server_UsbHostManager.cpp \
diff --git a/services/jni/com_android_server_SerialService.cpp b/services/jni/com_android_server_SerialService.cpp
new file mode 100644
index 0000000..b889b78
--- /dev/null
+++ b/services/jni/com_android_server_SerialService.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2011 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 "SerialServiceJNI"
+#include "utils/Log.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "android_runtime/AndroidRuntime.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+namespace android
+{
+
+static struct parcel_file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+} gParcelFileDescriptorOffsets;
+
+static jobject android_server_SerialService_open(JNIEnv *env, jobject thiz, jstring path)
+{
+    const char *pathStr = env->GetStringUTFChars(path, NULL);
+
+    int fd = open(pathStr, O_RDWR | O_NOCTTY);
+    if (fd < 0) {
+        ALOGE("could not open %s", pathStr);
+        env->ReleaseStringUTFChars(path, pathStr);
+        return NULL;
+    }
+    env->ReleaseStringUTFChars(path, pathStr);
+
+    jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
+    if (fileDescriptor == NULL) {
+        return NULL;
+    }
+    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
+        gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
+}
+
+
+static JNINativeMethod method_table[] = {
+    { "native_open",                "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
+                                    (void*)android_server_SerialService_open },
+};
+
+int register_android_server_SerialService(JNIEnv *env)
+{
+    jclass clazz = env->FindClass("com/android/server/SerialService");
+    if (clazz == NULL) {
+        ALOGE("Can't find com/android/server/SerialService");
+        return -1;
+    }
+
+    clazz = env->FindClass("android/os/ParcelFileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
+    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
+    LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
+                 "Unable to find constructor for android.os.ParcelFileDescriptor");
+
+    return jniRegisterNativeMethods(env, "com/android/server/SerialService",
+            method_table, NELEM(method_table));
+}
+
+};
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index c7beb5f..423ebd1 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -27,6 +27,7 @@
 int register_android_server_InputManager(JNIEnv* env);
 int register_android_server_LightsService(JNIEnv* env);
 int register_android_server_PowerManagerService(JNIEnv* env);
+int register_android_server_SerialService(JNIEnv* env);
 int register_android_server_UsbDeviceManager(JNIEnv* env);
 int register_android_server_UsbHostManager(JNIEnv* env);
 int register_android_server_VibratorService(JNIEnv* env);
@@ -49,6 +50,7 @@
     ALOG_ASSERT(env, "Could not retrieve the env!");
 
     register_android_server_PowerManagerService(env);
+    register_android_server_SerialService(env);
     register_android_server_InputApplicationHandle(env);
     register_android_server_InputWindowHandle(env);
     register_android_server_InputManager(env);