am eed8d0b3: Fix for issue 9656 - custom xml attributes in Android Libraries. do not merge.

* commit 'eed8d0b3af8e76d6146ce4ed3fbe6ef7e172c8cd':
  Fix for issue 9656 - custom xml attributes in Android Libraries. do not merge.
diff --git a/Android.mk b/Android.mk
index 83c4b5b..2515e56 100644
--- a/Android.mk
+++ b/Android.mk
@@ -109,6 +109,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/api/current.txt b/api/current.txt
index 68fb4bc..e731ddd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6557,6 +6557,7 @@
     field public static final int UI_MODE_NIGHT_NO = 16; // 0x10
     field public static final int UI_MODE_NIGHT_UNDEFINED = 0; // 0x0
     field public static final int UI_MODE_NIGHT_YES = 32; // 0x20
+    field public static final int UI_MODE_TYPE_APPLIANCE = 5; // 0x5
     field public static final int UI_MODE_TYPE_CAR = 3; // 0x3
     field public static final int UI_MODE_TYPE_DESK = 2; // 0x2
     field public static final int UI_MODE_TYPE_MASK = 15; // 0xf
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index fddb429..f4c5359 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.Intent;
+import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
@@ -109,6 +110,8 @@
             runStartService();
         } else if (op.equals("force-stop")) {
             runForceStop();
+        } else if (op.equals("clear-data")) {
+            runClearData();
         } else if (op.equals("kill")) {
             runKill();
         } else if (op.equals("kill-all")) {
@@ -212,6 +215,19 @@
                     list[i] = Long.valueOf(strings[i]);
                 }
                 intent.putExtra(key, list);
+            } else if (opt.equals("--ef")) {
+                String key = nextArgRequired();
+                String value = nextArgRequired();
+                intent.putExtra(key, Float.valueOf(value));
+            } else if (opt.equals("--efa")) {
+                String key = nextArgRequired();
+                String value = nextArgRequired();
+                String[] strings = value.split(",");
+                float[] list = new float[strings.length];
+                for (int i = 0; i < strings.length; i++) {
+                    list[i] = Float.valueOf(strings[i]);
+                }
+                intent.putExtra(key, list);
             } else if (opt.equals("--ez")) {
                 String key = nextArgRequired();
                 String value = nextArgRequired();
@@ -527,6 +543,31 @@
         mAm.killAllBackgroundProcesses();
     }
 
+    class ClearUserDataObserver extends IPackageDataObserver.Stub {
+        public int status = -1;
+
+        public void onRemoveCompleted(final String packageName, final boolean succeeded) {
+            synchronized (this) {
+                status = succeeded ? 0 : 1;
+                notify();
+            }
+        }
+    }
+
+    private void runClearData() throws Exception {
+        ClearUserDataObserver observer = new ClearUserDataObserver();
+        mAm.clearApplicationUserData(nextArgRequired(), observer);
+        synchronized (observer) {
+            while (observer.status < 0) {
+                try {
+                    observer.wait();
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        System.exit(observer.status);
+    }
+
     private void sendBroadcast() throws Exception {
         Intent intent = makeIntent();
         IntentReceiver receiver = new IntentReceiver();
@@ -1252,6 +1293,7 @@
                 "               [--R COUNT] [-S] <INTENT>\n" +
                 "       am startservice <INTENT>\n" +
                 "       am force-stop <PACKAGE>\n" +
+                "       am clear-data <PACKAGE>\n" +
                 "       am kill <PACKAGE>\n" +
                 "       am kill-all\n" +
                 "       am broadcast <INTENT>\n" +
@@ -1287,6 +1329,8 @@
                 "\n" +
                 "am kill-all: Kill all background processes.\n" +
                 "\n" +
+                "am clear-data: clear the user data associated with <PACKAGE>.\n" +
+                "\n" +
                 "am broadcast: send a broadcast Intent.\n" +
                 "\n" +
                 "am instrument: start an Instrumentation.  Typically this target <COMPONENT>\n" +
@@ -1330,9 +1374,11 @@
                 "    [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
                 "    [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
                 "    [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
+                "    [--ef <EXTRA_KEY> <EXTRA_FLOAT_VALUE> ...]\n" +
                 "    [--eu <EXTRA_KEY> <EXTRA_URI_VALUE> ...]\n" +
                 "    [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
                 "    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
+                "    [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
                 "    [-n <COMPONENT>] [-f <FLAGS>]\n" +
                 "    [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
                 "    [--debug-log-resolution] [--exclude-stopped-packages]\n" +
diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c
index 2df450f3..e3ce1dc 100644
--- a/cmds/servicemanager/service_manager.c
+++ b/cmds/servicemanager/service_manager.c
@@ -46,6 +46,8 @@
     { AID_RADIO, "isms" },
     { AID_RADIO, "iphonesubinfo" },
     { AID_RADIO, "simphonebook" },
+    { AID_MEDIA, "common_time.clock" },
+    { AID_MEDIA, "common_time.config" },
 };
 
 void *svcmgr_handle;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2bf1fb7..bb483d1 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;
@@ -427,6 +429,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/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 71f6445..0c22740 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -168,7 +168,7 @@
      * {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL},
      * {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, or
      * {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR}, or
-     * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_TV}.
+     * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_APPLIANCE}.
      */
     public int getCurrentModeType() {
         if (mService != null) {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index b1c1f30..a2b495f 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -437,7 +437,12 @@
      */
     public WallpaperInfo getWallpaperInfo() {
         try {
-            return sGlobals.mService.getWallpaperInfo();
+            if (sGlobals.mService == null) {
+                Log.w(TAG, "WallpaperService not running");
+                return null;
+            } else {
+                return sGlobals.mService.getWallpaperInfo();
+            }
         } catch (RemoteException e) {
             return null;
         }
@@ -455,6 +460,10 @@
      * wallpaper.
      */
     public void setResource(int resid) throws IOException {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            return;
+        }
         try {
             Resources resources = mContext.getResources();
             /* Set the wallpaper to the default values */
@@ -487,6 +496,10 @@
      * wallpaper.
      */
     public void setBitmap(Bitmap bitmap) throws IOException {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            return;
+        }
         try {
             ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
             if (fd == null) {
@@ -519,6 +532,10 @@
      * wallpaper.
      */
     public void setStream(InputStream data) throws IOException {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            return;
+        }
         try {
             ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null);
             if (fd == null) {
@@ -562,6 +579,10 @@
      * mandatory.
      */
     public int getDesiredMinimumWidth() {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            return 0;
+        }
         try {
             return sGlobals.mService.getWidthHint();
         } catch (RemoteException e) {
@@ -585,6 +606,10 @@
      * mandatory.
      */
     public int getDesiredMinimumHeight() {
+        if (sGlobals.mService == null) {
+            Log.w(TAG, "WallpaperService not running");
+            return 0;
+        }
         try {
             return sGlobals.mService.getHeightHint();
         } catch (RemoteException e) {
@@ -603,7 +628,11 @@
      */
     public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) {
         try {
-            sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
+            if (sGlobals.mService == null) {
+                Log.w(TAG, "WallpaperService not running");
+            } else {
+                sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight);
+            }
         } catch (RemoteException e) {
             // Ignore
         }
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/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 5c3a17a..6015668 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -228,6 +228,7 @@
     public static final int UI_MODE_TYPE_DESK = 0x02;
     public static final int UI_MODE_TYPE_CAR = 0x03;
     public static final int UI_MODE_TYPE_TELEVISION = 0x04;
+    public static final int UI_MODE_TYPE_APPLIANCE = 0x05;
 
     public static final int UI_MODE_NIGHT_MASK = 0x30;
     public static final int UI_MODE_NIGHT_UNDEFINED = 0x00;
@@ -239,7 +240,8 @@
      * <p>The {@link #UI_MODE_TYPE_MASK} bits define the overall ui mode of the
      * device. They may be one of {@link #UI_MODE_TYPE_UNDEFINED},
      * {@link #UI_MODE_TYPE_NORMAL}, {@link #UI_MODE_TYPE_DESK},
-     * or {@link #UI_MODE_TYPE_CAR}.
+     * {@link #UI_MODE_TYPE_CAR}, {@link #UI_MODE_TYPE_TELEVISION}, or
+     * {@link #UI_MODE_TYPE_APPLIANCE}.
      *
      * <p>The {@link #UI_MODE_NIGHT_MASK} defines whether the screen
      * is in a special mode. They may be one of {@link #UI_MODE_NIGHT_UNDEFINED},
@@ -391,6 +393,7 @@
             case UI_MODE_TYPE_DESK: sb.append(" desk"); break;
             case UI_MODE_TYPE_CAR: sb.append(" car"); break;
             case UI_MODE_TYPE_TELEVISION: sb.append(" television"); break;
+            case UI_MODE_TYPE_APPLIANCE: sb.append(" appliance"); break;
             default: sb.append(" uimode="); sb.append(uiMode&UI_MODE_TYPE_MASK); break;
         }
         switch ((uiMode&UI_MODE_NIGHT_MASK)) {
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..5ef122b
--- /dev/null
+++ b/core/java/android/hardware/SerialPort.java
@@ -0,0 +1,130 @@
+/*
+ * 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");
+        }
+    }
+
+    /**
+     * Sends a stream of zero valued bits for 0.25 to 0.5 seconds
+     */
+    public void sendBreak() {
+        native_send_break();
+    }
+
+    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;
+    private native void native_send_break();
+}
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 21ecc22..b1dd62c 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -49,6 +49,7 @@
     private LinkCapabilities mLinkCapabilities;
     private NetworkInfo mNetworkInfo;
     private InterfaceObserver mInterfaceObserver;
+    private String mHwAddr;
 
     /* For sending events to connectivity service handler */
     private Handler mCsHandler;
@@ -74,15 +75,13 @@
             if (mIface.equals(iface) && mLinkUp != up) {
                 Log.d(TAG, "Interface " + iface + " link " + (up ? "up" : "down"));
                 mLinkUp = up;
+                mTracker.mNetworkInfo.setIsAvailable(up);
 
                 // use DHCP
                 if (up) {
                     mTracker.reconnect();
                 } else {
-                    NetworkUtils.stopDhcp(mIface);
-                    mTracker.mNetworkInfo.setIsAvailable(false);
-                    mTracker.mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED,
-                                                           null, null);
+                    mTracker.disconnect();
                 }
             }
         }
@@ -104,10 +103,6 @@
         mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORKTYPE, "");
         mLinkProperties = new LinkProperties();
         mLinkCapabilities = new LinkCapabilities();
-        mLinkUp = false;
-
-        mNetworkInfo.setIsAvailable(false);
-        setTeardownRequested(false);
     }
 
     private void interfaceAdded(String iface) {
@@ -116,7 +111,7 @@
 
         Log.d(TAG, "Adding " + iface);
 
-        synchronized(mIface) {
+        synchronized(this) {
             if(!mIface.isEmpty())
                 return;
             mIface = iface;
@@ -125,21 +120,15 @@
         mNetworkInfo.setIsAvailable(true);
         Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
         msg.sendToTarget();
-
-        runDhcp();
     }
 
-    private void interfaceRemoved(String iface) {
-        if (!iface.equals(mIface))
-            return;
-
-        Log.d(TAG, "Removing " + iface);
+    public void disconnect() {
 
         NetworkUtils.stopDhcp(mIface);
 
         mLinkProperties.clear();
         mNetworkInfo.setIsAvailable(false);
-        mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
+        mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
 
         Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
         msg.sendToTarget();
@@ -147,6 +136,21 @@
         msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
         msg.sendToTarget();
 
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        INetworkManagementService service = INetworkManagementService.Stub.asInterface(b);
+        try {
+            service.clearInterfaceAddresses(mIface);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to clear addresses or disable ipv6" + e);
+        }
+    }
+
+    private void interfaceRemoved(String iface) {
+        if (!iface.equals(mIface))
+            return;
+
+        Log.d(TAG, "Removing " + iface);
+        disconnect();
         mIface = "";
     }
 
@@ -161,7 +165,7 @@
                 mLinkProperties = dhcpInfoInternal.makeLinkProperties();
                 mLinkProperties.setInterfaceName(mIface);
 
-                mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+                mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
                 Message msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
                 msg.sendToTarget();
             }
@@ -208,8 +212,15 @@
             for (String iface : ifaces) {
                 if (iface.matches(sIfaceMatch)) {
                     mIface = iface;
+                    service.setInterfaceUp(iface);
                     InterfaceConfiguration config = service.getInterfaceConfig(iface);
                     mLinkUp = config.isActive();
+                    if (config != null && mHwAddr == null) {
+                        mHwAddr = config.hwAddr;
+                        if (mHwAddr != null) {
+                            mNetworkInfo.setExtraInfo(mHwAddr);
+                        }
+                    }
                     reconnect();
                     break;
                 }
@@ -239,9 +250,11 @@
      * Re-enable connectivity to a network after a {@link #teardown()}.
      */
     public boolean reconnect() {
-        mTeardownRequested.set(false);
-        runDhcp();
-        return true;
+        if (mLinkUp) {
+            mTeardownRequested.set(false);
+            runDhcp();
+        }
+        return mLinkUp;
     }
 
     /**
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 537750a..1e645a1 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -346,6 +346,18 @@
     }
 
     /**
+     * Set the extraInfo field.
+     * @param extraInfo an optional {@code String} providing addditional network state
+     * information passed up from the lower networking layers.
+     * @hide
+     */
+    public void setExtraInfo(String extraInfo) {
+        synchronized (this) {
+            this.mExtraInfo = extraInfo;
+        }
+    }
+
+    /**
      * Report the reason an attempt to establish connectivity failed,
      * if one is available.
      * @return the reason for failure, or null if not available
diff --git a/core/java/android/os/CommonClock.java b/core/java/android/os/CommonClock.java
new file mode 100644
index 0000000..e728144
--- /dev/null
+++ b/core/java/android/os/CommonClock.java
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) 2012 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.os;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetSocketAddress;
+import java.util.NoSuchElementException;
+import static libcore.io.OsConstants.*;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+class CommonTimeUtils {
+    /**
+     * Successful operation.
+     */
+    public static final int SUCCESS = 0;
+    /**
+     * Unspecified error.
+     */
+    public static final int ERROR = -1;
+    /**
+     * Operation failed due to bad parameter value.
+     */
+    public static final int ERROR_BAD_VALUE = -4;
+    /**
+     * Operation failed due to dead remote object.
+     */
+    public static final int ERROR_DEAD_OBJECT = -7;
+
+    public CommonTimeUtils(IBinder remote, String interfaceDesc) {
+        mRemote = remote;
+        mInterfaceDesc = interfaceDesc;
+    }
+
+    public int transactGetInt(int method_code, int error_ret_val)
+    throws RemoteException {
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+        int ret_val;
+
+        try {
+            int res;
+            data.writeInterfaceToken(mInterfaceDesc);
+            mRemote.transact(method_code, data, reply, 0);
+
+            res = reply.readInt();
+            ret_val = (0 == res) ? reply.readInt() : error_ret_val;
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+
+        return ret_val;
+    }
+
+    public int transactSetInt(int method_code, int val) {
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+
+        try {
+            data.writeInterfaceToken(mInterfaceDesc);
+            data.writeInt(val);
+            mRemote.transact(method_code, data, reply, 0);
+
+            return reply.readInt();
+        }
+        catch (RemoteException e) {
+            return ERROR_DEAD_OBJECT;
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+    }
+
+    public long transactGetLong(int method_code, long error_ret_val)
+    throws RemoteException {
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+        long ret_val;
+
+        try {
+            int res;
+            data.writeInterfaceToken(mInterfaceDesc);
+            mRemote.transact(method_code, data, reply, 0);
+
+            res = reply.readInt();
+            ret_val = (0 == res) ? reply.readLong() : error_ret_val;
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+
+        return ret_val;
+    }
+
+    public int transactSetLong(int method_code, long val) {
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+
+        try {
+            data.writeInterfaceToken(mInterfaceDesc);
+            data.writeLong(val);
+            mRemote.transact(method_code, data, reply, 0);
+
+            return reply.readInt();
+        }
+        catch (RemoteException e) {
+            return ERROR_DEAD_OBJECT;
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+    }
+
+    public String transactGetString(int method_code, String error_ret_val)
+    throws RemoteException {
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+        String ret_val;
+
+        try {
+            int res;
+            data.writeInterfaceToken(mInterfaceDesc);
+            mRemote.transact(method_code, data, reply, 0);
+
+            res = reply.readInt();
+            ret_val = (0 == res) ? reply.readString() : error_ret_val;
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+
+        return ret_val;
+    }
+
+    public int transactSetString(int method_code, String val) {
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+
+        try {
+            data.writeInterfaceToken(mInterfaceDesc);
+            data.writeString(val);
+            mRemote.transact(method_code, data, reply, 0);
+
+            return reply.readInt();
+        }
+        catch (RemoteException e) {
+            return ERROR_DEAD_OBJECT;
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+    }
+
+    public InetSocketAddress transactGetSockaddr(int method_code)
+    throws RemoteException {
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+        InetSocketAddress ret_val = null;
+
+        try {
+            int res;
+            data.writeInterfaceToken(mInterfaceDesc);
+            mRemote.transact(method_code, data, reply, 0);
+
+            res = reply.readInt();
+            if (0 == res) {
+                int type;
+                int port = 0;
+                String addrStr = null;
+
+                type = reply.readInt();
+
+                if (AF_INET == type) {
+                    int addr = reply.readInt();
+                    port = reply.readInt();
+                    addrStr = String.format("%d.%d.%d.%d", (addr >> 24) & 0xFF,
+                                                           (addr >> 16) & 0xFF,
+                                                           (addr >>  8) & 0xFF,
+                                                            addr        & 0xFF);
+                } else if (AF_INET6 == type) {
+                    int addr1 = reply.readInt();
+                    int addr2 = reply.readInt();
+                    int addr3 = reply.readInt();
+                    int addr4 = reply.readInt();
+
+                    port = reply.readInt();
+
+                    int flowinfo = reply.readInt();
+                    int scope_id = reply.readInt();
+
+                    addrStr = String.format("[%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X]",
+                                            (addr1 >> 16) & 0xFFFF, addr1 & 0xFFFF,
+                                            (addr2 >> 16) & 0xFFFF, addr2 & 0xFFFF,
+                                            (addr3 >> 16) & 0xFFFF, addr3 & 0xFFFF,
+                                            (addr4 >> 16) & 0xFFFF, addr4 & 0xFFFF);
+                }
+
+                if (null != addrStr) {
+                    ret_val = new InetSocketAddress(addrStr, port);
+                }
+            }
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+
+        return ret_val;
+    }
+
+    public int transactSetSockaddr(int method_code, InetSocketAddress addr) {
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+        int ret_val = ERROR;
+
+        try {
+            data.writeInterfaceToken(mInterfaceDesc);
+
+            if (null == addr) {
+                data.writeInt(0);
+            } else {
+                data.writeInt(1);
+                final InetAddress a = addr.getAddress();
+                final byte[]      b = a.getAddress();
+                final int         p = addr.getPort();
+
+                if (a instanceof Inet4Address) {
+                    int v4addr = (((int)b[0] & 0xFF) << 24) |
+                                 (((int)b[1] & 0xFF) << 16) |
+                                 (((int)b[2] & 0xFF) << 8) |
+                                  ((int)b[3] & 0xFF);
+
+                    data.writeInt(AF_INET);
+                    data.writeInt(v4addr);
+                    data.writeInt(p);
+                } else
+                if (a instanceof Inet6Address) {
+                    int i;
+                    Inet6Address v6 = (Inet6Address)a;
+                    data.writeInt(AF_INET6);
+                    for (i = 0; i < 4; ++i) {
+                        int aword = (((int)b[(i*4) + 0] & 0xFF) << 24) |
+                                    (((int)b[(i*4) + 1] & 0xFF) << 16) |
+                                    (((int)b[(i*4) + 2] & 0xFF) << 8) |
+                                     ((int)b[(i*4) + 3] & 0xFF);
+                        data.writeInt(aword);
+                    }
+                    data.writeInt(p);
+                    data.writeInt(0);   // flow info
+                    data.writeInt(v6.getScopeId());
+                } else {
+                    return ERROR_BAD_VALUE;
+                }
+            }
+
+            mRemote.transact(method_code, data, reply, 0);
+            ret_val = reply.readInt();
+        }
+        catch (RemoteException e) {
+            ret_val = ERROR_DEAD_OBJECT;
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+
+        return ret_val;
+    }
+
+    private IBinder mRemote;
+    private String mInterfaceDesc;
+};
+
+/**
+ * Used for accessing the android common time service's common clock and receiving notifications
+ * about common time synchronization status changes.
+ * @hide
+ */
+public class CommonClock {
+    /**
+     * Sentinel value returned by {@link #getTime()} and {@link #getEstimatedError()} when the
+     * common time service is not able to determine the current common time due to a lack of
+     * synchronization.
+     */
+    public static final long TIME_NOT_SYNCED = -1;
+
+    /**
+     * Sentinel value returned by {@link #getTimelineId()} when the common time service is not
+     * currently synced to any timeline.
+     */
+    public static final long INVALID_TIMELINE_ID = 0;
+
+    /**
+     * Sentinel value returned by {@link #getEstimatedError()} when the common time service is not
+     * currently synced to any timeline.
+     */
+    public static final int ERROR_ESTIMATE_UNKNOWN = 0x7FFFFFFF;
+
+    /**
+     * Value used by {@link #getState()} to indicate that there was an internal error while
+     * attempting to determine the state of the common time service.
+     */
+    public static final int STATE_INVALID = -1;
+
+    /**
+     * Value used by {@link #getState()} to indicate that the common time service is in its initial
+     * state and attempting to find the current timeline master, if any.  The service will
+     * transition to either {@link #STATE_CLIENT} if it finds an active master, or to
+     * {@link #STATE_MASTER} if no active master is found and this client becomes the master of a
+     * new timeline.
+     */
+    public static final int STATE_INITIAL = 0;
+
+    /**
+     * Value used by {@link #getState()} to indicate that the common time service is in its client
+     * state and is synchronizing its time to a different timeline master on the network.
+     */
+    public static final int STATE_CLIENT = 1;
+
+    /**
+     * Value used by {@link #getState()} to indicate that the common time service is in its master
+     * state and is serving as the timeline master for other common time service clients on the
+     * network.
+     */
+    public static final int STATE_MASTER = 2;
+
+    /**
+     * Value used by {@link #getState()} to indicate that the common time service is in its Ronin
+     * state.  Common time service instances in the client state enter the Ronin state after their
+     * timeline master becomes unreachable on the network.  Common time services who enter the Ronin
+     * state will begin a new master election for the timeline they were recently clients of.  As
+     * clients detect they are not the winner and drop out of the election, they will transition to
+     * the {@link #STATE_WAIT_FOR_ELECTION} state.  When there is only one client remaining in the
+     * election, it will assume ownership of the timeline and transition to the
+     * {@link #STATE_MASTER} state.  During the election, all clients will allow their timeline to
+     * drift without applying correction.
+     */
+    public static final int STATE_RONIN = 3;
+
+    /**
+     * Value used by {@link #getState()} to indicate that the common time service is waiting for a
+     * master election to conclude and for the new master to announce itself before transitioning to
+     * the {@link #STATE_CLIENT} state.  If no new master announces itself within the timeout
+     * threshold, the time service will transition back to the {@link #STATE_RONIN} state in order
+     * to restart the election.
+     */
+    public static final int STATE_WAIT_FOR_ELECTION = 4;
+
+    /**
+     * Name of the underlying native binder service
+     */
+    public static final String SERVICE_NAME = "common_time.clock";
+
+    /**
+     * Class constructor.
+     * @throws android.os.RemoteException
+     */
+    public CommonClock()
+    throws RemoteException {
+        mRemote = ServiceManager.getService(SERVICE_NAME);
+        if (null == mRemote)
+            throw new RemoteException();
+
+        mInterfaceDesc = mRemote.getInterfaceDescriptor();
+        mUtils = new CommonTimeUtils(mRemote, mInterfaceDesc);
+        mRemote.linkToDeath(mDeathHandler, 0);
+        registerTimelineChangeListener();
+    }
+
+    /**
+     * Handy class factory method.
+     */
+    static public CommonClock create() {
+        CommonClock retVal;
+
+        try {
+            retVal = new CommonClock();
+        }
+        catch (RemoteException e) {
+            retVal = null;
+        }
+
+        return retVal;
+    }
+
+    /**
+     * Release all native resources held by this {@link android.os.CommonClock} instance.  Once
+     * resources have been released, the {@link android.os.CommonClock} instance is disconnected from
+     * the native service and will throw a {@link android.os.RemoteException} if any of its
+     * methods are called.  Clients should always call release on their client instances before
+     * releasing their last Java reference to the instance.  Failure to do this will cause
+     * non-deterministic native resource reclamation and may cause the common time service to remain
+     * active on the network for longer than it should.
+     */
+    public void release() {
+        unregisterTimelineChangeListener();
+        if (null != mRemote) {
+            try {
+                mRemote.unlinkToDeath(mDeathHandler, 0);
+            }
+            catch (NoSuchElementException e) { }
+            mRemote = null;
+        }
+        mUtils = null;
+    }
+
+    /**
+     * Gets the common clock's current time.
+     *
+     * @return a signed 64-bit value representing the current common time in microseconds, or the
+     * special value {@link #TIME_NOT_SYNCED} if the common time service is currently not
+     * synchronized.
+     * @throws android.os.RemoteException
+     */
+    public long getTime()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetLong(METHOD_GET_COMMON_TIME, TIME_NOT_SYNCED);
+    }
+
+    /**
+     * Gets the current estimation of common clock's synchronization accuracy from the common time
+     * service.
+     *
+     * @return a signed 32-bit value representing the common time service's estimation of
+     * synchronization accuracy in microseconds, or the special value
+     * {@link #ERROR_ESTIMATE_UNKNOWN} if the common time service is currently not synchronized.
+     * Negative values indicate that the local server estimates that the nominal common time is
+     * behind the local server's time (in other words, the local clock is running fast) Positive
+     * values indicate that the local server estimates that the nominal common time is ahead of the
+     * local server's time (in other words, the local clock is running slow)
+     * @throws android.os.RemoteException
+     */
+    public int getEstimatedError()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetInt(METHOD_GET_ESTIMATED_ERROR, ERROR_ESTIMATE_UNKNOWN);
+    }
+
+    /**
+     * Gets the ID of the timeline the common time service is currently synchronizing its clock to.
+     *
+     * @return a long representing the unique ID of the timeline the common time service is
+     * currently synchronizing with, or {@link #INVALID_TIMELINE_ID} if the common time service is
+     * currently not synchronized.
+     * @throws android.os.RemoteException
+     */
+    public long getTimelineId()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetLong(METHOD_GET_TIMELINE_ID, INVALID_TIMELINE_ID);
+    }
+
+    /**
+     * Gets the current state of this clock's common time service in the the master election
+     * algorithm.
+     *
+     * @return a integer indicating the current state of the this clock's common time service in the
+     * master election algorithm or {@link #STATE_INVALID} if there is an internal error.
+     * @throws android.os.RemoteException
+     */
+    public int getState()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetInt(METHOD_GET_STATE, STATE_INVALID);
+    }
+
+    /**
+     * Gets the IP address and UDP port of the current timeline master.
+     *
+     * @return an InetSocketAddress containing the IP address and UDP port of the current timeline
+     * master, or null if there is no current master.
+     * @throws android.os.RemoteException
+     */
+    public InetSocketAddress getMasterAddr()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetSockaddr(METHOD_GET_MASTER_ADDRESS);
+    }
+
+    /**
+     * The OnTimelineChangedListener interface defines a method called by the
+     * {@link android.os.CommonClock} instance to indicate that the time synchronization service has
+     * either synchronized with a new timeline, or is no longer a member of any timeline.  The
+     * client application can implement this interface and register the listener with the
+     * {@link #setTimelineChangedListener(OnTimelineChangedListener)} method.
+     */
+    public interface OnTimelineChangedListener  {
+        /**
+         * Method called when the time service's timeline has changed.
+         *
+         * @param newTimelineId a long which uniquely identifies the timeline the time
+         * synchronization service is now a member of, or {@link #INVALID_TIMELINE_ID} if the the
+         * service is not synchronized to any timeline.
+         */
+        void onTimelineChanged(long newTimelineId);
+    }
+
+    /**
+     * Registers an OnTimelineChangedListener interface.
+     * <p>Call this method with a null listener to stop receiving server death notifications.
+     */
+    public void setTimelineChangedListener(OnTimelineChangedListener listener) {
+        synchronized (mListenerLock) {
+            mTimelineChangedListener = listener;
+        }
+    }
+
+    /**
+     * The OnServerDiedListener interface defines a method called by the
+     * {@link android.os.CommonClock} instance to indicate that the connection to the native media
+     * server has been broken and that the {@link android.os.CommonClock} instance will need to be
+     * released and re-created.  The client application can implement this interface and register
+     * the listener with the {@link #setServerDiedListener(OnServerDiedListener)} method.
+     */
+    public interface OnServerDiedListener  {
+        /**
+         * Method called when the native media server has died.  <p>If the native common time
+         * service encounters a fatal error and needs to restart, the binder connection from the
+         * {@link android.os.CommonClock} instance to the common time service will be broken.  To
+         * restore functionality, clients should {@link #release()} their old visualizer and create
+         * a new instance.
+         */
+        void onServerDied();
+    }
+
+    /**
+     * Registers an OnServerDiedListener interface.
+     * <p>Call this method with a null listener to stop receiving server death notifications.
+     */
+    public void setServerDiedListener(OnServerDiedListener listener) {
+        synchronized (mListenerLock) {
+            mServerDiedListener = listener;
+        }
+    }
+
+    protected void finalize() throws Throwable { release(); }
+
+    private void throwOnDeadServer() throws RemoteException {
+        if ((null == mRemote) || (null == mUtils))
+            throw new RemoteException();
+    }
+
+    private final Object mListenerLock = new Object();
+    private OnTimelineChangedListener mTimelineChangedListener = null;
+    private OnServerDiedListener mServerDiedListener = null;
+
+    private IBinder mRemote = null;
+    private String mInterfaceDesc = "";
+    private CommonTimeUtils mUtils;
+
+    private IBinder.DeathRecipient mDeathHandler = new IBinder.DeathRecipient() {
+        public void binderDied() {
+            synchronized (mListenerLock) {
+                if (null != mServerDiedListener)
+                    mServerDiedListener.onServerDied();
+            }
+        }
+    };
+
+    private class TimelineChangedListener extends Binder {
+        @Override
+        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+        throws RemoteException {
+            switch (code) {
+                case METHOD_CBK_ON_TIMELINE_CHANGED:
+                    data.enforceInterface(DESCRIPTOR);
+                    long timelineId = data.readLong();
+                    synchronized (mListenerLock) {
+                        if (null != mTimelineChangedListener)
+                            mTimelineChangedListener.onTimelineChanged(timelineId);
+                    }
+                    return true;
+            }
+
+            return super.onTransact(code, data, reply, flags);
+        }
+
+        private static final String DESCRIPTOR = "android.os.ICommonClockListener";
+    };
+
+    private TimelineChangedListener mCallbackTgt = null;
+
+    private void registerTimelineChangeListener() throws RemoteException {
+        if (null != mCallbackTgt)
+            return;
+
+        boolean success = false;
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+        mCallbackTgt = new TimelineChangedListener();
+
+        try {
+            data.writeInterfaceToken(mInterfaceDesc);
+            data.writeStrongBinder(mCallbackTgt);
+            mRemote.transact(METHOD_REGISTER_LISTENER, data, reply, 0);
+            success = (0 == reply.readInt());
+        }
+        catch (RemoteException e) {
+            success = false;
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+
+        // Did we catch a remote exception or fail to register our callback target?  If so, our
+        // object must already be dead (or be as good as dead).  Clear out all of our state so that
+        // our other methods will properly indicate a dead object.
+        if (!success) {
+            mCallbackTgt = null;
+            mRemote = null;
+            mUtils = null;
+        }
+    }
+
+    private void unregisterTimelineChangeListener() {
+        if (null == mCallbackTgt)
+            return;
+
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+
+        try {
+            data.writeInterfaceToken(mInterfaceDesc);
+            data.writeStrongBinder(mCallbackTgt);
+            mRemote.transact(METHOD_UNREGISTER_LISTENER, data, reply, 0);
+        }
+        catch (RemoteException e) { }
+        finally {
+            reply.recycle();
+            data.recycle();
+            mCallbackTgt = null;
+        }
+    }
+
+    private static final int METHOD_IS_COMMON_TIME_VALID = IBinder.FIRST_CALL_TRANSACTION;
+    private static final int METHOD_COMMON_TIME_TO_LOCAL_TIME = METHOD_IS_COMMON_TIME_VALID + 1;
+    private static final int METHOD_LOCAL_TIME_TO_COMMON_TIME = METHOD_COMMON_TIME_TO_LOCAL_TIME + 1;
+    private static final int METHOD_GET_COMMON_TIME = METHOD_LOCAL_TIME_TO_COMMON_TIME + 1;
+    private static final int METHOD_GET_COMMON_FREQ = METHOD_GET_COMMON_TIME + 1;
+    private static final int METHOD_GET_LOCAL_TIME = METHOD_GET_COMMON_FREQ + 1;
+    private static final int METHOD_GET_LOCAL_FREQ = METHOD_GET_LOCAL_TIME + 1;
+    private static final int METHOD_GET_ESTIMATED_ERROR = METHOD_GET_LOCAL_FREQ + 1;
+    private static final int METHOD_GET_TIMELINE_ID = METHOD_GET_ESTIMATED_ERROR + 1;
+    private static final int METHOD_GET_STATE = METHOD_GET_TIMELINE_ID + 1;
+    private static final int METHOD_GET_MASTER_ADDRESS = METHOD_GET_STATE + 1;
+    private static final int METHOD_REGISTER_LISTENER = METHOD_GET_MASTER_ADDRESS + 1;
+    private static final int METHOD_UNREGISTER_LISTENER = METHOD_REGISTER_LISTENER + 1;
+
+    private static final int METHOD_CBK_ON_TIMELINE_CHANGED = IBinder.FIRST_CALL_TRANSACTION;
+}
diff --git a/core/java/android/os/CommonTimeConfig.java b/core/java/android/os/CommonTimeConfig.java
new file mode 100644
index 0000000..6b0f4fc
--- /dev/null
+++ b/core/java/android/os/CommonTimeConfig.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright (C) 2012 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.os;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.NoSuchElementException;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+/**
+ * Used for configuring and controlling the status of the android common time service.
+ * @hide
+ */
+public class CommonTimeConfig {
+    /**
+     * Successful operation.
+     */
+    public static final int SUCCESS = 0;
+    /**
+     * Unspecified error.
+     */
+    public static final int ERROR = -1;
+    /**
+     * Operation failed due to bad parameter value.
+     */
+    public static final int ERROR_BAD_VALUE = -4;
+    /**
+     * Operation failed due to dead remote object.
+     */
+    public static final int ERROR_DEAD_OBJECT = -7;
+
+    /**
+     * Sentinel value returned by {@link #getMasterElectionGroupId()} when an error occurs trying to
+     * fetch the master election group.
+     */
+    public static final long INVALID_GROUP_ID = -1;
+
+    /**
+     * Name of the underlying native binder service
+     */
+    public static final String SERVICE_NAME = "common_time.config";
+
+    /**
+     * Class constructor.
+     * @throws android.os.RemoteException
+     */
+    public CommonTimeConfig()
+    throws RemoteException {
+        mRemote = ServiceManager.getService(SERVICE_NAME);
+        if (null == mRemote)
+            throw new RemoteException();
+
+        mInterfaceDesc = mRemote.getInterfaceDescriptor();
+        mUtils = new CommonTimeUtils(mRemote, mInterfaceDesc);
+        mRemote.linkToDeath(mDeathHandler, 0);
+    }
+
+    /**
+     * Handy class factory method.
+     */
+    static public CommonTimeConfig create() {
+        CommonTimeConfig retVal;
+
+        try {
+            retVal = new CommonTimeConfig();
+        }
+        catch (RemoteException e) {
+            retVal = null;
+        }
+
+        return retVal;
+    }
+
+    /**
+     * Release all native resources held by this {@link android.os.CommonTimeConfig} instance.  Once
+     * resources have been released, the {@link android.os.CommonTimeConfig} instance is
+     * disconnected from the native service and will throw a {@link android.os.RemoteException} if
+     * any of its methods are called.  Clients should always call release on their client instances
+     * before releasing their last Java reference to the instance.  Failure to do this will cause
+     * non-deterministic native resource reclamation and may cause the common time service to remain
+     * active on the network for longer than it should.
+     */
+    public void release() {
+        if (null != mRemote) {
+            try {
+                mRemote.unlinkToDeath(mDeathHandler, 0);
+            }
+            catch (NoSuchElementException e) { }
+            mRemote = null;
+        }
+        mUtils = null;
+    }
+
+    /**
+     * Gets the current priority of the common time service used in the master election protocol.
+     *
+     * @return an 8 bit value indicating the priority of this common time service relative to other
+     * common time services operating in the same domain.
+     * @throws android.os.RemoteException
+     */
+    public byte getMasterElectionPriority()
+    throws RemoteException {
+        throwOnDeadServer();
+        return (byte)mUtils.transactGetInt(METHOD_GET_MASTER_ELECTION_PRIORITY, -1);
+    }
+
+    /**
+     * Sets the current priority of the common time service used in the master election protocol.
+     *
+     * @param priority priority of the common time service used in the master election protocol.
+     * Lower numbers are lower priority.
+     * @return {@link #SUCCESS} in case of success,
+     * {@link #ERROR} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+     */
+    public int setMasterElectionPriority(byte priority) {
+        if (checkDeadServer())
+            return ERROR_DEAD_OBJECT;
+        return mUtils.transactSetInt(METHOD_SET_MASTER_ELECTION_PRIORITY, priority);
+    }
+
+    /**
+     * Gets the IP endpoint used by the time service to participate in the master election protocol.
+     *
+     * @return an InetSocketAddress containing the IP address and UDP port being used by the
+     * system's common time service to participate in the master election protocol.
+     * @throws android.os.RemoteException
+     */
+    public InetSocketAddress getMasterElectionEndpoint()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetSockaddr(METHOD_GET_MASTER_ELECTION_ENDPOINT);
+    }
+
+    /**
+     * Sets the IP endpoint used by the common time service to participate in the master election
+     * protocol.
+     *
+     * @param ep The IP address and UDP port to be used by the common time service to participate in
+     * the master election protocol.  The supplied IP address must be either the broadcast or
+     * multicast address, unicast addresses are considered to be illegal values.
+     * @return {@link #SUCCESS} in case of success,
+     * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+     */
+    public int setMasterElectionEndpoint(InetSocketAddress ep) {
+        if (checkDeadServer())
+            return ERROR_DEAD_OBJECT;
+        return mUtils.transactSetSockaddr(METHOD_SET_MASTER_ELECTION_ENDPOINT, ep);
+    }
+
+    /**
+     * Gets the current group ID used by the common time service in the master election protocol.
+     *
+     * @return The 64-bit group ID of the common time service.
+     * @throws android.os.RemoteException
+     */
+    public long getMasterElectionGroupId()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetLong(METHOD_GET_MASTER_ELECTION_GROUP_ID, INVALID_GROUP_ID);
+    }
+
+    /**
+     * Sets the current group ID used by the common time service in the master election protocol.
+     *
+     * @param id The 64-bit group ID of the common time service.
+     * @return {@link #SUCCESS} in case of success,
+     * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+     */
+    public int setMasterElectionGroupId(long id) {
+        if (checkDeadServer())
+            return ERROR_DEAD_OBJECT;
+        return mUtils.transactSetLong(METHOD_SET_MASTER_ELECTION_GROUP_ID, id);
+    }
+
+    /**
+     * Gets the name of the network interface which the common time service attempts to bind to.
+     *
+     * @return a string with the network interface name which the common time service is bound to,
+     * or null if the service is currently unbound.  Examples of interface names are things like
+     * "eth0", or "wlan0".
+     * @throws android.os.RemoteException
+     */
+    public String getInterfaceBinding()
+    throws RemoteException {
+        throwOnDeadServer();
+
+        String ifaceName = mUtils.transactGetString(METHOD_GET_INTERFACE_BINDING, null);
+
+        if ((null != ifaceName) && (0 == ifaceName.length()))
+                return null;
+
+        return ifaceName;
+    }
+
+    /**
+     * Sets the name of the network interface which the common time service should attempt to bind
+     * to.
+     *
+     * @param ifaceName The name of the network interface ("eth0", "wlan0", etc...) wich the common
+     * time service should attempt to bind to, or null to force the common time service to unbind
+     * from the network and run in networkless mode.
+     * @return {@link #SUCCESS} in case of success,
+     * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+     */
+    public int setNetworkBinding(String ifaceName) {
+        if (checkDeadServer())
+            return ERROR_DEAD_OBJECT;
+
+        return mUtils.transactSetString(METHOD_SET_INTERFACE_BINDING,
+                                       (null == ifaceName) ? "" : ifaceName);
+    }
+
+    /**
+     * Gets the amount of time the common time service will wait between master announcements when
+     * it is the timeline master.
+     *
+     * @return The time (in milliseconds) between master announcements.
+     * @throws android.os.RemoteException
+     */
+    public int getMasterAnnounceInterval()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetInt(METHOD_GET_MASTER_ANNOUNCE_INTERVAL, -1);
+    }
+
+    /**
+     * Sets the amount of time the common time service will wait between master announcements when
+     * it is the timeline master.
+     *
+     * @param interval The time (in milliseconds) between master announcements.
+     * @return {@link #SUCCESS} in case of success,
+     * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+     */
+    public int setMasterAnnounceInterval(int interval) {
+        if (checkDeadServer())
+            return ERROR_DEAD_OBJECT;
+        return mUtils.transactSetInt(METHOD_SET_MASTER_ANNOUNCE_INTERVAL, interval);
+    }
+
+    /**
+     * Gets the amount of time the common time service will wait between time synchronization
+     * requests when it is the client of another common time service on the network.
+     *
+     * @return The time (in milliseconds) between time sync requests.
+     * @throws android.os.RemoteException
+     */
+    public int getClientSyncInterval()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetInt(METHOD_GET_CLIENT_SYNC_INTERVAL, -1);
+    }
+
+    /**
+     * Sets the amount of time the common time service will wait between time synchronization
+     * requests when it is the client of another common time service on the network.
+     *
+     * @param interval The time (in milliseconds) between time sync requests.
+     * @return {@link #SUCCESS} in case of success,
+     * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+     */
+    public int setClientSyncInterval(int interval) {
+        if (checkDeadServer())
+            return ERROR_DEAD_OBJECT;
+        return mUtils.transactSetInt(METHOD_SET_CLIENT_SYNC_INTERVAL, interval);
+    }
+
+    /**
+     * Gets the panic threshold for the estimated error level of the common time service.  When the
+     * common time service's estimated error rises above this level, the service will panic and
+     * reset, causing a discontinuity in the currently synchronized timeline.
+     *
+     * @return The threshold (in microseconds) past which the common time service will panic.
+     * @throws android.os.RemoteException
+     */
+    public int getPanicThreshold()
+    throws RemoteException {
+        throwOnDeadServer();
+        return mUtils.transactGetInt(METHOD_GET_PANIC_THRESHOLD, -1);
+    }
+
+    /**
+     * Sets the panic threshold for the estimated error level of the common time service.  When the
+     * common time service's estimated error rises above this level, the service will panic and
+     * reset, causing a discontinuity in the currently synchronized timeline.
+     *
+     * @param threshold The threshold (in microseconds) past which the common time service will
+     * panic.
+     * @return {@link #SUCCESS} in case of success,
+     * {@link #ERROR}, {@link #ERROR_BAD_VALUE} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+     */
+    public int setPanicThreshold(int threshold) {
+        if (checkDeadServer())
+            return ERROR_DEAD_OBJECT;
+        return mUtils.transactSetInt(METHOD_SET_PANIC_THRESHOLD, threshold);
+    }
+
+    /**
+     * Gets the current state of the common time service's auto disable flag.
+     *
+     * @return The current state of the common time service's auto disable flag.
+     * @throws android.os.RemoteException
+     */
+    public boolean getAutoDisable()
+    throws RemoteException {
+        throwOnDeadServer();
+        return (1 == mUtils.transactGetInt(METHOD_GET_AUTO_DISABLE, 1));
+    }
+
+    /**
+     * Sets the current state of the common time service's auto disable flag.  When the time
+     * service's auto disable flag is set, it will automatically cease all network activity when
+     * it has no active local clients, resuming activity the next time the service has interested
+     * local clients.  When the auto disabled flag is cleared, the common time service will continue
+     * to participate the time synchronization group even when it has no active local clients.
+     *
+     * @param autoDisable The desired state of the common time service's auto disable flag.
+     * @return {@link #SUCCESS} in case of success,
+     * {@link #ERROR} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+     */
+    public int setAutoDisable(boolean autoDisable) {
+        if (checkDeadServer())
+            return ERROR_DEAD_OBJECT;
+
+        return mUtils.transactSetInt(METHOD_SET_AUTO_DISABLE, autoDisable ? 1 : 0);
+    }
+
+    /**
+     * At startup, the time service enters the initial state and remains there until it is given a
+     * network interface to bind to.  Common time will be unavailable to clients of the common time
+     * service until the service joins a network (even an empty network).  Devices may use the
+     * {@link #forceNetworklessMasterMode()} method to force a time service in the INITIAL state
+     * with no network configuration to assume MASTER status for a brand new timeline in order to
+     * allow clients of the common time service to operate, even though the device is isolated and
+     * not on any network.  When a networkless master does join a network, it will defer to any
+     * masters already on the network, or continue to maintain the timeline it made up during its
+     * networkless state if no other masters are detected.  Attempting to force a client into master
+     * mode while it is actively bound to a network will fail with the status code {@link #ERROR}
+     *
+     * @return {@link #SUCCESS} in case of success,
+     * {@link #ERROR} or {@link #ERROR_DEAD_OBJECT} in case of failure.
+     */
+    public int forceNetworklessMasterMode() {
+        android.os.Parcel data  = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+
+        try {
+            data.writeInterfaceToken(mInterfaceDesc);
+            mRemote.transact(METHOD_FORCE_NETWORKLESS_MASTER_MODE, data, reply, 0);
+
+            return reply.readInt();
+        }
+        catch (RemoteException e) {
+            return ERROR_DEAD_OBJECT;
+        }
+        finally {
+            reply.recycle();
+            data.recycle();
+        }
+    }
+
+    /**
+     * The OnServerDiedListener interface defines a method called by the
+     * {@link android.os.CommonTimeConfig} instance to indicate that the connection to the native
+     * media server has been broken and that the {@link android.os.CommonTimeConfig} instance will
+     * need to be released and re-created.  The client application can implement this interface and
+     * register the listener with the {@link #setServerDiedListener(OnServerDiedListener)} method.
+     */
+    public interface OnServerDiedListener  {
+        /**
+         * Method called when the native common time service has died.  <p>If the native common time
+         * service encounters a fatal error and needs to restart, the binder connection from the
+         * {@link android.os.CommonTimeConfig} instance to the common time service will be broken.
+         */
+        void onServerDied();
+    }
+
+    /**
+     * Registers an OnServerDiedListener interface.
+     * <p>Call this method with a null listener to stop receiving server death notifications.
+     */
+    public void setServerDiedListener(OnServerDiedListener listener) {
+        synchronized (mListenerLock) {
+            mServerDiedListener = listener;
+        }
+    }
+
+    protected void finalize() throws Throwable { release(); }
+
+    private boolean checkDeadServer() {
+        return ((null == mRemote) || (null == mUtils));
+    }
+
+    private void throwOnDeadServer() throws RemoteException {
+        if (checkDeadServer())
+            throw new RemoteException();
+    }
+
+    private final Object mListenerLock = new Object();
+    private OnServerDiedListener mServerDiedListener = null;
+
+    private IBinder mRemote = null;
+    private String mInterfaceDesc = "";
+    private CommonTimeUtils mUtils;
+
+    private IBinder.DeathRecipient mDeathHandler = new IBinder.DeathRecipient() {
+        public void binderDied() {
+            synchronized (mListenerLock) {
+                if (null != mServerDiedListener)
+                    mServerDiedListener.onServerDied();
+            }
+        }
+    };
+
+    private static final int METHOD_GET_MASTER_ELECTION_PRIORITY = IBinder.FIRST_CALL_TRANSACTION;
+    private static final int METHOD_SET_MASTER_ELECTION_PRIORITY = METHOD_GET_MASTER_ELECTION_PRIORITY + 1;
+    private static final int METHOD_GET_MASTER_ELECTION_ENDPOINT = METHOD_SET_MASTER_ELECTION_PRIORITY + 1;
+    private static final int METHOD_SET_MASTER_ELECTION_ENDPOINT = METHOD_GET_MASTER_ELECTION_ENDPOINT + 1;
+    private static final int METHOD_GET_MASTER_ELECTION_GROUP_ID = METHOD_SET_MASTER_ELECTION_ENDPOINT + 1;
+    private static final int METHOD_SET_MASTER_ELECTION_GROUP_ID = METHOD_GET_MASTER_ELECTION_GROUP_ID + 1;
+    private static final int METHOD_GET_INTERFACE_BINDING = METHOD_SET_MASTER_ELECTION_GROUP_ID + 1;
+    private static final int METHOD_SET_INTERFACE_BINDING = METHOD_GET_INTERFACE_BINDING + 1;
+    private static final int METHOD_GET_MASTER_ANNOUNCE_INTERVAL = METHOD_SET_INTERFACE_BINDING + 1;
+    private static final int METHOD_SET_MASTER_ANNOUNCE_INTERVAL = METHOD_GET_MASTER_ANNOUNCE_INTERVAL + 1;
+    private static final int METHOD_GET_CLIENT_SYNC_INTERVAL = METHOD_SET_MASTER_ANNOUNCE_INTERVAL + 1;
+    private static final int METHOD_SET_CLIENT_SYNC_INTERVAL = METHOD_GET_CLIENT_SYNC_INTERVAL + 1;
+    private static final int METHOD_GET_PANIC_THRESHOLD = METHOD_SET_CLIENT_SYNC_INTERVAL + 1;
+    private static final int METHOD_SET_PANIC_THRESHOLD = METHOD_GET_PANIC_THRESHOLD + 1;
+    private static final int METHOD_GET_AUTO_DISABLE = METHOD_SET_PANIC_THRESHOLD + 1;
+    private static final int METHOD_SET_AUTO_DISABLE = METHOD_GET_AUTO_DISABLE + 1;
+    private static final int METHOD_FORCE_NETWORKLESS_MASTER_MODE = METHOD_SET_AUTO_DISABLE + 1;
+}
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 73e8d98..43cf74e 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -26,6 +26,7 @@
 import java.io.FileNotFoundException;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.RandomAccessFile;
 import java.security.GeneralSecurityException;
 import java.security.PublicKey;
@@ -103,7 +104,12 @@
             Enumeration<? extends ZipEntry> entries = zip.entries();
             while (entries.hasMoreElements()) {
                 ZipEntry entry = entries.nextElement();
-                trusted.add(cf.generateCertificate(zip.getInputStream(entry)));
+                InputStream is = zip.getInputStream(entry);
+                try {
+                    trusted.add(cf.generateCertificate(is));
+                } finally {
+                    is.close();
+                }
             }
         } finally {
             zip.close();
@@ -162,8 +168,6 @@
 
             int commentSize = (footer[4] & 0xff) | ((footer[5] & 0xff) << 8);
             int signatureStart = (footer[0] & 0xff) | ((footer[1] & 0xff) << 8);
-            Log.v(TAG, String.format("comment size %d; signature start %d",
-                                     commentSize, signatureStart));
 
             byte[] eocd = new byte[commentSize + 22];
             raf.seek(fileLen - (commentSize + 22));
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index cdb622c..fbf512c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,6 +16,7 @@
 
 package android.os.storage;
 
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -534,6 +535,7 @@
      * @hide
      */
     public String getVolumeState(String mountPoint) {
+         if (mMountService == null) return Environment.MEDIA_REMOVED;
         try {
             return mMountService.getVolumeState(mountPoint);
         } catch (RemoteException e) {
@@ -547,6 +549,7 @@
      * @hide
      */
     public StorageVolume[] getVolumeList() {
+        if (mMountService == null) return new StorageVolume[0];
         try {
             Parcelable[] list = mMountService.getVolumeList();
             if (list == null) return new StorageVolume[0];
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1243251..987e53c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1473,6 +1473,12 @@
         public static final String VOLUME_BLUETOOTH_SCO = "volume_bluetooth_sco";
 
         /**
+         * Master volume (float in the range 0.0f to 1.0f).
+         * @hide
+         */
+        public static final String VOLUME_MASTER = "volume_master";
+
+        /**
          * Whether the notifications should use the ring volume (value of 1) or a separate
          * notification volume (value of 0). In most cases, users will have this enabled so the
          * notification and ringer volumes will be the same. However, power users can disable this
@@ -1797,6 +1803,12 @@
         public static final String LOCKSCREEN_SOUNDS_ENABLED = "lockscreen_sounds_enabled";
 
         /**
+         * Whether the lockscreen should be completely disabled.
+         * @hide
+         */
+        public static final String LOCKSCREEN_DISABLED = "lockscreen.disabled";
+
+        /**
          * URI for the low battery sound file.
          * @hide
          */
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index fecc8f9..78717b7 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -1743,6 +1743,7 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (getBluetoothStateInternal() != BluetoothAdapter.STATE_ON) {
+            pw.println("state: " + getBluetoothStateInternal());
             return;
         }
 
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 48fe0df..b6e37a6 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -91,6 +91,10 @@
     private static final int MSG_VIBRATE = 4;
     private static final int MSG_TIMEOUT = 5;
     private static final int MSG_RINGER_MODE_CHANGED = 6;
+    private static final int MSG_MUTE_CHANGED = 7;
+
+    // Pseudo stream type for master volume
+    private static final int STREAM_MASTER = -100;
 
     protected Context mContext;
     private AudioManager mAudioManager;
@@ -148,7 +152,13 @@
                 R.string.volume_icon_description_notification,
                 R.drawable.ic_audio_notification,
                 R.drawable.ic_audio_notification_mute,
-                true);
+                true),
+        // for now, use media resources for master volume
+        MasterStream(STREAM_MASTER,
+                R.string.volume_icon_description_media,
+                R.drawable.ic_audio_vol,
+                R.drawable.ic_audio_vol_mute,
+                false);
 
         int streamType;
         int descRes;
@@ -173,7 +183,8 @@
         StreamResources.VoiceStream,
         StreamResources.MediaStream,
         StreamResources.NotificationStream,
-        StreamResources.AlarmStream
+        StreamResources.AlarmStream,
+        StreamResources.MasterStream
     };
 
     /** Object that contains data for each slider */
@@ -195,6 +206,16 @@
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         mAudioService = volumeService;
 
+        // For now, only show master volume if master volume is supported
+        boolean useMasterVolume = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_useMasterVolume);
+        if (useMasterVolume) {
+            for (int i = 0; i < STREAMS.length; i++) {
+                StreamResources streamRes = STREAMS[i];
+                streamRes.show = (streamRes.streamType == STREAM_MASTER);
+            }
+        }
+
         LayoutInflater inflater = (LayoutInflater) context
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         View view = mView = inflater.inflate(R.layout.volume_adjust, null);
@@ -245,7 +266,7 @@
         mVibrator = new Vibrator();
 
         mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
-        mShowCombinedVolumes = !mVoiceCapable;
+        mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume;
         // If we don't want to show multiple volumes, hide the settings button and divider
         if (!mShowCombinedVolumes) {
             mMoreButton.setVisibility(View.GONE);
@@ -274,7 +295,43 @@
     }
 
     private boolean isMuted(int streamType) {
-        return mAudioManager.isStreamMute(streamType);
+        if (streamType == STREAM_MASTER) {
+            return mAudioService.isMasterMute();
+        } else {
+            return mAudioService.isStreamMute(streamType);
+        }
+    }
+
+    private int getStreamMaxVolume(int streamType) {
+        if (streamType == STREAM_MASTER) {
+            return mAudioService.getMasterMaxVolume();
+        } else {
+            return mAudioService.getStreamMaxVolume(streamType);
+        }
+    }
+
+    private int getStreamVolume(int streamType) {
+        if (streamType == STREAM_MASTER) {
+            return mAudioService.getMasterVolume();
+        } else {
+            return mAudioService.getStreamVolume(streamType);
+        }
+    }
+
+    private void setStreamVolume(int streamType, int index, int flags) {
+        if (streamType == STREAM_MASTER) {
+            mAudioService.setMasterVolume(index, flags);
+        } else {
+            mAudioService.setStreamVolume(streamType, index, flags);
+        }
+    }
+
+    private int getLastAudibleStreamVolume(int streamType) {
+        if (streamType == STREAM_MASTER) {
+            return mAudioService.getLastAudibleMasterVolume();
+        } else {
+            return mAudioService.getLastAudibleStreamVolume(streamType);
+        }
     }
 
     private void createSliders() {
@@ -301,7 +358,7 @@
             sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar);
             int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
                     streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
-            sc.seekbarView.setMax(mAudioManager.getStreamMaxVolume(streamType) + plusOne);
+            sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
             sc.seekbarView.setOnSeekBarChangeListener(this);
             sc.seekbarView.setTag(sc);
             mStreamControls.put(streamType, sc);
@@ -342,7 +399,7 @@
 
     /** Update the mute and progress state of a slider */
     private void updateSlider(StreamControl sc) {
-        sc.seekbarView.setProgress(mAudioManager.getLastAudibleStreamVolume(sc.streamType));
+        sc.seekbarView.setProgress(getLastAudibleStreamVolume(sc.streamType));
         final boolean muted = isMuted(sc.streamType);
         sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
         if (sc.streamType == AudioManager.STREAM_RING && muted
@@ -390,6 +447,23 @@
         obtainMessage(MSG_VOLUME_CHANGED, streamType, flags).sendToTarget();
     }
 
+    public void postMasterVolumeChanged(int flags) {
+        postVolumeChanged(STREAM_MASTER, flags);
+    }
+
+    public void postMuteChanged(int streamType, int flags) {
+        if (hasMessages(MSG_VOLUME_CHANGED)) return;
+        if (mStreamControls == null) {
+            createSliders();
+        }
+        removeMessages(MSG_FREE_RESOURCES);
+        obtainMessage(MSG_MUTE_CHANGED, streamType, flags).sendToTarget();
+    }
+
+    public void postMasterMuteChanged(int flags) {
+        postMuteChanged(STREAM_MASTER, flags);
+    }
+
     /**
      * Override this if you have other work to do when the volume changes (for
      * example, vibrating, playing a sound, etc.). Make sure to call through to
@@ -423,10 +497,22 @@
         resetTimeout();
     }
 
+    protected void onMuteChanged(int streamType, int flags) {
+
+        if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")");
+
+        StreamControl sc = mStreamControls.get(streamType);
+        if (sc != null) {
+            sc.icon.setImageResource(isMuted(sc.streamType) ? sc.iconMuteRes : sc.iconRes);
+        }
+
+        onVolumeChanged(streamType, flags);
+    }
+
     protected void onShowVolumeChanged(int streamType, int flags) {
-        int index = mAudioService.isStreamMute(streamType) ?
-                mAudioService.getLastAudibleStreamVolume(streamType)
-                : mAudioService.getStreamVolume(streamType);
+        int index = isMuted(streamType) ?
+                getLastAudibleStreamVolume(streamType)
+                : getStreamVolume(streamType);
 
         mRingIsSilent = false;
 
@@ -437,7 +523,7 @@
 
         // get max volume for progress bar
 
-        int max = mAudioService.getStreamMaxVolume(streamType);
+        int max = getStreamMaxVolume(streamType);
 
         switch (streamType) {
 
@@ -571,6 +657,7 @@
      * Lock on this VolumePanel instance as long as you use the returned ToneGenerator.
      */
     private ToneGenerator getOrCreateToneGenerator(int streamType) {
+        if (streamType == STREAM_MASTER) return null;
         synchronized (this) {
             if (mToneGenerators[streamType] == null) {
                 try {
@@ -620,6 +707,11 @@
                 break;
             }
 
+            case MSG_MUTE_CHANGED: {
+                onMuteChanged(msg.arg1, msg.arg2);
+                break;
+            }
+
             case MSG_FREE_RESOURCES: {
                 onFreeResources();
                 break;
@@ -671,8 +763,8 @@
         final Object tag = seekBar.getTag();
         if (fromUser && tag instanceof StreamControl) {
             StreamControl sc = (StreamControl) tag;
-            if (mAudioManager.getStreamVolume(sc.streamType) != progress) {
-                mAudioManager.setStreamVolume(sc.streamType, progress, 0);
+            if (getStreamVolume(sc.streamType) != progress) {
+                setStreamVolume(sc.streamType, progress, 0);
             }
         }
         resetTimeout();
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 71c5d26..10d5094 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -129,6 +129,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 c00e6c9..493aaec 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);
@@ -1165,6 +1166,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..7514f48
--- /dev/null
+++ b/core/jni/android_hardware_SerialPort.cpp
@@ -0,0 +1,276 @@
+/*
+ * 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;
+    // Disable output processing, including messing with end-of-line characters.
+    tio.c_oflag &= ~OPOST;
+    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 void
+android_hardware_SerialPort_send_break(JNIEnv *env, jobject thiz)
+{
+    int fd = env->GetIntField(thiz, field_context);
+    tcsendbreak(fd, 0);
+}
+
+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},
+    {"native_send_break",       "()V",  (void *)android_hardware_SerialPort_send_break},
+};
+
+int register_android_hardware_SerialPort(JNIEnv *env)
+{
+    jclass clazz = env->FindClass("android/hardware/SerialPort");
+    if (clazz == NULL) {
+        LOGE("Can't find android/hardware/SerialPort");
+        return -1;
+    }
+    field_context = env->GetFieldID(clazz, "mNativeContext", "I");
+    if (field_context == NULL) {
+        LOGE("Can't find SerialPort.mNativeContext");
+        return -1;
+    }
+
+    return AndroidRuntime::registerNativeMethods(env, "android/hardware/SerialPort",
+            method_table, NELEM(method_table));
+}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 0c5101f..18cb1f0 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -198,6 +198,38 @@
     return index;
 }
 
+static int
+android_media_AudioSystem_setMasterVolume(JNIEnv *env, jobject thiz, jfloat value)
+{
+    return check_AudioSystem_Command(AudioSystem::setMasterVolume(value));
+}
+
+static jfloat
+android_media_AudioSystem_getMasterVolume(JNIEnv *env, jobject thiz)
+{
+    float value;
+    if (AudioSystem::getMasterVolume(&value) != NO_ERROR) {
+        value = -1.0;
+    }
+    return value;
+}
+
+static int
+android_media_AudioSystem_setMasterMute(JNIEnv *env, jobject thiz, jboolean mute)
+{
+    return check_AudioSystem_Command(AudioSystem::setMasterMute(mute));
+}
+
+static jfloat
+android_media_AudioSystem_getMasterMute(JNIEnv *env, jobject thiz)
+{
+    bool mute;
+    if (AudioSystem::getMasterMute(&mute) != NO_ERROR) {
+        mute = false;
+    }
+    return mute;
+}
+
 static jint
 android_media_AudioSystem_getDevicesForStream(JNIEnv *env, jobject thiz, jint stream)
 {
@@ -221,6 +253,10 @@
     {"initStreamVolume",    "(III)I",   (void *)android_media_AudioSystem_initStreamVolume},
     {"setStreamVolumeIndex","(II)I",    (void *)android_media_AudioSystem_setStreamVolumeIndex},
     {"getStreamVolumeIndex","(I)I",     (void *)android_media_AudioSystem_getStreamVolumeIndex},
+    {"setMasterVolume",     "(F)I",     (void *)android_media_AudioSystem_setMasterVolume},
+    {"getMasterVolume",     "()F",      (void *)android_media_AudioSystem_getMasterVolume},
+    {"setMasterMute",       "(Z)I",     (void *)android_media_AudioSystem_setMasterMute},
+    {"getMasterMute",       "()Z",      (void *)android_media_AudioSystem_getMasterMute},
     {"getDevicesForStream", "(I)I",     (void *)android_media_AudioSystem_getDevicesForStream},
 };
 
diff --git a/core/jni/android_view_Display.cpp b/core/jni/android_view_Display.cpp
index 366a52e..f076cc8 100644
--- a/core/jni/android_view_Display.cpp
+++ b/core/jni/android_view_Display.cpp
@@ -28,6 +28,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/misc.h>
 #include <utils/Log.h>
+#include <cutils/properties.h>
 
 // ----------------------------------------------------------------------------
 
@@ -44,6 +45,7 @@
     jfieldID ydpi;
 };
 static offsets_t offsets;
+static bool headless = false;
 
 // ----------------------------------------------------------------------------
 
@@ -51,10 +53,19 @@
         JNIEnv* env, jobject clazz, jint dpy)
 {
     DisplayInfo info;
-    status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
-    if (err < 0) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
-        return;
+    if (headless) {
+        // initialize dummy display with reasonable values
+        info.pixelFormatInfo.format = 1; // RGB_8888
+        info.fps = 60;
+        info.density = 160;
+        info.xdpi = 160;
+        info.ydpi = 160;
+    } else {
+        status_t err = SurfaceComposerClient::getDisplayInfo(DisplayID(dpy), &info);
+        if (err < 0) {
+            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+            return;
+        }
     }
     env->SetIntField(clazz, offsets.pixelFormat,info.pixelFormatInfo.format);
     env->SetFloatField(clazz, offsets.fps,      info.fps);
@@ -66,6 +77,7 @@
 static jint android_view_Display_getRawWidthNative(
         JNIEnv* env, jobject clazz)
 {
+    if (headless) return 640;
     DisplayID dpy = env->GetIntField(clazz, offsets.display);
     return SurfaceComposerClient::getDisplayWidth(dpy);
 }
@@ -73,6 +85,7 @@
 static jint android_view_Display_getRawHeightNative(
         JNIEnv* env, jobject clazz)
 {
+    if (headless) return 480;
     DisplayID dpy = env->GetIntField(clazz, offsets.display);
     return SurfaceComposerClient::getDisplayHeight(dpy);
 }
@@ -80,6 +93,7 @@
 static jint android_view_Display_getOrientation(
         JNIEnv* env, jobject clazz)
 {
+    if (headless) return 0; // Surface.ROTATION_0
     DisplayID dpy = env->GetIntField(clazz, offsets.display);
     return SurfaceComposerClient::getDisplayOrientation(dpy);
 }
@@ -87,6 +101,7 @@
 static jint android_view_Display_getDisplayCount(
         JNIEnv* env, jclass clazz)
 {
+    if (headless) return 1;
     return SurfaceComposerClient::getNumberOfDisplays();
 }
 
@@ -113,6 +128,12 @@
 
 void nativeClassInit(JNIEnv* env, jclass clazz)
 {
+    char value[PROPERTY_VALUE_MAX];
+
+    property_get("ro.config.headless", value, "0");
+    if (strcmp(value, "1") == 0)
+        headless = true;
+
     offsets.display     = env->GetFieldID(clazz, "mDisplay", "I");
     offsets.pixelFormat = env->GetFieldID(clazz, "mPixelFormat", "I");
     offsets.fps         = env->GetFieldID(clazz, "mRefreshRate", "F");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 97658a1..5c17a83 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1507,6 +1507,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 37a8edb..47df189 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -61,6 +61,25 @@
          As of Honeycomb, blurring is not supported anymore. -->
     <bool name="config_sf_slowBlur">true</bool>
 
+    <!-- Flag indicating that the media framework should allow changing
+         master volume stream and nothing else . -->
+    <bool name="config_useMasterVolume">false</bool>
+
+    <!-- Array of integer pairs controlling the rate at which the master volume changes
+         in response to volume up and down key events.
+         The first integer of each pair is compared against the current master volume
+         (in range 0 to 100).
+         The last pair with first integer <= the current volume is chosen,
+         and the second integer of the pair indicates the amount to increase the master volume
+         when volume up is pressed. -->
+    <integer-array name="config_masterVolumeRamp">
+        <item>0</item>  <item>5</item>  <!-- default: always increase volume by 5% -->
+    </integer-array>
+
+    <!-- Flag indicating whether the AUDIO_BECOMING_NOISY notification should
+         be sent during an change to the audio output device. -->
+    <bool name="config_sendAudioBecomingNoisy">true</bool>
+
     <!-- The duration (in milliseconds) of a short animation. -->
     <integer name="config_shortAnimTime">200</integer>
 
@@ -265,6 +284,10 @@
          point on the move. A value of 0 means no periodic scans will be used in the framework. -->
     <integer translatable="false" name="config_wifi_framework_scan_interval">300000</integer>
 
+    <!-- Wifi driver stop delay, in milliseconds.
+         Default value is 2 minutes. -->
+    <integer translatable="false" name="config_wifi_driver_stop_delay">120000</integer>
+
     <!-- Flag indicating whether the keyguard should be bypassed when
          the slider is open.  This can be set or unset depending how easily
          the slider can be opened (for example, in a pocket or purse). -->
@@ -277,14 +300,14 @@
     <!-- Don't name config resources like this.  It should look like config_annoyDianne -->
     <bool name="config_annoy_dianne">true</bool>
 
+    <!-- XXXXXX END OF RESOURCES USING WRONG NAMING CONVENTION -->
+
     <!-- If this is true, the screen will come on when you unplug usb/power/whatever. -->
     <bool name="config_unplugTurnsOnScreen">false</bool>
 
     <!-- If this is true, the screen will fade off. -->
     <bool name="config_animateScreenLights">true</bool>
 
-    <!-- XXXXXX END OF RESOURCES USING WRONG NAMING CONVENTION -->
-
     <!-- If true, the screen can be rotated via the accelerometer in all 4
          rotations as the default behavior. -->
     <bool name="config_allowAllRotations">false</bool>
@@ -308,6 +331,14 @@
          A value of -1 means no change in orientation by default. -->
     <integer name="config_carDockRotation">-1</integer>
 
+    <!-- Control the default UI mode type to use when there is no other type override
+         happening.  One of the following values (See Configuration.java):
+             1  UI_MODE_TYPE_NORMAL
+             4  UI_MODE_TYPE_TELEVISION
+             5  UI_MODE_TYPE_APPLIANCE
+         Any other values will have surprising consequences. -->
+    <integer name="config_defaultUiModeType">1</integer>
+
     <!-- Control whether being in the desk dock (and powered) always
          keeps the screen on.  By default it stays on when plugged in to
          AC.  0 will not keep it on; or together 1 to stay on when plugged
@@ -360,6 +391,12 @@
     <string-array name="config_usbHostBlacklist">
     </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>
@@ -531,6 +568,9 @@
          specified -->
     <string name="default_wallpaper_component">@null</string>
 
+    <!-- True if WallpaperService is enabled -->
+    <bool name="config_enableWallpaperService">true</bool>
+
     <!-- Component name of the service providing network location support. -->
     <string name="config_networkLocationProvider">@null</string>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7b785ec..dceef01 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2283,6 +2283,11 @@
     <string name="permdesc_bindPackageVerifier">Allows the holder to make requests of
         package verifiers. Should never be needed for normal applications.</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/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
index 3ffa085..7233e7f 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
@@ -46,7 +46,7 @@
     @Override
     public TestSuite getAllTests() {
         TestSuite suite = new InstrumentationTestSuite(this);
-        if (!UtilHelper.isWifiOnly()) {
+        if (!UtilHelper.isWifiOnly(getContext())) {
             suite.addTestSuite(WifiApStress.class);
             suite.addTestSuite(WifiStressTest.class);
         } else {
@@ -64,7 +64,7 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        if (!UtilHelper.isWifiOnly()) {
+        if (!UtilHelper.isWifiOnly(getContext())) {
             String valueStr = (String) icicle.get("softap_iterations");
             if (valueStr != null) {
                 int iteration = Integer.parseInt(valueStr);
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
index 20aae47..9c1922f 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestRunner.java
@@ -40,14 +40,13 @@
     @Override
     public TestSuite getAllTests() {
         TestSuite suite = new InstrumentationTestSuite(this);
-        if (!UtilHelper.isWifiOnly()) {
+        if (!UtilHelper.isWifiOnly(getContext())) {
             suite.addTestSuite(ConnectivityManagerMobileTest.class);
         } else {
             // create a new test suite
             suite.setName("ConnectivityManagerWifiOnlyFunctionalTests");
             String[] methodNames = {"testConnectToWifi", "testConnectToWifWithKnownAP",
-                    "testDisconnectWifi", "testDataConnectionOverAMWithWifi",
-                    "testDataConnectionWithWifiToAMToWifi", "testWifiStateChange"};
+                    "testDisconnectWifi", "testWifiStateChange"};
             Class<ConnectivityManagerMobileTest> testClass = ConnectivityManagerMobileTest.class;
             for (String method: methodNames) {
                 suite.addTest(TestSuite.createTest(testClass, method));
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java
index 1b966bf..b9fe6ed 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/UtilHelper.java
@@ -16,12 +16,31 @@
 
 package com.android.connectivitymanagertest;
 
-import android.os.SystemProperties;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.Log;
 
 public class UtilHelper {
-    public static boolean isWifiOnly() {
-        return "wifi-only".equals(SystemProperties.get("ro.carrier"));
+
+    private static Boolean mIsWifiOnly = null;
+    private static final Object sLock = new Object();
+
+    /**
+     * Return true if device is a wifi only device.
+     */
+    public static boolean isWifiOnly(Context context) {
+        synchronized (sLock) {
+            // cache the result from pkgMgr statically. It will never change, since its a
+            // device configuration setting
+            if (mIsWifiOnly == null) {
+                PackageManager pkgMgr = context.getPackageManager();
+                mIsWifiOnly = Boolean.valueOf(!pkgMgr
+                        .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+                        && pkgMgr.hasSystemFeature(PackageManager.FEATURE_WIFI));
+                String deviceType = mIsWifiOnly ? "wifi-only" : "telephony";
+                Log.d("ConnectivityManagerTest", String.format("detected a %s device", deviceType));
+            }
+        }
+        return mIsWifiOnly;
     }
-
-
 }
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
index b1f4bf1..52326d5 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java
@@ -16,37 +16,31 @@
 
 package com.android.connectivitymanagertest.functional;
 
-import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
-import com.android.connectivitymanagertest.UtilHelper;
-
-import android.content.Intent;
 import android.content.Context;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.app.Instrumentation;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.Settings;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.State;
-import android.net.NetworkInfo.DetailedState;
 import android.net.wifi.WifiManager;
-
-import android.test.suitebuilder.annotation.LargeTest;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.provider.Settings;
 import android.test.ActivityInstrumentationTestCase2;
-import com.android.connectivitymanagertest.ConnectivityManagerTestRunner;
-import com.android.connectivitymanagertest.NetworkState;
+import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
-public class ConnectivityManagerMobileTest
-    extends ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> {
+import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
+import com.android.connectivitymanagertest.ConnectivityManagerTestRunner;
+import com.android.connectivitymanagertest.NetworkState;
+import com.android.connectivitymanagertest.UtilHelper;
+
+public class ConnectivityManagerMobileTest extends
+        ActivityInstrumentationTestCase2<ConnectivityManagerTestActivity> {
     private static final String LOG_TAG = "ConnectivityManagerMobileTest";
-    private static final String PKG_NAME = "com.android.connectivitymanagertest";
 
     private String TEST_ACCESS_POINT;
     private ConnectivityManagerTestActivity cmActivity;
     private WakeLock wl;
+    private boolean mIsWifiOnlyDevice;
 
     public ConnectivityManagerMobileTest() {
         super(ConnectivityManagerTestActivity.class);
@@ -69,7 +63,8 @@
             log("airplane is not disabled, disable it.");
             cmActivity.setAirplaneMode(getInstrumentation().getContext(), false);
         }
-        if (!UtilHelper.isWifiOnly()) {
+        mIsWifiOnlyDevice = UtilHelper.isWifiOnly(mRunner.getTargetContext());
+        if (!mIsWifiOnlyDevice) {
             if (!cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
                     ConnectivityManagerTestActivity.LONG_TIMEOUT)) {
                 // Note: When the test fails in setUp(), tearDown is not called. In that case,
@@ -166,7 +161,7 @@
     public void testConnectToWifi() {
         assertNotNull("SSID is null", TEST_ACCESS_POINT);
         NetworkInfo networkInfo;
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             //Prepare for connectivity verification
             networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
             cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
@@ -185,7 +180,7 @@
         log("wifi state is enabled");
         assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
                 ConnectivityManagerTestActivity.LONG_TIMEOUT));
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
                     State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
         }
@@ -197,7 +192,7 @@
                     cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
             assertTrue(false);
         }
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
                 log("Mobile state transition validation failed.");
                 log("reason: " +
@@ -232,13 +227,13 @@
                 ConnectivityManagerTestActivity.LONG_TIMEOUT));
         assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI,
                 State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
                     State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
         }
 
         NetworkInfo networkInfo;
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             //Prepare for connectivity state verification
             networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
             cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
@@ -258,7 +253,7 @@
         // Wait for Wifi to be connected and mobile to be disconnected
         assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
                 ConnectivityManagerTestActivity.LONG_TIMEOUT));
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
                     State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
         }
@@ -288,7 +283,7 @@
         sleep(ConnectivityManagerTestActivity.SHORT_TIMEOUT);
 
         NetworkInfo networkInfo;
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
             cmActivity.setStateTransitionCriteria(ConnectivityManager.TYPE_MOBILE,
                                                   networkInfo.getState(),
@@ -304,7 +299,7 @@
 
         assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
                 ConnectivityManagerTestActivity.LONG_TIMEOUT));
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
                     State.CONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
         }
@@ -316,7 +311,7 @@
                     cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
             assertTrue(false);
         }
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
                 log("Mobile state transition validation failed.");
                 log("reason: " +
@@ -393,7 +388,7 @@
         cmActivity.setAirplaneMode(getInstrumentation().getContext(), true);
 
         NetworkInfo networkInfo;
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
                     State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
             networkInfo = cmActivity.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
@@ -419,7 +414,7 @@
                     cmActivity.getTransitionFailureReason(ConnectivityManager.TYPE_WIFI));
             assertTrue("State validation failed", false);
         }
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             if (!cmActivity.validateNetworkStates(ConnectivityManager.TYPE_MOBILE)) {
                 log("state validation for Mobile failed");
                 log("reason: " +
@@ -471,7 +466,7 @@
 
         assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.CONNECTED,
                             ConnectivityManagerTestActivity.LONG_TIMEOUT));
-        if (!UtilHelper.isWifiOnly()) {
+        if (!mIsWifiOnlyDevice) {
             assertTrue(cmActivity.waitForNetworkState(ConnectivityManager.TYPE_MOBILE,
                     State.DISCONNECTED, ConnectivityManagerTestActivity.LONG_TIMEOUT));
         }
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
index 2069789..feb63cd 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
@@ -16,10 +16,6 @@
 
 package com.android.connectivitymanagertest.stress;
 
-import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
-import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
-import com.android.connectivitymanagertest.UtilHelper;
-
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo.State;
@@ -31,15 +27,15 @@
 import android.net.wifi.WifiManager;
 import android.os.Environment;
 import android.os.PowerManager;
-import android.os.IPowerManager;
-import android.os.SystemClock;
-import android.os.ServiceManager;
 import android.provider.Settings;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
-
 import android.util.Log;
 
+import com.android.connectivitymanagertest.ConnectivityManagerStressTestRunner;
+import com.android.connectivitymanagertest.ConnectivityManagerTestActivity;
+import com.android.connectivitymanagertest.UtilHelper;
+
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileWriter;
@@ -79,6 +75,7 @@
     private String mPassword;
     private ConnectivityManagerStressTestRunner mRunner;
     private BufferedWriter mOutputWriter = null;
+    private boolean mIsWifiOnlyDevice;
 
     public WifiStressTest() {
         super(ConnectivityManagerTestActivity.class);
@@ -100,6 +97,7 @@
         mOutputWriter = new BufferedWriter(new FileWriter(new File(
                 Environment.getExternalStorageDirectory(), OUTPUT_FILE), true));
         mAct.turnScreenOn();
+        mIsWifiOnlyDevice = UtilHelper.isWifiOnly(mRunner.getTargetContext());
         if (!mAct.mWifiManager.isWifiEnabled()) {
             log("Enable wi-fi before stress tests.");
             if (!mAct.enableWifi()) {
@@ -271,7 +269,7 @@
             assertTrue("Wait for Wi-Fi to idle timeout",
                     mAct.waitForNetworkState(ConnectivityManager.TYPE_WIFI, State.DISCONNECTED,
                     6 * ConnectivityManagerTestActivity.SHORT_TIMEOUT));
-            if (!UtilHelper.isWifiOnly()) {
+            if (!mIsWifiOnlyDevice) {
                 // use long timeout as the pppd startup may take several retries.
                 assertTrue("Wait for cellular connection timeout",
                         mAct.waitForNetworkState(ConnectivityManager.TYPE_MOBILE, State.CONNECTED,
@@ -282,7 +280,7 @@
             assertEquals("Wi-Fi is reconnected", State.DISCONNECTED,
                     mAct.mCM.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState());
 
-            if (!UtilHelper.isWifiOnly()) {
+            if (!mIsWifiOnlyDevice) {
                 assertEquals("Cellular connection is down", State.CONNECTED,
                              mAct.mCM.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState());
                 assertTrue("Mobile is connected, but no data connection.", mAct.pingTest(null));
diff --git a/data/etc/android.hardware.bluetooth.xml b/data/etc/android.hardware.bluetooth.xml
new file mode 100644
index 0000000..4aa1744
--- /dev/null
+++ b/data/etc/android.hardware.bluetooth.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- Adds the feature indicating support for the Bluetooth API -->
+<permissions>
+    <feature name="android.hardware.bluetooth" />
+</permissions>
diff --git a/include/common_time/ICommonClock.h b/include/common_time/ICommonClock.h
new file mode 100644
index 0000000..d7073f1
--- /dev/null
+++ b/include/common_time/ICommonClock.h
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_ICOMMONCLOCK_H
+#define ANDROID_ICOMMONCLOCK_H
+
+#include <stdint.h>
+#include <linux/socket.h>
+
+#include <binder/IInterface.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+
+class ICommonClockListener : public IInterface {
+  public:
+    DECLARE_META_INTERFACE(CommonClockListener);
+
+    virtual void onTimelineChanged(uint64_t timelineID) = 0;
+};
+
+class BnCommonClockListener : public BnInterface<ICommonClockListener> {
+  public:
+    virtual status_t onTransact(uint32_t code, const Parcel& data,
+                                Parcel* reply, uint32_t flags = 0);
+};
+
+class ICommonClock : public IInterface {
+  public:
+    DECLARE_META_INTERFACE(CommonClock);
+
+    // Name of the ICommonClock service registered with the service manager.
+    static const String16 kServiceName;
+
+    // a reserved invalid timeline ID
+    static const uint64_t kInvalidTimelineID;
+
+    // a reserved invalid error estimate
+    static const int32_t kErrorEstimateUnknown;
+
+    enum State {
+        // the device just came up and is trying to discover the master
+        STATE_INITIAL,
+
+        // the device is a client of a master
+        STATE_CLIENT,
+
+        // the device is acting as master
+        STATE_MASTER,
+
+        // the device has lost contact with its master and needs to participate
+        // in the election of a new master
+        STATE_RONIN,
+
+        // the device is waiting for announcement of the newly elected master
+        STATE_WAIT_FOR_ELECTION,
+    };
+
+    virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) = 0;
+    virtual status_t commonTimeToLocalTime(int64_t commonTime,
+                                           int64_t* localTime) = 0;
+    virtual status_t localTimeToCommonTime(int64_t localTime,
+                                           int64_t* commonTime) = 0;
+    virtual status_t getCommonTime(int64_t* commonTime) = 0;
+    virtual status_t getCommonFreq(uint64_t* freq) = 0;
+    virtual status_t getLocalTime(int64_t* localTime) = 0;
+    virtual status_t getLocalFreq(uint64_t* freq) = 0;
+    virtual status_t getEstimatedError(int32_t* estimate) = 0;
+    virtual status_t getTimelineID(uint64_t* id) = 0;
+    virtual status_t getState(State* state) = 0;
+    virtual status_t getMasterAddr(struct sockaddr_storage* addr) = 0;
+
+    virtual status_t registerListener(
+            const sp<ICommonClockListener>& listener) = 0;
+    virtual status_t unregisterListener(
+            const sp<ICommonClockListener>& listener) = 0;
+
+    // Simple helper to make it easier to connect to the CommonClock service.
+    static inline sp<ICommonClock> getInstance() {
+        sp<IBinder> binder = defaultServiceManager()->checkService(
+                ICommonClock::kServiceName);
+        sp<ICommonClock> clk = interface_cast<ICommonClock>(binder);
+        return clk;
+    }
+};
+
+class BnCommonClock : public BnInterface<ICommonClock> {
+  public:
+    virtual status_t onTransact(uint32_t code, const Parcel& data,
+                                Parcel* reply, uint32_t flags = 0);
+};
+
+};  // namespace android
+
+#endif  // ANDROID_ICOMMONCLOCK_H
diff --git a/include/common_time/ICommonTimeConfig.h b/include/common_time/ICommonTimeConfig.h
new file mode 100644
index 0000000..497b666
--- /dev/null
+++ b/include/common_time/ICommonTimeConfig.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef ANDROID_ICOMMONTIMECONFIG_H
+#define ANDROID_ICOMMONTIMECONFIG_H
+
+#include <stdint.h>
+#include <linux/socket.h>
+
+#include <binder/IInterface.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+
+class String16;
+
+class ICommonTimeConfig : public IInterface {
+  public:
+    DECLARE_META_INTERFACE(CommonTimeConfig);
+
+    // Name of the ICommonTimeConfig service registered with the service
+    // manager.
+    static const String16 kServiceName;
+
+    virtual status_t getMasterElectionPriority(uint8_t *priority) = 0;
+    virtual status_t setMasterElectionPriority(uint8_t priority) = 0;
+    virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr) = 0;
+    virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr) = 0;
+    virtual status_t getMasterElectionGroupId(uint64_t *id) = 0;
+    virtual status_t setMasterElectionGroupId(uint64_t id) = 0;
+    virtual status_t getInterfaceBinding(String16& ifaceName) = 0;
+    virtual status_t setInterfaceBinding(const String16& ifaceName) = 0;
+    virtual status_t getMasterAnnounceInterval(int *interval) = 0;
+    virtual status_t setMasterAnnounceInterval(int interval) = 0;
+    virtual status_t getClientSyncInterval(int *interval) = 0;
+    virtual status_t setClientSyncInterval(int interval) = 0;
+    virtual status_t getPanicThreshold(int *threshold) = 0;
+    virtual status_t setPanicThreshold(int threshold) = 0;
+    virtual status_t getAutoDisable(bool *autoDisable) = 0;
+    virtual status_t setAutoDisable(bool autoDisable) = 0;
+    virtual status_t forceNetworklessMasterMode() = 0;
+
+    // Simple helper to make it easier to connect to the CommonTimeConfig service.
+    static inline sp<ICommonTimeConfig> getInstance() {
+        sp<IBinder> binder = defaultServiceManager()->checkService(
+                ICommonTimeConfig::kServiceName);
+        sp<ICommonTimeConfig> clk = interface_cast<ICommonTimeConfig>(binder);
+        return clk;
+    }
+};
+
+class BnCommonTimeConfig : public BnInterface<ICommonTimeConfig> {
+  public:
+    virtual status_t onTransact(uint32_t code, const Parcel& data,
+                                Parcel* reply, uint32_t flags = 0);
+};
+
+};  // namespace android
+
+#endif  // ANDROID_ICOMMONTIMECONFIG_H
diff --git a/include/common_time/cc_helper.h b/include/common_time/cc_helper.h
new file mode 100644
index 0000000..cbdacd0
--- /dev/null
+++ b/include/common_time/cc_helper.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+#ifndef __CC_HELPER_H__
+#define __CC_HELPER_H__
+
+#include <stdint.h>
+#include <common_time/ICommonClock.h>
+#include <utils/threads.h>
+
+namespace android {
+
+// CCHelper is a simple wrapper class to help with centralizing access to the
+// Common Clock service and implementing lifetime managment, as well as to
+// implement a simple policy of making a basic attempt to reconnect to the
+// common clock service when things go wrong.
+// 
+// On platforms which run the native common_time service in auto-diable mode,
+// the service will go into networkless mode whenever it has no active clients.
+// It tracks active clients using registered CommonClockListeners (the callback
+// interface for onTimelineChanged) since this provides a convienent death
+// handler notification for when the service's clients die unexpectedly.  This
+// means that users of the common time service should really always have a
+// CommonClockListener, unless they know that the time service is not running in
+// auto disabled mode, or that there is at least one other registered listener
+// active in the system.  The CCHelper makes this a little easier by sharing a
+// ref counted ICommonClock interface across all clients and automatically
+// registering and unregistering a listener whenever there are CCHelper
+// instances active in the process.
+class CCHelper {
+  public:
+    CCHelper();
+    ~CCHelper();
+
+    status_t isCommonTimeValid(bool* valid, uint32_t* timelineID);
+    status_t commonTimeToLocalTime(int64_t commonTime, int64_t* localTime);
+    status_t localTimeToCommonTime(int64_t localTime, int64_t* commonTime);
+    status_t getCommonTime(int64_t* commonTime);
+    status_t getCommonFreq(uint64_t* freq);
+    status_t getLocalTime(int64_t* localTime);
+    status_t getLocalFreq(uint64_t* freq);
+
+  private:
+    class CommonClockListener : public BnCommonClockListener {
+      public:
+        void onTimelineChanged(uint64_t timelineID);
+    };
+
+    static bool verifyClock_l();
+
+    static Mutex lock_;
+    static sp<ICommonClock> common_clock_;
+    static sp<ICommonClockListener> common_clock_listener_;
+    static uint32_t ref_count_;
+};
+
+
+}  // namespace android
+#endif  // __CC_HELPER_H__
diff --git a/include/common_time/local_clock.h b/include/common_time/local_clock.h
new file mode 100644
index 0000000..845d1c21
--- /dev/null
+++ b/include/common_time/local_clock.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+
+#ifndef __LOCAL_CLOCK_H__
+#define __LOCAL_CLOCK_H__
+
+#include <stdint.h>
+
+#include <hardware/local_time_hal.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class LocalClock {
+  public:
+     LocalClock();
+
+    bool initCheck();
+
+    int64_t  getLocalTime();
+    uint64_t getLocalFreq();
+    status_t setLocalSlew(int16_t rate);
+    int32_t  getDebugLog(struct local_time_debug_event* records,
+                         int max_records);
+
+  private:
+    static Mutex dev_lock_;
+    static local_time_hw_device_t* dev_;
+};
+
+}  // namespace android
+#endif  // __LOCAL_CLOCK_H__
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index d1a8105..922452e 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -416,7 +416,7 @@
      */
             status_t dump(int fd, const Vector<String16>& args) const;
 
-private:
+protected:
     /* copying audio tracks is not allowed */
                         AudioTrack(const AudioTrack& other);
             AudioTrack& operator = (const AudioTrack& other);
@@ -487,8 +487,27 @@
     int                     mAuxEffectId;
     Mutex                   mLock;
     status_t                mRestoreStatus;
+    bool                    mIsTimed;
 };
 
+class TimedAudioTrack : public AudioTrack
+{
+public:
+    TimedAudioTrack();
+
+    /* allocate a shared memory buffer that can be passed to queueTimedBuffer */
+    status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer);
+
+    /* queue a buffer obtained via allocateTimedBuffer for playback at the
+       given timestamp */
+    status_t queueTimedBuffer(const sp<IMemory>& buffer, int64_t pts);
+
+    /* define a transform between media time and either common time or
+       local time */
+    enum TargetTimeline {LOCAL_TIME, COMMON_TIME};
+    status_t setMediaTimeTransform(const LinearTransform& xform,
+                                   TargetTimeline target);
+};
 
 }; // namespace android
 
diff --git a/include/media/IAudioFlinger.h b/include/media/IAudioFlinger.h
index 9e3cb7f..f72452c 100644
--- a/include/media/IAudioFlinger.h
+++ b/include/media/IAudioFlinger.h
@@ -54,6 +54,7 @@
                                 uint32_t flags,
                                 const sp<IMemory>& sharedBuffer,
                                 int output,
+                                bool isTimed,
                                 int *sessionId,
                                 status_t *status) = 0;
 
diff --git a/include/media/IAudioTrack.h b/include/media/IAudioTrack.h
index 47d530b..7343a48 100644
--- a/include/media/IAudioTrack.h
+++ b/include/media/IAudioTrack.h
@@ -24,7 +24,7 @@
 #include <utils/Errors.h>
 #include <binder/IInterface.h>
 #include <binder/IMemory.h>
-
+#include <utils/LinearTransform.h>
 
 namespace android {
 
@@ -69,6 +69,23 @@
 
     /* get this tracks control block */
     virtual sp<IMemory> getCblk() const = 0;    
+
+    /* Allocate a shared memory buffer suitable for holding timed audio
+       samples */
+    virtual status_t    allocateTimedBuffer(size_t size,
+                                            sp<IMemory>* buffer) = 0;
+
+    /* Queue a buffer obtained via allocateTimedBuffer for playback at the given
+       timestamp */
+    virtual status_t    queueTimedBuffer(const sp<IMemory>& buffer,
+                                         int64_t pts) = 0;
+
+    /* Define the linear transform that will be applied to the timestamps
+       given to queueTimedBuffer (which are expressed in media time).
+       Target specifies whether this transform converts media time to local time
+       or Tungsten time. The values for target are defined in AudioTrack.h */
+    virtual status_t    setMediaTimeTransform(const LinearTransform& xform,
+                                              int target) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/media/IMediaPlayer.h b/include/media/IMediaPlayer.h
index e905903..ed98dce 100644
--- a/include/media/IMediaPlayer.h
+++ b/include/media/IMediaPlayer.h
@@ -22,6 +22,10 @@
 #include <binder/Parcel.h>
 #include <utils/KeyedVector.h>
 
+// Fwd decl to make sure everyone agrees that the scope of struct sockaddr_in is
+// global, and not in android::
+struct sockaddr_in;
+
 namespace android {
 
 class Parcel;
@@ -58,6 +62,7 @@
     virtual status_t        attachAuxEffect(int effectId) = 0;
     virtual status_t        setParameter(int key, const Parcel& request) = 0;
     virtual status_t        getParameter(int key, Parcel* reply) = 0;
+    virtual status_t        setRetransmitEndpoint(const struct sockaddr_in* endpoint) = 0;
 
     // Invoke a generic method on the player by using opaque parcels
     // for the request and reply.
diff --git a/include/media/MediaPlayerInterface.h b/include/media/MediaPlayerInterface.h
index 80f43a3..74a265e 100644
--- a/include/media/MediaPlayerInterface.h
+++ b/include/media/MediaPlayerInterface.h
@@ -29,6 +29,10 @@
 #include <media/AudioSystem.h>
 #include <media/Metadata.h>
 
+// Fwd decl to make sure everyone agrees that the scope of struct sockaddr_in is
+// global, and not in android::
+struct sockaddr_in;
+
 namespace android {
 
 class Parcel;
@@ -46,6 +50,9 @@
     // The shared library with the test player is passed passed as an
     // argument to the 'test:' url in the setDataSource call.
     TEST_PLAYER = 5,
+
+    AAH_RX_PLAYER = 100,
+    AAH_TX_PLAYER = 101,
 };
 
 
@@ -136,6 +143,14 @@
     virtual status_t    setParameter(int key, const Parcel &request) = 0;
     virtual status_t    getParameter(int key, Parcel *reply) = 0;
 
+    // Right now, only the AAX TX player supports this functionality.  For now,
+    // provide a default implementation which indicates a lack of support for
+    // this functionality to make life easier for all of the other media player
+    // maintainers out there.
+    virtual status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint) {
+        return INVALID_OPERATION;
+    }
+
     // Invoke a generic method on the player by using opaque parcels
     // for the request and reply.
     //
diff --git a/include/media/mediaplayer.h b/include/media/mediaplayer.h
index e6a0cc5..7cc1693 100644
--- a/include/media/mediaplayer.h
+++ b/include/media/mediaplayer.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_MEDIAPLAYER_H
 #define ANDROID_MEDIAPLAYER_H
 
+#include <arpa/inet.h>
+
 #include <binder/IMemory.h>
 #include <media/IMediaPlayerClient.h>
 #include <media/IMediaPlayer.h>
@@ -201,6 +203,7 @@
             status_t        attachAuxEffect(int effectId);
             status_t        setParameter(int key, const Parcel& request);
             status_t        getParameter(int key, Parcel* reply);
+            status_t        setRetransmitEndpoint(const char* addrString, uint16_t port);
 
 private:
             void            clear_l();
@@ -209,6 +212,7 @@
             status_t        getDuration_l(int *msec);
             status_t        attachNewPlayer(const sp<IMediaPlayer>& player);
             status_t        reset_l();
+            status_t        doSetRetransmitEndpoint(const sp<IMediaPlayer>& player);
 
     sp<IMediaPlayer>            mPlayer;
     thread_id_t                 mLockThreadId;
@@ -231,6 +235,8 @@
     int                         mVideoHeight;
     int                         mAudioSessionId;
     float                       mSendLevel;
+    struct sockaddr_in          mRetransmitEndpoint;
+    bool                        mRetransmitEndpointValid;
 };
 
 }; // namespace android
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index 612ff93..e045b2c 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -955,6 +955,7 @@
         UI_MODE_TYPE_DESK = ACONFIGURATION_UI_MODE_TYPE_DESK,
         UI_MODE_TYPE_CAR = ACONFIGURATION_UI_MODE_TYPE_CAR,
         UI_MODE_TYPE_TELEVISION = ACONFIGURATION_UI_MODE_TYPE_TELEVISION,
+        UI_MODE_TYPE_APPLIANCE = ACONFIGURATION_UI_MODE_TYPE_APPLIANCE,
 
         // uiMode bits for the night switch.
         MASK_UI_MODE_NIGHT = 0x30,
diff --git a/libs/common_time/Android.mk b/libs/common_time/Android.mk
new file mode 100644
index 0000000..526f17b
--- /dev/null
+++ b/libs/common_time/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+#
+# libcommon_time_client
+# (binder marshalers for ICommonClock as well as common clock and local clock
+# helper code)
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libcommon_time_client
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := cc_helper.cpp \
+                   local_clock.cpp \
+                   ICommonClock.cpp \
+                   ICommonTimeConfig.cpp \
+                   utils.cpp
+LOCAL_SHARED_LIBRARIES := libbinder \
+                          libhardware \
+                          libutils
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/libs/common_time/ICommonClock.cpp b/libs/common_time/ICommonClock.cpp
new file mode 100644
index 0000000..28b43ac
--- /dev/null
+++ b/libs/common_time/ICommonClock.cpp
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+#include <linux/socket.h>
+
+#include <common_time/ICommonClock.h>
+#include <binder/Parcel.h>
+
+#include "utils.h"
+
+namespace android {
+
+/***** ICommonClock *****/
+
+enum {
+    IS_COMMON_TIME_VALID = IBinder::FIRST_CALL_TRANSACTION,
+    COMMON_TIME_TO_LOCAL_TIME,
+    LOCAL_TIME_TO_COMMON_TIME,
+    GET_COMMON_TIME,
+    GET_COMMON_FREQ,
+    GET_LOCAL_TIME,
+    GET_LOCAL_FREQ,
+    GET_ESTIMATED_ERROR,
+    GET_TIMELINE_ID,
+    GET_STATE,
+    GET_MASTER_ADDRESS,
+    REGISTER_LISTENER,
+    UNREGISTER_LISTENER,
+};
+
+const String16 ICommonClock::kServiceName("common_time.clock");
+const uint64_t ICommonClock::kInvalidTimelineID = 0;
+const int32_t ICommonClock::kErrorEstimateUnknown = 0x7FFFFFFF;
+
+class BpCommonClock : public BpInterface<ICommonClock>
+{
+  public:
+    BpCommonClock(const sp<IBinder>& impl)
+        : BpInterface<ICommonClock>(impl) {}
+
+    virtual status_t isCommonTimeValid(bool* valid, uint32_t* timelineID) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        status_t status = remote()->transact(IS_COMMON_TIME_VALID,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *valid = reply.readInt32();
+                *timelineID = reply.readInt32();
+            }
+        }
+        return status;
+    }
+
+    virtual status_t commonTimeToLocalTime(int64_t commonTime,
+            int64_t* localTime) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        data.writeInt64(commonTime);
+        status_t status = remote()->transact(COMMON_TIME_TO_LOCAL_TIME,
+                data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *localTime = reply.readInt64();
+            }
+        }
+        return status;
+    }
+
+    virtual status_t localTimeToCommonTime(int64_t localTime,
+            int64_t* commonTime) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        data.writeInt64(localTime);
+        status_t status = remote()->transact(LOCAL_TIME_TO_COMMON_TIME,
+                data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *commonTime = reply.readInt64();
+            }
+        }
+        return status;
+    }
+
+    virtual status_t getCommonTime(int64_t* commonTime) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_COMMON_TIME, data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *commonTime = reply.readInt64();
+            }
+        }
+        return status;
+    }
+
+    virtual status_t getCommonFreq(uint64_t* freq) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_COMMON_FREQ, data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *freq = reply.readInt64();
+            }
+        }
+        return status;
+    }
+
+    virtual status_t getLocalTime(int64_t* localTime) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_LOCAL_TIME, data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *localTime = reply.readInt64();
+            }
+        }
+        return status;
+    }
+
+    virtual status_t getLocalFreq(uint64_t* freq) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_LOCAL_FREQ, data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *freq = reply.readInt64();
+            }
+        }
+        return status;
+    }
+
+    virtual status_t getEstimatedError(int32_t* estimate) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_ESTIMATED_ERROR, data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *estimate = reply.readInt32();
+            }
+        }
+        return status;
+    }
+
+    virtual status_t getTimelineID(uint64_t* id) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_TIMELINE_ID, data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *id = static_cast<uint64_t>(reply.readInt64());
+            }
+        }
+        return status;
+    }
+
+    virtual status_t getState(State* state) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_STATE, data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *state = static_cast<State>(reply.readInt32());
+            }
+        }
+        return status;
+    }
+
+    virtual status_t getMasterAddr(struct sockaddr_storage* addr) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_MASTER_ADDRESS, data, &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK)
+                deserializeSockaddr(&reply, addr);
+        }
+        return status;
+    }
+
+    virtual status_t registerListener(
+            const sp<ICommonClockListener>& listener) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        data.writeStrongBinder(listener->asBinder());
+
+        status_t status = remote()->transact(REGISTER_LISTENER, data, &reply);
+
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+
+    virtual status_t unregisterListener(
+            const sp<ICommonClockListener>& listener) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonClock::getInterfaceDescriptor());
+        data.writeStrongBinder(listener->asBinder());
+        status_t status = remote()->transact(UNREGISTER_LISTENER, data, &reply);
+
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+};
+
+IMPLEMENT_META_INTERFACE(CommonClock, "android.os.ICommonClock");
+
+status_t BnCommonClock::onTransact(uint32_t code,
+                                   const Parcel& data,
+                                   Parcel* reply,
+                                   uint32_t flags) {
+    switch(code) {
+        case IS_COMMON_TIME_VALID: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            bool valid;
+            uint32_t timelineID;
+            status_t status = isCommonTimeValid(&valid, &timelineID);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt32(valid);
+                reply->writeInt32(timelineID);
+            }
+            return OK;
+        } break;
+
+        case COMMON_TIME_TO_LOCAL_TIME: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            int64_t commonTime = data.readInt64();
+            int64_t localTime;
+            status_t status = commonTimeToLocalTime(commonTime, &localTime);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt64(localTime);
+            }
+            return OK;
+        } break;
+
+        case LOCAL_TIME_TO_COMMON_TIME: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            int64_t localTime = data.readInt64();
+            int64_t commonTime;
+            status_t status = localTimeToCommonTime(localTime, &commonTime);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt64(commonTime);
+            }
+            return OK;
+        } break;
+
+        case GET_COMMON_TIME: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            int64_t commonTime;
+            status_t status = getCommonTime(&commonTime);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt64(commonTime);
+            }
+            return OK;
+        } break;
+
+        case GET_COMMON_FREQ: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            uint64_t freq;
+            status_t status = getCommonFreq(&freq);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt64(freq);
+            }
+            return OK;
+        } break;
+
+        case GET_LOCAL_TIME: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            int64_t localTime;
+            status_t status = getLocalTime(&localTime);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt64(localTime);
+            }
+            return OK;
+        } break;
+
+        case GET_LOCAL_FREQ: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            uint64_t freq;
+            status_t status = getLocalFreq(&freq);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt64(freq);
+            }
+            return OK;
+        } break;
+
+        case GET_ESTIMATED_ERROR: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            int32_t error;
+            status_t status = getEstimatedError(&error);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt32(error);
+            }
+            return OK;
+        } break;
+
+        case GET_TIMELINE_ID: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            uint64_t id;
+            status_t status = getTimelineID(&id);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt64(static_cast<int64_t>(id));
+            }
+            return OK;
+        } break;
+
+        case GET_STATE: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            State state;
+            status_t status = getState(&state);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt32(static_cast<int32_t>(state));
+            }
+            return OK;
+        } break;
+
+        case GET_MASTER_ADDRESS: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            struct sockaddr_storage addr;
+            status_t status = getMasterAddr(&addr);
+
+            if ((status == OK) && !canSerializeSockaddr(&addr)) {
+                status = UNKNOWN_ERROR;
+            }
+
+            reply->writeInt32(status);
+
+            if (status == OK) {
+                serializeSockaddr(reply, &addr);
+            }
+
+            return OK;
+        } break;
+
+        case REGISTER_LISTENER: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            sp<ICommonClockListener> listener =
+                interface_cast<ICommonClockListener>(data.readStrongBinder());
+            status_t status = registerListener(listener);
+            reply->writeInt32(status);
+            return OK;
+        } break;
+
+        case UNREGISTER_LISTENER: {
+            CHECK_INTERFACE(ICommonClock, data, reply);
+            sp<ICommonClockListener> listener =
+                interface_cast<ICommonClockListener>(data.readStrongBinder());
+            status_t status = unregisterListener(listener);
+            reply->writeInt32(status);
+            return OK;
+        } break;
+    }
+    return BBinder::onTransact(code, data, reply, flags);
+}
+
+/***** ICommonClockListener *****/
+
+enum {
+    ON_TIMELINE_CHANGED = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpCommonClockListener : public BpInterface<ICommonClockListener>
+{
+  public:
+    BpCommonClockListener(const sp<IBinder>& impl)
+        : BpInterface<ICommonClockListener>(impl) {}
+
+    virtual void onTimelineChanged(uint64_t timelineID) {
+        Parcel data, reply;
+        data.writeInterfaceToken(
+                ICommonClockListener::getInterfaceDescriptor());
+        data.writeInt64(timelineID);
+        remote()->transact(ON_TIMELINE_CHANGED, data, &reply);
+    }
+};
+
+IMPLEMENT_META_INTERFACE(CommonClockListener,
+                         "android.os.ICommonClockListener");
+
+status_t BnCommonClockListener::onTransact(
+        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+    switch(code) {
+        case ON_TIMELINE_CHANGED: {
+            CHECK_INTERFACE(ICommonClockListener, data, reply);
+            uint32_t timelineID = data.readInt64();
+            onTimelineChanged(timelineID);
+            return NO_ERROR;
+        } break;
+    }
+
+    return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
diff --git a/libs/common_time/ICommonTimeConfig.cpp b/libs/common_time/ICommonTimeConfig.cpp
new file mode 100644
index 0000000..8eb37cb
--- /dev/null
+++ b/libs/common_time/ICommonTimeConfig.cpp
@@ -0,0 +1,508 @@
+/*
+ * 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.
+ */
+#include <linux/socket.h>
+
+#include <common_time/ICommonTimeConfig.h>
+#include <binder/Parcel.h>
+
+#include "utils.h"
+
+namespace android {
+
+/***** ICommonTimeConfig *****/
+
+enum {
+    GET_MASTER_ELECTION_PRIORITY = IBinder::FIRST_CALL_TRANSACTION,
+    SET_MASTER_ELECTION_PRIORITY,
+    GET_MASTER_ELECTION_ENDPOINT,
+    SET_MASTER_ELECTION_ENDPOINT,
+    GET_MASTER_ELECTION_GROUP_ID,
+    SET_MASTER_ELECTION_GROUP_ID,
+    GET_INTERFACE_BINDING,
+    SET_INTERFACE_BINDING,
+    GET_MASTER_ANNOUNCE_INTERVAL,
+    SET_MASTER_ANNOUNCE_INTERVAL,
+    GET_CLIENT_SYNC_INTERVAL,
+    SET_CLIENT_SYNC_INTERVAL,
+    GET_PANIC_THRESHOLD,
+    SET_PANIC_THRESHOLD,
+    GET_AUTO_DISABLE,
+    SET_AUTO_DISABLE,
+    FORCE_NETWORKLESS_MASTER_MODE,
+};
+
+const String16 ICommonTimeConfig::kServiceName("common_time.config");
+
+class BpCommonTimeConfig : public BpInterface<ICommonTimeConfig>
+{
+  public:
+    BpCommonTimeConfig(const sp<IBinder>& impl)
+        : BpInterface<ICommonTimeConfig>(impl) {}
+
+    virtual status_t getMasterElectionPriority(uint8_t *priority) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_MASTER_ELECTION_PRIORITY,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *priority = static_cast<uint8_t>(reply.readInt32());
+            }
+        }
+
+        return status;
+    }
+
+    virtual status_t setMasterElectionPriority(uint8_t priority) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        data.writeInt32(static_cast<int32_t>(priority));
+        status_t status = remote()->transact(SET_MASTER_ELECTION_PRIORITY,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+
+    virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_MASTER_ELECTION_ENDPOINT,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                deserializeSockaddr(&reply, addr);
+            }
+        }
+
+        return status;
+    }
+
+    virtual status_t setMasterElectionEndpoint(
+            const struct sockaddr_storage *addr) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        if (!canSerializeSockaddr(addr))
+            return BAD_VALUE;
+        if (NULL == addr) {
+            data.writeInt32(0);
+        } else {
+            data.writeInt32(1);
+            serializeSockaddr(&data, addr);
+        }
+        status_t status = remote()->transact(SET_MASTER_ELECTION_ENDPOINT,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+
+    virtual status_t getMasterElectionGroupId(uint64_t *id) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_MASTER_ELECTION_GROUP_ID,
+                                             data,
+                                             &reply);
+
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *id = static_cast<uint64_t>(reply.readInt64());
+            }
+        }
+
+        return status;
+    }
+
+    virtual status_t setMasterElectionGroupId(uint64_t id) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        data.writeInt64(id);
+        status_t status = remote()->transact(SET_MASTER_ELECTION_GROUP_ID,
+                                             data,
+                                             &reply);
+
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+
+    virtual status_t getInterfaceBinding(String16& ifaceName) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_INTERFACE_BINDING,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                ifaceName = reply.readString16();
+            }
+        }
+
+        return status;
+    }
+
+    virtual status_t setInterfaceBinding(const String16& ifaceName) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        data.writeString16(ifaceName);
+        status_t status = remote()->transact(SET_INTERFACE_BINDING,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+
+    virtual status_t getMasterAnnounceInterval(int *interval) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_MASTER_ANNOUNCE_INTERVAL,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *interval = reply.readInt32();
+            }
+        }
+
+        return status;
+    }
+
+    virtual status_t setMasterAnnounceInterval(int interval) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        data.writeInt32(interval);
+        status_t status = remote()->transact(SET_MASTER_ANNOUNCE_INTERVAL,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+
+    virtual status_t getClientSyncInterval(int *interval) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_CLIENT_SYNC_INTERVAL,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *interval = reply.readInt32();
+            }
+        }
+
+        return status;
+    }
+
+    virtual status_t setClientSyncInterval(int interval) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        data.writeInt32(interval);
+        status_t status = remote()->transact(SET_CLIENT_SYNC_INTERVAL,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+
+    virtual status_t getPanicThreshold(int *threshold) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_PANIC_THRESHOLD,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *threshold = reply.readInt32();
+            }
+        }
+
+        return status;
+    }
+
+    virtual status_t setPanicThreshold(int threshold) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        data.writeInt32(threshold);
+        status_t status = remote()->transact(SET_PANIC_THRESHOLD,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+
+    virtual status_t getAutoDisable(bool *autoDisable) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        status_t status = remote()->transact(GET_AUTO_DISABLE,
+                                             data,
+                                             &reply);
+        if (status == OK) {
+            status = reply.readInt32();
+            if (status == OK) {
+                *autoDisable = (0 != reply.readInt32());
+            }
+        }
+
+        return status;
+    }
+
+    virtual status_t setAutoDisable(bool autoDisable) {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        data.writeInt32(autoDisable ? 1 : 0);
+        status_t status = remote()->transact(SET_AUTO_DISABLE,
+                                             data,
+                                             &reply);
+
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+
+    virtual status_t forceNetworklessMasterMode() {
+        Parcel data, reply;
+        data.writeInterfaceToken(ICommonTimeConfig::getInterfaceDescriptor());
+        status_t status = remote()->transact(FORCE_NETWORKLESS_MASTER_MODE,
+                                             data,
+                                             &reply);
+
+        if (status == OK) {
+            status = reply.readInt32();
+        }
+
+        return status;
+    }
+};
+
+IMPLEMENT_META_INTERFACE(CommonTimeConfig, "android.os.ICommonTimeConfig");
+
+status_t BnCommonTimeConfig::onTransact(uint32_t code,
+                                   const Parcel& data,
+                                   Parcel* reply,
+                                   uint32_t flags) {
+    switch(code) {
+        case GET_MASTER_ELECTION_PRIORITY: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            uint8_t priority;
+            status_t status = getMasterElectionPriority(&priority);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt32(static_cast<int32_t>(priority));
+            }
+            return OK;
+        } break;
+
+        case SET_MASTER_ELECTION_PRIORITY: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            uint8_t priority = static_cast<uint8_t>(data.readInt32());
+            status_t status = setMasterElectionPriority(priority);
+            reply->writeInt32(status);
+            return OK;
+        } break;
+
+        case GET_MASTER_ELECTION_ENDPOINT: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            struct sockaddr_storage addr;
+            status_t status = getMasterElectionEndpoint(&addr);
+
+            if ((status == OK) && !canSerializeSockaddr(&addr)) {
+                status = UNKNOWN_ERROR;
+            }
+
+            reply->writeInt32(status);
+
+            if (status == OK) {
+                serializeSockaddr(reply, &addr);
+            }
+
+            return OK;
+        } break;
+
+        case SET_MASTER_ELECTION_ENDPOINT: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            struct sockaddr_storage addr;
+            int hasAddr = data.readInt32();
+
+            status_t status;
+            if (hasAddr) {
+                deserializeSockaddr(&data, &addr);
+                status = setMasterElectionEndpoint(&addr);
+            } else {
+                status = setMasterElectionEndpoint(&addr);
+            }
+
+            reply->writeInt32(status);
+            return OK;
+        } break;
+
+        case GET_MASTER_ELECTION_GROUP_ID: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            uint64_t id;
+            status_t status = getMasterElectionGroupId(&id);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt64(id);
+            }
+            return OK;
+        } break;
+
+        case SET_MASTER_ELECTION_GROUP_ID: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            uint64_t id = static_cast<uint64_t>(data.readInt64());
+            status_t status = setMasterElectionGroupId(id);
+            reply->writeInt32(status);
+            return OK;
+        } break;
+
+        case GET_INTERFACE_BINDING: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            String16 ret;
+            status_t status = getInterfaceBinding(ret);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeString16(ret);
+            }
+            return OK;
+        } break;
+
+        case SET_INTERFACE_BINDING: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            String16 ifaceName;
+            ifaceName = data.readString16();
+            status_t status = setInterfaceBinding(ifaceName);
+            reply->writeInt32(status);
+            return OK;
+        } break;
+
+        case GET_MASTER_ANNOUNCE_INTERVAL: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            int interval;
+            status_t status = getMasterAnnounceInterval(&interval);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt32(interval);
+            }
+            return OK;
+        } break;
+
+        case SET_MASTER_ANNOUNCE_INTERVAL: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            int interval = data.readInt32();
+            status_t status = setMasterAnnounceInterval(interval);
+            reply->writeInt32(status);
+            return OK;
+        } break;
+
+        case GET_CLIENT_SYNC_INTERVAL: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            int interval;
+            status_t status = getClientSyncInterval(&interval);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt32(interval);
+            }
+            return OK;
+        } break;
+
+        case SET_CLIENT_SYNC_INTERVAL: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            int interval = data.readInt32();
+            status_t status = setClientSyncInterval(interval);
+            reply->writeInt32(status);
+            return OK;
+        } break;
+
+        case GET_PANIC_THRESHOLD: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            int threshold;
+            status_t status = getPanicThreshold(&threshold);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt32(threshold);
+            }
+            return OK;
+        } break;
+
+        case SET_PANIC_THRESHOLD: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            int threshold = data.readInt32();
+            status_t status = setPanicThreshold(threshold);
+            reply->writeInt32(status);
+            return OK;
+        } break;
+
+        case GET_AUTO_DISABLE: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            bool autoDisable;
+            status_t status = getAutoDisable(&autoDisable);
+            reply->writeInt32(status);
+            if (status == OK) {
+                reply->writeInt32(autoDisable ? 1 : 0);
+            }
+            return OK;
+        } break;
+
+        case SET_AUTO_DISABLE: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            bool autoDisable = (0 != data.readInt32());
+            status_t status = setAutoDisable(autoDisable);
+            reply->writeInt32(status);
+            return OK;
+        } break;
+
+        case FORCE_NETWORKLESS_MASTER_MODE: {
+            CHECK_INTERFACE(ICommonTimeConfig, data, reply);
+            status_t status = forceNetworklessMasterMode();
+            reply->writeInt32(status);
+            return OK;
+        } break;
+    }
+    return BBinder::onTransact(code, data, reply, flags);
+}
+
+}; // namespace android
+
diff --git a/libs/common_time/cc_helper.cpp b/libs/common_time/cc_helper.cpp
new file mode 100644
index 0000000..8d8556c
--- /dev/null
+++ b/libs/common_time/cc_helper.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+#include <stdint.h>
+
+#include <common_time/cc_helper.h>
+#include <common_time/ICommonClock.h>
+#include <utils/threads.h>
+
+namespace android {
+
+Mutex CCHelper::lock_;
+sp<ICommonClock> CCHelper::common_clock_;
+sp<ICommonClockListener> CCHelper::common_clock_listener_;
+uint32_t CCHelper::ref_count_ = 0;
+
+bool CCHelper::verifyClock_l() {
+    bool ret = false;
+
+    if (common_clock_ == NULL) {
+        common_clock_ = ICommonClock::getInstance();
+        if (common_clock_ == NULL)
+            goto bailout;
+    }
+
+    if (ref_count_ > 0) {
+        if (common_clock_listener_ == NULL) {
+            common_clock_listener_ = new CommonClockListener();
+            if (common_clock_listener_ == NULL)
+                goto bailout;
+
+            if (OK != common_clock_->registerListener(common_clock_listener_))
+                goto bailout;
+        }
+    }
+
+    ret = true;
+
+bailout:
+    if (!ret) {
+        common_clock_listener_ = NULL;
+        common_clock_ = NULL;
+    }
+    return ret;
+}
+
+CCHelper::CCHelper() {
+    Mutex::Autolock lock(&lock_);
+    ref_count_++;
+    verifyClock_l();
+}
+
+CCHelper::~CCHelper() {
+    Mutex::Autolock lock(&lock_);
+
+    assert(ref_count_ > 0);
+    ref_count_--;
+
+    // If we were the last CCHelper instance in the system, and we had
+    // previously register a listener, unregister it now so that the common time
+    // service has the chance to go into auto-disabled mode.
+    if (!ref_count_ &&
+       (common_clock_ != NULL) &&
+       (common_clock_listener_ != NULL)) {
+        common_clock_->unregisterListener(common_clock_listener_);
+        common_clock_listener_ = NULL;
+    }
+}
+
+void CCHelper::CommonClockListener::onTimelineChanged(uint64_t timelineID) {
+    // do nothing; listener is only really used as a token so the server can
+    // find out when clients die.
+}
+
+// Helper methods which attempts to make calls to the common time binder
+// service.  If the first attempt fails with DEAD_OBJECT, the helpers will
+// attempt to make a connection to the service again (assuming that the process
+// hosting the service had crashed and the client proxy we are holding is dead)
+// If the second attempt fails, or no connection can be made, the we let the
+// error propagate up the stack and let the caller deal with the situation as
+// best they can.
+#define CCHELPER_METHOD(decl, call)                 \
+    status_t CCHelper::decl {                       \
+        Mutex::Autolock lock(&lock_);               \
+                                                    \
+        if (!verifyClock_l())                       \
+            return DEAD_OBJECT;                     \
+                                                    \
+        status_t status = common_clock_->call;      \
+        if (DEAD_OBJECT == status) {                \
+            if (!verifyClock_l())                   \
+                return DEAD_OBJECT;                 \
+            status = common_clock_->call;           \
+        }                                           \
+                                                    \
+        return status;                              \
+    }
+
+#define VERIFY_CLOCK()
+
+CCHELPER_METHOD(isCommonTimeValid(bool* valid, uint32_t* timelineID),
+                isCommonTimeValid(valid, timelineID))
+CCHELPER_METHOD(commonTimeToLocalTime(int64_t commonTime, int64_t* localTime),
+                commonTimeToLocalTime(commonTime, localTime))
+CCHELPER_METHOD(localTimeToCommonTime(int64_t localTime, int64_t* commonTime),
+                localTimeToCommonTime(localTime, commonTime))
+CCHELPER_METHOD(getCommonTime(int64_t* commonTime),
+                getCommonTime(commonTime))
+CCHELPER_METHOD(getCommonFreq(uint64_t* freq),
+                getCommonFreq(freq))
+CCHELPER_METHOD(getLocalTime(int64_t* localTime),
+                getLocalTime(localTime))
+CCHELPER_METHOD(getLocalFreq(uint64_t* freq),
+                getLocalFreq(freq))
+
+}  // namespace android
diff --git a/libs/common_time/local_clock.cpp b/libs/common_time/local_clock.cpp
new file mode 100644
index 0000000..9be3c46
--- /dev/null
+++ b/libs/common_time/local_clock.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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 "common_time"
+#include <utils/Log.h>
+
+#include <assert.h>
+#include <stdint.h>
+
+#include <common_time/local_clock.h>
+#include <hardware/hardware.h>
+#include <hardware/local_time_hal.h>
+#include <utils/Errors.h>
+#include <utils/threads.h>
+
+namespace android {
+
+Mutex LocalClock::dev_lock_;
+local_time_hw_device_t* LocalClock::dev_ = NULL;
+
+LocalClock::LocalClock() {
+    int res;
+    const hw_module_t* mod;
+
+    AutoMutex lock(&dev_lock_);
+
+    if (dev_ != NULL)
+        return;
+
+    res = hw_get_module_by_class(LOCAL_TIME_HARDWARE_MODULE_ID, NULL, &mod);
+    if (res) {
+        LOGE("Failed to open local time HAL module (res = %d)", res);
+    } else {
+        res = local_time_hw_device_open(mod, &dev_);
+        if (res) {
+            LOGE("Failed to open local time HAL device (res = %d)", res);
+            dev_ = NULL;
+        }
+    }
+}
+
+bool LocalClock::initCheck() {
+    return (NULL != dev_);
+}
+
+int64_t LocalClock::getLocalTime() {
+    assert(NULL != dev_);
+    assert(NULL != dev_->get_local_time);
+
+    return dev_->get_local_time(dev_);
+}
+
+uint64_t LocalClock::getLocalFreq() {
+    assert(NULL != dev_);
+    assert(NULL != dev_->get_local_freq);
+
+    return dev_->get_local_freq(dev_);
+}
+
+status_t LocalClock::setLocalSlew(int16_t rate) {
+    assert(NULL != dev_);
+
+    if (!dev_->set_local_slew)
+        return INVALID_OPERATION;
+
+    return static_cast<status_t>(dev_->set_local_slew(dev_, rate));
+}
+
+int32_t LocalClock::getDebugLog(struct local_time_debug_event* records,
+                                int max_records) {
+    assert(NULL != dev_);
+
+    if (!dev_->get_debug_log)
+        return INVALID_OPERATION;
+
+    return dev_->get_debug_log(dev_, records, max_records);
+}
+
+}  // namespace android
diff --git a/libs/common_time/utils.cpp b/libs/common_time/utils.cpp
new file mode 100644
index 0000000..6539171
--- /dev/null
+++ b/libs/common_time/utils.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <arpa/inet.h>
+#include <linux/socket.h>
+
+#include <binder/Parcel.h>
+
+namespace android {
+
+bool canSerializeSockaddr(const struct sockaddr_storage* addr) {
+    switch (addr->ss_family) {
+        case AF_INET:
+        case AF_INET6:
+            return true;
+        default:
+            return false;
+    }
+}
+
+void serializeSockaddr(Parcel* p, const struct sockaddr_storage* addr) {
+    switch (addr->ss_family) {
+        case AF_INET: {
+            const struct sockaddr_in* s =
+                reinterpret_cast<const struct sockaddr_in*>(addr);
+            p->writeInt32(AF_INET);
+            p->writeInt32(ntohl(s->sin_addr.s_addr));
+            p->writeInt32(static_cast<int32_t>(ntohs(s->sin_port)));
+        } break;
+
+        case AF_INET6: {
+            const struct sockaddr_in6* s =
+                reinterpret_cast<const struct sockaddr_in6*>(addr);
+            const int32_t* a =
+                reinterpret_cast<const int32_t*>(s->sin6_addr.s6_addr);
+            p->writeInt32(AF_INET6);
+            p->writeInt32(ntohl(a[0]));
+            p->writeInt32(ntohl(a[1]));
+            p->writeInt32(ntohl(a[2]));
+            p->writeInt32(ntohl(a[3]));
+            p->writeInt32(static_cast<int32_t>(ntohs(s->sin6_port)));
+            p->writeInt32(ntohl(s->sin6_flowinfo));
+            p->writeInt32(ntohl(s->sin6_scope_id));
+        } break;
+    }
+}
+
+void deserializeSockaddr(const Parcel* p, struct sockaddr_storage* addr) {
+    memset(addr, 0, sizeof(addr));
+
+    addr->ss_family = p->readInt32();
+    switch(addr->ss_family) {
+        case AF_INET: {
+            struct sockaddr_in* s =
+                reinterpret_cast<struct sockaddr_in*>(addr);
+            s->sin_addr.s_addr = htonl(p->readInt32());
+            s->sin_port = htons(static_cast<uint16_t>(p->readInt32()));
+        } break;
+
+        case AF_INET6: {
+            struct sockaddr_in6* s =
+                reinterpret_cast<struct sockaddr_in6*>(addr);
+            int32_t* a = reinterpret_cast<int32_t*>(s->sin6_addr.s6_addr);
+
+            a[0] = htonl(p->readInt32());
+            a[1] = htonl(p->readInt32());
+            a[2] = htonl(p->readInt32());
+            a[3] = htonl(p->readInt32());
+            s->sin6_port = htons(static_cast<uint16_t>(p->readInt32()));
+            s->sin6_flowinfo = htonl(p->readInt32());
+            s->sin6_scope_id = htonl(p->readInt32());
+        } break;
+    }
+}
+
+}  // namespace android
diff --git a/libs/common_time/utils.h b/libs/common_time/utils.h
new file mode 100644
index 0000000..ce79d0d
--- /dev/null
+++ b/libs/common_time/utils.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef ANDROID_LIBCOMMONCLOCK_UTILS_H
+#define ANDROID_LIBCOMMONCLOCK_UTILS_H
+
+#include <linux/socket.h>
+
+#include <binder/Parcel.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+extern bool canSerializeSockaddr(const struct sockaddr_storage* addr);
+extern void serializeSockaddr(Parcel* p, const struct sockaddr_storage* addr);
+extern status_t deserializeSockaddr(const Parcel* p,
+                                    struct sockaddr_storage* addr);
+
+};  // namespace android
+
+#endif  // ANDROID_LIBCOMMONCLOCK_UTILS_H
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a0881a7..a836718 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -52,6 +52,7 @@
     private final Handler mHandler;
     private long mVolumeKeyUpTime;
     private int  mVolumeControlStream = -1;
+    private final boolean mUseMasterVolume;
     private static String TAG = "AudioManager";
     private static boolean localLOGV = false;
 
@@ -95,7 +96,8 @@
      * @see #EXTRA_VIBRATE_SETTING
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
-    public static final String VIBRATE_SETTING_CHANGED_ACTION = "android.media.VIBRATE_SETTING_CHANGED";
+    public static final String VIBRATE_SETTING_CHANGED_ACTION =
+        "android.media.VIBRATE_SETTING_CHANGED";
 
     /**
      * @hide Broadcast intent when the volume for a particular stream type changes.
@@ -109,6 +111,27 @@
     public static final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";
 
     /**
+     * @hide Broadcast intent when the master volume changes.
+     * Includes the new volume
+     *
+     * @see #EXTRA_MASTER_VOLUME_VALUE
+     * @see #EXTRA_PREV_MASTER_VOLUME_VALUE
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String MASTER_VOLUME_CHANGED_ACTION =
+        "android.media.MASTER_VOLUME_CHANGED_ACTION";
+
+    /**
+     * @hide Broadcast intent when the master mute state changes.
+     * Includes the the new volume
+     *
+     * @see #EXTRA_MASTER_VOLUME_MUTED
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String MASTER_MUTE_CHANGED_ACTION =
+        "android.media.MASTER_MUTE_CHANGED_ACTION";
+
+    /**
      * The new vibrate setting for a particular type.
      *
      * @see #VIBRATE_SETTING_CHANGED_ACTION
@@ -145,6 +168,27 @@
     public static final String EXTRA_PREV_VOLUME_STREAM_VALUE =
         "android.media.EXTRA_PREV_VOLUME_STREAM_VALUE";
 
+    /**
+     * @hide The new master volume value for the master volume changed intent.
+     * Value is integer between 0 and 100 inclusive.
+     */
+    public static final String EXTRA_MASTER_VOLUME_VALUE =
+        "android.media.EXTRA_MASTER_VOLUME_VALUE";
+
+    /**
+     * @hide The previous master volume value for the master volume changed intent.
+     * Value is integer between 0 and 100 inclusive.
+     */
+    public static final String EXTRA_PREV_MASTER_VOLUME_VALUE =
+        "android.media.EXTRA_PREV_MASTER_VOLUME_VALUE";
+
+    /**
+     * @hide The new master volume mute state for the master mute changed intent.
+     * Value is boolean
+     */
+    public static final String EXTRA_MASTER_VOLUME_MUTED =
+        "android.media.EXTRA_MASTER_VOLUME_MUTED";
+
     /** The audio stream for phone calls */
     public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL;
     /** The audio stream for system sounds */
@@ -360,6 +404,8 @@
     public AudioManager(Context context) {
         mContext = context;
         mHandler = new Handler(context.getMainLooper());
+        mUseMasterVolume = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_useMasterVolume);
     }
 
     private static IAudioService getService()
@@ -375,11 +421,12 @@
     /**
      * @hide
      */
-    public void preDispatchKeyEvent(int keyCode, int stream) {
+    public void preDispatchKeyEvent(KeyEvent event, int stream) {
         /*
          * If the user hits another key within the play sound delay, then
          * cancel the sound
          */
+        int keyCode = event.getKeyCode();
         if (keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && keyCode != KeyEvent.KEYCODE_VOLUME_UP
                 && keyCode != KeyEvent.KEYCODE_VOLUME_MUTE
                 && mVolumeKeyUpTime + VolumePanel.PLAY_SOUND_DELAY
@@ -388,15 +435,20 @@
              * The user has hit another key during the delay (e.g., 300ms)
              * since the last volume key up, so cancel any sounds.
              */
-            adjustSuggestedStreamVolume(AudioManager.ADJUST_SAME,
+            if (mUseMasterVolume) {
+                adjustMasterVolume(ADJUST_SAME, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+            } else {
+                adjustSuggestedStreamVolume(ADJUST_SAME,
                         stream, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
+            }
         }
     }
 
     /**
      * @hide
      */
-    public void handleKeyDown(int keyCode, int stream) {
+    public void handleKeyDown(KeyEvent event, int stream) {
+        int keyCode = event.getKeyCode();
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -405,19 +457,33 @@
                  * responsive to the user.
                  */
                 int flags = FLAG_SHOW_UI | FLAG_VIBRATE;
-                if (mVolumeControlStream != -1) {
-                    stream = mVolumeControlStream;
-                    flags |= FLAG_FORCE_STREAM;
+                if (mUseMasterVolume) {
+                    adjustMasterVolume(
+                            keyCode == KeyEvent.KEYCODE_VOLUME_UP
+                                    ? ADJUST_RAISE
+                                    : ADJUST_LOWER,
+                            flags);
+                } else {
+                    if (mVolumeControlStream != -1) {
+                        stream = mVolumeControlStream;
+                        flags |= FLAG_FORCE_STREAM;
+                    }
+                    adjustSuggestedStreamVolume(
+                            keyCode == KeyEvent.KEYCODE_VOLUME_UP
+                                    ? ADJUST_RAISE
+                                    : ADJUST_LOWER,
+                            stream,
+                            flags);
                 }
-                adjustSuggestedStreamVolume(
-                        keyCode == KeyEvent.KEYCODE_VOLUME_UP
-                                ? ADJUST_RAISE
-                                : ADJUST_LOWER,
-                        stream,
-                        flags);
                 break;
             case KeyEvent.KEYCODE_VOLUME_MUTE:
-                // TODO: Actually handle MUTE.
+                if (event.getRepeatCount() == 0) {
+                    if (mUseMasterVolume) {
+                        setMasterMute(!isMasterMute());
+                    } else {
+                        // TODO: Actually handle MUTE.
+                    }
+                }
                 break;
         }
     }
@@ -425,7 +491,8 @@
     /**
      * @hide
      */
-    public void handleKeyUp(int keyCode, int stream) {
+    public void handleKeyUp(KeyEvent event, int stream) {
+        int keyCode = event.getKeyCode();
         switch (keyCode) {
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -433,21 +500,24 @@
                  * Play a sound. This is done on key up since we don't want the
                  * sound to play when a user holds down volume down to mute.
                  */
-                int flags = FLAG_PLAY_SOUND;
-                if (mVolumeControlStream != -1) {
-                    stream = mVolumeControlStream;
-                    flags |= FLAG_FORCE_STREAM;
+                if (mUseMasterVolume) {
+                    if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+                        adjustMasterVolume(ADJUST_SAME, FLAG_PLAY_SOUND);
+                    }
+                } else {
+                    int flags = FLAG_PLAY_SOUND;
+                    if (mVolumeControlStream != -1) {
+                        stream = mVolumeControlStream;
+                        flags |= FLAG_FORCE_STREAM;
+                    }
+                    adjustSuggestedStreamVolume(
+                            ADJUST_SAME,
+                            stream,
+                            flags);
                 }
-                adjustSuggestedStreamVolume(
-                        ADJUST_SAME,
-                        stream,
-                        flags);
 
                 mVolumeKeyUpTime = SystemClock.uptimeMillis();
                 break;
-            case KeyEvent.KEYCODE_VOLUME_MUTE:
-                // TODO: Actually handle MUTE.
-                break;
         }
     }
 
@@ -470,7 +540,13 @@
     public void adjustStreamVolume(int streamType, int direction, int flags) {
         IAudioService service = getService();
         try {
-            service.adjustStreamVolume(streamType, direction, flags);
+            Log.d(TAG, "adjustStreamVolume mUseMasterVolume=" + mUseMasterVolume
+                    + " direction=" + direction + " flags=0x" + Integer.toHexString(flags));
+            if (mUseMasterVolume) {
+                service.adjustMasterVolume(direction, flags);
+            } else {
+                service.adjustStreamVolume(streamType, direction, flags);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in adjustStreamVolume", e);
         }
@@ -496,7 +572,11 @@
     public void adjustVolume(int direction, int flags) {
         IAudioService service = getService();
         try {
-            service.adjustVolume(direction, flags);
+            if (mUseMasterVolume) {
+                service.adjustMasterVolume(direction, flags);
+            } else {
+                service.adjustVolume(direction, flags);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in adjustVolume", e);
         }
@@ -522,9 +602,32 @@
     public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
         IAudioService service = getService();
         try {
-            service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
+            if (mUseMasterVolume) {
+                service.adjustMasterVolume(direction, flags);
+            } else {
+                service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
+            }
         } catch (RemoteException e) {
-            Log.e(TAG, "Dead object in adjustVolume", e);
+            Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e);
+        }
+    }
+
+    /**
+     * Adjusts the master volume for the device's audio amplifier.
+     * <p>
+     *
+     * @param direction The direction to adjust the volume. One of
+     *            {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
+     *            {@link #ADJUST_SAME}.
+     * @param flags One or more flags.
+     * @hide
+     */
+    public void adjustMasterVolume(int direction, int flags) {
+        IAudioService service = getService();
+        try {
+            service.adjustMasterVolume(direction, flags);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in adjustMasterVolume", e);
         }
     }
 
@@ -570,7 +673,11 @@
     public int getStreamMaxVolume(int streamType) {
         IAudioService service = getService();
         try {
-            return service.getStreamMaxVolume(streamType);
+            if (mUseMasterVolume) {
+                return service.getMasterMaxVolume();
+            } else {
+                return service.getStreamMaxVolume(streamType);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in getStreamMaxVolume", e);
             return 0;
@@ -588,7 +695,11 @@
     public int getStreamVolume(int streamType) {
         IAudioService service = getService();
         try {
-            return service.getStreamVolume(streamType);
+            if (mUseMasterVolume) {
+                return service.getMasterVolume();
+            } else {
+                return service.getStreamVolume(streamType);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in getStreamVolume", e);
             return 0;
@@ -603,7 +714,11 @@
     public int getLastAudibleStreamVolume(int streamType) {
         IAudioService service = getService();
         try {
-            return service.getLastAudibleStreamVolume(streamType);
+            if (mUseMasterVolume) {
+                return service.getLastAudibleMasterVolume();
+            } else {
+                return service.getLastAudibleStreamVolume(streamType);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in getLastAudibleStreamVolume", e);
             return 0;
@@ -646,13 +761,82 @@
     public void setStreamVolume(int streamType, int index, int flags) {
         IAudioService service = getService();
         try {
-            service.setStreamVolume(streamType, index, flags);
+            if (mUseMasterVolume) {
+                service.setMasterVolume(index, flags);
+            } else {
+                service.setStreamVolume(streamType, index, flags);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in setStreamVolume", e);
         }
     }
 
     /**
+     * Returns the maximum volume index for master volume.
+     *
+     * @hide
+     */
+    public int getMasterMaxVolume() {
+        IAudioService service = getService();
+        try {
+            return service.getMasterMaxVolume();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in getMasterMaxVolume", e);
+            return 0;
+        }
+    }
+
+    /**
+     * Returns the current volume index for master volume.
+     *
+     * @return The current volume index for master volume.
+     * @hide
+     */
+    public int getMasterVolume() {
+        IAudioService service = getService();
+        try {
+            return service.getMasterVolume();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in getMasterVolume", e);
+            return 0;
+        }
+    }
+
+    /**
+     * Get last audible volume before master volume was muted.
+     *
+     * @hide
+     */
+    public int getLastAudibleMasterVolume() {
+        IAudioService service = getService();
+        try {
+            return service.getLastAudibleMasterVolume();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in getLastAudibleMasterVolume", e);
+            return 0;
+        }
+    }
+
+    /**
+     * Sets the volume index for master volume.
+     *
+     * @param index The volume index to set. See
+     *            {@link #getMasterMaxVolume(int)} for the largest valid value.
+     * @param flags One or more flags.
+     * @see #getMasterMaxVolume(int)
+     * @see #getMasterVolume(int)
+     * @hide
+     */
+    public void setMasterVolume(int index, int flags) {
+        IAudioService service = getService();
+        try {
+            service.setMasterVolume(index, flags);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setMasterVolume", e);
+        }
+    }
+
+    /**
      * Solo or unsolo a particular stream. All other streams are muted.
      * <p>
      * The solo command is protected against client process death: if a process
@@ -723,6 +907,35 @@
     }
 
     /**
+     * set master mute state.
+     *
+     * @hide
+     */
+    public void setMasterMute(boolean state) {
+        IAudioService service = getService();
+        try {
+            service.setMasterMute(state, mICallBack);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in setMasterMute", e);
+        }
+    }
+
+    /**
+     * get master mute state.
+     *
+     * @hide
+     */
+    public boolean isMasterMute() {
+        IAudioService service = getService();
+        try {
+            return service.isMasterMute();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Dead object in isMasterMute", e);
+            return false;
+        }
+    }
+
+    /**
      * forces the stream controlled by hard volume keys
      * specifying streamType == -1 releases control to the
      * logic.
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 37aacab..86641832 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -114,6 +114,7 @@
     // AudioHandler message.whats
     private static final int MSG_SET_SYSTEM_VOLUME = 0;
     private static final int MSG_PERSIST_VOLUME = 1;
+    private static final int MSG_PERSIST_MASTER_VOLUME = 2;
     private static final int MSG_PERSIST_RINGER_MODE = 3;
     private static final int MSG_PERSIST_VIBRATE_SETTING = 4;
     private static final int MSG_MEDIA_SERVER_DIED = 5;
@@ -131,7 +132,6 @@
     // Timeout for connection to bluetooth headset service
     private static final int BT_HEADSET_CNCT_TIMEOUT_MS = 3000;
 
-
     /** @see AudioSystemThread */
     private AudioSystemThread mAudioSystemThread;
     /** @see AudioHandler */
@@ -149,6 +149,10 @@
     private static final int NUM_SOUNDPOOL_CHANNELS = 4;
     private static final int SOUND_EFFECT_VOLUME = 1000;
 
+    // Internally master volume is a float in the 0.0 - 1.0 range,
+    // but to support integer based AudioManager API we translate it to 0 - 100
+    private static final int MAX_MASTER_VOLUME = 100;
+
     /* Sound effect file names  */
     private static final String SOUND_EFFECTS_PATH = "/media/audio/ui/";
     private static final String[] SOUND_EFFECT_FILES = new String[] {
@@ -268,6 +272,11 @@
     // Forced device usage for communications
     private int mForcedUseForComm;
 
+    // True if we have master volume support
+    private final boolean mUseMasterVolume;
+
+    private final int[] mMasterVolumeRamp;
+
     // List of binder death handlers for setMode() client processes.
     // The last process to have called setMode() is at the top of the list.
     private ArrayList <SetModeDeathHandler> mSetModeDeathHandlers = new ArrayList <SetModeDeathHandler>();
@@ -393,6 +402,13 @@
         TelephonyManager tmgr = (TelephonyManager)
                 context.getSystemService(Context.TELEPHONY_SERVICE);
         tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
+
+        mUseMasterVolume = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_useMasterVolume);
+        restoreMasterVolume();
+
+        mMasterVolumeRamp = context.getResources().getIntArray(
+                com.android.internal.R.array.config_masterVolumeRamp);
     }
 
     private void createAudioSystemThread() {
@@ -575,6 +591,32 @@
         sendVolumeUpdate(streamType, oldIndex, index, flags);
     }
 
+    /** @see AudioManager#adjustMasterVolume(int) */
+    public void adjustMasterVolume(int direction, int flags) {
+        ensureValidDirection(direction);
+        int volume = Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
+        int delta = 0;
+        for (int i = 0; i < mMasterVolumeRamp.length; i += 2) {
+            int testVolume = mMasterVolumeRamp[i];
+            int testDelta =  mMasterVolumeRamp[i + 1];
+            if (direction == AudioManager.ADJUST_RAISE) {
+                if (volume >= testVolume) {
+                    delta = testDelta;
+                } else {
+                    break;
+                }
+            } else if (direction == AudioManager.ADJUST_LOWER) {
+                if (volume - testDelta >= testVolume) {
+                    delta = -testDelta;
+                } else {
+                    break;
+                }
+            }
+        }
+//        Log.d(TAG, "adjustMasterVolume volume: " + volume + " delta: " + delta + " direction: " + direction);
+        setMasterVolume(volume + delta, flags);
+    }
+
     /** @see AudioManager#setStreamVolume(int, int, int) */
     public void setStreamVolume(int streamType, int index, int flags) {
         ensureValidStreamType(streamType);
@@ -624,6 +666,27 @@
         mContext.sendBroadcast(intent);
     }
 
+    // UI update and Broadcast Intent
+    private void sendMasterVolumeUpdate(int flags, int oldVolume, int newVolume) {
+        mVolumePanel.postMasterVolumeChanged(flags);
+
+        Intent intent = new Intent(AudioManager.MASTER_VOLUME_CHANGED_ACTION);
+        intent.putExtra(AudioManager.EXTRA_PREV_MASTER_VOLUME_VALUE, oldVolume);
+        intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_VALUE, newVolume);
+        mContext.sendBroadcast(intent);
+    }
+
+    // UI update and Broadcast Intent
+    private void sendMasterMuteUpdate(boolean muted, int flags) {
+        mVolumePanel.postMasterMuteChanged(flags);
+
+        Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION);
+        intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted);
+        long origCallerIdentityToken = Binder.clearCallingIdentity();
+        mContext.sendStickyBroadcast(intent);
+        Binder.restoreCallingIdentity(origCallerIdentityToken);
+    }
+
     /**
      * Sets the stream state's index, and posts a message to set system volume.
      * This will not call out to the UI. Assumes a valid stream type.
@@ -677,18 +740,64 @@
         return (mStreamStates[streamType].muteCount() != 0);
     }
 
+    /** @see AudioManager#setMasterMute(boolean, IBinder) */
+    public void setMasterMute(boolean state, IBinder cb) {
+        if (state != AudioSystem.getMasterMute()) {
+            AudioSystem.setMasterMute(state);
+            sendMasterMuteUpdate(state, AudioManager.FLAG_SHOW_UI);
+        }
+    }
+
+    /** get master mute state. */
+    public boolean isMasterMute() {
+        return AudioSystem.getMasterMute();
+    }
+
     /** @see AudioManager#getStreamVolume(int) */
     public int getStreamVolume(int streamType) {
         ensureValidStreamType(streamType);
         return (mStreamStates[streamType].mIndex + 5) / 10;
     }
 
+    public int getMasterVolume() {
+        if (isMasterMute()) return 0;
+        return getLastAudibleMasterVolume();
+    }
+
+    public void setMasterVolume(int volume, int flags) {
+        if (volume < 0) {
+            volume = 0;
+        } else if (volume > MAX_MASTER_VOLUME) {
+            volume = MAX_MASTER_VOLUME;
+        }
+        doSetMasterVolume((float)volume / MAX_MASTER_VOLUME, flags);
+    }
+
+    private void doSetMasterVolume(float volume, int flags) {
+        // don't allow changing master volume when muted
+        if (!AudioSystem.getMasterMute()) {
+            int oldVolume = getMasterVolume();
+            AudioSystem.setMasterVolume(volume);
+
+            int newVolume = getMasterVolume();
+            if (newVolume != oldVolume) {
+                // Post a persist master volume msg
+                sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME, 0, SENDMSG_REPLACE,
+                        Math.round(volume * (float)1000.0), 0, null, PERSIST_DELAY);
+                sendMasterVolumeUpdate(flags, oldVolume, newVolume);
+            }
+        }
+    }
+
     /** @see AudioManager#getStreamMaxVolume(int) */
     public int getStreamMaxVolume(int streamType) {
         ensureValidStreamType(streamType);
         return (mStreamStates[streamType].getMaxIndex() + 5) / 10;
     }
 
+    public int getMasterMaxVolume() {
+        return MAX_MASTER_VOLUME;
+    }
 
     /** Get last audible volume before stream was muted. */
     public int getLastAudibleStreamVolume(int streamType) {
@@ -696,6 +805,11 @@
         return (mStreamStates[streamType].mLastAudibleIndex + 5) / 10;
     }
 
+    /** Get last audible master volume before it was muted. */
+    public int getLastAudibleMasterVolume() {
+        return Math.round(AudioSystem.getMasterVolume() * MAX_MASTER_VOLUME);
+    }
+
     /** @see AudioManager#getRingerMode() */
     public int getRingerMode() {
         return mRingerMode;
@@ -750,6 +864,16 @@
         }
     }
 
+    private void restoreMasterVolume() {
+        if (mUseMasterVolume) {
+            float volume = Settings.System.getFloat(mContentResolver,
+                    Settings.System.VOLUME_MASTER, -1.0f);
+            if (volume >= 0.0f) {
+                AudioSystem.setMasterVolume(volume);
+            }
+        }
+    }
+
     /** @see AudioManager#shouldVibrate(int) */
     public boolean shouldVibrate(int vibrateType) {
 
@@ -1838,8 +1962,7 @@
             return;
         }
 
-        handler
-                .sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
+        handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
     }
 
     boolean checkAudioSettingsPermission(String method) {
@@ -2203,6 +2326,11 @@
                     persistVolume((VolumeStreamState) msg.obj, (msg.arg1 != 0), (msg.arg2 != 0));
                     break;
 
+                case MSG_PERSIST_MASTER_VOLUME:
+                    Settings.System.putFloat(mContentResolver, Settings.System.VOLUME_MASTER,
+                            (float)msg.arg1 / (float)1000.0);
+                    break;
+
                 case MSG_PERSIST_RINGER_MODE:
                     persistRingerMode();
                     break;
@@ -2266,6 +2394,9 @@
                     // Restore ringer mode
                     setRingerModeInt(getRingerMode(), false);
 
+                    // Restore master volume
+                    restoreMasterVolume();
+
                     // indicate the end of reconfiguration phase to audio HAL
                     AudioSystem.setParameters("restarting=false");
                     break;
@@ -2686,6 +2817,11 @@
                     adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
                                             BluetoothProfile.A2DP);
                 }
+
+                if (mUseMasterVolume) {
+                    // Send sticky broadcast for initial master mute state
+                    sendMasterMuteUpdate(false, 0);
+                }
             } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
                 if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                     // a package is being removed, not replaced
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 95d93b2..a4520b8 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -253,5 +253,9 @@
     public static native int initStreamVolume(int stream, int indexMin, int indexMax);
     public static native int setStreamVolumeIndex(int stream, int index);
     public static native int getStreamVolumeIndex(int stream);
+    public static native int setMasterVolume(float value);
+    public static native float getMasterVolume();
+    public static native int setMasterMute(boolean mute);
+    public static native boolean getMasterMute();
     public static native int getDevicesForStream(int stream);
 }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5294d36..17d8e4d 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -30,10 +30,14 @@
     void adjustVolume(int direction, int flags);
 
     void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
-    
+
     void adjustStreamVolume(int streamType, int direction, int flags);
-    
+
+    void adjustMasterVolume(int direction, int flags);
+
     void setStreamVolume(int streamType, int index, int flags);
+
+    void setMasterVolume(int index, int flags);
     
     void setStreamSolo(int streamType, boolean state, IBinder cb);
    	
@@ -41,12 +45,22 @@
 
     boolean isStreamMute(int streamType);
 
+    void setMasterMute(boolean state, IBinder cb);
+
+    boolean isMasterMute();
+
     int getStreamVolume(int streamType);
-    
+
+    int getMasterVolume();
+
     int getStreamMaxVolume(int streamType);
+
+    int getMasterMaxVolume();
     
     int getLastAudibleStreamVolume(int streamType);
 
+    int getLastAudibleMasterVolume();
+
     void setRingerMode(int ringerMode);
     
     int getRingerMode();
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 8d71dcf..ba22a13 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -35,6 +35,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.util.Map;
 import java.util.Set;
 import java.lang.ref.WeakReference;
@@ -1486,6 +1487,34 @@
      */
     public native static int native_pullBatteryData(Parcel reply);
 
+    /**
+     * Sets the target re-transmit endpoint for the low level player.  When set, the player will
+     * attempt to re-mux its media data using the A@H RTP profile and re-transmit to the target
+     * endpoint.  setRetransmitEndpoint may only be called before setDataSource has been called;
+     * while the player is in the Idle state.
+     *
+     * @param endpoint the address and UDP port of the re-transmission target or null if no
+     * re-transmission is to be performed.
+     * @throws IllegalStateException if it is called in an invalid state
+     * @return The status code.
+     * @hide pending API council
+     */
+    public int setRetransmitEndpoint(InetSocketAddress endpoint)
+            throws IllegalStateException
+    {
+        String addrString = null;
+        int port = 0;
+
+        if (null != endpoint) {
+            addrString = endpoint.getAddress().getHostAddress();
+            port = endpoint.getPort();
+        }
+
+        return native_setRetransmitEndpoint(addrString, port);
+    }
+
+    private native final int native_setRetransmitEndpoint(String addrString, int port);
+
     @Override
     protected void finalize() { native_finalize(); }
 
diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java
index bcf7b89..91d0addd 100755
--- a/media/java/android/media/audiofx/Visualizer.java
+++ b/media/java/android/media/audiofx/Visualizer.java
@@ -84,6 +84,7 @@
     // to keep in sync with frameworks/base/media/jni/audioeffect/android_media_Visualizer.cpp
     private static final int NATIVE_EVENT_PCM_CAPTURE = 0;
     private static final int NATIVE_EVENT_FFT_CAPTURE = 1;
+    private static final int NATIVE_EVENT_SERVER_DIED = 2;
 
     // Error codes:
     /**
@@ -147,6 +148,10 @@
      *  PCM and FFT capture listener registered by client
      */
     private OnDataCaptureListener mCaptureListener = null;
+    /**
+     *  Server Died listener registered by client
+     */
+    private OnServerDiedListener mServerDiedListener = null;
 
     // accessed by native methods
     private int mNativeVisualizer;
@@ -396,6 +401,9 @@
     public interface OnDataCaptureListener  {
         /**
          * Method called when a new waveform capture is available.
+         * <p>Data in the waveform buffer is valid only within the scope of the callback.
+         * Applications which needs access to the waveform data after returning from the callback
+         * should make a copy of the data instead of holding a reference.
          * @param visualizer Visualizer object on which the listener is registered.
          * @param waveform array of bytes containing the waveform representation.
          * @param samplingRate sampling rate of the audio visualized.
@@ -404,6 +412,9 @@
 
         /**
          * Method called when a new frequency capture is available.
+         * <p>Data in the fft buffer is valid only within the scope of the callback.
+         * Applications which needs access to the fft data after returning from the callback
+         * should make a copy of the data instead of holding a reference.
          * @param visualizer Visualizer object on which the listener is registered.
          * @param fft array of bytes containing the frequency representation.
          * @param samplingRate sampling rate of the audio visualized.
@@ -452,6 +463,43 @@
     }
 
     /**
+     * @hide
+     *
+     * The OnServerDiedListener interface defines a method called by the Visualizer to indicate that
+     * the connection to the native media server has been broken and that the Visualizer object will
+     * need to be released and re-created.
+     * The client application can implement this interface and register the listener with the
+     * {@link #setServerDiedListener(OnServerDiedListener)} method.
+     */
+    public interface OnServerDiedListener  {
+        /**
+         * @hide
+         *
+         * Method called when the native media server has died.
+         * <p>If the native media server encounters a fatal error and needs to restart, the binder
+         * connection from the {@link #Visualizer} to the media server will be broken.  Data capture
+         * callbacks will stop happening, and client initiated calls to the {@link #Visualizer}
+         * instance will fail with the error code {@link #DEAD_OBJECT}.  To restore functionality,
+         * clients should {@link #release()} their old visualizer and create a new instance.
+         */
+        void onServerDied();
+    }
+
+    /**
+     * @hide
+     *
+     * Registers an OnServerDiedListener interface.
+     * <p>Call this method with a null listener to stop receiving server death notifications.
+     * @return {@link #SUCCESS} in case of success,
+     */
+    public int setServerDiedListener(OnServerDiedListener listener) {
+        synchronized (mListenerLock) {
+            mServerDiedListener = listener;
+        }
+        return SUCCESS;
+    }
+
+    /**
      * Helper class to handle the forwarding of native events to the appropriate listeners
      */
     private class NativeEventHandler extends Handler
@@ -463,11 +511,7 @@
             mVisualizer = v;
         }
 
-        @Override
-        public void handleMessage(Message msg) {
-            if (mVisualizer == null) {
-                return;
-            }
+        private void handleCaptureMessage(Message msg) {
             OnDataCaptureListener l = null;
             synchronized (mListenerLock) {
                 l = mVisualizer.mCaptureListener;
@@ -476,6 +520,7 @@
             if (l != null) {
                 byte[] data = (byte[])msg.obj;
                 int samplingRate = msg.arg1;
+
                 switch(msg.what) {
                 case NATIVE_EVENT_PCM_CAPTURE:
                     l.onWaveFormDataCapture(mVisualizer, data, samplingRate);
@@ -484,11 +529,41 @@
                     l.onFftDataCapture(mVisualizer, data, samplingRate);
                     break;
                 default:
-                    Log.e(TAG,"Unknown native event: "+msg.what);
+                    Log.e(TAG,"Unknown native event in handleCaptureMessge: "+msg.what);
                     break;
                 }
             }
         }
+
+        private void handleServerDiedMessage(Message msg) {
+            OnServerDiedListener l = null;
+            synchronized (mListenerLock) {
+                l = mVisualizer.mServerDiedListener;
+            }
+
+            if (l != null)
+                l.onServerDied();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mVisualizer == null) {
+                return;
+            }
+
+            switch(msg.what) {
+            case NATIVE_EVENT_PCM_CAPTURE:
+            case NATIVE_EVENT_FFT_CAPTURE:
+                handleCaptureMessage(msg);
+                break;
+            case NATIVE_EVENT_SERVER_DIED:
+                handleServerDiedMessage(msg);
+                break;
+            default:
+                Log.e(TAG,"Unknown native event: "+msg.what);
+                break;
+            }
+        }
     }
 
     //---------------------------------------------------------
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 63cbf5e..f3c27d6 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -722,6 +722,45 @@
     return service->pullBatteryData(reply);
 }
 
+static jint
+android_media_MediaPlayer_setRetransmitEndpoint(JNIEnv *env, jobject thiz,
+                                                jstring addrString, jint port) {
+    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+    if (mp == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return INVALID_OPERATION;
+    }
+
+    const char *cAddrString = NULL;
+
+    if (NULL != addrString) {
+        cAddrString = env->GetStringUTFChars(addrString, NULL);
+        if (cAddrString == NULL) {  // Out of memory
+            return NO_MEMORY;
+        }
+    }
+    LOGV("setRetransmitEndpoint: %s:%d",
+            cAddrString ? cAddrString : "(null)", port);
+
+    status_t ret;
+    if (cAddrString && (port > 0xFFFF)) {
+        ret = BAD_VALUE;
+    } else {
+        ret = mp->setRetransmitEndpoint(cAddrString,
+                static_cast<uint16_t>(port));
+    }
+
+    if (NULL != addrString) {
+        env->ReleaseStringUTFChars(addrString, cAddrString);
+    }
+
+    if (ret == INVALID_OPERATION ) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+    }
+
+    return ret;
+}
+
 static jboolean
 android_media_MediaPlayer_setParameter(JNIEnv *env, jobject thiz, jint key, jobject java_request)
 {
@@ -799,6 +838,7 @@
     {"native_pullBatteryData", "(Landroid/os/Parcel;)I",        (void *)android_media_MediaPlayer_pullBatteryData},
     {"setParameter",        "(ILandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_setParameter},
     {"getParameter",        "(ILandroid/os/Parcel;)V",          (void *)android_media_MediaPlayer_getParameter},
+    {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I",  (void *)android_media_MediaPlayer_setRetransmitEndpoint},
 };
 
 static const char* const kClassPathName = "android/media/MediaPlayer";
diff --git a/media/jni/audioeffect/android_media_Visualizer.cpp b/media/jni/audioeffect/android_media_Visualizer.cpp
index 27b28ee..c05b003 100644
--- a/media/jni/audioeffect/android_media_Visualizer.cpp
+++ b/media/jni/audioeffect/android_media_Visualizer.cpp
@@ -23,6 +23,7 @@
 #include <nativehelper/jni.h>
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
+#include <utils/threads.h>
 #include "media/Visualizer.h"
 
 using namespace android;
@@ -38,6 +39,7 @@
 
 #define NATIVE_EVENT_PCM_CAPTURE                0
 #define NATIVE_EVENT_FFT_CAPTURE                1
+#define NATIVE_EVENT_SERVER_DIED                2
 
 // ----------------------------------------------------------------------------
 static const char* const kClassPathName = "android/media/audiofx/Visualizer";
@@ -54,6 +56,43 @@
 struct visualizer_callback_cookie {
     jclass      visualizer_class;  // Visualizer class
     jobject     visualizer_ref;    // Visualizer object instance
+
+    // Lazily allocated arrays used to hold callback data provided to java
+    // applications.  These arrays are allocated during the first callback and
+    // reallocated when the size of the callback data changes.  Allocating on
+    // demand and saving the arrays means that applications cannot safely hold a
+    // reference to the provided data (they need to make a copy if they want to
+    // hold onto outside of the callback scope), but it avoids GC thrash caused
+    // by constantly allocating and releasing arrays to hold callback data.
+    Mutex       callback_data_lock;
+    jbyteArray  waveform_data;
+    jbyteArray  fft_data;
+
+    visualizer_callback_cookie() {
+        waveform_data = NULL;
+        fft_data = NULL;
+    }
+
+    ~visualizer_callback_cookie() {
+        cleanupBuffers();
+    }
+
+    void cleanupBuffers() {
+        AutoMutex lock(&callback_data_lock);
+        if (waveform_data || fft_data) {
+            JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+            if (waveform_data) {
+                env->DeleteGlobalRef(waveform_data);
+                waveform_data = NULL;
+            }
+
+            if (fft_data) {
+                env->DeleteGlobalRef(fft_data);
+                fft_data = NULL;
+            }
+        }
+    }
  };
 
 // ----------------------------------------------------------------------------
@@ -66,7 +105,6 @@
 
     ~visualizerJniStorage() {
     }
-
 };
 
 
@@ -93,6 +131,26 @@
 
 
 // ----------------------------------------------------------------------------
+static void ensureArraySize(JNIEnv *env, jbyteArray *array, uint32_t size) {
+    if (NULL != *array) {
+        uint32_t len = env->GetArrayLength(*array);
+        if (len == size)
+            return;
+
+        env->DeleteGlobalRef(*array);
+        *array = NULL;
+    }
+
+    jbyteArray localRef = env->NewByteArray(size);
+    if (NULL != localRef) {
+        // Promote to global ref.
+        *array = (jbyteArray)env->NewGlobalRef(localRef);
+
+        // Release our (now pointless) local ref.
+        env->DeleteLocalRef(localRef);
+    }
+}
+
 static void captureCallback(void* user,
         uint32_t waveformSize,
         uint8_t *waveform,
@@ -106,6 +164,7 @@
 
     visualizer_callback_cookie *callbackInfo = (visualizer_callback_cookie *)user;
     JNIEnv *env = AndroidRuntime::getJNIEnv();
+    AutoMutex lock(&callbackInfo->callback_data_lock);
 
     LOGV("captureCallback: callbackInfo %p, visualizer_ref %p visualizer_class %p",
             callbackInfo,
@@ -118,7 +177,11 @@
     }
 
     if (waveformSize != 0 && waveform != NULL) {
-        jbyteArray jArray = env->NewByteArray(waveformSize);
+        jbyteArray jArray;
+
+        ensureArraySize(env, &callbackInfo->waveform_data, waveformSize);
+        jArray = callbackInfo->waveform_data;
+
         if (jArray != NULL) {
             jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
             memcpy(nArray, waveform, waveformSize);
@@ -131,12 +194,15 @@
                 samplingrate,
                 0,
                 jArray);
-            env->DeleteLocalRef(jArray);
         }
     }
 
     if (fftSize != 0 && fft != NULL) {
-        jbyteArray jArray = env->NewByteArray(fftSize);
+        jbyteArray jArray;
+
+        ensureArraySize(env, &callbackInfo->fft_data, fftSize);
+        jArray = callbackInfo->fft_data;
+
         if (jArray != NULL) {
             jbyte *nArray = env->GetByteArrayElements(jArray, NULL);
             memcpy(nArray, fft, fftSize);
@@ -149,7 +215,6 @@
                 samplingrate,
                 0,
                 jArray);
-            env->DeleteLocalRef(jArray);
         }
     }
 
@@ -220,6 +285,23 @@
 
 }
 
+static void android_media_visualizer_effect_callback(int32_t event,
+                                                     void *user,
+                                                     void *info) {
+    if ((event == AudioEffect::EVENT_ERROR) &&
+        (*((status_t*)info) == DEAD_OBJECT)) {
+        visualizerJniStorage* lpJniStorage = (visualizerJniStorage*)user;
+        visualizer_callback_cookie* callbackInfo = &lpJniStorage->mCallbackData;
+        JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+        env->CallStaticVoidMethod(
+            callbackInfo->visualizer_class,
+            fields.midPostNativeEvent,
+            callbackInfo->visualizer_ref,
+            NATIVE_EVENT_SERVER_DIED,
+            0, 0, 0);
+    }
+}
 
 static jint
 android_media_visualizer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
@@ -255,8 +337,8 @@
 
     // create the native Visualizer object
     lpVisualizer = new Visualizer(0,
-                                  NULL,
-                                  NULL,
+                                  android_media_visualizer_effect_callback,
+                                  lpJniStorage,
                                   sessionId);
     if (lpVisualizer == NULL) {
         LOGE("Error creating Visualizer");
@@ -345,7 +427,17 @@
         return VISUALIZER_ERROR_NO_INIT;
     }
 
-    return translateError(lpVisualizer->setEnabled(enabled));
+    jint retVal = translateError(lpVisualizer->setEnabled(enabled));
+
+    if (!enabled) {
+        visualizerJniStorage* lpJniStorage = (visualizerJniStorage *)env->GetIntField(
+            thiz, fields.fidJniData);
+
+        if (NULL != lpJniStorage)
+            lpJniStorage->mCallbackData.cleanupBuffers();
+    }
+
+    return retVal;
 }
 
 static jboolean
diff --git a/media/libaah_rtp/Android.mk b/media/libaah_rtp/Android.mk
new file mode 100644
index 0000000..54fd9ec
--- /dev/null
+++ b/media/libaah_rtp/Android.mk
@@ -0,0 +1,40 @@
+LOCAL_PATH:= $(call my-dir)
+#
+# libaah_rtp
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libaah_rtp
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+    aah_decoder_pump.cpp \
+    aah_rx_player.cpp \
+    aah_rx_player_core.cpp \
+    aah_rx_player_ring_buffer.cpp \
+    aah_rx_player_substream.cpp \
+    aah_tx_packet.cpp \
+    aah_tx_player.cpp \
+    aah_tx_sender.cpp \
+    pipe_event.cpp
+
+LOCAL_C_INCLUDES := \
+    frameworks/base/include \
+    frameworks/base/include/media/stagefright/openmax \
+    frameworks/base/media \
+    frameworks/base/media/libstagefright
+
+LOCAL_SHARED_LIBRARIES := \
+    libcommon_time_client \
+    libbinder \
+    libmedia \
+    libstagefright \
+    libstagefright_foundation \
+    libutils
+
+LOCAL_LDLIBS := \
+    -lpthread
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/media/libaah_rtp/aah_decoder_pump.cpp b/media/libaah_rtp/aah_decoder_pump.cpp
new file mode 100644
index 0000000..68bac4f
--- /dev/null
+++ b/media/libaah_rtp/aah_decoder_pump.cpp
@@ -0,0 +1,518 @@
+/*
+ * 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <poll.h>
+#include <pthread.h>
+
+#include <common_time/cc_helper.h>
+#include <media/AudioSystem.h>
+#include <media/AudioTrack.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
+#include <utils/Timers.h>
+#include <utils/threads.h>
+
+#include "aah_decoder_pump.h"
+
+namespace android {
+
+static const long long kLongDecodeErrorThreshold = 1000000ll;
+static const uint32_t kMaxLongErrorsBeforeFatal = 3;
+static const uint32_t kMaxErrorsBeforeFatal = 60;
+
+AAH_DecoderPump::AAH_DecoderPump(OMXClient& omx)
+    : omx_(omx)
+    , thread_status_(OK)
+    , renderer_(NULL)
+    , last_queued_pts_valid_(false)
+    , last_queued_pts_(0)
+    , last_ts_transform_valid_(false)
+    , last_volume_(0xFF) {
+    thread_ = new ThreadWrapper(this);
+}
+
+AAH_DecoderPump::~AAH_DecoderPump() {
+    shutdown();
+}
+
+status_t AAH_DecoderPump::initCheck() {
+    if (thread_ == NULL) {
+        LOGE("Failed to allocate thread");
+        return NO_MEMORY;
+    }
+
+    return OK;
+}
+
+status_t AAH_DecoderPump::queueForDecode(MediaBuffer* buf) {
+    if (NULL == buf) {
+        return BAD_VALUE;
+    }
+
+    if (OK != thread_status_) {
+        return thread_status_;
+    }
+
+    {   // Explicit scope for AutoMutex pattern.
+        AutoMutex lock(&thread_lock_);
+        in_queue_.push_back(buf);
+    }
+
+    thread_cond_.signal();
+
+    return OK;
+}
+
+void AAH_DecoderPump::queueToRenderer(MediaBuffer* decoded_sample) {
+    Mutex::Autolock lock(&render_lock_);
+    sp<MetaData> meta;
+    int64_t ts;
+    status_t res;
+
+    // Fetch the metadata and make sure the sample has a timestamp.  We
+    // cannot render samples which are missing PTSs.
+    meta = decoded_sample->meta_data();
+    if ((meta == NULL) || (!meta->findInt64(kKeyTime, &ts))) {
+        LOGV("Decoded sample missing timestamp, cannot render.");
+    } else {
+        // If we currently are not holding on to a renderer, go ahead and
+        // make one now.
+        if (NULL == renderer_) {
+            renderer_ = new TimedAudioTrack();
+            if (NULL != renderer_) {
+                int frameCount;
+                AudioTrack::getMinFrameCount(&frameCount,
+                        AUDIO_STREAM_DEFAULT,
+                        static_cast<int>(format_sample_rate_));
+                int ch_format = (format_channels_ == 1)
+                    ? AUDIO_CHANNEL_OUT_MONO
+                    : AUDIO_CHANNEL_OUT_STEREO;
+
+                res = renderer_->set(AUDIO_STREAM_DEFAULT,
+                        format_sample_rate_,
+                        AUDIO_FORMAT_PCM_16_BIT,
+                        ch_format,
+                        frameCount);
+                if (res != OK) {
+                    LOGE("Failed to setup audio renderer. (res = %d)", res);
+                    delete renderer_;
+                    renderer_ = NULL;
+                } else {
+                    CHECK(last_ts_transform_valid_);
+
+                    res = renderer_->setMediaTimeTransform(
+                            last_ts_transform_, TimedAudioTrack::COMMON_TIME);
+                    if (res != NO_ERROR) {
+                        LOGE("Failed to set media time transform on AudioTrack"
+                             " (res = %d)", res);
+                        delete renderer_;
+                        renderer_ = NULL;
+                    } else {
+                        float volume = static_cast<float>(last_volume_)
+                                     / 255.0f;
+                        if (renderer_->setVolume(volume, volume) != OK) {
+                            LOGW("%s: setVolume failed", __FUNCTION__);
+                        }
+
+                        renderer_->start();
+                    }
+                }
+            } else {
+                LOGE("Failed to allocate AudioTrack to use as a renderer.");
+            }
+        }
+
+        if (NULL != renderer_) {
+            uint8_t* decoded_data =
+                reinterpret_cast<uint8_t*>(decoded_sample->data());
+            uint32_t decoded_amt  = decoded_sample->range_length();
+            decoded_data += decoded_sample->range_offset();
+
+            sp<IMemory> pcm_payload;
+            res = renderer_->allocateTimedBuffer(decoded_amt, &pcm_payload);
+            if (res != OK) {
+                LOGE("Failed to allocate %d byte audio track buffer."
+                     " (res = %d)", decoded_amt, res);
+            } else {
+                memcpy(pcm_payload->pointer(), decoded_data, decoded_amt);
+
+                res = renderer_->queueTimedBuffer(pcm_payload, ts);
+                if (res != OK) {
+                    LOGE("Failed to queue %d byte audio track buffer with media"
+                         " PTS %lld. (res = %d)", decoded_amt, ts, res);
+                } else {
+                    last_queued_pts_valid_ = true;
+                    last_queued_pts_ = ts;
+                }
+            }
+
+        } else {
+            LOGE("No renderer, dropping audio payload.");
+        }
+    }
+}
+
+void AAH_DecoderPump::stopAndCleanupRenderer() {
+    if (NULL == renderer_) {
+        return;
+    }
+
+    renderer_->stop();
+    delete renderer_;
+    renderer_ = NULL;
+}
+
+void AAH_DecoderPump::setRenderTSTransform(const LinearTransform& trans) {
+    Mutex::Autolock lock(&render_lock_);
+
+    if (last_ts_transform_valid_ && !memcmp(&trans,
+                                            &last_ts_transform_,
+                                            sizeof(trans))) {
+        return;
+    }
+
+    last_ts_transform_       = trans;
+    last_ts_transform_valid_ = true;
+
+    if (NULL != renderer_) {
+        status_t res = renderer_->setMediaTimeTransform(
+                last_ts_transform_, TimedAudioTrack::COMMON_TIME);
+        if (res != NO_ERROR) {
+            LOGE("Failed to set media time transform on AudioTrack"
+                 " (res = %d)", res);
+        }
+    }
+}
+
+void AAH_DecoderPump::setRenderVolume(uint8_t volume) {
+    Mutex::Autolock lock(&render_lock_);
+
+    if (volume == last_volume_) {
+        return;
+    }
+
+    last_volume_ = volume;
+    if (renderer_ != NULL) {
+        float volume = static_cast<float>(last_volume_) / 255.0f;
+        if (renderer_->setVolume(volume, volume) != OK) {
+            LOGW("%s: setVolume failed", __FUNCTION__);
+        }
+    }
+}
+
+// isAboutToUnderflow is something of a hack used to figure out when it might be
+// time to give up on trying to fill in a gap in the RTP sequence and simply
+// move on with a discontinuity.  If we had perfect knowledge of when we were
+// going to underflow, it would not be a hack, but unfortunately we do not.
+// Right now, we just take the PTS of the last sample queued, and check to see
+// if its presentation time is within kAboutToUnderflowThreshold from now.  If
+// it is, then we say that we are about to underflow.  This decision is based on
+// two (possibly invalid) assumptions.
+//
+// 1) The transmitter is leading the clock by more than
+//    kAboutToUnderflowThreshold.
+// 2) The delta between the PTS of the last sample queued and the next sample
+//    is less than the transmitter's clock lead amount.
+//
+// Right now, the default transmitter lead time is 1 second, which is a pretty
+// large number and greater than the 50mSec that kAboutToUnderflowThreshold is
+// currently set to.  This should satisfy assumption #1 for now, but changes to
+// the transmitter clock lead time could effect this.
+//
+// For non-sparse streams with a homogeneous sample rate (the vast majority of
+// streams in the world), the delta between any two adjacent PTSs will always be
+// the homogeneous sample period.  It is very uncommon to see a sample period
+// greater than the 1 second clock lead we are currently using, and you
+// certainly will not see it in an MP3 file which should satisfy assumption #2.
+// Sparse audio streams (where no audio is transmitted for long periods of
+// silence) and extremely low framerate video stream (like an MPEG-2 slideshow
+// or the video stream for a pay TV audio channel) are examples of streams which
+// might violate assumption #2.
+bool AAH_DecoderPump::isAboutToUnderflow(int64_t threshold) {
+    Mutex::Autolock lock(&render_lock_);
+
+    // If we have never queued anything to the decoder, we really don't know if
+    // we are going to underflow or not.
+    if (!last_queued_pts_valid_ || !last_ts_transform_valid_) {
+        return false;
+    }
+
+    // Don't have access to Common Time?  If so, then things are Very Bad
+    // elsewhere in the system; it pretty much does not matter what we do here.
+    // Since we cannot really tell if we are about to underflow or not, its
+    // probably best to assume that we are not and proceed accordingly.
+    int64_t tt_now;
+    if (OK != cc_helper_.getCommonTime(&tt_now)) {
+        return false;
+    }
+
+    // Transform from media time to common time.
+    int64_t last_queued_pts_tt;
+    if (!last_ts_transform_.doForwardTransform(last_queued_pts_,
+                &last_queued_pts_tt)) {
+        return false;
+    }
+
+    // Check to see if we are underflowing.
+    return ((tt_now + threshold - last_queued_pts_tt) > 0);
+}
+
+void* AAH_DecoderPump::workThread() {
+    // No need to lock when accessing decoder_ from the thread.  The
+    // implementation of init and shutdown ensure that other threads never touch
+    // decoder_ while the work thread is running.
+    CHECK(decoder_ != NULL);
+    CHECK(format_  != NULL);
+
+    // Start the decoder and note its result code.  If something goes horribly
+    // wrong, callers of queueForDecode and getOutput will be able to detect
+    // that the thread encountered a fatal error and shut down by examining
+    // thread_status_.
+    thread_status_ = decoder_->start(format_.get());
+    if (OK != thread_status_) {
+        LOGE("AAH_DecoderPump's work thread failed to start decoder (res = %d)",
+                thread_status_);
+        return NULL;
+    }
+
+    DurationTimer decode_timer;
+    uint32_t consecutive_long_errors = 0;
+    uint32_t consecutive_errors = 0;
+
+    while (!thread_->exitPending()) {
+        status_t res;
+        MediaBuffer* bufOut = NULL;
+
+        decode_timer.start();
+        res = decoder_->read(&bufOut);
+        decode_timer.stop();
+
+        if (res == INFO_FORMAT_CHANGED) {
+            // Format has changed.  Destroy our current renderer so that a new
+            // one can be created during queueToRenderer with the proper format.
+            //
+            // TODO : In order to transition seamlessly, we should change this
+            // to put the old renderer in a queue to play out completely before
+            // we destroy it.  We can still create a new renderer, the timed
+            // nature of the renderer should ensure a seamless splice.
+            stopAndCleanupRenderer();
+            res = OK;
+        }
+
+        // Try to be a little nuanced in our handling of actual decode errors.
+        // Errors could happen because of minor stream corruption or because of
+        // transient resource limitations.  In these cases, we would rather drop
+        // a little bit of output and ride out the unpleasantness then throw up
+        // our hands and abort everything.
+        //
+        // OTOH - When things are really bad (like we have a non-transient
+        // resource or bookkeeping issue, or the stream being fed to us is just
+        // complete and total garbage) we really want to terminate playback and
+        // raise an error condition all the way up to the application level so
+        // they can deal with it.
+        //
+        // Unfortunately, the error codes returned by the decoder can be a
+        // little non-specific.  For example, if an OMXCodec times out
+        // attempting to obtain an output buffer, the error we get back is a
+        // generic -1.  Try to distinguish between this resource timeout error
+        // and ES corruption error by timing how long the decode operation
+        // takes.  Maintain accounting for both errors and "long errors".  If we
+        // get more than a certain number consecutive errors of either type,
+        // consider it fatal and shutdown (which will cause the error to
+        // propagate all of the way up to the application level).  The threshold
+        // for "long errors" is deliberately much lower than that of normal
+        // decode errors, both because of how long they take to happen and
+        // because they generally indicate resource limitation errors which are
+        // unlikely to go away in pathologically bad cases (in contrast to
+        // stream corruption errors which might happen 20 times in a row and
+        // then be suddenly OK again)
+        if (res != OK) {
+            consecutive_errors++;
+            if (decode_timer.durationUsecs() >= kLongDecodeErrorThreshold)
+                consecutive_long_errors++;
+
+            CHECK(NULL == bufOut);
+
+            LOGW("%s: Failed to decode data (res = %d)",
+                    __PRETTY_FUNCTION__, res);
+
+            if ((consecutive_errors      >= kMaxErrorsBeforeFatal) ||
+                (consecutive_long_errors >= kMaxLongErrorsBeforeFatal)) {
+                LOGE("%s: Maximum decode error threshold has been reached."
+                     " There have been %d consecutive decode errors, and %d"
+                     " consecutive decode operations which resulted in errors"
+                     " and took more than %lld uSec to process.  The last"
+                     " decode operation took %lld uSec.",
+                     __PRETTY_FUNCTION__,
+                     consecutive_errors, consecutive_long_errors,
+                     kLongDecodeErrorThreshold, decode_timer.durationUsecs());
+                thread_status_ = res;
+                break;
+            }
+
+            continue;
+        }
+
+        if (NULL == bufOut) {
+            LOGW("%s: Successful decode, but no buffer produced",
+                    __PRETTY_FUNCTION__);
+            continue;
+        }
+
+        // Successful decode (with actual output produced).  Clear the error
+        // counters.
+        consecutive_errors = 0;
+        consecutive_long_errors = 0;
+
+        queueToRenderer(bufOut);
+        bufOut->release();
+    }
+
+    decoder_->stop();
+    stopAndCleanupRenderer();
+
+    return NULL;
+}
+
+status_t AAH_DecoderPump::init(sp<MetaData> params) {
+    Mutex::Autolock lock(&init_lock_);
+
+    if (decoder_ != NULL) {
+        // already inited
+        return OK;
+    }
+
+    if (params == NULL) {
+        return BAD_VALUE;
+    }
+
+    if (!params->findInt32(kKeyChannelCount, &format_channels_)) {
+        return BAD_VALUE;
+    }
+
+    if (!params->findInt32(kKeySampleRate, &format_sample_rate_)) {
+        return BAD_VALUE;
+    }
+
+    CHECK(OK == thread_status_);
+    CHECK(decoder_ == NULL);
+
+    status_t ret_val = UNKNOWN_ERROR;
+
+    // Cache the format and attempt to create the decoder.
+    format_  = params;
+    decoder_ = OMXCodec::Create(
+            omx_.interface(),       // IOMX Handle
+            format_,                // Metadata for substream (indicates codec)
+            false,                  // Make a decoder, not an encoder
+            sp<MediaSource>(this)); // We will be the source for this codec.
+
+    if (decoder_ == NULL) {
+      LOGE("Failed to allocate decoder in %s", __PRETTY_FUNCTION__);
+      goto bailout;
+    }
+
+    // Fire up the pump thread.  It will take care of starting and stopping the
+    // decoder.
+    ret_val = thread_->run("aah_decode_pump", ANDROID_PRIORITY_AUDIO);
+    if (OK != ret_val) {
+        LOGE("Failed to start work thread in %s (res = %d)",
+                __PRETTY_FUNCTION__, ret_val);
+        goto bailout;
+    }
+
+bailout:
+    if (OK != ret_val) {
+        decoder_ = NULL;
+        format_  = NULL;
+    }
+
+    return OK;
+}
+
+status_t AAH_DecoderPump::shutdown() {
+    Mutex::Autolock lock(&init_lock_);
+    return shutdown_l();
+}
+
+status_t AAH_DecoderPump::shutdown_l() {
+    thread_->requestExit();
+    thread_cond_.signal();
+    thread_->requestExitAndWait();
+
+    MBQueue::iterator I;
+    for (I = in_queue_.begin(); I != in_queue_.end(); ++I) {
+        (*I)->release();
+    }
+    in_queue_.clear();
+
+    last_queued_pts_valid_   = false;
+    last_ts_transform_valid_ = false;
+    last_volume_             = 0xFF;
+    thread_status_           = OK;
+
+    decoder_ = NULL;
+    format_  = NULL;
+
+    return OK;
+}
+
+status_t AAH_DecoderPump::read(MediaBuffer **buffer,
+                               const ReadOptions *options) {
+    if (!buffer) {
+        return BAD_VALUE;
+    }
+
+    *buffer = NULL;
+
+    // While its not time to shut down, and we have no data to process, wait.
+    AutoMutex lock(&thread_lock_);
+    while (!thread_->exitPending() && in_queue_.empty())
+        thread_cond_.wait(thread_lock_);
+
+    // At this point, if its not time to shutdown then we must have something to
+    // process.  Go ahead and pop the front of the queue for processing.
+    if (!thread_->exitPending()) {
+        CHECK(!in_queue_.empty());
+
+        *buffer = *(in_queue_.begin());
+        in_queue_.erase(in_queue_.begin());
+    }
+
+    // If we managed to get a buffer, then everything must be OK.  If not, then
+    // we must be shutting down.
+    return (NULL == *buffer) ? INVALID_OPERATION : OK;
+}
+
+AAH_DecoderPump::ThreadWrapper::ThreadWrapper(AAH_DecoderPump* owner)
+    : Thread(false /* canCallJava*/ )
+    , owner_(owner) {
+}
+
+bool AAH_DecoderPump::ThreadWrapper::threadLoop() {
+    CHECK(NULL != owner_);
+    owner_->workThread();
+    return false;
+}
+
+}  // namespace android
diff --git a/media/libaah_rtp/aah_decoder_pump.h b/media/libaah_rtp/aah_decoder_pump.h
new file mode 100644
index 0000000..3f323d1
--- /dev/null
+++ b/media/libaah_rtp/aah_decoder_pump.h
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+#ifndef __DECODER_PUMP_H__
+#define __DECODER_PUMP_H__
+
+#include <pthread.h>
+
+#include <common_time/cc_helper.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/LinearTransform.h>
+#include <utils/List.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class MetaData;
+class OMXClient;
+class TimedAudioTrack;
+
+class AAH_DecoderPump : public MediaSource {
+  public:
+    explicit AAH_DecoderPump(OMXClient& omx);
+    status_t initCheck();
+
+    status_t queueForDecode(MediaBuffer* buf);
+
+    status_t init(sp<MetaData> params);
+    status_t shutdown();
+
+    void setRenderTSTransform(const LinearTransform& trans);
+    void setRenderVolume(uint8_t volume);
+    bool isAboutToUnderflow(int64_t threshold);
+    bool getStatus() const { return thread_status_; }
+
+    // MediaSource methods
+    virtual status_t     start(MetaData *params) { return OK; }
+    virtual sp<MetaData> getFormat() { return format_; }
+    virtual status_t     stop() { return OK; }
+    virtual status_t     read(MediaBuffer **buffer,
+                              const ReadOptions *options);
+
+  protected:
+    virtual ~AAH_DecoderPump();
+
+  private:
+    class ThreadWrapper : public Thread {
+      public:
+        friend class AAH_DecoderPump;
+        explicit ThreadWrapper(AAH_DecoderPump* owner);
+
+      private:
+        virtual bool threadLoop();
+        AAH_DecoderPump* owner_;
+
+        DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper);
+    };
+
+    void* workThread();
+    virtual status_t shutdown_l();
+    void queueToRenderer(MediaBuffer* decoded_sample);
+    void stopAndCleanupRenderer();
+
+    sp<MetaData>        format_;
+    int32_t             format_channels_;
+    int32_t             format_sample_rate_;
+
+    sp<MediaSource>     decoder_;
+    OMXClient&          omx_;
+    Mutex               init_lock_;
+
+    sp<ThreadWrapper>   thread_;
+    Condition           thread_cond_;
+    Mutex               thread_lock_;
+    status_t            thread_status_;
+
+    Mutex               render_lock_;
+    TimedAudioTrack*    renderer_;
+    bool                last_queued_pts_valid_;
+    int64_t             last_queued_pts_;
+    bool                last_ts_transform_valid_;
+    LinearTransform     last_ts_transform_;
+    uint8_t             last_volume_;
+    CCHelper            cc_helper_;
+
+    // protected by the thread_lock_
+    typedef List<MediaBuffer*> MBQueue;
+    MBQueue in_queue_;
+
+    DISALLOW_EVIL_CONSTRUCTORS(AAH_DecoderPump);
+};
+
+}  // namespace android
+#endif  // __DECODER_PUMP_H__
diff --git a/media/libaah_rtp/aah_rx_player.cpp b/media/libaah_rtp/aah_rx_player.cpp
new file mode 100644
index 0000000..bf72383
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player.cpp
@@ -0,0 +1,288 @@
+/*
+ * 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+
+#include <binder/IServiceManager.h>
+#include <media/MediaPlayerInterface.h>
+#include <utils/Log.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+const uint32_t AAH_RXPlayer::kRTPRingBufferSize = 1 << 10;
+
+sp<MediaPlayerBase> createAAH_RXPlayer() {
+    sp<MediaPlayerBase> ret = new AAH_RXPlayer();
+    return ret;
+}
+
+AAH_RXPlayer::AAH_RXPlayer()
+        : ring_buffer_(kRTPRingBufferSize)
+        , substreams_(NULL) {
+    thread_wrapper_ = new ThreadWrapper(*this);
+
+    is_playing_          = false;
+    multicast_joined_    = false;
+    transmitter_known_   = false;
+    current_epoch_known_ = false;
+    data_source_set_     = false;
+    sock_fd_             = -1;
+
+    substreams_.setCapacity(4);
+
+    memset(&listen_addr_,      0, sizeof(listen_addr_));
+    memset(&transmitter_addr_, 0, sizeof(transmitter_addr_));
+
+    fetchAudioFlinger();
+}
+
+AAH_RXPlayer::~AAH_RXPlayer() {
+    reset_l();
+    CHECK(substreams_.size() == 0);
+    omx_.disconnect();
+}
+
+status_t AAH_RXPlayer::initCheck() {
+    if (thread_wrapper_ == NULL) {
+        LOGE("Failed to allocate thread wrapper!");
+        return NO_MEMORY;
+    }
+
+    if (!ring_buffer_.initCheck()) {
+        LOGE("Failed to allocate reassembly ring buffer!");
+        return NO_MEMORY;
+    }
+
+    // Check for the presense of the common time service by attempting to query
+    // for CommonTime's frequency.  If we get an error back, we cannot talk to
+    // the service at all and should abort now.
+    status_t res;
+    uint64_t freq;
+    res = cc_helper_.getCommonFreq(&freq);
+    if (OK != res) {
+        LOGE("Failed to connect to common time service!");
+        return res;
+    }
+
+    return omx_.connect();
+}
+
+status_t AAH_RXPlayer::setDataSource(
+        const char *url,
+        const KeyedVector<String8, String8> *headers) {
+    AutoMutex api_lock(&api_lock_);
+    uint32_t a, b, c, d;
+    uint16_t port;
+
+    if (data_source_set_) {
+        return INVALID_OPERATION;
+    }
+
+    if (NULL == url) {
+        return BAD_VALUE;
+    }
+
+    if (5 != sscanf(url, "%*[^:/]://%u.%u.%u.%u:%hu", &a, &b, &c, &d, &port)) {
+        LOGE("Failed to parse URL \"%s\"", url);
+        return BAD_VALUE;
+    }
+
+    if ((a > 255) || (b > 255) || (c > 255) || (d > 255) || (port == 0)) {
+        LOGE("Bad multicast address \"%s\"", url);
+        return BAD_VALUE;
+    }
+
+    LOGI("setDataSource :: %u.%u.%u.%u:%hu", a, b, c, d, port);
+
+    a = (a << 24) | (b << 16) | (c <<  8) | d;
+
+    memset(&listen_addr_, 0, sizeof(listen_addr_));
+    listen_addr_.sin_family      = AF_INET;
+    listen_addr_.sin_port        = htons(port);
+    listen_addr_.sin_addr.s_addr = htonl(a);
+    data_source_set_ = true;
+
+    return OK;
+}
+
+status_t AAH_RXPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+    return INVALID_OPERATION;
+}
+
+status_t AAH_RXPlayer::setVideoSurface(const sp<Surface>& surface) {
+    return OK;
+}
+
+status_t AAH_RXPlayer::setVideoSurfaceTexture(
+        const sp<ISurfaceTexture>& surfaceTexture) {
+    return OK;
+}
+
+status_t AAH_RXPlayer::prepare() {
+    return OK;
+}
+
+status_t AAH_RXPlayer::prepareAsync() {
+    sendEvent(MEDIA_PREPARED);
+    return OK;
+}
+
+status_t AAH_RXPlayer::start() {
+    AutoMutex api_lock(&api_lock_);
+
+    if (is_playing_) {
+        return OK;
+    }
+
+    status_t res = startWorkThread();
+    is_playing_ = (res == OK);
+    return res;
+}
+
+status_t AAH_RXPlayer::stop() {
+    return pause();
+}
+
+status_t AAH_RXPlayer::pause() {
+    AutoMutex api_lock(&api_lock_);
+    stopWorkThread();
+    CHECK(sock_fd_ < 0);
+    is_playing_ = false;
+    return OK;
+}
+
+bool AAH_RXPlayer::isPlaying() {
+    AutoMutex api_lock(&api_lock_);
+    return is_playing_;
+}
+
+status_t AAH_RXPlayer::seekTo(int msec) {
+    sendEvent(MEDIA_SEEK_COMPLETE);
+    return OK;
+}
+
+status_t AAH_RXPlayer::getCurrentPosition(int *msec) {
+    if (NULL != msec) {
+        *msec = 0;
+    }
+    return OK;
+}
+
+status_t AAH_RXPlayer::getDuration(int *msec) {
+    if (NULL != msec) {
+        *msec = 1;
+    }
+    return OK;
+}
+
+status_t AAH_RXPlayer::reset() {
+    AutoMutex api_lock(&api_lock_);
+    reset_l();
+    return OK;
+}
+
+void AAH_RXPlayer::reset_l() {
+    stopWorkThread();
+    CHECK(sock_fd_ < 0);
+    CHECK(!multicast_joined_);
+    is_playing_ = false;
+    data_source_set_ = false;
+    transmitter_known_ = false;
+    memset(&listen_addr_, 0, sizeof(listen_addr_));
+}
+
+status_t AAH_RXPlayer::setLooping(int loop) {
+    return OK;
+}
+
+player_type AAH_RXPlayer::playerType() {
+    return AAH_RX_PLAYER;
+}
+
+status_t AAH_RXPlayer::setParameter(int key, const Parcel &request) {
+    return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_RXPlayer::getParameter(int key, Parcel *reply) {
+    return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_RXPlayer::invoke(const Parcel& request, Parcel *reply) {
+    if (!reply) {
+        return BAD_VALUE;
+    }
+
+    int32_t magic;
+    status_t err = request.readInt32(&magic);
+    if (err != OK) {
+        reply->writeInt32(err);
+        return OK;
+    }
+
+    if (magic != 0x12345) {
+        reply->writeInt32(BAD_VALUE);
+        return OK;
+    }
+
+    int32_t methodID;
+    err = request.readInt32(&methodID);
+    if (err != OK) {
+        reply->writeInt32(err);
+        return OK;
+    }
+
+    switch (methodID) {
+        // Get Volume
+        case INVOKE_GET_MASTER_VOLUME: {
+            if (audio_flinger_ != NULL) {
+                reply->writeInt32(OK);
+                reply->writeFloat(audio_flinger_->masterVolume());
+            } else {
+                reply->writeInt32(UNKNOWN_ERROR);
+            }
+        } break;
+
+        // Set Volume
+        case INVOKE_SET_MASTER_VOLUME: {
+            float targetVol = request.readFloat();
+            reply->writeInt32(audio_flinger_->setMasterVolume(targetVol));
+        } break;
+
+        default: return BAD_VALUE;
+    }
+
+    return OK;
+}
+
+void AAH_RXPlayer::fetchAudioFlinger() {
+    if (audio_flinger_ == NULL) {
+        sp<IServiceManager> sm = defaultServiceManager();
+        sp<IBinder> binder;
+        binder = sm->getService(String16("media.audio_flinger"));
+
+        if (binder == NULL) {
+            LOGW("AAH_RXPlayer failed to fetch handle to audio flinger."
+                 " Master volume control will not be possible.");
+        }
+
+        audio_flinger_ = interface_cast<IAudioFlinger>(binder);
+    }
+}
+
+}  // namespace android
diff --git a/media/libaah_rtp/aah_rx_player.h b/media/libaah_rtp/aah_rx_player.h
new file mode 100644
index 0000000..53361b4
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player.h
@@ -0,0 +1,299 @@
+/*
+ * 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.
+ */
+
+#ifndef __AAH_RX_PLAYER_H__
+#define __AAH_RX_PLAYER_H__
+
+#include <common_time/cc_helper.h>
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXClient.h>
+#include <netinet/in.h>
+#include <utils/KeyedVector.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+#include "aah_decoder_pump.h"
+#include "pipe_event.h"
+
+namespace android {
+
+class AAH_RXPlayer : public MediaPlayerInterface {
+  public:
+    AAH_RXPlayer();
+
+    virtual status_t    initCheck();
+    virtual status_t    setDataSource(const char *url,
+                                      const KeyedVector<String8, String8>*
+                                      headers);
+    virtual status_t    setDataSource(int fd, int64_t offset, int64_t length);
+    virtual status_t    setVideoSurface(const sp<Surface>& surface);
+    virtual status_t    setVideoSurfaceTexture(const sp<ISurfaceTexture>&
+                                               surfaceTexture);
+    virtual status_t    prepare();
+    virtual status_t    prepareAsync();
+    virtual status_t    start();
+    virtual status_t    stop();
+    virtual status_t    pause();
+    virtual bool        isPlaying();
+    virtual status_t    seekTo(int msec);
+    virtual status_t    getCurrentPosition(int *msec);
+    virtual status_t    getDuration(int *msec);
+    virtual status_t    reset();
+    virtual status_t    setLooping(int loop);
+    virtual player_type playerType();
+    virtual status_t    setParameter(int key, const Parcel &request);
+    virtual status_t    getParameter(int key, Parcel *reply);
+    virtual status_t    invoke(const Parcel& request, Parcel *reply);
+
+  protected:
+    virtual ~AAH_RXPlayer();
+
+  private:
+    class ThreadWrapper : public Thread {
+      public:
+        friend class AAH_RXPlayer;
+        explicit ThreadWrapper(AAH_RXPlayer& player)
+            : Thread(false /* canCallJava */ )
+            , player_(player) { }
+
+        virtual bool threadLoop() { return player_.threadLoop(); }
+
+      private:
+        AAH_RXPlayer& player_;
+
+        DISALLOW_EVIL_CONSTRUCTORS(ThreadWrapper);
+    };
+
+#pragma pack(push, 1)
+    struct PacketBuffer {
+        ssize_t length_;
+        uint8_t data_[0];
+
+        // TODO : consider changing this to be some form of ring buffer or free
+        // pool system instead of just using the heap in order to avoid heap
+        // fragmentation.
+        static PacketBuffer* allocate(ssize_t length);
+        static void destroy(PacketBuffer* pb);
+
+      private:
+        // Force people to use allocate/destroy instead of new/delete.
+        PacketBuffer() { }
+        ~PacketBuffer() { }
+    };
+
+    struct RetransRequest {
+        uint32_t magic_;
+        uint32_t mcast_ip_;
+        uint16_t mcast_port_;
+        uint16_t start_seq_;
+        uint16_t end_seq_;
+    };
+#pragma pack(pop)
+
+    enum GapStatus {
+        kGS_NoGap = 0,
+        kGS_NormalGap,
+        kGS_FastStartGap,
+    };
+
+    struct SeqNoGap {
+        uint16_t start_seq_;
+        uint16_t end_seq_;
+    };
+
+    class RXRingBuffer {
+      public:
+        explicit RXRingBuffer(uint32_t capacity);
+        ~RXRingBuffer();
+
+        bool initCheck() const { return (ring_ != NULL); }
+        void reset();
+
+        // Push a packet buffer with a given sequence number into the ring
+        // buffer.  pushBuffer will always consume the buffer pushed to it,
+        // either destroying it because it was a duplicate or overflow, or
+        // holding on to it in the ring.  Callers should not hold any references
+        // to PacketBuffers after they have been pushed to the ring.  Returns
+        // false in the case of a serious error (such as ring overflow).
+        // Callers should consider resetting the pipeline entirely in the event
+        // of a serious error.
+        bool pushBuffer(PacketBuffer* buf, uint16_t seq);
+
+        // Fetch the next buffer in the RTP sequence.  Returns NULL if there is
+        // no buffer to fetch.  If a non-NULL PacketBuffer is returned,
+        // is_discon will be set to indicate whether or not this PacketBuffer is
+        // discontiuous with any previously returned packet buffers.  Packet
+        // buffers returned by fetchBuffer are the caller's responsibility; they
+        // must be certain to destroy the buffers when they are done.
+        PacketBuffer* fetchBuffer(bool* is_discon);
+
+        // Returns true and fills out the gap structure if the read pointer of
+        // the ring buffer is currently pointing to a gap which would stall a
+        // fetchBuffer operation.  Returns false if the read pointer is not
+        // pointing to a gap in the sequence currently.
+        GapStatus fetchCurrentGap(SeqNoGap* gap);
+
+        // Causes the read pointer to skip over any portion of a gap indicated
+        // by nak.  If nak is NULL, any gap currently blocking the read pointer
+        // will be completely skipped.  If any portion of a gap is skipped, the
+        // next successful read from fetch buffer will indicate a discontinuity.
+        void processNAK(SeqNoGap* nak = NULL);
+
+        // Compute the number of milliseconds until the inactivity timer for
+        // this RTP stream.  Returns -1 if there is no active timeout, or 0 if
+        // the system has already timed out.
+        int computeInactivityTimeout();
+
+      private:
+        Mutex          lock_;
+        PacketBuffer** ring_;
+        uint32_t       capacity_;
+        uint32_t       rd_;
+        uint32_t       wr_;
+
+        uint16_t       rd_seq_;
+        bool           rd_seq_known_;
+        bool           waiting_for_fast_start_;
+        bool           fetched_first_packet_;
+
+        uint64_t       rtp_activity_timeout_;
+        bool           rtp_activity_timeout_valid_;
+
+        DISALLOW_EVIL_CONSTRUCTORS(RXRingBuffer);
+    };
+
+    class Substream : public virtual RefBase {
+      public:
+        Substream(uint32_t ssrc, OMXClient& omx);
+
+        void cleanupBufferInProgress();
+        void shutdown();
+        void processPayloadStart(uint8_t* buf,
+                                 uint32_t amt,
+                                 int32_t ts_lower);
+        void processPayloadCont (uint8_t* buf,
+                                 uint32_t amt);
+        void processTSTransform(const LinearTransform& trans);
+
+        bool     isAboutToUnderflow();
+        uint32_t getSSRC()      const { return ssrc_; }
+        uint16_t getProgramID() const { return (ssrc_ >> 5) & 0x1F; }
+        status_t getStatus() const { return status_; }
+
+      protected:
+        virtual ~Substream();
+
+      private:
+        void                cleanupDecoder();
+        bool                shouldAbort(const char* log_tag);
+        void                processCompletedBuffer();
+        bool                setupSubstreamMeta();
+        bool                setupMP3SubstreamMeta();
+        bool                setupAACSubstreamMeta();
+        bool                setupSubstreamType(uint8_t substream_type,
+                                               uint8_t codec_type);
+
+        uint32_t            ssrc_;
+        bool                waiting_for_rap_;
+        status_t            status_;
+
+        bool                substream_details_known_;
+        uint8_t             substream_type_;
+        uint8_t             codec_type_;
+        const char*         codec_mime_type_;
+        sp<MetaData>        substream_meta_;
+
+        MediaBuffer*        buffer_in_progress_;
+        uint32_t            expected_buffer_size_;
+        uint32_t            buffer_filled_;
+
+        Vector<uint8_t>     aux_data_in_progress_;
+        uint32_t            aux_data_expected_size_;
+
+        sp<AAH_DecoderPump> decoder_;
+
+        static int64_t      kAboutToUnderflowThreshold;
+
+        DISALLOW_EVIL_CONSTRUCTORS(Substream);
+    };
+
+    typedef DefaultKeyedVector< uint32_t, sp<Substream> > SubstreamVec;
+
+    status_t            startWorkThread();
+    void                stopWorkThread();
+    virtual bool        threadLoop();
+    bool                setupSocket();
+    void                cleanupSocket();
+    void                resetPipeline();
+    void                reset_l();
+    bool                processRX(PacketBuffer* pb);
+    void                processRingBuffer();
+    void                processCommandPacket(PacketBuffer* pb);
+    bool                processGaps();
+    int                 computeNextGapRetransmitTimeout();
+    void                fetchAudioFlinger();
+
+    PipeEvent           wakeup_work_thread_evt_;
+    sp<ThreadWrapper>   thread_wrapper_;
+    Mutex               api_lock_;
+    bool                is_playing_;
+    bool                data_source_set_;
+
+    struct sockaddr_in  listen_addr_;
+    int                 sock_fd_;
+    bool                multicast_joined_;
+
+    struct sockaddr_in  transmitter_addr_;
+    bool                transmitter_known_;
+
+    uint32_t            current_epoch_;
+    bool                current_epoch_known_;
+
+    SeqNoGap            current_gap_;
+    GapStatus           current_gap_status_;
+    uint64_t            next_retrans_req_time_;
+
+    RXRingBuffer        ring_buffer_;
+    SubstreamVec        substreams_;
+    OMXClient           omx_;
+    CCHelper            cc_helper_;
+
+    // Connection to audio flinger used to hack a path to setMasterVolume.
+    sp<IAudioFlinger>   audio_flinger_;
+
+    static const uint32_t kRTPRingBufferSize;
+    static const uint32_t kRetransRequestMagic;
+    static const uint32_t kFastStartRequestMagic;
+    static const uint32_t kRetransNAKMagic;
+    static const uint32_t kGapRerequestTimeoutUSec;
+    static const uint32_t kFastStartTimeoutUSec;
+    static const uint32_t kRTPActivityTimeoutUSec;
+
+    static const uint32_t INVOKE_GET_MASTER_VOLUME = 3;
+    static const uint32_t INVOKE_SET_MASTER_VOLUME = 4;
+
+    static uint64_t monotonicUSecNow();
+
+    DISALLOW_EVIL_CONSTRUCTORS(AAH_RXPlayer);
+};
+
+}  // namespace android
+
+#endif  // __AAH_RX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_rx_player_core.cpp b/media/libaah_rtp/aah_rx_player_core.cpp
new file mode 100644
index 0000000..7fbf9c8
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_core.cpp
@@ -0,0 +1,808 @@
+/*
+ * 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <utils/misc.h>
+
+#include <media/stagefright/Utils.h>
+
+#include "aah_rx_player.h"
+#include "aah_tx_packet.h"
+
+namespace android {
+
+const uint32_t AAH_RXPlayer::kRetransRequestMagic =
+    FOURCC('T','r','e','q');
+const uint32_t AAH_RXPlayer::kRetransNAKMagic =
+    FOURCC('T','n','a','k');
+const uint32_t AAH_RXPlayer::kFastStartRequestMagic =
+    FOURCC('T','f','s','t');
+const uint32_t AAH_RXPlayer::kGapRerequestTimeoutUSec = 75000;
+const uint32_t AAH_RXPlayer::kFastStartTimeoutUSec = 800000;
+const uint32_t AAH_RXPlayer::kRTPActivityTimeoutUSec = 10000000;
+
+static inline int16_t fetchInt16(uint8_t* data) {
+    return static_cast<int16_t>(U16_AT(data));
+}
+
+static inline int32_t fetchInt32(uint8_t* data) {
+    return static_cast<int32_t>(U32_AT(data));
+}
+
+static inline int64_t fetchInt64(uint8_t* data) {
+    return static_cast<int64_t>(U64_AT(data));
+}
+
+uint64_t AAH_RXPlayer::monotonicUSecNow() {
+    struct timespec now;
+    int res = clock_gettime(CLOCK_MONOTONIC, &now);
+    CHECK(res >= 0);
+
+    uint64_t ret = static_cast<uint64_t>(now.tv_sec) * 1000000;
+    ret += now.tv_nsec / 1000;
+
+    return ret;
+}
+
+status_t AAH_RXPlayer::startWorkThread() {
+    status_t res;
+    stopWorkThread();
+    res = thread_wrapper_->run("TRX_Player", PRIORITY_AUDIO);
+
+    if (res != OK) {
+        LOGE("Failed to start work thread (res = %d)", res);
+    }
+
+    return res;
+}
+
+void AAH_RXPlayer::stopWorkThread() {
+    thread_wrapper_->requestExit();  // set the exit pending flag
+    wakeup_work_thread_evt_.setEvent();
+
+    status_t res;
+    res = thread_wrapper_->requestExitAndWait(); // block until thread exit.
+    if (res != OK) {
+        LOGE("Failed to stop work thread (res = %d)", res);
+    }
+
+    wakeup_work_thread_evt_.clearPendingEvents();
+}
+
+void AAH_RXPlayer::cleanupSocket() {
+    if (sock_fd_ >= 0) {
+        if (multicast_joined_) {
+            int res;
+            struct ip_mreq mreq;
+            mreq.imr_multiaddr = listen_addr_.sin_addr;
+            mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+            res = setsockopt(sock_fd_,
+                             IPPROTO_IP,
+                             IP_DROP_MEMBERSHIP,
+                             &mreq, sizeof(mreq));
+            if (res < 0) {
+                LOGW("Failed to leave multicast group. (%d, %d)", res, errno);
+            }
+            multicast_joined_ = false;
+        }
+
+        close(sock_fd_);
+        sock_fd_ = -1;
+    }
+
+    resetPipeline();
+}
+
+void AAH_RXPlayer::resetPipeline() {
+    ring_buffer_.reset();
+
+    // Explicitly shudown all of the active substreams, then call clear out the
+    // collection.  Failure to clear out a substream can result in its decoder
+    // holding a reference to itself and therefor not going away when the
+    // collection is cleared.
+    for (size_t i = 0; i < substreams_.size(); ++i)
+        substreams_.valueAt(i)->shutdown();
+
+    substreams_.clear();
+
+    current_gap_status_ = kGS_NoGap;
+}
+
+bool AAH_RXPlayer::setupSocket() {
+    long flags;
+    int  res, buf_size;
+    socklen_t opt_size;
+
+    cleanupSocket();
+    CHECK(sock_fd_ < 0);
+
+    // Make the socket
+    sock_fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (sock_fd_ < 0) {
+        LOGE("Failed to create listen socket (errno %d)", errno);
+        goto bailout;
+    }
+
+    // Set non-blocking operation
+    flags = fcntl(sock_fd_, F_GETFL);
+    res   = fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK);
+    if (res < 0) {
+        LOGE("Failed to set socket (%d) to non-blocking mode (errno %d)",
+             sock_fd_, errno);
+        goto bailout;
+    }
+
+    // Bind to our port
+    struct sockaddr_in bind_addr;
+    memset(&bind_addr, 0, sizeof(bind_addr));
+    bind_addr.sin_family = AF_INET;
+    bind_addr.sin_addr.s_addr = INADDR_ANY;
+    bind_addr.sin_port = listen_addr_.sin_port;
+    res = bind(sock_fd_,
+               reinterpret_cast<const sockaddr*>(&bind_addr),
+               sizeof(bind_addr));
+    if (res < 0) {
+        uint32_t a = ntohl(bind_addr.sin_addr.s_addr);
+        uint16_t p = ntohs(bind_addr.sin_port);
+        LOGE("Failed to bind socket (%d) to %d.%d.%d.%d:%hd. (errno %d)",
+             sock_fd_,
+             (a >> 24) & 0xFF,
+             (a >> 16) & 0xFF,
+             (a >>  8) & 0xFF,
+             (a      ) & 0xFF,
+             p,
+             errno);
+
+        goto bailout;
+    }
+
+    buf_size = 1 << 16;   // 64k
+    res = setsockopt(sock_fd_,
+                     SOL_SOCKET, SO_RCVBUF,
+                     &buf_size, sizeof(buf_size));
+    if (res < 0) {
+        LOGW("Failed to increase socket buffer size to %d.  (errno %d)",
+             buf_size, errno);
+    }
+
+    buf_size = 0;
+    opt_size = sizeof(buf_size);
+    res = getsockopt(sock_fd_,
+                     SOL_SOCKET, SO_RCVBUF,
+                     &buf_size, &opt_size);
+    if (res < 0) {
+        LOGW("Failed to increase socket buffer size to %d.  (errno %d)",
+             buf_size, errno);
+    } else {
+        LOGI("RX socket buffer size is now %d bytes",  buf_size);
+    }
+
+    if (listen_addr_.sin_addr.s_addr) {
+        // Join the multicast group and we should be good to go.
+        struct ip_mreq mreq;
+        mreq.imr_multiaddr = listen_addr_.sin_addr;
+        mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+        res = setsockopt(sock_fd_,
+                         IPPROTO_IP,
+                         IP_ADD_MEMBERSHIP,
+                         &mreq, sizeof(mreq));
+        if (res < 0) {
+            LOGE("Failed to join multicast group. (errno %d)", errno);
+            goto bailout;
+        }
+        multicast_joined_ = true;
+    }
+
+    return true;
+
+bailout:
+    cleanupSocket();
+    return false;
+}
+
+bool AAH_RXPlayer::threadLoop() {
+    struct pollfd poll_fds[2];
+    bool process_more_right_now = false;
+
+    if (!setupSocket()) {
+        sendEvent(MEDIA_ERROR);
+        goto bailout;
+    }
+
+    while (!thread_wrapper_->exitPending()) {
+        // Step 1: Wait until there is something to do.
+        int gap_timeout = computeNextGapRetransmitTimeout();
+        int ring_timeout = ring_buffer_.computeInactivityTimeout();
+        int timeout = -1;
+
+        if (!ring_timeout) {
+            LOGW("RTP inactivity timeout reached, resetting pipeline.");
+            resetPipeline();
+            timeout = gap_timeout;
+        } else {
+            if (gap_timeout < 0) {
+                timeout = ring_timeout;
+            } else if (ring_timeout < 0) {
+                timeout = gap_timeout;
+            } else {
+                timeout = (gap_timeout < ring_timeout) ? gap_timeout
+                                                       : ring_timeout;
+            }
+        }
+
+        if ((0 != timeout) && (!process_more_right_now)) {
+            // Set up the events to wait on.  Start with the wakeup pipe.
+            memset(&poll_fds, 0, sizeof(poll_fds));
+            poll_fds[0].fd     = wakeup_work_thread_evt_.getWakeupHandle();
+            poll_fds[0].events = POLLIN;
+
+            // Add the RX socket.
+            poll_fds[1].fd     = sock_fd_;
+            poll_fds[1].events = POLLIN;
+
+            // Wait for something interesing to happen.
+            int poll_res = poll(poll_fds, NELEM(poll_fds), timeout);
+            if (poll_res < 0) {
+                LOGE("Fatal error (%d,%d) while waiting on events",
+                     poll_res, errno);
+                sendEvent(MEDIA_ERROR);
+                goto bailout;
+            }
+        }
+
+        if (thread_wrapper_->exitPending()) {
+            break;
+        }
+
+        wakeup_work_thread_evt_.clearPendingEvents();
+        process_more_right_now = false;
+
+        // Step 2: Do we have data waiting in the socket?  If so, drain the
+        // socket moving valid RTP information into the ring buffer to be
+        // processed.
+        if (poll_fds[1].revents) {
+            struct sockaddr_in from;
+            socklen_t from_len;
+
+            ssize_t res = 0;
+            while (!thread_wrapper_->exitPending()) {
+                // Check the size of any pending packet.
+                res = recv(sock_fd_, NULL, 0, MSG_PEEK | MSG_TRUNC);
+
+                // Error?
+                if (res < 0) {
+                    // If the error is anything other than would block,
+                    // something has gone very wrong.
+                    if ((errno != EAGAIN) && (errno != EWOULDBLOCK)) {
+                        LOGE("Fatal socket error during recvfrom (%d, %d)",
+                             (int)res, errno);
+                        goto bailout;
+                    }
+
+                    // Socket is out of data, just break out of processing and
+                    // wait for more.
+                    break;
+                }
+
+                // Allocate a payload.
+                PacketBuffer* pb = PacketBuffer::allocate(res);
+                if (NULL == pb) {
+                    LOGE("Fatal error, failed to allocate packet buffer of"
+                         " length %u", static_cast<uint32_t>(res));
+                    goto bailout;
+                }
+
+                // Fetch the data.
+                from_len = sizeof(from);
+                res = recvfrom(sock_fd_, pb->data_, pb->length_, 0,
+                               reinterpret_cast<struct sockaddr*>(&from),
+                               &from_len);
+                if (res != pb->length_) {
+                    LOGE("Fatal error, fetched packet length (%d) does not"
+                         " match peeked packet length (%u).  This should never"
+                         " happen.  (errno = %d)",
+                         static_cast<int>(res),
+                         static_cast<uint32_t>(pb->length_),
+                         errno);
+                }
+
+                bool drop_packet = false;
+                if (transmitter_known_) {
+                    if (from.sin_addr.s_addr !=
+                        transmitter_addr_.sin_addr.s_addr) {
+                        uint32_t a = ntohl(from.sin_addr.s_addr);
+                        uint16_t p = ntohs(from.sin_port);
+                        LOGV("Dropping packet from unknown transmitter"
+                             " %u.%u.%u.%u:%hu",
+                             ((a >> 24) & 0xFF),
+                             ((a >> 16) & 0xFF),
+                             ((a >>  8) & 0xFF),
+                             ( a        & 0xFF),
+                             p);
+
+                        drop_packet = true;
+                    } else {
+                        transmitter_addr_.sin_port = from.sin_port;
+                    }
+                } else {
+                    memcpy(&transmitter_addr_, &from, sizeof(from));
+                    transmitter_known_ = true;
+                }
+
+                if (!drop_packet) {
+                    bool serious_error = !processRX(pb);
+
+                    if (serious_error) {
+                        // Something went "seriously wrong".  Currently, the
+                        // only trigger for this should be a ring buffer
+                        // overflow.  The current failsafe behavior for when
+                        // something goes seriously wrong is to just reset the
+                        // pipeline.  The system should behave as if this
+                        // AAH_RXPlayer was just set up for the first time.
+                        LOGE("Something just went seriously wrong with the"
+                             " pipeline.  Resetting.");
+                        resetPipeline();
+                    }
+                } else {
+                    PacketBuffer::destroy(pb);
+                }
+            }
+        }
+
+        // Step 3: Process any data we mave have accumulated in the ring buffer
+        // so far.
+        if (!thread_wrapper_->exitPending()) {
+            processRingBuffer();
+        }
+
+        // Step 4: At this point in time, the ring buffer should either be
+        // empty, or stalled in front of a gap caused by some dropped packets.
+        // Check on the current gap situation and deal with it in an appropriate
+        // fashion.  If processGaps returns true, it means that it has given up
+        // on a gap and that we should try to process some more data
+        // immediately.
+        if (!thread_wrapper_->exitPending()) {
+            process_more_right_now = processGaps();
+        }
+
+        // Step 5: Check for fatal errors.  If any of our substreams has
+        // encountered a fatal, unrecoverable, error, then propagate the error
+        // up to user level and shut down.
+        for (size_t i = 0; i < substreams_.size(); ++i) {
+            status_t status;
+            CHECK(substreams_.valueAt(i) != NULL);
+
+            status = substreams_.valueAt(i)->getStatus();
+            if (OK != status) {
+                LOGE("Substream index %d has encountered an unrecoverable"
+                     " error (%d).  Signalling application level and shutting"
+                     " down.", i, status);
+                sendEvent(MEDIA_ERROR);
+                goto bailout;
+            }
+        }
+    }
+
+bailout:
+    cleanupSocket();
+    return false;
+}
+
+bool AAH_RXPlayer::processRX(PacketBuffer* pb) {
+    CHECK(NULL != pb);
+
+    uint8_t* data = pb->data_;
+    ssize_t  amt  = pb->length_;
+    uint32_t nak_magic;
+    uint16_t seq_no;
+    uint32_t epoch;
+
+    // Every packet either starts with an RTP header which is at least 12 bytes
+    // long or is a retry NAK which is 14 bytes long.  If there are fewer than
+    // 12 bytes here, this cannot be a proper RTP packet.
+    if (amt < 12) {
+        LOGV("Dropping packet, too short to contain RTP header (%u bytes)",
+             static_cast<uint32_t>(amt));
+        goto drop_packet;
+    }
+
+    // Check to see if this is the special case of a NAK packet.
+    nak_magic = ntohl(*(reinterpret_cast<uint32_t*>(data)));
+    if (nak_magic == kRetransNAKMagic) {
+        // Looks like a NAK packet; make sure its long enough.
+
+        if (amt < static_cast<ssize_t>(sizeof(RetransRequest))) {
+            LOGV("Dropping packet, too short to contain NAK payload (%u bytes)",
+                 static_cast<uint32_t>(amt));
+            goto drop_packet;
+        }
+
+        SeqNoGap gap;
+        RetransRequest* rtr = reinterpret_cast<RetransRequest*>(data);
+        gap.start_seq_ = ntohs(rtr->start_seq_);
+        gap.end_seq_   = ntohs(rtr->end_seq_);
+
+        LOGV("Process NAK for gap at [%hu, %hu]", gap.start_seq_, gap.end_seq_);
+        ring_buffer_.processNAK(&gap);
+
+        return true;
+    }
+
+    // According to the TRTP spec, version should be 2, padding should be 0,
+    // extension should be 0 and CSRCCnt should be 0.  If any of these tests
+    // fail, we chuck the packet.
+    if (data[0] != 0x80) {
+        LOGV("Dropping packet, bad V/P/X/CSRCCnt field (0x%02x)",
+             data[0]);
+        goto drop_packet;
+    }
+
+    // Check the payload type.  For TRTP, it should always be 100.
+    if ((data[1] & 0x7F) != 100) {
+        LOGV("Dropping packet, bad payload type. (%u)",
+             data[1] & 0x7F);
+        goto drop_packet;
+    }
+
+    // Check whether the transmitter has begun a new epoch.
+    epoch = (U32_AT(data + 8) >> 10) & 0x3FFFFF;
+    if (current_epoch_known_) {
+        if (epoch != current_epoch_) {
+            LOGV("%s: new epoch %u", __PRETTY_FUNCTION__, epoch);
+            current_epoch_ = epoch;
+            resetPipeline();
+        }
+    } else {
+        current_epoch_ = epoch;
+        current_epoch_known_ = true;
+    }
+
+    // Extract the sequence number and hand the packet off to the ring buffer
+    // for dropped packet detection and later processing.
+    seq_no = U16_AT(data + 2);
+    return ring_buffer_.pushBuffer(pb, seq_no);
+
+drop_packet:
+    PacketBuffer::destroy(pb);
+    return true;
+}
+
+void AAH_RXPlayer::processRingBuffer() {
+    PacketBuffer* pb;
+    bool is_discon;
+    sp<Substream> substream;
+    LinearTransform trans;
+    bool foundTrans = false;
+
+    while (NULL != (pb = ring_buffer_.fetchBuffer(&is_discon))) {
+        if (is_discon) {
+            // Abort all partially assembled payloads.
+            for (size_t i = 0; i < substreams_.size(); ++i) {
+                CHECK(substreams_.valueAt(i) != NULL);
+                substreams_.valueAt(i)->cleanupBufferInProgress();
+            }
+        }
+
+        uint8_t* data = pb->data_;
+        ssize_t  amt  = pb->length_;
+
+        // Should not have any non-RTP packets in the ring buffer.  RTP packets
+        // must be at least 12 bytes long.
+        CHECK(amt >= 12);
+
+        // Extract the marker bit and the SSRC field.
+        bool     marker = (data[1] & 0x80) != 0;
+        uint32_t ssrc   = U32_AT(data + 8);
+
+        // Is this the start of a new TRTP payload?  If so, the marker bit
+        // should be set and there are some things we should be checking for.
+        if (marker) {
+            // TRTP headers need to have at least a byte for version, a byte for
+            // payload type and flags, and 4 bytes for length.
+            if (amt < 18) {
+                LOGV("Dropping packet, too short to contain TRTP header"
+                     " (%u bytes)", static_cast<uint32_t>(amt));
+                goto process_next_packet;
+            }
+
+            // Check the TRTP version and extract the payload type/flags.
+            uint8_t trtp_version =  data[12];
+            uint8_t payload_type = (data[13] >> 4) & 0xF;
+            uint8_t trtp_flags   =  data[13]       & 0xF;
+
+            if (1 != trtp_version) {
+                LOGV("Dropping packet, bad trtp version %hhu", trtp_version);
+                goto process_next_packet;
+            }
+
+            // Is there a timestamp transformation present on this packet?  If
+            // so, extract it and pass it to the appropriate substreams.
+            if (trtp_flags & 0x02) {
+                ssize_t offset = 18 + ((trtp_flags & 0x01) ? 4 : 0);
+                if (amt < (offset + 24)) {
+                    LOGV("Dropping packet, too short to contain TRTP Timestamp"
+                         " Transformation (%u bytes)",
+                         static_cast<uint32_t>(amt));
+                    goto process_next_packet;
+                }
+
+                trans.a_zero = fetchInt64(data + offset);
+                trans.b_zero = fetchInt64(data + offset + 16);
+                trans.a_to_b_numer = static_cast<int32_t>(
+                        fetchInt32 (data + offset + 8));
+                trans.a_to_b_denom = U32_AT(data + offset + 12);
+                foundTrans = true;
+
+                uint32_t program_id = (ssrc >> 5) & 0x1F;
+                for (size_t i = 0; i < substreams_.size(); ++i) {
+                    sp<Substream> iter = substreams_.valueAt(i);
+                    CHECK(iter != NULL);
+
+                    if (iter->getProgramID() == program_id) {
+                        iter->processTSTransform(trans);
+                    }
+                }
+            }
+
+            // Is this a command packet?  If so, its not necessarily associate
+            // with one particular substream.  Just give it to the command
+            // packet handler and then move on.
+            if (4 == payload_type) {
+                processCommandPacket(pb);
+                goto process_next_packet;
+            }
+        }
+
+        // If we got to here, then we are a normal packet.  Find (or allocate)
+        // the substream we belong to and send the packet off to be processed.
+        substream = substreams_.valueFor(ssrc);
+        if (substream == NULL) {
+            substream = new Substream(ssrc, omx_);
+            if (substream == NULL) {
+                LOGE("Failed to allocate substream for SSRC 0x%08x", ssrc);
+                goto process_next_packet;
+            }
+            substreams_.add(ssrc, substream);
+
+            if (foundTrans) {
+                substream->processTSTransform(trans);
+            }
+        }
+
+        CHECK(substream != NULL);
+
+        if (marker) {
+            // Start of a new TRTP payload for this substream.  Extract the
+            // lower 32 bits of the timestamp and hand the buffer to the
+            // substream for processing.
+            uint32_t ts_lower = U32_AT(data + 4);
+            substream->processPayloadStart(data + 12, amt - 12, ts_lower);
+        } else {
+            // Continuation of an existing TRTP payload.  Just hand it off to
+            // the substream for processing.
+            substream->processPayloadCont(data + 12, amt - 12);
+        }
+
+process_next_packet:
+        PacketBuffer::destroy(pb);
+    }  // end of main processing while loop.
+}
+
+void AAH_RXPlayer::processCommandPacket(PacketBuffer* pb) {
+    CHECK(NULL != pb);
+
+    uint8_t* data = pb->data_;
+    ssize_t  amt  = pb->length_;
+
+    // verify that this packet meets the minimum length of a command packet
+    if (amt < 20) {
+        return;
+    }
+
+    uint8_t trtp_version =  data[12];
+    uint8_t trtp_flags   =  data[13]       & 0xF;
+
+    if (1 != trtp_version) {
+        LOGV("Dropping packet, bad trtp version %hhu", trtp_version);
+        return;
+    }
+
+    // calculate the start of the command payload
+    ssize_t offset = 18;
+    if (trtp_flags & 0x01) {
+        // timestamp is present (4 bytes)
+        offset += 4;
+    }
+    if (trtp_flags & 0x02) {
+        // transform is present (24 bytes)
+        offset += 24;
+    }
+
+    // the packet must contain 2 bytes of command payload beyond the TRTP header
+    if (amt < offset + 2) {
+        return;
+    }
+
+    uint16_t command_id = U16_AT(data + offset);
+
+    switch (command_id) {
+        case TRTPControlPacket::kCommandNop:
+            break;
+
+        case TRTPControlPacket::kCommandEOS:
+        case TRTPControlPacket::kCommandFlush: {
+            uint16_t program_id = (U32_AT(data + 8) >> 5) & 0x1F;
+            LOGI("*** %s flushing program_id=%d",
+                 __PRETTY_FUNCTION__, program_id);
+
+            Vector<uint32_t> substreams_to_remove;
+            for (size_t i = 0; i < substreams_.size(); ++i) {
+                sp<Substream> iter = substreams_.valueAt(i);
+                if (iter->getProgramID() == program_id) {
+                    iter->shutdown();
+                    substreams_to_remove.add(iter->getSSRC());
+                }
+            }
+
+            for (size_t i = 0; i < substreams_to_remove.size(); ++i) {
+                substreams_.removeItem(substreams_to_remove[i]);
+            }
+        } break;
+    }
+}
+
+bool AAH_RXPlayer::processGaps() {
+    // Deal with the current gap situation.  Specifically...
+    //
+    // 1) If a new gap has shown up, send a retransmit request to the
+    //    transmitter.
+    // 2) If a gap we were working on has had a packet in the middle or at
+    //    the end filled in, send another retransmit request for the begining
+    //    portion of the gap.  TRTP was designed for LANs where packet
+    //    re-ordering is very unlikely; so see the middle or end of a gap
+    //    filled in before the begining is an almost certain indication that
+    //    a retransmission packet was also dropped.
+    // 3) If we have been working on a gap for a while and it still has not
+    //    been filled in, send another retransmit request.
+    // 4) If the are no more gaps in the ring, clear the current_gap_status_
+    //    flag to indicate that all is well again.
+
+    // Start by fetching the active gap status.
+    SeqNoGap gap;
+    bool send_retransmit_request = false;
+    bool ret_val = false;
+    GapStatus gap_status;
+    if (kGS_NoGap != (gap_status = ring_buffer_.fetchCurrentGap(&gap))) {
+        // Note: checking for a change in the end sequence number should cover
+        // moving on to an entirely new gap for case #1 as well as resending the
+        // begining of a gap range for case #2.
+        send_retransmit_request = (kGS_NoGap == current_gap_status_) ||
+                                  (current_gap_.end_seq_ != gap.end_seq_);
+
+        // If this is the same gap we have been working on, and it has timed
+        // out, then check to see if our substreams are about to underflow.  If
+        // so, instead of sending another retransmit request, just give up on
+        // this gap and move on.
+        if (!send_retransmit_request &&
+           (kGS_NoGap != current_gap_status_) &&
+           (0 == computeNextGapRetransmitTimeout())) {
+
+            // If out current gap is the fast-start gap, don't bother to skip it
+            // because substreams look like the are about to underflow.
+            if ((kGS_FastStartGap != gap_status) ||
+                (current_gap_.end_seq_ != gap.end_seq_)) {
+                for (size_t i = 0; i < substreams_.size(); ++i) {
+                    if (substreams_.valueAt(i)->isAboutToUnderflow()) {
+                        LOGV("About to underflow, giving up on gap [%hu, %hu]",
+                                gap.start_seq_, gap.end_seq_);
+                        ring_buffer_.processNAK();
+                        current_gap_status_ = kGS_NoGap;
+                        return true;
+                    }
+                }
+            }
+
+            // Looks like no one is about to underflow.  Just go ahead and send
+            // the request.
+            send_retransmit_request = true;
+        }
+    } else {
+        current_gap_status_ = kGS_NoGap;
+    }
+
+    if (send_retransmit_request) {
+        // If we have been working on a fast start, and it is still not filled
+        // in, even after the extended retransmit time out, give up and skip it.
+        // The system should fall back into its normal slow-start behavior.
+        if ((kGS_FastStartGap == current_gap_status_) &&
+            (current_gap_.end_seq_ == gap.end_seq_)) {
+            LOGV("Fast start is taking forever; giving up.");
+            ring_buffer_.processNAK();
+            current_gap_status_ = kGS_NoGap;
+            return true;
+        }
+
+        // Send the request.
+        RetransRequest req;
+        uint32_t magic  = (kGS_FastStartGap == gap_status)
+                        ? kFastStartRequestMagic
+                        : kRetransRequestMagic;
+        req.magic_      = htonl(magic);
+        req.mcast_ip_   = listen_addr_.sin_addr.s_addr;
+        req.mcast_port_ = listen_addr_.sin_port;
+        req.start_seq_  = htons(gap.start_seq_);
+        req.end_seq_    = htons(gap.end_seq_);
+
+        {
+            uint32_t a = ntohl(transmitter_addr_.sin_addr.s_addr);
+            uint16_t p = ntohs(transmitter_addr_.sin_port);
+            LOGV("Sending to transmitter %u.%u.%u.%u:%hu",
+                    ((a >> 24) & 0xFF),
+                    ((a >> 16) & 0xFF),
+                    ((a >>  8) & 0xFF),
+                    ( a        & 0xFF),
+                    p);
+        }
+
+        int res = sendto(sock_fd_, &req, sizeof(req), 0,
+                         reinterpret_cast<struct sockaddr*>(&transmitter_addr_),
+                         sizeof(transmitter_addr_));
+        if (res < 0) {
+            LOGE("Error when sending retransmit request (%d)", errno);
+        } else {
+            LOGV("%s request for range [%hu, %hu] sent",
+                 (kGS_FastStartGap == gap_status) ? "Fast Start" : "Retransmit",
+                 gap.start_seq_, gap.end_seq_);
+        }
+
+        // Update the current gap info.
+        current_gap_ = gap;
+        current_gap_status_ = gap_status;
+        next_retrans_req_time_ = monotonicUSecNow() +
+                               ((kGS_FastStartGap == current_gap_status_)
+                                ? kFastStartTimeoutUSec
+                                : kGapRerequestTimeoutUSec);
+    }
+
+    return false;
+}
+
+// Compute when its time to send the next gap retransmission in milliseconds.
+// Returns < 0 for an infinite timeout (no gap) and 0 if its time to retransmit
+// right now.
+int AAH_RXPlayer::computeNextGapRetransmitTimeout() {
+    if (kGS_NoGap == current_gap_status_) {
+        return -1;
+    }
+
+    int64_t timeout_delta = next_retrans_req_time_ - monotonicUSecNow();
+
+    timeout_delta /= 1000;
+    if (timeout_delta <= 0) {
+        return 0;
+    }
+
+    return static_cast<uint32_t>(timeout_delta);
+}
+
+}  // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_ring_buffer.cpp b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp
new file mode 100644
index 0000000..22467f7
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_ring_buffer.cpp
@@ -0,0 +1,366 @@
+/*
+ * 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include "aah_rx_player.h"
+
+namespace android {
+
+AAH_RXPlayer::RXRingBuffer::RXRingBuffer(uint32_t capacity) {
+    capacity_ = capacity;
+    rd_ = wr_ = 0;
+    ring_ = new PacketBuffer*[capacity];
+    memset(ring_, 0, sizeof(PacketBuffer*) * capacity);
+    reset();
+}
+
+AAH_RXPlayer::RXRingBuffer::~RXRingBuffer() {
+    reset();
+    delete[] ring_;
+}
+
+void AAH_RXPlayer::RXRingBuffer::reset() {
+    AutoMutex lock(&lock_);
+
+    if (NULL != ring_) {
+        while (rd_ != wr_) {
+            CHECK(rd_ < capacity_);
+            if (NULL != ring_[rd_]) {
+                PacketBuffer::destroy(ring_[rd_]);
+                ring_[rd_] = NULL;
+            }
+            rd_ = (rd_ + 1) % capacity_;
+        }
+    }
+
+    rd_ = wr_ = 0;
+    rd_seq_known_ = false;
+    waiting_for_fast_start_ = true;
+    fetched_first_packet_ = false;
+    rtp_activity_timeout_valid_ = false;
+}
+
+bool AAH_RXPlayer::RXRingBuffer::pushBuffer(PacketBuffer* buf,
+                                                uint16_t seq) {
+    AutoMutex lock(&lock_);
+    CHECK(NULL != ring_);
+    CHECK(NULL != buf);
+
+    rtp_activity_timeout_valid_ = true;
+    rtp_activity_timeout_ = monotonicUSecNow() + kRTPActivityTimeoutUSec;
+
+    // If the ring buffer is totally reset (we have never received a single
+    // payload) then we don't know the rd sequence number and this should be
+    // simple.  We just store the payload, advance the wr pointer and record the
+    // initial sequence number.
+    if (!rd_seq_known_) {
+        CHECK(rd_ == wr_);
+        CHECK(NULL == ring_[wr_]);
+        CHECK(wr_ < capacity_);
+
+        ring_[wr_] = buf;
+        wr_ = (wr_ + 1) % capacity_;
+        rd_seq_ = seq;
+        rd_seq_known_ = true;
+        return true;
+    }
+
+    // Compute the seqence number of this payload and of the write pointer,
+    // normalized around the read pointer.  IOW - transform the payload seq no
+    // and the wr pointer seq no into a space where the rd pointer seq no is
+    // zero.  This will define 4 cases we can consider...
+    //
+    // 1) norm_seq == norm_wr_seq
+    //    This payload is contiguous with the last.  All is good.
+    //
+    // 2)  ((norm_seq <  norm_wr_seq) && (norm_seq >= norm_rd_seq)
+    // aka ((norm_seq <  norm_wr_seq) && (norm_seq >= 0)
+    //    This payload is in the past, in the unprocessed region of the ring
+    //    buffer.  It is probably a retransmit intended to fill in a dropped
+    //    payload; it may be a duplicate.
+    //
+    // 3) ((norm_seq - norm_wr_seq) & 0x8000) != 0
+    //    This payload is in the past compared to the write pointer (or so very
+    //    far in the future that it has wrapped the seq no space), but not in
+    //    the unprocessed region of the ring buffer.  This could be a duplicate
+    //    retransmit; we just drop these payloads unless we are waiting for our
+    //    first fast start packet.  If we are waiting for fast start, than this
+    //    packet is probably the first packet of the fast start retransmission.
+    //    If it will fit in the buffer, back up the read pointer to its position
+    //    and clear the fast start flag, otherwise just drop it.
+    //
+    // 4) ((norm_seq - norm_wr_seq) & 0x8000) == 0
+    //    This payload which is ahead of the next write pointer.  This indicates
+    //    that we have missed some payloads and need to request a retransmit.
+    //    If norm_seq >= (capacity - 1), then the gap is so large that it would
+    //    overflow the ring buffer and we should probably start to panic.
+
+    uint16_t norm_wr_seq = ((wr_ + capacity_ - rd_) % capacity_);
+    uint16_t norm_seq    = seq - rd_seq_;
+
+    // Check for overflow first.
+    if ((!(norm_seq & 0x8000)) && (norm_seq >= (capacity_ - 1))) {
+        LOGW("Ring buffer overflow; cap = %u, [rd, wr] = [%hu, %hu], seq = %hu",
+             capacity_, rd_seq_, norm_wr_seq + rd_seq_, seq);
+        PacketBuffer::destroy(buf);
+        return false;
+    }
+
+    // Check for case #1
+    if (norm_seq == norm_wr_seq) {
+        CHECK(wr_ < capacity_);
+        CHECK(NULL == ring_[wr_]);
+
+        ring_[wr_] = buf;
+        wr_ = (wr_ + 1) % capacity_;
+
+        CHECK(wr_ != rd_);
+        return true;
+    }
+
+    // Check case #2
+    uint32_t ring_pos = (rd_ + norm_seq) % capacity_;
+    if ((norm_seq < norm_wr_seq) && (!(norm_seq & 0x8000))) {
+        // Do we already have a payload for this slot?  If so, then this looks
+        // like a duplicate retransmit.  Just ignore it.
+        if (NULL != ring_[ring_pos]) {
+            LOGD("RXed duplicate retransmit, seq = %hu", seq);
+            PacketBuffer::destroy(buf);
+        } else {
+            // Looks like we were missing this payload.  Go ahead and store it.
+            ring_[ring_pos] = buf;
+        }
+
+        return true;
+    }
+
+    // Check case #3
+    if ((norm_seq - norm_wr_seq) & 0x8000) {
+        if (!waiting_for_fast_start_) {
+            LOGD("RXed duplicate retransmit from before rd pointer, seq = %hu",
+                 seq);
+            PacketBuffer::destroy(buf);
+        } else {
+            // Looks like a fast start fill-in.  Go ahead and store it, assuming
+            // that we can fit it in the buffer.
+            uint32_t implied_ring_size = static_cast<uint32_t>(norm_wr_seq)
+                                       + (rd_seq_ - seq);
+
+            if (implied_ring_size >= (capacity_ - 1)) {
+                LOGD("RXed what looks like a fast start packet (seq = %hu),"
+                     " but packet is too far in the past to fit into the ring"
+                     "  buffer.  Dropping.", seq);
+                PacketBuffer::destroy(buf);
+            } else {
+                ring_pos = (rd_ + capacity_ + seq - rd_seq_) % capacity_;
+                rd_seq_ = seq;
+                rd_ = ring_pos;
+                waiting_for_fast_start_ = false;
+
+                CHECK(ring_pos < capacity_);
+                CHECK(NULL == ring_[ring_pos]);
+                ring_[ring_pos] = buf;
+            }
+
+        }
+        return true;
+    }
+
+    // Must be in case #4 with no overflow.  This packet fits in the current
+    // ring buffer, but is discontiuguous.  Advance the write pointer leaving a
+    // gap behind.
+    uint32_t gap_len = (ring_pos + capacity_ - wr_) % capacity_;
+    LOGD("Drop detected; %u packets, seq_range [%hu, %hu]",
+         gap_len,
+         rd_seq_ + norm_wr_seq,
+         rd_seq_ + norm_wr_seq + gap_len - 1);
+
+    CHECK(NULL == ring_[ring_pos]);
+    ring_[ring_pos] = buf;
+    wr_ = (ring_pos + 1) % capacity_;
+    CHECK(wr_ != rd_);
+
+    return true;
+}
+
+AAH_RXPlayer::PacketBuffer*
+AAH_RXPlayer::RXRingBuffer::fetchBuffer(bool* is_discon) {
+    AutoMutex lock(&lock_);
+    CHECK(NULL != ring_);
+    CHECK(NULL != is_discon);
+
+    // If the read seqence number is not known, then this ring buffer has not
+    // received a packet since being reset and there cannot be any packets to
+    // return.  If we are still waiting for the first fast start packet to show
+    // up, we don't want to let any buffer be consumed yet because we expect to
+    // see a packet before the initial read sequence number show up shortly.
+    if (!rd_seq_known_ || waiting_for_fast_start_) {
+        *is_discon = false;
+        return NULL;
+    }
+
+    PacketBuffer* ret = NULL;
+    *is_discon = !fetched_first_packet_;
+
+    while ((rd_ != wr_) && (NULL == ret)) {
+        CHECK(rd_ < capacity_);
+
+        // If we hit a gap, stall and do not advance the read pointer.  Let the
+        // higher level code deal with requesting retries and/or deciding to
+        // skip the current gap.
+        ret = ring_[rd_];
+        if (NULL == ret) {
+            break;
+        }
+
+        ring_[rd_] = NULL;
+        rd_ = (rd_ + 1) % capacity_;
+        ++rd_seq_;
+    }
+
+    if (NULL != ret) {
+        fetched_first_packet_ = true;
+    }
+
+    return ret;
+}
+
+AAH_RXPlayer::GapStatus
+AAH_RXPlayer::RXRingBuffer::fetchCurrentGap(SeqNoGap* gap) {
+    AutoMutex lock(&lock_);
+    CHECK(NULL != ring_);
+    CHECK(NULL != gap);
+
+    // If the read seqence number is not known, then this ring buffer has not
+    // received a packet since being reset and there cannot be any gaps.
+    if (!rd_seq_known_) {
+        return kGS_NoGap;
+    }
+
+    // If we are waiting for fast start, then the current gap is a fast start
+    // gap and it includes all packets before the read sequence number.
+    if (waiting_for_fast_start_) {
+        gap->start_seq_ =
+        gap->end_seq_   = rd_seq_ - 1;
+        return kGS_FastStartGap;
+    }
+
+    // If rd == wr, then the buffer is empty and there cannot be any gaps.
+    if (rd_ == wr_) {
+        return kGS_NoGap;
+    }
+
+    // If rd_ is currently pointing at an unprocessed packet, then there is no
+    // current gap.
+    CHECK(rd_ < capacity_);
+    if (NULL != ring_[rd_]) {
+        return kGS_NoGap;
+    }
+
+    // Looks like there must be a gap here.  The start of the gap is the current
+    // rd sequence number, all we need to do now is determine its length in
+    // order to compute the end sequence number.
+    gap->start_seq_ = rd_seq_;
+    uint16_t end = rd_seq_;
+    uint32_t tmp = (rd_ + 1) % capacity_;
+    while ((tmp != wr_) && (NULL == ring_[tmp])) {
+        ++end;
+        tmp = (tmp + 1) % capacity_;
+    }
+    gap->end_seq_ = end;
+
+    return kGS_NormalGap;
+}
+
+void AAH_RXPlayer::RXRingBuffer::processNAK(SeqNoGap* nak) {
+    AutoMutex lock(&lock_);
+    CHECK(NULL != ring_);
+
+    // If we were waiting for our first fast start fill-in packet, and we
+    // received a NAK, then apparantly we are not getting our fast start.  Just
+    // clear the waiting flag and go back to normal behavior.
+    if (waiting_for_fast_start_) {
+        waiting_for_fast_start_ = false;
+    }
+
+    // If we have not received a packet since last reset, or there is no data in
+    // the ring, then there is nothing to skip.
+    if ((!rd_seq_known_) || (rd_ == wr_)) {
+        return;
+    }
+
+    // If rd_ is currently pointing at an unprocessed packet, then there is no
+    // gap to skip.
+    CHECK(rd_ < capacity_);
+    if (NULL != ring_[rd_]) {
+        return;
+    }
+
+    // Looks like there must be a gap here.  Advance rd until we have passed
+    // over the portion of it indicated by nak (or all of the gap if nak is
+    // NULL).  Then reset fetched_first_packet_ so that the next read will show
+    // up as being discontiguous.
+    uint16_t seq_after_gap = (NULL == nak) ? 0 : nak->end_seq_ + 1;
+    while ((rd_ != wr_) &&
+           (NULL == ring_[rd_]) &&
+          ((NULL == nak) || (seq_after_gap != rd_seq_))) {
+        rd_ = (rd_ + 1) % capacity_;
+        ++rd_seq_;
+    }
+    fetched_first_packet_ = false;
+}
+
+int AAH_RXPlayer::RXRingBuffer::computeInactivityTimeout() {
+    AutoMutex lock(&lock_);
+
+    if (!rtp_activity_timeout_valid_) {
+        return -1;
+    }
+
+    uint64_t now = monotonicUSecNow();
+    if (rtp_activity_timeout_ <= now) {
+        return 0;
+    }
+
+    return (rtp_activity_timeout_ - now) / 1000;
+}
+
+AAH_RXPlayer::PacketBuffer*
+AAH_RXPlayer::PacketBuffer::allocate(ssize_t length) {
+    if (length <= 0) {
+        return NULL;
+    }
+
+    uint32_t alloc_len = sizeof(PacketBuffer) + length;
+    PacketBuffer* ret = reinterpret_cast<PacketBuffer*>(
+                        new uint8_t[alloc_len]);
+
+    if (NULL != ret) {
+        ret->length_ = length;
+    }
+
+    return ret;
+}
+
+void AAH_RXPlayer::PacketBuffer::destroy(PacketBuffer* pb) {
+    uint8_t* kill_me = reinterpret_cast<uint8_t*>(pb);
+    delete[] kill_me;
+}
+
+}  // namespace android
diff --git a/media/libaah_rtp/aah_rx_player_substream.cpp b/media/libaah_rtp/aah_rx_player_substream.cpp
new file mode 100644
index 0000000..3e3a95c
--- /dev/null
+++ b/media/libaah_rtp/aah_rx_player_substream.cpp
@@ -0,0 +1,676 @@
+/*
+ * 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 "LibAAH_RTP"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+
+#include <include/avc_utils.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXCodec.h>
+#include <media/stagefright/Utils.h>
+
+#include "aah_rx_player.h"
+#include "aah_tx_packet.h"
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+namespace android {
+
+int64_t AAH_RXPlayer::Substream::kAboutToUnderflowThreshold =
+    50ull * 1000;
+
+AAH_RXPlayer::Substream::Substream(uint32_t ssrc, OMXClient& omx) {
+    ssrc_ = ssrc;
+    substream_details_known_ = false;
+    buffer_in_progress_ = NULL;
+    status_ = OK;
+    codec_mime_type_ = "";
+
+    decoder_ = new AAH_DecoderPump(omx);
+    if (decoder_ == NULL) {
+        LOGE("%s failed to allocate decoder pump!", __PRETTY_FUNCTION__);
+    }
+    if (OK != decoder_->initCheck()) {
+        LOGE("%s failed to initialize decoder pump!", __PRETTY_FUNCTION__);
+    }
+
+    // cleanupBufferInProgress will reset most of the internal state variables.
+    // Just need to make sure that buffer_in_progress_ is NULL before calling.
+    cleanupBufferInProgress();
+}
+
+AAH_RXPlayer::Substream::~Substream() {
+    shutdown();
+}
+
+void AAH_RXPlayer::Substream::shutdown() {
+    substream_meta_ = NULL;
+    status_ = OK;
+    cleanupBufferInProgress();
+    cleanupDecoder();
+}
+
+void AAH_RXPlayer::Substream::cleanupBufferInProgress() {
+    if (NULL != buffer_in_progress_) {
+        buffer_in_progress_->release();
+        buffer_in_progress_ = NULL;
+    }
+
+    expected_buffer_size_ = 0;
+    buffer_filled_ = 0;
+    waiting_for_rap_ = true;
+
+    aux_data_in_progress_.clear();
+    aux_data_expected_size_ = 0;
+}
+
+void AAH_RXPlayer::Substream::cleanupDecoder() {
+    if (decoder_ != NULL) {
+        decoder_->shutdown();
+    }
+}
+
+bool AAH_RXPlayer::Substream::shouldAbort(const char* log_tag) {
+    // If we have already encountered a fatal error, do nothing.  We are just
+    // waiting for our owner to shut us down now.
+    if (OK != status_) {
+        LOGV("Skipping %s, substream has encountered fatal error (%d).",
+                log_tag, status_);
+        return true;
+    }
+
+    return false;
+}
+
+void AAH_RXPlayer::Substream::processPayloadStart(uint8_t* buf,
+                                                  uint32_t amt,
+                                                  int32_t ts_lower) {
+    uint32_t min_length = 6;
+
+    if (shouldAbort(__PRETTY_FUNCTION__)) {
+        return;
+    }
+
+    // Do we have a buffer in progress already?  If so, abort the buffer.  In
+    // theory, this should never happen.  If there were a discontinutity in the
+    // stream, the discon in the seq_nos at the RTP level should have already
+    // triggered a cleanup of the buffer in progress.  To see a problem at this
+    // level is an indication either of a bug in the transmitter, or some form
+    // of terrible corruption/tampering on the wire.
+    if (NULL != buffer_in_progress_) {
+        LOGE("processPayloadStart is aborting payload already in progress.");
+        cleanupBufferInProgress();
+    }
+
+    // Parse enough of the header to know where we stand.  Since this is a
+    // payload start, it should begin with a TRTP header which has to be at
+    // least 6 bytes long.
+    if (amt < min_length) {
+        LOGV("Discarding payload too short to contain TRTP header (len = %u)",
+                amt);
+        return;
+    }
+
+    // Check the TRTP version number.
+    if (0x01 != buf[0]) {
+        LOGV("Unexpected TRTP version (%u) in header.  Expected %u.",
+                buf[0], 1);
+        return;
+    }
+
+    // Extract the substream type field and make sure its one we understand (and
+    // one that does not conflict with any previously received substream type.
+    uint8_t header_type = (buf[1] >> 4) & 0xF;
+    switch (header_type) {
+        case 0x01:
+            // Audio, yay!  Just break.  We understand audio payloads.
+            break;
+        case 0x02:
+            LOGV("RXed packet with unhandled TRTP header type (Video).");
+            return;
+        case 0x03:
+            LOGV("RXed packet with unhandled TRTP header type (Subpicture).");
+            return;
+        case 0x04:
+            LOGV("RXed packet with unhandled TRTP header type (Control).");
+            return;
+        default:
+            LOGV("RXed packet with unhandled TRTP header type (%u).",
+                    header_type);
+            return;
+    }
+
+    if (substream_details_known_ && (header_type != substream_type_)) {
+        LOGV("RXed TRTP Payload for SSRC=0x%08x where header type (%u) does not"
+                " match previously received header type (%u)",
+                ssrc_, header_type, substream_type_);
+        return;
+    }
+
+    // Check the flags to see if there is another 32 bits of timestamp present.
+    uint32_t trtp_header_len = 6;
+    bool ts_valid = buf[1] & 0x1;
+    if (ts_valid) {
+        min_length += 4;
+        trtp_header_len += 4;
+        if (amt < min_length) {
+            LOGV("Discarding payload too short to contain TRTP timestamp"
+                 " (len = %u)", amt);
+            return;
+        }
+    }
+
+    // Extract the TRTP length field and sanity check it.
+    uint32_t trtp_len = U32_AT(buf + 2);
+    if (trtp_len < min_length) {
+        LOGV("TRTP length (%u) is too short to be valid.  Must be at least %u"
+                " bytes.", trtp_len, min_length);
+        return;
+    }
+
+    // Extract the rest of the timestamp field if valid.
+    int64_t ts = 0;
+    uint32_t parse_offset = 6;
+    if (ts_valid) {
+        uint32_t ts_upper = U32_AT(buf + parse_offset);
+        parse_offset += 4;
+        ts = (static_cast<int64_t>(ts_upper) << 32) | ts_lower;
+    }
+
+    // Check the flags to see if there is another 24 bytes of timestamp
+    // transformation present.
+    if (buf[1] & 0x2) {
+        min_length += 24;
+        parse_offset += 24;
+        trtp_header_len += 24;
+        if (amt < min_length) {
+            LOGV("Discarding payload too short to contain TRTP timestamp"
+                    " transformation (len = %u)", amt);
+            return;
+        }
+    }
+
+    // TODO : break the parsing into individual parsers for the different
+    // payload types (audio, video, etc).
+    //
+    // At this point in time, we know that this is audio.  Go ahead and parse
+    // the basic header, check the codec type, and find the payload portion of
+    // the packet.
+    min_length += 3;
+    if (trtp_len < min_length) {
+        LOGV("TRTP length (%u) is too short to be a valid audio payload.  Must"
+             " be at least %u bytes.", trtp_len, min_length);
+        return;
+    }
+
+    if (amt < min_length) {
+        LOGV("TRTP porttion of RTP payload (%u bytes) too small to contain"
+             " entire TRTP header.  TRTP does not currently support fragmenting"
+             " TRTP headers across RTP payloads", amt);
+        return;
+    }
+
+    uint8_t codec_type = buf[parse_offset    ];
+    uint8_t flags      = buf[parse_offset + 1];
+    uint8_t volume     = buf[parse_offset + 2];
+    parse_offset += 3;
+    trtp_header_len += 3;
+
+    if (!setupSubstreamType(header_type, codec_type)) {
+        return;
+    }
+
+    if (decoder_ != NULL) {
+        decoder_->setRenderVolume(volume);
+    }
+
+    // TODO : move all of the constant flag and offset definitions for TRTP up
+    // into some sort of common header file.
+    if (waiting_for_rap_ && !(flags & 0x08)) {
+        LOGV("Dropping non-RAP TRTP Audio Payload while waiting for RAP.");
+        return;
+    }
+
+    // Check for the presence of codec aux data.
+    if (flags & 0x10) {
+        min_length += 4;
+        trtp_header_len += 4;
+
+        if (trtp_len < min_length) {
+            LOGV("TRTP length (%u) is too short to be a valid audio payload.  "
+                 "Must be at least %u bytes.", trtp_len, min_length);
+            return;
+        }
+
+        if (amt < min_length) {
+            LOGV("TRTP porttion of RTP payload (%u bytes) too small to contain"
+                 " entire TRTP header.  TRTP does not currently support"
+                 " fragmenting TRTP headers across RTP payloads", amt);
+            return;
+        }
+
+        aux_data_expected_size_ = U32_AT(buf + parse_offset);
+        aux_data_in_progress_.clear();
+        if (aux_data_in_progress_.capacity() < aux_data_expected_size_) {
+            aux_data_in_progress_.setCapacity(aux_data_expected_size_);
+        }
+    } else {
+        aux_data_expected_size_ = 0;
+    }
+
+    if ((aux_data_expected_size_ + trtp_header_len) > trtp_len) {
+        LOGV("Expected codec aux data length (%u) and TRTP header overhead (%u)"
+             " too large for total TRTP payload length (%u).",
+             aux_data_expected_size_, trtp_header_len, trtp_len);
+        return;
+    }
+
+    // OK - everything left is just payload.  Compute the payload size, start
+    // the buffer in progress and pack as much payload as we can into it.  If
+    // the payload is finished once we are done, go ahead and send the payload
+    // to the decoder.
+    expected_buffer_size_ = trtp_len
+                          - trtp_header_len
+                          - aux_data_expected_size_;
+    if (!expected_buffer_size_) {
+        LOGV("Dropping TRTP Audio Payload with 0 Access Unit length");
+        return;
+    }
+
+    CHECK(amt >= trtp_header_len);
+    uint32_t todo = amt - trtp_header_len;
+    if ((expected_buffer_size_ + aux_data_expected_size_) < todo) {
+        LOGV("Extra data (%u > %u) present in initial TRTP Audio Payload;"
+             " dropping payload.", todo,
+             expected_buffer_size_ + aux_data_expected_size_);
+        return;
+    }
+
+    buffer_filled_ = 0;
+    buffer_in_progress_ = new MediaBuffer(expected_buffer_size_);
+    if ((NULL == buffer_in_progress_) ||
+            (NULL == buffer_in_progress_->data())) {
+        LOGV("Failed to allocate MediaBuffer of length %u",
+                expected_buffer_size_);
+        cleanupBufferInProgress();
+        return;
+    }
+
+    sp<MetaData> meta = buffer_in_progress_->meta_data();
+    if (meta == NULL) {
+        LOGV("Missing metadata structure in allocated MediaBuffer; dropping"
+                " payload");
+        cleanupBufferInProgress();
+        return;
+    }
+
+    // TODO : set this based on the codec type indicated in the TRTP stream.
+    // Right now, we only support MP3, so the choice is obvious.
+    meta->setCString(kKeyMIMEType, codec_mime_type_);
+    if (ts_valid) {
+        meta->setInt64(kKeyTime, ts);
+    }
+
+    // Skip over the header we have already extracted.
+    amt -= trtp_header_len;
+    buf += trtp_header_len;
+
+    // Extract as much of the expected aux data as we can.
+    todo = MIN(aux_data_expected_size_, amt);
+    if (todo) {
+        aux_data_in_progress_.appendArray(buf, todo);
+        buf += todo;
+        amt -= todo;
+    }
+
+    // Extract as much of the expected payload as we can.
+    todo = MIN(expected_buffer_size_, amt);
+    if (todo > 0) {
+        uint8_t* tgt =
+            reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
+        memcpy(tgt, buf, todo);
+        buffer_filled_ = amt;
+        buf += todo;
+        amt -= todo;
+    }
+
+    if (buffer_filled_ >= expected_buffer_size_) {
+        processCompletedBuffer();
+    }
+}
+
+void AAH_RXPlayer::Substream::processPayloadCont(uint8_t* buf,
+                                                 uint32_t amt) {
+    if (shouldAbort(__PRETTY_FUNCTION__)) {
+        return;
+    }
+
+    if (NULL == buffer_in_progress_) {
+        LOGV("TRTP Receiver skipping payload continuation; no buffer currently"
+             " in progress.");
+        return;
+    }
+
+    CHECK(aux_data_in_progress_.size() <= aux_data_expected_size_);
+    uint32_t aux_left = aux_data_expected_size_ - aux_data_in_progress_.size();
+    if (aux_left) {
+        uint32_t todo = MIN(aux_left, amt);
+        aux_data_in_progress_.appendArray(buf, todo);
+        amt -= todo;
+        buf += todo;
+
+        if (!amt)
+            return;
+    }
+
+    CHECK(buffer_filled_ < expected_buffer_size_);
+    uint32_t buffer_left = expected_buffer_size_ - buffer_filled_;
+    if (amt > buffer_left) {
+        LOGV("Extra data (%u > %u) present in continued TRTP Audio Payload;"
+                " dropping payload.", amt, buffer_left);
+        cleanupBufferInProgress();
+        return;
+    }
+
+    if (amt > 0) {
+        uint8_t* tgt =
+            reinterpret_cast<uint8_t*>(buffer_in_progress_->data());
+        memcpy(tgt + buffer_filled_, buf, amt);
+        buffer_filled_ += amt;
+    }
+
+    if (buffer_filled_ >= expected_buffer_size_) {
+        processCompletedBuffer();
+    }
+}
+
+void AAH_RXPlayer::Substream::processCompletedBuffer() {
+    status_t res;
+
+    CHECK(NULL != buffer_in_progress_);
+
+    if (decoder_ == NULL) {
+        LOGV("Dropping complete buffer, no decoder pump allocated");
+        goto bailout;
+    }
+
+    // Make sure our metadata used to initialize the decoder has been properly
+    // set up.
+    if (!setupSubstreamMeta())
+        goto bailout;
+
+    // If our decoder has not be set up, do so now.
+    res = decoder_->init(substream_meta_);
+    if (OK != res) {
+        LOGE("Failed to init decoder (res = %d)", res);
+        cleanupDecoder();
+        substream_meta_ = NULL;
+        goto bailout;
+    }
+
+    // Queue the payload for decode.
+    res = decoder_->queueForDecode(buffer_in_progress_);
+
+    if (res != OK) {
+        LOGD("Failed to queue payload for decode, resetting decoder pump!"
+             " (res = %d)", res);
+        status_ = res;
+        cleanupDecoder();
+        cleanupBufferInProgress();
+    }
+
+    // NULL out buffer_in_progress before calling the cleanup helper.
+    //
+    // MediaBuffers use something of a hybrid ref-counting pattern which prevent
+    // the AAH_DecoderPump's input queue from adding their own reference to the
+    // MediaBuffer.  MediaBuffers start life with a reference count of 0, as
+    // well as an observer which starts as NULL.  Before being given an
+    // observer, the ref count cannot be allowed to become non-zero as it will
+    // cause calls to release() to assert.  Basically, before a MediaBuffer has
+    // an observer, they behave like non-ref counted obects where release()
+    // serves the roll of delete.  After a MediaBuffer has an observer, they
+    // become more like ref counted objects where add ref and release can be
+    // used, and when the ref count hits zero, the MediaBuffer is handed off to
+    // the observer.
+    //
+    // Given all of this, when we give the buffer to the decoder pump to wait in
+    // the to-be-processed queue, the decoder cannot add a ref to the buffer as
+    // it would in a traditional ref counting system.  Instead it needs to
+    // "steal" the non-existent ref.  In the case of queue failure, we need to
+    // make certain to release this non-existent reference so that the buffer is
+    // cleaned up during the cleanupBufferInProgress helper.  In the case of a
+    // successful queue operation, we need to make certain that the
+    // cleanupBufferInProgress helper does not release the buffer since it needs
+    // to remain alive in the queue.  We acomplish this by NULLing out the
+    // buffer pointer before calling the cleanup helper.
+    buffer_in_progress_ = NULL;
+
+bailout:
+    cleanupBufferInProgress();
+}
+
+bool AAH_RXPlayer::Substream::setupSubstreamMeta() {
+    switch (codec_type_) {
+        case TRTPAudioPacket::kCodecMPEG1Audio:
+            codec_mime_type_ = MEDIA_MIMETYPE_AUDIO_MPEG;
+            return setupMP3SubstreamMeta();
+
+        case TRTPAudioPacket::kCodecAACAudio:
+            codec_mime_type_ = MEDIA_MIMETYPE_AUDIO_AAC;
+            return setupAACSubstreamMeta();
+
+        default:
+            LOGV("Failed to setup substream metadata for unsupported codec type"
+                 " (%u)", codec_type_);
+            break;
+    }
+
+    return false;
+}
+
+bool AAH_RXPlayer::Substream::setupMP3SubstreamMeta() {
+    const uint8_t* buffer_data = NULL;
+    int sample_rate;
+    int channel_count;
+    size_t frame_size;
+    status_t res;
+
+    buffer_data = reinterpret_cast<const uint8_t*>(buffer_in_progress_->data());
+    if (buffer_in_progress_->size() < 4) {
+        LOGV("MP3 payload too short to contain header, dropping payload.");
+        return false;
+    }
+
+    // Extract the channel count and the sample rate from the MP3 header.  The
+    // stagefright MP3 requires that these be delivered before decoing can
+    // begin.
+    if (!GetMPEGAudioFrameSize(U32_AT(buffer_data),
+                               &frame_size,
+                               &sample_rate,
+                               &channel_count,
+                               NULL,
+                               NULL)) {
+        LOGV("Failed to parse MP3 header in payload, droping payload.");
+        return false;
+    }
+
+
+    // Make sure that our substream metadata is set up properly.  If there has
+    // been a format change, be sure to reset the underlying decoder.  In
+    // stagefright, it seems like the only way to do this is to destroy and
+    // recreate the decoder.
+    if (substream_meta_ == NULL) {
+        substream_meta_ = new MetaData();
+
+        if (substream_meta_ == NULL) {
+            LOGE("Failed to allocate MetaData structure for MP3 substream");
+            return false;
+        }
+
+        substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG);
+        substream_meta_->setInt32  (kKeyChannelCount, channel_count);
+        substream_meta_->setInt32  (kKeySampleRate,   sample_rate);
+    } else {
+        int32_t prev_sample_rate;
+        int32_t prev_channel_count;
+        substream_meta_->findInt32(kKeySampleRate,   &prev_sample_rate);
+        substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count);
+
+        if ((prev_channel_count != channel_count) ||
+            (prev_sample_rate   != sample_rate)) {
+            LOGW("MP3 format change detected, forcing decoder reset.");
+            cleanupDecoder();
+
+            substream_meta_->setInt32(kKeyChannelCount, channel_count);
+            substream_meta_->setInt32(kKeySampleRate,   sample_rate);
+        }
+    }
+
+    return true;
+}
+
+bool AAH_RXPlayer::Substream::setupAACSubstreamMeta() {
+    int32_t sample_rate, channel_cnt;
+    static const size_t overhead = sizeof(sample_rate)
+                                 + sizeof(channel_cnt);
+
+    if (aux_data_in_progress_.size() < overhead) {
+        LOGE("Not enough aux data (%u) to initialize AAC substream decoder",
+                aux_data_in_progress_.size());
+        return false;
+    }
+
+    const uint8_t* aux_data = aux_data_in_progress_.array();
+    size_t aux_data_size = aux_data_in_progress_.size();
+    sample_rate = U32_AT(aux_data);
+    channel_cnt = U32_AT(aux_data + sizeof(sample_rate));
+
+    const uint8_t* esds_data = NULL;
+    size_t esds_data_size = 0;
+    if (aux_data_size > overhead) {
+        esds_data = aux_data + overhead;
+        esds_data_size = aux_data_size - overhead;
+    }
+
+    // Do we already have metadata?  If so, has it changed at all?  If not, then
+    // there should be nothing else to do.  Otherwise, release our old stream
+    // metadata and make new metadata.
+    if (substream_meta_ != NULL) {
+        uint32_t type;
+        const void* data;
+        size_t size;
+        int32_t prev_sample_rate;
+        int32_t prev_channel_count;
+
+        substream_meta_->findInt32(kKeySampleRate,   &prev_sample_rate);
+        substream_meta_->findInt32(kKeyChannelCount, &prev_channel_count);
+
+        // If nothing has changed about the codec aux data (esds, sample rate,
+        // channel count), then we can just do nothing and get out.  Otherwise,
+        // we will need to reset the decoder and make a new metadata object to
+        // deal with the format change.
+        bool hasData = (esds_data != NULL);
+        bool hadData = substream_meta_->findData(kKeyESDS, &type, &data, &size);
+        bool esds_change = (hadData != hasData);
+
+        if (!esds_change && hasData)
+            esds_change = ((size != esds_data_size) ||
+                           memcmp(data, esds_data, size));
+
+        if (!esds_change &&
+            (prev_sample_rate   == sample_rate) &&
+            (prev_channel_count == channel_cnt)) {
+            return true;  // no change, just get out.
+        }
+
+        LOGW("AAC format change detected, forcing decoder reset.");
+        cleanupDecoder();
+        substream_meta_ = NULL;
+    }
+
+    CHECK(substream_meta_ == NULL);
+
+    substream_meta_ = new MetaData();
+    if (substream_meta_ == NULL) {
+        LOGE("Failed to allocate MetaData structure for AAC substream");
+        return false;
+    }
+
+    substream_meta_->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
+    substream_meta_->setInt32  (kKeySampleRate,   sample_rate);
+    substream_meta_->setInt32  (kKeyChannelCount, channel_cnt);
+
+    if (esds_data) {
+        substream_meta_->setData(kKeyESDS, kTypeESDS,
+                                 esds_data, esds_data_size);
+    }
+
+    return true;
+}
+
+void AAH_RXPlayer::Substream::processTSTransform(const LinearTransform& trans) {
+    if (decoder_ != NULL) {
+        decoder_->setRenderTSTransform(trans);
+    }
+}
+
+bool AAH_RXPlayer::Substream::isAboutToUnderflow() {
+    if (decoder_ == NULL) {
+        return false;
+    }
+
+    return decoder_->isAboutToUnderflow(kAboutToUnderflowThreshold);
+}
+
+bool AAH_RXPlayer::Substream::setupSubstreamType(uint8_t substream_type,
+                                                 uint8_t codec_type) {
+    // Sanity check the codec type.  Right now we only support MP3 and AAC.
+    // Also check for conflicts with previously delivered codec types.
+    if (substream_details_known_) {
+        if (codec_type != codec_type_) {
+            LOGV("RXed TRTP Payload for SSRC=0x%08x where codec type (%u) does "
+                 "not match previously received codec type (%u)",
+                 ssrc_, codec_type, codec_type_);
+            return false;
+        }
+
+        return true;
+    }
+
+    switch (codec_type) {
+        // MP3 and AAC are all we support right now.
+        case TRTPAudioPacket::kCodecMPEG1Audio:
+        case TRTPAudioPacket::kCodecAACAudio:
+            break;
+
+        default:
+            LOGV("RXed TRTP Audio Payload for SSRC=0x%08x with unsupported"
+                 " codec type (%u)", ssrc_, codec_type);
+            return false;
+    }
+
+    substream_type_ = substream_type;
+    codec_type_ = codec_type;
+    substream_details_known_ = true;
+
+    return true;
+}
+
+}  // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.cpp b/media/libaah_rtp/aah_tx_packet.cpp
new file mode 100644
index 0000000..c7ad3e0
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_packet.cpp
@@ -0,0 +1,344 @@
+/*
+ * 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 "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <string.h>
+
+#include <media/stagefright/MediaDebug.h>
+
+#include "aah_tx_packet.h"
+
+namespace android {
+
+const int TRTPPacket::kRTPHeaderLen;
+const uint32_t TRTPPacket::kTRTPEpochMask;
+
+TRTPPacket::~TRTPPacket() {
+    delete mPacket;
+}
+
+/*** TRTP packet properties ***/
+
+void TRTPPacket::setSeqNumber(uint16_t val) {
+    mSeqNumber = val;
+
+    if (mIsPacked) {
+        const int kTRTPSeqNumberOffset = 2;
+        uint16_t* buf = reinterpret_cast<uint16_t*>(
+            mPacket + kTRTPSeqNumberOffset);
+        *buf = htons(mSeqNumber);
+    }
+}
+
+uint16_t TRTPPacket::getSeqNumber() const {
+    return mSeqNumber;
+}
+
+void TRTPPacket::setPTS(int64_t val) {
+    CHECK(!mIsPacked);
+    mPTS = val;
+    mPTSValid = true;
+}
+
+int64_t TRTPPacket::getPTS() const {
+    return mPTS;
+}
+
+void TRTPPacket::setEpoch(uint32_t val) {
+    mEpoch = val;
+
+    if (mIsPacked) {
+        const int kTRTPEpochOffset = 8;
+        uint32_t* buf = reinterpret_cast<uint32_t*>(
+            mPacket + kTRTPEpochOffset);
+        uint32_t val = ntohl(*buf);
+        val &= ~(kTRTPEpochMask << kTRTPEpochShift);
+        val |= (mEpoch & kTRTPEpochMask) << kTRTPEpochShift;
+        *buf = htonl(val);
+    }
+}
+
+void TRTPPacket::setProgramID(uint16_t val) {
+    CHECK(!mIsPacked);
+    mProgramID = val;
+}
+
+void TRTPPacket::setSubstreamID(uint16_t val) {
+    CHECK(!mIsPacked);
+    mSubstreamID = val;
+}
+
+
+void TRTPPacket::setClockTransform(const LinearTransform& trans) {
+    CHECK(!mIsPacked);
+    mClockTranform = trans;
+    mClockTranformValid = true;
+}
+
+uint8_t* TRTPPacket::getPacket() const {
+    CHECK(mIsPacked);
+    return mPacket;
+}
+
+int TRTPPacket::getPacketLen() const {
+    CHECK(mIsPacked);
+    return mPacketLen;
+}
+
+void TRTPPacket::setExpireTime(nsecs_t val) {
+    CHECK(!mIsPacked);
+    mExpireTime = val;
+}
+
+nsecs_t TRTPPacket::getExpireTime() const {
+    return mExpireTime;
+}
+
+/*** TRTP audio packet properties ***/
+
+void TRTPAudioPacket::setCodecType(TRTPAudioCodecType val) {
+    CHECK(!mIsPacked);
+    mCodecType = val;
+}
+
+void TRTPAudioPacket::setRandomAccessPoint(bool val) {
+    CHECK(!mIsPacked);
+    mRandomAccessPoint = val;
+}
+
+void TRTPAudioPacket::setDropable(bool val) {
+    CHECK(!mIsPacked);
+    mDropable = val;
+}
+
+void TRTPAudioPacket::setDiscontinuity(bool val) {
+    CHECK(!mIsPacked);
+    mDiscontinuity = val;
+}
+
+void TRTPAudioPacket::setEndOfStream(bool val) {
+    CHECK(!mIsPacked);
+    mEndOfStream = val;
+}
+
+void TRTPAudioPacket::setVolume(uint8_t val) {
+    CHECK(!mIsPacked);
+    mVolume = val;
+}
+
+void TRTPAudioPacket::setAccessUnitData(const void* data, size_t len) {
+    CHECK(!mIsPacked);
+    mAccessUnitData = data;
+    mAccessUnitLen = len;
+}
+
+void TRTPAudioPacket::setAuxData(const void* data, size_t len) {
+    CHECK(!mIsPacked);
+    mAuxData = data;
+    mAuxDataLen = len;
+}
+
+/*** TRTP control packet properties ***/
+
+void TRTPControlPacket::setCommandID(TRTPCommandID val) {
+    CHECK(!mIsPacked);
+    mCommandID = val;
+}
+
+/*** TRTP packet serializers ***/
+
+void TRTPPacket::writeU8(uint8_t*& buf, uint8_t val) {
+    *buf = val;
+    buf++;
+}
+
+void TRTPPacket::writeU16(uint8_t*& buf, uint16_t val) {
+    *reinterpret_cast<uint16_t*>(buf) = htons(val);
+    buf += 2;
+}
+
+void TRTPPacket::writeU32(uint8_t*& buf, uint32_t val) {
+    *reinterpret_cast<uint32_t*>(buf) = htonl(val);
+    buf += 4;
+}
+
+void TRTPPacket::writeU64(uint8_t*& buf, uint64_t val) {
+    buf[0] = static_cast<uint8_t>(val >> 56);
+    buf[1] = static_cast<uint8_t>(val >> 48);
+    buf[2] = static_cast<uint8_t>(val >> 40);
+    buf[3] = static_cast<uint8_t>(val >> 32);
+    buf[4] = static_cast<uint8_t>(val >> 24);
+    buf[5] = static_cast<uint8_t>(val >> 16);
+    buf[6] = static_cast<uint8_t>(val >>  8);
+    buf[7] = static_cast<uint8_t>(val);
+    buf += 8;
+}
+
+void TRTPPacket::writeTRTPHeader(uint8_t*& buf,
+                                 bool isFirstFragment,
+                                 int totalPacketLen) {
+    // RTP header
+    writeU8(buf,
+            ((mVersion & 0x03) << 6) |
+            (static_cast<int>(mPadding) << 5) |
+            (static_cast<int>(mExtension) << 4) |
+            (mCsrcCount & 0x0F));
+    writeU8(buf,
+            (static_cast<int>(isFirstFragment) << 7) |
+            (mPayloadType & 0x7F));
+    writeU16(buf, mSeqNumber);
+    if (isFirstFragment && mPTSValid) {
+        writeU32(buf, mPTS & 0xFFFFFFFF);
+    } else {
+        writeU32(buf, 0);
+    }
+    writeU32(buf,
+            ((mEpoch & kTRTPEpochMask) << kTRTPEpochShift) |
+            ((mProgramID & 0x1F) << 5) |
+            (mSubstreamID & 0x1F));
+
+    // TRTP header
+    writeU8(buf, mTRTPVersion);
+    writeU8(buf,
+            ((mTRTPHeaderType & 0x0F) << 4) |
+            (mClockTranformValid ? 0x02 : 0x00) |
+            (mPTSValid ? 0x01 : 0x00));
+    writeU32(buf, totalPacketLen - kRTPHeaderLen);
+    if (mPTSValid) {
+        writeU32(buf, mPTS >> 32);
+    }
+
+    if (mClockTranformValid) {
+        writeU64(buf, mClockTranform.a_zero);
+        writeU32(buf, mClockTranform.a_to_b_numer);
+        writeU32(buf, mClockTranform.a_to_b_denom);
+        writeU64(buf, mClockTranform.b_zero);
+    }
+}
+
+bool TRTPAudioPacket::pack() {
+    if (mIsPacked) {
+        return false;
+    }
+
+    int packetLen = kRTPHeaderLen +
+                    mAuxDataLen +
+                    mAccessUnitLen +
+                    TRTPHeaderLen();
+
+    // TODO : support multiple fragments
+    const int kMaxUDPPayloadLen = 65507;
+    if (packetLen > kMaxUDPPayloadLen) {
+        return false;
+    }
+
+    mPacket = new uint8_t[packetLen];
+    if (!mPacket) {
+        return false;
+    }
+
+    mPacketLen = packetLen;
+
+    uint8_t* cur = mPacket;
+    bool hasAux = mAuxData && mAuxDataLen;
+    uint8_t flags = (static_cast<int>(hasAux) << 4) |
+                    (static_cast<int>(mRandomAccessPoint) << 3) |
+                    (static_cast<int>(mDropable) << 2) |
+                    (static_cast<int>(mDiscontinuity) << 1) |
+                    (static_cast<int>(mEndOfStream));
+
+    writeTRTPHeader(cur, true, packetLen);
+    writeU8(cur, mCodecType);
+    writeU8(cur, flags);
+    writeU8(cur, mVolume);
+
+    if (hasAux) {
+        writeU32(cur, mAuxDataLen);
+        memcpy(cur, mAuxData, mAuxDataLen);
+        cur += mAuxDataLen;
+    }
+
+    memcpy(cur, mAccessUnitData, mAccessUnitLen);
+
+    mIsPacked = true;
+    return true;
+}
+
+int TRTPPacket::TRTPHeaderLen() const {
+    // 6 bytes for version, payload type, flags and length.  An additional 4 if
+    // there are upper timestamp bits present and another 24 if there is a clock
+    // transformation present.
+    return 6 +
+           (mClockTranformValid ? 24 : 0) +
+           (mPTSValid ? 4 : 0);
+}
+
+int TRTPAudioPacket::TRTPHeaderLen() const {
+    // TRTPPacket::TRTPHeaderLen() for the base TRTPHeader.  3 bytes for audio's
+    // codec type, flags and volume field.  Another 5 bytes if the codec type is
+    // PCM and we are sending sample rate/channel count. as well as however long
+    // the aux data (if present) is.
+
+    int pcmParamLength;
+    switch(mCodecType) {
+        case kCodecPCMBigEndian:
+        case kCodecPCMLittleEndian:
+            pcmParamLength = 5;
+            break;
+
+        default:
+            pcmParamLength = 0;
+            break;
+    }
+
+
+    int auxDataLenField = (NULL != mAuxData) ? sizeof(uint32_t) : 0;
+    return TRTPPacket::TRTPHeaderLen() +
+           3 +
+           auxDataLenField +
+           pcmParamLength;
+}
+
+bool TRTPControlPacket::pack() {
+    if (mIsPacked) {
+        return false;
+    }
+
+    // command packets contain a 2-byte command ID
+    int packetLen = kRTPHeaderLen +
+                    TRTPHeaderLen() +
+                    2;
+
+    mPacket = new uint8_t[packetLen];
+    if (!mPacket) {
+        return false;
+    }
+
+    mPacketLen = packetLen;
+
+    uint8_t* cur = mPacket;
+
+    writeTRTPHeader(cur, true, packetLen);
+    writeU16(cur, mCommandID);
+
+    mIsPacked = true;
+    return true;
+}
+
+}  // namespace android
diff --git a/media/libaah_rtp/aah_tx_packet.h b/media/libaah_rtp/aah_tx_packet.h
new file mode 100644
index 0000000..62436a0
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_packet.h
@@ -0,0 +1,191 @@
+/*
+ * 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.
+ */
+
+#ifndef __AAH_TX_PACKET_H__
+#define __AAH_TX_PACKET_H__
+
+#include <utils/LinearTransform.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+class TRTPPacket : public RefBase {
+  protected:
+    enum TRTPHeaderType {
+        kHeaderTypeAudio = 1,
+        kHeaderTypeVideo = 2,
+        kHeaderTypeSubpicture = 3,
+        kHeaderTypeControl = 4,
+    };
+
+    TRTPPacket(TRTPHeaderType headerType)
+        : mIsPacked(false)
+        , mVersion(2)
+        , mPadding(false)
+        , mExtension(false)
+        , mCsrcCount(0)
+        , mPayloadType(100)
+        , mSeqNumber(0)
+        , mPTSValid(false)
+        , mPTS(0)
+        , mEpoch(0)
+        , mProgramID(0)
+        , mSubstreamID(0)
+        , mClockTranformValid(false)
+        , mTRTPVersion(1)
+        , mTRTPLength(0)
+        , mTRTPHeaderType(headerType)
+        , mPacket(NULL)
+        , mPacketLen(0) { }
+
+  public:
+    virtual ~TRTPPacket();
+
+    void setSeqNumber(uint16_t val);
+    uint16_t getSeqNumber() const;
+
+    void setPTS(int64_t val);
+    int64_t getPTS() const;
+
+    void setEpoch(uint32_t val);
+    void setProgramID(uint16_t val);
+    void setSubstreamID(uint16_t val);
+    void setClockTransform(const LinearTransform& trans);
+
+    uint8_t* getPacket() const;
+    int getPacketLen() const;
+
+    void setExpireTime(nsecs_t val);
+    nsecs_t getExpireTime() const;
+
+    virtual bool pack() = 0;
+
+    // mask for the number of bits in a TRTP epoch
+    static const uint32_t kTRTPEpochMask = (1 << 22) - 1;
+    static const int kTRTPEpochShift = 10;
+
+  protected:
+    static const int kRTPHeaderLen = 12;
+    virtual int TRTPHeaderLen() const;
+
+    void writeTRTPHeader(uint8_t*& buf,
+                         bool isFirstFragment,
+                         int totalPacketLen);
+
+    void writeU8(uint8_t*& buf, uint8_t val);
+    void writeU16(uint8_t*& buf, uint16_t val);
+    void writeU32(uint8_t*& buf, uint32_t val);
+    void writeU64(uint8_t*& buf, uint64_t val);
+
+    bool mIsPacked;
+
+    uint8_t mVersion;
+    bool mPadding;
+    bool mExtension;
+    uint8_t mCsrcCount;
+    uint8_t mPayloadType;
+    uint16_t mSeqNumber;
+    bool mPTSValid;
+    int64_t  mPTS;
+    uint32_t mEpoch;
+    uint16_t mProgramID;
+    uint16_t mSubstreamID;
+    LinearTransform mClockTranform;
+    bool mClockTranformValid;
+    uint8_t mTRTPVersion;
+    uint32_t mTRTPLength;
+    TRTPHeaderType mTRTPHeaderType;
+
+    uint8_t* mPacket;
+    int mPacketLen;
+
+    nsecs_t mExpireTime;
+};
+
+class TRTPAudioPacket : public TRTPPacket {
+  public:
+    TRTPAudioPacket()
+        : TRTPPacket(kHeaderTypeAudio)
+        , mCodecType(kCodecInvalid)
+        , mRandomAccessPoint(false)
+        , mDropable(false)
+        , mDiscontinuity(false)
+        , mEndOfStream(false)
+        , mVolume(0)
+        , mAccessUnitData(NULL)
+        , mAccessUnitLen(0)
+        , mAuxData(NULL)
+        , mAuxDataLen(0) { }
+
+    enum TRTPAudioCodecType {
+        kCodecInvalid = 0,
+        kCodecPCMBigEndian = 1,
+        kCodecPCMLittleEndian = 2,
+        kCodecMPEG1Audio = 3,
+        kCodecAACAudio = 4,
+    };
+
+    void setCodecType(TRTPAudioCodecType val);
+    void setRandomAccessPoint(bool val);
+    void setDropable(bool val);
+    void setDiscontinuity(bool val);
+    void setEndOfStream(bool val);
+    void setVolume(uint8_t val);
+    void setAccessUnitData(const void* data, size_t len);
+    void setAuxData(const void* data, size_t len);
+
+    virtual bool pack();
+
+  protected:
+    virtual int TRTPHeaderLen() const;
+
+  private:
+    TRTPAudioCodecType mCodecType;
+    bool mRandomAccessPoint;
+    bool mDropable;
+    bool mDiscontinuity;
+    bool mEndOfStream;
+    uint8_t mVolume;
+    const void* mAccessUnitData;
+    size_t mAccessUnitLen;
+    const void* mAuxData;
+    size_t mAuxDataLen;
+};
+
+class TRTPControlPacket : public TRTPPacket {
+  public:
+    TRTPControlPacket()
+        : TRTPPacket(kHeaderTypeControl)
+        , mCommandID(kCommandNop) {}
+
+    enum TRTPCommandID {
+        kCommandNop   = 1,
+        kCommandFlush = 2,
+        kCommandEOS   = 3,
+    };
+
+    void setCommandID(TRTPCommandID val);
+
+    virtual bool pack();
+
+  private:
+    TRTPCommandID mCommandID;
+};
+
+}  // namespace android
+
+#endif  // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_player.cpp b/media/libaah_rtp/aah_tx_player.cpp
new file mode 100644
index 0000000..6328115
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.cpp
@@ -0,0 +1,1177 @@
+/*
+ * 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 "LibAAH_RTP"
+#include <utils/Log.h>
+
+#define __STDC_FORMAT_MACROS
+#include <inttypes.h>
+#include <netdb.h>
+#include <netinet/ip.h>
+
+#include <common_time/cc_helper.h>
+#include <media/IMediaPlayer.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/FileSource.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaDebug.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/MetaData.h>
+#include <utils/Timers.h>
+
+#include "aah_tx_packet.h"
+#include "aah_tx_player.h"
+
+namespace android {
+
+static int64_t kLowWaterMarkUs = 2000000ll;  // 2secs
+static int64_t kHighWaterMarkUs = 10000000ll;  // 10secs
+static const size_t kLowWaterMarkBytes = 40000;
+static const size_t kHighWaterMarkBytes = 200000;
+
+// When we start up, how much lead time should we put on the first access unit?
+static const int64_t kAAHStartupLeadTimeUs = 300000LL;
+
+// How much time do we attempt to lead the clock by in steady state?
+static const int64_t kAAHBufferTimeUs = 1000000LL;
+
+// how long do we keep data in our retransmit buffer after sending it.
+const int64_t AAH_TXPlayer::kAAHRetryKeepAroundTimeNs =
+    kAAHBufferTimeUs * 1100;
+
+sp<MediaPlayerBase> createAAH_TXPlayer() {
+    sp<MediaPlayerBase> ret = new AAH_TXPlayer();
+    return ret;
+}
+
+template <typename T> static T clamp(T val, T min, T max) {
+    if (val < min) {
+        return min;
+    } else if (val > max) {
+        return max;
+    } else {
+        return val;
+    }
+}
+
+struct AAH_TXEvent : public TimedEventQueue::Event {
+    AAH_TXEvent(AAH_TXPlayer *player,
+                void (AAH_TXPlayer::*method)()) : mPlayer(player)
+                                                , mMethod(method) {}
+
+  protected:
+    virtual ~AAH_TXEvent() {}
+
+    virtual void fire(TimedEventQueue *queue, int64_t /* now_us */) {
+        (mPlayer->*mMethod)();
+    }
+
+  private:
+    AAH_TXPlayer *mPlayer;
+    void (AAH_TXPlayer::*mMethod)();
+
+    AAH_TXEvent(const AAH_TXEvent &);
+    AAH_TXEvent& operator=(const AAH_TXEvent &);
+};
+
+AAH_TXPlayer::AAH_TXPlayer()
+        : mQueueStarted(false)
+        , mFlags(0)
+        , mExtractorFlags(0) {
+    DataSource::RegisterDefaultSniffers();
+
+    mBufferingEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onBufferingUpdate);
+    mBufferingEventPending = false;
+
+    mPumpAudioEvent = new AAH_TXEvent(this, &AAH_TXPlayer::onPumpAudio);
+    mPumpAudioEventPending = false;
+
+    mAudioCodecData = NULL;
+
+    reset();
+}
+
+AAH_TXPlayer::~AAH_TXPlayer() {
+    if (mQueueStarted) {
+        mQueue.stop();
+    }
+
+    reset();
+}
+
+void AAH_TXPlayer::cancelPlayerEvents(bool keepBufferingGoing) {
+    if (!keepBufferingGoing) {
+        mQueue.cancelEvent(mBufferingEvent->eventID());
+        mBufferingEventPending = false;
+
+        mQueue.cancelEvent(mPumpAudioEvent->eventID());
+        mPumpAudioEventPending = false;
+    }
+}
+
+status_t AAH_TXPlayer::initCheck() {
+    // Check for the presense of the common time service by attempting to query
+    // for CommonTime's frequency.  If we get an error back, we cannot talk to
+    // the service at all and should abort now.
+    status_t res;
+    uint64_t freq;
+    res = mCCHelper.getCommonFreq(&freq);
+    if (OK != res) {
+        LOGE("Failed to connect to common time service! (res %d)", res);
+        return res;
+    }
+
+    return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(
+        const char *url,
+        const KeyedVector<String8, String8> *headers) {
+    Mutex::Autolock autoLock(mLock);
+    return setDataSource_l(url, headers);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(
+        const char *url,
+        const KeyedVector<String8, String8> *headers) {
+    reset_l();
+
+    mUri.setTo(url);
+
+    if (headers) {
+        mUriHeaders = *headers;
+
+        ssize_t index = mUriHeaders.indexOfKey(String8("x-hide-urls-from-log"));
+        if (index >= 0) {
+            // Browser is in "incognito" mode, suppress logging URLs.
+
+            // This isn't something that should be passed to the server.
+            mUriHeaders.removeItemsAt(index);
+
+            mFlags |= INCOGNITO;
+        }
+    }
+
+    // The URL may optionally contain a "#" character followed by a Skyjam
+    // cookie.  Ideally the cookie header should just be passed in the headers
+    // argument, but the Java API for supplying headers is apparently not yet
+    // exposed in the SDK used by application developers.
+    const char kSkyjamCookieDelimiter = '#';
+    char* skyjamCookie = strrchr(mUri.string(), kSkyjamCookieDelimiter);
+    if (skyjamCookie) {
+        skyjamCookie++;
+        mUriHeaders.add(String8("Cookie"), String8(skyjamCookie));
+        mUri = String8(mUri.string(), skyjamCookie - mUri.string());
+    }
+
+    return OK;
+}
+
+status_t AAH_TXPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+    Mutex::Autolock autoLock(mLock);
+
+    reset_l();
+
+    sp<DataSource> dataSource = new FileSource(dup(fd), offset, length);
+
+    status_t err = dataSource->initCheck();
+
+    if (err != OK) {
+        return err;
+    }
+
+    mFileSource = dataSource;
+
+    sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+
+    if (extractor == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    return setDataSource_l(extractor);
+}
+
+status_t AAH_TXPlayer::setVideoSurface(const sp<Surface>& surface) {
+    return OK;
+}
+
+status_t AAH_TXPlayer::setVideoSurfaceTexture(
+        const sp<ISurfaceTexture>& surfaceTexture) {
+    return OK;
+}
+
+status_t AAH_TXPlayer::prepare() {
+    return INVALID_OPERATION;
+}
+
+status_t AAH_TXPlayer::prepareAsync() {
+    Mutex::Autolock autoLock(mLock);
+
+    return prepareAsync_l();
+}
+
+status_t AAH_TXPlayer::prepareAsync_l() {
+    if (mFlags & PREPARING) {
+        return UNKNOWN_ERROR;  // async prepare already pending
+    }
+
+    mAAH_Sender = AAH_TXSender::GetInstance();
+    if (mAAH_Sender == NULL) {
+        return NO_MEMORY;
+    }
+
+    if (!mQueueStarted) {
+        mQueue.start();
+        mQueueStarted = true;
+    }
+
+    mFlags |= PREPARING;
+    mAsyncPrepareEvent = new AAH_TXEvent(
+            this, &AAH_TXPlayer::onPrepareAsyncEvent);
+
+    mQueue.postEvent(mAsyncPrepareEvent);
+
+    return OK;
+}
+
+status_t AAH_TXPlayer::finishSetDataSource_l() {
+    sp<DataSource> dataSource;
+
+    if (!strncasecmp("http://",  mUri.string(), 7) ||
+        !strncasecmp("https://", mUri.string(), 8)) {
+
+        mConnectingDataSource = HTTPBase::Create(
+                (mFlags & INCOGNITO)
+                    ? HTTPBase::kFlagIncognito
+                    : 0);
+
+        mLock.unlock();
+        status_t err = mConnectingDataSource->connect(mUri, &mUriHeaders);
+        mLock.lock();
+
+        if (err != OK) {
+            mConnectingDataSource.clear();
+
+            LOGI("mConnectingDataSource->connect() returned %d", err);
+            return err;
+        }
+
+        mCachedSource = new NuCachedSource2(mConnectingDataSource);
+        mConnectingDataSource.clear();
+
+        dataSource = mCachedSource;
+
+        // We're going to prefill the cache before trying to instantiate
+        // the extractor below, as the latter is an operation that otherwise
+        // could block on the datasource for a significant amount of time.
+        // During that time we'd be unable to abort the preparation phase
+        // without this prefill.
+
+        mLock.unlock();
+
+        for (;;) {
+            status_t finalStatus;
+            size_t cachedDataRemaining =
+                mCachedSource->approxDataRemaining(&finalStatus);
+
+            if (finalStatus != OK ||
+                cachedDataRemaining >= kHighWaterMarkBytes ||
+                (mFlags & PREPARE_CANCELLED)) {
+                break;
+            }
+
+            usleep(200000);
+        }
+
+        mLock.lock();
+
+        if (mFlags & PREPARE_CANCELLED) {
+            LOGI("Prepare cancelled while waiting for initial cache fill.");
+            return UNKNOWN_ERROR;
+        }
+    } else {
+        dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders);
+    }
+
+    if (dataSource == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
+
+    if (extractor == NULL) {
+        return UNKNOWN_ERROR;
+    }
+
+    return setDataSource_l(extractor);
+}
+
+status_t AAH_TXPlayer::setDataSource_l(const sp<MediaExtractor> &extractor) {
+    // Attempt to approximate overall stream bitrate by summing all
+    // tracks' individual bitrates, if not all of them advertise bitrate,
+    // we have to fail.
+
+    int64_t totalBitRate = 0;
+
+    for (size_t i = 0; i < extractor->countTracks(); ++i) {
+        sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+        int32_t bitrate;
+        if (!meta->findInt32(kKeyBitRate, &bitrate)) {
+            totalBitRate = -1;
+            break;
+        }
+
+        totalBitRate += bitrate;
+    }
+
+    mBitrate = totalBitRate;
+
+    LOGV("mBitrate = %lld bits/sec", mBitrate);
+
+    bool haveAudio = false;
+    for (size_t i = 0; i < extractor->countTracks(); ++i) {
+        sp<MetaData> meta = extractor->getTrackMetaData(i);
+
+        const char *mime;
+        CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+        if (!strncasecmp(mime, "audio/", 6)) {
+            mAudioSource = extractor->getTrack(i);
+            CHECK(mAudioSource != NULL);
+            haveAudio = true;
+            break;
+        }
+    }
+
+    if (!haveAudio) {
+        return UNKNOWN_ERROR;
+    }
+
+    mExtractorFlags = extractor->flags();
+
+    return OK;
+}
+
+void AAH_TXPlayer::abortPrepare(status_t err) {
+    CHECK(err != OK);
+
+    notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
+
+    mPrepareResult = err;
+    mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+    mPreparedCondition.broadcast();
+}
+
+void AAH_TXPlayer::onPrepareAsyncEvent() {
+    Mutex::Autolock autoLock(mLock);
+
+    if (mFlags & PREPARE_CANCELLED) {
+        LOGI("prepare was cancelled before doing anything");
+        abortPrepare(UNKNOWN_ERROR);
+        return;
+    }
+
+    if (mUri.size() > 0) {
+        status_t err = finishSetDataSource_l();
+
+        if (err != OK) {
+            abortPrepare(err);
+            return;
+        }
+    }
+
+    mAudioFormat = mAudioSource->getFormat();
+    if (!mAudioFormat->findInt64(kKeyDuration, &mDurationUs))
+        mDurationUs = 1;
+
+    const char* mime_type = NULL;
+    if (!mAudioFormat->findCString(kKeyMIMEType, &mime_type)) {
+        LOGE("Failed to find audio substream MIME type during prepare.");
+        abortPrepare(BAD_VALUE);
+        return;
+    }
+
+    if (!strcmp(mime_type, MEDIA_MIMETYPE_AUDIO_MPEG)) {
+        mAudioCodec = TRTPAudioPacket::kCodecMPEG1Audio;
+    } else
+    if (!strcmp(mime_type, MEDIA_MIMETYPE_AUDIO_AAC)) {
+        mAudioCodec = TRTPAudioPacket::kCodecAACAudio;
+
+        uint32_t type;
+        int32_t  sample_rate;
+        int32_t  channel_count;
+        const void* esds_data;
+        size_t esds_len;
+
+        if (!mAudioFormat->findInt32(kKeySampleRate, &sample_rate)) {
+            LOGE("Failed to find sample rate for AAC substream.");
+            abortPrepare(BAD_VALUE);
+            return;
+        }
+
+        if (!mAudioFormat->findInt32(kKeyChannelCount, &channel_count)) {
+            LOGE("Failed to find channel count for AAC substream.");
+            abortPrepare(BAD_VALUE);
+            return;
+        }
+
+        if (!mAudioFormat->findData(kKeyESDS, &type, &esds_data, &esds_len)) {
+            LOGE("Failed to find codec init data for AAC substream.");
+            abortPrepare(BAD_VALUE);
+            return;
+        }
+
+        CHECK(NULL == mAudioCodecData);
+        mAudioCodecDataSize = esds_len
+                            + sizeof(sample_rate)
+                            + sizeof(channel_count);
+        mAudioCodecData = new uint8_t[mAudioCodecDataSize];
+        if (NULL == mAudioCodecData) {
+            LOGE("Failed to allocate %u bytes for AAC substream codec aux"
+                 " data.", mAudioCodecDataSize);
+            mAudioCodecDataSize = 0;
+            abortPrepare(BAD_VALUE);
+            return;
+        }
+
+        uint8_t* tmp = mAudioCodecData;
+        tmp[0] = static_cast<uint8_t>((sample_rate   >> 24) & 0xFF);
+        tmp[1] = static_cast<uint8_t>((sample_rate   >> 16) & 0xFF);
+        tmp[2] = static_cast<uint8_t>((sample_rate   >>  8) & 0xFF);
+        tmp[3] = static_cast<uint8_t>((sample_rate        ) & 0xFF);
+        tmp[4] = static_cast<uint8_t>((channel_count >> 24) & 0xFF);
+        tmp[5] = static_cast<uint8_t>((channel_count >> 16) & 0xFF);
+        tmp[6] = static_cast<uint8_t>((channel_count >>  8) & 0xFF);
+        tmp[7] = static_cast<uint8_t>((channel_count      ) & 0xFF);
+
+        memcpy(tmp + 8, esds_data, esds_len);
+    } else {
+        LOGE("Unsupported MIME type \"%s\" in audio substream", mime_type);
+        abortPrepare(BAD_VALUE);
+        return;
+    }
+
+    status_t err = mAudioSource->start();
+    if (err != OK) {
+        LOGI("failed to start audio source, err=%d", err);
+        abortPrepare(err);
+        return;
+    }
+
+    mFlags |= PREPARING_CONNECTED;
+
+    if (mCachedSource != NULL) {
+        postBufferingEvent_l();
+    } else {
+        finishAsyncPrepare_l();
+    }
+}
+
+void AAH_TXPlayer::finishAsyncPrepare_l() {
+    notifyListener_l(MEDIA_PREPARED);
+
+    mPrepareResult = OK;
+    mFlags &= ~(PREPARING|PREPARE_CANCELLED|PREPARING_CONNECTED);
+    mFlags |= PREPARED;
+    mPreparedCondition.broadcast();
+}
+
+status_t AAH_TXPlayer::start() {
+    Mutex::Autolock autoLock(mLock);
+
+    mFlags &= ~CACHE_UNDERRUN;
+
+    return play_l();
+}
+
+status_t AAH_TXPlayer::play_l() {
+    if (mFlags & PLAYING) {
+        return OK;
+    }
+
+    if (!(mFlags & PREPARED)) {
+        return INVALID_OPERATION;
+    }
+
+    {
+        Mutex::Autolock lock(mEndpointLock);
+        if (!mEndpointValid) {
+            return INVALID_OPERATION;
+        }
+        if (!mEndpointRegistered) {
+            mProgramID = mAAH_Sender->registerEndpoint(mEndpoint);
+            mEndpointRegistered = true;
+        }
+    }
+
+    mFlags |= PLAYING;
+
+    updateClockTransform_l(false);
+
+    postPumpAudioEvent_l(-1);
+
+    return OK;
+}
+
+status_t AAH_TXPlayer::stop() {
+    status_t ret = pause();
+    sendEOS_l();
+    return ret;
+}
+
+status_t AAH_TXPlayer::pause() {
+    Mutex::Autolock autoLock(mLock);
+
+    mFlags &= ~CACHE_UNDERRUN;
+
+    return pause_l();
+}
+
+status_t AAH_TXPlayer::pause_l(bool doClockUpdate) {
+    if (!(mFlags & PLAYING)) {
+        return OK;
+    }
+
+    cancelPlayerEvents(true /* keepBufferingGoing */);
+
+    mFlags &= ~PLAYING;
+
+    if (doClockUpdate) {
+        updateClockTransform_l(true);
+    }
+
+    return OK;
+}
+
+void AAH_TXPlayer::updateClockTransform_l(bool pause) {
+    // record the new pause status so that onPumpAudio knows what rate to apply
+    // when it initializes the transform
+    mPlayRateIsPaused = pause;
+
+    // if we haven't yet established a valid clock transform, then we can't
+    // do anything here
+    if (!mCurrentClockTransformValid) {
+        return;
+    }
+
+    // sample the current common time
+    int64_t commonTimeNow;
+    if (OK != mCCHelper.getCommonTime(&commonTimeNow)) {
+        LOGE("updateClockTransform_l get common time failed");
+        mCurrentClockTransformValid = false;
+        return;
+    }
+
+    // convert the current common time to media time using the old
+    // transform
+    int64_t mediaTimeNow;
+    if (!mCurrentClockTransform.doReverseTransform(
+            commonTimeNow, &mediaTimeNow)) {
+        LOGE("updateClockTransform_l reverse transform failed");
+        mCurrentClockTransformValid = false;
+        return;
+    }
+
+    // calculate a new transform that preserves the old transform's
+    // result for the current time
+    mCurrentClockTransform.a_zero = mediaTimeNow;
+    mCurrentClockTransform.b_zero = commonTimeNow;
+    mCurrentClockTransform.a_to_b_numer = 1;
+    mCurrentClockTransform.a_to_b_denom = pause ? 0 : 1;
+
+    // send a packet announcing the new transform
+    sp<TRTPControlPacket> packet = new TRTPControlPacket();
+    packet->setClockTransform(mCurrentClockTransform);
+    packet->setCommandID(TRTPControlPacket::kCommandNop);
+    queuePacketToSender_l(packet);
+}
+
+void AAH_TXPlayer::sendEOS_l() {
+    sp<TRTPControlPacket> packet = new TRTPControlPacket();
+    packet->setCommandID(TRTPControlPacket::kCommandEOS);
+    queuePacketToSender_l(packet);
+}
+
+bool AAH_TXPlayer::isPlaying() {
+    return (mFlags & PLAYING) || (mFlags & CACHE_UNDERRUN);
+}
+
+status_t AAH_TXPlayer::seekTo(int msec) {
+    if (mExtractorFlags & MediaExtractor::CAN_SEEK) {
+        Mutex::Autolock autoLock(mLock);
+        return seekTo_l(static_cast<int64_t>(msec) * 1000);
+    }
+
+    notifyListener_l(MEDIA_SEEK_COMPLETE);
+    return OK;
+}
+
+status_t AAH_TXPlayer::seekTo_l(int64_t timeUs) {
+    mIsSeeking = true;
+    mSeekTimeUs = timeUs;
+
+    mCurrentClockTransformValid = false;
+    mLastQueuedMediaTimePTSValid = false;
+
+    // send a flush command packet
+    sp<TRTPControlPacket> packet = new TRTPControlPacket();
+    packet->setCommandID(TRTPControlPacket::kCommandFlush);
+    queuePacketToSender_l(packet);
+
+    return OK;
+}
+
+status_t AAH_TXPlayer::getCurrentPosition(int *msec) {
+    if (!msec) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock lock(mLock);
+
+    int position;
+
+    if (mIsSeeking) {
+        position = mSeekTimeUs / 1000;
+    } else if (mCurrentClockTransformValid) {
+        // sample the current common time
+        int64_t commonTimeNow;
+        if (OK != mCCHelper.getCommonTime(&commonTimeNow)) {
+            LOGE("getCurrentPosition get common time failed");
+            return INVALID_OPERATION;
+        }
+
+        int64_t mediaTimeNow;
+        if (!mCurrentClockTransform.doReverseTransform(commonTimeNow,
+                    &mediaTimeNow)) {
+            LOGE("getCurrentPosition reverse transform failed");
+            return INVALID_OPERATION;
+        }
+
+        position = static_cast<int>(mediaTimeNow / 1000);
+    } else {
+        position = 0;
+    }
+
+    int duration;
+    if (getDuration_l(&duration) == OK) {
+        *msec = clamp(position, 0, duration);
+    } else {
+        *msec = (position >= 0) ? position : 0;
+    }
+
+    return OK;
+}
+
+status_t AAH_TXPlayer::getDuration(int* msec) {
+    if (!msec) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock lock(mLock);
+
+    return getDuration_l(msec);
+}
+
+status_t AAH_TXPlayer::getDuration_l(int* msec) {
+    if (mDurationUs < 0) {
+        return UNKNOWN_ERROR;
+    }
+
+    *msec = (mDurationUs + 500) / 1000;
+
+    return OK;
+}
+
+status_t AAH_TXPlayer::reset() {
+    Mutex::Autolock autoLock(mLock);
+    reset_l();
+    return OK;
+}
+
+void AAH_TXPlayer::reset_l() {
+    if (mFlags & PREPARING) {
+        mFlags |= PREPARE_CANCELLED;
+        if (mConnectingDataSource != NULL) {
+            LOGI("interrupting the connection process");
+            mConnectingDataSource->disconnect();
+        }
+
+        if (mFlags & PREPARING_CONNECTED) {
+            // We are basically done preparing, we're just buffering
+            // enough data to start playback, we can safely interrupt that.
+            finishAsyncPrepare_l();
+        }
+    }
+
+    while (mFlags & PREPARING) {
+        mPreparedCondition.wait(mLock);
+    }
+
+    cancelPlayerEvents();
+
+    sendEOS_l();
+
+    mCachedSource.clear();
+
+    if (mAudioSource != NULL) {
+        mAudioSource->stop();
+    }
+    mAudioSource.clear();
+    mAudioCodec = TRTPAudioPacket::kCodecInvalid;
+    mAudioFormat = NULL;
+    delete[] mAudioCodecData;
+    mAudioCodecData = NULL;
+    mAudioCodecDataSize = 0;
+
+    mFlags = 0;
+    mExtractorFlags = 0;
+
+    mDurationUs = -1;
+    mIsSeeking = false;
+    mSeekTimeUs = 0;
+
+    mUri.setTo("");
+    mUriHeaders.clear();
+
+    mFileSource.clear();
+
+    mBitrate = -1;
+
+    {
+        Mutex::Autolock lock(mEndpointLock);
+        if (mAAH_Sender != NULL && mEndpointRegistered) {
+            mAAH_Sender->unregisterEndpoint(mEndpoint);
+        }
+        mEndpointRegistered = false;
+        mEndpointValid = false;
+    }
+
+    mProgramID = 0;
+
+    mAAH_Sender.clear();
+    mLastQueuedMediaTimePTSValid = false;
+    mCurrentClockTransformValid = false;
+    mPlayRateIsPaused = false;
+
+    mTRTPVolume = 255;
+}
+
+status_t AAH_TXPlayer::setLooping(int loop) {
+    return OK;
+}
+
+player_type AAH_TXPlayer::playerType() {
+    return AAH_TX_PLAYER;
+}
+
+status_t AAH_TXPlayer::setParameter(int key, const Parcel &request) {
+    return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::getParameter(int key, Parcel *reply) {
+    return ERROR_UNSUPPORTED;
+}
+
+status_t AAH_TXPlayer::invoke(const Parcel& request, Parcel *reply) {
+    return INVALID_OPERATION;
+}
+
+status_t AAH_TXPlayer::getMetadata(const media::Metadata::Filter& ids,
+                                   Parcel* records) {
+    using media::Metadata;
+
+    Metadata metadata(records);
+
+    metadata.appendBool(Metadata::kPauseAvailable, true);
+    metadata.appendBool(Metadata::kSeekBackwardAvailable, false);
+    metadata.appendBool(Metadata::kSeekForwardAvailable, false);
+    metadata.appendBool(Metadata::kSeekAvailable, false);
+
+    return OK;
+}
+
+status_t AAH_TXPlayer::setVolume(float leftVolume, float rightVolume) {
+    if (leftVolume != rightVolume) {
+        LOGE("%s does not support per channel volume: %f, %f",
+             __PRETTY_FUNCTION__, leftVolume, rightVolume);
+    }
+
+    float volume = clamp(leftVolume, 0.0f, 1.0f);
+
+    Mutex::Autolock lock(mLock);
+    mTRTPVolume = static_cast<uint8_t>((leftVolume * 255.0) + 0.5);
+
+    return OK;
+}
+
+status_t AAH_TXPlayer::setAudioStreamType(int streamType) {
+    return OK;
+}
+
+status_t AAH_TXPlayer::setRetransmitEndpoint(
+        const struct sockaddr_in* endpoint) {
+    Mutex::Autolock lock(mLock);
+
+    if (NULL == endpoint)
+        return BAD_VALUE;
+
+    // Once the endpoint has been registered, it may not be changed.
+    if (mEndpointRegistered)
+        return INVALID_OPERATION;
+
+    mEndpoint.addr = endpoint->sin_addr.s_addr;
+    mEndpoint.port = endpoint->sin_port;
+    mEndpointValid = true;
+
+    return OK;
+}
+
+void AAH_TXPlayer::notifyListener_l(int msg, int ext1, int ext2) {
+    sendEvent(msg, ext1, ext2);
+}
+
+bool AAH_TXPlayer::getBitrate_l(int64_t *bitrate) {
+    off64_t size;
+    if (mDurationUs >= 0 &&
+        mCachedSource != NULL &&
+        mCachedSource->getSize(&size) == OK) {
+        *bitrate = size * 8000000ll / mDurationUs;  // in bits/sec
+        return true;
+    }
+
+    if (mBitrate >= 0) {
+        *bitrate = mBitrate;
+        return true;
+    }
+
+    *bitrate = 0;
+
+    return false;
+}
+
+// Returns true iff cached duration is available/applicable.
+bool AAH_TXPlayer::getCachedDuration_l(int64_t *durationUs, bool *eos) {
+    int64_t bitrate;
+
+    if (mCachedSource != NULL && getBitrate_l(&bitrate)) {
+        status_t finalStatus;
+        size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+                                        &finalStatus);
+        *durationUs = cachedDataRemaining * 8000000ll / bitrate;
+        *eos = (finalStatus != OK);
+        return true;
+    }
+
+    return false;
+}
+
+void AAH_TXPlayer::ensureCacheIsFetching_l() {
+    if (mCachedSource != NULL) {
+        mCachedSource->resumeFetchingIfNecessary();
+    }
+}
+
+void AAH_TXPlayer::postBufferingEvent_l() {
+    if (mBufferingEventPending) {
+        return;
+    }
+    mBufferingEventPending = true;
+    mQueue.postEventWithDelay(mBufferingEvent, 1000000ll);
+}
+
+void AAH_TXPlayer::postPumpAudioEvent_l(int64_t delayUs) {
+    if (mPumpAudioEventPending) {
+        return;
+    }
+    mPumpAudioEventPending = true;
+    mQueue.postEventWithDelay(mPumpAudioEvent, delayUs < 0 ? 10000 : delayUs);
+}
+
+void AAH_TXPlayer::onBufferingUpdate() {
+    Mutex::Autolock autoLock(mLock);
+    if (!mBufferingEventPending) {
+        return;
+    }
+    mBufferingEventPending = false;
+
+    if (mCachedSource != NULL) {
+        status_t finalStatus;
+        size_t cachedDataRemaining = mCachedSource->approxDataRemaining(
+                                        &finalStatus);
+        bool eos = (finalStatus != OK);
+
+        if (eos) {
+            if (finalStatus == ERROR_END_OF_STREAM) {
+                notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+            }
+            if (mFlags & PREPARING) {
+                LOGV("cache has reached EOS, prepare is done.");
+                finishAsyncPrepare_l();
+            }
+        } else {
+            int64_t bitrate;
+            if (getBitrate_l(&bitrate)) {
+                size_t cachedSize = mCachedSource->cachedSize();
+                int64_t cachedDurationUs = cachedSize * 8000000ll / bitrate;
+
+                int percentage = (100.0 * (double) cachedDurationUs)
+                               / mDurationUs;
+                if (percentage > 100) {
+                    percentage = 100;
+                }
+
+                notifyListener_l(MEDIA_BUFFERING_UPDATE, percentage);
+            } else {
+                // We don't know the bitrate of the stream, use absolute size
+                // limits to maintain the cache.
+
+                if ((mFlags & PLAYING) &&
+                    !eos &&
+                    (cachedDataRemaining < kLowWaterMarkBytes)) {
+                    LOGI("cache is running low (< %d) , pausing.",
+                         kLowWaterMarkBytes);
+                    mFlags |= CACHE_UNDERRUN;
+                    pause_l();
+                    ensureCacheIsFetching_l();
+                    notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+                } else if (eos || cachedDataRemaining > kHighWaterMarkBytes) {
+                    if (mFlags & CACHE_UNDERRUN) {
+                        LOGI("cache has filled up (> %d), resuming.",
+                              kHighWaterMarkBytes);
+                        mFlags &= ~CACHE_UNDERRUN;
+                        play_l();
+                        notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+                    } else if (mFlags & PREPARING) {
+                        LOGV("cache has filled up (> %d), prepare is done",
+                              kHighWaterMarkBytes);
+                        finishAsyncPrepare_l();
+                    }
+                }
+            }
+        }
+    }
+
+    int64_t cachedDurationUs;
+    bool eos;
+    if (getCachedDuration_l(&cachedDurationUs, &eos)) {
+        LOGV("cachedDurationUs = %.2f secs, eos=%d",
+              cachedDurationUs / 1E6, eos);
+
+        if ((mFlags & PLAYING) &&
+            !eos &&
+            (cachedDurationUs < kLowWaterMarkUs)) {
+            LOGI("cache is running low (%.2f secs) , pausing.",
+                  cachedDurationUs / 1E6);
+            mFlags |= CACHE_UNDERRUN;
+            pause_l();
+            ensureCacheIsFetching_l();
+            notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_START);
+        } else if (eos || cachedDurationUs > kHighWaterMarkUs) {
+            if (mFlags & CACHE_UNDERRUN) {
+                LOGI("cache has filled up (%.2f secs), resuming.",
+                      cachedDurationUs / 1E6);
+                mFlags &= ~CACHE_UNDERRUN;
+                play_l();
+                notifyListener_l(MEDIA_INFO, MEDIA_INFO_BUFFERING_END);
+            } else if (mFlags & PREPARING) {
+                LOGV("cache has filled up (%.2f secs), prepare is done",
+                        cachedDurationUs / 1E6);
+                finishAsyncPrepare_l();
+            }
+        }
+    }
+
+    postBufferingEvent_l();
+}
+
+void AAH_TXPlayer::onPumpAudio() {
+    while (true) {
+        Mutex::Autolock autoLock(mLock);
+        // If this flag is clear, its because someone has externally canceled
+        // this pump operation (probably because we a resetting/shutting down).
+        // Get out immediately, do not reschedule ourselves.
+        if (!mPumpAudioEventPending) {
+            return;
+        }
+
+        // Start by checking if there is still work to be doing.  If we have
+        // never queued a payload (so we don't know what the last queued PTS is)
+        // or we have never established a MediaTime->CommonTime transformation,
+        // then we have work to do (one time through this loop should establish
+        // both).  Otherwise, we want to keep a fixed amt of presentation time
+        // worth of data buffered.  If we cannot get common time (service is
+        // unavailable, or common time is undefined)) then we don't have a lot
+        // of good options here.  For now, signal an error up to the app level
+        // and shut down the transmission pump.
+        int64_t commonTimeNow;
+        if (OK != mCCHelper.getCommonTime(&commonTimeNow)) {
+            // Failed to get common time; either the service is down or common
+            // time is not synced.  Raise an error and shutdown the player.
+            LOGE("*** Cannot pump audio, unable to fetch common time."
+                 "  Shutting down.");
+            notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, UNKNOWN_ERROR);
+            mPumpAudioEventPending = false;
+            break;
+        }
+
+        if (mCurrentClockTransformValid && mLastQueuedMediaTimePTSValid) {
+            int64_t mediaTimeNow;
+            bool conversionResult = mCurrentClockTransform.doReverseTransform(
+                                        commonTimeNow,
+                                        &mediaTimeNow);
+            CHECK(conversionResult);
+
+            if ((mediaTimeNow +
+                 kAAHBufferTimeUs -
+                 mLastQueuedMediaTimePTS) <= 0) {
+                break;
+            }
+        }
+
+        MediaSource::ReadOptions options;
+        if (mIsSeeking) {
+            options.setSeekTo(mSeekTimeUs);
+        }
+
+        MediaBuffer* mediaBuffer;
+        status_t err = mAudioSource->read(&mediaBuffer, &options);
+        if (err != NO_ERROR) {
+            if (err == ERROR_END_OF_STREAM) {
+                LOGI("*** %s reached end of stream", __PRETTY_FUNCTION__);
+                notifyListener_l(MEDIA_BUFFERING_UPDATE, 100);
+                notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
+                pause_l(false);
+                sendEOS_l();
+            } else {
+                LOGE("*** %s read failed err=%d", __PRETTY_FUNCTION__, err);
+            }
+            return;
+        }
+
+        if (mIsSeeking) {
+            mIsSeeking = false;
+            notifyListener_l(MEDIA_SEEK_COMPLETE);
+        }
+
+        uint8_t* data = (static_cast<uint8_t*>(mediaBuffer->data()) +
+                mediaBuffer->range_offset());
+        LOGV("*** %s got media buffer data=[%02hhx %02hhx %02hhx %02hhx]"
+             " offset=%d length=%d", __PRETTY_FUNCTION__,
+             data[0], data[1], data[2], data[3],
+             mediaBuffer->range_offset(), mediaBuffer->range_length());
+
+        int64_t mediaTimeUs;
+        CHECK(mediaBuffer->meta_data()->findInt64(kKeyTime, &mediaTimeUs));
+        LOGV("*** timeUs=%lld", mediaTimeUs);
+
+        if (!mCurrentClockTransformValid) {
+            if (OK == mCCHelper.getCommonTime(&commonTimeNow)) {
+                mCurrentClockTransform.a_zero = mediaTimeUs;
+                mCurrentClockTransform.b_zero = commonTimeNow +
+                                                kAAHStartupLeadTimeUs;
+                mCurrentClockTransform.a_to_b_numer = 1;
+                mCurrentClockTransform.a_to_b_denom = mPlayRateIsPaused ? 0 : 1;
+                mCurrentClockTransformValid = true;
+            } else {
+                // Failed to get common time; either the service is down or
+                // common time is not synced.  Raise an error and shutdown the
+                // player.
+                LOGE("*** Cannot begin transmission, unable to fetch common"
+                     " time. Dropping sample with pts=%lld", mediaTimeUs);
+                notifyListener_l(MEDIA_ERROR,
+                                 MEDIA_ERROR_UNKNOWN,
+                                 UNKNOWN_ERROR);
+                mPumpAudioEventPending = false;
+                break;
+            }
+        }
+
+        LOGV("*** transmitting packet with pts=%lld", mediaTimeUs);
+
+        sp<TRTPAudioPacket> packet = new TRTPAudioPacket();
+        packet->setPTS(mediaTimeUs);
+        packet->setSubstreamID(1);
+
+        packet->setCodecType(mAudioCodec);
+        packet->setVolume(mTRTPVolume);
+        // TODO : introduce a throttle for this so we can control the
+        // frequency with which transforms get sent.
+        packet->setClockTransform(mCurrentClockTransform);
+        packet->setAccessUnitData(data, mediaBuffer->range_length());
+
+        // TODO : while its pretty much universally true that audio ES payloads
+        // are all RAPs across all codecs, it might be a good idea to throttle
+        // the frequency with which we send codec out of band data to the RXers.
+        // If/when we do, we need to flag only those payloads which have
+        // required out of band data attached to them as RAPs.
+        packet->setRandomAccessPoint(true);
+
+        if (mAudioCodecData && mAudioCodecDataSize) {
+            packet->setAuxData(mAudioCodecData, mAudioCodecDataSize);
+        }
+
+        queuePacketToSender_l(packet);
+        mediaBuffer->release();
+
+        mLastQueuedMediaTimePTSValid = true;
+        mLastQueuedMediaTimePTS = mediaTimeUs;
+    }
+
+    { // Explicit scope for the autolock pattern.
+        Mutex::Autolock autoLock(mLock);
+
+        // If someone externally has cleared this flag, its because we should be
+        // shutting down.  Do not reschedule ourselves.
+        if (!mPumpAudioEventPending) {
+            return;
+        }
+
+        // Looks like no one canceled us explicitly.  Clear our flag and post a
+        // new event to ourselves.
+        mPumpAudioEventPending = false;
+        postPumpAudioEvent_l(10000);
+    }
+}
+
+void AAH_TXPlayer::queuePacketToSender_l(const sp<TRTPPacket>& packet) {
+    if (mAAH_Sender == NULL) {
+        return;
+    }
+
+    sp<AMessage> message = new AMessage(AAH_TXSender::kWhatSendPacket,
+                                        mAAH_Sender->handlerID());
+
+    {
+        Mutex::Autolock lock(mEndpointLock);
+        if (!mEndpointValid) {
+            return;
+        }
+
+        message->setInt32(AAH_TXSender::kSendPacketIPAddr, mEndpoint.addr);
+        message->setInt32(AAH_TXSender::kSendPacketPort, mEndpoint.port);
+    }
+
+    packet->setProgramID(mProgramID);
+    packet->setExpireTime(systemTime() + kAAHRetryKeepAroundTimeNs);
+    packet->pack();
+
+    message->setObject(AAH_TXSender::kSendPacketTRTPPacket, packet);
+
+    message->post();
+}
+
+}  // namespace android
diff --git a/media/libaah_rtp/aah_tx_player.h b/media/libaah_rtp/aah_tx_player.h
new file mode 100644
index 0000000..6843f9b
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_player.h
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ */
+
+#ifndef __AAH_TX_PLAYER_H__
+#define __AAH_TX_PLAYER_H__
+
+#include <common_time/cc_helper.h>
+#include <libstagefright/include/HTTPBase.h>
+#include <libstagefright/include/NuCachedSource2.h>
+#include <libstagefright/include/TimedEventQueue.h>
+#include <media/MediaPlayerInterface.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaSource.h>
+#include <utils/LinearTransform.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+
+#include "aah_tx_sender.h"
+
+namespace android {
+
+class AAH_TXPlayer : public MediaPlayerHWInterface {
+  public:
+    AAH_TXPlayer();
+
+    virtual status_t    initCheck();
+    virtual status_t    setDataSource(const char *url,
+                                      const KeyedVector<String8, String8>*
+                                      headers);
+    virtual status_t    setDataSource(int fd, int64_t offset, int64_t length);
+    virtual status_t    setVideoSurface(const sp<Surface>& surface);
+    virtual status_t    setVideoSurfaceTexture(const sp<ISurfaceTexture>&
+                                               surfaceTexture);
+    virtual status_t    prepare();
+    virtual status_t    prepareAsync();
+    virtual status_t    start();
+    virtual status_t    stop();
+    virtual status_t    pause();
+    virtual bool        isPlaying();
+    virtual status_t    seekTo(int msec);
+    virtual status_t    getCurrentPosition(int *msec);
+    virtual status_t    getDuration(int *msec);
+    virtual status_t    reset();
+    virtual status_t    setLooping(int loop);
+    virtual player_type playerType();
+    virtual status_t    setParameter(int key, const Parcel &request);
+    virtual status_t    getParameter(int key, Parcel *reply);
+    virtual status_t    invoke(const Parcel& request, Parcel *reply);
+    virtual status_t    getMetadata(const media::Metadata::Filter& ids,
+                                    Parcel* records);
+    virtual status_t    setVolume(float leftVolume, float rightVolume);
+    virtual status_t    setAudioStreamType(int streamType);
+    virtual status_t    setRetransmitEndpoint(const struct sockaddr_in*
+                                              endpoint);
+
+    static const int64_t kAAHRetryKeepAroundTimeNs;
+
+  protected:
+    virtual ~AAH_TXPlayer();
+
+  private:
+    friend struct AwesomeEvent;
+
+    enum {
+        PLAYING             = 1,
+        PREPARING           = 8,
+        PREPARED            = 16,
+        PREPARE_CANCELLED   = 64,
+        CACHE_UNDERRUN      = 128,
+
+        // We are basically done preparing but are currently buffering
+        // sufficient data to begin playback and finish the preparation
+        // phase for good.
+        PREPARING_CONNECTED = 2048,
+
+        INCOGNITO           = 32768,
+    };
+
+    status_t setDataSource_l(const char *url,
+                             const KeyedVector<String8, String8> *headers);
+    status_t setDataSource_l(const sp<MediaExtractor>& extractor);
+    status_t finishSetDataSource_l();
+    status_t prepareAsync_l();
+    void onPrepareAsyncEvent();
+    void finishAsyncPrepare_l();
+    void abortPrepare(status_t err);
+    status_t play_l();
+    status_t pause_l(bool doClockUpdate = true);
+    status_t seekTo_l(int64_t timeUs);
+    void updateClockTransform_l(bool pause);
+    void sendEOS_l();
+    void cancelPlayerEvents(bool keepBufferingGoing = false);
+    void reset_l();
+    void notifyListener_l(int msg, int ext1 = 0, int ext2 = 0);
+    bool getBitrate_l(int64_t* bitrate);
+    status_t getDuration_l(int* msec);
+    bool getCachedDuration_l(int64_t* durationUs, bool* eos);
+    void ensureCacheIsFetching_l();
+    void postBufferingEvent_l();
+    void postPumpAudioEvent_l(int64_t delayUs);
+    void onBufferingUpdate();
+    void onPumpAudio();
+    void queuePacketToSender_l(const sp<TRTPPacket>& packet);
+
+    Mutex mLock;
+
+    TimedEventQueue mQueue;
+    bool mQueueStarted;
+
+    sp<TimedEventQueue::Event> mBufferingEvent;
+    bool mBufferingEventPending;
+
+    uint32_t mFlags;
+    uint32_t mExtractorFlags;
+
+    String8 mUri;
+    KeyedVector<String8, String8> mUriHeaders;
+
+    sp<DataSource> mFileSource;
+
+    sp<TimedEventQueue::Event> mAsyncPrepareEvent;
+    Condition mPreparedCondition;
+    status_t mPrepareResult;
+
+    bool mIsSeeking;
+    int64_t mSeekTimeUs;
+
+    sp<TimedEventQueue::Event> mPumpAudioEvent;
+    bool mPumpAudioEventPending;
+
+    sp<HTTPBase> mConnectingDataSource;
+    sp<NuCachedSource2> mCachedSource;
+
+    sp<MediaSource> mAudioSource;
+    TRTPAudioPacket::TRTPAudioCodecType mAudioCodec;
+    sp<MetaData> mAudioFormat;
+    uint8_t* mAudioCodecData;
+    size_t mAudioCodecDataSize;
+
+    int64_t mDurationUs;
+    int64_t mBitrate;
+
+    sp<AAH_TXSender> mAAH_Sender;
+    LinearTransform  mCurrentClockTransform;
+    bool             mCurrentClockTransformValid;
+    int64_t          mLastQueuedMediaTimePTS;
+    bool             mLastQueuedMediaTimePTSValid;
+    bool             mPlayRateIsPaused;
+    CCHelper         mCCHelper;
+
+    Mutex mEndpointLock;
+    AAH_TXSender::Endpoint mEndpoint;
+    bool mEndpointValid;
+    bool mEndpointRegistered;
+    uint16_t mProgramID;
+    uint8_t mTRTPVolume;
+
+    DISALLOW_EVIL_CONSTRUCTORS(AAH_TXPlayer);
+};
+
+}  // namespace android
+
+#endif  // __AAH_TX_PLAYER_H__
diff --git a/media/libaah_rtp/aah_tx_sender.cpp b/media/libaah_rtp/aah_tx_sender.cpp
new file mode 100644
index 0000000..1dcb6e9
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_sender.cpp
@@ -0,0 +1,602 @@
+/*
+ * 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 "LibAAH_RTP"
+#include <media/stagefright/foundation/ADebug.h>
+
+#include <netinet/in.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <media/stagefright/foundation/AMessage.h>
+#include <utils/misc.h>
+
+#include "aah_tx_player.h"
+#include "aah_tx_sender.h"
+
+namespace android {
+
+const char* AAH_TXSender::kSendPacketIPAddr = "ipaddr";
+const char* AAH_TXSender::kSendPacketPort = "port";
+const char* AAH_TXSender::kSendPacketTRTPPacket = "trtp";
+
+const int AAH_TXSender::kRetryTrimIntervalUs = 100000;
+const int AAH_TXSender::kHeartbeatIntervalUs = 1000000;
+const int AAH_TXSender::kRetryBufferCapacity = 100;
+const nsecs_t AAH_TXSender::kHeartbeatTimeout = 600ull * 1000000000ull;
+
+Mutex AAH_TXSender::sLock;
+wp<AAH_TXSender> AAH_TXSender::sInstance;
+uint32_t AAH_TXSender::sNextEpoch;
+bool AAH_TXSender::sNextEpochValid = false;
+
+AAH_TXSender::AAH_TXSender() : mSocket(-1) {
+    mLastSentPacketTime = systemTime();
+}
+
+sp<AAH_TXSender> AAH_TXSender::GetInstance() {
+    Mutex::Autolock autoLock(sLock);
+
+    sp<AAH_TXSender> sender = sInstance.promote();
+
+    if (sender == NULL) {
+        sender = new AAH_TXSender();
+        if (sender == NULL) {
+            return NULL;
+        }
+
+        sender->mLooper = new ALooper();
+        if (sender->mLooper == NULL) {
+            return NULL;
+        }
+
+        sender->mReflector = new AHandlerReflector<AAH_TXSender>(sender.get());
+        if (sender->mReflector == NULL) {
+            return NULL;
+        }
+
+        sender->mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+        if (sender->mSocket == -1) {
+            LOGW("%s unable to create socket", __PRETTY_FUNCTION__);
+            return NULL;
+        }
+
+        struct sockaddr_in bind_addr;
+        memset(&bind_addr, 0, sizeof(bind_addr));
+        bind_addr.sin_family = AF_INET;
+        if (bind(sender->mSocket,
+                 reinterpret_cast<const sockaddr*>(&bind_addr),
+                 sizeof(bind_addr)) < 0) {
+            LOGW("%s unable to bind socket (errno %d)",
+                 __PRETTY_FUNCTION__, errno);
+            return NULL;
+        }
+
+        sender->mRetryReceiver = new RetryReceiver(sender.get());
+        if (sender->mRetryReceiver == NULL) {
+            return NULL;
+        }
+
+        sender->mLooper->setName("AAH_TXSender");
+        sender->mLooper->registerHandler(sender->mReflector);
+        sender->mLooper->start(false, false, PRIORITY_AUDIO);
+
+        if (sender->mRetryReceiver->run("AAH_TXSenderRetry", PRIORITY_AUDIO)
+                != OK) {
+            LOGW("%s unable to start retry thread", __PRETTY_FUNCTION__);
+            return NULL;
+        }
+
+        sInstance = sender;
+    }
+
+    return sender;
+}
+
+AAH_TXSender::~AAH_TXSender() {
+    mLooper->stop();
+    mLooper->unregisterHandler(mReflector->id());
+
+    if (mRetryReceiver != NULL) {
+        mRetryReceiver->requestExit();
+        mRetryReceiver->mWakeupEvent.setEvent();
+        if (mRetryReceiver->requestExitAndWait() != OK) {
+            LOGW("%s shutdown of retry receiver failed", __PRETTY_FUNCTION__);
+        }
+        mRetryReceiver->mSender = NULL;
+        mRetryReceiver.clear();
+    }
+
+    if (mSocket != -1) {
+        close(mSocket);
+    }
+}
+
+// Return the next epoch number usable for a newly instantiated endpoint.
+uint32_t AAH_TXSender::getNextEpoch() {
+    Mutex::Autolock autoLock(sLock);
+
+    if (sNextEpochValid) {
+        sNextEpoch = (sNextEpoch + 1) & TRTPPacket::kTRTPEpochMask;
+    } else {
+        sNextEpoch = ns2ms(systemTime()) & TRTPPacket::kTRTPEpochMask;
+        sNextEpochValid = true;
+    }
+
+    return sNextEpoch;
+}
+
+// Notify the sender that a player has started sending to this endpoint.
+// Returns a program ID for use by the calling player.
+uint16_t AAH_TXSender::registerEndpoint(const Endpoint& endpoint) {
+    Mutex::Autolock lock(mEndpointLock);
+
+    EndpointState* eps = mEndpointMap.valueFor(endpoint);
+    if (eps) {
+        eps->playerRefCount++;
+    } else {
+        eps = new EndpointState(getNextEpoch());
+        mEndpointMap.add(endpoint, eps);
+    }
+
+    // if this is the first registered endpoint, then send a message to start
+    // trimming retry buffers and a message to start sending heartbeats.
+    if (mEndpointMap.size() == 1) {
+        sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers,
+                                                handlerID());
+        trimMessage->post(kRetryTrimIntervalUs);
+
+        sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats,
+                                                     handlerID());
+        heartbeatMessage->post(kHeartbeatIntervalUs);
+    }
+
+    eps->nextProgramID++;
+    return eps->nextProgramID;
+}
+
+// Notify the sender that a player has ceased sending to this endpoint.
+// An endpoint's state can not be deleted until all of the endpoint's
+// registered players have called unregisterEndpoint.
+void AAH_TXSender::unregisterEndpoint(const Endpoint& endpoint) {
+    Mutex::Autolock lock(mEndpointLock);
+
+    EndpointState* eps = mEndpointMap.valueFor(endpoint);
+    if (eps) {
+        eps->playerRefCount--;
+        CHECK(eps->playerRefCount >= 0);
+    }
+}
+
+void AAH_TXSender::onMessageReceived(const sp<AMessage>& msg) {
+    switch (msg->what()) {
+        case kWhatSendPacket:
+            onSendPacket(msg);
+            break;
+
+        case kWhatTrimRetryBuffers:
+            trimRetryBuffers();
+            break;
+
+        case kWhatSendHeartbeats:
+            sendHeartbeats();
+            break;
+
+        default:
+            TRESPASS();
+            break;
+    }
+}
+
+void AAH_TXSender::onSendPacket(const sp<AMessage>& msg) {
+    sp<RefBase> obj;
+    CHECK(msg->findObject(kSendPacketTRTPPacket, &obj));
+    sp<TRTPPacket> packet = static_cast<TRTPPacket*>(obj.get());
+
+    uint32_t ipAddr;
+    CHECK(msg->findInt32(kSendPacketIPAddr,
+                         reinterpret_cast<int32_t*>(&ipAddr)));
+
+    int32_t port32;
+    CHECK(msg->findInt32(kSendPacketPort, &port32));
+    uint16_t port = port32;
+
+    Mutex::Autolock lock(mEndpointLock);
+    doSendPacket_l(packet, Endpoint(ipAddr, port));
+    mLastSentPacketTime = systemTime();
+}
+
+void AAH_TXSender::doSendPacket_l(const sp<TRTPPacket>& packet,
+                                  const Endpoint& endpoint) {
+    EndpointState* eps = mEndpointMap.valueFor(endpoint);
+    if (!eps) {
+        // the endpoint state has disappeared, so the player that sent this
+        // packet must be dead.
+        return;
+    }
+
+    // assign the packet's sequence number
+    packet->setEpoch(eps->epoch);
+    packet->setSeqNumber(eps->trtpSeqNumber++);
+
+    // add the packet to the retry buffer
+    RetryBuffer& retry = eps->retry;
+    retry.push_back(packet);
+
+    // send the packet
+    struct sockaddr_in addr;
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_family = AF_INET;
+    addr.sin_addr.s_addr = endpoint.addr;
+    addr.sin_port = endpoint.port;
+
+    ssize_t result = sendto(mSocket,
+                            packet->getPacket(),
+                            packet->getPacketLen(),
+                            0,
+                            (const struct sockaddr *) &addr,
+                            sizeof(addr));
+    if (result == -1) {
+        LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+    }
+}
+
+void AAH_TXSender::trimRetryBuffers() {
+    Mutex::Autolock lock(mEndpointLock);
+
+    nsecs_t localTimeNow = systemTime();
+
+    Vector<Endpoint> endpointsToRemove;
+
+    for (size_t i = 0; i < mEndpointMap.size(); i++) {
+        EndpointState* eps = mEndpointMap.editValueAt(i);
+        RetryBuffer& retry = eps->retry;
+
+        while (!retry.isEmpty()) {
+            if (retry[0]->getExpireTime() < localTimeNow) {
+                retry.pop_front();
+            } else {
+                break;
+            }
+        }
+
+        if (retry.isEmpty() && eps->playerRefCount == 0) {
+            endpointsToRemove.add(mEndpointMap.keyAt(i));
+        }
+    }
+
+    // remove the state for any endpoints that are no longer in use
+    for (size_t i = 0; i < endpointsToRemove.size(); i++) {
+        Endpoint& e = endpointsToRemove.editItemAt(i);
+        LOGD("*** %s removing endpoint addr=%08x", __PRETTY_FUNCTION__, e.addr);
+        size_t index = mEndpointMap.indexOfKey(e);
+        delete mEndpointMap.valueAt(index);
+        mEndpointMap.removeItemsAt(index);
+    }
+
+    // schedule the next trim
+    if (mEndpointMap.size()) {
+        sp<AMessage> trimMessage = new AMessage(kWhatTrimRetryBuffers,
+                                                handlerID());
+        trimMessage->post(kRetryTrimIntervalUs);
+    }
+}
+
+void AAH_TXSender::sendHeartbeats() {
+    Mutex::Autolock lock(mEndpointLock);
+
+    if (shouldSendHeartbeats_l()) {
+        for (size_t i = 0; i < mEndpointMap.size(); i++) {
+            EndpointState* eps = mEndpointMap.editValueAt(i);
+            const Endpoint& ep = mEndpointMap.keyAt(i);
+
+            sp<TRTPControlPacket> packet = new TRTPControlPacket();
+            packet->setCommandID(TRTPControlPacket::kCommandNop);
+
+            packet->setExpireTime(systemTime() +
+                                  AAH_TXPlayer::kAAHRetryKeepAroundTimeNs);
+            packet->pack();
+
+            doSendPacket_l(packet, ep);
+        }
+    }
+
+    // schedule the next heartbeat
+    if (mEndpointMap.size()) {
+        sp<AMessage> heartbeatMessage = new AMessage(kWhatSendHeartbeats,
+                                                     handlerID());
+        heartbeatMessage->post(kHeartbeatIntervalUs);
+    }
+}
+
+bool AAH_TXSender::shouldSendHeartbeats_l() {
+    // assert(holding endpoint lock)
+    return (systemTime() < (mLastSentPacketTime + kHeartbeatTimeout));
+}
+
+// Receiver
+
+// initial 4-byte ID of a retry request packet
+const uint32_t AAH_TXSender::RetryReceiver::kRetryRequestID = 'Treq';
+
+// initial 4-byte ID of a retry NAK packet
+const uint32_t AAH_TXSender::RetryReceiver::kRetryNakID = 'Tnak';
+
+// initial 4-byte ID of a fast start request packet
+const uint32_t AAH_TXSender::RetryReceiver::kFastStartRequestID = 'Tfst';
+
+AAH_TXSender::RetryReceiver::RetryReceiver(AAH_TXSender* sender)
+        : Thread(false),
+    mSender(sender) {}
+
+    AAH_TXSender::RetryReceiver::~RetryReceiver() {
+        mWakeupEvent.clearPendingEvents();
+    }
+
+// Returns true if val is within the interval bounded inclusively by
+// start and end.  Also handles the case where there is a rollover of the
+// range between start and end.
+template <typename T>
+static inline bool withinIntervalWithRollover(T val, T start, T end) {
+    return ((start <= end && val >= start && val <= end) ||
+            (start > end && (val >= start || val <= end)));
+}
+
+bool AAH_TXSender::RetryReceiver::threadLoop() {
+    struct pollfd pollFds[2];
+    pollFds[0].fd = mSender->mSocket;
+    pollFds[0].events = POLLIN;
+    pollFds[0].revents = 0;
+    pollFds[1].fd = mWakeupEvent.getWakeupHandle();
+    pollFds[1].events = POLLIN;
+    pollFds[1].revents = 0;
+
+    int pollResult = poll(pollFds, NELEM(pollFds), -1);
+    if (pollResult == -1) {
+        LOGE("%s poll failed", __PRETTY_FUNCTION__);
+        return false;
+    }
+
+    if (exitPending()) {
+        LOGI("*** %s exiting", __PRETTY_FUNCTION__);
+        return false;
+    }
+
+    if (pollFds[0].revents) {
+        handleRetryRequest();
+    }
+
+    return true;
+}
+
+void AAH_TXSender::RetryReceiver::handleRetryRequest() {
+    LOGV("*** RX %s start", __PRETTY_FUNCTION__);
+
+    RetryPacket request;
+    struct sockaddr requestSrcAddr;
+    socklen_t requestSrcAddrLen = sizeof(requestSrcAddr);
+
+    ssize_t result = recvfrom(mSender->mSocket, &request, sizeof(request), 0,
+                              &requestSrcAddr, &requestSrcAddrLen);
+    if (result == -1) {
+        LOGE("%s recvfrom failed, errno=%d", __PRETTY_FUNCTION__, errno);
+        return;
+    }
+
+    if (static_cast<size_t>(result) < sizeof(RetryPacket)) {
+        LOGW("%s short packet received", __PRETTY_FUNCTION__);
+        return;
+    }
+
+    uint32_t host_request_id = ntohl(request.id);
+    if ((host_request_id != kRetryRequestID) &&
+        (host_request_id != kFastStartRequestID)) {
+        LOGW("%s received retry request with bogus ID (%08x)",
+                __PRETTY_FUNCTION__, host_request_id);
+        return;
+    }
+
+    Endpoint endpoint(request.endpointIP, request.endpointPort);
+
+    Mutex::Autolock lock(mSender->mEndpointLock);
+
+    EndpointState* eps = mSender->mEndpointMap.valueFor(endpoint);
+
+    if (eps == NULL || eps->retry.isEmpty()) {
+        // we have no retry buffer or an empty retry buffer for this endpoint,
+        // so NAK the entire request
+        RetryPacket nak = request;
+        nak.id = htonl(kRetryNakID);
+        result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+                        &requestSrcAddr, requestSrcAddrLen);
+        if (result == -1) {
+            LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+        }
+        return;
+    }
+
+    RetryBuffer& retry = eps->retry;
+
+    uint16_t startSeq = ntohs(request.seqStart);
+    uint16_t endSeq = ntohs(request.seqEnd);
+
+    uint16_t retryFirstSeq = retry[0]->getSeqNumber();
+    uint16_t retryLastSeq = retry[retry.size() - 1]->getSeqNumber();
+
+    // If this is a fast start, then force the start of the retry to match the
+    // start of the retransmit ring buffer (unless the end of the retransmit
+    // ring buffer is already past the point of fast start)
+    if ((host_request_id == kFastStartRequestID) &&
+        !((startSeq - retryFirstSeq) & 0x8000)) {
+        startSeq = retryFirstSeq;
+    }
+
+    int startIndex;
+    if (withinIntervalWithRollover(startSeq, retryFirstSeq, retryLastSeq)) {
+        startIndex = static_cast<uint16_t>(startSeq - retryFirstSeq);
+    } else {
+        startIndex = -1;
+    }
+
+    int endIndex;
+    if (withinIntervalWithRollover(endSeq, retryFirstSeq, retryLastSeq)) {
+        endIndex = static_cast<uint16_t>(endSeq - retryFirstSeq);
+    } else {
+        endIndex = -1;
+    }
+
+    if (startIndex == -1 && endIndex == -1) {
+        // no part of the request range is found in the retry buffer
+        RetryPacket nak = request;
+        nak.id = htonl(kRetryNakID);
+        result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+                        &requestSrcAddr, requestSrcAddrLen);
+        if (result == -1) {
+            LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+        }
+        return;
+    }
+
+    if (startIndex == -1) {
+        // NAK a subrange at the front of the request range
+        RetryPacket nak = request;
+        nak.id = htonl(kRetryNakID);
+        nak.seqEnd = htons(retryFirstSeq - 1);
+        result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+                        &requestSrcAddr, requestSrcAddrLen);
+        if (result == -1) {
+            LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+        }
+
+        startIndex = 0;
+    } else if (endIndex == -1) {
+        // NAK a subrange at the back of the request range
+        RetryPacket nak = request;
+        nak.id = htonl(kRetryNakID);
+        nak.seqStart = htons(retryLastSeq + 1);
+        result = sendto(mSender->mSocket, &nak, sizeof(nak), 0,
+                        &requestSrcAddr, requestSrcAddrLen);
+        if (result == -1) {
+            LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+        }
+
+        endIndex = retry.size() - 1;
+    }
+
+    // send the retry packets
+    for (int i = startIndex; i <= endIndex; i++) {
+        const sp<TRTPPacket>& replyPacket = retry[i];
+
+        result = sendto(mSender->mSocket,
+                        replyPacket->getPacket(),
+                        replyPacket->getPacketLen(),
+                        0,
+                        &requestSrcAddr,
+                        requestSrcAddrLen);
+
+        if (result == -1) {
+            LOGW("%s sendto failed", __PRETTY_FUNCTION__);
+        }
+    }
+}
+
+// Endpoint
+
+AAH_TXSender::Endpoint::Endpoint()
+        : addr(0)
+        , port(0) { }
+
+AAH_TXSender::Endpoint::Endpoint(uint32_t a, uint16_t p)
+        : addr(a)
+        , port(p) {}
+
+bool AAH_TXSender::Endpoint::operator<(const Endpoint& other) const {
+    return ((addr < other.addr) ||
+            (addr == other.addr && port < other.port));
+}
+
+// EndpointState
+
+AAH_TXSender::EndpointState::EndpointState(uint32_t _epoch)
+    : retry(kRetryBufferCapacity)
+    , playerRefCount(1)
+    , trtpSeqNumber(0)
+    , nextProgramID(0)
+    , epoch(_epoch) { }
+
+// CircularBuffer
+
+template <typename T>
+CircularBuffer<T>::CircularBuffer(size_t capacity)
+        : mCapacity(capacity)
+        , mHead(0)
+        , mTail(0)
+        , mFillCount(0) {
+    mBuffer = new T[capacity];
+}
+
+template <typename T>
+CircularBuffer<T>::~CircularBuffer() {
+    delete [] mBuffer;
+}
+
+template <typename T>
+void CircularBuffer<T>::push_back(const T& item) {
+    if (this->isFull()) {
+        this->pop_front();
+    }
+    mBuffer[mHead] = item;
+    mHead = (mHead + 1) % mCapacity;
+    mFillCount++;
+}
+
+template <typename T>
+void CircularBuffer<T>::pop_front() {
+    CHECK(!isEmpty());
+    mBuffer[mTail] = T();
+    mTail = (mTail + 1) % mCapacity;
+    mFillCount--;
+}
+
+template <typename T>
+size_t CircularBuffer<T>::size() const {
+    return mFillCount;
+}
+
+template <typename T>
+bool CircularBuffer<T>::isFull() const {
+    return (mFillCount == mCapacity);
+}
+
+template <typename T>
+bool CircularBuffer<T>::isEmpty() const {
+    return (mFillCount == 0);
+}
+
+template <typename T>
+const T& CircularBuffer<T>::itemAt(size_t index) const {
+    CHECK(index < mFillCount);
+    return mBuffer[(mTail + index) % mCapacity];
+}
+
+template <typename T>
+const T& CircularBuffer<T>::operator[](size_t index) const {
+    return itemAt(index);
+}
+
+}  // namespace android
diff --git a/media/libaah_rtp/aah_tx_sender.h b/media/libaah_rtp/aah_tx_sender.h
new file mode 100644
index 0000000..47814de
--- /dev/null
+++ b/media/libaah_rtp/aah_tx_sender.h
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+#ifndef __AAH_TX_SENDER_H__
+#define __AAH_TX_SENDER_H__
+
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AHandlerReflector.h>
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+
+#include "aah_tx_packet.h"
+#include "pipe_event.h"
+
+namespace android {
+
+template <typename T> class CircularBuffer {
+  public:
+    CircularBuffer(size_t capacity);
+    ~CircularBuffer();
+    void push_back(const T& item);;
+    void pop_front();
+    size_t size() const;
+    bool isFull() const;
+    bool isEmpty() const;
+    const T& itemAt(size_t index) const;
+    const T& operator[](size_t index) const;
+
+  private:
+    T* mBuffer;
+    size_t mCapacity;
+    size_t mHead;
+    size_t mTail;
+    size_t mFillCount;
+};
+
+class AAH_TXSender : public virtual RefBase {
+  public:
+    ~AAH_TXSender();
+
+    static sp<AAH_TXSender> GetInstance();
+
+    ALooper::handler_id handlerID() { return mReflector->id(); }
+
+    // an IP address and port
+    struct Endpoint {
+        Endpoint();
+        Endpoint(uint32_t a, uint16_t p);
+        bool operator<(const Endpoint& other) const;
+
+        uint32_t addr;
+        uint16_t port;
+    };
+
+    uint16_t registerEndpoint(const Endpoint& endpoint);
+    void unregisterEndpoint(const Endpoint& endpoint);
+
+    enum {
+        kWhatSendPacket,
+        kWhatTrimRetryBuffers,
+        kWhatSendHeartbeats,
+    };
+
+    // fields for SendPacket messages
+    static const char* kSendPacketIPAddr;
+    static const char* kSendPacketPort;
+    static const char* kSendPacketTRTPPacket;
+
+  private:
+    AAH_TXSender();
+
+    static Mutex sLock;
+    static wp<AAH_TXSender> sInstance;
+    static uint32_t sNextEpoch;
+    static bool sNextEpochValid;
+
+    static uint32_t getNextEpoch();
+
+    typedef CircularBuffer<sp<TRTPPacket> > RetryBuffer;
+
+    // state maintained on a per-endpoint basis
+    struct EndpointState {
+        EndpointState(uint32_t epoch);
+        RetryBuffer retry;
+        int playerRefCount;
+        uint16_t trtpSeqNumber;
+        uint16_t nextProgramID;
+        uint32_t epoch;
+    };
+
+    friend class AHandlerReflector<AAH_TXSender>;
+    void onMessageReceived(const sp<AMessage>& msg);
+    void onSendPacket(const sp<AMessage>& msg);
+    void doSendPacket_l(const sp<TRTPPacket>& packet,
+                        const Endpoint& endpoint);
+    void trimRetryBuffers();
+    void sendHeartbeats();
+    bool shouldSendHeartbeats_l();
+
+    sp<ALooper> mLooper;
+    sp<AHandlerReflector<AAH_TXSender> > mReflector;
+
+    int mSocket;
+    nsecs_t mLastSentPacketTime;
+
+    DefaultKeyedVector<Endpoint, EndpointState*> mEndpointMap;
+    Mutex mEndpointLock;
+
+    static const int kRetryTrimIntervalUs;
+    static const int kHeartbeatIntervalUs;
+    static const int kRetryBufferCapacity;
+    static const nsecs_t kHeartbeatTimeout;
+
+    class RetryReceiver : public Thread {
+      private:
+        friend class AAH_TXSender;
+
+        RetryReceiver(AAH_TXSender* sender);
+        virtual ~RetryReceiver();
+        virtual bool threadLoop();
+        void handleRetryRequest();
+
+        static const int kMaxReceiverPacketLen;
+        static const uint32_t kRetryRequestID;
+        static const uint32_t kFastStartRequestID;
+        static const uint32_t kRetryNakID;
+
+        AAH_TXSender* mSender;
+        PipeEvent mWakeupEvent;
+    };
+
+    sp<RetryReceiver> mRetryReceiver;
+};
+
+struct RetryPacket {
+    uint32_t id;
+    uint32_t endpointIP;
+    uint16_t endpointPort;
+    uint16_t seqStart;
+    uint16_t seqEnd;
+} __attribute__((packed));
+
+}  // namespace android
+
+#endif  // __AAH_TX_SENDER_H__
diff --git a/media/libaah_rtp/pipe_event.cpp b/media/libaah_rtp/pipe_event.cpp
new file mode 100644
index 0000000..96c674b
--- /dev/null
+++ b/media/libaah_rtp/pipe_event.cpp
@@ -0,0 +1,86 @@
+/*
+ * 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 "LibAAH_RTP"
+#include <utils/Log.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include "pipe_event.h"
+
+namespace android {
+
+PipeEvent::PipeEvent() {
+    pipe_[0] = -1;
+    pipe_[1] = -1;
+
+    // Create the pipe.
+    if (pipe(pipe_) >= 0) {
+        // Set non-blocking mode on the read side of the pipe so we can
+        // easily drain it whenever we wakeup.
+        fcntl(pipe_[0], F_SETFL, O_NONBLOCK);
+    } else {
+        LOGE("Failed to create pipe event %d %d %d",
+             pipe_[0], pipe_[1], errno);
+        pipe_[0] = -1;
+        pipe_[1] = -1;
+    }
+}
+
+PipeEvent::~PipeEvent() {
+    if (pipe_[0] >= 0) {
+        close(pipe_[0]);
+    }
+
+    if (pipe_[1] >= 0) {
+        close(pipe_[1]);
+    }
+}
+
+void PipeEvent::clearPendingEvents() {
+    char drain_buffer[16];
+    while (read(pipe_[0], drain_buffer, sizeof(drain_buffer)) > 0) {
+        // No body.
+    }
+}
+
+bool PipeEvent::wait(int timeout) {
+    struct pollfd wait_fd;
+
+    wait_fd.fd = getWakeupHandle();
+    wait_fd.events = POLLIN;
+    wait_fd.revents = 0;
+
+    int res = poll(&wait_fd, 1, timeout);
+
+    if (res < 0) {
+        LOGE("Wait error in PipeEvent; sleeping to prevent overload!");
+        usleep(1000);
+    }
+
+    return (res > 0);
+}
+
+void PipeEvent::setEvent() {
+    char foo = 'q';
+    write(pipe_[1], &foo, 1);
+}
+
+}  // namespace android
+
diff --git a/media/libaah_rtp/pipe_event.h b/media/libaah_rtp/pipe_event.h
new file mode 100644
index 0000000..637bfef
--- /dev/null
+++ b/media/libaah_rtp/pipe_event.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#ifndef __PIPE_EVENT_H__
+#define __PIPE_EVENT_H__
+
+namespace android {
+
+class PipeEvent {
+  public:
+    PipeEvent();
+   ~PipeEvent();
+
+    bool initCheck() const {
+        return ((pipe_[0] >= 0) && (pipe_[1] >= 0));
+    }
+
+    int getWakeupHandle() const { return pipe_[0]; }
+
+    // block until the event fires; returns true if the event fired and false if
+    // the wait timed out.  Timeout is expressed in milliseconds; negative
+    // values mean wait forever.
+    bool wait(int timeout = -1);
+
+    void clearPendingEvents();
+    void setEvent();
+
+  private:
+    int pipe_[2];
+};
+
+}  // namespace android
+
+#endif  // __PIPE_EVENT_H__
diff --git a/media/libmedia/AudioEffect.cpp b/media/libmedia/AudioEffect.cpp
index 0633744..676007b 100644
--- a/media/libmedia/AudioEffect.cpp
+++ b/media/libmedia/AudioEffect.cpp
@@ -202,7 +202,7 @@
 status_t AudioEffect::setEnabled(bool enabled)
 {
     if (mStatus != NO_ERROR) {
-        return INVALID_OPERATION;
+        return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
     }
 
     status_t status = NO_ERROR;
@@ -231,7 +231,7 @@
 {
     if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) {
         LOGV("command() bad status %d", mStatus);
-        return INVALID_OPERATION;
+        return mStatus;
     }
 
     if (cmdCode == EFFECT_CMD_ENABLE || cmdCode == EFFECT_CMD_DISABLE) {
@@ -263,7 +263,7 @@
 status_t AudioEffect::setParameter(effect_param_t *param)
 {
     if (mStatus != NO_ERROR) {
-        return INVALID_OPERATION;
+        return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
     }
 
     if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -281,7 +281,7 @@
 status_t AudioEffect::setParameterDeferred(effect_param_t *param)
 {
     if (mStatus != NO_ERROR) {
-        return INVALID_OPERATION;
+        return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
     }
 
     if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -307,7 +307,7 @@
 status_t AudioEffect::setParameterCommit()
 {
     if (mStatus != NO_ERROR) {
-        return INVALID_OPERATION;
+        return (mStatus == ALREADY_EXISTS) ? INVALID_OPERATION : mStatus;
     }
 
     Mutex::Autolock _l(mCblk->lock);
@@ -321,7 +321,7 @@
 status_t AudioEffect::getParameter(effect_param_t *param)
 {
     if (mStatus != NO_ERROR && mStatus != ALREADY_EXISTS) {
-        return INVALID_OPERATION;
+        return mStatus;
     }
 
     if (param == NULL || param->psize == 0 || param->vsize == 0) {
@@ -341,7 +341,7 @@
 void AudioEffect::binderDied()
 {
     LOGW("IEffect died");
-    mStatus = NO_INIT;
+    mStatus = DEAD_OBJECT;
     if (mCbf) {
         status_t status = DEAD_OBJECT;
         mCbf(EVENT_ERROR, mUserData, &status);
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 8ebb652..4bc534b 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -79,7 +79,8 @@
 // ---------------------------------------------------------------------------
 
 AudioTrack::AudioTrack()
-    : mStatus(NO_INIT)
+    : mStatus(NO_INIT),
+      mIsTimed(false)
 {
 }
 
@@ -94,7 +95,8 @@
         void* user,
         int notificationFrames,
         int sessionId)
-    : mStatus(NO_INIT)
+    : mStatus(NO_INIT),
+      mIsTimed(false)
 {
     mStatus = set(streamType, sampleRate, format, channelMask,
             frameCount, flags, cbf, user, notificationFrames,
@@ -112,7 +114,8 @@
         void* user,
         int notificationFrames,
         int sessionId)
-    : mStatus(NO_INIT)
+    : mStatus(NO_INIT),
+      mIsTimed(false)
 {
     mStatus = set(streamType, sampleRate, format, channelMask,
             0, flags, cbf, user, notificationFrames,
@@ -520,6 +523,10 @@
 {
     int afSamplingRate;
 
+    if (mIsTimed) {
+        return INVALID_OPERATION;
+    }
+
     if (AudioSystem::getOutputSamplingRate(&afSamplingRate, mStreamType) != NO_ERROR) {
         return NO_INIT;
     }
@@ -533,6 +540,10 @@
 
 uint32_t AudioTrack::getSampleRate()
 {
+    if (mIsTimed) {
+        return INVALID_OPERATION;
+    }
+
     AutoMutex lock(mLock);
     return mCblk->sampleRate;
 }
@@ -558,6 +569,10 @@
         return NO_ERROR;
     }
 
+    if (mIsTimed) {
+        return INVALID_OPERATION;
+    }
+
     if (loopStart >= loopEnd ||
         loopEnd - loopStart > cblk->frameCount ||
         cblk->server > loopStart) {
@@ -641,6 +656,8 @@
 
 status_t AudioTrack::setPosition(uint32_t position)
 {
+    if (mIsTimed) return INVALID_OPERATION;
+
     AutoMutex lock(mLock);
     Mutex::Autolock _l(mCblk->lock);
 
@@ -790,6 +807,7 @@
                                                       ((uint16_t)flags) << 16,
                                                       sharedBuffer,
                                                       output,
+                                                      mIsTimed,
                                                       &mSessionId,
                                                       &status);
 
@@ -958,6 +976,7 @@
 {
 
     if (mSharedBuffer != 0) return INVALID_OPERATION;
+    if (mIsTimed) return INVALID_OPERATION;
 
     if (ssize_t(userSize) < 0) {
         // sanity-check. user is most-likely passing an error code.
@@ -1020,6 +1039,36 @@
 
 // -------------------------------------------------------------------------
 
+TimedAudioTrack::TimedAudioTrack() {
+    mIsTimed = true;
+}
+
+status_t TimedAudioTrack::allocateTimedBuffer(size_t size, sp<IMemory>* buffer)
+{
+    return mAudioTrack->allocateTimedBuffer(size, buffer);
+}
+
+status_t TimedAudioTrack::queueTimedBuffer(const sp<IMemory>& buffer,
+                                           int64_t pts)
+{
+    // restart track if it was disabled by audioflinger due to previous underrun
+    if (mActive && (mCblk->flags & CBLK_DISABLED_MSK)) {
+        mCblk->flags &= ~CBLK_DISABLED_ON;
+        LOGW("queueTimedBuffer() track %p disabled, restarting", this);
+        mAudioTrack->start();
+    }
+
+    return mAudioTrack->queueTimedBuffer(buffer, pts);
+}
+
+status_t TimedAudioTrack::setMediaTimeTransform(const LinearTransform& xform,
+                                                TargetTimeline target)
+{
+    return mAudioTrack->setMediaTimeTransform(xform, target);
+}
+
+// -------------------------------------------------------------------------
+
 bool AudioTrack::processAudioBuffer(const sp<AudioTrackThread>& thread)
 {
     Buffer audioBuffer;
@@ -1462,4 +1511,3 @@
 // -------------------------------------------------------------------------
 
 }; // namespace android
-
diff --git a/media/libmedia/IAudioFlinger.cpp b/media/libmedia/IAudioFlinger.cpp
index d58834b..c7b49cd 100644
--- a/media/libmedia/IAudioFlinger.cpp
+++ b/media/libmedia/IAudioFlinger.cpp
@@ -90,6 +90,7 @@
                                 uint32_t flags,
                                 const sp<IMemory>& sharedBuffer,
                                 int output,
+                                bool isTimed,
                                 int *sessionId,
                                 status_t *status)
     {
@@ -105,6 +106,7 @@
         data.writeInt32(flags);
         data.writeStrongBinder(sharedBuffer->asBinder());
         data.writeInt32(output);
+        data.writeInt32(isTimed);
         int lSessionId = 0;
         if (sessionId != NULL) {
             lSessionId = *sessionId;
@@ -684,11 +686,12 @@
             uint32_t flags = data.readInt32();
             sp<IMemory> buffer = interface_cast<IMemory>(data.readStrongBinder());
             int output = data.readInt32();
+            bool isTimed = data.readInt32();
             int sessionId = data.readInt32();
             status_t status;
             sp<IAudioTrack> track = createTrack(pid,
                     streamType, sampleRate, format,
-                    channelCount, bufferCount, flags, buffer, output, &sessionId, &status);
+                    channelCount, bufferCount, flags, buffer, output, isTimed, &sessionId, &status);
             reply->writeInt32(sessionId);
             reply->writeInt32(status);
             reply->writeStrongBinder(track->asBinder());
diff --git a/media/libmedia/IAudioTrack.cpp b/media/libmedia/IAudioTrack.cpp
index bc8ff34..a496fb8 100644
--- a/media/libmedia/IAudioTrack.cpp
+++ b/media/libmedia/IAudioTrack.cpp
@@ -35,7 +35,10 @@
     FLUSH,
     MUTE,
     PAUSE,
-    ATTACH_AUX_EFFECT
+    ATTACH_AUX_EFFECT,
+    ALLOCATE_TIMED_BUFFER,
+    QUEUE_TIMED_BUFFER,
+    SET_MEDIA_TIME_TRANSFORM,
 };
 
 class BpAudioTrack : public BpInterface<IAudioTrack>
@@ -113,6 +116,52 @@
         }
         return status;
     }
+
+    virtual status_t allocateTimedBuffer(size_t size, sp<IMemory>* buffer) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+        data.writeInt32(size);
+        status_t status = remote()->transact(ALLOCATE_TIMED_BUFFER,
+                                             data, &reply);
+        if (status == NO_ERROR) {
+            status = reply.readInt32();
+            if (status == NO_ERROR) {
+                *buffer = interface_cast<IMemory>(reply.readStrongBinder());
+            }
+        }
+        return status;
+    }
+
+    virtual status_t queueTimedBuffer(const sp<IMemory>& buffer,
+                                      int64_t pts) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+        data.writeStrongBinder(buffer->asBinder());
+        data.writeInt64(pts);
+        status_t status = remote()->transact(QUEUE_TIMED_BUFFER,
+                                             data, &reply);
+        if (status == NO_ERROR) {
+            status = reply.readInt32();
+        }
+        return status;
+    }
+
+    virtual status_t setMediaTimeTransform(const LinearTransform& xform,
+                                           int target) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IAudioTrack::getInterfaceDescriptor());
+        data.writeInt64(xform.a_zero);
+        data.writeInt64(xform.b_zero);
+        data.writeInt32(xform.a_to_b_numer);
+        data.writeInt32(xform.a_to_b_denom);
+        data.writeInt32(target);
+        status_t status = remote()->transact(SET_MEDIA_TIME_TRANSFORM,
+                                             data, &reply);
+        if (status == NO_ERROR) {
+            status = reply.readInt32();
+        }
+        return status;
+    }
 };
 
 IMPLEMENT_META_INTERFACE(AudioTrack, "android.media.IAudioTrack");
@@ -158,10 +207,38 @@
             reply->writeInt32(attachAuxEffect(data.readInt32()));
             return NO_ERROR;
         } break;
+        case ALLOCATE_TIMED_BUFFER: {
+            CHECK_INTERFACE(IAudioTrack, data, reply);
+            sp<IMemory> buffer;
+            status_t status = allocateTimedBuffer(data.readInt32(), &buffer);
+            reply->writeInt32(status);
+            if (status == NO_ERROR) {
+                reply->writeStrongBinder(buffer->asBinder());
+            }
+            return NO_ERROR;
+        } break;
+        case QUEUE_TIMED_BUFFER: {
+            CHECK_INTERFACE(IAudioTrack, data, reply);
+            sp<IMemory> buffer = interface_cast<IMemory>(
+                data.readStrongBinder());
+            uint64_t pts = data.readInt64();
+            reply->writeInt32(queueTimedBuffer(buffer, pts));
+            return NO_ERROR;
+        } break;
+        case SET_MEDIA_TIME_TRANSFORM: {
+            CHECK_INTERFACE(IAudioTrack, data, reply);
+            LinearTransform xform;
+            xform.a_zero = data.readInt64();
+            xform.b_zero = data.readInt64();
+            xform.a_to_b_numer = data.readInt32();
+            xform.a_to_b_denom = data.readInt32();
+            int target = data.readInt32();
+            reply->writeInt32(setMediaTimeTransform(xform, target));
+            return NO_ERROR;
+        } break;
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
 }
 
 }; // namespace android
-
diff --git a/media/libmedia/IEffect.cpp b/media/libmedia/IEffect.cpp
index a945b97..f586aad 100644
--- a/media/libmedia/IEffect.cpp
+++ b/media/libmedia/IEffect.cpp
@@ -83,8 +83,15 @@
             size = *pReplySize;
         }
         data.writeInt32(size);
-        remote()->transact(COMMAND, data, &reply);
-        status_t status = reply.readInt32();
+
+        status_t status = remote()->transact(COMMAND, data, &reply);
+        if (status != NO_ERROR) {
+            if (pReplySize != NULL)
+                *pReplySize = 0;
+            return status;
+        }
+
+        status = reply.readInt32();
         size = reply.readInt32();
         if (size != 0 && pReplyData != NULL && pReplySize != NULL) {
             reply.read(pReplyData, size);
diff --git a/media/libmedia/IMediaPlayer.cpp b/media/libmedia/IMediaPlayer.cpp
index 9c1e6b7..36a9efe 100644
--- a/media/libmedia/IMediaPlayer.cpp
+++ b/media/libmedia/IMediaPlayer.cpp
@@ -15,6 +15,7 @@
 ** limitations under the License.
 */
 
+#include <arpa/inet.h>
 #include <stdint.h>
 #include <sys/types.h>
 
@@ -55,6 +56,7 @@
     SET_VIDEO_SURFACETEXTURE,
     SET_PARAMETER,
     GET_PARAMETER,
+    SET_RETRANSMIT_ENDPOINT,
 };
 
 class BpMediaPlayer: public BpInterface<IMediaPlayer>
@@ -291,6 +293,25 @@
         return remote()->transact(GET_PARAMETER, data, reply);
     }
 
+    status_t setRetransmitEndpoint(const struct sockaddr_in* endpoint) {
+        Parcel data, reply;
+        status_t err;
+
+        data.writeInterfaceToken(IMediaPlayer::getInterfaceDescriptor());
+        if (NULL != endpoint) {
+            data.writeInt32(sizeof(*endpoint));
+            data.write(endpoint, sizeof(*endpoint));
+        } else {
+            data.writeInt32(0);
+        }
+
+        err = remote()->transact(SET_RETRANSMIT_ENDPOINT, data, &reply);
+        if (OK != err) {
+            return err;
+        }
+
+        return reply.readInt32();
+    }
 };
 
 IMPLEMENT_META_INTERFACE(MediaPlayer, "android.media.IMediaPlayer");
@@ -459,6 +480,20 @@
             CHECK_INTERFACE(IMediaPlayer, data, reply);
             return getParameter(data.readInt32(), reply);
         } break;
+        case SET_RETRANSMIT_ENDPOINT: {
+            CHECK_INTERFACE(IMediaPlayer, data, reply);
+
+            struct sockaddr_in endpoint;
+            int amt = data.readInt32();
+            if (amt == sizeof(endpoint)) {
+                data.read(&endpoint, sizeof(struct sockaddr_in));
+                reply->writeInt32(setRetransmitEndpoint(&endpoint));
+            } else {
+                reply->writeInt32(setRetransmitEndpoint(NULL));
+            }
+
+            return NO_ERROR;
+        } break;
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/media/libmedia/Visualizer.cpp b/media/libmedia/Visualizer.cpp
index bf40481..00113d9 100644
--- a/media/libmedia/Visualizer.cpp
+++ b/media/libmedia/Visualizer.cpp
@@ -173,7 +173,7 @@
         uint32_t replySize = mCaptureSize;
         status = command(VISUALIZER_CMD_CAPTURE, 0, NULL, &replySize, waveform);
         LOGV("getWaveForm() command returned %d", status);
-        if (replySize == 0) {
+        if ((status == NO_ERROR) && (replySize == 0)) {
             status = NOT_ENOUGH_DATA;
         }
     } else {
diff --git a/media/libmedia/mediaplayer.cpp b/media/libmedia/mediaplayer.cpp
index c2e1ddf..111962c 100644
--- a/media/libmedia/mediaplayer.cpp
+++ b/media/libmedia/mediaplayer.cpp
@@ -63,6 +63,7 @@
     mAudioSessionId = AudioSystem::newAudioSessionId();
     AudioSystem::acquireAudioSessionId(mAudioSessionId);
     mSendLevel = 0;
+    mRetransmitEndpointValid = false;
 }
 
 MediaPlayer::~MediaPlayer()
@@ -95,6 +96,7 @@
     mCurrentPosition = -1;
     mSeekPosition = -1;
     mVideoWidth = mVideoHeight = 0;
+    mRetransmitEndpointValid = false;
 }
 
 status_t MediaPlayer::setListener(const sp<MediaPlayerListener>& listener)
@@ -146,7 +148,8 @@
         const sp<IMediaPlayerService>& service(getMediaPlayerService());
         if (service != 0) {
             sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));
-            if (NO_ERROR != player->setDataSource(url, headers)) {
+            if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
+                (NO_ERROR != player->setDataSource(url, headers))) {
                 player.clear();
             }
             err = attachNewPlayer(player);
@@ -162,7 +165,8 @@
     const sp<IMediaPlayerService>& service(getMediaPlayerService());
     if (service != 0) {
         sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));
-        if (NO_ERROR != player->setDataSource(fd, offset, length)) {
+        if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
+            (NO_ERROR != player->setDataSource(fd, offset, length))) {
             player.clear();
         }
         err = attachNewPlayer(player);
@@ -177,7 +181,8 @@
     const sp<IMediaPlayerService>& service(getMediaPlayerService());
     if (service != 0) {
         sp<IMediaPlayer> player(service->create(getpid(), this, mAudioSessionId));
-        if (NO_ERROR != player->setDataSource(source)) {
+        if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
+            (NO_ERROR != player->setDataSource(source))) {
             player.clear();
         }
         err = attachNewPlayer(player);
@@ -471,6 +476,18 @@
     return NO_ERROR;
 }
 
+status_t MediaPlayer::doSetRetransmitEndpoint(const sp<IMediaPlayer>& player) {
+    Mutex::Autolock _l(mLock);
+
+    if (player == NULL)
+        return UNKNOWN_ERROR;
+
+    if (mRetransmitEndpointValid)
+        return player->setRetransmitEndpoint(&mRetransmitEndpoint);
+
+    return OK;
+}
+
 status_t MediaPlayer::reset()
 {
     LOGV("reset");
@@ -599,6 +616,36 @@
     return INVALID_OPERATION;
 }
 
+status_t MediaPlayer::setRetransmitEndpoint(const char* addrString,
+                                            uint16_t port) {
+    LOGV("MediaPlayer::setRetransmitEndpoint(%s:%hu)",
+            addrString ? addrString : "(null)", port);
+
+    Mutex::Autolock _l(mLock);
+    if ((mPlayer == NULL) && (mCurrentState == MEDIA_PLAYER_IDLE)) {
+        if (NULL == addrString) {
+            mRetransmitEndpointValid = false;
+            return OK;
+        }
+
+        struct in_addr saddr;
+        if(!inet_aton(addrString, &saddr)) {
+            return BAD_VALUE;
+        }
+
+        memset(&mRetransmitEndpoint, 0, sizeof(&mRetransmitEndpoint));
+        mRetransmitEndpoint.sin_family = AF_INET;
+        mRetransmitEndpoint.sin_addr   = saddr;
+        mRetransmitEndpoint.sin_port   = htons(port);
+        mRetransmitEndpointValid       = true;
+
+        return OK;
+    }
+
+    LOGV("setRetransmitEndpoint: after idle");
+    return INVALID_OPERATION;
+}
+
 void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
 {
     LOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index a3e2517..e521648 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -29,7 +29,8 @@
 	libstagefright_omx    			\
 	libstagefright_foundation       \
 	libgui                          \
-	libdl
+	libdl                           \
+	libaah_rtp
 
 LOCAL_STATIC_LIBRARIES := \
         libstagefright_nuplayer                 \
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index e8d0f0c..3b05085 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -70,6 +70,11 @@
 
 #include <OMX.h>
 
+namespace android {
+sp<MediaPlayerBase> createAAH_TXPlayer();
+sp<MediaPlayerBase> createAAH_RXPlayer();
+}
+
 namespace {
 using android::media::Metadata;
 using android::status_t;
@@ -487,6 +492,7 @@
     mStatus = NO_INIT;
     mAudioSessionId = audioSessionId;
     mUID = uid;
+    mRetransmitEndpointValid = false;
 
 #if CALLBACK_ANTAGONIZER
     LOGD("create Antagonizer");
@@ -593,6 +599,10 @@
         return NU_PLAYER;
     }
 
+    if (!strncasecmp("aahRX://", url, 8)) {
+        return AAH_RX_PLAYER;
+    }
+
     // use MidiFile for MIDI extensions
     int lenURL = strlen(url);
     for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
@@ -629,6 +639,14 @@
             LOGV("Create Test Player stub");
             p = new TestPlayerStub();
             break;
+        case AAH_RX_PLAYER:
+            LOGV(" create A@H RX Player");
+            p = createAAH_RXPlayer();
+            break;
+        case AAH_TX_PLAYER:
+            LOGV(" create A@H TX Player");
+            p = createAAH_TXPlayer();
+            break;
         default:
             LOGE("Unknown player type: %d", playerType);
             return NULL;
@@ -695,7 +713,12 @@
         close(fd);
         return mStatus;
     } else {
-        player_type playerType = getPlayerType(url);
+        // Until re-transmit functionality is added to the existing core android
+        // players, we use the special AAH TX player whenever we were configured
+        // for retransmission.
+        player_type playerType = mRetransmitEndpointValid
+                               ? AAH_TX_PLAYER
+                               : getPlayerType(url);
         LOGV("player type = %d", playerType);
 
         // create the right type of player
@@ -711,6 +734,15 @@
         LOGV(" setDataSource");
         mStatus = p->setDataSource(url, headers);
         if (mStatus == NO_ERROR) {
+            // Set the re-transmission endpoint if one was chosen.
+            if (mRetransmitEndpointValid) {
+                mStatus = p->setRetransmitEndpoint(&mRetransmitEndpoint);
+                if (mStatus != NO_ERROR) {
+                    LOGE("setRetransmitEndpoint error: %d", mStatus);
+                    return mStatus;
+                }
+            }
+
             mPlayer = p;
         } else {
             LOGE("  error: %d", mStatus);
@@ -745,7 +777,12 @@
         LOGV("calculated length = %lld", length);
     }
 
-    player_type playerType = getPlayerType(fd, offset, length);
+    // Until re-transmit functionality is added to the existing core android
+    // players, we use the special AAH TX player whenever we were configured for
+    // retransmission.
+    player_type playerType = mRetransmitEndpointValid
+                           ? AAH_TX_PLAYER
+                           : getPlayerType(fd, offset, length);
     LOGV("player type = %d", playerType);
 
     // create the right type of player
@@ -759,7 +796,18 @@
 
     // now set data source
     mStatus = p->setDataSource(fd, offset, length);
-    if (mStatus == NO_ERROR) mPlayer = p;
+    if (mStatus == NO_ERROR) {
+        // Set the re-transmission endpoint if one was chosen.
+        if (mRetransmitEndpointValid) {
+            mStatus = p->setRetransmitEndpoint(&mRetransmitEndpoint);
+            if (mStatus != NO_ERROR) {
+                LOGE("setRetransmitEndpoint error: %d", mStatus);
+                return mStatus;
+            }
+        }
+
+        mPlayer = p;
+    }
 
     return mStatus;
 }
@@ -767,7 +815,14 @@
 status_t MediaPlayerService::Client::setDataSource(
         const sp<IStreamSource> &source) {
     // create the right type of player
-    sp<MediaPlayerBase> p = createPlayer(NU_PLAYER);
+    // Until re-transmit functionality is added to the existing core android
+    // players, we use the special AAH TX player whenever we were configured for
+    // retransmission.
+    player_type playerType = mRetransmitEndpointValid
+                           ? AAH_TX_PLAYER
+                           : NU_PLAYER;
+    LOGV("player type = %d", playerType);
+    sp<MediaPlayerBase> p = createPlayer(playerType);
 
     if (p == NULL) {
         return NO_INIT;
@@ -782,6 +837,15 @@
     mStatus = p->setDataSource(source);
 
     if (mStatus == OK) {
+        // Set the re-transmission endpoint if one was chosen.
+        if (mRetransmitEndpointValid) {
+            mStatus = p->setRetransmitEndpoint(&mRetransmitEndpoint);
+            if (mStatus != NO_ERROR) {
+                LOGE("setRetransmitEndpoint error: %d", mStatus);
+                return mStatus;
+            }
+        }
+
         mPlayer = p;
     }
 
@@ -1005,6 +1069,7 @@
 status_t MediaPlayerService::Client::reset()
 {
     LOGV("[%d] reset", mConnId);
+    mRetransmitEndpointValid = false;
     sp<MediaPlayerBase> p = getPlayer();
     if (p == 0) return UNKNOWN_ERROR;
     return p->reset();
@@ -1031,9 +1096,21 @@
 status_t MediaPlayerService::Client::setVolume(float leftVolume, float rightVolume)
 {
     LOGV("[%d] setVolume(%f, %f)", mConnId, leftVolume, rightVolume);
-    // TODO: for hardware output, call player instead
-    Mutex::Autolock l(mLock);
-    if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume);
+
+    // for hardware output, call player instead
+    sp<MediaPlayerBase> p = getPlayer();
+    {
+      Mutex::Autolock l(mLock);
+      if (p != 0 && p->hardwareOutput()) {
+          MediaPlayerHWInterface* hwp =
+                  reinterpret_cast<MediaPlayerHWInterface*>(p.get());
+          return hwp->setVolume(leftVolume, rightVolume);
+      } else {
+          if (mAudioOutput != 0) mAudioOutput->setVolume(leftVolume, rightVolume);
+          return NO_ERROR;
+      }
+    }
+
     return NO_ERROR;
 }
 
@@ -1067,6 +1144,36 @@
     return p->getParameter(key, reply);
 }
 
+status_t MediaPlayerService::Client::setRetransmitEndpoint(
+        const struct sockaddr_in* endpoint) {
+
+    if (NULL != endpoint) {
+        uint32_t a = ntohl(endpoint->sin_addr.s_addr);
+        uint16_t p = ntohs(endpoint->sin_port);
+        LOGV("[%d] setRetransmitEndpoint(%u.%u.%u.%u:%hu)", mConnId,
+                (a >> 24), (a >> 16) & 0xFF, (a >> 8) & 0xFF, (a & 0xFF), p);
+    } else {
+        LOGV("[%d] setRetransmitEndpoint = <none>", mConnId);
+    }
+
+    sp<MediaPlayerBase> p = getPlayer();
+
+    // Right now, the only valid time to set a retransmit endpoint is before
+    // player selection has been made (since the presence or absence of a
+    // retransmit endpoint is going to determine which player is selected during
+    // setDataSource).
+    if (p != 0) return INVALID_OPERATION;
+
+    if (NULL != endpoint) {
+        mRetransmitEndpoint = *endpoint;
+        mRetransmitEndpointValid = true;
+    } else {
+        mRetransmitEndpointValid = false;
+    }
+
+    return NO_ERROR;
+}
+
 void MediaPlayerService::Client::notify(
         void* cookie, int msg, int ext1, int ext2, const Parcel *obj)
 {
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index 04d9e28..25ed054 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -18,6 +18,8 @@
 #ifndef ANDROID_MEDIAPLAYERSERVICE_H
 #define ANDROID_MEDIAPLAYERSERVICE_H
 
+#include <arpa/inet.h>
+
 #include <utils/Log.h>
 #include <utils/threads.h>
 #include <utils/List.h>
@@ -271,6 +273,7 @@
         virtual status_t        attachAuxEffect(int effectId);
         virtual status_t        setParameter(int key, const Parcel &request);
         virtual status_t        getParameter(int key, Parcel *reply);
+        virtual status_t        setRetransmitEndpoint(const struct sockaddr_in* endpoint);
 
         sp<MediaPlayerBase>     createPlayer(player_type playerType);
 
@@ -333,6 +336,8 @@
                     uid_t                       mUID;
                     sp<ANativeWindow>           mConnectedWindow;
                     sp<IBinder>                 mConnectedWindowBinder;
+                    struct sockaddr_in          mRetransmitEndpoint;
+                    bool                        mRetransmitEndpointValid;
 
         // Metadata filters.
         media::Metadata::Filter mMetadataAllow;  // protected by mLock
diff --git a/native/include/android/configuration.h b/native/include/android/configuration.h
index 2444c4b..4d683fb 100644
--- a/native/include/android/configuration.h
+++ b/native/include/android/configuration.h
@@ -79,6 +79,7 @@
     ACONFIGURATION_UI_MODE_TYPE_DESK = 0x02,
     ACONFIGURATION_UI_MODE_TYPE_CAR = 0x03,
     ACONFIGURATION_UI_MODE_TYPE_TELEVISION = 0x04,
+    ACONFIGURATION_UI_MODE_TYPE_APPLIANCE = 0x05,
 
     ACONFIGURATION_UI_MODE_NIGHT_ANY = 0x00,
     ACONFIGURATION_UI_MODE_NIGHT_NO = 0x1,
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 1ebed1f..cdb16a0 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -43,6 +43,7 @@
     <bool name="assisted_gps_enabled">true</bool>
     <!--  0 == mobile, 1 == wifi. -->
     <integer name="def_network_preference">1</integer>
+    <bool name="def_netstats_enabled">true</bool>
     <bool name="def_usb_mass_storage_enabled">true</bool>
     <bool name="def_wifi_on">false</bool>
     <bool name="def_networks_available_notification_on">true</bool>
@@ -70,6 +71,8 @@
     <integer name="def_lockscreen_sounds_enabled">1</integer>
     <string name="def_lock_sound" translatable="false">/system/media/audio/ui/Lock.ogg</string>
     <string name="def_unlock_sound" translatable="false">/system/media/audio/ui/Unlock.ogg</string>
+    <bool name="def_lockscreen_disabled">false</bool>
+    <bool name="def_device_provisioned">false</bool>
 
     <!-- Notifications use ringer volume -->
     <bool name="def_notifications_use_ring_volume">true</bool>
@@ -136,4 +139,11 @@
     <bool name="def_dtmf_tones_enabled">true</bool>
     <!-- Default for UI touch sounds enabled -->
     <bool name="def_sound_effects_enabled">true</bool>
+
+    <!-- Development settings -->
+    <bool name="def_stay_on_while_plugged_in">false</bool>
+
+    <!-- Number of retries for connecting to DHCP.
+         Value here is the same as WifiStateMachine.DEFAULT_MAX_DHCP_RETRIES -->
+    <integer name="def_max_dhcp_retries">9</integer>
 </resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index a5e3483..2081b95 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -1337,7 +1337,9 @@
             loadBooleanSetting(stmt, Settings.System.DIM_SCREEN,
                     R.bool.def_dim_screen);
             loadSetting(stmt, Settings.System.STAY_ON_WHILE_PLUGGED_IN,
-                    "1".equals(SystemProperties.get("ro.kernel.qemu")) ? 1 : 0);
+                    ("1".equals(SystemProperties.get("ro.kernel.qemu")) ||
+                        mContext.getResources().getBoolean(R.bool.def_stay_on_while_plugged_in))
+                     ? 1 : 0);
             loadIntegerSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT,
                     R.integer.def_screen_off_timeout);
     
@@ -1554,6 +1556,18 @@
 
             loadBooleanSetting(stmt, Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
                     R.bool.def_accessibility_speak_password);
+
+            loadBooleanSetting(stmt, Settings.System.LOCKSCREEN_DISABLED,
+                    R.bool.def_lockscreen_disabled);
+
+            loadBooleanSetting(stmt, Settings.Secure.DEVICE_PROVISIONED,
+                    R.bool.def_device_provisioned);
+
+            loadBooleanSetting(stmt, Settings.Secure.NETSTATS_ENABLED,
+                    R.bool.def_netstats_enabled);
+
+            loadIntegerSetting(stmt, Settings.Secure.WIFI_MAX_DHCP_RETRY_COUNT,
+                    R.integer.def_max_dhcp_retries);
         } finally {
             if (stmt != null) stmt.close();
         }
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index fe5b983..c886eea 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -13,3 +13,5 @@
   public void setGlowAlpha(float);
   public void setGlowScale(float);
 }
+
+-keep class com.android.systemui.statusbar.tv.TvStatusBar
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 005d12f..c424284 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.ServiceManager;
+import android.util.DisplayMetrics;
 import android.util.AttributeSet;
 import android.util.Slog;
 import android.view.animation.AccelerateInterpolator;
@@ -55,11 +56,11 @@
 
     protected IStatusBarService mBarService;
     final Display mDisplay;
+    private boolean mNaturallyPortrait = true;
     View mCurrentView = null;
     View[] mRotatedViews = new View[4];
 
     int mBarSize;
-    boolean mVertical;
 
     boolean mHidden, mLowProfile, mShowMenu;
     int mDisabledFlags = 0;
@@ -119,9 +120,12 @@
         mBarService = IStatusBarService.Stub.asInterface(
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
+        DisplayMetrics metrics = new DisplayMetrics();
+        mDisplay.getMetrics(metrics);
+        mNaturallyPortrait = metrics.heightPixels >= metrics.widthPixels;
+
         final Resources res = mContext.getResources();
         mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
-        mVertical = false;
         mShowMenu = false;
     }
 
@@ -252,13 +256,15 @@
     }
 
     public void reorient() {
-        final int rot = mDisplay.getRotation();
+        int rot = mDisplay.getRotation();
+        // cycle all views around 90 degrees if this is a landscape-natural device
+        if (!mNaturallyPortrait) rot = (rot+1)%4;
+
         for (int i=0; i<4; i++) {
             mRotatedViews[i].setVisibility(View.GONE);
         }
         mCurrentView = mRotatedViews[rot];
         mCurrentView.setVisibility(View.VISIBLE);
-        mVertical = (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270);
 
         // force the low profile & disabled states into compliance
         setLowProfile(mLowProfile, false, true /* force */);
@@ -352,9 +358,9 @@
                         mCurrentView.getWidth(), mCurrentView.getHeight(),
                         visibilityToString(mCurrentView.getVisibility())));
 
-        pw.println(String.format("      disabled=0x%08x vertical=%s hidden=%s low=%s menu=%s",
+        pw.println(String.format("      disabled=0x%08x portrait=%s hidden=%s low=%s menu=%s",
                         mDisabledFlags,
-                        mVertical ? "true" : "false",
+                        mNaturallyPortrait ? "true" : "false",
                         mHidden ? "true" : "false",
                         mLowProfile ? "true" : "false",
                         mShowMenu ? "true" : "false"));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index 6913239..2b1c1ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -448,8 +448,7 @@
             // Sanity-check that someone hasn't set up the config wrong and asked for a navigation
             // bar on a tablet that has only the system bar
             if (mWindowManager.hasNavigationBar()) {
-                throw new RuntimeException(
-                        "Tablet device cannot show navigation bar and system bar");
+                Slog.e(TAG, "Tablet device cannot show navigation bar and system bar");
             }
         } catch (RemoteException ex) {
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
new file mode 100644
index 0000000..6bc4467
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2012 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.systemui.statusbar.tv;
+
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.statusbar.StatusBarNotification;
+import com.android.systemui.statusbar.StatusBar;
+
+import android.os.IBinder;
+import android.view.View;
+
+/*
+ * Status bar implementation for "large screen" products that mostly present no on-screen nav
+ */
+
+public class TvStatusBar extends StatusBar {
+    View mView;
+
+    @Override
+    public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
+    }
+
+    @Override
+    public void updateIcon(String slot, int index, int viewIndex, StatusBarIcon old,
+            StatusBarIcon icon) {
+    }
+
+    @Override
+    public void removeIcon(String slot, int index, int viewIndex) {
+    }
+
+    @Override
+    public void addNotification(IBinder key, StatusBarNotification notification) {
+    }
+
+    @Override
+    public void updateNotification(IBinder key, StatusBarNotification notification) {
+    }
+
+    @Override
+    public void removeNotification(IBinder key) {
+    }
+
+    @Override
+    public void disable(int state) {
+    }
+
+    @Override
+    public void animateExpand() {
+    }
+
+    @Override
+    public void setSystemUiVisibility(int vis) {
+    }
+
+    @Override
+    public void topAppWindowChanged(boolean visible) {
+    }
+
+    @Override
+    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
+    }
+
+    @Override
+    public void setHardKeyboardStatus(boolean available, boolean enabled) {
+    }
+
+    @Override
+    public void toggleRecentApps() {
+    }
+
+    @Override
+    protected View makeStatusBarView() {
+        synchronized (this) {
+            if (mView == null) {
+                mView = new View(mContext);
+            }
+        }
+        return mView;
+    }
+
+    @Override
+    protected int getStatusBarGravity() {
+        return 0;
+    }
+
+    @Override
+    public int getStatusBarHeight() {
+        return 0;
+    }
+
+    @Override
+    public void animateCollapse() {
+    }
+
+}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
index abed18f..83f7788 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneFallbackEventHandler.java
@@ -52,8 +52,7 @@
     }
 
     public void preDispatchKeyEvent(KeyEvent event) {
-        getAudioManager().preDispatchKeyEvent(event.getKeyCode(),
-                AudioManager.USE_DEFAULT_STREAM_TYPE);
+        getAudioManager().preDispatchKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE);
     }
 
     public boolean dispatchKeyEvent(KeyEvent event) {
@@ -79,7 +78,7 @@
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
-                getAudioManager().handleKeyDown(keyCode, AudioManager.USE_DEFAULT_STREAM_TYPE);
+                getAudioManager().handleKeyDown(event, AudioManager.USE_DEFAULT_STREAM_TYPE);
                 return true;
             }
 
@@ -197,8 +196,7 @@
                     AudioManager audioManager = (AudioManager)mContext.getSystemService(
                             Context.AUDIO_SERVICE);
                     if (audioManager != null) {
-                        getAudioManager().handleKeyUp(keyCode,
-                                AudioManager.USE_DEFAULT_STREAM_TYPE);
+                        getAudioManager().handleKeyUp(event, AudioManager.USE_DEFAULT_STREAM_TYPE);
                     }
                 }
                 return true;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index f1fe43b..b87b8c3 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -1416,7 +1416,7 @@
                 // doesn't have one of these.  In this case, we execute it here and
                 // eat the event instead, because we have mVolumeControlStreamType
                 // and they don't.
-                getAudioManager().handleKeyDown(keyCode, mVolumeControlStreamType);
+                getAudioManager().handleKeyDown(event, mVolumeControlStreamType);
                 return true;
             }
 
@@ -1478,7 +1478,7 @@
                 // doesn't have one of these.  In this case, we execute it here and
                 // eat the event instead, because we have mVolumeControlStreamType
                 // and they don't.
-                getAudioManager().handleKeyUp(keyCode, mVolumeControlStreamType);
+                getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
                 return true;
             }
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 0de76a7..adbb8c4 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -283,7 +283,8 @@
 
     /** If true, hitting shift & menu will broadcast Intent.ACTION_BUG_REPORT */
     boolean mEnableShiftMenuBugReports = false;
-    
+
+    boolean mHeadless;
     boolean mSafeMode;
     WindowState mStatusBar = null;
     boolean mStatusBarCanHide;
@@ -316,7 +317,7 @@
     boolean mSystemReady;
     boolean mSystemBooted;
     boolean mHdmiPlugged;
-    int mUiMode = Configuration.UI_MODE_TYPE_NORMAL;
+    int mUiMode;
     int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
     int mLidOpenRotation;
     int mCarDockRotation;
@@ -664,7 +665,7 @@
         if (mGlobalActions == null) {
             mGlobalActions = new GlobalActions(mContext);
         }
-        final boolean keyguardShowing = mKeyguardMediator.isShowingAndNotHidden();
+        final boolean keyguardShowing = keyguardIsShowingTq();
         mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
         if (keyguardShowing) {
             // since it took two seconds of long press to bring this up,
@@ -761,7 +762,11 @@
         mWindowManager = windowManager;
         mWindowManagerFuncs = windowManagerFuncs;
         mPowerManager = powerManager;
-        mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager);
+        mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
+        if (!mHeadless) {
+            // don't create KeyguardViewMediator if headless
+            mKeyguardMediator = new KeyguardViewMediator(context, this, powerManager);
+        }
         mHandler = new Handler();
         mOrientationListener = new MyOrientationListener(mContext);
         try {
@@ -771,6 +776,8 @@
         settingsObserver.observe();
         mShortcutManager = new ShortcutManager(context, mHandler);
         mShortcutManager.observe();
+        mUiMode = context.getResources().getInteger(
+                com.android.internal.R.integer.config_defaultUiModeType);
         mHomeIntent =  new Intent(Intent.ACTION_MAIN, null);
         mHomeIntent.addCategory(Intent.CATEGORY_HOME);
         mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -1458,12 +1465,8 @@
     }
     
     static ITelephony getTelephonyService() {
-        ITelephony telephonyService = ITelephony.Stub.asInterface(
+        return ITelephony.Stub.asInterface(
                 ServiceManager.checkService(Context.TELEPHONY_SERVICE));
-        if (telephonyService == null) {
-            Log.w(TAG, "Unable to find ITelephony interface.");
-        }
-        return telephonyService;
     }
 
     static IAudioService getAudioService() {
@@ -1786,7 +1789,7 @@
      * given the situation with the keyguard.
      */
     void launchHomeFromHotKey() {
-        if (mKeyguardMediator.isShowingAndNotHidden()) {
+        if (mKeyguardMediator != null && mKeyguardMediator.isShowingAndNotHidden()) {
             // don't launch home if keyguard showing
         } else if (!mHideLockScreen && mKeyguardMediator.isInputRestricted()) {
             // when in keyguard restricted mode, must first verify unlock
@@ -2520,6 +2523,9 @@
 
     /** {@inheritDoc} */
     public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+        // do nothing if headless
+        if (mHeadless) return;
+
         // lid changed state
         mLidOpen = lidOpen ? LID_OPEN : LID_CLOSED;
         updateKeyboardVisibility();
@@ -2716,9 +2722,10 @@
         // the same as if it were open and in front.
         // This will prevent any keys other than the power button from waking the screen
         // when the keyguard is hidden by another activity.
-        final boolean keyguardActive = (isScreenOn ?
-                                        mKeyguardMediator.isShowingAndNotHidden() :
-                                        mKeyguardMediator.isShowing());
+        final boolean keyguardActive = (mKeyguardMediator == null ? false :
+                                            (isScreenOn ?
+                                                mKeyguardMediator.isShowingAndNotHidden() :
+                                                mKeyguardMediator.isShowing()));
 
         if (!mSystemBooted) {
             // If we have not yet booted, don't let key events do anything.
@@ -2744,7 +2751,7 @@
         //        the device some other way (which is why we have an exemption here for injected
         //        events).
         int result;
-        if (isScreenOn || isInjected) {
+        if ((isScreenOn && !mHeadless) || isInjected) {
             // When the screen is on or if the key is injected pass the key to the application.
             result = ACTION_PASS_TO_USER;
         } else {
@@ -2983,7 +2990,7 @@
         final boolean isWakeMotion = (policyFlags
                 & (WindowManagerPolicy.FLAG_WAKE | WindowManagerPolicy.FLAG_WAKE_DROPPED)) != 0;
         if (isWakeMotion) {
-            if (mKeyguardMediator.isShowing()) {
+            if (mKeyguardMediator != null && mKeyguardMediator.isShowing()) {
                 // If the keyguard is showing, let it decide what to do with the wake motion.
                 mKeyguardMediator.onWakeMotionWhenKeyguardShowingTq();
             } else {
@@ -3042,7 +3049,9 @@
             mScreenOnEarly = false;
             mScreenOnFully = false;
         }
-        mKeyguardMediator.onScreenTurnedOff(why);
+        if (mKeyguardMediator != null) {
+            mKeyguardMediator.onScreenTurnedOff(why);
+        }
         synchronized (mLock) {
             updateOrientationListenerLp();
             updateLockScreenTimeout();
@@ -3058,31 +3067,33 @@
             Slog.i(TAG, "Screen turning on...", here);
         }
         if (screenOnListener != null) {
-            mKeyguardMediator.onScreenTurnedOn(new KeyguardViewManager.ShowListener() {
-                @Override public void onShown(IBinder windowToken) {
-                    if (windowToken != null) {
-                        try {
-                            mWindowManager.waitForWindowDrawn(windowToken,
-                                    new IRemoteCallback.Stub() {
-                                @Override public void sendResult(Bundle data) {
-                                    Slog.i(TAG, "Lock screen displayed!");
-                                    screenOnListener.onScreenOn();
-                                    synchronized (mLock) {
-                                        mScreenOnFully = true;
+            if (mKeyguardMediator != null) {
+                mKeyguardMediator.onScreenTurnedOn(new KeyguardViewManager.ShowListener() {
+                    @Override public void onShown(IBinder windowToken) {
+                        if (windowToken != null) {
+                            try {
+                                mWindowManager.waitForWindowDrawn(windowToken,
+                                        new IRemoteCallback.Stub() {
+                                    @Override public void sendResult(Bundle data) {
+                                        Slog.i(TAG, "Lock screen displayed!");
+                                        screenOnListener.onScreenOn();
+                                        synchronized (mLock) {
+                                            mScreenOnFully = true;
+                                        }
                                     }
-                                }
-                            });
-                        } catch (RemoteException e) {
-                        }
-                    } else {
-                        Slog.i(TAG, "No lock screen!");
-                        screenOnListener.onScreenOn();
-                        synchronized (mLock) {
-                            mScreenOnFully = true;
+                                });
+                            } catch (RemoteException e) {
+                            }
+                        } else {
+                            Slog.i(TAG, "No lock screen!");
+                            screenOnListener.onScreenOn();
+                            synchronized (mLock) {
+                                mScreenOnFully = true;
+                            }
                         }
                     }
-                }
-            });
+                });
+            }
         } else {
             synchronized (mLock) {
                 mScreenOnFully = true;
@@ -3107,15 +3118,20 @@
     
     /** {@inheritDoc} */
     public void enableKeyguard(boolean enabled) {
-        mKeyguardMediator.setKeyguardEnabled(enabled);
+        if (mKeyguardMediator != null) {
+            mKeyguardMediator.setKeyguardEnabled(enabled);
+        }
     }
 
     /** {@inheritDoc} */
     public void exitKeyguardSecurely(OnKeyguardExitResult callback) {
-        mKeyguardMediator.verifyUnlock(callback);
+        if (mKeyguardMediator != null) {
+            mKeyguardMediator.verifyUnlock(callback);
+        }
     }
 
     private boolean keyguardIsShowingTq() {
+        if (mKeyguardMediator == null) return false;
         return mKeyguardMediator.isShowingAndNotHidden();
     }
 
@@ -3127,11 +3143,13 @@
 
     /** {@inheritDoc} */
     public boolean isKeyguardSecure() {
+        if (mKeyguardMediator == null) return false;
         return mKeyguardMediator.isSecure();
     }
 
     /** {@inheritDoc} */
     public boolean inKeyguardRestrictedKeyInputMode() {
+        if (mKeyguardMediator == null) return false;
         return mKeyguardMediator.isInputRestricted();
     }
 
@@ -3387,8 +3405,10 @@
     
     /** {@inheritDoc} */
     public void systemReady() {
-        // tell the keyguard
-        mKeyguardMediator.onSystemReady();
+        if (mKeyguardMediator != null) {
+            // tell the keyguard
+            mKeyguardMediator.onSystemReady();
+        }
         android.os.SystemProperties.set("dev.bootcomplete", "1"); 
         synchronized (mLock) {
             updateOrientationListenerLp();
@@ -3412,6 +3432,7 @@
 
     /** {@inheritDoc} */
     public void showBootMessage(final CharSequence msg, final boolean always) {
+        if (mHeadless) return;
         mHandler.post(new Runnable() {
             @Override public void run() {
                 if (mBootMsgDialog == null) {
@@ -3496,7 +3517,9 @@
         public void run() {
             synchronized (this) {
                 if (localLOGV) Log.v(TAG, "mScreenLockTimeout activating keyguard");
-                mKeyguardMediator.doKeyguardTimeout();
+                if (mKeyguardMediator != null) {
+                    mKeyguardMediator.doKeyguardTimeout();
+                }
                 mLockScreenTimerActive = false;
             }
         }
@@ -3510,7 +3533,8 @@
 
     private void updateLockScreenTimeout() {
         synchronized (mScreenLockTimeout) {
-            boolean enable = (mAllowLockscreenWhenOn && mScreenOnEarly && mKeyguardMediator.isSecure());
+            boolean enable = (mAllowLockscreenWhenOn && mScreenOnEarly &&
+                    mKeyguardMediator != null && mKeyguardMediator.isSecure());
             if (mLockScreenTimerActive != enable) {
                 if (enable) {
                     if (localLOGV) Log.v(TAG, "setting lockscreen timer");
@@ -3705,7 +3729,7 @@
 
     public void screenOnStoppedLw() {
         if (mPowerManager.isScreenOn()) {
-            if (!mKeyguardMediator.isShowingAndNotHidden()) {
+            if (mKeyguardMediator != null && !mKeyguardMediator.isShowingAndNotHidden()) {
                 long curTime = SystemClock.uptimeMillis();
                 mPowerManager.userActivity(curTime, false, LocalPowerManager.OTHER_EVENT);
             }
diff --git a/services/audioflinger/Android.mk b/services/audioflinger/Android.mk
index fa49592..fd0609a 100644
--- a/services/audioflinger/Android.mk
+++ b/services/audioflinger/Android.mk
@@ -8,12 +8,14 @@
     AudioResampler.cpp.arm      \
     AudioResamplerSinc.cpp.arm  \
     AudioResamplerCubic.cpp.arm \
-    AudioPolicyService.cpp
+    AudioPolicyService.cpp      \
+    AudioBufferProvider.cpp
 
 LOCAL_C_INCLUDES := \
     system/media/audio_effects/include
 
 LOCAL_SHARED_LIBRARIES := \
+    libcommon_time_client \
     libcutils \
     libutils \
     libbinder \
diff --git a/services/audioflinger/AudioBufferProvider.cpp b/services/audioflinger/AudioBufferProvider.cpp
new file mode 100644
index 0000000..678fd58
--- /dev/null
+++ b/services/audioflinger/AudioBufferProvider.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#undef __STRICT_ANSI__
+#define __STDINT_LIMITS
+#define __STDC_LIMIT_MACROS
+#include <stdint.h>
+
+#include "AudioBufferProvider.h"
+
+namespace android {
+
+const int64_t AudioBufferProvider::kInvalidPTS = INT64_MAX;
+
+}; // namespace android
diff --git a/services/audioflinger/AudioBufferProvider.h b/services/audioflinger/AudioBufferProvider.h
index 81c5c39..62ad6bd 100644
--- a/services/audioflinger/AudioBufferProvider.h
+++ b/services/audioflinger/AudioBufferProvider.h
@@ -38,8 +38,15 @@
     };
 
     virtual ~AudioBufferProvider() {}
-    
-    virtual status_t getNextBuffer(Buffer* buffer) = 0;
+
+    // value representing an invalid presentation timestamp
+    static const int64_t kInvalidPTS;
+
+    // pts is the local time when the next sample yielded by getNextBuffer
+    // will be rendered.
+    // Pass kInvalidPTS if the PTS is unknown or not applicable.
+    virtual status_t getNextBuffer(Buffer* buffer, int64_t pts) = 0;
+
     virtual void releaseBuffer(Buffer* buffer) = 0;
 };
 
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index ce701ca..6efd671 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -58,6 +58,9 @@
 #include <powermanager/PowerManager.h>
 // #define DEBUG_CPU_USAGE 10  // log statistics every n wall clock seconds
 
+#include <common_time/cc_helper.h>
+#include <common_time/local_clock.h>
+
 // ----------------------------------------------------------------------------
 
 
@@ -66,7 +69,6 @@
 static const char* kDeadlockedString = "AudioFlinger may be deadlocked\n";
 static const char* kHardwareLockedString = "Hardware lock is taken\n";
 
-//static const nsecs_t kStandbyTimeInNsecs = seconds(3);
 static const float MAX_GAIN = 4096.0f;
 static const float MAX_GAIN_INT = 0x1000;
 
@@ -94,6 +96,9 @@
 // maximum divider applied to the active sleep time in the mixer thread loop
 static const uint32_t kMaxThreadSleepTimeShift = 2;
 
+nsecs_t AudioFlinger::mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs;
+bool    AudioFlinger::mStaticInitDone = false;
+Mutex   AudioFlinger::mStaticInitLock;
 
 // ----------------------------------------------------------------------------
 
@@ -158,11 +163,37 @@
 
 AudioFlinger::AudioFlinger()
     : BnAudioFlinger(),
-        mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterMute(false), mNextUniqueId(1),
-        mBtNrecIsOff(false)
+      mPrimaryHardwareDev(0), mMasterVolume(1.0f), mMasterVolumeSW(1.0f),
+      mMasterVolumeSupportLvl(MVS_NONE), mMasterMute(false), mNextUniqueId(1),
+      mBtNrecIsOff(false)
 {
 }
 
+bool AudioFlinger::doStaticInit() {
+    if (!mStaticInitDone) {
+        Mutex::Autolock _l(mStaticInitLock);
+
+        if (mStaticInitDone)
+            return true;
+
+        char val_str[PROPERTY_VALUE_MAX] = { 0 };
+        if (property_get("ro.audio.flinger_standbytime_ms", val_str, NULL) >= 0) {
+            uint32_t int_val;
+            if (1 == sscanf(val_str, "%u", &int_val)) {
+                mStandbyTimeInNsecs = milliseconds(int_val);
+                LOGI("Using %u mSec as standby time.", int_val);
+            } else {
+                mStandbyTimeInNsecs = kDefaultStandbyTimeInNsecs;
+                LOGI("Using default %u mSec as standby time.",
+                        (uint32_t)(mStandbyTimeInNsecs / 1000000));
+            }
+        }
+
+        mStaticInitDone = true;
+    }
+
+    return mStaticInitDone;
+}
 void AudioFlinger::onFirstRef()
 {
     int rc = 0;
@@ -172,6 +203,10 @@
     /* TODO: move all this work into an Init() function */
     mHardwareStatus = AUDIO_HW_IDLE;
 
+    // Make sure all static variables are set up.  Right now, this is limited to
+    // the standby time parameter.
+    doStaticInit();
+
     for (size_t i = 0; i < ARRAY_SIZE(audio_interfaces); i++) {
         const hw_module_t *mod;
         audio_hw_device_t *dev;
@@ -198,6 +233,33 @@
         return;
     }
 
+    // Determine the level of master volume support the primary audio HAL has,
+    // and set the initial master volume at the same time.
+    float initialVolume = 1.0;
+    mMasterVolumeSupportLvl = MVS_NONE;
+    if (0 == mPrimaryHardwareDev->init_check(mPrimaryHardwareDev)) {
+        AutoMutex lock(mHardwareLock);
+        audio_hw_device_t *dev = mPrimaryHardwareDev;
+
+        mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME;
+        if ((NULL != dev->get_master_volume) &&
+            (NO_ERROR == dev->get_master_volume(dev, &initialVolume))) {
+            mMasterVolumeSupportLvl = MVS_FULL;
+        } else {
+            mMasterVolumeSupportLvl = MVS_SETONLY;
+            initialVolume = 1.0;
+        }
+
+        mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+        if ((NULL == dev->set_master_volume) ||
+            (NO_ERROR != dev->set_master_volume(dev, initialVolume))) {
+            mMasterVolumeSupportLvl = MVS_NONE;
+        }
+        mHardwareStatus = AUDIO_HW_INIT;
+    }
+
+    // Set the mode for each audio HAL, and try to set the initial volume (if
+    // supported) for all of the non-primary audio HALs.
     for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
         audio_hw_device_t *dev = mAudioHwDevs[i];
 
@@ -209,11 +271,22 @@
             mMode = AUDIO_MODE_NORMAL;
             mHardwareStatus = AUDIO_HW_SET_MODE;
             dev->set_mode(dev, mMode);
-            mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
-            dev->set_master_volume(dev, 1.0f);
-            mHardwareStatus = AUDIO_HW_IDLE;
+
+            if ((dev != mPrimaryHardwareDev) &&
+                (NULL != dev->set_master_volume)) {
+                mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+                dev->set_master_volume(dev, initialVolume);
+            }
+
+            mHardwareStatus = AUDIO_HW_INIT;
         }
     }
+
+    mMasterVolumeSW = (MVS_NONE == mMasterVolumeSupportLvl)
+                    ? initialVolume
+                    : 1.0;
+    mMasterVolume   = initialVolume;
+    mHardwareStatus = AUDIO_HW_IDLE;
 }
 
 status_t AudioFlinger::initCheck() const
@@ -292,7 +365,10 @@
     String8 result;
     int hardwareStatus = mHardwareStatus;
 
-    snprintf(buffer, SIZE, "Hardware status: %d\n", hardwareStatus);
+    snprintf(buffer, SIZE, "Hardware status: %d\n"
+                           "Standby Time mSec: %u\n",
+                            hardwareStatus,
+                            (uint32_t)(mStandbyTimeInNsecs / 1000000));
     result.append(buffer);
     write(fd, result.string(), result.size());
     return NO_ERROR;
@@ -384,6 +460,7 @@
         uint32_t flags,
         const sp<IMemory>& sharedBuffer,
         int output,
+        bool isTimed,
         int *sessionId,
         status_t *status)
 {
@@ -447,7 +524,7 @@
         LOGV("createTrack() lSessionId: %d", lSessionId);
 
         track = thread->createTrack_l(client, streamType, sampleRate, format,
-                channelMask, frameCount, sharedBuffer, lSessionId, &lStatus);
+                channelMask, frameCount, sharedBuffer, lSessionId, isTimed, &lStatus);
 
         // move effect chain to this output thread if an effect on same session was waiting
         // for a track to be created
@@ -540,20 +617,29 @@
         return PERMISSION_DENIED;
     }
 
+    float swmv = value;
+
     // when hw supports master volume, don't scale in sw mixer
-    { // scope for the lock
-        AutoMutex lock(mHardwareLock);
-        mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
-        if (mPrimaryHardwareDev->set_master_volume(mPrimaryHardwareDev, value) == NO_ERROR) {
-            value = 1.0f;
+    if (MVS_NONE != mMasterVolumeSupportLvl) {
+        for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
+            AutoMutex lock(mHardwareLock);
+            audio_hw_device_t *dev = mAudioHwDevs[i];
+
+            mHardwareStatus = AUDIO_HW_SET_MASTER_VOLUME;
+            if (NULL != dev->set_master_volume) {
+                dev->set_master_volume(dev, value);
+            }
+            mHardwareStatus = AUDIO_HW_IDLE;
         }
-        mHardwareStatus = AUDIO_HW_IDLE;
+
+        swmv = 1.0;
     }
 
     Mutex::Autolock _l(mLock);
-    mMasterVolume = value;
+    mMasterVolume   = value;
+    mMasterVolumeSW = swmv;
     for (uint32_t i = 0; i < mPlaybackThreads.size(); i++)
-       mPlaybackThreads.valueAt(i)->setMasterVolume(value);
+       mPlaybackThreads.valueAt(i)->setMasterVolume(swmv);
 
     return NO_ERROR;
 }
@@ -641,9 +727,27 @@
 
 float AudioFlinger::masterVolume() const
 {
+    if (MVS_FULL == mMasterVolumeSupportLvl) {
+        float ret_val;
+        AutoMutex lock(mHardwareLock);
+
+        mHardwareStatus = AUDIO_HW_GET_MASTER_VOLUME;
+        assert(NULL != mPrimaryHardwareDev);
+        assert(NULL != mPrimaryHardwareDev->get_master_volume);
+
+        mPrimaryHardwareDev->get_master_volume(mPrimaryHardwareDev, &ret_val);
+        mHardwareStatus = AUDIO_HW_IDLE;
+        return ret_val;
+    }
+
     return mMasterVolume;
 }
 
+float AudioFlinger::masterVolumeSW() const
+{
+    return mMasterVolumeSW;
+}
+
 bool AudioFlinger::masterMute() const
 {
     return mMasterMute;
@@ -819,7 +923,7 @@
         for (size_t i = 0; i < mAudioHwDevs.size(); i++) {
             audio_hw_device_t *dev = mAudioHwDevs[i];
             char *s = dev->get_parameters(dev, keys.string());
-            out_s8 += String8(s);
+            out_s8 += String8(s ? s : "");
             free(s);
         }
         return out_s8;
@@ -1374,7 +1478,8 @@
 
     readOutputParameters();
 
-    mMasterVolume = mAudioFlinger->masterVolume();
+    mMasterVolume = mAudioFlinger->masterVolumeSW();
+
     mMasterMute = mAudioFlinger->masterMute();
 
     for (int stream = 0; stream < AUDIO_STREAM_CNT; stream++) {
@@ -1485,6 +1590,7 @@
         int frameCount,
         const sp<IMemory>& sharedBuffer,
         int sessionId,
+        bool isTimed,
         status_t *status)
 {
     sp<Track> track;
@@ -1534,9 +1640,14 @@
             }
         }
 
-        track = new Track(this, client, streamType, sampleRate, format,
-                channelMask, frameCount, sharedBuffer, sessionId);
-        if (track->getCblk() == NULL || track->name() < 0) {
+        if (!isTimed) {
+            track = new Track(this, client, streamType, sampleRate, format,
+                    channelMask, frameCount, sharedBuffer, sessionId);
+        } else {
+            track = TimedTrack::create(this, client, streamType, sampleRate, format,
+                    channelMask, frameCount, sharedBuffer, sessionId);
+        }
+        if (track == NULL || track->getCblk() == NULL || track->name() < 0) {
             lStatus = NO_MEMORY;
             goto Exit;
         }
@@ -1873,6 +1984,10 @@
 
     acquireWakeLock();
 
+    LocalClock lc;
+    uint64_t localTimeFreq;
+    localTimeFreq = lc.getLocalFreq();
+
     while (!exitPending())
     {
 #ifdef DEBUG_CPU_USAGE
@@ -1955,7 +2070,7 @@
                         }
                     }
 
-                    standbyTime = systemTime() + kStandbyTimeInNsecs;
+                    standbyTime = systemTime() + mStandbyTimeInNsecs;
                     sleepTime = idleSleepTime;
                     sleepTimeShift = 0;
                     continue;
@@ -1971,8 +2086,21 @@
        }
 
         if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
+            // obtain the presentation timestamp of the next output buffer
+            int64_t pts;
+            status_t status = INVALID_OPERATION;
+
+            if (NULL != mOutput->stream->get_next_write_timestamp) {
+                status = mOutput->stream->get_next_write_timestamp(
+                        mOutput->stream, &pts);
+            }
+
+            if (status != NO_ERROR) {
+                pts = AudioBufferProvider::kInvalidPTS;
+            }
+
             // mix buffers...
-            mAudioMixer->process();
+            mAudioMixer->process(pts);
             // increase sleep time progressively when application underrun condition clears.
             // Only increase sleep time if the mixer is ready for two consecutive times to avoid
             // that a steady state of alternating ready/not ready conditions keeps the sleep time
@@ -1981,7 +2109,7 @@
                 sleepTimeShift--;
             }
             sleepTime = 0;
-            standbyTime = systemTime() + kStandbyTimeInNsecs;
+            standbyTime = systemTime() + mStandbyTimeInNsecs;
             //TODO: delay standby when effects have a tail
         } else {
             // If no tracks are ready, sleep once for the duration of an output
@@ -2126,7 +2254,7 @@
                 LOG_ASSERT(minFrames <= cblk->frameCount);
             }
         }
-        if ((cblk->framesReady() >= minFrames) && track->isReady() &&
+        if ((track->framesReady() >= minFrames) && track->isReady() &&
                 !track->isPaused() && !track->isTerminated())
         {
             //LOGV("track %d u=%08x, s=%08x [OK] on thread %p", track->name(), cblk->user, cblk->server, this);
@@ -2788,7 +2916,8 @@
             // output audio to hardware
             while (frameCount) {
                 buffer.frameCount = frameCount;
-                activeTrack->getNextBuffer(&buffer);
+                activeTrack->getNextBuffer(&buffer,
+                                           AudioBufferProvider::kInvalidPTS);
                 if (UNLIKELY(buffer.raw == 0)) {
                     memset(curBuf, 0, frameCount * mFrameSize);
                     break;
@@ -3041,7 +3170,7 @@
                         }
                     }
 
-                    standbyTime = systemTime() + kStandbyTimeInNsecs;
+                    standbyTime = systemTime() + mStandbyTimeInNsecs;
                     sleepTime = idleSleepTime;
                     continue;
                 }
@@ -3058,7 +3187,7 @@
         if (LIKELY(mixerStatus == MIXER_TRACKS_READY)) {
             // mix buffers...
             if (outputsReady(outputTracks)) {
-                mAudioMixer->process();
+                mAudioMixer->process(AudioBufferProvider::kInvalidPTS);
             } else {
                 memset(mMixBuffer, 0, mixBufferSize);
             }
@@ -3095,7 +3224,7 @@
             // enable changes in effect chain
             unlockEffectChains(effectChains);
 
-            standbyTime = systemTime() + kStandbyTimeInNsecs;
+            standbyTime = systemTime() + mStandbyTimeInNsecs;
             for (size_t i = 0; i < outputTracks.size(); i++) {
                 outputTracks[i]->write(mMixBuffer, writeFrames);
             }
@@ -3453,7 +3582,8 @@
             (int)mAuxBuffer);
 }
 
-status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::PlaybackThread::Track::getNextBuffer(
+    AudioBufferProvider::Buffer* buffer, int64_t pts)
 {
      audio_track_cblk_t* cblk = this->cblk();
      uint32_t framesReady;
@@ -3494,10 +3624,14 @@
      return NOT_ENOUGH_DATA;
 }
 
+uint32_t AudioFlinger::PlaybackThread::Track::framesReady() const{
+    return mCblk->framesReady();
+}
+
 bool AudioFlinger::PlaybackThread::Track::isReady() const {
     if (mFillingUpStatus != FS_FILLING || isStopped() || isPausing()) return true;
 
-    if (mCblk->framesReady() >= mCblk->frameCount ||
+    if (framesReady() >= mCblk->frameCount ||
             (mCblk->flags & CBLK_FORCEREADY_MSK)) {
         mFillingUpStatus = FS_FILLED;
         android_atomic_and(~CBLK_FORCEREADY_MSK, &mCblk->flags);
@@ -3666,6 +3800,406 @@
     mAuxBuffer = buffer;
 }
 
+// timed audio tracks
+
+sp<AudioFlinger::PlaybackThread::TimedTrack>
+AudioFlinger::PlaybackThread::TimedTrack::create(
+            const wp<ThreadBase>& thread,
+            const sp<Client>& client,
+            int streamType,
+            uint32_t sampleRate,
+            uint32_t format,
+            uint32_t channelMask,
+            int frameCount,
+            const sp<IMemory>& sharedBuffer,
+            int sessionId) {
+    if (!client->reserveTimedTrack())
+        return NULL;
+
+    sp<TimedTrack> track = new TimedTrack(
+        thread, client, streamType, sampleRate, format, channelMask, frameCount,
+        sharedBuffer, sessionId);
+
+    if (track == NULL) {
+        client->releaseTimedTrack();
+        return NULL;
+    }
+
+    return track;
+}
+
+AudioFlinger::PlaybackThread::TimedTrack::TimedTrack(
+            const wp<ThreadBase>& thread,
+            const sp<Client>& client,
+            int streamType,
+            uint32_t sampleRate,
+            uint32_t format,
+            uint32_t channelMask,
+            int frameCount,
+            const sp<IMemory>& sharedBuffer,
+            int sessionId)
+    : Track(thread, client, streamType, sampleRate, format, channelMask,
+            frameCount, sharedBuffer, sessionId),
+      mTimedSilenceBuffer(NULL),
+      mTimedSilenceBufferSize(0),
+      mTimedAudioOutputOnTime(false),
+      mMediaTimeTransformValid(false)
+{
+    LocalClock lc;
+    mLocalTimeFreq = lc.getLocalFreq();
+
+    mLocalTimeToSampleTransform.a_zero = 0;
+    mLocalTimeToSampleTransform.b_zero = 0;
+    mLocalTimeToSampleTransform.a_to_b_numer = sampleRate;
+    mLocalTimeToSampleTransform.a_to_b_denom = mLocalTimeFreq;
+    LinearTransform::reduce(&mLocalTimeToSampleTransform.a_to_b_numer,
+                            &mLocalTimeToSampleTransform.a_to_b_denom);
+}
+
+AudioFlinger::PlaybackThread::TimedTrack::~TimedTrack() {
+    mClient->releaseTimedTrack();
+    delete [] mTimedSilenceBuffer;
+}
+
+status_t AudioFlinger::PlaybackThread::TimedTrack::allocateTimedBuffer(
+    size_t size, sp<IMemory>* buffer) {
+
+    Mutex::Autolock _l(mTimedBufferQueueLock);
+
+    trimTimedBufferQueue_l();
+
+    // lazily initialize the shared memory heap for timed buffers
+    if (mTimedMemoryDealer == NULL) {
+        const int kTimedBufferHeapSize = 512 << 10;
+
+        mTimedMemoryDealer = new MemoryDealer(kTimedBufferHeapSize,
+                                              "AudioFlingerTimed");
+        if (mTimedMemoryDealer == NULL)
+            return NO_MEMORY;
+    }
+
+    sp<IMemory> newBuffer = mTimedMemoryDealer->allocate(size);
+    if (newBuffer == NULL) {
+        newBuffer = mTimedMemoryDealer->allocate(size);
+        if (newBuffer == NULL)
+            return NO_MEMORY;
+    }
+
+    *buffer = newBuffer;
+    return NO_ERROR;
+}
+
+// caller must hold mTimedBufferQueueLock
+void AudioFlinger::PlaybackThread::TimedTrack::trimTimedBufferQueue_l() {
+    int64_t mediaTimeNow;
+    {
+        Mutex::Autolock mttLock(mMediaTimeTransformLock);
+        if (!mMediaTimeTransformValid)
+            return;
+
+        int64_t targetTimeNow;
+        status_t res = (mMediaTimeTransformTarget == TimedAudioTrack::COMMON_TIME)
+            ? mCCHelper.getCommonTime(&targetTimeNow)
+            : mCCHelper.getLocalTime(&targetTimeNow);
+
+        if (OK != res)
+            return;
+
+        if (!mMediaTimeTransform.doReverseTransform(targetTimeNow,
+                                                    &mediaTimeNow)) {
+            return;
+        }
+    }
+
+    size_t trimIndex;
+    for (trimIndex = 0; trimIndex < mTimedBufferQueue.size(); trimIndex++) {
+        if (mTimedBufferQueue[trimIndex].pts() > mediaTimeNow)
+            break;
+    }
+
+    if (trimIndex) {
+        mTimedBufferQueue.removeItemsAt(0, trimIndex);
+    }
+}
+
+status_t AudioFlinger::PlaybackThread::TimedTrack::queueTimedBuffer(
+    const sp<IMemory>& buffer, int64_t pts) {
+
+    {
+        Mutex::Autolock mttLock(mMediaTimeTransformLock);
+        if (!mMediaTimeTransformValid)
+            return INVALID_OPERATION;
+    }
+
+    Mutex::Autolock _l(mTimedBufferQueueLock);
+
+    mTimedBufferQueue.add(TimedBuffer(buffer, pts));
+
+    return NO_ERROR;
+}
+
+status_t AudioFlinger::PlaybackThread::TimedTrack::setMediaTimeTransform(
+    const LinearTransform& xform, TimedAudioTrack::TargetTimeline target) {
+
+    LOGV("%s az=%lld bz=%lld n=%d d=%u tgt=%d", __PRETTY_FUNCTION__,
+         xform.a_zero, xform.b_zero, xform.a_to_b_numer, xform.a_to_b_denom,
+         target);
+
+    if (!(target == TimedAudioTrack::LOCAL_TIME ||
+          target == TimedAudioTrack::COMMON_TIME)) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock lock(mMediaTimeTransformLock);
+    mMediaTimeTransform = xform;
+    mMediaTimeTransformTarget = target;
+    mMediaTimeTransformValid = true;
+
+    return NO_ERROR;
+}
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+
+// implementation of getNextBuffer for tracks whose buffers have timestamps
+status_t AudioFlinger::PlaybackThread::TimedTrack::getNextBuffer(
+    AudioBufferProvider::Buffer* buffer, int64_t pts)
+{
+    if (pts == AudioBufferProvider::kInvalidPTS) {
+        buffer->raw = 0;
+        buffer->frameCount = 0;
+        return INVALID_OPERATION;
+    }
+
+    // get ahold of the output stream that these samples will be written to
+    sp<ThreadBase> thread = mThread.promote();
+    if (thread == NULL) {
+        buffer->raw = 0;
+        buffer->frameCount = 0;
+        return INVALID_OPERATION;
+    }
+    PlaybackThread* playbackThread = static_cast<PlaybackThread*>(thread.get());
+
+    Mutex::Autolock _l(mTimedBufferQueueLock);
+
+    while (true) {
+
+        // if we have no timed buffers, then fail
+        if (mTimedBufferQueue.isEmpty()) {
+            buffer->raw = 0;
+            buffer->frameCount = 0;
+            return NOT_ENOUGH_DATA;
+        }
+
+        TimedBuffer& head = mTimedBufferQueue.editItemAt(0);
+
+        // calculate the PTS of the head of the timed buffer queue expressed in
+        // local time
+        int64_t headLocalPTS;
+        {
+            Mutex::Autolock mttLock(mMediaTimeTransformLock);
+
+            assert(mMediaTimeTransformValid);
+
+            if (mMediaTimeTransform.a_to_b_denom == 0) {
+                // the transform represents a pause, so yield silence
+                timedYieldSilence(buffer->frameCount, buffer);
+                return NO_ERROR;
+            }
+
+            int64_t transformedPTS;
+            if (!mMediaTimeTransform.doForwardTransform(head.pts(),
+                                                        &transformedPTS)) {
+                // the transform failed.  this shouldn't happen, but if it does
+                // then just drop this buffer
+                LOGW("timedGetNextBuffer transform failed");
+                buffer->raw = 0;
+                buffer->frameCount = 0;
+                mTimedBufferQueue.removeAt(0);
+                return NO_ERROR;
+            }
+
+            if (mMediaTimeTransformTarget == TimedAudioTrack::COMMON_TIME) {
+                if (OK != mCCHelper.commonTimeToLocalTime(transformedPTS,
+                                                          &headLocalPTS)) {
+                    buffer->raw = 0;
+                    buffer->frameCount = 0;
+                    return INVALID_OPERATION;
+                }
+            } else {
+                headLocalPTS = transformedPTS;
+            }
+        }
+
+        // adjust the head buffer's PTS to reflect the portion of the head buffer
+        // that has already been consumed
+        int64_t effectivePTS = headLocalPTS +
+                ((head.position() / mCblk->frameSize) * mLocalTimeFreq / sampleRate());
+
+        // Calculate the delta in samples between the head of the input buffer
+        // queue and the start of the next output buffer that will be written.
+        // If the transformation fails because of over or underflow, it means
+        // that the sample's position in the output stream is so far out of
+        // whack that it should just be dropped.
+        int64_t sampleDelta;
+        if (llabs(effectivePTS - pts) >= (static_cast<int64_t>(1) << 31)) {
+            LOGV("*** head buffer is too far from PTS: dropped buffer");
+            mTimedBufferQueue.removeAt(0);
+            continue;
+        }
+        if (!mLocalTimeToSampleTransform.doForwardTransform(
+                (effectivePTS - pts) << 32, &sampleDelta)) {
+            LOGV("*** too late during sample rate transform: dropped buffer");
+            mTimedBufferQueue.removeAt(0);
+            continue;
+        }
+
+        LOGV("*** %s head.pts=%lld head.pos=%d pts=%lld sampleDelta=[%d.%08x]",
+             __PRETTY_FUNCTION__, head.pts(), head.position(), pts,
+             static_cast<int32_t>((sampleDelta >= 0 ? 0 : 1) + (sampleDelta >> 32)),
+             static_cast<uint32_t>(sampleDelta & 0xFFFFFFFF));
+
+        // if the delta between the ideal placement for the next input sample and
+        // the current output position is within this threshold, then we will
+        // concatenate the next input samples to the previous output
+        const int64_t kSampleContinuityThreshold =
+                (static_cast<int64_t>(sampleRate()) << 32) / 10;
+
+        // if this is the first buffer of audio that we're emitting from this track
+        // then it should be almost exactly on time.
+        const int64_t kSampleStartupThreshold = 1LL << 32;
+
+        if ((mTimedAudioOutputOnTime && llabs(sampleDelta) <= kSampleContinuityThreshold) ||
+            (!mTimedAudioOutputOnTime && llabs(sampleDelta) <= kSampleStartupThreshold)) {
+            // the next input is close enough to being on time, so concatenate it
+            // with the last output
+            timedYieldSamples(buffer);
+
+            LOGV("*** on time: head.pos=%d frameCount=%u", head.position(), buffer->frameCount);
+            return NO_ERROR;
+        } else if (sampleDelta > 0) {
+            // the gap between the current output position and the proper start of
+            // the next input sample is too big, so fill it with silence
+            uint32_t framesUntilNextInput = (sampleDelta + 0x80000000) >> 32;
+
+            timedYieldSilence(framesUntilNextInput, buffer);
+            LOGV("*** silence: frameCount=%u", buffer->frameCount);
+            return NO_ERROR;
+        } else {
+            // the next input sample is late
+            uint32_t lateFrames = static_cast<uint32_t>(-((sampleDelta + 0x80000000) >> 32));
+            size_t onTimeSamplePosition =
+                    head.position() + lateFrames * mCblk->frameSize;
+
+            if (onTimeSamplePosition > head.buffer()->size()) {
+                // all the remaining samples in the head are too late, so
+                // drop it and move on
+                LOGV("*** too late: dropped buffer");
+                mTimedBufferQueue.removeAt(0);
+                continue;
+            } else {
+                // skip over the late samples
+                head.setPosition(onTimeSamplePosition);
+
+                // yield the available samples
+                timedYieldSamples(buffer);
+
+                LOGV("*** late: head.pos=%d frameCount=%u", head.position(), buffer->frameCount);
+                return NO_ERROR;
+            }
+        }
+    }
+}
+
+// Yield samples from the timed buffer queue head up to the given output
+// buffer's capacity.
+//
+// Caller must hold mTimedBufferQueueLock
+void AudioFlinger::PlaybackThread::TimedTrack::timedYieldSamples(
+    AudioBufferProvider::Buffer* buffer) {
+
+    const TimedBuffer& head = mTimedBufferQueue[0];
+
+    buffer->raw = (static_cast<uint8_t*>(head.buffer()->pointer()) +
+                   head.position());
+
+    uint32_t framesLeftInHead = ((head.buffer()->size() - head.position()) /
+                                 mCblk->frameSize);
+    size_t framesRequested = buffer->frameCount;
+    buffer->frameCount = min(framesLeftInHead, framesRequested);
+
+    mTimedAudioOutputOnTime = true;
+}
+
+// Yield samples of silence up to the given output buffer's capacity
+//
+// Caller must hold mTimedBufferQueueLock
+void AudioFlinger::PlaybackThread::TimedTrack::timedYieldSilence(
+    uint32_t numFrames, AudioBufferProvider::Buffer* buffer) {
+
+    // lazily allocate a buffer filled with silence
+    if (mTimedSilenceBufferSize < numFrames * mCblk->frameSize) {
+        delete [] mTimedSilenceBuffer;
+        mTimedSilenceBufferSize = numFrames * mCblk->frameSize;
+        mTimedSilenceBuffer = new uint8_t[mTimedSilenceBufferSize];
+        memset(mTimedSilenceBuffer, 0, mTimedSilenceBufferSize);
+    }
+
+    buffer->raw = mTimedSilenceBuffer;
+    size_t framesRequested = buffer->frameCount;
+    buffer->frameCount = min(numFrames, framesRequested);
+
+    mTimedAudioOutputOnTime = false;
+}
+
+void AudioFlinger::PlaybackThread::TimedTrack::releaseBuffer(
+    AudioBufferProvider::Buffer* buffer) {
+
+    Mutex::Autolock _l(mTimedBufferQueueLock);
+
+    // If the buffer which was just released is part of the buffer at the head
+    // of the queue, be sure to update the amt of the buffer which has been
+    // consumed.  If the buffer being returned is not part of the head of the
+    // queue, its either because the buffer is part of the silence buffer, or
+    // because the head of the timed queue was trimmed after the mixer called
+    // getNextBuffer but before the mixer called releaseBuffer.
+    if ((buffer->raw != mTimedSilenceBuffer) && mTimedBufferQueue.size()) {
+        TimedBuffer& head = mTimedBufferQueue.editItemAt(0);
+
+        void* start = head.buffer()->pointer();
+        void* end   = head.buffer()->pointer() + head.buffer()->size();
+
+        if ((buffer->raw >= start) && (buffer->raw <= end)) {
+            head.setPosition(head.position() +
+                    (buffer->frameCount * mCblk->frameSize));
+            if (static_cast<size_t>(head.position()) >= head.buffer()->size()) {
+                mTimedBufferQueue.removeAt(0);
+            }
+        }
+    }
+
+    buffer->raw = 0;
+    buffer->frameCount = 0;
+}
+
+uint32_t AudioFlinger::PlaybackThread::TimedTrack::framesReady() const {
+    Mutex::Autolock _l(mTimedBufferQueueLock);
+
+    uint32_t frames = 0;
+    for (size_t i = 0; i < mTimedBufferQueue.size(); i++) {
+        const TimedBuffer& tb = mTimedBufferQueue[i];
+        frames += (tb.buffer()->size() - tb.position())  / mCblk->frameSize;
+    }
+
+    return frames;
+}
+
+AudioFlinger::PlaybackThread::TimedTrack::TimedBuffer::TimedBuffer()
+        : mPTS(0), mPosition(0) {}
+
+AudioFlinger::PlaybackThread::TimedTrack::TimedBuffer::TimedBuffer(
+    const sp<IMemory>& buffer, int64_t pts)
+        : mBuffer(buffer), mPTS(pts), mPosition(0) {}
+
 // ----------------------------------------------------------------------------
 
 // RecordTrack constructor must be called with AudioFlinger::mLock held
@@ -3702,7 +4236,7 @@
     }
 }
 
-status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::RecordThread::RecordTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts)
 {
     audio_track_cblk_t* cblk = this->cblk();
     uint32_t framesAvail;
@@ -4024,7 +4558,8 @@
     :   RefBase(),
         mAudioFlinger(audioFlinger),
         mMemoryDealer(new MemoryDealer(1024*1024, "AudioFlinger::Client")),
-        mPid(pid)
+        mPid(pid),
+        mTimedTrackCount(0)
 {
     // 1 MB of address space is good for 32 tracks, 8 buffers each, 4 KB/buffer
 }
@@ -4040,6 +4575,31 @@
     return mMemoryDealer;
 }
 
+// Reserve one of the limited slots for a timed audio track associated
+// with this client
+bool AudioFlinger::Client::reserveTimedTrack()
+{
+    const int kMaxTimedTracksPerClient = 4;
+
+    Mutex::Autolock _l(mTimedTrackLock);
+
+    if (mTimedTrackCount >= kMaxTimedTracksPerClient) {
+        LOGW("can not create timed track - pid %d has exceeded the limit",
+             mPid);
+        return false;
+    }
+
+    mTimedTrackCount++;
+    return true;
+}
+
+// Release a slot for a timed audio track
+void AudioFlinger::Client::releaseTimedTrack()
+{
+    Mutex::Autolock _l(mTimedTrackLock);
+    mTimedTrackCount--;
+}
+
 // ----------------------------------------------------------------------------
 
 AudioFlinger::NotificationClient::NotificationClient(const sp<AudioFlinger>& audioFlinger,
@@ -4111,6 +4671,38 @@
     return mTrack->attachAuxEffect(EffectId);
 }
 
+status_t AudioFlinger::TrackHandle::allocateTimedBuffer(size_t size,
+                                                         sp<IMemory>* buffer) {
+    if (!mTrack->isTimedTrack())
+        return INVALID_OPERATION;
+
+    PlaybackThread::TimedTrack* tt =
+            reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+    return tt->allocateTimedBuffer(size, buffer);
+}
+
+status_t AudioFlinger::TrackHandle::queueTimedBuffer(const sp<IMemory>& buffer,
+                                                     int64_t pts) {
+    if (!mTrack->isTimedTrack())
+        return INVALID_OPERATION;
+
+    PlaybackThread::TimedTrack* tt =
+            reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+    return tt->queueTimedBuffer(buffer, pts);
+}
+
+status_t AudioFlinger::TrackHandle::setMediaTimeTransform(
+    const LinearTransform& xform, int target) {
+
+    if (!mTrack->isTimedTrack())
+        return INVALID_OPERATION;
+
+    PlaybackThread::TimedTrack* tt =
+            reinterpret_cast<PlaybackThread::TimedTrack*>(mTrack.get());
+    return tt->setMediaTimeTransform(
+        xform, static_cast<TimedAudioTrack::TargetTimeline>(target));
+}
+
 status_t AudioFlinger::TrackHandle::onTransact(
     uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
 {
@@ -4348,7 +4940,8 @@
             }
 
             buffer.frameCount = mFrameCount;
-            if (LIKELY(mActiveTrack->getNextBuffer(&buffer) == NO_ERROR)) {
+            if (LIKELY(mActiveTrack->getNextBuffer(
+                    &buffer, AudioBufferProvider::kInvalidPTS) == NO_ERROR)) {
                 size_t framesOut = buffer.frameCount;
                 if (mResampler == 0) {
                     // no resampling
@@ -4627,7 +5220,7 @@
     return NO_ERROR;
 }
 
-status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+status_t AudioFlinger::RecordThread::getNextBuffer(AudioBufferProvider::Buffer* buffer, int64_t pts)
 {
     size_t framesReq = buffer->frameCount;
     size_t framesReady = mFrameCount - mRsmpInIndex;
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 9bd2c7f..71870a2 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -22,6 +22,8 @@
 #include <sys/types.h>
 #include <limits.h>
 
+#include <common_time/cc_helper.h>
+
 #include <media/IAudioFlinger.h>
 #include <media/IAudioFlingerClient.h>
 #include <media/IAudioTrack.h>
@@ -61,7 +63,7 @@
 
 // ----------------------------------------------------------------------------
 
-static const nsecs_t kStandbyTimeInNsecs = seconds(3);
+static const nsecs_t kDefaultStandbyTimeInNsecs = seconds(3);
 
 class AudioFlinger :
     public BinderService<AudioFlinger>,
@@ -84,6 +86,7 @@
                                 uint32_t flags,
                                 const sp<IMemory>& sharedBuffer,
                                 int output,
+                                bool isTimed,
                                 int *sessionId,
                                 status_t *status);
 
@@ -97,6 +100,7 @@
     virtual     status_t    setMasterMute(bool muted);
 
     virtual     float       masterVolume() const;
+    virtual     float       masterVolumeSW() const;
     virtual     bool        masterMute() const;
 
     virtual     status_t    setStreamVolume(int stream, float value, int output);
@@ -188,6 +192,7 @@
         AUDIO_HW_SET_MIC_MUTE,
         AUDIO_SET_VOICE_VOLUME,
         AUDIO_SET_PARAMETER,
+        AUDIO_HW_GET_MASTER_VOLUME,
     };
 
     // record interface
@@ -221,6 +226,11 @@
     audio_hw_device_t*      findSuitableHwDev_l(uint32_t devices);
     void                    purgeStaleEffects_l();
 
+    static Mutex            mStaticInitLock;
+    static bool             mStaticInitDone;
+    static bool             doStaticInit();
+    static nsecs_t          mStandbyTimeInNsecs;
+
     // Internal dump utilites.
     status_t dumpPermissionDenial(int fd, const Vector<String16>& args);
     status_t dumpClients(int fd, const Vector<String16>& args);
@@ -235,12 +245,18 @@
         pid_t               pid() const { return mPid; }
         sp<AudioFlinger>    audioFlinger() { return mAudioFlinger; }
 
+        bool reserveTimedTrack();
+        void releaseTimedTrack();
+
     private:
                             Client(const Client&);
                             Client& operator = (const Client&);
         sp<AudioFlinger>    mAudioFlinger;
         sp<MemoryDealer>    mMemoryDealer;
         pid_t               mPid;
+
+        Mutex               mTimedTrackLock;
+        int                 mTimedTrackCount;
     };
 
     // --- Notification Client ---
@@ -346,7 +362,9 @@
                                 TrackBase(const TrackBase&);
                                 TrackBase& operator = (const TrackBase&);
 
-            virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer) = 0;
+            virtual status_t getNextBuffer(
+                AudioBufferProvider::Buffer* buffer,
+                int64_t pts) = 0;
             virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
 
             uint32_t format() const {
@@ -611,7 +629,6 @@
                     int16_t     *mainBuffer() { return mMainBuffer; }
                     int         auxEffectId() { return mAuxEffectId; }
 
-
         protected:
             friend class ThreadBase;
             friend class TrackHandle;
@@ -622,7 +639,11 @@
                                 Track(const Track&);
                                 Track& operator = (const Track&);
 
-            virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+            virtual status_t getNextBuffer(
+                AudioBufferProvider::Buffer* buffer,
+                int64_t pts);
+            virtual uint32_t framesReady() const;
+
             bool isMuted() { return mMute; }
             bool isPausing() const {
                 return mState == PAUSING;
@@ -638,6 +659,8 @@
                 return (mStreamType == AUDIO_STREAM_CNT);
             }
 
+            virtual bool isTimedTrack() const { return false; }
+
             // we don't really need a lock for these
             float               mVolume[2];
             volatile bool       mMute;
@@ -655,6 +678,79 @@
             bool                mHasVolumeController;
         };  // end of Track
 
+        class TimedTrack : public Track {
+          public:
+            static sp<TimedTrack> create(const wp<ThreadBase>& thread,
+                                         const sp<Client>& client,
+                                         int streamType,
+                                         uint32_t sampleRate,
+                                         uint32_t format,
+                                         uint32_t channelMask,
+                                         int frameCount,
+                                         const sp<IMemory>& sharedBuffer,
+                                         int sessionId);
+            ~TimedTrack();
+
+            class TimedBuffer {
+              public:
+                TimedBuffer();
+                TimedBuffer(const sp<IMemory>& buffer, int64_t pts);
+                const sp<IMemory>& buffer() const { return mBuffer; }
+                int64_t pts() const { return mPTS; }
+                int position() const { return mPosition; }
+                void setPosition(int pos) { mPosition = pos; }
+              private:
+                sp<IMemory> mBuffer;
+                int64_t mPTS;
+                int mPosition;
+            };
+
+            virtual bool isTimedTrack() const { return true; }
+
+            virtual uint32_t framesReady() const;
+
+            virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer,
+                                           int64_t pts);
+            virtual void releaseBuffer(AudioBufferProvider::Buffer* buffer);
+            void timedYieldSamples(AudioBufferProvider::Buffer* buffer);
+            void timedYieldSilence(uint32_t numFrames,
+                                   AudioBufferProvider::Buffer* buffer);
+
+            status_t    allocateTimedBuffer(size_t size,
+                                            sp<IMemory>* buffer);
+            status_t    queueTimedBuffer(const sp<IMemory>& buffer,
+                                         int64_t pts);
+            status_t    setMediaTimeTransform(const LinearTransform& xform,
+                                              TimedAudioTrack::TargetTimeline target);
+            void        trimTimedBufferQueue_l();
+
+          private:
+            TimedTrack(const wp<ThreadBase>& thread,
+                       const sp<Client>& client,
+                       int streamType,
+                       uint32_t sampleRate,
+                       uint32_t format,
+                       uint32_t channelMask,
+                       int frameCount,
+                       const sp<IMemory>& sharedBuffer,
+                       int sessionId);
+
+            uint64_t            mLocalTimeFreq;
+            LinearTransform     mLocalTimeToSampleTransform;
+            sp<MemoryDealer>    mTimedMemoryDealer;
+            Vector<TimedBuffer> mTimedBufferQueue;
+            uint8_t*            mTimedSilenceBuffer;
+            uint32_t            mTimedSilenceBufferSize;
+            mutable Mutex       mTimedBufferQueueLock;
+            bool                mTimedAudioOutputOnTime;
+            CCHelper            mCCHelper;
+
+            Mutex               mMediaTimeTransformLock;
+            LinearTransform     mMediaTimeTransform;
+            bool                mMediaTimeTransformValid;
+            TimedAudioTrack::TargetTimeline mMediaTimeTransformTarget;
+        };
+
 
         // playback track
         class OutputTrack : public Track {
@@ -728,6 +824,7 @@
                                     int frameCount,
                                     const sp<IMemory>& sharedBuffer,
                                     int sessionId,
+                                    bool isTimed,
                                     status_t *status);
 
                     AudioStreamOut* getOutput();
@@ -917,6 +1014,12 @@
         virtual void        setVolume(float left, float right);
         virtual sp<IMemory> getCblk() const;
         virtual status_t    attachAuxEffect(int effectId);
+        virtual status_t    allocateTimedBuffer(size_t size,
+                                                sp<IMemory>* buffer);
+        virtual status_t    queueTimedBuffer(const sp<IMemory>& buffer,
+                                             int64_t pts);
+        virtual status_t    setMediaTimeTransform(const LinearTransform& xform,
+                                                  int target);
         virtual status_t onTransact(
             uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags);
     private:
@@ -964,7 +1067,9 @@
                                 RecordTrack(const RecordTrack&);
                                 RecordTrack& operator = (const RecordTrack&);
 
-            virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+            virtual status_t getNextBuffer(
+                AudioBufferProvider::Buffer* buffer,
+                int64_t pts);
 
             bool                mOverflow;
         };
@@ -1000,7 +1105,8 @@
                 AudioStreamIn* clearInput();
                 virtual audio_stream_t* stream();
 
-        virtual status_t    getNextBuffer(AudioBufferProvider::Buffer* buffer);
+        virtual status_t    getNextBuffer(AudioBufferProvider::Buffer* buffer,
+                                          int64_t pts);
         virtual void        releaseBuffer(AudioBufferProvider::Buffer* buffer);
         virtual bool        checkForNewParameters_l();
         virtual String8     getParameters(const String8& keys);
@@ -1384,6 +1490,12 @@
     friend class RecordThread;
     friend class PlaybackThread;
 
+    enum master_volume_support {
+        MVS_NONE,
+        MVS_SETONLY,
+        MVS_FULL,
+    };
+
     mutable     Mutex                               mLock;
 
                 DefaultKeyedVector< pid_t, wp<Client> >     mClients;
@@ -1397,6 +1509,8 @@
                 DefaultKeyedVector< int, sp<PlaybackThread> >  mPlaybackThreads;
                 PlaybackThread::stream_type_t       mStreamTypes[AUDIO_STREAM_CNT];
                 float                               mMasterVolume;
+                float                               mMasterVolumeSW;
+                master_volume_support               mMasterVolumeSupportLvl;
                 bool                                mMasterMute;
 
                 DefaultKeyedVector< int, sp<RecordThread> >    mRecordThreads;
diff --git a/services/audioflinger/AudioMixer.cpp b/services/audioflinger/AudioMixer.cpp
index 1200f75..98415b1 100644
--- a/services/audioflinger/AudioMixer.cpp
+++ b/services/audioflinger/AudioMixer.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "AudioMixer"
 //#define LOG_NDEBUG 0
 
+#include <assert.h>
 #include <stdint.h>
 #include <string.h>
 #include <stdlib.h>
@@ -30,6 +31,9 @@
 
 #include <system/audio.h>
 
+#include <common_time/local_clock.h>
+#include <common_time/cc_helper.h>
+
 #include "AudioMixer.h"
 
 namespace android {
@@ -47,6 +51,9 @@
 AudioMixer::AudioMixer(size_t frameCount, uint32_t sampleRate)
     :   mActiveTrack(0), mTrackNames(0), mSampleRate(sampleRate)
 {
+    LocalClock lc;
+    mLocalTimeFreq = lc.getLocalFreq();
+
     mState.enabledTracks= 0;
     mState.needsChanged = 0;
     mState.frameCount   = frameCount;
@@ -74,6 +81,7 @@
         t->in = 0;
         t->mainBuffer = NULL;
         t->auxBuffer = NULL;
+        t->localTimeFreq = mLocalTimeFreq;
         t++;
     }
 }
@@ -293,6 +301,7 @@
             if (resampler == 0) {
                 resampler = AudioResampler::create(
                         format, channelCount, devSampleRate);
+                resampler->setLocalTimeFreq(localTimeFreq);
             }
             return true;
         }
@@ -357,13 +366,13 @@
 
 
 
-void AudioMixer::process()
+void AudioMixer::process(int64_t pts)
 {
-    mState.hook(&mState);
+    mState.hook(&mState, pts);
 }
 
 
-void AudioMixer::process__validate(state_t* state)
+void AudioMixer::process__validate(state_t* state, int64_t pts)
 {
     LOGW_IF(!state->needsChanged,
         "in process__validate() but nothing's invalid");
@@ -467,7 +476,7 @@
         countActiveTracks, state->enabledTracks,
         all16BitsStereoNoResample, resampling, volumeRamp);
 
-   state->hook(state);
+   state->hook(state, pts);
 
    // Now that the volume ramp has been done, set optimal state and
    // track hooks for subsequent mixer process
@@ -876,7 +885,7 @@
 }
 
 // no-op case
-void AudioMixer::process__nop(state_t* state)
+void AudioMixer::process__nop(state_t* state, int64_t pts)
 {
     uint32_t e0 = state->enabledTracks;
     size_t bufSize = state->frameCount * sizeof(int16_t) * MAX_NUM_CHANNELS;
@@ -906,7 +915,9 @@
             size_t outFrames = state->frameCount;
             while (outFrames) {
                 t1.buffer.frameCount = outFrames;
-                t1.bufferProvider->getNextBuffer(&t1.buffer);
+                int64_t outputPTS = calculateOutputPTS(
+                    t1, pts, state->frameCount - outFrames);
+                t1.bufferProvider->getNextBuffer(&t1.buffer, outputPTS);
                 if (!t1.buffer.raw) break;
                 outFrames -= t1.buffer.frameCount;
                 t1.bufferProvider->releaseBuffer(&t1.buffer);
@@ -916,7 +927,7 @@
 }
 
 // generic code without resampling
-void AudioMixer::process__genericNoResampling(state_t* state)
+void AudioMixer::process__genericNoResampling(state_t* state, int64_t pts)
 {
     int32_t outTemp[BLOCKSIZE * MAX_NUM_CHANNELS] __attribute__((aligned(32)));
 
@@ -928,7 +939,7 @@
         e0 &= ~(1<<i);
         track_t& t = state->tracks[i];
         t.buffer.frameCount = state->frameCount;
-        t.bufferProvider->getNextBuffer(&t.buffer);
+        t.bufferProvider->getNextBuffer(&t.buffer, pts);
         t.frameCount = t.buffer.frameCount;
         t.in = t.buffer.raw;
         // t.in == NULL can happen if the track was flushed just after having
@@ -982,7 +993,9 @@
                     if (t.frameCount == 0 && outFrames) {
                         t.bufferProvider->releaseBuffer(&t.buffer);
                         t.buffer.frameCount = (state->frameCount - numFrames) - (BLOCKSIZE - outFrames);
-                        t.bufferProvider->getNextBuffer(&t.buffer);
+                        int64_t outputPTS = calculateOutputPTS(
+                            t, pts, numFrames + (BLOCKSIZE - outFrames));
+                        t.bufferProvider->getNextBuffer(&t.buffer, outputPTS);
                         t.in = t.buffer.raw;
                         if (t.in == NULL) {
                             enabledTracks &= ~(1<<i);
@@ -1011,7 +1024,7 @@
 
 
   // generic code with resampling
-void AudioMixer::process__genericResampling(state_t* state)
+void AudioMixer::process__genericResampling(state_t* state, int64_t pts)
 {
     int32_t* const outTemp = state->outputTemp;
     const size_t size = sizeof(int32_t) * MAX_NUM_CHANNELS * state->frameCount;
@@ -1050,6 +1063,7 @@
             // acquire/release the buffers because it's done by
             // the resampler.
             if ((t.needs & NEEDS_RESAMPLE__MASK) == NEEDS_RESAMPLE_ENABLED) {
+                t.resampler->setPTS(pts);
                 (t.hook)(&t, outTemp, numFrames, state->resampleTemp, aux);
             } else {
 
@@ -1057,7 +1071,8 @@
 
                 while (outFrames < numFrames) {
                     t.buffer.frameCount = numFrames - outFrames;
-                    t.bufferProvider->getNextBuffer(&t.buffer);
+                    int64_t outputPTS = calculateOutputPTS(t, pts, outFrames);
+                    t.bufferProvider->getNextBuffer(&t.buffer, outputPTS);
                     t.in = t.buffer.raw;
                     // t.in == NULL can happen if the track was flushed just after having
                     // been enabled for mixing.
@@ -1077,7 +1092,8 @@
 }
 
 // one track, 16 bits stereo without resampling is the most common case
-void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__OneTrack16BitsStereoNoResampling(state_t* state,
+                                                           int64_t pts)
 {
     const int i = 31 - __builtin_clz(state->enabledTracks);
     const track_t& t = state->tracks[i];
@@ -1092,7 +1108,8 @@
     const uint32_t vrl = t.volumeRL;
     while (numFrames) {
         b.frameCount = numFrames;
-        t.bufferProvider->getNextBuffer(&b);
+        int64_t outputPTS = calculateOutputPTS(t, pts, out - t.mainBuffer);
+        t.bufferProvider->getNextBuffer(&b, outputPTS);
         int16_t const *in = b.i16;
 
         // in == NULL can happen if the track was flushed just after having
@@ -1135,7 +1152,8 @@
 // 2 tracks is also a common case
 // NEVER used in current implementation of process__validate()
 // only use if the 2 tracks have the same output buffer
-void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state)
+void AudioMixer::process__TwoTracks16BitsStereoNoResampling(state_t* state,
+                                                            int64_t pts)
 {
     int i;
     uint32_t en = state->enabledTracks;
@@ -1169,7 +1187,9 @@
 
         if (frameCount0 == 0) {
             b0.frameCount = numFrames;
-            t0.bufferProvider->getNextBuffer(&b0);
+            int64_t outputPTS = calculateOutputPTS(t0, pts,
+                                                   out - t0.mainBuffer);
+            t0.bufferProvider->getNextBuffer(&b0, outputPTS);
             if (b0.i16 == NULL) {
                 if (buff == NULL) {
                     buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount];
@@ -1183,7 +1203,9 @@
         }
         if (frameCount1 == 0) {
             b1.frameCount = numFrames;
-            t1.bufferProvider->getNextBuffer(&b1);
+            int64_t outputPTS = calculateOutputPTS(t1, pts,
+                                                   out - t0.mainBuffer);
+            t1.bufferProvider->getNextBuffer(&b1, outputPTS);
             if (b1.i16 == NULL) {
                 if (buff == NULL) {
                     buff = new int16_t[MAX_NUM_CHANNELS * state->frameCount];
@@ -1230,6 +1252,11 @@
     }
 }
 
+int64_t AudioMixer::calculateOutputPTS(const track_t& t, int64_t basePTS,
+                                       int outputFrameIndex)
+{
+    return basePTS + ((outputFrameIndex * t.localTimeFreq) / t.sampleRate);
+}
+
 // ----------------------------------------------------------------------------
 }; // namespace android
-
diff --git a/services/audioflinger/AudioMixer.h b/services/audioflinger/AudioMixer.h
index 0137185..200ad03 100644
--- a/services/audioflinger/AudioMixer.h
+++ b/services/audioflinger/AudioMixer.h
@@ -85,7 +85,7 @@
     status_t    setParameter(int target, int name, void *value);
 
     status_t    setBufferProvider(AudioBufferProvider* bufferProvider);
-    void        process();
+    void        process(int64_t pts);
 
     uint32_t    trackNames() const { return mTrackNames; }
 
@@ -127,7 +127,7 @@
     struct state_t;
     struct track_t;
 
-    typedef void (*mix_t)(state_t* state);
+    typedef void (*mix_t)(state_t* state, int64_t pts);
     typedef void (*hook_t)(track_t* t, int32_t* output, size_t numOutFrames, int32_t* temp, int32_t* aux);
     static const int BLOCKSIZE = 16; // 4 cache lines
 
@@ -165,6 +165,8 @@
         int32_t*           mainBuffer;
         int32_t*           auxBuffer;
 
+        uint64_t    localTimeFreq;
+
         bool        setResampler(uint32_t sampleRate, uint32_t devSampleRate);
         bool        doesResample() const;
         void        resetResampler();
@@ -188,6 +190,8 @@
     uint32_t        mTrackNames;
     const uint32_t  mSampleRate;
 
+    int64_t         mLocalTimeFreq;
+
     state_t         mState __attribute__((aligned(32)));
 
     void invalidateState(uint32_t mask);
@@ -199,12 +203,17 @@
     static void volumeRampStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
     static void volumeStereo(track_t* t, int32_t* out, size_t frameCount, int32_t* temp, int32_t* aux);
 
-    static void process__validate(state_t* state);
-    static void process__nop(state_t* state);
-    static void process__genericNoResampling(state_t* state);
-    static void process__genericResampling(state_t* state);
-    static void process__OneTrack16BitsStereoNoResampling(state_t* state);
-    static void process__TwoTracks16BitsStereoNoResampling(state_t* state);
+    static void process__validate(state_t* state, int64_t pts);
+    static void process__nop(state_t* state, int64_t pts);
+    static void process__genericNoResampling(state_t* state, int64_t pts);
+    static void process__genericResampling(state_t* state, int64_t pts);
+    static void process__OneTrack16BitsStereoNoResampling(state_t* state,
+                                                          int64_t pts);
+    static void process__TwoTracks16BitsStereoNoResampling(state_t* state,
+                                                           int64_t pts);
+
+    static int64_t calculateOutputPTS(const track_t& t, int64_t basePTS,
+                                      int outputFrameIndex);
 };
 
 // ----------------------------------------------------------------------------
diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp
index 9ee5a30..cd2857f 100644
--- a/services/audioflinger/AudioResampler.cpp
+++ b/services/audioflinger/AudioResampler.cpp
@@ -59,10 +59,12 @@
 #ifdef ASM_ARM_RESAMP1  // asm optimisation for ResamplerOrder1
     void AsmMono16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx,
             size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr,
-            uint32_t &phaseFraction, uint32_t phaseIncrement);
+            uint32_t &phaseFraction, uint32_t phaseIncrement)
+        __attribute__((noinline));
     void AsmStereo16Loop(int16_t *in, int32_t* maxOutPt, int32_t maxInIdx,
             size_t &outputIndex, int32_t* out, size_t &inputIndex, int32_t vl, int32_t vr,
-            uint32_t &phaseFraction, uint32_t phaseIncrement);
+            uint32_t &phaseFraction, uint32_t phaseIncrement)
+        __attribute__((noinline));
 #endif  // ASM_ARM_RESAMP1
 
     static inline int32_t Interp(int32_t x0, int32_t x1, uint32_t f) {
@@ -118,7 +120,8 @@
         int32_t sampleRate) :
     mBitDepth(bitDepth), mChannelCount(inChannelCount),
             mSampleRate(sampleRate), mInSampleRate(sampleRate), mInputIndex(0),
-            mPhaseFraction(0) {
+            mPhaseFraction(0), mLocalTimeFreq(0),
+            mPTS(AudioBufferProvider::kInvalidPTS) {
     // sanity check on format
     if ((bitDepth != 16) ||(inChannelCount < 1) || (inChannelCount > 2)) {
         LOGE("Unsupported sample format, %d bits, %d channels", bitDepth,
@@ -152,6 +155,23 @@
     mVolume[1] = right;
 }
 
+void AudioResampler::setLocalTimeFreq(uint64_t freq) {
+    mLocalTimeFreq = freq;
+}
+
+void AudioResampler::setPTS(int64_t pts) {
+    mPTS = pts;
+}
+
+int64_t AudioResampler::calculateOutputPTS(int outputFrameIndex) {
+
+    if (mPTS == AudioBufferProvider::kInvalidPTS) {
+        return AudioBufferProvider::kInvalidPTS;
+    } else {
+        return mPTS + ((outputFrameIndex * mLocalTimeFreq) / mSampleRate);
+    }
+}
+
 void AudioResampler::reset() {
     mInputIndex = 0;
     mPhaseFraction = 0;
@@ -198,7 +218,8 @@
         // buffer is empty, fetch a new one
         while (mBuffer.frameCount == 0) {
             mBuffer.frameCount = inFrameCount;
-            provider->getNextBuffer(&mBuffer);
+            provider->getNextBuffer(&mBuffer,
+                                    calculateOutputPTS(outputIndex / 2));
             if (mBuffer.raw == NULL) {
                 goto resampleStereo16_exit;
             }
@@ -292,7 +313,8 @@
         // buffer is empty, fetch a new one
         while (mBuffer.frameCount == 0) {
             mBuffer.frameCount = inFrameCount;
-            provider->getNextBuffer(&mBuffer);
+            provider->getNextBuffer(&mBuffer,
+                                    calculateOutputPTS(outputIndex / 2));
             if (mBuffer.raw == NULL) {
                 mInputIndex = inputIndex;
                 mPhaseFraction = phaseFraction;
@@ -602,4 +624,3 @@
 // ----------------------------------------------------------------------------
 }
 ; // namespace android
-
diff --git a/services/audioflinger/AudioResampler.h b/services/audioflinger/AudioResampler.h
index ffa690a..2b1488b 100644
--- a/services/audioflinger/AudioResampler.h
+++ b/services/audioflinger/AudioResampler.h
@@ -49,6 +49,10 @@
     virtual void init() = 0;
     virtual void setSampleRate(int32_t inSampleRate);
     virtual void setVolume(int16_t left, int16_t right);
+    virtual void setLocalTimeFreq(uint64_t freq);
+
+    // set the PTS of the next buffer output by the resampler
+    virtual void setPTS(int64_t pts);
 
     virtual void resample(int32_t* out, size_t outFrameCount,
             AudioBufferProvider* provider) = 0;
@@ -73,6 +77,8 @@
     AudioResampler(const AudioResampler&);
     AudioResampler& operator=(const AudioResampler&);
 
+    int64_t calculateOutputPTS(int outputFrameIndex);
+
     int32_t mBitDepth;
     int32_t mChannelCount;
     int32_t mSampleRate;
@@ -87,6 +93,8 @@
     size_t mInputIndex;
     int32_t mPhaseIncrement;
     uint32_t mPhaseFraction;
+    uint64_t mLocalTimeFreq;
+    int64_t mPTS;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/services/audioflinger/AudioResamplerCubic.cpp b/services/audioflinger/AudioResamplerCubic.cpp
index 4d721f6..e864b30 100644
--- a/services/audioflinger/AudioResamplerCubic.cpp
+++ b/services/audioflinger/AudioResamplerCubic.cpp
@@ -65,7 +65,7 @@
     // fetch first buffer
     if (mBuffer.frameCount == 0) {
         mBuffer.frameCount = inFrameCount;
-        provider->getNextBuffer(&mBuffer);
+        provider->getNextBuffer(&mBuffer, mPTS);
         if (mBuffer.raw == NULL)
             return;
         // LOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
@@ -95,7 +95,8 @@
                 inputIndex = 0;
                 provider->releaseBuffer(&mBuffer);
                 mBuffer.frameCount = inFrameCount;
-                provider->getNextBuffer(&mBuffer);
+                provider->getNextBuffer(&mBuffer,
+                                        calculateOutputPTS(outputIndex / 2));
                 if (mBuffer.raw == NULL)
                     goto save_state;  // ugly, but efficient
                 in = mBuffer.i16;
@@ -130,7 +131,7 @@
     // fetch first buffer
     if (mBuffer.frameCount == 0) {
         mBuffer.frameCount = inFrameCount;
-        provider->getNextBuffer(&mBuffer);
+        provider->getNextBuffer(&mBuffer, mPTS);
         if (mBuffer.raw == NULL)
             return;
         // LOGW("New buffer: offset=%p, frames=%d\n", mBuffer.raw, mBuffer.frameCount);
@@ -160,7 +161,8 @@
                 inputIndex = 0;
                 provider->releaseBuffer(&mBuffer);
                 mBuffer.frameCount = inFrameCount;
-                provider->getNextBuffer(&mBuffer);
+                provider->getNextBuffer(&mBuffer,
+                                        calculateOutputPTS(outputIndex / 2));
                 if (mBuffer.raw == NULL)
                     goto save_state;  // ugly, but efficient
                 // LOGW("New buffer: offset=%p, frames=%dn", mBuffer.raw, mBuffer.frameCount);
@@ -181,4 +183,3 @@
 // ----------------------------------------------------------------------------
 }
 ; // namespace android
-
diff --git a/services/audioflinger/AudioResamplerSinc.cpp b/services/audioflinger/AudioResamplerSinc.cpp
index 9e5e254..6184795 100644
--- a/services/audioflinger/AudioResamplerSinc.cpp
+++ b/services/audioflinger/AudioResamplerSinc.cpp
@@ -204,7 +204,8 @@
         // buffer is empty, fetch a new one
         while (buffer.frameCount == 0) {
             buffer.frameCount = inFrameCount;
-            provider->getNextBuffer(&buffer);
+            provider->getNextBuffer(&buffer,
+                                    calculateOutputPTS(outputIndex / 2));
             if (buffer.raw == NULL) {
                 goto resample_exit;
             }
@@ -355,4 +356,3 @@
 
 // ----------------------------------------------------------------------------
 }; // namespace android
-
diff --git a/services/common_time/Android.mk b/services/common_time/Android.mk
new file mode 100644
index 0000000..e534d49
--- /dev/null
+++ b/services/common_time/Android.mk
@@ -0,0 +1,34 @@
+LOCAL_PATH:= $(call my-dir)
+
+#
+# common_time_service
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    common_clock_service.cpp \
+    common_time_config_service.cpp \
+    common_time_server.cpp \
+    common_time_server_api.cpp \
+    common_time_server_packets.cpp \
+    clock_recovery.cpp \
+    common_clock.cpp \
+    main.cpp
+
+# Uncomment to enable vesbose logging and debug service.
+#TIME_SERVICE_DEBUG=true
+ifeq ($(TIME_SERVICE_DEBUG), true)
+LOCAL_SRC_FILES += diag_thread.cpp
+LOCAL_CFLAGS += -DTIME_SERVICE_DEBUG
+endif
+
+LOCAL_SHARED_LIBRARIES := \
+    libbinder \
+    libcommon_time_client \
+    libutils
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := common_time
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/common_time/clock_recovery.cpp b/services/common_time/clock_recovery.cpp
new file mode 100644
index 0000000..4a1b57e
--- /dev/null
+++ b/services/common_time/clock_recovery.cpp
@@ -0,0 +1,321 @@
+/*
+ * 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define __STDC_LIMIT_MACROS
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+#include <stdint.h>
+
+#include <common_time/local_clock.h>
+#include <assert.h>
+
+#include "clock_recovery.h"
+#include "common_clock.h"
+#ifdef TIME_SERVICE_DEBUG
+#include "diag_thread.h"
+#endif
+
+// Define log macro so we can make LOGV into LOGE when we are exclusively
+// debugging this code.
+#ifdef TIME_SERVICE_DEBUG
+#define LOG_TS LOGE
+#else
+#define LOG_TS LOGV
+#endif
+
+namespace android {
+
+ClockRecoveryLoop::ClockRecoveryLoop(LocalClock* local_clock,
+                                     CommonClock* common_clock) {
+    assert(NULL != local_clock);
+    assert(NULL != common_clock);
+
+    local_clock_  = local_clock;
+    common_clock_ = common_clock;
+
+    local_clock_can_slew_ = local_clock_->initCheck() &&
+                           (local_clock_->setLocalSlew(0) == OK);
+
+    reset(true, true);
+
+#ifdef TIME_SERVICE_DEBUG
+    diag_thread_ = new DiagThread(common_clock_, local_clock_);
+    if (diag_thread_ != NULL) {
+        status_t res = diag_thread_->startWorkThread();
+        if (res != OK)
+            LOGW("Failed to start A@H clock recovery diagnostic thread.");
+    } else
+        LOGW("Failed to allocate diagnostic thread.");
+#endif
+}
+
+ClockRecoveryLoop::~ClockRecoveryLoop() {
+#ifdef TIME_SERVICE_DEBUG
+    diag_thread_->stopWorkThread();
+#endif
+}
+
+// Constants.
+const float ClockRecoveryLoop::dT = 1.0;
+const float ClockRecoveryLoop::Kc = 1.0f;
+const float ClockRecoveryLoop::Ti = 15.0f;
+const float ClockRecoveryLoop::Tf = 0.05;
+const float ClockRecoveryLoop::bias_Fc = 0.01;
+const float ClockRecoveryLoop::bias_RC = (dT / (2 * 3.14159f * bias_Fc));
+const float ClockRecoveryLoop::bias_Alpha = (dT / (bias_RC + dT));
+const int64_t ClockRecoveryLoop::panic_thresh_ = 50000;
+const int64_t ClockRecoveryLoop::control_thresh_ = 10000;
+const float ClockRecoveryLoop::COmin = -100.0f;
+const float ClockRecoveryLoop::COmax = 100.0f;
+
+void ClockRecoveryLoop::reset(bool position, bool frequency) {
+    Mutex::Autolock lock(&lock_);
+    reset_l(position, frequency);
+}
+
+uint32_t ClockRecoveryLoop::findMinRTTNdx(DisciplineDataPoint* data,
+                                          uint32_t count) {
+    uint32_t min_rtt = 0;
+    for (uint32_t i = 1; i < count; ++i)
+        if (data[min_rtt].rtt > data[i].rtt)
+            min_rtt = i;
+
+    return min_rtt;
+}
+
+bool ClockRecoveryLoop::pushDisciplineEvent(int64_t local_time,
+                                            int64_t nominal_common_time,
+                                            int64_t rtt) {
+    Mutex::Autolock lock(&lock_);
+
+    int64_t local_common_time = 0;
+    common_clock_->localToCommon(local_time, &local_common_time);
+    int64_t raw_delta = nominal_common_time - local_common_time;
+
+#ifdef TIME_SERVICE_DEBUG
+    LOGE("local=%lld, common=%lld, delta=%lld, rtt=%lld\n",
+         local_common_time, nominal_common_time,
+         raw_delta, rtt);
+#endif
+
+    // If we have not defined a basis for common time, then we need to use these
+    // initial points to do so.  In order to avoid significant initial error
+    // from a particularly bad startup data point, we collect the first N data
+    // points and choose the best of them before moving on.
+    if (!common_clock_->isValid()) {
+        if (startup_filter_wr_ < kStartupFilterSize) {
+            DisciplineDataPoint& d =  startup_filter_data_[startup_filter_wr_];
+            d.local_time = local_time;
+            d.nominal_common_time = nominal_common_time;
+            d.rtt = rtt;
+            startup_filter_wr_++;
+        }
+
+        if (startup_filter_wr_ == kStartupFilterSize) {
+            uint32_t min_rtt = findMinRTTNdx(startup_filter_data_,
+                    kStartupFilterSize);
+
+            common_clock_->setBasis(
+                    startup_filter_data_[min_rtt].local_time,
+                    startup_filter_data_[min_rtt].nominal_common_time);
+        }
+
+        return true;
+    }
+
+    int64_t observed_common;
+    int64_t delta;
+    float delta_f, dCO;
+    int32_t correction_cur;
+
+    if (OK != common_clock_->localToCommon(local_time, &observed_common)) {
+        // Since we just checked to make certain that this conversion was valid,
+        // and no one else in the system should be messing with it, if this
+        // conversion is suddenly invalid, it is a good reason to panic.
+        LOGE("Failed to convert local time to common time in %s:%d",
+                __PRETTY_FUNCTION__, __LINE__);
+        return false;
+    }
+
+    // Implement a filter which should match NTP filtering behavior when a
+    // client is associated with only one peer of lower stratum.  Basically,
+    // always use the best of the N last data points, where best is defined as
+    // lowest round trip time.  NTP uses an N of 8; we use a value of 6.
+    //
+    // TODO(johngro) : experiment with other filter strategies.  The goal here
+    // is to mitigate the effects of high RTT data points which typically have
+    // large asymmetries in the TX/RX legs.  Downside of the existing NTP
+    // approach (particularly because of the PID controller we are using to
+    // produce the control signal from the filtered data) are that the rate at
+    // which discipline events are actually acted upon becomes irregular and can
+    // become drawn out (the time between actionable event can go way up).  If
+    // the system receives a strong high quality data point, the proportional
+    // component of the controller can produce a strong correction which is left
+    // in place for too long causing overshoot.  In addition, the integral
+    // component of the system currently is an approximation based on the
+    // assumption of a more or less homogeneous sampling of the error.  Its
+    // unclear what the effect of undermining this assumption would be right
+    // now.
+
+    // Two ideas which come to mind immediately would be to...
+    // 1) Keep a history of more data points (32 or so) and ignore data points
+    //    whose RTT is more than a certain number of standard deviations outside
+    //    of the norm.
+    // 2) Eliminate the PID controller portion of this system entirely.
+    //    Instead, move to a system which uses a very wide filter (128 data
+    //    points or more) with a sum-of-least-squares line fitting approach to
+    //    tracking the long term drift.  This would take the place of the I
+    //    component in the current PID controller.  Also use a much more narrow
+    //    outlier-rejector filter (as described in #1) to drive a short term
+    //    correction factor similar to the P component of the PID controller.
+    assert(filter_wr_ < kFilterSize);
+    filter_data_[filter_wr_].local_time           = local_time;
+    filter_data_[filter_wr_].observed_common_time = observed_common;
+    filter_data_[filter_wr_].nominal_common_time  = nominal_common_time;
+    filter_data_[filter_wr_].rtt                  = rtt;
+    filter_data_[filter_wr_].point_used           = false;
+    uint32_t current_point = filter_wr_;
+    filter_wr_ = (filter_wr_ + 1) % kFilterSize;
+    if (!filter_wr_)
+        filter_full_ = true;
+
+    uint32_t scan_end = filter_full_ ? kFilterSize : filter_wr_;
+    uint32_t min_rtt = findMinRTTNdx(filter_data_, scan_end);
+    // We only use packets with low RTTs for control. If the packet RTT
+    // is less than the panic threshold, we can probably eat the jitter with the
+    // control loop. Otherwise, take the packet only if it better than all
+    // of the packets we have in the history. That way we try to track
+    // something, even if it is noisy.
+    if (current_point == min_rtt || rtt < control_thresh_) {
+        delta_f = delta = nominal_common_time - observed_common;
+
+        // Compute the error then clamp to the panic threshold.  If we ever
+        // exceed this amt of error, its time to panic and reset the system.
+        // Given that the error in the measurement of the error could be as
+        // high as the RTT of the data point, we don't actually panic until
+        // the implied error (delta) is greater than the absolute panic
+        // threashold plus the RTT.  IOW - we don't panic until we are
+        // absoluely sure that our best case sync is worse than the absolute
+        // panic threshold.
+        int64_t effective_panic_thresh = panic_thresh_ + rtt;
+        if ((delta > effective_panic_thresh) ||
+            (delta < -effective_panic_thresh)) {
+            // PANIC!!!
+            reset_l(false, true);
+            return false;
+        }
+
+    } else {
+        // We do not have a good packet to look at, but we also do not want to
+        // free-run the clock at some crazy slew rate. So we guess the
+        // trajectory of the clock based on the last controller output and the
+        // estimated bias of our clock against the master.
+        // The net effect of this is that CO == CObias after some extended
+        // period of no feedback.
+        delta_f = last_delta_f_ - dT*(CO - CObias);
+        delta = delta_f;
+    }
+
+    // Velocity form PI control equation.
+    dCO = Kc * (1.0f + dT/Ti) * delta_f - Kc * last_delta_f_;
+    CO += dCO * Tf; // Filter CO by applying gain <1 here.
+
+    // Save error terms for later.
+    last_delta_f_ = delta_f;
+    last_delta_ = delta;
+
+    // Clamp CO to +/- 100ppm.
+    if (CO < COmin)
+        CO = COmin;
+    else if (CO > COmax)
+        CO = COmax;
+
+    // Update the controller bias.
+    CObias = bias_Alpha * CO + (1.0f - bias_Alpha) * lastCObias;
+    lastCObias = CObias;
+
+    // Convert PPM to 16-bit int range. Add some guard band (-0.01) so we
+    // don't get fp weirdness.
+    correction_cur = CO * 327.66;
+
+    // If there was a change in the amt of correction to use, update the
+    // system.
+    if (correction_cur_ != correction_cur) {
+        correction_cur_ = correction_cur;
+        applySlew();
+    }
+
+    LOG_TS("clock_loop %lld %f %f %f %d\n", raw_delta, delta_f, CO, CObias, correction_cur);
+
+#ifdef TIME_SERVICE_DEBUG
+    diag_thread_->pushDisciplineEvent(
+            local_time,
+            observed_common,
+            nominal_common_time,
+            correction_cur,
+            rtt);
+#endif
+
+    return true;
+}
+
+int32_t ClockRecoveryLoop::getLastErrorEstimate() {
+    Mutex::Autolock lock(&lock_);
+
+    if (last_delta_valid_)
+        return last_delta_;
+    else
+        return ICommonClock::kErrorEstimateUnknown;
+}
+
+void ClockRecoveryLoop::reset_l(bool position, bool frequency) {
+    assert(NULL != common_clock_);
+
+    if (position) {
+        common_clock_->resetBasis();
+        startup_filter_wr_ = 0;
+    }
+
+    if (frequency) {
+        last_delta_valid_ = false;
+        last_delta_ = 0;
+        last_delta_f_ = 0.0;
+        correction_cur_ = 0x0;
+        CO = 0.0f;
+        lastCObias = CObias = 0.0f;
+        applySlew();
+    }
+
+    filter_wr_   = 0;
+    filter_full_ = false;
+}
+
+void ClockRecoveryLoop::applySlew() {
+    if (local_clock_can_slew_) {
+        local_clock_->setLocalSlew(correction_cur_);
+    } else {
+        // The SW clock recovery implemented by the common clock class expects
+        // values expressed in PPM. CO is in ppm.
+        common_clock_->setSlew(local_clock_->getLocalTime(), CO);
+    }
+}
+
+}  // namespace android
diff --git a/services/common_time/clock_recovery.h b/services/common_time/clock_recovery.h
new file mode 100644
index 0000000..b7362be
--- /dev/null
+++ b/services/common_time/clock_recovery.h
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+#ifndef __CLOCK_RECOVERY_H__
+#define __CLOCK_RECOVERY_H__
+
+#include <stdint.h>
+#include <common_time/ICommonClock.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+#ifdef TIME_SERVICE_DEBUG
+#include "diag_thread.h"
+#endif
+
+namespace android {
+
+class CommonClock;
+class LocalClock;
+
+class ClockRecoveryLoop {
+  public:
+     ClockRecoveryLoop(LocalClock* local_clock, CommonClock* common_clock);
+    ~ClockRecoveryLoop();
+
+    void reset(bool position, bool frequency);
+    bool pushDisciplineEvent(int64_t local_time,
+                             int64_t nominal_common_time,
+                             int64_t data_point_rtt);
+    int32_t getLastErrorEstimate();
+
+  private:
+
+    // Tuned using the "Good Gain" method.
+    // See:
+    // http://techteach.no/publications/books/dynamics_and_control/tuning_pid_controller.pdf
+
+    // Controller period (1Hz for now).
+    static const float dT;
+
+    // Controller gain, positive and unitless. Larger values converge faster,
+    // but can cause instability.
+    static const float Kc;
+
+    // Integral reset time. Smaller values cause loop to track faster, but can
+    // also cause instability.
+    static const float Ti;
+
+    // Controller output filter time constant. Range (0-1). Smaller values make
+    // output smoother, but slow convergence.
+    static const float Tf;
+
+    // Low-pass filter for bias tracker.
+    static const float bias_Fc; // HZ
+    static const float bias_RC; // Computed in constructor.
+    static const float bias_Alpha; // Computed inconstructor.
+
+    // The maximum allowed error (as indicated by a  pushDisciplineEvent) before
+    // we panic.
+    static const int64_t panic_thresh_;
+
+    // The maximum allowed error rtt time for packets to be used for control
+    // feedback, unless the packet is the best in recent memory.
+    static const int64_t control_thresh_;
+
+    typedef struct {
+        int64_t local_time;
+        int64_t observed_common_time;
+        int64_t nominal_common_time;
+        int64_t rtt;
+        bool point_used;
+    } DisciplineDataPoint;
+
+    static uint32_t findMinRTTNdx(DisciplineDataPoint* data, uint32_t count);
+
+    void reset_l(bool position, bool frequency);
+    void applySlew();
+
+    // The local clock HW abstraction we use as the basis for common time.
+    LocalClock* local_clock_;
+    bool local_clock_can_slew_;
+
+    // The common clock we end up controlling along with the lock used to
+    // serialize operations.
+    CommonClock* common_clock_;
+    Mutex lock_;
+
+    // parameters maintained while running and reset during a reset
+    // of the frequency correction.
+    bool    last_delta_valid_;
+    int32_t last_delta_;
+    float last_delta_f_;
+    int32_t integrated_error_;
+    int32_t correction_cur_;
+
+    // Contoller Output.
+    float CO;
+
+    // Bias tracking for trajectory estimation.
+    float CObias;
+    float lastCObias;
+
+    // Controller output bounds. The controller will not try to
+    // slew faster that +/-100ppm offset from center per interation.
+    static const float COmin;
+    static const float COmax;
+
+    // State kept for filtering the discipline data.
+    static const uint32_t kFilterSize = 16;
+    DisciplineDataPoint filter_data_[kFilterSize];
+    uint32_t filter_wr_;
+    bool filter_full_;
+
+    static const uint32_t kStartupFilterSize = 4;
+    DisciplineDataPoint startup_filter_data_[kStartupFilterSize];
+    uint32_t startup_filter_wr_;
+
+#ifdef TIME_SERVICE_DEBUG
+    sp<DiagThread> diag_thread_;
+#endif
+};
+
+}  // namespace android
+
+#endif  // __CLOCK_RECOVERY_H__
diff --git a/services/common_time/common_clock.cpp b/services/common_time/common_clock.cpp
new file mode 100644
index 0000000..c8edf35
--- /dev/null
+++ b/services/common_time/common_clock.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012 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 __STDC_LIMIT_MACROS
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <stdint.h>
+
+#include <utils/Errors.h>
+#include <utils/LinearTransform.h>
+
+#include "common_clock.h"
+
+namespace android {
+
+CommonClock::CommonClock() {
+    cur_slew_        = 0;
+    cur_trans_valid_ = false;
+
+    cur_trans_.a_zero = 0;
+    cur_trans_.b_zero = 0;
+    cur_trans_.a_to_b_numer = local_to_common_freq_numer_ = 1;
+    cur_trans_.a_to_b_denom = local_to_common_freq_denom_ = 1;
+    duration_trans_ = cur_trans_;
+}
+
+bool CommonClock::init(uint64_t local_freq) {
+    Mutex::Autolock lock(&lock_);
+
+    if (!local_freq)
+        return false;
+
+    uint64_t numer = kCommonFreq;
+    uint64_t denom = local_freq;
+
+    LinearTransform::reduce(&numer, &denom);
+    if ((numer > UINT32_MAX) || (denom > UINT32_MAX)) {
+        LOGE("Overflow in CommonClock::init while trying to reduce %lld/%lld",
+             kCommonFreq, local_freq);
+        return false;
+    }
+
+    cur_trans_.a_to_b_numer = local_to_common_freq_numer_ =
+        static_cast<uint32_t>(numer);
+    cur_trans_.a_to_b_denom = local_to_common_freq_denom_ =
+        static_cast<uint32_t>(denom);
+    duration_trans_ = cur_trans_;
+
+    return true;
+}
+
+status_t CommonClock::localToCommon(int64_t local, int64_t *common_out) const {
+    Mutex::Autolock lock(&lock_);
+
+    if (!cur_trans_valid_)
+        return INVALID_OPERATION;
+
+    if (!cur_trans_.doForwardTransform(local, common_out))
+        return INVALID_OPERATION;
+
+    return OK;
+}
+
+status_t CommonClock::commonToLocal(int64_t common, int64_t *local_out) const {
+    Mutex::Autolock lock(&lock_);
+
+    if (!cur_trans_valid_)
+        return INVALID_OPERATION;
+
+    if (!cur_trans_.doReverseTransform(common, local_out))
+        return INVALID_OPERATION;
+
+    return OK;
+}
+
+int64_t CommonClock::localDurationToCommonDuration(int64_t localDur) const {
+    int64_t ret;
+    duration_trans_.doForwardTransform(localDur, &ret);
+    return ret;
+}
+
+void CommonClock::setBasis(int64_t local, int64_t common) {
+    Mutex::Autolock lock(&lock_);
+
+    cur_trans_.a_zero = local;
+    cur_trans_.b_zero = common;
+    cur_trans_valid_ = true;
+}
+
+void CommonClock::resetBasis() {
+    Mutex::Autolock lock(&lock_);
+
+    cur_trans_.a_zero = 0;
+    cur_trans_.b_zero = 0;
+    cur_trans_valid_ = false;
+}
+
+status_t CommonClock::setSlew(int64_t change_time, int32_t ppm) {
+    Mutex::Autolock lock(&lock_);
+
+    int64_t new_local_basis;
+    int64_t new_common_basis;
+
+    if (cur_trans_valid_) {
+        new_local_basis = change_time;
+        if (!cur_trans_.doForwardTransform(change_time, &new_common_basis)) {
+            LOGE("Overflow when attempting to set slew rate to %d", ppm);
+            return INVALID_OPERATION;
+        }
+    } else {
+        new_local_basis = 0;
+        new_common_basis = 0;
+    }
+
+    cur_slew_ = ppm;
+    uint32_t n1 = local_to_common_freq_numer_;
+    uint32_t n2 = 1000000 + cur_slew_;
+
+    uint32_t d1 = local_to_common_freq_denom_;
+    uint32_t d2 = 1000000;
+
+    // n1/d1 has alredy been reduced, no need to do so here.
+    LinearTransform::reduce(&n1, &d2);
+    LinearTransform::reduce(&n2, &d1);
+    LinearTransform::reduce(&n2, &d2);
+
+    cur_trans_.a_zero = new_local_basis;
+    cur_trans_.b_zero = new_common_basis;
+    cur_trans_.a_to_b_numer = n1 * n2;
+    cur_trans_.a_to_b_denom = d1 * d2;
+
+    return OK;
+}
+
+}  // namespace android
diff --git a/services/common_time/common_clock.h b/services/common_time/common_clock.h
new file mode 100644
index 0000000..b786fdc
--- /dev/null
+++ b/services/common_time/common_clock.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef __COMMON_CLOCK_H__
+#define __COMMON_CLOCK_H__
+
+#include <stdint.h>
+
+#include <utils/Errors.h>
+#include <utils/LinearTransform.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class CommonClock {
+  public:
+    CommonClock();
+
+    bool      init(uint64_t local_freq);
+
+    status_t  localToCommon(int64_t local, int64_t *common_out) const;
+    status_t  commonToLocal(int64_t common, int64_t *local_out) const;
+    int64_t   localDurationToCommonDuration(int64_t localDur) const;
+    uint64_t  getCommonFreq() const { return kCommonFreq; }
+    bool      isValid() const { return cur_trans_valid_; }
+    status_t  setSlew(int64_t change_time, int32_t ppm);
+    void      setBasis(int64_t local, int64_t common);
+    void      resetBasis();
+  private:
+    mutable Mutex lock_;
+
+    int32_t  cur_slew_;
+    uint32_t local_to_common_freq_numer_;
+    uint32_t local_to_common_freq_denom_;
+
+    LinearTransform duration_trans_;
+    LinearTransform cur_trans_;
+    bool cur_trans_valid_;
+
+    static const uint64_t kCommonFreq = 1000000ull;
+};
+
+}  // namespace android
+#endif  // __COMMON_CLOCK_H__
diff --git a/services/common_time/common_clock_service.cpp b/services/common_time/common_clock_service.cpp
new file mode 100644
index 0000000..9ca6f35
--- /dev/null
+++ b/services/common_time/common_clock_service.cpp
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+#include <common_time/local_clock.h>
+#include <utils/String8.h>
+
+#include "common_clock_service.h"
+#include "common_clock.h"
+#include "common_time_server.h"
+
+namespace android {
+
+sp<CommonClockService> CommonClockService::instantiate(
+        CommonTimeServer& timeServer) {
+    sp<CommonClockService> tcc = new CommonClockService(timeServer);
+    if (tcc == NULL)
+        return NULL;
+
+    defaultServiceManager()->addService(ICommonClock::kServiceName, tcc);
+    return tcc;
+}
+
+status_t CommonClockService::dump(int fd, const Vector<String16>& args) {
+    Mutex::Autolock lock(mRegistrationLock);
+    return mTimeServer.dumpClockInterface(fd, args, mListeners.size());
+}
+
+status_t CommonClockService::isCommonTimeValid(bool* valid,
+                                               uint32_t* timelineID) {
+    return mTimeServer.isCommonTimeValid(valid, timelineID);
+}
+
+status_t CommonClockService::commonTimeToLocalTime(int64_t  commonTime,
+                                                   int64_t* localTime) {
+    return mTimeServer.getCommonClock().commonToLocal(commonTime, localTime);
+}
+
+status_t CommonClockService::localTimeToCommonTime(int64_t  localTime,
+                                                   int64_t* commonTime) {
+    return mTimeServer.getCommonClock().localToCommon(localTime, commonTime);
+}
+
+status_t CommonClockService::getCommonTime(int64_t* commonTime) {
+    return localTimeToCommonTime(mTimeServer.getLocalClock().getLocalTime(), commonTime);
+}
+
+status_t CommonClockService::getCommonFreq(uint64_t* freq) {
+    *freq = mTimeServer.getCommonClock().getCommonFreq();
+    return OK;
+}
+
+status_t CommonClockService::getLocalTime(int64_t* localTime) {
+    *localTime = mTimeServer.getLocalClock().getLocalTime();
+    return OK;
+}
+
+status_t CommonClockService::getLocalFreq(uint64_t* freq) {
+    *freq = mTimeServer.getLocalClock().getLocalFreq();
+    return OK;
+}
+
+status_t CommonClockService::getEstimatedError(int32_t* estimate) {
+    *estimate = mTimeServer.getEstimatedError();
+    return OK;
+}
+
+status_t CommonClockService::getTimelineID(uint64_t* id) {
+    *id = mTimeServer.getTimelineID();
+    return OK;
+}
+
+status_t CommonClockService::getState(State* state) {
+    *state = mTimeServer.getState();
+    return OK;
+}
+
+status_t CommonClockService::getMasterAddr(struct sockaddr_storage* addr) {
+    return mTimeServer.getMasterAddr(addr);
+}
+
+status_t CommonClockService::registerListener(
+        const sp<ICommonClockListener>& listener) {
+    Mutex::Autolock lock(mRegistrationLock);
+
+    {   // scoping for autolock pattern
+        Mutex::Autolock lock(mCallbackLock);
+        // check whether this is a duplicate
+        for (size_t i = 0; i < mListeners.size(); i++) {
+            if (mListeners[i]->asBinder() == listener->asBinder())
+                return ALREADY_EXISTS;
+        }
+    }
+
+    mListeners.add(listener);
+    mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+    return listener->asBinder()->linkToDeath(this);
+}
+
+status_t CommonClockService::unregisterListener(
+        const sp<ICommonClockListener>& listener) {
+    Mutex::Autolock lock(mRegistrationLock);
+    status_t ret_val = NAME_NOT_FOUND;
+
+    {   // scoping for autolock pattern
+        Mutex::Autolock lock(mCallbackLock);
+        for (size_t i = 0; i < mListeners.size(); i++) {
+            if (mListeners[i]->asBinder() == listener->asBinder()) {
+                mListeners[i]->asBinder()->unlinkToDeath(this);
+                mListeners.removeAt(i);
+                ret_val = OK;
+                break;
+            }
+        }
+    }
+
+    mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+    return ret_val;
+}
+
+void CommonClockService::binderDied(const wp<IBinder>& who) {
+    Mutex::Autolock lock(mRegistrationLock);
+
+    {   // scoping for autolock pattern
+        Mutex::Autolock lock(mCallbackLock);
+        for (size_t i = 0; i < mListeners.size(); i++) {
+            if (mListeners[i]->asBinder() == who) {
+                mListeners.removeAt(i);
+                break;
+            }
+        }
+    }
+
+    mTimeServer.reevaluateAutoDisableState(0 != mListeners.size());
+}
+
+void CommonClockService::notifyOnTimelineChanged(uint64_t timelineID) {
+    Mutex::Autolock lock(mCallbackLock);
+
+    for (size_t i = 0; i < mListeners.size(); i++) {
+        mListeners[i]->onTimelineChanged(timelineID);
+    }
+}
+
+}; // namespace android
diff --git a/services/common_time/common_clock_service.h b/services/common_time/common_clock_service.h
new file mode 100644
index 0000000..a65e398
--- /dev/null
+++ b/services/common_time/common_clock_service.h
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#include <common_time/ICommonClock.h>
+
+#ifndef ANDROID_COMMON_CLOCK_SERVICE_H
+#define ANDROID_COMMON_CLOCK_SERVICE_H
+
+namespace android {
+
+class CommonTimeServer;
+
+class CommonClockService : public BnCommonClock,
+                           public android::IBinder::DeathRecipient {
+  public:
+    static sp<CommonClockService> instantiate(CommonTimeServer& timeServer);
+
+    virtual status_t dump(int fd, const Vector<String16>& args);
+
+    virtual status_t isCommonTimeValid(bool* valid, uint32_t *timelineID);
+    virtual status_t commonTimeToLocalTime(int64_t  common_time,
+                                           int64_t* local_time);
+    virtual status_t localTimeToCommonTime(int64_t  local_time,
+                                           int64_t* common_time);
+    virtual status_t getCommonTime(int64_t* common_time);
+    virtual status_t getCommonFreq(uint64_t* freq);
+    virtual status_t getLocalTime(int64_t* local_time);
+    virtual status_t getLocalFreq(uint64_t* freq);
+    virtual status_t getEstimatedError(int32_t* estimate);
+    virtual status_t getTimelineID(uint64_t* id);
+    virtual status_t getState(ICommonClock::State* state);
+    virtual status_t getMasterAddr(struct sockaddr_storage* addr);
+
+    virtual status_t registerListener(
+            const sp<ICommonClockListener>& listener);
+    virtual status_t unregisterListener(
+            const sp<ICommonClockListener>& listener);
+
+    void notifyOnTimelineChanged(uint64_t timelineID);
+
+  private:
+    CommonClockService(CommonTimeServer& timeServer)
+        : mTimeServer(timeServer) { };
+
+    virtual void binderDied(const wp<IBinder>& who);
+
+    CommonTimeServer& mTimeServer;
+
+    // locks used to synchronize access to the list of registered listeners.
+    // The callback lock is held whenever the list is used to perform callbacks
+    // or while the list is being modified.  The registration lock used to
+    // serialize access across registerListener, unregisterListener, and
+    // binderDied.
+    //
+    // The reason for two locks is that registerListener, unregisterListener,
+    // and binderDied each call into the core service and obtain the core
+    // service thread lock when they call reevaluateAutoDisableState.  The core
+    // service thread obtains the main thread lock whenever its thread is
+    // running, and sometimes needs to call notifyOnTimelineChanged which then
+    // obtains the callback lock.  If callers of registration functions were
+    // holding the callback lock when they called into the core service, we
+    // would have a classic A/B, B/A ordering deadlock.  To avoid this, the
+    // registration functions hold the registration lock for the duration of
+    // their call, but hold the callback lock only while they mutate the list.
+    // This way, the list's size cannot change (because of the registration
+    // lock) during the call into reevaluateAutoDisableState, but the core work
+    // thread can still safely call notifyOnTimelineChanged while holding the
+    // main thread lock.
+    Mutex mCallbackLock;
+    Mutex mRegistrationLock;
+
+    Vector<sp<ICommonClockListener> > mListeners;
+};
+
+};  // namespace android
+
+#endif  // ANDROID_COMMON_CLOCK_SERVICE_H
diff --git a/services/common_time/common_time_config_service.cpp b/services/common_time/common_time_config_service.cpp
new file mode 100644
index 0000000..9585618
--- /dev/null
+++ b/services/common_time/common_time_config_service.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <utils/String8.h>
+
+#include "common_time_config_service.h"
+#include "common_time_server.h"
+
+namespace android {
+
+sp<CommonTimeConfigService> CommonTimeConfigService::instantiate(
+        CommonTimeServer& timeServer) {
+    sp<CommonTimeConfigService> ctcs = new CommonTimeConfigService(timeServer);
+    if (ctcs == NULL)
+        return NULL;
+
+    defaultServiceManager()->addService(ICommonTimeConfig::kServiceName, ctcs);
+    return ctcs;
+}
+
+status_t CommonTimeConfigService::dump(int fd, const Vector<String16>& args) {
+    return mTimeServer.dumpConfigInterface(fd, args);
+}
+
+status_t CommonTimeConfigService::getMasterElectionPriority(uint8_t *priority) {
+    return mTimeServer.getMasterElectionPriority(priority);
+}
+
+status_t CommonTimeConfigService::setMasterElectionPriority(uint8_t priority) {
+    return mTimeServer.setMasterElectionPriority(priority);
+}
+
+status_t CommonTimeConfigService::getMasterElectionEndpoint(
+        struct sockaddr_storage *addr) {
+    return mTimeServer.getMasterElectionEndpoint(addr);
+}
+
+status_t CommonTimeConfigService::setMasterElectionEndpoint(
+        const struct sockaddr_storage *addr) {
+    return mTimeServer.setMasterElectionEndpoint(addr);
+}
+
+status_t CommonTimeConfigService::getMasterElectionGroupId(uint64_t *id) {
+    return mTimeServer.getMasterElectionGroupId(id);
+}
+
+status_t CommonTimeConfigService::setMasterElectionGroupId(uint64_t id) {
+    return mTimeServer.setMasterElectionGroupId(id);
+}
+
+status_t CommonTimeConfigService::getInterfaceBinding(String16& ifaceName) {
+    String8 tmp;
+    status_t ret = mTimeServer.getInterfaceBinding(tmp);
+    ifaceName = String16(tmp);
+    return ret;
+}
+
+status_t CommonTimeConfigService::setInterfaceBinding(const String16& ifaceName) {
+    String8 tmp(ifaceName);
+    return mTimeServer.setInterfaceBinding(tmp);
+}
+
+status_t CommonTimeConfigService::getMasterAnnounceInterval(int *interval) {
+    return mTimeServer.getMasterAnnounceInterval(interval);
+}
+
+status_t CommonTimeConfigService::setMasterAnnounceInterval(int interval) {
+    return mTimeServer.setMasterAnnounceInterval(interval);
+}
+
+status_t CommonTimeConfigService::getClientSyncInterval(int *interval) {
+    return mTimeServer.getClientSyncInterval(interval);
+}
+
+status_t CommonTimeConfigService::setClientSyncInterval(int interval) {
+    return mTimeServer.setClientSyncInterval(interval);
+}
+
+status_t CommonTimeConfigService::getPanicThreshold(int *threshold) {
+    return mTimeServer.getPanicThreshold(threshold);
+}
+
+status_t CommonTimeConfigService::setPanicThreshold(int threshold) {
+    return mTimeServer.setPanicThreshold(threshold);
+}
+
+status_t CommonTimeConfigService::getAutoDisable(bool *autoDisable) {
+    return mTimeServer.getAutoDisable(autoDisable);
+}
+
+status_t CommonTimeConfigService::setAutoDisable(bool autoDisable) {
+    return mTimeServer.setAutoDisable(autoDisable);
+}
+
+status_t CommonTimeConfigService::forceNetworklessMasterMode() {
+    return mTimeServer.forceNetworklessMasterMode();
+}
+
+}; // namespace android
diff --git a/services/common_time/common_time_config_service.h b/services/common_time/common_time_config_service.h
new file mode 100644
index 0000000..8283c24
--- /dev/null
+++ b/services/common_time/common_time_config_service.h
@@ -0,0 +1,59 @@
+/* * Copyright (C) 2012 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.
+ */
+
+#include <common_time/ICommonTimeConfig.h>
+
+#ifndef ANDROID_COMMON_TIME_CONFIG_SERVICE_H
+#define ANDROID_COMMON_TIME_CONFIG_SERVICE_H
+
+namespace android {
+
+class String16;
+class CommonTimeServer;
+
+class CommonTimeConfigService : public BnCommonTimeConfig {
+  public:
+    static sp<CommonTimeConfigService> instantiate(CommonTimeServer& timeServer);
+
+    virtual status_t dump(int fd, const Vector<String16>& args);
+
+    virtual status_t getMasterElectionPriority(uint8_t *priority);
+    virtual status_t setMasterElectionPriority(uint8_t priority);
+    virtual status_t getMasterElectionEndpoint(struct sockaddr_storage *addr);
+    virtual status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr);
+    virtual status_t getMasterElectionGroupId(uint64_t *id);
+    virtual status_t setMasterElectionGroupId(uint64_t id);
+    virtual status_t getInterfaceBinding(String16& ifaceName);
+    virtual status_t setInterfaceBinding(const String16& ifaceName);
+    virtual status_t getMasterAnnounceInterval(int *interval);
+    virtual status_t setMasterAnnounceInterval(int interval);
+    virtual status_t getClientSyncInterval(int *interval);
+    virtual status_t setClientSyncInterval(int interval);
+    virtual status_t getPanicThreshold(int *threshold);
+    virtual status_t setPanicThreshold(int threshold);
+    virtual status_t getAutoDisable(bool *autoDisable);
+    virtual status_t setAutoDisable(bool autoDisable);
+    virtual status_t forceNetworklessMasterMode();
+
+  private:
+    CommonTimeConfigService(CommonTimeServer& timeServer)
+        : mTimeServer(timeServer) { }
+    CommonTimeServer& mTimeServer;
+
+};
+
+};  // namespace android
+
+#endif  // ANDROID_COMMON_TIME_CONFIG_SERVICE_H
diff --git a/services/common_time/common_time_server.cpp b/services/common_time/common_time_server.cpp
new file mode 100644
index 0000000..48fea66
--- /dev/null
+++ b/services/common_time/common_time_server.cpp
@@ -0,0 +1,1379 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <limits>
+#include <linux/if_ether.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <stdio.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <common_time/local_clock.h>
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <utils/Timers.h>
+
+#include "common_clock_service.h"
+#include "common_time_config_service.h"
+#include "common_time_server.h"
+#include "common_time_server_packets.h"
+#include "clock_recovery.h"
+#include "common_clock.h"
+
+using std::numeric_limits;
+
+namespace android {
+
+const char*    CommonTimeServer::kDefaultMasterElectionAddr = "239.195.128.88";
+const uint16_t CommonTimeServer::kDefaultMasterElectionPort = 8887;
+const uint64_t CommonTimeServer::kDefaultSyncGroupID = 0;
+const uint8_t  CommonTimeServer::kDefaultMasterPriority = 1;
+const uint32_t CommonTimeServer::kDefaultMasterAnnounceIntervalMs = 10000;
+const uint32_t CommonTimeServer::kDefaultSyncRequestIntervalMs = 1000;
+const uint32_t CommonTimeServer::kDefaultPanicThresholdUsec = 50000;
+const bool     CommonTimeServer::kDefaultAutoDisable = true;
+const int      CommonTimeServer::kSetupRetryTimeout = 30000;
+const int64_t  CommonTimeServer::kNoGoodDataPanicThreshold = 600000000ll;
+
+// timeout value representing an infinite timeout
+const int CommonTimeServer::kInfiniteTimeout = -1;
+
+/*** Initial state constants ***/
+
+// number of WhoIsMaster attempts sent before giving up
+const int CommonTimeServer::kInitial_NumWhoIsMasterRetries = 6;
+
+// timeout used when waiting for a response to a WhoIsMaster request
+const int CommonTimeServer::kInitial_WhoIsMasterTimeoutMs = 500;
+
+/*** Client state constants ***/
+
+// number of sync requests that can fail before a client assumes its master
+// is dead
+const int CommonTimeServer::kClient_NumSyncRequestRetries = 5;
+
+/*** Master state constants ***/
+
+/*** Ronin state constants ***/
+
+// number of WhoIsMaster attempts sent before declaring ourselves master
+const int CommonTimeServer::kRonin_NumWhoIsMasterRetries = 4;
+
+// timeout used when waiting for a response to a WhoIsMaster request
+const int CommonTimeServer::kRonin_WhoIsMasterTimeoutMs = 500;
+
+/*** WaitForElection state constants ***/
+
+// how long do we wait for an announcement from a master before
+// trying another election?
+const int CommonTimeServer::kWaitForElection_TimeoutMs = 5000;
+
+CommonTimeServer::CommonTimeServer()
+    : Thread(false)
+    , mState(ICommonClock::STATE_INITIAL)
+    , mClockRecovery(&mLocalClock, &mCommonClock)
+    , mSocket(-1)
+    , mLastPacketRxLocalTime(0)
+    , mTimelineID(ICommonClock::kInvalidTimelineID)
+    , mClockSynced(false)
+    , mCommonClockHasClients(false)
+    , mInitial_WhoIsMasterRequestTimeouts(0)
+    , mClient_MasterDeviceID(0)
+    , mClient_MasterDevicePriority(0)
+    , mRonin_WhoIsMasterRequestTimeouts(0) {
+    // zero out sync stats
+    resetSyncStats();
+
+    // Setup the master election endpoint to use the default.
+    struct sockaddr_in* meep =
+        reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP);
+    memset(&mMasterElectionEP, 0, sizeof(mMasterElectionEP));
+    inet_aton(kDefaultMasterElectionAddr, &meep->sin_addr);
+    meep->sin_family = AF_INET;
+    meep->sin_port   = htons(kDefaultMasterElectionPort);
+
+    // Zero out the master endpoint.
+    memset(&mMasterEP, 0, sizeof(mMasterEP));
+    mMasterEPValid    = false;
+    mBindIfaceValid   = false;
+    setForceLowPriority(false);
+
+    // Set all remaining configuration parameters to their defaults.
+    mDeviceID                 = 0;
+    mSyncGroupID              = kDefaultSyncGroupID;
+    mMasterPriority           = kDefaultMasterPriority;
+    mMasterAnnounceIntervalMs = kDefaultMasterAnnounceIntervalMs;
+    mSyncRequestIntervalMs    = kDefaultSyncRequestIntervalMs;
+    mPanicThresholdUsec       = kDefaultPanicThresholdUsec;
+    mAutoDisable              = kDefaultAutoDisable;
+
+    // Create the eventfd we will use to signal our thread to wake up when
+    // needed.
+    mWakeupThreadFD = eventfd(0, EFD_NONBLOCK);
+
+    // seed the random number generator (used to generated timeline IDs)
+    srand48(static_cast<unsigned int>(systemTime()));
+}
+
+CommonTimeServer::~CommonTimeServer() {
+    shutdownThread();
+
+    // No need to grab the lock here.  We are in the destructor; if the the user
+    // has a thread in any of the APIs while the destructor is being called,
+    // there is a threading problem a the application level we cannot reasonably
+    // do anything about.
+    cleanupSocket_l();
+
+    if (mWakeupThreadFD >= 0) {
+        close(mWakeupThreadFD);
+        mWakeupThreadFD = -1;
+    }
+}
+
+bool CommonTimeServer::startServices() {
+    // start the ICommonClock service
+    mICommonClock = CommonClockService::instantiate(*this);
+    if (mICommonClock == NULL)
+        return false;
+
+    // start the ICommonTimeConfig service
+    mICommonTimeConfig = CommonTimeConfigService::instantiate(*this);
+    if (mICommonTimeConfig == NULL)
+        return false;
+
+    return true;
+}
+
+bool CommonTimeServer::threadLoop() {
+    // Register our service interfaces.
+    if (!startServices())
+        return false;
+
+    // Hold the lock while we are in the main thread loop.  It will release the
+    // lock when it blocks, and hold the lock at all other times.
+    mLock.lock();
+    runStateMachine_l();
+    mLock.unlock();
+
+    IPCThreadState::self()->stopProcess();
+    return false;
+}
+
+bool CommonTimeServer::runStateMachine_l() {
+    if (!mLocalClock.initCheck())
+        return false;
+
+    if (!mCommonClock.init(mLocalClock.getLocalFreq()))
+        return false;
+
+    // Enter the initial state.
+    becomeInitial("startup");
+
+    // run the state machine
+    while (!exitPending()) {
+        struct pollfd pfds[2];
+        int rc;
+        int eventCnt = 0;
+        int64_t wakeupTime;
+
+        // We are always interested in our wakeup FD.
+        pfds[eventCnt].fd      = mWakeupThreadFD;
+        pfds[eventCnt].events  = POLLIN;
+        pfds[eventCnt].revents = 0;
+        eventCnt++;
+
+        // If we have a valid socket, then we are interested in what it has to
+        // say as well.
+        if (mSocket >= 0) {
+            pfds[eventCnt].fd      = mSocket;
+            pfds[eventCnt].events  = POLLIN;
+            pfds[eventCnt].revents = 0;
+            eventCnt++;
+        }
+
+        // Note, we were holding mLock when this function was called.  We
+        // release it only while we are blocking and hold it at all other times.
+        mLock.unlock();
+        rc          = poll(pfds, eventCnt, mCurTimeout.msecTillTimeout());
+        wakeupTime  = mLocalClock.getLocalTime();
+        mLock.lock();
+
+        // Is it time to shutdown?  If so, don't hesitate... just do it.
+        if (exitPending())
+            break;
+
+        // Did the poll fail?  This should never happen and is fatal if it does.
+        if (rc < 0) {
+            LOGE("%s:%d poll failed", __PRETTY_FUNCTION__, __LINE__);
+            return false;
+        }
+
+        if (rc == 0)
+            mCurTimeout.setTimeout(kInfiniteTimeout);
+
+        // Were we woken up on purpose?  If so, clear the eventfd with a read.
+        if (pfds[0].revents)
+            clearPendingWakeupEvents_l();
+
+        // Is out bind address dirty?  If so, clean up our socket (if any).
+        // Alternatively, do we have an active socket but should be auto
+        // disabled?  If so, release the socket and enter the proper sync state.
+        bool droppedSocket = false;
+        if (mBindIfaceDirty || ((mSocket >= 0) && shouldAutoDisable())) {
+            cleanupSocket_l();
+            mBindIfaceDirty = false;
+            droppedSocket = true;
+        }
+
+        // Do we not have a socket but should have one?  If so, try to set one
+        // up.
+        if ((mSocket < 0) && mBindIfaceValid && !shouldAutoDisable()) {
+            if (setupSocket_l()) {
+                // Success!  We are now joining a new network (either coming
+                // from no network, or coming from a potentially different
+                // network).  Force our priority to be lower so that we defer to
+                // any other masters which may already be on the network we are
+                // joining.  Later, when we enter either the client or the
+                // master state, we will clear this flag and go back to our
+                // normal election priority.
+                setForceLowPriority(true);
+                switch (mState) {
+                    // If we were in initial (whether we had a immediately
+                    // before this network or not) we want to simply reset the
+                    // system and start again.  Forcing a transition from
+                    // INITIAL to INITIAL should do the job.
+                    case CommonClockService::STATE_INITIAL:
+                        becomeInitial("bound interface");
+                        break;
+
+                    // If we were in the master state, then either we were the
+                    // master in a no-network situation, or we were the master
+                    // of a different network and have moved to a new interface.
+                    // In either case, immediately send out a master
+                    // announcement at low priority.
+                    case CommonClockService::STATE_MASTER:
+                        sendMasterAnnouncement();
+                        break;
+                        
+                    // If we were in any other state (CLIENT, RONIN, or
+                    // WAIT_FOR_ELECTION) then we must be moving from one
+                    // network to another.  We have lost our old master;
+                    // transition to RONIN in an attempt to find a new master.
+                    // If there are none out there, we will just assume
+                    // responsibility for the timeline we used to be a client
+                    // of.
+                    default:
+                        becomeRonin("bound interface");
+                        break;
+                }
+            } else {
+                // That's odd... we failed to set up our socket.  This could be
+                // due to some transient network change which will work itself
+                // out shortly; schedule a retry attempt in the near future.
+                mCurTimeout.setTimeout(kSetupRetryTimeout);
+            }
+
+            // One way or the other, we don't have any data to process at this
+            // point (since we just tried to bulid a new socket).  Loop back
+            // around and wait for the next thing to do.
+            continue;
+        } else if (droppedSocket) {
+            // We just lost our socket, and for whatever reason (either no
+            // config, or auto disable engaged) we are not supposed to rebuild
+            // one at this time.  We are not going to rebuild our socket until
+            // something about our config/auto-disabled status changes, so we
+            // are basically in network-less mode.  If we are already in either
+            // INITIAL or MASTER, just stay there until something changes.  If
+            // we are in any other state (CLIENT, RONIN or WAIT_FOR_ELECTION),
+            // then transition to either INITIAL or MASTER depending on whether
+            // or not our timeline is valid.
+            LOGI("Entering networkless mode interface is %s, "
+                 "shouldAutoDisable = %s",
+                 mBindIfaceValid ? "valid" : "invalid",
+                 shouldAutoDisable() ? "true" : "false");
+            if ((mState != ICommonClock::STATE_INITIAL) &&
+                (mState != ICommonClock::STATE_MASTER)) {
+                if (mTimelineID == ICommonClock::kInvalidTimelineID)
+                    becomeInitial("network-less mode");
+                else
+                    becomeMaster("network-less mode");
+            }
+
+            continue;
+        }
+
+        // Did we wakeup with no signalled events across all of our FDs?  If so,
+        // we must have hit our timeout.
+        if (rc == 0) {
+            if (!handleTimeout())
+                LOGE("handleTimeout failed");
+            continue;
+        }
+
+        // Does our socket have data for us (assuming we still have one, we
+        // may have RXed a packet at the same time as a config change telling us
+        // to shut our socket down)?  If so, process its data.
+        if ((mSocket >= 0) && (eventCnt > 1) && (pfds[1].revents)) {
+            mLastPacketRxLocalTime = wakeupTime;
+            if (!handlePacket())
+                LOGE("handlePacket failed");
+        }
+    }
+
+    cleanupSocket_l();
+    return true;
+}
+
+void CommonTimeServer::clearPendingWakeupEvents_l() {
+    int64_t tmp;
+    read(mWakeupThreadFD, &tmp, sizeof(tmp));
+}
+
+void CommonTimeServer::wakeupThread_l() {
+    int64_t tmp = 1;
+    write(mWakeupThreadFD, &tmp, sizeof(tmp));
+}
+
+void CommonTimeServer::cleanupSocket_l() {
+    if (mSocket >= 0) {
+        close(mSocket);
+        mSocket = -1;
+    }
+}
+
+void CommonTimeServer::shutdownThread() {
+    // Flag the work thread for shutdown.
+    this->requestExit();
+
+    // Signal the thread in case its sleeping.
+    mLock.lock();
+    wakeupThread_l();
+    mLock.unlock();
+
+    // Wait for the thread to exit.
+    this->join();
+}
+
+bool CommonTimeServer::setupSocket_l() {
+    int rc;
+    bool ret_val = false;
+    struct sockaddr_in* ipv4_addr = NULL;
+    char masterElectionEPStr[64];
+    const int one = 1;
+
+    // This should never be needed, but if we happened to have an old socket
+    // lying around, be sure to not leak it before proceeding.
+    cleanupSocket_l();
+
+    // If we don't have a valid endpoint to bind to, then how did we get here in
+    // the first place?  Regardless, we know that we are going to fail to bind,
+    // so don't even try.
+    if (!mBindIfaceValid)
+        return false;
+
+    sockaddrToString(mMasterElectionEP, true, masterElectionEPStr,
+                     sizeof(masterElectionEPStr));
+    LOGI("Building socket :: bind = %s master election = %s",
+         mBindIface.string(), masterElectionEPStr);
+
+    // TODO: add proper support for IPv6.  Right now, we block IPv6 addresses at
+    // the configuration interface level.
+    if (AF_INET != mMasterElectionEP.ss_family) {
+        LOGW("TODO: add proper IPv6 support");
+        goto bailout;
+    }
+
+    // open a UDP socket for the timeline serivce
+    mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (mSocket < 0) {
+        LOGE("Failed to create socket (errno = %d)", errno);
+        goto bailout;
+    }
+
+    // Bind to the selected interface using Linux's spiffy SO_BINDTODEVICE.
+    struct ifreq ifr;
+    memset(&ifr, 0, sizeof(ifr));
+    snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", mBindIface.string());
+    ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = 0;
+    rc = setsockopt(mSocket, SOL_SOCKET, SO_BINDTODEVICE,
+                    (void *)&ifr, sizeof(ifr));
+    if (rc) {
+        LOGE("Failed to bind socket at to interface %s (errno = %d)",
+              ifr.ifr_name, errno);
+        goto bailout;
+    }
+
+    // Bind our socket to INADDR_ANY and the master election port.  The
+    // interface binding we made using SO_BINDTODEVICE should limit us to
+    // traffic only on the interface we are interested in.  We need to bind to
+    // INADDR_ANY and the specific master election port in order to be able to
+    // receive both unicast traffic and master election multicast traffic with
+    // just a single socket.
+    struct sockaddr_in bindAddr;
+    ipv4_addr = reinterpret_cast<struct sockaddr_in*>(&mMasterElectionEP);
+    memcpy(&bindAddr, ipv4_addr, sizeof(bindAddr));
+    bindAddr.sin_addr.s_addr = INADDR_ANY;
+    rc = bind(mSocket,
+              reinterpret_cast<const sockaddr *>(&bindAddr),
+              sizeof(bindAddr));
+    if (rc) {
+        LOGE("Failed to bind socket to port %hu (errno = %d)",
+              ntohs(bindAddr.sin_port), errno);
+        goto bailout;
+    }
+
+    if (0xE0000000 == (ntohl(ipv4_addr->sin_addr.s_addr) & 0xF0000000)) {
+        // If our master election endpoint is a multicast address, be sure to join
+        // the multicast group.
+        struct ip_mreq mreq;
+        mreq.imr_multiaddr = ipv4_addr->sin_addr;
+        mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+        rc = setsockopt(mSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+                        &mreq, sizeof(mreq));
+        if (rc == -1) {
+            LOGE("Failed to join multicast group at %s.  (errno = %d)",
+                 masterElectionEPStr, errno);
+            goto bailout;
+        }
+
+        // disable loopback of multicast packets
+        const int zero = 0;
+        rc = setsockopt(mSocket, IPPROTO_IP, IP_MULTICAST_LOOP,
+                        &zero, sizeof(zero));
+        if (rc == -1) {
+            LOGE("Failed to disable multicast loopback (errno = %d)", errno);
+            goto bailout;
+        }
+    } else
+    if (ntohl(ipv4_addr->sin_addr.s_addr) != 0xFFFFFFFF) {
+        // If the master election address is neither broadcast, nor multicast,
+        // then we are misconfigured.  The config API layer should prevent this
+        // from ever happening.
+        goto bailout;
+    }
+
+    // Set the TTL of sent packets to 1.  (Time protocol sync should never leave
+    // the local subnet)
+    rc = setsockopt(mSocket, IPPROTO_IP, IP_TTL, &one, sizeof(one));
+    if (rc == -1) {
+        LOGE("Failed to set TTL to %d (errno = %d)", one, errno);
+        goto bailout;
+    }
+
+    // get the device's unique ID
+    if (!assignDeviceID())
+        goto bailout;
+
+    ret_val = true;
+
+bailout:
+    if (!ret_val)
+        cleanupSocket_l();
+    return ret_val;
+}
+
+// generate a unique device ID that can be used for arbitration
+bool CommonTimeServer::assignDeviceID() {
+    if (!mBindIfaceValid)
+        return false;
+
+    struct ifreq ifr;
+    memset(&ifr, 0, sizeof(ifr));
+    ifr.ifr_addr.sa_family = AF_INET;
+    strlcpy(ifr.ifr_name, mBindIface.string(), IFNAMSIZ);
+
+    int rc = ioctl(mSocket, SIOCGIFHWADDR, &ifr);
+    if (rc) {
+        LOGE("%s:%d ioctl failed", __PRETTY_FUNCTION__, __LINE__);
+        return false;
+    }
+
+    if (ifr.ifr_addr.sa_family != ARPHRD_ETHER) {
+        LOGE("%s:%d got non-Ethernet address", __PRETTY_FUNCTION__, __LINE__);
+        return false;
+    }
+
+    mDeviceID = 0;
+    for (int i = 0; i < ETH_ALEN; i++) {
+        mDeviceID = (mDeviceID << 8) | ifr.ifr_hwaddr.sa_data[i];
+    }
+
+    return true;
+}
+
+// generate a new timeline ID
+void CommonTimeServer::assignTimelineID() {
+    do {
+        mTimelineID = (static_cast<uint64_t>(lrand48()) << 32)
+                    |  static_cast<uint64_t>(lrand48());
+    } while (mTimelineID == ICommonClock::kInvalidTimelineID);
+}
+
+// Select a preference between the device IDs of two potential masters.
+// Returns true if the first ID wins, or false if the second ID wins.
+bool CommonTimeServer::arbitrateMaster(
+        uint64_t deviceID1, uint8_t devicePrio1,
+        uint64_t deviceID2, uint8_t devicePrio2) {
+    return ((devicePrio1 >  devicePrio2) ||
+           ((devicePrio1 == devicePrio2) && (deviceID1 > deviceID2)));
+}
+
+bool CommonTimeServer::handlePacket() {
+    uint8_t buf[256];
+    struct sockaddr_storage srcAddr;
+    socklen_t srcAddrLen = sizeof(srcAddr);
+
+    ssize_t recvBytes = recvfrom(
+            mSocket, buf, sizeof(buf), 0,
+            reinterpret_cast<const sockaddr *>(&srcAddr), &srcAddrLen);
+
+    if (recvBytes < 0) {
+        LOGE("%s:%d recvfrom failed", __PRETTY_FUNCTION__, __LINE__);
+        return false;
+    }
+
+    UniversalTimeServicePacket pkt;
+    recvBytes = pkt.deserializePacket(buf, recvBytes, mSyncGroupID);
+    if (recvBytes < 0)
+        return false;
+
+    bool result;
+    switch (pkt.packetType) {
+        case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+            result = handleWhoIsMasterRequest(&pkt.p.who_is_master_request,
+                                              srcAddr);
+            break;
+
+        case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+            result = handleWhoIsMasterResponse(&pkt.p.who_is_master_response,
+                                               srcAddr);
+            break;
+
+        case TIME_PACKET_SYNC_REQUEST:
+            result = handleSyncRequest(&pkt.p.sync_request, srcAddr);
+            break;
+
+        case TIME_PACKET_SYNC_RESPONSE:
+            result = handleSyncResponse(&pkt.p.sync_response, srcAddr);
+            break;
+
+        case TIME_PACKET_MASTER_ANNOUNCEMENT:
+            result = handleMasterAnnouncement(&pkt.p.master_announcement,
+                                              srcAddr);
+            break;
+
+        default: {
+            LOGD("%s:%d unknown packet type(%d)",
+                    __PRETTY_FUNCTION__, __LINE__, pkt.packetType);
+            result = false;
+        } break;
+    }
+
+    return result;
+}
+
+bool CommonTimeServer::handleTimeout() {
+    // If we have no socket, then this must be a timeout to retry socket setup.
+    if (mSocket < 0)
+        return true;
+
+    switch (mState) {
+        case ICommonClock::STATE_INITIAL:
+            return handleTimeoutInitial();
+        case ICommonClock::STATE_CLIENT:
+            return handleTimeoutClient();
+        case ICommonClock::STATE_MASTER:
+            return handleTimeoutMaster();
+        case ICommonClock::STATE_RONIN:
+            return handleTimeoutRonin();
+        case ICommonClock::STATE_WAIT_FOR_ELECTION:
+            return handleTimeoutWaitForElection();
+    }
+
+    return false;
+}
+
+bool CommonTimeServer::handleTimeoutInitial() {
+    if (++mInitial_WhoIsMasterRequestTimeouts ==
+            kInitial_NumWhoIsMasterRetries) {
+        // none of our attempts to discover a master succeeded, so make
+        // this device the master
+        return becomeMaster("initial timeout");
+    } else {
+        // retry the WhoIsMaster request
+        return sendWhoIsMasterRequest();
+    }
+}
+
+bool CommonTimeServer::handleTimeoutClient() {
+    if (shouldPanicNotGettingGoodData())
+        return becomeInitial("timeout panic, no good data");
+
+    if (mClient_SyncRequestPending) {
+        mClient_SyncRequestPending = false;
+
+        if (++mClient_SyncRequestTimeouts < kClient_NumSyncRequestRetries) {
+            // a sync request has timed out, so retry
+            return sendSyncRequest();
+        } else {
+            // The master has failed to respond to a sync request for too many
+            // times in a row.  Assume the master is dead and start electing
+            // a new master.
+            return becomeRonin("master not responding");
+        }
+    } else {
+        // initiate the next sync request
+        return sendSyncRequest();
+    }
+}
+
+bool CommonTimeServer::handleTimeoutMaster() {
+    // send another announcement from the master
+    return sendMasterAnnouncement();
+}
+
+bool CommonTimeServer::handleTimeoutRonin() {
+    if (++mRonin_WhoIsMasterRequestTimeouts == kRonin_NumWhoIsMasterRetries) {
+        // no other master is out there, so we won the election
+        return becomeMaster("no better masters detected");
+    } else {
+        return sendWhoIsMasterRequest();
+    }
+}
+
+bool CommonTimeServer::handleTimeoutWaitForElection() {
+    return becomeRonin("timeout waiting for election conclusion");
+}
+
+bool CommonTimeServer::handleWhoIsMasterRequest(
+        const WhoIsMasterRequestPacket* request,
+        const sockaddr_storage& srcAddr) {
+    if (mState == ICommonClock::STATE_MASTER) {
+        // is this request related to this master's timeline?
+        if (request->timelineID != ICommonClock::kInvalidTimelineID &&
+            request->timelineID != mTimelineID)
+            return true;
+
+        WhoIsMasterResponsePacket pkt;
+        pkt.initHeader(mTimelineID, mSyncGroupID);
+        pkt.deviceID = mDeviceID;
+        pkt.devicePriority = effectivePriority();
+
+        uint8_t buf[256];
+        ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+        if (bufSz < 0)
+            return false;
+
+        ssize_t sendBytes = sendto(
+                mSocket, buf, bufSz, 0,
+                reinterpret_cast<const sockaddr *>(&srcAddr),
+                sizeof(srcAddr));
+        if (sendBytes == -1) {
+            LOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+            return false;
+        }
+    } else if (mState == ICommonClock::STATE_RONIN) {
+        // if we hear a WhoIsMaster request from another device following
+        // the same timeline and that device wins arbitration, then we will stop
+        // trying to elect ourselves master and will instead wait for an
+        // announcement from the election winner
+        if (request->timelineID != mTimelineID)
+            return true;
+
+        if (arbitrateMaster(request->senderDeviceID,
+                            request->senderDevicePriority,
+                            mDeviceID,
+                            effectivePriority()))
+            return becomeWaitForElection("would lose election");
+
+        return true;
+    } else if (mState == ICommonClock::STATE_INITIAL) {
+        // If a group of devices booted simultaneously (e.g. after a power
+        // outage) and all of them are in the initial state and there is no
+        // master, then each device may time out and declare itself master at
+        // the same time.  To avoid this, listen for
+        // WhoIsMaster(InvalidTimeline) requests from peers.  If we would lose
+        // arbitration against that peer, reset our timeout count so that the
+        // peer has a chance to become master before we time out.
+        if (request->timelineID == ICommonClock::kInvalidTimelineID &&
+                arbitrateMaster(request->senderDeviceID, 
+                                request->senderDevicePriority,
+                                mDeviceID,
+                                effectivePriority())) {
+            mInitial_WhoIsMasterRequestTimeouts = 0;
+        }
+    }
+
+    return true;
+}
+
+bool CommonTimeServer::handleWhoIsMasterResponse(
+        const WhoIsMasterResponsePacket* response,
+        const sockaddr_storage& srcAddr) {
+    if (mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN) {
+        return becomeClient(srcAddr,
+                            response->deviceID,
+                            response->devicePriority,
+                            response->timelineID,
+                            "heard whois response");
+    } else if (mState == ICommonClock::STATE_CLIENT) {
+        // if we get multiple responses because there are multiple devices
+        // who believe that they are master, then follow the master that
+        // wins arbitration
+        if (arbitrateMaster(response->deviceID,
+                            response->devicePriority,
+                            mClient_MasterDeviceID,
+                            mClient_MasterDevicePriority)) {
+            return becomeClient(srcAddr,
+                                response->deviceID,
+                                response->devicePriority,
+                                response->timelineID,
+                                "heard whois response");
+        }
+    }
+
+    return true;
+}
+
+bool CommonTimeServer::handleSyncRequest(const SyncRequestPacket* request,
+                                         const sockaddr_storage& srcAddr) {
+    SyncResponsePacket pkt;
+    pkt.initHeader(mTimelineID, mSyncGroupID);
+
+    if ((mState == ICommonClock::STATE_MASTER) &&
+        (mTimelineID == request->timelineID)) {
+        int64_t rxLocalTime = mLastPacketRxLocalTime;
+        int64_t rxCommonTime;
+
+        // If we are master on an actual network and have actual clients, then
+        // we are no longer low priority.
+        setForceLowPriority(false);
+
+        if (OK != mCommonClock.localToCommon(rxLocalTime, &rxCommonTime)) {
+            return false;
+        }
+
+        int64_t txLocalTime = mLocalClock.getLocalTime();;
+        int64_t txCommonTime;
+        if (OK != mCommonClock.localToCommon(txLocalTime, &txCommonTime)) {
+            return false;
+        }
+
+        pkt.nak = 0;
+        pkt.clientTxLocalTime  = request->clientTxLocalTime;
+        pkt.masterRxCommonTime = rxCommonTime;
+        pkt.masterTxCommonTime = txCommonTime;
+    } else {
+        pkt.nak = 1;
+        pkt.clientTxLocalTime  = 0;
+        pkt.masterRxCommonTime = 0;
+        pkt.masterTxCommonTime = 0;
+    }
+
+    uint8_t buf[256];
+    ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+    if (bufSz < 0)
+        return false;
+
+    ssize_t sendBytes = sendto(
+            mSocket, &buf, bufSz, 0, 
+            reinterpret_cast<const sockaddr *>(&srcAddr),
+            sizeof(srcAddr));
+    if (sendBytes == -1) {
+        LOGE("%s:%d sendto failed", __PRETTY_FUNCTION__, __LINE__);
+        return false;
+    }
+
+    return true;
+}
+
+bool CommonTimeServer::handleSyncResponse(
+        const SyncResponsePacket* response,
+        const sockaddr_storage& srcAddr) {
+    if (mState != ICommonClock::STATE_CLIENT)
+        return true;
+
+    assert(mMasterEPValid);
+    if (!sockaddrMatch(srcAddr, mMasterEP, true)) {
+        char srcEP[64], expectedEP[64];
+        sockaddrToString(srcAddr, true, srcEP, sizeof(srcEP));
+        sockaddrToString(mMasterEP, true, expectedEP, sizeof(expectedEP));
+        LOGI("Dropping sync response from unexpected address."
+             " Expected %s Got %s", expectedEP, srcEP);
+        return true;
+    }
+
+    if (response->nak) {
+        // if our master is no longer accepting requests, then we need to find
+        // a new master
+        return becomeRonin("master NAK'ed");
+    }
+
+    mClient_SyncRequestPending = 0;
+    mClient_SyncRequestTimeouts = 0;
+    mClient_PacketRTTLog.logRX(response->clientTxLocalTime,
+                               mLastPacketRxLocalTime);
+
+    bool result;
+    if (!(mClient_SyncRespsRXedFromCurMaster++)) {
+        // the first request/response exchange between a client and a master
+        // may take unusually long due to ARP, so discard it.
+        result = true;
+    } else {
+        int64_t clientTxLocalTime  = response->clientTxLocalTime;
+        int64_t clientRxLocalTime  = mLastPacketRxLocalTime;
+        int64_t masterTxCommonTime = response->masterTxCommonTime;
+        int64_t masterRxCommonTime = response->masterRxCommonTime;
+
+        int64_t rtt       = (clientRxLocalTime - clientTxLocalTime);
+        int64_t avgLocal  = (clientTxLocalTime + clientRxLocalTime) >> 1;
+        int64_t avgCommon = (masterTxCommonTime + masterRxCommonTime) >> 1;
+
+        // if the RTT of the packet is significantly larger than the panic
+        // threshold, we should simply discard it.  Its better to do nothing
+        // than to take cues from a packet like that.
+        int rttCommon = mCommonClock.localDurationToCommonDuration(rtt);
+        if (rttCommon > (static_cast<int64_t>(mPanicThresholdUsec) * 5)) {
+            LOGV("Dropping sync response with RTT of %lld uSec", rttCommon);
+            mClient_ExpiredSyncRespsRXedFromCurMaster++;
+            if (shouldPanicNotGettingGoodData())
+                return becomeInitial("RX panic, no good data");
+        } else {
+            result = mClockRecovery.pushDisciplineEvent(avgLocal, avgCommon, rttCommon);
+            mClient_LastGoodSyncRX = clientRxLocalTime;
+
+            if (result) {
+                // indicate to listeners that we've synced to the common timeline
+                notifyClockSync();
+            } else {
+                LOGE("Panic!  Observed clock sync error is too high to tolerate,"
+                        " resetting state machine and starting over.");
+                notifyClockSyncLoss();
+                return becomeInitial("panic");
+            }
+        }
+    }
+
+    mCurTimeout.setTimeout(mSyncRequestIntervalMs);
+    return result;
+}
+
+bool CommonTimeServer::handleMasterAnnouncement(
+        const MasterAnnouncementPacket* packet,
+        const sockaddr_storage& srcAddr) {
+    uint64_t newDeviceID   = packet->deviceID;
+    uint8_t  newDevicePrio = packet->devicePriority;
+    uint64_t newTimelineID = packet->timelineID;
+
+    if (mState == ICommonClock::STATE_INITIAL ||
+        mState == ICommonClock::STATE_RONIN ||
+        mState == ICommonClock::STATE_WAIT_FOR_ELECTION) {
+        // if we aren't currently following a master, then start following
+        // this new master
+        return becomeClient(srcAddr,
+                            newDeviceID,
+                            newDevicePrio,
+                            newTimelineID,
+                            "heard master announcement");
+    } else if (mState == ICommonClock::STATE_CLIENT) {
+        // if the new master wins arbitration against our current master,
+        // then become a client of the new master
+        if (arbitrateMaster(newDeviceID,
+                            newDevicePrio,
+                            mClient_MasterDeviceID,
+                            mClient_MasterDevicePriority))
+            return becomeClient(srcAddr,
+                                newDeviceID,
+                                newDevicePrio,
+                                newTimelineID,
+                                "heard master announcement");
+    } else if (mState == ICommonClock::STATE_MASTER) {
+        // two masters are competing - if the new one wins arbitration, then
+        // cease acting as master
+        if (arbitrateMaster(newDeviceID, newDevicePrio,
+                            mDeviceID, effectivePriority()))
+            return becomeClient(srcAddr, newDeviceID,
+                                newDevicePrio, newTimelineID,
+                                "heard master announcement");
+    }
+
+    return true;
+}
+
+bool CommonTimeServer::sendWhoIsMasterRequest() {
+    assert(mState == ICommonClock::STATE_INITIAL || mState == ICommonClock::STATE_RONIN);
+
+    // If we have no socket, then we must be in the unconfigured initial state.
+    // Don't report any errors, just don't try to send the initial who-is-master
+    // query.  Eventually, our network will either become configured, or we will
+    // be forced into network-less master mode by higher level code.
+    if (mSocket < 0) {
+        assert(mState == ICommonClock::STATE_INITIAL);
+        return true;
+    }
+
+    bool ret = false;
+    WhoIsMasterRequestPacket pkt;
+    pkt.initHeader(mSyncGroupID);
+    pkt.senderDeviceID = mDeviceID;
+    pkt.senderDevicePriority = effectivePriority();
+
+    uint8_t buf[256];
+    ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+    if (bufSz >= 0) {
+        ssize_t sendBytes = sendto(
+                mSocket, buf, bufSz, 0,
+                reinterpret_cast<const sockaddr *>(&mMasterElectionEP),
+                sizeof(mMasterElectionEP));
+        if (sendBytes < 0)
+            LOGE("WhoIsMaster sendto failed (errno %d)", errno);
+        ret = true;
+    }
+
+    if (mState == ICommonClock::STATE_INITIAL) {
+        mCurTimeout.setTimeout(kInitial_WhoIsMasterTimeoutMs);
+    } else {
+        mCurTimeout.setTimeout(kRonin_WhoIsMasterTimeoutMs);
+    }
+
+    return ret;
+}
+
+bool CommonTimeServer::sendSyncRequest() {
+    // If we are sending sync requests, then we must be in the client state and
+    // we must have a socket (when we have no network, we are only supposed to
+    // be in INITIAL or MASTER)
+    assert(mState == ICommonClock::STATE_CLIENT);
+    assert(mSocket >= 0);
+
+    bool ret = false;
+    SyncRequestPacket pkt;
+    pkt.initHeader(mTimelineID, mSyncGroupID);
+    pkt.clientTxLocalTime = mLocalClock.getLocalTime();
+
+    if (!mClient_FirstSyncTX)
+        mClient_FirstSyncTX = pkt.clientTxLocalTime;
+
+    mClient_PacketRTTLog.logTX(pkt.clientTxLocalTime);
+
+    uint8_t buf[256];
+    ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+    if (bufSz >= 0) {
+        ssize_t sendBytes = sendto(
+                mSocket, buf, bufSz, 0,
+                reinterpret_cast<const sockaddr *>(&mMasterEP),
+                sizeof(mMasterEP));
+        if (sendBytes < 0)
+            LOGE("SyncRequest sendto failed (errno %d)", errno);
+        ret = true;
+    }
+
+    mClient_SyncsSentToCurMaster++;
+    mCurTimeout.setTimeout(mSyncRequestIntervalMs);
+    mClient_SyncRequestPending = true;
+
+    return ret;
+}
+
+bool CommonTimeServer::sendMasterAnnouncement() {
+    bool ret = false;
+    assert(mState == ICommonClock::STATE_MASTER);
+
+    // If we are being asked to send a master announcement, but we have no
+    // socket, we must be in network-less master mode.  Don't bother to send the
+    // announcement, and don't bother to schedule a timeout.  When the network
+    // comes up, the work thread will get poked and start the process of
+    // figuring out who the current master should be.
+    if (mSocket < 0) {
+        mCurTimeout.setTimeout(kInfiniteTimeout);
+        return true;
+    }
+
+    MasterAnnouncementPacket pkt;
+    pkt.initHeader(mTimelineID, mSyncGroupID);
+    pkt.deviceID = mDeviceID;
+    pkt.devicePriority = effectivePriority();
+
+    uint8_t buf[256];
+    ssize_t bufSz = pkt.serializePacket(buf, sizeof(buf));
+    if (bufSz >= 0) {
+        ssize_t sendBytes = sendto(
+                mSocket, buf, bufSz, 0,
+                reinterpret_cast<const sockaddr *>(&mMasterElectionEP),
+                sizeof(mMasterElectionEP));
+        if (sendBytes < 0)
+            LOGE("MasterAnnouncement sendto failed (errno %d)", errno);
+        ret = true;
+    }
+
+    mCurTimeout.setTimeout(mMasterAnnounceIntervalMs);
+    return ret;
+}
+
+bool CommonTimeServer::becomeClient(const sockaddr_storage& masterEP,
+                                    uint64_t masterDeviceID,
+                                    uint8_t  masterDevicePriority,
+                                    uint64_t timelineID,
+                                    const char* cause) {
+    char newEPStr[64], oldEPStr[64];
+    sockaddrToString(masterEP, true, newEPStr, sizeof(newEPStr));
+    sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr));
+
+    LOGI("%s --> CLIENT (%s) :%s"
+         " OldMaster: %02x-%014llx::%016llx::%s"
+         " NewMaster: %02x-%014llx::%016llx::%s",
+         stateToString(mState), cause,
+         (mTimelineID != timelineID) ? " (new timeline)" : "",
+         mClient_MasterDevicePriority, mClient_MasterDeviceID,
+         mTimelineID, oldEPStr,
+         masterDevicePriority, masterDeviceID,
+         timelineID, newEPStr);
+
+    if (mTimelineID != timelineID) {
+        // start following a new timeline
+        mTimelineID = timelineID;
+        mClockRecovery.reset(true, true);
+        notifyClockSyncLoss();
+    } else {
+        // start following a new master on the existing timeline
+        mClockRecovery.reset(false, true);
+    }
+
+    mMasterEP = masterEP;
+    mMasterEPValid = true;
+    setForceLowPriority(false);
+
+    mClient_MasterDeviceID = masterDeviceID;
+    mClient_MasterDevicePriority = masterDevicePriority;
+    resetSyncStats();
+
+    setState(ICommonClock::STATE_CLIENT);
+
+    // add some jitter to when the various clients send their requests
+    // in order to reduce the likelihood that a group of clients overload
+    // the master after receiving a master announcement
+    usleep((lrand48() % 100) * 1000);
+
+    return sendSyncRequest();
+}
+
+bool CommonTimeServer::becomeMaster(const char* cause) {
+    uint64_t oldTimelineID = mTimelineID;
+    if (mTimelineID == ICommonClock::kInvalidTimelineID) {
+        // this device has not been following any existing timeline,
+        // so it will create a new timeline and declare itself master
+        assert(!mCommonClock.isValid());
+
+        // set the common time basis
+        mCommonClock.setBasis(mLocalClock.getLocalTime(), 0);
+
+        // assign an arbitrary timeline iD
+        assignTimelineID();
+
+        // notify listeners that we've created a common timeline
+        notifyClockSync();
+    }
+
+    LOGI("%s --> MASTER (%s) : %s timeline %016llx",
+         stateToString(mState), cause,
+         (oldTimelineID == mTimelineID) ? "taking ownership of"
+                                        : "creating new",
+         mTimelineID);
+
+    memset(&mMasterEP, 0, sizeof(mMasterEP));
+    mMasterEPValid = false;
+    setForceLowPriority(false);
+    mClient_MasterDevicePriority = effectivePriority();
+    mClient_MasterDeviceID = mDeviceID;
+    mClockRecovery.reset(false, true);
+    resetSyncStats();
+
+    setState(ICommonClock::STATE_MASTER);
+    return sendMasterAnnouncement();
+}
+
+bool CommonTimeServer::becomeRonin(const char* cause) {
+    // If we were the client of a given timeline, but had never received even a
+    // single time sync packet, then we transition back to Initial instead of
+    // Ronin.  If we transition to Ronin and end up becoming the new Master, we
+    // will be unable to service requests for other clients because we never
+    // actually knew what time it was.  By going to initial, we ensure that
+    // other clients who know what time it is, but would lose master arbitration
+    // in the Ronin case, will step up and become the proper new master of the
+    // old timeline.
+
+    char oldEPStr[64];
+    sockaddrToString(mMasterEP, mMasterEPValid, oldEPStr, sizeof(oldEPStr));
+    memset(&mMasterEP, 0, sizeof(mMasterEP));
+    mMasterEPValid = false;
+
+    if (mCommonClock.isValid()) {
+        LOGI("%s --> RONIN (%s) : lost track of previously valid timeline "
+             "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)",
+             stateToString(mState), cause,
+             mClient_MasterDevicePriority, mClient_MasterDeviceID,
+             mTimelineID, oldEPStr,
+             mClient_SyncsSentToCurMaster,
+             mClient_SyncRespsRXedFromCurMaster,
+             mClient_ExpiredSyncRespsRXedFromCurMaster);
+
+        mRonin_WhoIsMasterRequestTimeouts = 0;
+        setState(ICommonClock::STATE_RONIN);
+        return sendWhoIsMasterRequest();
+    } else {
+        LOGI("%s --> INITIAL (%s) : never synced timeline "
+             "%02x-%014llx::%016llx::%s (%d TXed %d RXed %d RXExpired)",
+             stateToString(mState), cause,
+             mClient_MasterDevicePriority, mClient_MasterDeviceID,
+             mTimelineID, oldEPStr,
+             mClient_SyncsSentToCurMaster,
+             mClient_SyncRespsRXedFromCurMaster,
+             mClient_ExpiredSyncRespsRXedFromCurMaster);
+
+        return becomeInitial("ronin, no timeline");
+    }
+}
+
+bool CommonTimeServer::becomeWaitForElection(const char* cause) {
+    LOGI("%s --> WAIT_FOR_ELECTION (%s) : dropping out of election,"
+         " waiting %d mSec for completion.",
+         stateToString(mState), cause, kWaitForElection_TimeoutMs);
+
+    setState(ICommonClock::STATE_WAIT_FOR_ELECTION);
+    mCurTimeout.setTimeout(kWaitForElection_TimeoutMs);
+    return true;
+}
+
+bool CommonTimeServer::becomeInitial(const char* cause) {
+    LOGI("Entering INITIAL (%s), total reset.", cause);
+
+    setState(ICommonClock::STATE_INITIAL);
+
+    // reset clock recovery
+    mClockRecovery.reset(true, true);
+
+    // reset internal state bookkeeping.
+    mCurTimeout.setTimeout(kInfiniteTimeout);
+    memset(&mMasterEP, 0, sizeof(mMasterEP));
+    mMasterEPValid = false;
+    mLastPacketRxLocalTime = 0;
+    mTimelineID = ICommonClock::kInvalidTimelineID;
+    mClockSynced = false;
+    mInitial_WhoIsMasterRequestTimeouts = 0;
+    mClient_MasterDeviceID = 0;
+    mClient_MasterDevicePriority = 0;
+    mRonin_WhoIsMasterRequestTimeouts = 0;
+    resetSyncStats();
+
+    // send the first request to discover the master
+    return sendWhoIsMasterRequest();
+}
+
+void CommonTimeServer::notifyClockSync() {
+    if (!mClockSynced) {
+        mClockSynced = true;
+        mICommonClock->notifyOnTimelineChanged(mTimelineID);
+    }
+}
+
+void CommonTimeServer::notifyClockSyncLoss() {
+    if (mClockSynced) {
+        mClockSynced = false;
+        mICommonClock->notifyOnTimelineChanged(
+                ICommonClock::kInvalidTimelineID);
+    }
+}
+
+void CommonTimeServer::setState(ICommonClock::State s) {
+    mState = s;
+}
+
+const char* CommonTimeServer::stateToString(ICommonClock::State s) {
+    switch(s) {
+        case ICommonClock::STATE_INITIAL:
+            return "INITIAL";
+        case ICommonClock::STATE_CLIENT:
+            return "CLIENT";
+        case ICommonClock::STATE_MASTER:
+            return "MASTER";
+        case ICommonClock::STATE_RONIN:
+            return "RONIN";
+        case ICommonClock::STATE_WAIT_FOR_ELECTION:
+            return "WAIT_FOR_ELECTION";
+        default:
+            return "unknown";
+    }
+}
+
+void CommonTimeServer::sockaddrToString(const sockaddr_storage& addr,
+                                        bool addrValid,
+                                        char* buf, size_t bufLen) {
+    if (!bufLen || !buf)
+        return;
+
+    if (addrValid) {
+        switch (addr.ss_family) {
+            case AF_INET: {
+                const struct sockaddr_in* sa =
+                    reinterpret_cast<const struct sockaddr_in*>(&addr);
+                unsigned long a = ntohl(sa->sin_addr.s_addr);
+                uint16_t      p = ntohs(sa->sin_port);
+                snprintf(buf, bufLen, "%lu.%lu.%lu.%lu:%hu",
+                        ((a >> 24) & 0xFF), ((a >> 16) & 0xFF),
+                        ((a >>  8) & 0xFF),  (a        & 0xFF), p);
+            } break;
+
+            case AF_INET6: {
+                const struct sockaddr_in6* sa =
+                    reinterpret_cast<const struct sockaddr_in6*>(&addr);
+                const uint8_t* a = sa->sin6_addr.s6_addr;
+                uint16_t       p = ntohs(sa->sin6_port);
+                snprintf(buf, bufLen,
+                        "%02X%02X:%02X%02X:%02X%02X:%02X%02X:"
+                        "%02X%02X:%02X%02X:%02X%02X:%02X%02X port %hd",
+                        a[0], a[1], a[ 2], a[ 3], a[ 4], a[ 5], a[ 6], a[ 7],
+                        a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15],
+                        p);
+            } break;
+
+            default:
+                snprintf(buf, bufLen,
+                         "<unknown sockaddr family %d>", addr.ss_family);
+                break;
+        }
+    } else {
+        snprintf(buf, bufLen, "<none>");
+    }
+
+    buf[bufLen - 1] = 0;
+}
+
+bool CommonTimeServer::sockaddrMatch(const sockaddr_storage& a1,
+                                     const sockaddr_storage& a2,
+                                     bool matchAddressOnly) {
+    if (a1.ss_family != a2.ss_family)
+        return false;
+
+    switch (a1.ss_family) {
+        case AF_INET: {
+            const struct sockaddr_in* sa1 =
+                reinterpret_cast<const struct sockaddr_in*>(&a1);
+            const struct sockaddr_in* sa2 =
+                reinterpret_cast<const struct sockaddr_in*>(&a2);
+
+            if (sa1->sin_addr.s_addr != sa2->sin_addr.s_addr)
+                return false;
+
+            return (matchAddressOnly || (sa1->sin_port == sa2->sin_port));
+        } break;
+
+        case AF_INET6: {
+            const struct sockaddr_in6* sa1 =
+                reinterpret_cast<const struct sockaddr_in6*>(&a1);
+            const struct sockaddr_in6* sa2 =
+                reinterpret_cast<const struct sockaddr_in6*>(&a2);
+
+            if (memcmp(&sa1->sin6_addr, &sa2->sin6_addr, sizeof(sa2->sin6_addr)))
+                return false;
+
+            return (matchAddressOnly || (sa1->sin6_port == sa2->sin6_port));
+        } break;
+
+        // Huh?  We don't deal in non-IPv[46] addresses.  Not sure how we got
+        // here, but we don't know how to comapre these addresses and simply
+        // default to a no-match decision.
+        default: return false;
+    }
+}
+
+void CommonTimeServer::TimeoutHelper::setTimeout(int msec) {
+    mTimeoutValid = (msec >= 0);
+    if (mTimeoutValid)
+        mEndTime = systemTime() +
+                   (static_cast<nsecs_t>(msec) * 1000000);
+}
+
+int CommonTimeServer::TimeoutHelper::msecTillTimeout() {
+    if (!mTimeoutValid)
+        return kInfiniteTimeout;
+
+    nsecs_t now = systemTime();
+    if (now >= mEndTime)
+        return 0;
+
+    uint64_t deltaMsec = (((mEndTime - now) + 999999) / 1000000);
+
+    if (deltaMsec > static_cast<uint64_t>(std::numeric_limits<int>::max()))
+        return std::numeric_limits<int>::max();
+
+    return static_cast<int>(deltaMsec);
+}
+
+bool CommonTimeServer::shouldPanicNotGettingGoodData() {
+    if (mClient_FirstSyncTX) {
+        int64_t now = mLocalClock.getLocalTime();
+        int64_t delta = now - (mClient_LastGoodSyncRX
+                             ? mClient_LastGoodSyncRX
+                             : mClient_FirstSyncTX);
+        int64_t deltaUsec = mCommonClock.localDurationToCommonDuration(delta);
+
+        if (deltaUsec >= kNoGoodDataPanicThreshold)
+            return true;
+    }
+
+    return false;
+}
+
+void CommonTimeServer::PacketRTTLog::logTX(int64_t txTime) {
+    txTimes[wrPtr] = txTime;
+    rxTimes[wrPtr] = 0;
+    wrPtr = (wrPtr + 1) % RTT_LOG_SIZE;
+    if (!wrPtr)
+        logFull = true;
+}
+
+void CommonTimeServer::PacketRTTLog::logRX(int64_t txTime, int64_t rxTime) {
+    if (!logFull && !wrPtr)
+        return;
+
+    uint32_t i = logFull ? wrPtr : 0;
+    do {
+        if (txTimes[i] == txTime) {
+            rxTimes[i] = rxTime;
+            break;
+        }
+        i = (i + 1) % RTT_LOG_SIZE;
+    } while (i != wrPtr);
+}
+
+}  // namespace android
diff --git a/services/common_time/common_time_server.h b/services/common_time/common_time_server.h
new file mode 100644
index 0000000..2aa2b4c
--- /dev/null
+++ b/services/common_time/common_time_server.h
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef ANDROID_COMMON_TIME_SERVER_H
+#define ANDROID_COMMON_TIME_SERVER_H
+
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <linux/socket.h>
+
+#include <common_time/ICommonClock.h>
+#include <common_time/local_clock.h>
+#include <utils/String8.h>
+
+#include "clock_recovery.h"
+#include "common_clock.h"
+#include "common_time_server_packets.h"
+
+#define RTT_LOG_SIZE 30
+
+namespace android {
+
+class CommonClockService;
+class CommonTimeConfigService;
+
+/***** time service implementation *****/
+
+class CommonTimeServer : public Thread {
+  public:
+    CommonTimeServer();
+    ~CommonTimeServer();
+
+    bool startServices();
+
+    // Common Clock API methods
+    CommonClock&        getCommonClock()        { return mCommonClock; }
+    LocalClock&         getLocalClock()         { return mLocalClock; }
+    uint64_t            getTimelineID();
+    int32_t             getEstimatedError();
+    ICommonClock::State getState();
+    status_t            getMasterAddr(struct sockaddr_storage* addr);
+    status_t            isCommonTimeValid(bool* valid, uint32_t* timelineID);
+
+    // Config API methods
+    status_t getMasterElectionPriority(uint8_t *priority);
+    status_t setMasterElectionPriority(uint8_t priority);
+    status_t getMasterElectionEndpoint(struct sockaddr_storage *addr);
+    status_t setMasterElectionEndpoint(const struct sockaddr_storage *addr);
+    status_t getMasterElectionGroupId(uint64_t *id);
+    status_t setMasterElectionGroupId(uint64_t id);
+    status_t getInterfaceBinding(String8& ifaceName);
+    status_t setInterfaceBinding(const String8& ifaceName);
+    status_t getMasterAnnounceInterval(int *interval);
+    status_t setMasterAnnounceInterval(int interval);
+    status_t getClientSyncInterval(int *interval);
+    status_t setClientSyncInterval(int interval);
+    status_t getPanicThreshold(int *threshold);
+    status_t setPanicThreshold(int threshold);
+    status_t getAutoDisable(bool *autoDisable);
+    status_t setAutoDisable(bool autoDisable);
+    status_t forceNetworklessMasterMode();
+
+    // Method used by the CommonClockService to notify the core service about
+    // changes in the number of active common clock clients.
+    void reevaluateAutoDisableState(bool commonClockHasClients);
+
+    status_t dumpClockInterface(int fd, const Vector<String16>& args,
+                                size_t activeClients);
+    status_t dumpConfigInterface(int fd, const Vector<String16>& args);
+
+  private:
+    class PacketRTTLog {
+      public:
+        PacketRTTLog() {
+            resetLog();
+        }
+
+        void resetLog() {
+            wrPtr = 0;
+            logFull = 0;
+        }
+
+        void logTX(int64_t txTime);
+        void logRX(int64_t txTime, int64_t rxTime);
+        void dumpLog(int fd, const CommonClock& cclk);
+
+      private:
+        uint32_t wrPtr;
+        bool logFull;
+        int64_t txTimes[RTT_LOG_SIZE];
+        int64_t rxTimes[RTT_LOG_SIZE];
+    };
+
+    class TimeoutHelper {
+      public:
+        TimeoutHelper() : mTimeoutValid(false) { }
+
+        void setTimeout(int msec);
+        int msecTillTimeout();
+
+      private:
+        bool        mTimeoutValid;
+        nsecs_t     mEndTime;
+    };
+
+    bool threadLoop();
+
+    bool runStateMachine_l();
+    bool setupSocket_l();
+
+    void assignTimelineID();
+    bool assignDeviceID();
+
+    static bool arbitrateMaster(uint64_t deviceID1, uint8_t devicePrio1,
+                                uint64_t deviceID2, uint8_t devicePrio2);
+
+    bool handlePacket();
+    bool handleWhoIsMasterRequest (const WhoIsMasterRequestPacket* request,
+                                   const sockaddr_storage& srcAddr);
+    bool handleWhoIsMasterResponse(const WhoIsMasterResponsePacket* response,
+                                   const sockaddr_storage& srcAddr);
+    bool handleSyncRequest        (const SyncRequestPacket* request,
+                                   const sockaddr_storage& srcAddr);
+    bool handleSyncResponse       (const SyncResponsePacket* response,
+                                   const sockaddr_storage& srcAddr);
+    bool handleMasterAnnouncement (const MasterAnnouncementPacket* packet,
+                                   const sockaddr_storage& srcAddr);
+
+    bool handleTimeout();
+    bool handleTimeoutInitial();
+    bool handleTimeoutClient();
+    bool handleTimeoutMaster();
+    bool handleTimeoutRonin();
+    bool handleTimeoutWaitForElection();
+
+    bool sendWhoIsMasterRequest();
+    bool sendSyncRequest();
+    bool sendMasterAnnouncement();
+
+    bool becomeClient(const sockaddr_storage& masterAddr,
+                      uint64_t masterDeviceID,
+                      uint8_t  masterDevicePriority,
+                      uint64_t timelineID,
+                      const char* cause);
+    bool becomeMaster(const char* cause);
+    bool becomeRonin(const char* cause);
+    bool becomeWaitForElection(const char* cause);
+    bool becomeInitial(const char* cause);
+
+    void notifyClockSync();
+    void notifyClockSyncLoss();
+
+    ICommonClock::State mState;
+    void setState(ICommonClock::State s);
+
+    void clearPendingWakeupEvents_l();
+    void wakeupThread_l();
+    void cleanupSocket_l();
+    void shutdownThread();
+
+    inline uint8_t effectivePriority() const {
+        return (mMasterPriority & 0x7F) |
+               (mForceLowPriority ? 0x00 : 0x80);
+    }
+
+    inline bool shouldAutoDisable() const {
+        return (mAutoDisable && !mCommonClockHasClients);
+    }
+
+    inline void resetSyncStats() {
+        mClient_SyncRequestPending = false;
+        mClient_SyncRequestTimeouts = 0;
+        mClient_SyncsSentToCurMaster = 0;
+        mClient_SyncRespsRXedFromCurMaster = 0;
+        mClient_ExpiredSyncRespsRXedFromCurMaster = 0;
+        mClient_FirstSyncTX = 0;
+        mClient_LastGoodSyncRX = 0;
+        mClient_PacketRTTLog.resetLog();
+    }
+    
+    bool shouldPanicNotGettingGoodData();
+
+    // Helper to keep track of the state machine's current timeout
+    TimeoutHelper mCurTimeout;
+
+    // common clock, local clock abstraction, and clock recovery loop
+    CommonClock mCommonClock;
+    LocalClock mLocalClock;
+    ClockRecoveryLoop mClockRecovery;
+
+    // implementation of ICommonClock
+    sp<CommonClockService> mICommonClock;
+
+    // implementation of ICommonTimeConfig
+    sp<CommonTimeConfigService> mICommonTimeConfig;
+
+    // UDP socket for the time sync protocol
+    int mSocket;
+
+    // eventfd used to wakeup the work thread in response to configuration
+    // changes.
+    int mWakeupThreadFD;
+
+    // timestamp captured when a packet is received
+    int64_t mLastPacketRxLocalTime;
+
+    // ID of the timeline that this device is following
+    uint64_t mTimelineID;
+
+    // flag for whether the clock has been synced to a timeline
+    bool mClockSynced;
+
+    // flag used to indicate that clients should be considered to be lower
+    // priority than all of their peers during elections.  This flag is set and
+    // cleared by the state machine.  It is set when the client joins a new
+    // network.  If the client had been a master in the old network (or an
+    // isolated master with no network connectivity) it should defer to any
+    // masters which may already be on the network.  It will be cleared whenever
+    // the state machine transitions to the master state.
+    bool mForceLowPriority;
+    inline void setForceLowPriority(bool val) {
+        mForceLowPriority = val;
+        if (mState == ICommonClock::STATE_MASTER)
+            mClient_MasterDevicePriority = effectivePriority();
+    }
+
+    // Lock to synchronize access to internal state and configuration.
+    Mutex mLock;
+
+    // Flag updated by the common clock service to indicate that it does or does
+    // not currently have registered clients.  When the the auto disable flag is
+    // cleared on the common time service, the service will participate in
+    // network synchronization whenever it has a valid network interface to bind
+    // to.  When the auto disable flag is set on the common time service, it
+    // will only participate in network synchronization when it has both a valid
+    // interface AND currently active common clock clients.
+    bool mCommonClockHasClients;
+
+    // Configuration info
+    struct sockaddr_storage mMasterElectionEP;          // Endpoint over which we conduct master election
+    String8                 mBindIface;                 // Endpoint for the service to bind to.
+    bool                    mBindIfaceValid;            // whether or not the bind Iface is valid.
+    bool                    mBindIfaceDirty;            // whether or not the bind Iface is valid.
+    struct sockaddr_storage mMasterEP;                  // Endpoint of our current master (if any)
+    bool                    mMasterEPValid;
+    uint64_t                mDeviceID;                  // unique ID of this device
+    uint64_t                mSyncGroupID;               // synchronization group ID of this device.
+    uint8_t                 mMasterPriority;            // Priority of this device in master election.
+    uint32_t                mMasterAnnounceIntervalMs;
+    uint32_t                mSyncRequestIntervalMs;
+    uint32_t                mPanicThresholdUsec;
+    bool                    mAutoDisable;
+
+    // Config defaults.
+    static const char*      kDefaultMasterElectionAddr;
+    static const uint16_t   kDefaultMasterElectionPort;
+    static const uint64_t   kDefaultSyncGroupID;
+    static const uint8_t    kDefaultMasterPriority;
+    static const uint32_t   kDefaultMasterAnnounceIntervalMs;
+    static const uint32_t   kDefaultSyncRequestIntervalMs;
+    static const uint32_t   kDefaultPanicThresholdUsec;
+    static const bool       kDefaultAutoDisable;
+
+    // Priority mask and shift fields.
+    static const uint64_t kDeviceIDMask;
+    static const uint8_t  kDevicePriorityMask;
+    static const uint8_t  kDevicePriorityHiLowBit;
+    static const uint32_t kDevicePriorityShift;
+
+    // Unconfgurable constants
+    static const int      kSetupRetryTimeout;
+    static const int64_t  kNoGoodDataPanicThreshold;
+
+    /*** status while in the Initial state ***/
+    int mInitial_WhoIsMasterRequestTimeouts;
+    static const int kInitial_NumWhoIsMasterRetries;
+    static const int kInitial_WhoIsMasterTimeoutMs;
+
+    /*** status while in the Client state ***/
+    uint64_t mClient_MasterDeviceID;
+    uint8_t mClient_MasterDevicePriority;
+    bool mClient_SyncRequestPending;
+    int mClient_SyncRequestTimeouts;
+    uint32_t mClient_SyncsSentToCurMaster;
+    uint32_t mClient_SyncRespsRXedFromCurMaster;
+    uint32_t mClient_ExpiredSyncRespsRXedFromCurMaster;
+    int64_t mClient_FirstSyncTX;
+    int64_t mClient_LastGoodSyncRX;
+    PacketRTTLog mClient_PacketRTTLog;
+    static const int kClient_NumSyncRequestRetries;
+
+
+    /*** status while in the Master state ***/
+    static const uint32_t kDefaultMaster_AnnouncementIntervalMs;
+
+    /*** status while in the Ronin state ***/
+    int mRonin_WhoIsMasterRequestTimeouts;
+    static const int kRonin_NumWhoIsMasterRetries;
+    static const int kRonin_WhoIsMasterTimeoutMs;
+
+    /*** status while in the WaitForElection state ***/
+    static const int kWaitForElection_TimeoutMs;
+
+    static const int kInfiniteTimeout;
+
+    static const char* stateToString(ICommonClock::State s);
+    static void sockaddrToString(const sockaddr_storage& addr, bool addrValid,
+                                 char* buf, size_t bufLen);
+    static bool sockaddrMatch(const sockaddr_storage& a1,
+                              const sockaddr_storage& a2,
+                              bool matchAddressOnly);
+};
+
+}  // namespace android
+
+#endif  // ANDROID_COMMON_TIME_SERVER_H
diff --git a/services/common_time/common_time_server_api.cpp b/services/common_time/common_time_server_api.cpp
new file mode 100644
index 0000000..657a382
--- /dev/null
+++ b/services/common_time/common_time_server_api.cpp
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <binder/IServiceManager.h>
+#include <binder/IPCThreadState.h>
+
+#include "common_time_server.h"
+
+namespace android {
+
+//
+// Clock API
+//
+uint64_t CommonTimeServer::getTimelineID() {
+    AutoMutex _lock(&mLock);
+    return mTimelineID;
+}
+
+ICommonClock::State CommonTimeServer::getState() {
+    AutoMutex _lock(&mLock);
+    return mState;
+}
+
+status_t CommonTimeServer::getMasterAddr(struct sockaddr_storage* addr) {
+    AutoMutex _lock(&mLock);
+    if (mMasterEPValid) {
+        memcpy(addr, &mMasterEP, sizeof(*addr));
+        return OK;
+    }
+
+    return UNKNOWN_ERROR;
+}
+
+int32_t CommonTimeServer::getEstimatedError() {
+    AutoMutex _lock(&mLock);
+
+    if (ICommonClock::STATE_MASTER == mState)
+        return 0;
+
+    if (!mClockSynced)
+        return ICommonClock::kErrorEstimateUnknown;
+
+    return mClockRecovery.getLastErrorEstimate();
+}
+
+status_t CommonTimeServer::isCommonTimeValid(bool* valid,
+                                             uint32_t* timelineID) {
+    AutoMutex _lock(&mLock);
+    *valid = mCommonClock.isValid();
+    *timelineID = mTimelineID;
+    return OK;
+}
+
+//
+// Config API
+//
+status_t CommonTimeServer::getMasterElectionPriority(uint8_t *priority) {
+    AutoMutex _lock(&mLock);
+    *priority = mMasterPriority;
+    return OK;
+}
+
+status_t CommonTimeServer::setMasterElectionPriority(uint8_t priority) {
+    AutoMutex _lock(&mLock);
+
+    if (priority > 0x7F)
+        return BAD_VALUE;
+
+    mMasterPriority = priority;
+    return OK;
+}
+
+status_t CommonTimeServer::getMasterElectionEndpoint(
+        struct sockaddr_storage *addr) {
+    AutoMutex _lock(&mLock);
+    memcpy(addr, &mMasterElectionEP, sizeof(*addr));
+    return OK;
+}
+
+status_t CommonTimeServer::setMasterElectionEndpoint(
+        const struct sockaddr_storage *addr) {
+    AutoMutex _lock(&mLock);
+
+    if (!addr) 
+        return BAD_VALUE;
+
+    // TODO: add proper support for IPv6
+    if (addr->ss_family != AF_INET)
+        return BAD_VALUE;
+
+    // Only multicast and broadcast endpoints with explicit ports are allowed.
+    uint16_t ipv4Port = ntohs(
+        reinterpret_cast<const struct sockaddr_in*>(addr)->sin_port);
+    if (!ipv4Port)
+        return BAD_VALUE;
+
+    uint32_t ipv4Addr = ntohl(
+        reinterpret_cast<const struct sockaddr_in*>(addr)->sin_addr.s_addr);
+    if ((ipv4Addr != 0xFFFFFFFF) && (0xE0000000 != (ipv4Addr & 0xF0000000)))
+        return BAD_VALUE;
+
+    memcpy(&mMasterElectionEP, addr, sizeof(mMasterElectionEP));
+
+    // Force a rebind in order to change election enpoints.
+    mBindIfaceDirty = true;
+    wakeupThread_l();
+    return OK;
+}
+
+status_t CommonTimeServer::getMasterElectionGroupId(uint64_t *id) {
+    AutoMutex _lock(&mLock);
+    *id = mSyncGroupID;
+    return OK;
+}
+
+status_t CommonTimeServer::setMasterElectionGroupId(uint64_t id) {
+    AutoMutex _lock(&mLock);
+    mSyncGroupID = id;
+    return OK;
+}
+
+status_t CommonTimeServer::getInterfaceBinding(String8& ifaceName) {
+    AutoMutex _lock(&mLock);
+    if (!mBindIfaceValid)
+        return INVALID_OPERATION;
+    ifaceName = mBindIface;
+    return OK;
+}
+
+status_t CommonTimeServer::setInterfaceBinding(const String8& ifaceName) {
+    AutoMutex _lock(&mLock);
+
+    mBindIfaceDirty = true;
+    if (ifaceName.size()) {
+        mBindIfaceValid = true;
+        mBindIface = ifaceName;
+    } else {
+        mBindIfaceValid = false;
+        mBindIface.clear();
+    }
+
+    wakeupThread_l();
+    return OK;
+}
+
+status_t CommonTimeServer::getMasterAnnounceInterval(int *interval) {
+    AutoMutex _lock(&mLock);
+    *interval = mMasterAnnounceIntervalMs;
+    return OK;
+}
+
+status_t CommonTimeServer::setMasterAnnounceInterval(int interval) {
+    AutoMutex _lock(&mLock);
+
+    if (interval > (6 *3600000)) // Max interval is once every 6 hrs
+        return BAD_VALUE;
+
+    if (interval < 500) // Min interval is once per 0.5 seconds
+        return BAD_VALUE;
+
+    mMasterAnnounceIntervalMs = interval;
+    if (ICommonClock::STATE_MASTER == mState) {
+        int pendingTimeout = mCurTimeout.msecTillTimeout();
+        if ((kInfiniteTimeout == pendingTimeout) ||
+            (pendingTimeout > interval)) {
+            mCurTimeout.setTimeout(mMasterAnnounceIntervalMs);
+            wakeupThread_l();
+        }
+    }
+
+    return OK;
+}
+
+status_t CommonTimeServer::getClientSyncInterval(int *interval) {
+    AutoMutex _lock(&mLock);
+    *interval = mSyncRequestIntervalMs;
+    return OK;
+}
+
+status_t CommonTimeServer::setClientSyncInterval(int interval) {
+    AutoMutex _lock(&mLock);
+
+    if (interval > (3600000)) // Max interval is once every 60 min
+        return BAD_VALUE;
+
+    if (interval < 250) // Min interval is once per 0.25 seconds
+        return BAD_VALUE;
+
+    mSyncRequestIntervalMs = interval;
+    if (ICommonClock::STATE_CLIENT == mState) {
+        int pendingTimeout = mCurTimeout.msecTillTimeout();
+        if ((kInfiniteTimeout == pendingTimeout) ||
+            (pendingTimeout > interval)) {
+            mCurTimeout.setTimeout(mSyncRequestIntervalMs);
+            wakeupThread_l();
+        }
+    }
+
+    return OK;
+}
+
+status_t CommonTimeServer::getPanicThreshold(int *threshold) {
+    AutoMutex _lock(&mLock);
+    *threshold = mPanicThresholdUsec;
+    return OK;
+}
+
+status_t CommonTimeServer::setPanicThreshold(int threshold) {
+    AutoMutex _lock(&mLock);
+
+    if (threshold < 1000) // Min threshold is 1mSec
+        return BAD_VALUE;
+
+    mPanicThresholdUsec = threshold;
+    return OK;
+}
+
+status_t CommonTimeServer::getAutoDisable(bool *autoDisable) {
+    AutoMutex _lock(&mLock);
+    *autoDisable = mAutoDisable;
+    return OK;
+}
+
+status_t CommonTimeServer::setAutoDisable(bool autoDisable) {
+    AutoMutex _lock(&mLock);
+    mAutoDisable = autoDisable;
+    wakeupThread_l();
+    return OK;
+}
+
+status_t CommonTimeServer::forceNetworklessMasterMode() {
+    AutoMutex _lock(&mLock);
+
+    // Can't force networkless master mode if we are currently bound to a
+    // network.
+    if (mSocket >= 0)
+        return INVALID_OPERATION;
+
+    becomeMaster("force networkless");
+
+    return OK;
+}
+
+void CommonTimeServer::reevaluateAutoDisableState(bool commonClockHasClients) {
+    AutoMutex _lock(&mLock);
+    bool needWakeup = (mAutoDisable && mMasterEPValid &&
+                      (commonClockHasClients != mCommonClockHasClients));
+
+    mCommonClockHasClients = commonClockHasClients;
+
+    if (needWakeup) {
+        LOGI("Waking up service, auto-disable is engaged and service now has%s"
+             " clients", mCommonClockHasClients ? "" : " no");
+        wakeupThread_l();
+    }
+}
+
+#define dump_printf(a, b...) do {                 \
+    int res;                                      \
+    res = snprintf(buffer, sizeof(buffer), a, b); \
+    buffer[sizeof(buffer) - 1] = 0;               \
+    if (res > 0)                                  \
+        write(fd, buffer, res);                   \
+} while (0)
+#define checked_percentage(a, b) ((0 == b) ? 0.0f : ((100.0f * a) / b))
+
+status_t CommonTimeServer::dumpClockInterface(int fd,
+                                              const Vector<String16>& args,
+                                              size_t activeClients) {
+    AutoMutex _lock(&mLock);
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+
+    if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+        snprintf(buffer, SIZE, "Permission Denial: "
+                 "can't dump CommonClockService from pid=%d, uid=%d\n",
+                 IPCThreadState::self()->getCallingPid(),
+                 IPCThreadState::self()->getCallingUid());
+        write(fd, buffer, strlen(buffer));
+    } else {
+        int64_t commonTime;
+        int64_t localTime;
+        bool    synced;
+        char maStr[64];
+
+        localTime  = mLocalClock.getLocalTime();
+        synced     = (OK == mCommonClock.localToCommon(localTime, &commonTime));
+        sockaddrToString(mMasterEP, mMasterEPValid, maStr, sizeof(maStr));
+
+        dump_printf("Common Clock Service Status\nLocal time     : %lld\n",
+                    localTime);
+
+        if (synced)
+            dump_printf("Common time    : %lld\n", commonTime);
+        else
+            dump_printf("Common time    : %s\n", "not synced");
+
+        dump_printf("Timeline ID    : %016llx\n", mTimelineID);
+        dump_printf("State          : %s\n", stateToString(mState));
+        dump_printf("Master Addr    : %s\n", maStr);
+
+
+        if (synced) {
+            int32_t est = (ICommonClock::STATE_MASTER != mState)
+                        ? mClockRecovery.getLastErrorEstimate()
+                        : 0;
+            dump_printf("Error Est.     : %.3f msec\n",
+                        static_cast<float>(est) / 1000.0);
+        } else {
+            dump_printf("Error Est.     : %s\n", "unknown");
+        }
+
+        dump_printf("Syncs TXes     : %u\n", mClient_SyncsSentToCurMaster);
+        dump_printf("Syncs RXes     : %u (%.2f%%)\n",
+                    mClient_SyncRespsRXedFromCurMaster,
+                    checked_percentage(
+                        mClient_SyncRespsRXedFromCurMaster,
+                        mClient_SyncsSentToCurMaster));
+        dump_printf("RXs Expired    : %u (%.2f%%)\n",
+                    mClient_ExpiredSyncRespsRXedFromCurMaster,
+                    checked_percentage(
+                        mClient_ExpiredSyncRespsRXedFromCurMaster,
+                        mClient_SyncsSentToCurMaster));
+
+        if (!mClient_LastGoodSyncRX) {
+            dump_printf("Last Good RX   : %s\n", "unknown");
+        } else {
+            int64_t localDelta, usecDelta;
+            localDelta = localTime - mClient_LastGoodSyncRX;
+            usecDelta  = mCommonClock.localDurationToCommonDuration(localDelta);
+            dump_printf("Last Good RX   : %lld uSec ago\n", usecDelta);
+        }
+
+        dump_printf("Active Clients : %u\n", activeClients);
+        mClient_PacketRTTLog.dumpLog(fd, mCommonClock);
+    }
+
+    return NO_ERROR;
+}
+
+status_t CommonTimeServer::dumpConfigInterface(int fd,
+                                               const Vector<String16>& args) {
+    AutoMutex _lock(&mLock);
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+
+    if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+        snprintf(buffer, SIZE, "Permission Denial: "
+                 "can't dump CommonTimeConfigService from pid=%d, uid=%d\n",
+                 IPCThreadState::self()->getCallingPid(),
+                 IPCThreadState::self()->getCallingUid());
+        write(fd, buffer, strlen(buffer));
+    } else {
+        char meStr[64];
+
+        sockaddrToString(mMasterElectionEP, true, meStr, sizeof(meStr));
+
+        dump_printf("Common Time Config Service Status\n"
+                    "Bound Interface           : %s\n",
+                    mBindIfaceValid ? mBindIface.string() : "<unbound>");
+        dump_printf("Master Election Endpoint  : %s\n", meStr);
+        dump_printf("Master Election Group ID  : %016llx\n", mSyncGroupID);
+        dump_printf("Master Announce Interval  : %d mSec\n",
+                    mMasterAnnounceIntervalMs);
+        dump_printf("Client Sync Interval      : %d mSec\n",
+                    mSyncRequestIntervalMs);
+        dump_printf("Panic Threshold           : %d uSec\n",
+                    mPanicThresholdUsec);
+        dump_printf("Base ME Prio              : 0x%02x\n",
+                    static_cast<uint32_t>(mMasterPriority));
+        dump_printf("Effective ME Prio         : 0x%02x\n",
+                    static_cast<uint32_t>(effectivePriority()));
+        dump_printf("Auto Disable Allowed      : %s\n",
+                    mAutoDisable ? "yes" : "no");
+        dump_printf("Auto Disable Engaged      : %s\n",
+                    shouldAutoDisable() ? "yes" : "no");
+    }
+
+    return NO_ERROR;
+}
+
+void CommonTimeServer::PacketRTTLog::dumpLog(int fd, const CommonClock& cclk) {
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+    uint32_t avail = !logFull ? wrPtr : RTT_LOG_SIZE;
+
+    if (!avail)
+        return;
+
+    dump_printf("\nPacket Log (%d entries)\n", avail);
+
+    uint32_t ndx = 0;
+    uint32_t i = logFull ? wrPtr : 0;
+    do {
+        if (rxTimes[i]) {
+            int64_t delta = rxTimes[i] - txTimes[i];
+            int64_t deltaUsec = cclk.localDurationToCommonDuration(delta);
+            dump_printf("pkt[%2d] : localTX %12lld localRX %12lld "
+                        "(%.3f msec RTT)\n",
+                        ndx, txTimes[i], rxTimes[i],
+                        static_cast<float>(deltaUsec) / 1000.0);
+        } else {
+            dump_printf("pkt[%2d] : localTX %12lld localRX never\n",
+                        ndx, txTimes[i]);
+        }
+        i = (i + 1) % RTT_LOG_SIZE;
+        ndx++;
+    } while (i != wrPtr);
+}
+
+#undef dump_printf
+#undef checked_percentage
+
+}  // namespace android
diff --git a/services/common_time/common_time_server_packets.cpp b/services/common_time/common_time_server_packets.cpp
new file mode 100644
index 0000000..9833c37
--- /dev/null
+++ b/services/common_time/common_time_server_packets.cpp
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <stdint.h>
+
+#include "common_time_server_packets.h"
+
+namespace android {
+
+const uint32_t TimeServicePacketHeader::kMagic =
+    (static_cast<uint32_t>('c') << 24) |
+    (static_cast<uint32_t>('c') << 16) |
+    (static_cast<uint32_t>('l') <<  8) |
+     static_cast<uint32_t>('k');
+
+const uint16_t TimeServicePacketHeader::kCurVersion = 1;
+
+#define SERIALIZE_FIELD(field_name, type, converter)        \
+    do {                                                    \
+        if ((offset + sizeof(field_name)) > length)         \
+            return -1;                                      \
+        *((type*)(data + offset)) = converter(field_name);  \
+        offset += sizeof(field_name);                       \
+    } while (0)
+#define SERIALIZE_INT16(field_name) SERIALIZE_FIELD(field_name, int16_t, htons)
+#define SERIALIZE_INT32(field_name) SERIALIZE_FIELD(field_name, int32_t, htonl)
+#define SERIALIZE_INT64(field_name) SERIALIZE_FIELD(field_name, int64_t, htonq)
+
+#define DESERIALIZE_FIELD(field_name, type, converter)      \
+    do {                                                    \
+        if ((offset + sizeof(field_name)) > length)         \
+            return -1;                                      \
+        field_name = converter(*((type*)(data + offset)));  \
+        offset += sizeof(field_name);                       \
+    } while (0)
+#define DESERIALIZE_INT16(field_name) DESERIALIZE_FIELD(field_name, int16_t, ntohs)
+#define DESERIALIZE_INT32(field_name) DESERIALIZE_FIELD(field_name, int32_t, ntohl)
+#define DESERIALIZE_INT64(field_name) DESERIALIZE_FIELD(field_name, int64_t, ntohq)
+
+#define kDevicePriorityShift 56
+#define kDeviceIDMask ((static_cast<uint64_t>(1) << kDevicePriorityShift) - 1)
+
+inline uint64_t packDeviceID(uint64_t devID, uint8_t prio) {
+    return (devID & kDeviceIDMask) |
+           (static_cast<uint64_t>(prio) << kDevicePriorityShift);
+}
+
+inline uint64_t unpackDeviceID(uint64_t packed) {
+    return (packed & kDeviceIDMask);
+}
+
+inline uint8_t unpackDevicePriority(uint64_t packed) {
+    return static_cast<uint8_t>(packed >> kDevicePriorityShift);
+}
+
+ssize_t TimeServicePacketHeader::serializeHeader(uint8_t* data,
+                                                 uint32_t length) {
+    ssize_t offset = 0;
+    int16_t pktType = static_cast<int16_t>(packetType);
+    SERIALIZE_INT32(magic);
+    SERIALIZE_INT16(version);
+    SERIALIZE_INT16(pktType);
+    SERIALIZE_INT64(timelineID);
+    SERIALIZE_INT64(syncGroupID);
+    return offset;
+}
+
+ssize_t TimeServicePacketHeader::deserializeHeader(const uint8_t* data,
+                                                   uint32_t length) {
+    ssize_t offset = 0;
+    int16_t tmp;
+    DESERIALIZE_INT32(magic);
+    DESERIALIZE_INT16(version);
+    DESERIALIZE_INT16(tmp);
+    DESERIALIZE_INT64(timelineID);
+    DESERIALIZE_INT64(syncGroupID);
+    packetType = static_cast<TimeServicePacketType>(tmp);
+    return offset;
+}
+
+ssize_t TimeServicePacketHeader::serializePacket(uint8_t* data,
+                                                 uint32_t length) {
+    ssize_t ret, tmp;
+
+    ret = serializeHeader(data, length);
+    if (ret < 0)
+        return ret;
+
+    data += ret;
+    length -= ret;
+
+    switch (packetType) {
+        case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+            tmp =((WhoIsMasterRequestPacket*)(this))->serializePacket(data,
+                                                                      length);
+            break;
+        case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+            tmp =((WhoIsMasterResponsePacket*)(this))->serializePacket(data,
+                                                                       length);
+            break;
+        case TIME_PACKET_SYNC_REQUEST:
+            tmp =((SyncRequestPacket*)(this))->serializePacket(data, length);
+            break;
+        case TIME_PACKET_SYNC_RESPONSE:
+            tmp =((SyncResponsePacket*)(this))->serializePacket(data, length);
+            break;
+        case TIME_PACKET_MASTER_ANNOUNCEMENT:
+            tmp =((MasterAnnouncementPacket*)(this))->serializePacket(data,
+                                                                      length);
+            break;
+        default:
+            return -1;
+    }
+
+    if (tmp < 0)
+        return tmp;
+
+    return ret + tmp;
+}
+
+ssize_t UniversalTimeServicePacket::deserializePacket(
+        const uint8_t* data,
+        uint32_t length,
+        uint64_t expectedSyncGroupID) {
+    ssize_t ret;
+    TimeServicePacketHeader* header;
+    if (length < 8)
+        return -1;
+
+    packetType = ntohs(*((uint16_t*)(data + 6)));
+    switch (packetType) {
+        case TIME_PACKET_WHO_IS_MASTER_REQUEST:
+            ret = p.who_is_master_request.deserializePacket(data, length);
+            header = &p.who_is_master_request;
+            break;
+        case TIME_PACKET_WHO_IS_MASTER_RESPONSE:
+            ret = p.who_is_master_response.deserializePacket(data, length);
+            header = &p.who_is_master_response;
+            break;
+        case TIME_PACKET_SYNC_REQUEST:
+            ret = p.sync_request.deserializePacket(data, length);
+            header = &p.sync_request;
+            break;
+        case TIME_PACKET_SYNC_RESPONSE:
+            ret = p.sync_response.deserializePacket(data, length);
+            header = &p.sync_response;
+            break;
+        case TIME_PACKET_MASTER_ANNOUNCEMENT:
+            ret = p.master_announcement.deserializePacket(data, length);
+            header = &p.master_announcement;
+            break;
+        default:
+            return -1;
+    }
+
+    if ((ret >= 0) && !header->checkPacket(expectedSyncGroupID))
+        ret = -1;
+
+    return ret;
+}
+
+ssize_t WhoIsMasterRequestPacket::serializePacket(uint8_t* data,
+                                                  uint32_t length) {
+    ssize_t offset = serializeHeader(data, length);
+    if (offset > 0) {
+        uint64_t packed = packDeviceID(senderDeviceID, senderDevicePriority);
+        SERIALIZE_INT64(packed);
+    }
+    return offset;
+}
+
+ssize_t WhoIsMasterRequestPacket::deserializePacket(const uint8_t* data,
+                                                    uint32_t length) {
+    ssize_t offset = deserializeHeader(data, length);
+    if (offset > 0) {
+        uint64_t packed;
+        DESERIALIZE_INT64(packed);
+        senderDeviceID       = unpackDeviceID(packed);
+        senderDevicePriority = unpackDevicePriority(packed);
+    }
+    return offset;
+}
+
+ssize_t WhoIsMasterResponsePacket::serializePacket(uint8_t* data,
+                                                   uint32_t length) {
+    ssize_t offset = serializeHeader(data, length);
+    if (offset > 0) {
+        uint64_t packed = packDeviceID(deviceID, devicePriority);
+        SERIALIZE_INT64(packed);
+    }
+    return offset;
+}
+
+ssize_t WhoIsMasterResponsePacket::deserializePacket(const uint8_t* data,
+                                                     uint32_t length) {
+    ssize_t offset = deserializeHeader(data, length);
+    if (offset > 0) {
+        uint64_t packed;
+        DESERIALIZE_INT64(packed);
+        deviceID       = unpackDeviceID(packed);
+        devicePriority = unpackDevicePriority(packed);
+    }
+    return offset;
+}
+
+ssize_t SyncRequestPacket::serializePacket(uint8_t* data,
+                                           uint32_t length) {
+    ssize_t offset = serializeHeader(data, length);
+    if (offset > 0) {
+        SERIALIZE_INT64(clientTxLocalTime);
+    }
+    return offset;
+}
+
+ssize_t SyncRequestPacket::deserializePacket(const uint8_t* data,
+                                             uint32_t length) {
+    ssize_t offset = deserializeHeader(data, length);
+    if (offset > 0) {
+        DESERIALIZE_INT64(clientTxLocalTime);
+    }
+    return offset;
+}
+
+ssize_t SyncResponsePacket::serializePacket(uint8_t* data,
+                                            uint32_t length) {
+    ssize_t offset = serializeHeader(data, length);
+    if (offset > 0) {
+        SERIALIZE_INT64(clientTxLocalTime);
+        SERIALIZE_INT64(masterRxCommonTime);
+        SERIALIZE_INT64(masterTxCommonTime);
+        SERIALIZE_INT32(nak);
+    }
+    return offset;
+}
+
+ssize_t SyncResponsePacket::deserializePacket(const uint8_t* data,
+                                              uint32_t length) {
+    ssize_t offset = deserializeHeader(data, length);
+    if (offset > 0) {
+        DESERIALIZE_INT64(clientTxLocalTime);
+        DESERIALIZE_INT64(masterRxCommonTime);
+        DESERIALIZE_INT64(masterTxCommonTime);
+        DESERIALIZE_INT32(nak);
+    }
+    return offset;
+}
+
+ssize_t MasterAnnouncementPacket::serializePacket(uint8_t* data,
+                                                  uint32_t length) {
+    ssize_t offset = serializeHeader(data, length);
+    if (offset > 0) {
+        uint64_t packed = packDeviceID(deviceID, devicePriority);
+        SERIALIZE_INT64(packed);
+    }
+    return offset;
+}
+
+ssize_t MasterAnnouncementPacket::deserializePacket(const uint8_t* data,
+                                                    uint32_t length) {
+    ssize_t offset = deserializeHeader(data, length);
+    if (offset > 0) {
+        uint64_t packed;
+        DESERIALIZE_INT64(packed);
+        deviceID       = unpackDeviceID(packed);
+        devicePriority = unpackDevicePriority(packed);
+    }
+    return offset;
+}
+
+}  // namespace android
+
diff --git a/services/common_time/common_time_server_packets.h b/services/common_time/common_time_server_packets.h
new file mode 100644
index 0000000..57ba8a2
--- /dev/null
+++ b/services/common_time/common_time_server_packets.h
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef ANDROID_COMMON_TIME_SERVER_PACKETS_H
+#define ANDROID_COMMON_TIME_SERVER_PACKETS_H
+
+#include <stdint.h>
+#include <common_time/ICommonClock.h>
+
+namespace android {
+
+/***** time sync protocol packets *****/
+
+enum TimeServicePacketType {
+    TIME_PACKET_WHO_IS_MASTER_REQUEST = 1,
+    TIME_PACKET_WHO_IS_MASTER_RESPONSE,
+    TIME_PACKET_SYNC_REQUEST,
+    TIME_PACKET_SYNC_RESPONSE,
+    TIME_PACKET_MASTER_ANNOUNCEMENT,
+};
+
+class TimeServicePacketHeader {
+  public:
+    friend class UniversalTimeServicePacket;
+    // magic number identifying the protocol
+    uint32_t magic;
+
+    // protocol version of the packet
+    uint16_t version;
+
+    // type of the packet
+    TimeServicePacketType packetType;
+
+    // the timeline ID
+    uint64_t timelineID;
+
+    // synchronization group this packet belongs to (used to operate multiple
+    // synchronization domains which all use the same master election endpoint)
+    uint64_t syncGroupID;
+
+    ssize_t serializePacket(uint8_t* data, uint32_t length);
+
+  protected:
+    void initHeader(TimeServicePacketType type,
+                    const uint64_t tlID,
+                    const uint64_t groupID) {
+        magic              = kMagic;
+        version            = kCurVersion;
+        packetType         = type;
+        timelineID         = tlID;
+        syncGroupID        = groupID;
+    }
+
+    bool checkPacket(uint64_t expectedSyncGroupID) const {
+        return ((magic       == kMagic) &&
+                (version     == kCurVersion) &&
+                (!expectedSyncGroupID || (syncGroupID == expectedSyncGroupID)));
+    }
+
+    ssize_t serializeHeader(uint8_t* data, uint32_t length);
+    ssize_t deserializeHeader(const uint8_t* data, uint32_t length);
+
+  private:
+    static const uint32_t kMagic;
+    static const uint16_t kCurVersion;
+};
+
+// packet querying for a suitable master
+class WhoIsMasterRequestPacket : public TimeServicePacketHeader {
+  public:
+    uint64_t senderDeviceID;
+    uint8_t senderDevicePriority;
+
+    void initHeader(const uint64_t groupID) {
+        TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_REQUEST,
+                                            ICommonClock::kInvalidTimelineID,
+                                            groupID);
+    }
+
+    ssize_t serializePacket(uint8_t* data, uint32_t length);
+    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// response to a WhoIsMaster request
+class WhoIsMasterResponsePacket : public TimeServicePacketHeader {
+  public:
+    uint64_t deviceID;
+    uint8_t devicePriority;
+
+    void initHeader(const uint64_t tlID, const uint64_t groupID) {
+        TimeServicePacketHeader::initHeader(TIME_PACKET_WHO_IS_MASTER_RESPONSE,
+                                            tlID, groupID);
+    }
+
+    ssize_t serializePacket(uint8_t* data, uint32_t length);
+    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// packet sent by a client requesting correspondence between local
+// and common time
+class SyncRequestPacket : public TimeServicePacketHeader {
+  public:
+    // local time when this request was transmitted
+    int64_t clientTxLocalTime;
+
+    void initHeader(const uint64_t tlID, const uint64_t groupID) {
+        TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_REQUEST,
+                                            tlID, groupID);
+    }
+
+    ssize_t serializePacket(uint8_t* data, uint32_t length);
+    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// response to a sync request sent by the master
+class SyncResponsePacket : public TimeServicePacketHeader {
+  public:
+    // local time when this request was transmitted by the client
+    int64_t clientTxLocalTime;
+
+    // common time when the master received the request
+    int64_t masterRxCommonTime;
+
+    // common time when the master transmitted the response
+    int64_t masterTxCommonTime;
+
+    // flag that is set if the recipient of the sync request is not acting
+    // as a master for the requested timeline
+    uint32_t nak;
+
+    void initHeader(const uint64_t tlID, const uint64_t groupID) {
+        TimeServicePacketHeader::initHeader(TIME_PACKET_SYNC_RESPONSE,
+                                            tlID, groupID);
+    }
+
+    ssize_t serializePacket(uint8_t* data, uint32_t length);
+    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+// announcement of the master's presence
+class MasterAnnouncementPacket : public TimeServicePacketHeader {
+  public:
+    // the master's device ID
+    uint64_t deviceID;
+    uint8_t devicePriority;
+
+    void initHeader(const uint64_t tlID, const uint64_t groupID) {
+        TimeServicePacketHeader::initHeader(TIME_PACKET_MASTER_ANNOUNCEMENT,
+                                            tlID, groupID);
+    }
+
+    ssize_t serializePacket(uint8_t* data, uint32_t length);
+    ssize_t deserializePacket(const uint8_t* data, uint32_t length);
+};
+
+class UniversalTimeServicePacket {
+  public:
+    uint16_t packetType;
+    union {
+        WhoIsMasterRequestPacket  who_is_master_request;
+        WhoIsMasterResponsePacket who_is_master_response;
+        SyncRequestPacket         sync_request;
+        SyncResponsePacket        sync_response;
+        MasterAnnouncementPacket  master_announcement;
+    } p;
+
+    ssize_t deserializePacket(const uint8_t* data,
+                              uint32_t       length,
+                              uint64_t       expectedSyncGroupID);
+};
+
+};  // namespace android
+
+#endif  // ANDROID_COMMON_TIME_SERVER_PACKETS_H
+
+
diff --git a/services/common_time/diag_thread.cpp b/services/common_time/diag_thread.cpp
new file mode 100644
index 0000000..eebe983
--- /dev/null
+++ b/services/common_time/diag_thread.cpp
@@ -0,0 +1,323 @@
+/*
+ * 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 "common_time"
+#include <utils/Log.h>
+
+#include <fcntl.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <utils/Errors.h>
+#include <utils/misc.h>
+
+#include <common_time/local_clock.h>
+
+#include "common_clock.h"
+#include "diag_thread.h"
+
+#define kMaxEvents 16
+#define kListenPort 9876
+
+static bool setNonblocking(int fd) {
+    int flags = fcntl(fd, F_GETFL);
+    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
+        LOGE("Failed to set socket (%d) to non-blocking mode (errno %d)",
+             fd, errno);
+        return false;
+    }
+
+    return true;
+}
+
+static bool setNodelay(int fd) {
+    int tmp = 1;
+    if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &tmp, sizeof(tmp)) < 0) {
+        LOGE("Failed to set socket (%d) to no-delay mode (errno %d)",
+             fd, errno);
+        return false;
+    }
+
+    return true;
+}
+
+namespace android {
+
+DiagThread::DiagThread(CommonClock* common_clock, LocalClock* local_clock) {
+    common_clock_ = common_clock;
+    local_clock_ = local_clock;
+    listen_fd_ = -1;
+    data_fd_ = -1;
+    kernel_logID_basis_known_ = false;
+    discipline_log_ID_ = 0;
+}
+
+DiagThread::~DiagThread() {
+}
+
+status_t DiagThread::startWorkThread() {
+    status_t res;
+    stopWorkThread();
+    res = run("Diag");
+
+    if (res != OK)
+        LOGE("Failed to start work thread (res = %d)", res);
+
+    return res;
+}
+
+void DiagThread::stopWorkThread() {
+    status_t res;
+    res = requestExitAndWait(); // block until thread exit.
+    if (res != OK)
+        LOGE("Failed to stop work thread (res = %d)", res);
+}
+
+bool DiagThread::openListenSocket() {
+    bool ret = false;
+    int flags;
+    cleanupListenSocket();
+
+    if ((listen_fd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+        LOGE("Socket failed.");
+        goto bailout;
+    }
+
+    // Set non-blocking operation
+    if (!setNonblocking(listen_fd_))
+        goto bailout;
+
+    struct sockaddr_in addr;
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_family = AF_INET;
+    addr.sin_addr.s_addr = INADDR_ANY;
+    addr.sin_port = htons(kListenPort);
+
+    if (bind(listen_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
+        LOGE("Bind failed.");
+        goto bailout;
+    }
+
+    if (listen(listen_fd_, 1) < 0) {
+        LOGE("Listen failed.");
+        goto bailout;
+    }
+
+    ret = true;
+bailout:
+    if (!ret)
+        cleanupListenSocket();
+
+    return ret;
+}
+
+void DiagThread::cleanupListenSocket() {
+    if (listen_fd_ >= 0) {
+        int res;
+
+        struct linger l;
+        l.l_onoff  = 1;
+        l.l_linger = 0;
+
+        setsockopt(listen_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+        shutdown(listen_fd_, SHUT_RDWR);
+        close(listen_fd_);
+        listen_fd_ = -1;
+    }
+}
+
+void DiagThread::cleanupDataSocket() {
+    if (data_fd_ >= 0) {
+        int res;
+
+        struct linger l;
+        l.l_onoff  = 1;
+        l.l_linger = 0;
+
+        setsockopt(data_fd_, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+        shutdown(data_fd_, SHUT_RDWR);
+        close(data_fd_);
+        data_fd_ = -1;
+    }
+}
+
+void DiagThread::resetLogIDs() {
+    // Drain and discard all of the events from the kernel
+    struct local_time_debug_event events[kMaxEvents];
+    while(local_clock_->getDebugLog(events, kMaxEvents) > 0)
+        ;
+
+    {
+        Mutex::Autolock lock(&discipline_log_lock_);
+        discipline_log_.clear();
+        discipline_log_ID_ = 0;
+    }
+
+    kernel_logID_basis_known_ = false;
+}
+
+void DiagThread::pushDisciplineEvent(int64_t observed_local_time,
+                                     int64_t observed_common_time,
+                                     int64_t nominal_common_time,
+                                     int32_t total_correction,
+                                     int32_t rtt) {
+    Mutex::Autolock lock(&discipline_log_lock_);
+
+    DisciplineEventRecord evt;
+
+    evt.event_id = discipline_log_ID_++;
+
+    evt.action_local_time = local_clock_->getLocalTime();
+    common_clock_->localToCommon(evt.action_local_time,
+            &evt.action_common_time);
+
+    evt.observed_local_time  = observed_local_time;
+    evt.observed_common_time = observed_common_time;
+    evt.nominal_common_time  = nominal_common_time;
+    evt.total_correction     = total_correction;
+    evt.rtt                  = rtt;
+
+    discipline_log_.push_back(evt);
+    while (discipline_log_.size() > kMaxDisciplineLogSize)
+        discipline_log_.erase(discipline_log_.begin());
+}
+
+bool DiagThread::threadLoop() {
+    struct pollfd poll_fds[1];
+
+    if (!openListenSocket()) {
+        LOGE("Failed to open listen socket");
+        goto bailout;
+    }
+
+    while (!exitPending()) {
+        memset(&poll_fds, 0, sizeof(poll_fds));
+
+        if (data_fd_ < 0) {
+            poll_fds[0].fd     = listen_fd_;
+            poll_fds[0].events = POLLIN;
+        } else {
+            poll_fds[0].fd     = data_fd_;
+            poll_fds[0].events = POLLRDHUP | POLLIN;
+        }
+
+        int poll_res = poll(poll_fds, NELEM(poll_fds), 50);
+        if (poll_res < 0) {
+            LOGE("Fatal error (%d,%d) while waiting on events",
+                 poll_res, errno);
+            goto bailout;
+        }
+
+        if (exitPending())
+            break;
+
+        if (poll_fds[0].revents) {
+            if (poll_fds[0].fd == listen_fd_) {
+                data_fd_ = accept(listen_fd_, NULL, NULL);
+
+                if (data_fd_ < 0) {
+                    LOGW("Failed accept on socket %d with err %d",
+                         listen_fd_, errno);
+                } else {
+                    if (!setNonblocking(data_fd_))
+                        cleanupDataSocket();
+                    if (!setNodelay(data_fd_))
+                        cleanupDataSocket();
+                }
+            } else
+                if (poll_fds[0].fd == data_fd_) {
+                    if (poll_fds[0].revents & POLLRDHUP) {
+                        // Connection hung up; time to clean up.
+                        cleanupDataSocket();
+                    } else
+                        if (poll_fds[0].revents & POLLIN) {
+                            uint8_t cmd;
+                            if (read(data_fd_, &cmd, sizeof(cmd)) > 0) {
+                                switch(cmd) {
+                                    case 'r':
+                                    case 'R':
+                                        resetLogIDs();
+                                        break;
+                                }
+                            }
+                        }
+                }
+        }
+
+        struct local_time_debug_event events[kMaxEvents];
+        int amt = local_clock_->getDebugLog(events, kMaxEvents);
+
+        if (amt > 0) {
+            for (int i = 0; i < amt; i++) {
+                struct local_time_debug_event& e = events[i];
+
+                if (!kernel_logID_basis_known_) {
+                    kernel_logID_basis_ = e.local_timesync_event_id;
+                    kernel_logID_basis_known_ = true;
+                }
+
+                char buf[1024];
+                int64_t common_time;
+                status_t res = common_clock_->localToCommon(e.local_time,
+                                                            &common_time);
+                snprintf(buf, sizeof(buf), "E,%lld,%lld,%lld,%d\n",
+                         e.local_timesync_event_id - kernel_logID_basis_,
+                         e.local_time,
+                         common_time,
+                         (OK == res) ? 1 : 0);
+                buf[sizeof(buf) - 1] = 0;
+
+                if (data_fd_ >= 0)
+                    write(data_fd_, buf, strlen(buf));
+            }
+        }
+
+        { // scope for autolock pattern
+            Mutex::Autolock lock(&discipline_log_lock_);
+
+            while (discipline_log_.size() > 0) {
+                char buf[1024];
+                DisciplineEventRecord& e = *discipline_log_.begin();
+                snprintf(buf, sizeof(buf),
+                         "D,%lld,%lld,%lld,%lld,%lld,%lld,%d,%d\n",
+                         e.event_id,
+                         e.action_local_time,
+                         e.action_common_time,
+                         e.observed_local_time,
+                         e.observed_common_time,
+                         e.nominal_common_time,
+                         e.total_correction,
+                         e.rtt);
+                buf[sizeof(buf) - 1] = 0;
+
+                if (data_fd_ >= 0)
+                    write(data_fd_, buf, strlen(buf));
+
+                discipline_log_.erase(discipline_log_.begin());
+            }
+        }
+    }
+
+bailout:
+    cleanupDataSocket();
+    cleanupListenSocket();
+    return false;
+}
+
+}  // namespace android
diff --git a/services/common_time/diag_thread.h b/services/common_time/diag_thread.h
new file mode 100644
index 0000000..c630e0d
--- /dev/null
+++ b/services/common_time/diag_thread.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+#ifndef __DIAG_THREAD_H__
+#define __DIAG_THREAD_H__
+
+#include <utils/List.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class CommonClock;
+class LocalClock;
+
+class DiagThread : public Thread {
+  public:
+    DiagThread(CommonClock* common_clock, LocalClock* local_clock);
+    ~DiagThread();
+
+    status_t      startWorkThread();
+    void          stopWorkThread();
+    virtual bool  threadLoop();
+
+    void pushDisciplineEvent(int64_t observed_local_time,
+                             int64_t observed_common_time,
+                             int64_t nominal_common_time,
+                             int32_t total_correction,
+                             int32_t rtt);
+
+  private:
+    typedef struct {
+        int64_t event_id;
+        int64_t action_local_time;
+        int64_t action_common_time;
+        int64_t observed_local_time;
+        int64_t observed_common_time;
+        int64_t nominal_common_time;
+        int32_t total_correction;
+        int32_t rtt;
+    } DisciplineEventRecord;
+
+    bool            openListenSocket();
+    void            cleanupListenSocket();
+    void            cleanupDataSocket();
+    void            resetLogIDs();
+
+    CommonClock*    common_clock_;
+    LocalClock*     local_clock_;
+    int             listen_fd_;
+    int             data_fd_;
+
+    int64_t         kernel_logID_basis_;
+    bool            kernel_logID_basis_known_;
+
+    static const size_t         kMaxDisciplineLogSize = 16;
+    Mutex                       discipline_log_lock_;
+    List<DisciplineEventRecord> discipline_log_;
+    int64_t                     discipline_log_ID_;
+};
+
+}  // namespace android
+
+#endif  //__ DIAG_THREAD_H__
diff --git a/services/common_time/main.cpp b/services/common_time/main.cpp
new file mode 100644
index 0000000..49eb30a
--- /dev/null
+++ b/services/common_time/main.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+/*
+ * A service that exchanges time synchronization information between
+ * a master that defines a timeline and clients that follow the timeline.
+ */
+
+#define LOG_TAG "common_time"
+#include <utils/Log.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+
+#include "common_time_server.h"
+
+int main(int argc, char *argv[]) {
+    using namespace android;
+
+    sp<CommonTimeServer> service = new CommonTimeServer();
+    if (service == NULL)
+        return 1;
+
+    ProcessState::self()->startThreadPool();
+    service->run("CommonTimeServer", ANDROID_PRIORITY_NORMAL);
+
+    IPCThreadState::self()->joinThreadPool();
+    return 0;
+}
+
diff --git a/services/java/com/android/server/CommonTimeManagementService.java b/services/java/com/android/server/CommonTimeManagementService.java
new file mode 100644
index 0000000..9a25d2e
--- /dev/null
+++ b/services/java/com/android/server/CommonTimeManagementService.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2012 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.server;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.InterfaceConfiguration;
+import android.net.NetworkInfo;
+import android.os.Binder;
+import android.os.CommonTimeConfig;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
+
+/**
+ * @hide
+ * <p>CommonTimeManagementService manages the configuration of the native Common Time service,
+ * reconfiguring the native service as appropriate in response to changes in network configuration.
+ */
+class CommonTimeManagementService extends Binder {
+    /*
+     * Constants and globals.
+     */
+    private static final String TAG = CommonTimeManagementService.class.getSimpleName();
+    private static final int NATIVE_SERVICE_RECONNECT_TIMEOUT = 5000;
+    private static final String AUTO_DISABLE_PROP = "ro.common_time.auto_disable";
+    private static final String ALLOW_WIFI_PROP = "ro.common_time.allow_wifi";
+    private static final String SERVER_PRIO_PROP = "ro.common_time.server_prio";
+    private static final String NO_INTERFACE_TIMEOUT_PROP = "ro.common_time.no_iface_timeout";
+    private static final boolean AUTO_DISABLE;
+    private static final boolean ALLOW_WIFI;
+    private static final byte BASE_SERVER_PRIO;
+    private static final int NO_INTERFACE_TIMEOUT;
+    private static final InterfaceScoreRule[] IFACE_SCORE_RULES;
+
+    static {
+        int tmp;
+        AUTO_DISABLE         = (0 != SystemProperties.getInt(AUTO_DISABLE_PROP, 1));
+        ALLOW_WIFI           = (0 != SystemProperties.getInt(ALLOW_WIFI_PROP, 0));
+        tmp                  = SystemProperties.getInt(SERVER_PRIO_PROP, 1);
+        NO_INTERFACE_TIMEOUT = SystemProperties.getInt(NO_INTERFACE_TIMEOUT_PROP, 60000);
+
+        if (tmp < 1)
+            BASE_SERVER_PRIO = 1;
+        else
+        if (tmp > 30)
+            BASE_SERVER_PRIO = 30;
+        else
+            BASE_SERVER_PRIO = (byte)tmp;
+
+        if (ALLOW_WIFI) {
+            IFACE_SCORE_RULES = new InterfaceScoreRule[] {
+                new InterfaceScoreRule("wlan", (byte)1),
+                new InterfaceScoreRule("eth", (byte)2),
+            };
+        } else {
+            IFACE_SCORE_RULES = new InterfaceScoreRule[] {
+                new InterfaceScoreRule("eth", (byte)2),
+            };
+        }
+    };
+
+    /*
+     * Internal state
+     */
+    private final Context mContext;
+    private INetworkManagementService mNetMgr;
+    private CommonTimeConfig mCTConfig;
+    private String mCurIface;
+    private Handler mReconnectHandler = new Handler();
+    private Handler mNoInterfaceHandler = new Handler();
+    private Object mLock = new Object();
+    private boolean mDetectedAtStartup = false;
+    private byte mEffectivePrio = BASE_SERVER_PRIO;
+
+    /*
+     * Callback handler implementations.
+     */
+    private INetworkManagementEventObserver mIfaceObserver =
+        new INetworkManagementEventObserver.Stub() {
+
+        public void interfaceStatusChanged(String iface, boolean up) {
+            reevaluateServiceState();
+        }
+        public void interfaceLinkStateChanged(String iface, boolean up) {
+            reevaluateServiceState();
+        }
+        public void interfaceAdded(String iface) {
+            reevaluateServiceState();
+        }
+        public void interfaceRemoved(String iface) {
+            reevaluateServiceState();
+        }
+        public void limitReached(String limitName, String iface) { }
+    };
+
+    private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reevaluateServiceState();
+        }
+    };
+
+    private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener =
+        new CommonTimeConfig.OnServerDiedListener() {
+            public void onServerDied() {
+                scheduleTimeConfigReconnect();
+            }
+        };
+
+    private Runnable mReconnectRunnable = new Runnable() {
+        public void run() { connectToTimeConfig(); }
+    };
+
+    private Runnable mNoInterfaceRunnable = new Runnable() {
+        public void run() { handleNoInterfaceTimeout(); }
+    };
+
+    /*
+     * Public interface (constructor, systemReady and dump)
+     */
+    public CommonTimeManagementService(Context context) {
+        mContext = context;
+    }
+
+    void systemReady() {
+        if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) {
+            Log.i(TAG, "No common time service detected on this platform.  " +
+                       "Common time services will be unavailable.");
+            return;
+        }
+
+        mDetectedAtStartup = true;
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        mNetMgr = INetworkManagementService.Stub.asInterface(b);
+
+        // Network manager is running along-side us, so we should never receiver a remote exception
+        // while trying to register this observer.
+        try {
+            mNetMgr.registerObserver(mIfaceObserver);
+        }
+        catch (RemoteException e) { }
+
+        // Register with the connectivity manager for connectivity changed intents.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mContext.registerReceiver(mConnectivityMangerObserver, filter);
+
+        // Connect to the common time config service and apply the initial configuration.
+        connectToTimeConfig();
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println(String.format(
+                        "Permission Denial: can't dump CommonTimeManagement service from from " +
+                        "pid=%d, uid=%d", Binder.getCallingPid(), Binder.getCallingUid()));
+            return;
+        }
+
+        if (!mDetectedAtStartup) {
+            pw.println("Native Common Time service was not detected at startup.  " +
+                       "Service is unavailable");
+            return;
+        }
+
+        synchronized (mLock) {
+            pw.println("Current Common Time Management Service Config:");
+            pw.println(String.format("  Native service     : %s",
+                                     (null == mCTConfig) ? "reconnecting"
+                                                         : "alive"));
+            pw.println(String.format("  Bound interface    : %s",
+                                     (null == mCurIface ? "unbound" : mCurIface)));
+            pw.println(String.format("  Allow WiFi         : %s", ALLOW_WIFI ? "yes" : "no"));
+            pw.println(String.format("  Allow Auto Disable : %s", AUTO_DISABLE ? "yes" : "no"));
+            pw.println(String.format("  Server Priority    : %d", mEffectivePrio));
+            pw.println(String.format("  No iface timeout   : %d", NO_INTERFACE_TIMEOUT));
+        }
+    }
+
+    /*
+     * Inner helper classes
+     */
+    private static class InterfaceScoreRule {
+        public final String mPrefix;
+        public final byte mScore;
+        public InterfaceScoreRule(String prefix, byte score) {
+            mPrefix = prefix;
+            mScore = score;
+        }
+    };
+
+    /*
+     * Internal implementation
+     */
+    private void cleanupTimeConfig() {
+        mReconnectHandler.removeCallbacks(mReconnectRunnable);
+        mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
+        if (null != mCTConfig) {
+            mCTConfig.release();
+            mCTConfig = null;
+        }
+    }
+
+    private void connectToTimeConfig() {
+        // Get access to the common time service configuration interface.  If we catch a remote
+        // exception in the process (service crashed or no running for w/e reason), schedule an
+        // attempt to reconnect in the future.
+        cleanupTimeConfig();
+        try {
+            synchronized (mLock) {
+                mCTConfig = new CommonTimeConfig();
+                mCTConfig.setServerDiedListener(mCTServerDiedListener);
+                mCurIface = mCTConfig.getInterfaceBinding();
+                mCTConfig.setAutoDisable(AUTO_DISABLE);
+                mCTConfig.setMasterElectionPriority(mEffectivePrio);
+            }
+
+            if (NO_INTERFACE_TIMEOUT >= 0)
+                mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
+
+            reevaluateServiceState();
+        }
+        catch (RemoteException e) {
+            scheduleTimeConfigReconnect();
+        }
+    }
+
+    private void scheduleTimeConfigReconnect() {
+        cleanupTimeConfig();
+        Log.w(TAG, String.format("Native service died, will reconnect in %d mSec",
+                                 NATIVE_SERVICE_RECONNECT_TIMEOUT));
+        mReconnectHandler.postDelayed(mReconnectRunnable,
+                                      NATIVE_SERVICE_RECONNECT_TIMEOUT);
+    }
+
+    private void handleNoInterfaceTimeout() {
+        if (null != mCTConfig) {
+            Log.i(TAG, "Timeout waiting for interface to come up.  " +
+                       "Forcing networkless master mode.");
+            if (CommonTimeConfig.ERROR_DEAD_OBJECT == mCTConfig.forceNetworklessMasterMode())
+                scheduleTimeConfigReconnect();
+        }
+    }
+
+    private void reevaluateServiceState() {
+        String bindIface = null;
+        byte bestScore = -1;
+        try {
+            // Check to see if this interface is suitable to use for time synchronization.
+            //
+            // TODO : This selection algorithm needs to be enhanced for use with mobile devices.  In
+            // particular, the choice of whether to a wireless interface or not should not be an all
+            // or nothing thing controlled by properties.  It would probably be better if the
+            // platform had some concept of public wireless networks vs. home or friendly wireless
+            // networks (something a user would configure in settings or when a new interface is
+            // added).  Then this algorithm could pick only wireless interfaces which were flagged
+            // as friendly, and be dormant when on public wireless networks.
+            //
+            // Another issue which needs to be dealt with is the use of driver supplied interface
+            // name to determine the network type.  The fact that the wireless interface on a device
+            // is named "wlan0" is just a matter of convention; its not a 100% rule.  For example,
+            // there are devices out there where the wireless is name "tiwlan0", not "wlan0".  The
+            // internal network management interfaces in Android have all of the information needed
+            // to make a proper classification, there is just no way (currently) to fetch an
+            // interface's type (available from the ConnectionManager) as well as its address
+            // (available from either the java.net interfaces or from the NetworkManagment service).
+            // Both can enumerate interfaces, but that is no way to correlate their results (no
+            // common shared key; although using the interface name in the connection manager would
+            // be a good start).  Until this gets resolved, we resort to substring searching for
+            // tags like wlan and eth.
+            //
+            String ifaceList[] = mNetMgr.listInterfaces();
+            if (null != ifaceList) {
+                for (String iface : ifaceList) {
+
+                    byte thisScore = -1;
+                    for (InterfaceScoreRule r : IFACE_SCORE_RULES) {
+                        if (iface.contains(r.mPrefix)) {
+                            thisScore = r.mScore;
+                            break;
+                        }
+                    }
+
+                    if (thisScore <= bestScore)
+                        continue;
+
+                    InterfaceConfiguration config = mNetMgr.getInterfaceConfig(iface);
+                    if (null == config)
+                        continue;
+
+                    if (config.isActive()) {
+                        bindIface = iface;
+                        bestScore = thisScore;
+                    }
+                }
+            }
+        }
+        catch (RemoteException e) {
+            // Bad news; we should not be getting remote exceptions from the connectivity manager
+            // since it is running in SystemServer along side of us.  It probably does not matter
+            // what we do here, but go ahead and unbind the common time service in this case, just
+            // so we have some defined behavior.
+            bindIface = null;
+        }
+
+        boolean doRebind = true;
+        synchronized (mLock) {
+            if ((null != bindIface) && (null == mCurIface)) {
+                Log.e(TAG, String.format("Binding common time service to %s.", bindIface));
+                mCurIface = bindIface;
+            } else
+            if ((null == bindIface) && (null != mCurIface)) {
+                Log.e(TAG, "Unbinding common time service.");
+                mCurIface = null;
+            } else
+            if ((null != bindIface) && (null != mCurIface) && !bindIface.equals(mCurIface)) {
+                Log.e(TAG, String.format("Switching common time service binding from %s to %s.",
+                                         mCurIface, bindIface));
+                mCurIface = bindIface;
+            } else {
+                doRebind = false;
+            }
+        }
+
+        if (doRebind && (null != mCTConfig)) {
+            byte newPrio = (bestScore > 0)
+                         ? (byte)(bestScore * BASE_SERVER_PRIO)
+                         : BASE_SERVER_PRIO;
+            if (newPrio != mEffectivePrio) {
+                mEffectivePrio = newPrio;
+                mCTConfig.setMasterElectionPriority(mEffectivePrio);
+            }
+
+            int res = mCTConfig.setNetworkBinding(mCurIface);
+            if (res != CommonTimeConfig.SUCCESS)
+                scheduleTimeConfigReconnect();
+
+            else if (NO_INTERFACE_TIMEOUT >= 0) {
+                mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
+                if (null == mCurIface)
+                    mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
+            }
+        }
+    }
+}
diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java
index f7fe39e..02a0872 100644
--- a/services/java/com/android/server/NetworkTimeUpdateService.java
+++ b/services/java/com/android/server/NetworkTimeUpdateService.java
@@ -55,7 +55,7 @@
 
     private static final int EVENT_AUTO_TIME_CHANGED = 1;
     private static final int EVENT_POLL_NETWORK_TIME = 2;
-    private static final int EVENT_WIFI_CONNECTED = 3;
+    private static final int EVENT_NETWORK_CONNECTED = 3;
 
     /** Normal polling frequency */
     private static final long POLLING_INTERVAL_MS = 24L * 60 * 60 * 1000; // 24 hrs
@@ -239,8 +239,9 @@
                 if (netInfo != null) {
                     // Verify that it's a WIFI connection
                     if (netInfo.getState() == NetworkInfo.State.CONNECTED &&
-                            netInfo.getType() == ConnectivityManager.TYPE_WIFI ) {
-                        mHandler.obtainMessage(EVENT_WIFI_CONNECTED).sendToTarget();
+                            (netInfo.getType() == ConnectivityManager.TYPE_WIFI ||
+                                netInfo.getType() == ConnectivityManager.TYPE_ETHERNET) ) {
+                        mHandler.obtainMessage(EVENT_NETWORK_CONNECTED).sendToTarget();
                     }
                 }
             }
@@ -259,7 +260,7 @@
             switch (msg.what) {
                 case EVENT_AUTO_TIME_CHANGED:
                 case EVENT_POLL_NETWORK_TIME:
-                case EVENT_WIFI_CONNECTED:
+                case EVENT_NETWORK_CONNECTED:
                     onPollNetworkTime(msg.what);
                     break;
             }
diff --git a/services/java/com/android/server/PowerManagerService.java b/services/java/com/android/server/PowerManagerService.java
index 2a0d2a0..0538a88 100644
--- a/services/java/com/android/server/PowerManagerService.java
+++ b/services/java/com/android/server/PowerManagerService.java
@@ -50,6 +50,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
 import android.provider.Settings;
@@ -163,6 +164,7 @@
 
     private boolean mDoneBooting = false;
     private boolean mBootCompleted = false;
+    private boolean mHeadless = false;
     private int mStayOnConditions = 0;
     private final int[] mBroadcastQueue = new int[] { -1, -1, -1 };
     private final int[] mBroadcastWhy = new int[3];
@@ -512,6 +514,7 @@
         mButtonLight = lights.getLight(LightsService.LIGHT_ID_BUTTONS);
         mKeyboardLight = lights.getLight(LightsService.LIGHT_ID_KEYBOARD);
         mAttentionLight = lights.getLight(LightsService.LIGHT_ID_ATTENTION);
+        mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
 
         nativeInit();
         synchronized (mLocks) {
@@ -1877,9 +1880,11 @@
     }
 
     private void updateNativePowerStateLocked() {
-        nativeSetPowerState(
-                (mPowerState & SCREEN_ON_BIT) != 0,
-                (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT);
+        if (!mHeadless) {
+            nativeSetPowerState(
+                    (mPowerState & SCREEN_ON_BIT) != 0,
+                    (mPowerState & SCREEN_BRIGHT) == SCREEN_BRIGHT);
+        }
     }
 
     private int screenOffFinishedAnimatingLocked(int reason) {
@@ -2223,11 +2228,13 @@
                         mScreenOffHandler.postAtTime(this, now+(1000/60));
                     }
                 } else {
-                    // It's pretty scary to hold mLocks for this long, and we should
-                    // redesign this, but it works for now.
-                    nativeStartSurfaceFlingerAnimation(
-                            mScreenOffReason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR
-                            ? 0 : mAnimationSetting);
+                    if (!mHeadless) {
+                        // It's pretty scary to hold mLocks for this long, and we should
+                        // redesign this, but it works for now.
+                        nativeStartSurfaceFlingerAnimation(
+                                mScreenOffReason == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR
+                                ? 0 : mAnimationSetting);
+                    }
                     mScreenBrightness.jumpToTargetLocked();
                 }
             }
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 3ae62ad..5961b4d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -108,6 +108,7 @@
         String factoryTestStr = SystemProperties.get("ro.factorytest");
         int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF
                 : Integer.parseInt(factoryTestStr);
+        final boolean headless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
 
         LightsService lights = null;
         PowerManagerService power = null;
@@ -126,10 +127,12 @@
         BluetoothA2dpService bluetoothA2dp = null;
         DockObserver dock = null;
         UsbService usb = null;
+        SerialService serial = null;
         UiModeManagerService uiMode = null;
         RecognitionManagerService recognition = null;
         ThrottleService throttle = null;
         NetworkTimeUpdateService networkTimeUpdater = null;
+        CommonTimeManagementService commonTimeMgmtService = null;
 
         // Critical services...
         try {
@@ -231,10 +234,13 @@
                 bluetooth = new BluetoothService(context);
                 ServiceManager.addService(BluetoothAdapter.BLUETOOTH_SERVICE, bluetooth);
                 bluetooth.initAfterRegistration();
-                bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);
-                ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,
-                                          bluetoothA2dp);
-                bluetooth.initAfterA2dpRegistration();
+
+                if (!"0".equals(SystemProperties.get("system_init.startaudioservice"))) {
+                    bluetoothA2dp = new BluetoothA2dpService(context, bluetooth);
+                    ServiceManager.addService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE,
+                                              bluetoothA2dp);
+                    bluetooth.initAfterA2dpRegistration();
+                }
 
                 int airplaneModeOn = Settings.System.getInt(mContentResolver,
                         Settings.System.AIRPLANE_MODE_ON, 0);
@@ -396,15 +402,17 @@
                 reportWtf("starting ThrottleService", e);
             }
 
-            try {
-                /*
-                 * NotificationManagerService is dependant on MountService,
-                 * (for media / usb notifications) so we must start MountService first.
-                 */
-                Slog.i(TAG, "Mount Service");
-                ServiceManager.addService("mount", new MountService(context));
-            } catch (Throwable e) {
-                reportWtf("starting Mount Service", e);
+            if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+                try {
+                    /*
+                     * NotificationManagerService is dependant on MountService,
+                     * (for media / usb notifications) so we must start MountService first.
+                     */
+                    Slog.i(TAG, "Mount Service");
+                    ServiceManager.addService("mount", new MountService(context));
+                } catch (Throwable e) {
+                    reportWtf("starting Mount Service", e);
+                }
             }
 
             try {
@@ -456,19 +464,26 @@
                 reportWtf("starting DropBoxManagerService", e);
             }
 
-            try {
-                Slog.i(TAG, "Wallpaper Service");
-                wallpaper = new WallpaperManagerService(context);
-                ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
-            } catch (Throwable e) {
-                reportWtf("starting Wallpaper Service", e);
+            if (context.getResources().getBoolean(
+                        com.android.internal.R.bool.config_enableWallpaperService)) {
+                try {
+                    Slog.i(TAG, "Wallpaper Service");
+                    if (!headless) {
+                        wallpaper = new WallpaperManagerService(context);
+                        ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
+                    }
+                } catch (Throwable e) {
+                    reportWtf("starting Wallpaper Service", e);
+                }
             }
 
-            try {
-                Slog.i(TAG, "Audio Service");
-                ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));
-            } catch (Throwable e) {
-                reportWtf("starting Audio Service", e);
+            if (!"0".equals(SystemProperties.get("system_init.startaudioservice"))) {
+                try {
+                    Slog.i(TAG, "Audio Service");
+                    ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));
+                } catch (Throwable e) {
+                    reportWtf("starting Audio Service", e);
+                }
             }
 
             try {
@@ -497,6 +512,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);
@@ -552,6 +576,14 @@
             } catch (Throwable e) {
                 reportWtf("starting NetworkTimeUpdate service", e);
             }
+
+            try {
+                Slog.i(TAG, "CommonTimeManagementService");
+                commonTimeMgmtService = new CommonTimeManagementService(context);
+                ServiceManager.addService("commontime_management", commonTimeMgmtService);
+            } catch (Throwable e) {
+                reportWtf("starting CommonTimeManagementService service", e);
+            }
         }
 
         // Before things start rolling, be sure we have decided whether
@@ -630,6 +662,7 @@
         final LocationManagerService locationF = location;
         final CountryDetectorService countryDetectorF = countryDetector;
         final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
+        final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService;
         final TextServicesManagerService textServiceManagerServiceF = tsms;
         final StatusBarManagerService statusBarF = statusBar;
 
@@ -642,7 +675,7 @@
             public void run() {
                 Slog.i(TAG, "Making services ready");
 
-                startSystemUi(contextF);
+                if (!headless) startSystemUi(contextF);
                 try {
                     if (batteryF != null) batteryF.systemReady();
                 } catch (Throwable e) {
@@ -729,6 +762,11 @@
                     reportWtf("making Network Time Service ready", e);
                 }
                 try {
+                    if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Common time management service ready", e);
+                }
+                try {
                     if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady();
                 } catch (Throwable e) {
                     reportWtf("making Text Services Manager Service ready", e);
diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java
index c7fbc00..1709001 100644
--- a/services/java/com/android/server/UiModeManagerService.java
+++ b/services/java/com/android/server/UiModeManagerService.java
@@ -90,6 +90,7 @@
     private int mNightMode = UiModeManager.MODE_NIGHT_NO;
     private boolean mCarModeEnabled = false;
     private boolean mCharging = false;
+    private final int mDefaultUiModeType;
     private final boolean mCarModeKeepsScreenOn;
     private final boolean mDeskModeKeepsScreenOn;
 
@@ -347,6 +348,8 @@
 
         mConfiguration.setToDefaults();
 
+        mDefaultUiModeType = context.getResources().getInteger(
+                com.android.internal.R.integer.config_defaultUiModeType);
         mCarModeKeepsScreenOn = (context.getResources().getInteger(
                 com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
         mDeskModeKeepsScreenOn = (context.getResources().getInteger(
@@ -452,7 +455,7 @@
     }
 
     final void updateConfigurationLocked(boolean sendIt) {
-        int uiMode = Configuration.UI_MODE_TYPE_NORMAL;
+        int uiMode = mDefaultUiModeType;
         if (mCarModeEnabled) {
             uiMode = Configuration.UI_MODE_TYPE_CAR;
         } else if (isDeskDockState(mDockState)) {
diff --git a/services/java/com/android/server/WiredAccessoryObserver.java b/services/java/com/android/server/WiredAccessoryObserver.java
index 6a63eac..326b940 100644
--- a/services/java/com/android/server/WiredAccessoryObserver.java
+++ b/services/java/com/android/server/WiredAccessoryObserver.java
@@ -191,8 +191,12 @@
         mHeadsetState = headsetState;
 
         if (headsetState == 0) {
-            Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
-            mContext.sendBroadcast(intent);
+            if (mContext.getResources().getBoolean(
+                    com.android.internal.R.bool.config_sendAudioBecomingNoisy)) {
+                Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+                mContext.sendBroadcast(intent);
+            }
+
             // It can take hundreds of ms flush the audio pipeline after
             // apps pause audio playback, but audio route changes are
             // immediate, so delay the route change by 1000ms.
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index df58e83..96a6e58 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -259,7 +259,14 @@
     static final String[] EMPTY_STRING_ARRAY = new String[0];
 
     public ActivityStack mMainStack;
-    
+
+    private final boolean mHeadless;
+
+    // Whether we should show our dialogs (ANR, crash, etc) or just perform their
+    // default actuion automatically.  Important for devices without direct input
+    // devices.
+    private boolean mShowDialogs = true;
+
     /**
      * Description of a request to start a new activity, which has been held
      * due to app switches being disabled.
@@ -886,7 +893,7 @@
                         return;
                     }
                     AppErrorResult res = (AppErrorResult) data.get("result");
-                    if (!mSleeping && !mShuttingDown) {
+                    if (mShowDialogs && !mSleeping && !mShuttingDown) {
                         Dialog d = new AppErrorDialog(mContext, res, proc);
                         d.show();
                         proc.crashDialog = d;
@@ -937,7 +944,7 @@
                         return;
                     }
                     AppErrorResult res = (AppErrorResult) data.get("result");
-                    if (!mSleeping && !mShuttingDown) {
+                    if (mShowDialogs && !mSleeping && !mShuttingDown) {
                         Dialog d = new StrictModeViolationDialog(mContext, res, proc);
                         d.show();
                         proc.crashDialog = d;
@@ -1057,16 +1064,22 @@
                 }
             } break;
             case SHOW_UID_ERROR_MSG: {
-                // XXX This is a temporary dialog, no need to localize.
-                AlertDialog d = new BaseErrorDialog(mContext);
-                d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
-                d.setCancelable(false);
-                d.setTitle("System UIDs Inconsistent");
-                d.setMessage("UIDs on the system are inconsistent, you need to wipe your data partition or your device will be unstable.");
-                d.setButton(DialogInterface.BUTTON_POSITIVE, "I'm Feeling Lucky",
-                        mHandler.obtainMessage(IM_FEELING_LUCKY_MSG));
-                mUidAlert = d;
-                d.show();
+                String title = "System UIDs Inconsistent";
+                String text = "UIDs on the system are inconsistent, you need to wipe your"
+                        + " data partition or your device will be unstable.";
+                Log.e(TAG, title + ": " + text);
+                if (mShowDialogs) {
+                    // XXX This is a temporary dialog, no need to localize.
+                    AlertDialog d = new BaseErrorDialog(mContext);
+                    d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+                    d.setCancelable(false);
+                    d.setTitle(title);
+                    d.setMessage(text);
+                    d.setButton(DialogInterface.BUTTON_POSITIVE, "I'm Feeling Lucky",
+                            mHandler.obtainMessage(IM_FEELING_LUCKY_MSG));
+                    mUidAlert = d;
+                    d.show();
+                }
             } break;
             case IM_FEELING_LUCKY_MSG: {
                 if (mUidAlert != null) {
@@ -1493,6 +1506,7 @@
         
         mUsageStatsService = new UsageStatsService(new File(
                 systemDir, "usagestats").toString());
+        mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0"));
 
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
             ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
@@ -2031,6 +2045,13 @@
     }
 
     boolean startHomeActivityLocked() {
+        if (mHeadless) {
+            // Added because none of the other calls to ensureBootCompleted seem to fire
+            // when running headless.
+            ensureBootCompleted();
+            return false;
+        }
+
         if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL
                 && mTopAction == null) {
             // We are running in factory test mode, but unable to find
@@ -3855,7 +3876,9 @@
             if (hr.app == null && app.info.uid == hr.info.applicationInfo.uid
                     && processName.equals(hr.processName)) {
                 try {
-                    if (mMainStack.realStartActivityLocked(hr, app, true, true)) {
+                    if (mHeadless) {
+                        Slog.e(TAG, "Starting activities not supported on headless device: " + hr);
+                    } else if (mMainStack.realStartActivityLocked(hr, app, true, true)) {
                         didSomething = true;
                     }
                 } catch (Exception e) {
@@ -13368,6 +13391,9 @@
      */
     public boolean updateConfigurationLocked(Configuration values,
             ActivityRecord starting, boolean persistent, boolean initLocale) {
+        // do nothing if we are headless
+        if (mHeadless) return true;
+
         int changes = 0;
         
         boolean kept = true;
@@ -13396,6 +13422,10 @@
                 mConfiguration = newConfig;
                 Slog.i(TAG, "Config changed: " + newConfig);
 
+                // TODO: If our config changes, should we auto dismiss any currently
+                // showing dialogs?
+                mShowDialogs = shouldShowDialogs(newConfig);
+
                 final Configuration configCopy = new Configuration(mConfiguration);
 
                 AttributeCache ac = AttributeCache.instance();
@@ -13463,6 +13493,19 @@
         
         return kept;
     }
+
+    /**
+     * Decide based on the configuration whether we should shouw the ANR,
+     * crash, etc dialogs.  The idea is that if there is no affordnace to
+     * press the on-screen buttons, we shouldn't show the dialog.
+     *
+     * A thought: SystemUI might also want to get told about this, the Power
+     * dialog / global actions also might want different behaviors.
+     */
+    private static final boolean shouldShowDialogs(Configuration config) {
+        return !(config.keyboard == Configuration.KEYBOARD_NOKEYS
+                && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH);
+    }
     
     /**
      * Save the locale.  You must be inside a synchronized (this) block.
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 36442a0..b54c519 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -2020,6 +2020,39 @@
         return false;
     }
 
+    static final void printFlags(PrintWriter pw, int val, Object[] spec) {
+        pw.print("[ ");
+        for (int i=0; i<spec.length; i+=2) {
+            int mask = (Integer)spec[i];
+            if ((val & mask) != 0) {
+                pw.print(spec[i+1]);
+                pw.print(" ");
+            }
+        }
+        pw.print("]");
+    }
+
+    static final Object[] FLAG_DUMP_SPEC = new Object[] {
+        ApplicationInfo.FLAG_SYSTEM, "SYSTEM",
+        ApplicationInfo.FLAG_DEBUGGABLE, "DEBUGGABLE",
+        ApplicationInfo.FLAG_HAS_CODE, "HAS_CODE",
+        ApplicationInfo.FLAG_PERSISTENT, "PERSISTENT",
+        ApplicationInfo.FLAG_FACTORY_TEST, "FACTORY_TEST",
+        ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING, "ALLOW_TASK_REPARENTING",
+        ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA, "ALLOW_CLEAR_USER_DATA",
+        ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, "UPDATED_SYSTEM_APP",
+        ApplicationInfo.FLAG_TEST_ONLY, "TEST_ONLY",
+        ApplicationInfo.FLAG_VM_SAFE_MODE, "VM_SAFE_MODE",
+        ApplicationInfo.FLAG_ALLOW_BACKUP, "ALLOW_BACKUP",
+        ApplicationInfo.FLAG_KILL_AFTER_RESTORE, "KILL_AFTER_RESTORE",
+        ApplicationInfo.FLAG_RESTORE_ANY_VERSION, "RESTORE_ANY_VERSION",
+        ApplicationInfo.FLAG_EXTERNAL_STORAGE, "EXTERNAL_STORAGE",
+        ApplicationInfo.FLAG_LARGE_HEAP, "LARGE_HEAP",
+        ApplicationInfo.FLAG_STOPPED, "STOPPED",
+        ApplicationInfo.FLAG_FORWARD_LOCK, "FORWARD_LOCK",
+        ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
+    };
+
     void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
         final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         final Date date = new Date();
@@ -2060,6 +2093,7 @@
             pw.print("    nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
             pw.print("    versionCode="); pw.println(ps.versionCode);
             if (ps.pkg != null) {
+                pw.print("    flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println();
                 pw.print("    versionName="); pw.println(ps.pkg.mVersionName);
                 pw.print("    dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
                 pw.print("    targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion);
@@ -2261,4 +2295,4 @@
         pw.println("Settings parse messages:");
         pw.print(mReadMessages.toString());
     }
-}
\ No newline at end of file
+}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 3f72dec..1c1e88f 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -250,6 +250,7 @@
 
     private static final String SYSTEM_SECURE = "ro.secure";
     private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
+    private static final String SYSTEM_HEADLESS = "ro.config.headless";
 
     /**
      * Condition waited on by {@link #reenableKeyguard} to know the call to
@@ -259,6 +260,8 @@
      */
     private boolean mKeyguardDisabled = false;
 
+    private final boolean mHeadless;
+
     private static final int ALLOW_DISABLE_YES = 1;
     private static final int ALLOW_DISABLE_NO = 0;
     private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager
@@ -753,6 +756,7 @@
         mAllowBootMessages = showBootMsgs;
         mLimitedAlphaCompositing = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_sf_limitedAlpha);
+        mHeadless = "1".equals(SystemProperties.get(SYSTEM_HEADLESS, "0"));
 
         mPowerManager = pm;
         mPowerManager.setPolicy(mPolicy);
@@ -4839,7 +4843,7 @@
 
     public void performBootTimeout() {
         synchronized(mWindowMap) {
-            if (mDisplayEnabled) {
+            if (mDisplayEnabled || mHeadless) {
                 return;
             }
             Slog.w(TAG, "***** BOOT TIMEOUT: forcing display enabled");
@@ -5007,6 +5011,8 @@
     // TODO: more accounting of which pid(s) turned it on, keep count,
     // only allow disables from pids which have count on, etc.
     public void showStrictModeViolation(boolean on) {
+        if (mHeadless) return;
+
         int pid = Binder.getCallingPid();
         synchronized(mWindowMap) {
             // Ignoring requests to enable the red border from clients
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_BatteryService.cpp b/services/jni/com_android_server_BatteryService.cpp
index 2ceb535..33027a4 100644
--- a/services/jni/com_android_server_BatteryService.cpp
+++ b/services/jni/com_android_server_BatteryService.cpp
@@ -233,75 +233,75 @@
     DIR* dir = opendir(POWER_SUPPLY_PATH);
     if (dir == NULL) {
         LOGE("Could not open %s\n", POWER_SUPPLY_PATH);
-        return -1;
-    }
-    while ((entry = readdir(dir))) {
-        const char* name = entry->d_name;
+    } else {
+        while ((entry = readdir(dir))) {
+            const char* name = entry->d_name;
 
-        // ignore "." and ".."
-        if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
-            continue;
-        }
-
-        char buf[20];
-        // Look for "type" file in each subdirectory
-        snprintf(path, sizeof(path), "%s/%s/type", POWER_SUPPLY_PATH, name);
-        int length = readFromFile(path, buf, sizeof(buf));
-        if (length > 0) {
-            if (buf[length - 1] == '\n')
-                buf[length - 1] = 0;
-
-            if (strcmp(buf, "Mains") == 0) {
-                snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
-                if (access(path, R_OK) == 0)
-                    gPaths.acOnlinePath = strdup(path);
+            // ignore "." and ".."
+            if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
+                continue;
             }
-            else if (strcmp(buf, "USB") == 0) {
-                snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
-                if (access(path, R_OK) == 0)
-                    gPaths.usbOnlinePath = strdup(path);
-            }
-            else if (strcmp(buf, "Battery") == 0) {
-                snprintf(path, sizeof(path), "%s/%s/status", POWER_SUPPLY_PATH, name);
-                if (access(path, R_OK) == 0)
-                    gPaths.batteryStatusPath = strdup(path);
-                snprintf(path, sizeof(path), "%s/%s/health", POWER_SUPPLY_PATH, name);
-                if (access(path, R_OK) == 0)
-                    gPaths.batteryHealthPath = strdup(path);
-                snprintf(path, sizeof(path), "%s/%s/present", POWER_SUPPLY_PATH, name);
-                if (access(path, R_OK) == 0)
-                    gPaths.batteryPresentPath = strdup(path);
-                snprintf(path, sizeof(path), "%s/%s/capacity", POWER_SUPPLY_PATH, name);
-                if (access(path, R_OK) == 0)
-                    gPaths.batteryCapacityPath = strdup(path);
 
-                snprintf(path, sizeof(path), "%s/%s/voltage_now", POWER_SUPPLY_PATH, name);
-                if (access(path, R_OK) == 0) {
-                    gPaths.batteryVoltagePath = strdup(path);
-                    // voltage_now is in microvolts, not millivolts
-                    gVoltageDivisor = 1000;
-                } else {
-                    snprintf(path, sizeof(path), "%s/%s/batt_vol", POWER_SUPPLY_PATH, name);
+            char buf[20];
+            // Look for "type" file in each subdirectory
+            snprintf(path, sizeof(path), "%s/%s/type", POWER_SUPPLY_PATH, name);
+            int length = readFromFile(path, buf, sizeof(buf));
+            if (length > 0) {
+                if (buf[length - 1] == '\n')
+                    buf[length - 1] = 0;
+
+                if (strcmp(buf, "Mains") == 0) {
+                    snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
                     if (access(path, R_OK) == 0)
+                        gPaths.acOnlinePath = strdup(path);
+                }
+                else if (strcmp(buf, "USB") == 0) {
+                    snprintf(path, sizeof(path), "%s/%s/online", POWER_SUPPLY_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        gPaths.usbOnlinePath = strdup(path);
+                }
+                else if (strcmp(buf, "Battery") == 0) {
+                    snprintf(path, sizeof(path), "%s/%s/status", POWER_SUPPLY_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        gPaths.batteryStatusPath = strdup(path);
+                    snprintf(path, sizeof(path), "%s/%s/health", POWER_SUPPLY_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        gPaths.batteryHealthPath = strdup(path);
+                    snprintf(path, sizeof(path), "%s/%s/present", POWER_SUPPLY_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        gPaths.batteryPresentPath = strdup(path);
+                    snprintf(path, sizeof(path), "%s/%s/capacity", POWER_SUPPLY_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        gPaths.batteryCapacityPath = strdup(path);
+
+                    snprintf(path, sizeof(path), "%s/%s/voltage_now", POWER_SUPPLY_PATH, name);
+                    if (access(path, R_OK) == 0) {
                         gPaths.batteryVoltagePath = strdup(path);
-                }
+                        // voltage_now is in microvolts, not millivolts
+                        gVoltageDivisor = 1000;
+                    } else {
+                        snprintf(path, sizeof(path), "%s/%s/batt_vol", POWER_SUPPLY_PATH, name);
+                        if (access(path, R_OK) == 0)
+                            gPaths.batteryVoltagePath = strdup(path);
+                    }
 
-                snprintf(path, sizeof(path), "%s/%s/temp", POWER_SUPPLY_PATH, name);
-                if (access(path, R_OK) == 0) {
-                    gPaths.batteryTemperaturePath = strdup(path);
-                } else {
-                    snprintf(path, sizeof(path), "%s/%s/batt_temp", POWER_SUPPLY_PATH, name);
-                    if (access(path, R_OK) == 0)
+                    snprintf(path, sizeof(path), "%s/%s/temp", POWER_SUPPLY_PATH, name);
+                    if (access(path, R_OK) == 0) {
                         gPaths.batteryTemperaturePath = strdup(path);
-                }
+                    } else {
+                        snprintf(path, sizeof(path), "%s/%s/batt_temp", POWER_SUPPLY_PATH, name);
+                        if (access(path, R_OK) == 0)
+                            gPaths.batteryTemperaturePath = strdup(path);
+                    }
 
-                snprintf(path, sizeof(path), "%s/%s/technology", POWER_SUPPLY_PATH, name);
-                if (access(path, R_OK) == 0)
-                    gPaths.batteryTechnologyPath = strdup(path);
+                    snprintf(path, sizeof(path), "%s/%s/technology", POWER_SUPPLY_PATH, name);
+                    if (access(path, R_OK) == 0)
+                        gPaths.batteryTechnologyPath = strdup(path);
+                }
             }
         }
+        closedir(dir);
     }
-    closedir(dir);
 
     if (!gPaths.acOnlinePath)
         LOGE("acOnlinePath not found");
diff --git a/services/jni/com_android_server_SerialService.cpp b/services/jni/com_android_server_SerialService.cpp
new file mode 100644
index 0000000..4bb7e36
--- /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) {
+        LOGE("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) {
+        LOGE("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 4178039..0a93525 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 @@
     LOG_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);
diff --git a/tests/SerialChat/Android.mk b/tests/SerialChat/Android.mk
new file mode 100644
index 0000000..a534e1a
--- /dev/null
+++ b/tests/SerialChat/Android.mk
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := SerialChat
+
+include $(BUILD_PACKAGE)
diff --git a/tests/SerialChat/AndroidManifest.xml b/tests/SerialChat/AndroidManifest.xml
new file mode 100644
index 0000000..0efdb58
--- /dev/null
+++ b/tests/SerialChat/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.serialchat">
+
+    <uses-permission android:name="android.permission.SERIAL_PORT"/>
+
+    <application android:label="Serial Chat">
+        <activity android:name="SerialChat" android:label="Serial Chat">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/SerialChat/res/layout/serial_chat.xml b/tests/SerialChat/res/layout/serial_chat.xml
new file mode 100644
index 0000000..596ecbf
--- /dev/null
+++ b/tests/SerialChat/res/layout/serial_chat.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    >
+
+    <ScrollView android:id="@+id/scroll"
+        android:layout_width="match_parent"
+        android:layout_height="0px"
+        android:layout_weight="1"
+        >
+        <TextView android:id="@+id/log"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="25dp"
+            android:textSize="12sp"
+            android:textColor="#ffffffff"
+            />
+    </ScrollView>
+
+    <EditText android:id="@+id/message"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:capitalize="sentences"
+        android:autoText="true"
+        android:singleLine="true"
+        />
+
+</LinearLayout>
+
+
diff --git a/tests/SerialChat/src/com/android/serialchat/SerialChat.java b/tests/SerialChat/src/com/android/serialchat/SerialChat.java
new file mode 100644
index 0000000..faec312
--- /dev/null
+++ b/tests/SerialChat/src/com/android/serialchat/SerialChat.java
@@ -0,0 +1,163 @@
+/*
+ * 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 com.android.serialchat;
+
+import android.app.Activity;
+import android.content.Context;
+import android.hardware.SerialManager;
+import android.hardware.SerialPort;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.util.Log;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.nio.ByteBuffer;
+import java.io.IOException;
+
+public class SerialChat extends Activity implements Runnable, TextView.OnEditorActionListener {
+
+    private static final String TAG = "SerialChat";
+
+    private TextView mLog;
+    private EditText mEditText;
+    private ByteBuffer mInputBuffer;
+    private ByteBuffer mOutputBuffer;
+    private SerialManager mSerialManager;
+    private SerialPort mSerialPort;
+    private boolean mPermissionRequestPending;
+
+    private static final int MESSAGE_LOG = 1;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mSerialManager = (SerialManager)getSystemService(Context.SERIAL_SERVICE);
+        setContentView(R.layout.serial_chat);
+        mLog = (TextView)findViewById(R.id.log);
+        mEditText = (EditText)findViewById(R.id.message);
+        mEditText.setOnEditorActionListener(this);
+
+        if (false) {
+            mInputBuffer = ByteBuffer.allocateDirect(1024);
+            mOutputBuffer = ByteBuffer.allocateDirect(1024);
+        } else {
+            mInputBuffer = ByteBuffer.allocate(1024);
+            mOutputBuffer = ByteBuffer.allocate(1024);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        String[] ports = mSerialManager.getSerialPorts();
+        if (ports != null && ports.length > 0) {
+            try {
+                mSerialPort = mSerialManager.openSerialPort(ports[0], 115200);
+                if (mSerialPort != null) {
+                    new Thread(this).start();
+                }
+            } catch (IOException e) {
+            }
+        }
+
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mSerialPort != null) {
+            try {
+                mSerialPort.close();
+            } catch (IOException e) {
+            }
+            mSerialPort = null;
+        }
+        super.onDestroy();
+    }
+
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (/* actionId == EditorInfo.IME_ACTION_DONE && */ mSerialPort != null) {
+            try {
+                String text = v.getText().toString();
+                Log.d(TAG, "write: " + text);
+                byte[] bytes = text.getBytes();
+                mOutputBuffer.clear();
+                mOutputBuffer.put(bytes);
+                mSerialPort.write(mOutputBuffer, bytes.length);
+            } catch (IOException e) {
+                Log.e(TAG, "write failed", e);
+            }
+            v.setText("");
+            return true;
+        }
+        Log.d(TAG, "onEditorAction " + actionId + " event: " + event);
+        return false;
+    }
+
+    public void run() {
+        Log.d(TAG, "run");
+        int ret = 0;
+        byte[] buffer = new byte[1024];
+        while (ret >= 0) {
+            try {
+                Log.d(TAG, "calling read");
+                mInputBuffer.clear();
+                ret = mSerialPort.read(mInputBuffer);
+                Log.d(TAG, "read returned " + ret);
+                mInputBuffer.get(buffer, 0, ret);
+            } catch (IOException e) {
+                Log.e(TAG, "read failed", e);
+                break;
+            }
+
+            if (ret > 0) {
+                Message m = Message.obtain(mHandler, MESSAGE_LOG);
+                String text = new String(buffer, 0, ret);
+                Log.d(TAG, "chat: " + text);
+                m.obj = text;
+                mHandler.sendMessage(m);
+            }
+        }
+        Log.d(TAG, "thread out");
+    }
+
+   Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_LOG:
+                    mLog.setText(mLog.getText() + (String)msg.obj);
+                    break;
+             }
+        }
+    };
+}
+
+
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index 3d6537a..f0c215e 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -1019,6 +1019,11 @@
               (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
               | ResTable_config::UI_MODE_TYPE_TELEVISION;
         return true;
+    } else if (strcmp(name, "appliance") == 0) {
+      if (out) out->uiMode =
+              (out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
+              | ResTable_config::UI_MODE_TYPE_APPLIANCE;
+        return true;
     }
 
     return false;
diff --git a/tools/aidl/AST.cpp b/tools/aidl/AST.cpp
index 752ef7c..bfa6765 100755
--- a/tools/aidl/AST.cpp
+++ b/tools/aidl/AST.cpp
@@ -111,6 +111,21 @@
     fprintf(to, "%s", this->value.c_str());
 }
 
+StringLiteralExpression::StringLiteralExpression(const string& v)
+    :value(v)
+{
+}
+
+StringLiteralExpression::~StringLiteralExpression()
+{
+}
+
+void
+StringLiteralExpression::Write(FILE* to)
+{
+    fprintf(to, "\"%s\"", this->value.c_str());
+}
+
 Variable::Variable()
     :type(NULL),
      name(),
@@ -277,6 +292,17 @@
 {
 }
 
+MethodCall::MethodCall(const string& n, int argc = 0, ...)
+    :obj(NULL),
+     clazz(NULL),
+     name(n)
+{
+  va_list args;
+  va_start(args, argc);
+  init(argc, args);
+  va_end(args);
+}
+
 MethodCall::MethodCall(Expression* o, const string& n)
     :obj(o),
      clazz(NULL),
@@ -367,11 +393,29 @@
 {
 }
 
+NewExpression::NewExpression(Type* t, int argc = 0, ...)
+    :type(t)
+{
+  va_list args;
+  va_start(args, argc);
+  init(argc, args);
+  va_end(args);
+}
+
 NewExpression::~NewExpression()
 {
 }
 
 void
+NewExpression::init(int n, va_list args)
+{
+    for (int i=0; i<n; i++) {
+        Expression* expression = (Expression*)va_arg(args, void*);
+        this->arguments.push_back(expression);
+    }
+}
+
+void
 NewExpression::Write(FILE* to)
 {
     fprintf(to, "new %s(", this->type->InstantiableName().c_str());
@@ -636,6 +680,20 @@
     fprintf(to, "}\n");
 }
 
+Break::Break()
+{
+}
+
+Break::~Break()
+{
+}
+
+void
+Break::Write(FILE* to)
+{
+    fprintf(to, "break;\n");
+}
+
 Method::Method()
     :ClassElement(),
      modifiers(0),
@@ -678,7 +736,7 @@
         fprintf(to, "%s\n", this->comment.c_str());
     }
 
-    WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | FINAL | OVERRIDE);
+    WriteModifiers(to, this->modifiers, SCOPE_MASK | STATIC | ABSTRACT | FINAL | OVERRIDE);
 
     if (this->returnType != NULL) {
         string dim;
diff --git a/tools/aidl/AST.h b/tools/aidl/AST.h
index 3156356..ead5e7a 100755
--- a/tools/aidl/AST.h
+++ b/tools/aidl/AST.h
@@ -54,6 +54,16 @@
     virtual void Write(FILE* to);
 };
 
+// TODO: also escape the contents.  not needed for now
+struct StringLiteralExpression : public Expression
+{
+    string value;
+
+    StringLiteralExpression(const string& value);
+    virtual ~StringLiteralExpression();
+    virtual void Write(FILE* to);
+};
+
 struct Variable : public Expression
 {
     Type* type;
@@ -104,7 +114,7 @@
     virtual void Write(FILE* to) = 0;
 };
 
-struct StatementBlock
+struct StatementBlock : public Statement
 {
     vector<Statement*> statements;
 
@@ -146,6 +156,7 @@
     vector<string> exceptions;
 
     MethodCall(const string& name);
+    MethodCall(const string& name, int argc, ...);
     MethodCall(Expression* obj, const string& name);
     MethodCall(Type* clazz, const string& name);
     MethodCall(Expression* obj, const string& name, int argc, ...);
@@ -174,8 +185,12 @@
     vector<Expression*> arguments;
 
     NewExpression(Type* type);
+    NewExpression(Type* type, int argc, ...);
     virtual ~NewExpression();
     virtual void Write(FILE* to);
+
+private:
+    void init(int n, va_list args);
 };
 
 struct NewArrayExpression : public Expression
@@ -292,6 +307,13 @@
     virtual void Write(FILE* to);
 };
 
+struct Break : public Statement
+{
+    Break();
+    virtual ~Break();
+    virtual void Write(FILE* to);
+};
+
 struct Method : public ClassElement
 {
     string comment;
diff --git a/tools/aidl/Android.mk b/tools/aidl/Android.mk
index 2ad0728..77d46ab 100644
--- a/tools/aidl/Android.mk
+++ b/tools/aidl/Android.mk
@@ -17,7 +17,9 @@
 	search_path.cpp \
 	AST.cpp \
 	Type.cpp \
-	generate_java.cpp
+	generate_java.cpp \
+	generate_java_binder.cpp \
+	generate_java_rpc.cpp
 
 LOCAL_CFLAGS := -g
 LOCAL_MODULE := aidl
diff --git a/tools/aidl/Type.cpp b/tools/aidl/Type.cpp
index 6b69864..700d6ef 100755
--- a/tools/aidl/Type.cpp
+++ b/tools/aidl/Type.cpp
@@ -11,6 +11,7 @@
 Type* FLOAT_TYPE;
 Type* DOUBLE_TYPE;
 Type* STRING_TYPE;
+Type* OBJECT_TYPE;
 Type* CHAR_SEQUENCE_TYPE;
 Type* TEXT_UTILS_TYPE;
 Type* REMOTE_EXCEPTION_TYPE;
@@ -21,9 +22,13 @@
 Type* BINDER_PROXY_TYPE;
 Type* PARCEL_TYPE;
 Type* PARCELABLE_INTERFACE_TYPE;
+Type* CONTEXT_TYPE;
 Type* MAP_TYPE;
 Type* LIST_TYPE;
 Type* CLASSLOADER_TYPE;
+Type* RPC_DATA_TYPE;
+Type* RPC_ERROR_TYPE;
+Type* EVENT_FAKE_TYPE;
 
 Expression* NULL_VALUE;
 Expression* THIS_VALUE;
@@ -34,38 +39,48 @@
 void
 register_base_types()
 {
-    VOID_TYPE = new BasicType("void", "XXX", "XXX", "XXX", "XXX", "XXX");
+    VOID_TYPE = new BasicType("void",
+            "XXX", "XXX", "XXX", "XXX", "XXX",
+            "XXX", "XXX", "XXX", "XXX", "XXX");
     NAMES.Add(VOID_TYPE);
 
     BOOLEAN_TYPE = new BooleanType();
     NAMES.Add(BOOLEAN_TYPE);
 
-    BYTE_TYPE = new BasicType("byte", "writeByte", "readByte",
-                "writeByteArray", "createByteArray", "readByteArray");
+    BYTE_TYPE = new BasicType("byte",
+            "writeByte", "readByte", "writeByteArray", "createByteArray", "readByteArray",
+            "putByte", "getByte", "putByteArray", "createByteArray", "getByteArray");
     NAMES.Add(BYTE_TYPE);
 
     CHAR_TYPE = new CharType();
     NAMES.Add(CHAR_TYPE);
 
-    INT_TYPE = new BasicType("int", "writeInt", "readInt",
-                "writeIntArray", "createIntArray", "readIntArray");
+    INT_TYPE = new BasicType("int",
+            "writeInt", "readInt", "writeIntArray", "createIntArray", "readIntArray",
+            "putInteger", "getInteger", "putIntegerArray", "createIntegerArray", "getIntegerArray");
     NAMES.Add(INT_TYPE);
 
-    LONG_TYPE = new BasicType("long", "writeLong", "readLong",
-                "writeLongArray", "createLongArray", "readLongArray");
+    LONG_TYPE = new BasicType("long",
+            "writeLong", "readLong", "writeLongArray", "createLongArray", "readLongArray",
+            "putLong", "getLong", "putLongArray", "createLongArray", "getLongArray");
     NAMES.Add(LONG_TYPE);
 
-    FLOAT_TYPE = new BasicType("float", "writeFloat", "readFloat",
-                "writeFloatArray", "createFloatArray", "readFloatArray");
+    FLOAT_TYPE = new BasicType("float",
+            "writeFloat", "readFloat", "writeFloatArray", "createFloatArray", "readFloatArray",
+            "putFloat", "getFloat", "putFloatArray", "createFloatArray", "getFloatArray");
     NAMES.Add(FLOAT_TYPE);
 
-    DOUBLE_TYPE = new BasicType("double", "writeDouble", "readDouble",
-                "writeDoubleArray", "createDoubleArray", "readDoubleArray");
+    DOUBLE_TYPE = new BasicType("double",
+            "writeDouble", "readDouble", "writeDoubleArray", "createDoubleArray", "readDoubleArray",
+            "putDouble", "getDouble", "putDoubleArray", "createDoubleArray", "getDoubleArray");
     NAMES.Add(DOUBLE_TYPE);
 
     STRING_TYPE = new StringType();
     NAMES.Add(STRING_TYPE);
 
+    OBJECT_TYPE = new Type("java.lang", "Object", Type::BUILT_IN, false, false, false);
+    NAMES.Add(OBJECT_TYPE);
+
     CHAR_SEQUENCE_TYPE = new CharSequenceType();
     NAMES.Add(CHAR_SEQUENCE_TYPE);
 
@@ -75,8 +90,7 @@
     LIST_TYPE = new ListType();
     NAMES.Add(LIST_TYPE);
 
-    TEXT_UTILS_TYPE = new Type("android.text", "TextUtils",
-                                    Type::BUILT_IN, false, false);
+    TEXT_UTILS_TYPE = new Type("android.text", "TextUtils", Type::BUILT_IN, false, false, false);
     NAMES.Add(TEXT_UTILS_TYPE);
 
     REMOTE_EXCEPTION_TYPE = new RemoteExceptionType();
@@ -103,6 +117,19 @@
     PARCELABLE_INTERFACE_TYPE = new ParcelableInterfaceType();
     NAMES.Add(PARCELABLE_INTERFACE_TYPE);
 
+    CONTEXT_TYPE = new Type("android.content", "Context", Type::BUILT_IN, false, false, false);
+    NAMES.Add(CONTEXT_TYPE);
+
+    RPC_DATA_TYPE = new RpcDataType();
+    NAMES.Add(RPC_DATA_TYPE);
+
+    RPC_ERROR_TYPE = new UserDataType("android.support.place.rpc", "RpcError",
+                                    true, __FILE__, __LINE__);
+    NAMES.Add(RPC_ERROR_TYPE);
+
+    EVENT_FAKE_TYPE = new Type("event", Type::BUILT_IN, false, false, false);
+    NAMES.Add(EVENT_FAKE_TYPE);
+
     CLASSLOADER_TYPE = new ClassLoaderType();
     NAMES.Add(CLASSLOADER_TYPE);
 
@@ -129,27 +156,30 @@
 
 // ================================================================
 
-Type::Type(const string& name, int kind, bool canWriteToParcel, bool canBeOut)
+Type::Type(const string& name, int kind, bool canWriteToParcel, bool canWriteToRpcData,
+        bool canBeOut)
     :m_package(),
      m_name(name),
      m_declFile(""),
      m_declLine(-1),
      m_kind(kind),
      m_canWriteToParcel(canWriteToParcel),
+     m_canWriteToRpcData(canWriteToRpcData),
      m_canBeOut(canBeOut)
 {
     m_qualifiedName = name;
 }
 
 Type::Type(const string& package, const string& name,
-            int kind, bool canWriteToParcel, bool canBeOut,
-            const string& declFile, int declLine)
+            int kind, bool canWriteToParcel, bool canWriteToRpcData,
+            bool canBeOut, const string& declFile, int declLine)
     :m_package(package),
      m_name(name),
      m_declFile(declFile),
      m_declLine(declLine),
      m_kind(kind),
      m_canWriteToParcel(canWriteToParcel),
+     m_canWriteToRpcData(canWriteToRpcData),
      m_canBeOut(canBeOut)
 {
     if (package.length() > 0) {
@@ -182,6 +212,12 @@
 }
 
 string
+Type::RpcCreatorName() const
+{
+    return "";
+}
+
+string
 Type::InstantiableName() const
 {
     return QualifiedName();
@@ -244,6 +280,26 @@
 }
 
 void
+Type::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, int flags)
+{
+    fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+            __FILE__, __LINE__, m_qualifiedName.c_str());
+    addTo->Add(new LiteralExpression("/* WriteToRpcData error "
+                + m_qualifiedName + " */"));
+}
+
+void
+Type::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+        Variable** cl)
+{
+    fprintf(stderr, "aidl:internal error %s:%d qualifiedName=%s\n",
+            __FILE__, __LINE__, m_qualifiedName.c_str());
+    addTo->Add(new LiteralExpression("/* ReadFromRpcData error "
+                + m_qualifiedName + " */"));
+}
+
+void
 Type::SetQualifiedName(const string& qualified)
 {
     m_qualifiedName = qualified;
@@ -264,29 +320,35 @@
 
 // ================================================================
 
-BasicType::BasicType(const string& name, const string& marshallMethod,
-                     const string& unmarshallMethod,
-                     const string& writeArray, const string& createArray,
-                     const string& readArray)
-    :Type(name, BUILT_IN, true, false),
-     m_marshallMethod(marshallMethod),
-     m_unmarshallMethod(unmarshallMethod),
-     m_writeArrayMethod(writeArray),
-     m_createArrayMethod(createArray),
-     m_readArrayMethod(readArray)
+BasicType::BasicType(const string& name, const string& marshallParcel,
+          const string& unmarshallParcel, const string& writeArrayParcel,
+          const string& createArrayParcel, const string& readArrayParcel,
+          const string& marshallRpc, const string& unmarshallRpc,
+          const string& writeArrayRpc, const string& createArrayRpc, const string& readArrayRpc)
+    :Type(name, BUILT_IN, true, true, false),
+     m_marshallParcel(marshallParcel),
+     m_unmarshallParcel(unmarshallParcel),
+     m_writeArrayParcel(writeArrayParcel),
+     m_createArrayParcel(createArrayParcel),
+     m_readArrayParcel(readArrayParcel),
+     m_marshallRpc(marshallRpc),
+     m_unmarshallRpc(unmarshallRpc),
+     m_writeArrayRpc(writeArrayRpc),
+     m_createArrayRpc(createArrayRpc),
+     m_readArrayRpc(readArrayRpc)
 {
 }
 
 void
 BasicType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
 {
-    addTo->Add(new MethodCall(parcel, m_marshallMethod, 1, v));
+    addTo->Add(new MethodCall(parcel, m_marshallParcel, 1, v));
 }
 
 void
 BasicType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
 {
-    addTo->Add(new Assignment(v, new MethodCall(parcel, m_unmarshallMethod)));
+    addTo->Add(new Assignment(v, new MethodCall(parcel, m_unmarshallParcel)));
 }
 
 bool
@@ -298,27 +360,40 @@
 void
 BasicType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
 {
-    addTo->Add(new MethodCall(parcel, m_writeArrayMethod, 1, v));
+    addTo->Add(new MethodCall(parcel, m_writeArrayParcel, 1, v));
 }
 
 void
 BasicType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
                             Variable* parcel, Variable**)
 {
-    addTo->Add(new Assignment(v, new MethodCall(parcel, m_createArrayMethod)));
+    addTo->Add(new Assignment(v, new MethodCall(parcel, m_createArrayParcel)));
 }
 
 void
 BasicType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
 {
-    addTo->Add(new MethodCall(parcel, m_readArrayMethod, 1, v));
+    addTo->Add(new MethodCall(parcel, m_readArrayParcel, 1, v));
 }
 
+void
+BasicType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, int flags)
+{
+    addTo->Add(new MethodCall(data, m_marshallRpc, 2, k, v));
+}
+
+void
+BasicType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+        Variable** cl)
+{
+    addTo->Add(new Assignment(v, new MethodCall(data, m_unmarshallRpc, 1, k)));
+}
 
 // ================================================================
 
 BooleanType::BooleanType()
-    :Type("boolean", BUILT_IN, true, false)
+    :Type("boolean", BUILT_IN, true, true, false)
 {
 }
 
@@ -362,11 +437,24 @@
     addTo->Add(new MethodCall(parcel, "readBooleanArray", 1, v));
 }
 
+void
+BooleanType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, int flags)
+{
+    addTo->Add(new MethodCall(data, "putBoolean", 2, k, v));
+}
+
+void
+BooleanType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+        Variable** cl)
+{
+    addTo->Add(new Assignment(v, new MethodCall(data, "getBoolean", 1, k)));
+}
 
 // ================================================================
 
 CharType::CharType()
-    :Type("char", BUILT_IN, true, false)
+    :Type("char", BUILT_IN, true, true, false)
 {
 }
 
@@ -408,10 +496,24 @@
     addTo->Add(new MethodCall(parcel, "readCharArray", 1, v));
 }
 
+void
+CharType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, int flags)
+{
+    addTo->Add(new MethodCall(data, "putChar", 2, k, v));
+}
+
+void
+CharType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+        Variable** cl)
+{
+    addTo->Add(new Assignment(v, new MethodCall(data, "getChar", 1, k)));
+}
+
 // ================================================================
 
 StringType::StringType()
-    :Type("java.lang", "String", BUILT_IN, true, false)
+    :Type("java.lang", "String", BUILT_IN, true, true, false)
 {
 }
 
@@ -458,10 +560,24 @@
     addTo->Add(new MethodCall(parcel, "readStringArray", 1, v));
 }
 
+void
+StringType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, int flags)
+{
+    addTo->Add(new MethodCall(data, "putString", 2, k, v));
+}
+
+void
+StringType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, Variable**)
+{
+    addTo->Add(new Assignment(v, new MethodCall(data, "getString", 1, k)));
+}
+
 // ================================================================
 
 CharSequenceType::CharSequenceType()
-    :Type("java.lang", "CharSequence", BUILT_IN, true, false)
+    :Type("java.lang", "CharSequence", BUILT_IN, true, true, false)
 {
 }
 
@@ -521,7 +637,7 @@
 // ================================================================
 
 RemoteExceptionType::RemoteExceptionType()
-    :Type("android.os", "RemoteException", BUILT_IN, false, false)
+    :Type("android.os", "RemoteException", BUILT_IN, false, false, false)
 {
 }
 
@@ -540,7 +656,7 @@
 // ================================================================
 
 RuntimeExceptionType::RuntimeExceptionType()
-    :Type("java.lang", "RuntimeException", BUILT_IN, false, false)
+    :Type("java.lang", "RuntimeException", BUILT_IN, false, false, false)
 {
 }
 
@@ -560,7 +676,7 @@
 // ================================================================
 
 IBinderType::IBinderType()
-    :Type("android.os", "IBinder", BUILT_IN, true, false)
+    :Type("android.os", "IBinder", BUILT_IN, true, false, false)
 {
 }
 
@@ -599,7 +715,7 @@
 // ================================================================
 
 IInterfaceType::IInterfaceType()
-    :Type("android.os", "IInterface", BUILT_IN, false, false)
+    :Type("android.os", "IInterface", BUILT_IN, false, false, false)
 {
 }
 
@@ -619,7 +735,7 @@
 // ================================================================
 
 BinderType::BinderType()
-    :Type("android.os", "Binder", BUILT_IN, false, false)
+    :Type("android.os", "Binder", BUILT_IN, false, false, false)
 {
 }
 
@@ -640,7 +756,7 @@
 // ================================================================
 
 BinderProxyType::BinderProxyType()
-    :Type("android.os", "BinderProxy", BUILT_IN, false, false)
+    :Type("android.os", "BinderProxy", BUILT_IN, false, false, false)
 {
 }
 
@@ -661,7 +777,7 @@
 // ================================================================
 
 ParcelType::ParcelType()
-    :Type("android.os", "Parcel", BUILT_IN, false, false)
+    :Type("android.os", "Parcel", BUILT_IN, false, false, false)
 {
 }
 
@@ -680,7 +796,7 @@
 // ================================================================
 
 ParcelableInterfaceType::ParcelableInterfaceType()
-    :Type("android.os", "Parcelable", BUILT_IN, false, false)
+    :Type("android.os", "Parcelable", BUILT_IN, false, false, false)
 {
 }
 
@@ -699,7 +815,7 @@
 // ================================================================
 
 MapType::MapType()
-    :Type("java.util", "Map", BUILT_IN, true, true)
+    :Type("java.util", "Map", BUILT_IN, true, false, true)
 {
 }
 
@@ -729,8 +845,7 @@
 }
 
 void
-MapType::ReadFromParcel(StatementBlock* addTo, Variable* v,
-                    Variable* parcel, Variable** cl)
+MapType::ReadFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable** cl)
 {
     EnsureClassLoader(addTo, cl);
     addTo->Add(new MethodCall(parcel, "readMap", 2, v, *cl));
@@ -740,7 +855,7 @@
 // ================================================================
 
 ListType::ListType()
-    :Type("java.util", "List", BUILT_IN, true, true)
+    :Type("java.util", "List", BUILT_IN, true, true, true)
 {
 }
 
@@ -771,24 +886,45 @@
     addTo->Add(new MethodCall(parcel, "readList", 2, v, *cl));
 }
 
+void
+ListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, int flags)
+{
+    addTo->Add(new MethodCall(data, "putList", 2, k, v));
+}
+
+void
+ListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+        Variable** cl)
+{
+    addTo->Add(new Assignment(v, new MethodCall(data, "getList", 1, k)));
+}
 
 // ================================================================
 
-ParcelableType::ParcelableType(const string& package, const string& name,
-                        bool builtIn, const string& declFile, int declLine)
-    :Type(package, name, builtIn ? BUILT_IN : PARCELABLE, true, true,
-            declFile, declLine)
+UserDataType::UserDataType(const string& package, const string& name,
+                        bool builtIn, bool canWriteToParcel, bool canWriteToRpcData,
+                        const string& declFile, int declLine)
+    //:Type(package, name, builtIn ? BUILT_IN : USERDATA, canWriteToParcel, canWriteToRpcData,
+    :Type(package, name, builtIn ? BUILT_IN : USERDATA, true, true,
+            true, declFile, declLine)
 {
 }
 
 string
-ParcelableType::CreatorName() const
+UserDataType::CreatorName() const
 {
     return QualifiedName() + ".CREATOR";
 }
 
+string
+UserDataType::RpcCreatorName() const
+{
+    return QualifiedName() + ".RPC_CREATOR";
+}
+
 void
-ParcelableType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+UserDataType::WriteToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
 {
     // if (v != null) {
     //     parcel.writeInt(1);
@@ -811,7 +947,7 @@
 }
 
 void
-ParcelableType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+UserDataType::CreateFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
 {
     // if (0 != parcel.readInt()) {
     //     v = CLASS.CREATOR.createFromParcel(parcel)
@@ -832,7 +968,7 @@
 }
 
 void
-ParcelableType::ReadFromParcel(StatementBlock* addTo, Variable* v,
+UserDataType::ReadFromParcel(StatementBlock* addTo, Variable* v,
                     Variable* parcel, Variable**)
 {
     // TODO: really, we don't need to have this extra check, but we
@@ -848,20 +984,20 @@
 }
 
 bool
-ParcelableType::CanBeArray() const
+UserDataType::CanBeArray() const
 {
     return true;
 }
 
 void
-ParcelableType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
+UserDataType::WriteArrayToParcel(StatementBlock* addTo, Variable* v, Variable* parcel, int flags)
 {
     addTo->Add(new MethodCall(parcel, "writeTypedArray", 2, v,
                 BuildWriteToParcelFlags(flags)));
 }
 
 void
-ParcelableType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
+UserDataType::CreateArrayFromParcel(StatementBlock* addTo, Variable* v,
                             Variable* parcel, Variable**)
 {
     string creator = v->type->QualifiedName() + ".CREATOR";
@@ -870,20 +1006,36 @@
 }
 
 void
-ParcelableType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
+UserDataType::ReadArrayFromParcel(StatementBlock* addTo, Variable* v, Variable* parcel, Variable**)
 {
     string creator = v->type->QualifiedName() + ".CREATOR";
     addTo->Add(new MethodCall(parcel, "readTypedArray", 2,
                     v, new LiteralExpression(creator)));
 }
 
+void
+UserDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags)
+{
+    // data.putFlattenable(k, v);
+    addTo->Add(new MethodCall(data, "putFlattenable", 2, k, v));
+}
+
+void
+UserDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl)
+{
+    // data.getFlattenable(k, CLASS.RPC_CREATOR);
+    addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenable", 2, k,
+                new FieldVariable(v->type, "RPC_CREATOR"))));
+}
 
 // ================================================================
 
 InterfaceType::InterfaceType(const string& package, const string& name,
                         bool builtIn, bool oneway,
                         const string& declFile, int declLine)
-    :Type(package, name, builtIn ? BUILT_IN : INTERFACE, true, false,
+    :Type(package, name, builtIn ? BUILT_IN : INTERFACE, true, false, false,
                         declFile, declLine)
     ,m_oneway(oneway)
 {
@@ -922,7 +1074,7 @@
 
 GenericType::GenericType(const string& package, const string& name,
                          const vector<Type*>& args)
-    :Type(package, name, BUILT_IN, true, true)
+    :Type(package, name, BUILT_IN, true, true, true)
 {
     m_args = args;
 
@@ -942,6 +1094,12 @@
     SetQualifiedName(m_importName + gen);
 }
 
+const vector<Type*>&
+GenericType::GenericArgumentTypes() const
+{
+    return m_args;
+}
+
 string
 GenericType::GenericArguments() const
 {
@@ -1041,10 +1199,65 @@
     }
 }
 
+void
+GenericListType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, int flags)
+{
+    Type* generic = GenericArgumentTypes()[0];
+    if (generic == RPC_DATA_TYPE) {
+        addTo->Add(new MethodCall(data, "putRpcDataList", 2, k, v));
+    } else if (generic->RpcCreatorName() != "") {
+        addTo->Add(new MethodCall(data, "putFlattenableList", 2, k, v));
+    } else {
+        addTo->Add(new MethodCall(data, "putList", 2, k, v));
+    }
+}
+
+void
+GenericListType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, Variable** cl)
+{
+    Type* generic = GenericArgumentTypes()[0];
+    if (generic == RPC_DATA_TYPE) {
+        addTo->Add(new Assignment(v, new MethodCall(data, "getRpcDataList", 2, k)));
+    } else if (generic->RpcCreatorName() != "") {
+        addTo->Add(new Assignment(v, new MethodCall(data, "getFlattenableList", 2, k, 
+                        new LiteralExpression(generic->RpcCreatorName()))));
+    } else {
+        string classArg = GenericArgumentTypes()[0]->QualifiedName();
+        classArg += ".class";
+        addTo->Add(new Assignment(v, new MethodCall(data, "getList", 2, k,
+                        new LiteralExpression(classArg))));
+    }
+}
+
+
+// ================================================================
+
+RpcDataType::RpcDataType()
+    :UserDataType("android.support.place.rpc", "RpcData", true, true, true)
+{
+}
+
+void
+RpcDataType::WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data, int flags)
+{
+    addTo->Add(new MethodCall(data, "putRpcData", 2, k, v));
+}
+
+void
+RpcDataType::CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v, Variable* data,
+        Variable** cl)
+{
+    addTo->Add(new Assignment(v, new MethodCall(data, "getRpcData", 1, k)));
+}
+
+
 // ================================================================
 
 ClassLoaderType::ClassLoaderType()
-    :Type("java.lang", "ClassLoader", BUILT_IN, false, false)
+    :Type("java.lang", "ClassLoader", BUILT_IN, false, false, false)
 {
 }
 
diff --git a/tools/aidl/Type.h b/tools/aidl/Type.h
index 662e3a2..ae12720 100755
--- a/tools/aidl/Type.h
+++ b/tools/aidl/Type.h
@@ -13,7 +13,7 @@
     // kinds
     enum {
         BUILT_IN,
-        PARCELABLE,
+        USERDATA,
         INTERFACE,
         GENERATED
     };
@@ -24,9 +24,9 @@
     };
 
                     Type(const string& name, int kind, bool canWriteToParcel,
-                            bool canBeOut);
+                            bool canWriteToRpcData, bool canBeOut);
                     Type(const string& package, const string& name,
-                            int kind, bool canWriteToParcel, bool canBeOut,
+                            int kind, bool canWriteToParcel, bool canWriteToRpcData, bool canBeOut,
                             const string& declFile = "", int declLine = -1);
     virtual         ~Type();
 
@@ -36,11 +36,13 @@
     inline int      Kind() const                { return m_kind; }
     inline string   DeclFile() const            { return m_declFile; }
     inline int      DeclLine() const            { return m_declLine; }
-    inline bool     CanBeMarshalled() const     { return m_canWriteToParcel; }
+    inline bool     CanWriteToParcel() const    { return m_canWriteToParcel; }
+    inline bool     CanWriteToRpcData() const   { return m_canWriteToRpcData; }
     inline bool     CanBeOutParameter() const   { return m_canBeOut; }
     
     virtual string  ImportType() const;
     virtual string  CreatorName() const;
+    virtual string  RpcCreatorName() const;
     virtual string  InstantiableName() const;
 
     virtual void    WriteToParcel(StatementBlock* addTo, Variable* v,
@@ -59,6 +61,11 @@
     virtual void    ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, Variable** cl);
 
+    virtual void    WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags);
+    virtual void    CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl);
+
 protected:
     void SetQualifiedName(const string& qualified);
     Expression* BuildWriteToParcelFlags(int flags);
@@ -74,17 +81,24 @@
     int m_declLine;
     int m_kind;
     bool m_canWriteToParcel;
+    bool m_canWriteToRpcData;
     bool m_canBeOut;
 };
 
 class BasicType : public Type
 {
 public:
-                    BasicType(const string& name, const string& marshallMethod,
-                              const string& unmarshallMethod,
-                              const string& writeArray,
-                              const string& createArray,
-                              const string& readArray);
+                    BasicType(const string& name,
+                              const string& marshallParcel,
+                              const string& unmarshallParcel,
+                              const string& writeArrayParcel,
+                              const string& createArrayParcel,
+                              const string& readArrayParcel,
+                              const string& marshallRpc,
+                              const string& unmarshallRpc,
+                              const string& writeArrayRpc,
+                              const string& createArrayRpc,
+                              const string& readArrayRpc);
 
     virtual void    WriteToParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, int flags);
@@ -100,12 +114,22 @@
     virtual void    ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, Variable** cl);
 
+    virtual void    WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags);
+    virtual void    CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl);
+
 private:
-    string m_marshallMethod;
-    string m_unmarshallMethod;
-    string m_writeArrayMethod;
-    string m_createArrayMethod;
-    string m_readArrayMethod;
+    string m_marshallParcel;
+    string m_unmarshallParcel;
+    string m_writeArrayParcel;
+    string m_createArrayParcel;
+    string m_readArrayParcel;
+    string m_marshallRpc;
+    string m_unmarshallRpc;
+    string m_writeArrayRpc;
+    string m_createArrayRpc;
+    string m_readArrayRpc;
 };
 
 class BooleanType : public Type
@@ -126,6 +150,11 @@
                                     Variable* parcel, Variable** cl);
     virtual void    ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, Variable** cl);
+
+    virtual void    WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags);
+    virtual void    CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl);
 };
 
 class CharType : public Type
@@ -146,6 +175,11 @@
                                     Variable* parcel, Variable** cl);
     virtual void    ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, Variable** cl);
+
+    virtual void    WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags);
+    virtual void    CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl);
 };
 
 
@@ -169,6 +203,11 @@
                                     Variable* parcel, Variable** cl);
     virtual void    ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, Variable** cl);
+
+    virtual void    WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags);
+    virtual void    CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl);
 };
 
 class CharSequenceType : public Type
@@ -305,15 +344,22 @@
                                     Variable* parcel, Variable** cl);
     virtual void    ReadFromParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, Variable** cl);
+
+    virtual void    WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags);
+    virtual void    CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl);
 };
 
-class ParcelableType : public Type
+class UserDataType : public Type
 {
 public:
-                    ParcelableType(const string& package, const string& name,
-                            bool builtIn, const string& declFile, int declLine);
+                    UserDataType(const string& package, const string& name,
+                            bool builtIn, bool canWriteToParcel, bool canWriteToRpcData,
+                            const string& declFile = "", int declLine = -1);
 
     virtual string  CreatorName() const;
+    virtual string  RpcCreatorName() const;
 
     virtual void    WriteToParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, int flags);
@@ -330,6 +376,11 @@
                                     Variable* parcel, Variable** cl);
     virtual void    ReadArrayFromParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, Variable** cl);
+
+    virtual void    WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags);
+    virtual void    CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl);
 };
 
 class InterfaceType : public Type
@@ -357,6 +408,7 @@
                     GenericType(const string& package, const string& name,
                                  const vector<Type*>& args);
 
+    const vector<Type*>& GenericArgumentTypes() const;
     string          GenericArguments() const;
 
     virtual string  ImportType() const;
@@ -374,6 +426,22 @@
     vector<Type*> m_args;
 };
 
+class RpcDataType : public UserDataType
+{
+public:
+                    RpcDataType();
+
+    virtual void    WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags);
+    virtual void    CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl);
+};
+
+class ClassLoaderType : public Type
+{
+public:
+                    ClassLoaderType();
+};
 
 class GenericListType : public GenericType
 {
@@ -391,16 +459,15 @@
     virtual void    ReadFromParcel(StatementBlock* addTo, Variable* v,
                                     Variable* parcel, Variable** cl);
 
+    virtual void    WriteToRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, int flags);
+    virtual void    CreateFromRpcData(StatementBlock* addTo, Expression* k, Variable* v,
+                                    Variable* data, Variable** cl);
+    
 private:
     string m_creator;
 };
 
-class ClassLoaderType : public Type
-{
-public:
-                    ClassLoaderType();
-};
-
 class Namespace
 {
 public:
@@ -438,11 +505,13 @@
 
 extern Type* VOID_TYPE;
 extern Type* BOOLEAN_TYPE;
+extern Type* BYTE_TYPE;
 extern Type* CHAR_TYPE;
 extern Type* INT_TYPE;
 extern Type* LONG_TYPE;
 extern Type* FLOAT_TYPE;
 extern Type* DOUBLE_TYPE;
+extern Type* OBJECT_TYPE;
 extern Type* STRING_TYPE;
 extern Type* CHAR_SEQUENCE_TYPE;
 extern Type* TEXT_UTILS_TYPE;
@@ -455,6 +524,13 @@
 extern Type* PARCEL_TYPE;
 extern Type* PARCELABLE_INTERFACE_TYPE;
 
+extern Type* CONTEXT_TYPE;
+
+extern Type* RPC_DATA_TYPE;
+extern Type* RPC_ERROR_TYPE;
+extern Type* RPC_CONTEXT_TYPE;
+extern Type* EVENT_FAKE_TYPE;
+
 extern Expression* NULL_VALUE;
 extern Expression* THIS_VALUE;
 extern Expression* SUPER_VALUE;
diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp
index fb4067a..3d314db 100644
--- a/tools/aidl/aidl.cpp
+++ b/tools/aidl/aidl.cpp
@@ -29,7 +29,7 @@
 test_document(document_item_type* d)
 {
     while (d) {
-        if (d->item_type == INTERFACE_TYPE) {
+        if (d->item_type == INTERFACE_TYPE_BINDER) {
             interface_type* c = (interface_type*)d;
             printf("interface %s %s {\n", c->package, c->name.data);
             interface_item_type *q = (interface_item_type*)c->interface_items;
@@ -50,9 +50,14 @@
             }
             printf("}\n");
         }
-        else if (d->item_type == PARCELABLE_TYPE) {
-            parcelable_type* b = (parcelable_type*)d;
-            printf("parcelable %s %s;\n", b->package, b->name.data);
+        else if (d->item_type == USER_DATA_TYPE) {
+            user_data_type* b = (user_data_type*)d;
+            if ((b->flattening_methods & PARCELABLE_DATA) != 0) {
+                printf("parcelable %s %s;\n", b->package, b->name.data);
+            }
+            if ((b->flattening_methods & RPC_DATA) != 0) {
+                printf("flattenable %s %s;\n", b->package, b->name.data);
+            }
         }
         else {
             printf("UNKNOWN d=0x%08lx d->item_type=%d\n", (long)d, d->item_type);
@@ -238,11 +243,12 @@
 {
     int err = 0;
     while (items) {
-        if (items->item_type == PARCELABLE_TYPE) {
-            parcelable_type* p = (parcelable_type*)items;
+        if (items->item_type == USER_DATA_TYPE) {
+            user_data_type* p = (user_data_type*)items;
             err |= check_filename(filename, p->package, &p->name);
         }
-        else if (items->item_type == INTERFACE_TYPE) {
+        else if (items->item_type == INTERFACE_TYPE_BINDER
+                || items->item_type == INTERFACE_TYPE_RPC) {
             interface_type* c = (interface_type*)items;
             err |= check_filename(filename, c->package, &c->name);
         }
@@ -264,8 +270,8 @@
     {
         case Type::INTERFACE:
             return "an interface";
-        case Type::PARCELABLE:
-            return "a parcelable";
+        case Type::USERDATA:
+            return "a user data";
         default:
             return "ERROR";
     }
@@ -290,12 +296,14 @@
     int err = 0;
     while (items) {
         Type* type;
-        if (items->item_type == PARCELABLE_TYPE) {
-            parcelable_type* p = (parcelable_type*)items;
-            type = new ParcelableType(p->package ? p->package : "",
-                            p->name.data, false, filename, p->name.lineno);
+        if (items->item_type == USER_DATA_TYPE) {
+            user_data_type* p = (user_data_type*)items;
+            type = new UserDataType(p->package ? p->package : "", p->name.data,
+                    false, ((p->flattening_methods & PARCELABLE_DATA) != 0),
+                    ((p->flattening_methods & RPC_DATA) != 0), filename, p->name.lineno);
         }
-        else if (items->item_type == INTERFACE_TYPE) {
+        else if (items->item_type == INTERFACE_TYPE_BINDER
+                || items->item_type == INTERFACE_TYPE_RPC) {
             interface_type* c = (interface_type*)items;
             type = new InterfaceType(c->package ? c->package : "",
                             c->name.data, false, c->oneway,
@@ -310,7 +318,7 @@
         if (old == NULL) {
             NAMES.Add(type);
 
-            if (items->item_type == INTERFACE_TYPE) {
+            if (items->item_type == INTERFACE_TYPE_BINDER) {
                 // for interfaces, also add the stub and proxy types, we don't
                 // bother checking these for duplicates, because the parser
                 // won't let us do it.
@@ -319,17 +327,30 @@
                 string name = c->name.data;
                 name += ".Stub";
                 Type* stub = new Type(c->package ? c->package : "",
-                                        name, Type::GENERATED, false, false,
+                                        name, Type::GENERATED, false, false, false,
                                         filename, c->name.lineno);
                 NAMES.Add(stub);
 
                 name = c->name.data;
                 name += ".Stub.Proxy";
                 Type* proxy = new Type(c->package ? c->package : "",
-                                        name, Type::GENERATED, false, false,
+                                        name, Type::GENERATED, false, false, false,
                                         filename, c->name.lineno);
                 NAMES.Add(proxy);
             }
+            else if (items->item_type == INTERFACE_TYPE_RPC) {
+                // for interfaces, also add the service base type, we don't
+                // bother checking these for duplicates, because the parser
+                // won't let us do it.
+                interface_type* c = (interface_type*)items;
+
+                string name = c->name.data;
+                name += ".ServiceBase";
+                Type* base = new Type(c->package ? c->package : "",
+                                        name, Type::GENERATED, false, false, false,
+                                        filename, c->name.lineno);
+                NAMES.Add(base);
+            }
         } else {
             if (old->Kind() == Type::BUILT_IN) {
                 fprintf(stderr, "%s:%d attempt to redefine built in class %s\n",
@@ -381,7 +402,7 @@
 }
 
 static int
-check_method(const char* filename, method_type* m)
+check_method(const char* filename, int kind, method_type* m)
 {
     int err = 0;
 
@@ -394,10 +415,20 @@
         return err;
     }
 
-    if (!returnType->CanBeMarshalled()) {
-        fprintf(stderr, "%s:%d return type %s can't be marshalled.\n", filename,
-                    m->type.type.lineno, m->type.type.data);
-        err = 1;
+    if (returnType == EVENT_FAKE_TYPE) {
+        if (kind != INTERFACE_TYPE_RPC) {
+            fprintf(stderr, "%s:%d event methods only supported for rpc interfaces\n",
+                    filename, m->type.type.lineno);
+            err = 1;
+        }
+    } else {
+        if (!(kind == INTERFACE_TYPE_BINDER ? returnType->CanWriteToParcel()
+                    : returnType->CanWriteToRpcData())) {
+            fprintf(stderr, "%s:%d return type %s can't be marshalled. kind=%d p=%d m=%d\n", filename,
+                        m->type.type.lineno, m->type.type.data, kind,
+                        returnType->CanWriteToParcel(), returnType->CanWriteToRpcData());
+            err = 1;
+        }
     }
 
     if (m->type.dimension > 0 && !returnType->CanBeArray()) {
@@ -429,14 +460,31 @@
             err = 1;
             goto next;
         }
+
+        if (t == EVENT_FAKE_TYPE) {
+            fprintf(stderr, "%s:%d parameter %s (%d) event can not be used as a parameter %s\n",
+                    filename, m->type.type.lineno, arg->name.data, index,
+                    arg->type.type.data);
+            err = 1;
+            goto next;
+        }
         
-        if (!t->CanBeMarshalled()) {
+        if (!(kind == INTERFACE_TYPE_BINDER ? t->CanWriteToParcel() : t->CanWriteToRpcData())) {
             fprintf(stderr, "%s:%d parameter %d: '%s %s' can't be marshalled.\n",
                         filename, m->type.type.lineno, index,
                         arg->type.type.data, arg->name.data);
             err = 1;
         }
 
+        if (returnType == EVENT_FAKE_TYPE
+                && convert_direction(arg->direction.data) != IN_PARAMETER) {
+            fprintf(stderr, "%s:%d parameter %d: '%s %s' All paremeters on events must be 'in'.\n",
+                    filename, m->type.type.lineno, index,
+                    arg->type.type.data, arg->name.data);
+            err = 1;
+            goto next;
+        }
+
         if (arg->direction.data == NULL
                 && (arg->type.dimension != 0 || t->CanBeOutParameter())) {
             fprintf(stderr, "%s:%d parameter %d: '%s %s' can be an out"
@@ -479,7 +527,7 @@
         // check that the name doesn't match a keyword
         if (matches_keyword(arg->name.data)) {
             fprintf(stderr, "%s:%d parameter %d %s is named the same as a"
-                    " Java keyword\n",
+                    " Java or aidl keyword\n",
                     filename, m->name.lineno, index, arg->name.data);
             err = 1;
         }
@@ -497,8 +545,9 @@
 {
     int err = 0;
     while (items) {
-        // (nothing to check for PARCELABLE_TYPE)
-        if (items->item_type == INTERFACE_TYPE) {
+        // (nothing to check for USER_DATA_TYPE)
+        if (items->item_type == INTERFACE_TYPE_BINDER
+                || items->item_type == INTERFACE_TYPE_RPC) {
             map<string,method_type*> methodNames;
             interface_type* c = (interface_type*)items;
 
@@ -507,7 +556,7 @@
                 if (member->item_type == METHOD_TYPE) {
                     method_type* m = (method_type*)member;
 
-                    err |= check_method(filename, m);
+                    err |= check_method(filename, items->item_type, m);
 
                     // prevent duplicate methods
                     if (methodNames.find(m->name.data) == methodNames.end()) {
@@ -544,26 +593,29 @@
     const document_item_type* next = items->next;
     if (items->next != NULL) {
         int lineno = -1;
-        if (next->item_type == INTERFACE_TYPE) {
+        if (next->item_type == INTERFACE_TYPE_BINDER) {
             lineno = ((interface_type*)next)->interface_token.lineno;
         }
-        else if (next->item_type == PARCELABLE_TYPE) {
-            lineno = ((parcelable_type*)next)->parcelable_token.lineno;
+        else if (next->item_type == INTERFACE_TYPE_RPC) {
+            lineno = ((interface_type*)next)->interface_token.lineno;
+        }
+        else if (next->item_type == USER_DATA_TYPE) {
+            lineno = ((user_data_type*)next)->keyword_token.lineno;
         }
         fprintf(stderr, "%s:%d aidl can only handle one interface per file\n",
                             filename, lineno);
         return 1;
     }
 
-    if (items->item_type == PARCELABLE_TYPE) {
+    if (items->item_type == USER_DATA_TYPE) {
         *onlyParcelable = true;
         if (options.failOnParcelable) {
             fprintf(stderr, "%s:%d aidl can only generate code for interfaces, not"
-                            " parcelables,\n", filename,
-                            ((parcelable_type*)items)->parcelable_token.lineno);
-            fprintf(stderr, "%s:%d .aidl files that only declare parcelables "
-                            "don't need to go in the Makefile.\n", filename,
-                            ((parcelable_type*)items)->parcelable_token.lineno);
+                            " parcelables or flattenables,\n", filename,
+                            ((user_data_type*)items)->keyword_token.lineno);
+            fprintf(stderr, "%s:%d .aidl files that only declare parcelables or flattenables"
+                            "may not go in the Makefile.\n", filename,
+                            ((user_data_type*)items)->keyword_token.lineno);
             return 1;
         }
     } else {
@@ -598,7 +650,7 @@
         slash = "";
     }
 
-    if (items->item_type == INTERFACE_TYPE) {
+    if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
         fprintf(to, "%s: \\\n", options.outputFileName.c_str());
     } else {
         // parcelable: there's no output file.
@@ -658,12 +710,12 @@
 generate_outputFileName(const Options& options, const document_item_type* items)
 {
     // items has already been checked to have only one interface.
-    if (items->item_type == INTERFACE_TYPE) {
+    if (items->item_type == INTERFACE_TYPE_BINDER || items->item_type == INTERFACE_TYPE_RPC) {
         interface_type* type = (interface_type*)items;
 
         return generate_outputFileName2(options, type->name, type->package);
-    } else if (items->item_type == PARCELABLE_TYPE) {
-        parcelable_type* type = (parcelable_type*)items;
+    } else if (items->item_type == USER_DATA_TYPE) {
+        user_data_type* type = (user_data_type*)items;
         return generate_outputFileName2(options, type->name, type->package);
     }
 
@@ -734,24 +786,40 @@
         document_item_type* doc;
         
         if (0 == strcmp("parcelable", type)) {
-            parcelable_type* parcl = (parcelable_type*)malloc(
-                    sizeof(parcelable_type));
-            memset(parcl, 0, sizeof(parcelable_type));
-            parcl->document_item.item_type = PARCELABLE_TYPE;
-            parcl->parcelable_token.lineno = lineno;
-            parcl->parcelable_token.data = strdup(type);
+            user_data_type* parcl = (user_data_type*)malloc(
+                    sizeof(user_data_type));
+            memset(parcl, 0, sizeof(user_data_type));
+            parcl->document_item.item_type = USER_DATA_TYPE;
+            parcl->keyword_token.lineno = lineno;
+            parcl->keyword_token.data = strdup(type);
             parcl->package = packagename ? strdup(packagename) : NULL;
             parcl->name.lineno = lineno;
             parcl->name.data = strdup(classname);
             parcl->semicolon_token.lineno = lineno;
             parcl->semicolon_token.data = strdup(";");
+            parcl->flattening_methods = PARCELABLE_DATA;
+            doc = (document_item_type*)parcl;
+        }
+        else if (0 == strcmp("flattenable", type)) {
+            user_data_type* parcl = (user_data_type*)malloc(
+                    sizeof(user_data_type));
+            memset(parcl, 0, sizeof(user_data_type));
+            parcl->document_item.item_type = USER_DATA_TYPE;
+            parcl->keyword_token.lineno = lineno;
+            parcl->keyword_token.data = strdup(type);
+            parcl->package = packagename ? strdup(packagename) : NULL;
+            parcl->name.lineno = lineno;
+            parcl->name.data = strdup(classname);
+            parcl->semicolon_token.lineno = lineno;
+            parcl->semicolon_token.data = strdup(";");
+            parcl->flattening_methods = RPC_DATA;
             doc = (document_item_type*)parcl;
         }
         else if (0 == strcmp("interface", type)) {
             interface_type* iface = (interface_type*)malloc(
                     sizeof(interface_type));
             memset(iface, 0, sizeof(interface_type));
-            iface->document_item.item_type = INTERFACE_TYPE;
+            iface->document_item.item_type = INTERFACE_TYPE_BINDER;
             iface->interface_token.lineno = lineno;
             iface->interface_token.data = strdup(type);
             iface->package = packagename ? strdup(packagename) : NULL;
@@ -923,9 +991,14 @@
         }
         document_item_type* doc = g_document;
         string line;
-        if (doc->item_type == PARCELABLE_TYPE) {
-            line = "parcelable ";
-            parcelable_type* parcelable = (parcelable_type*)doc;
+        if (doc->item_type == USER_DATA_TYPE) {
+            user_data_type* parcelable = (user_data_type*)doc;
+            if ((parcelable->flattening_methods & PARCELABLE_DATA) != 0) {
+                line = "parcelable ";
+            }
+            if ((parcelable->flattening_methods & RPC_DATA) != 0) {
+                line = "flattenable ";
+            }
             if (parcelable->package) {
                 line += parcelable->package;
                 line += '.';
@@ -995,5 +1068,3 @@
     fprintf(stderr, "aidl: internal error\n");
     return 1;
 }
-
-
diff --git a/tools/aidl/aidl_language.h b/tools/aidl/aidl_language.h
index 9ca5deb..f203dbb0 100644
--- a/tools/aidl/aidl_language.h
+++ b/tools/aidl/aidl_language.h
@@ -63,8 +63,9 @@
 } method_type;
 
 enum {
-    PARCELABLE_TYPE = 12,
-    INTERFACE_TYPE
+    USER_DATA_TYPE = 12,
+    INTERFACE_TYPE_BINDER,
+    INTERFACE_TYPE_RPC
 };
 
 typedef struct document_item_type {
@@ -72,13 +73,21 @@
     struct document_item_type* next;
 } document_item_type;
 
-typedef struct parcelable_type {
+
+// for user_data_type.flattening_methods
+enum {
+    PARCELABLE_DATA = 0x1,
+    RPC_DATA = 0x2
+};
+
+typedef struct user_data_type {
     document_item_type document_item;
-    buffer_type parcelable_token;
+    buffer_type keyword_token; // only the first one
     char* package;
     buffer_type name;
     buffer_type semicolon_token;
-} parcelable_type;
+    int flattening_methods;
+} user_data_type;
 
 typedef struct interface_type {
     document_item_type document_item;
@@ -100,7 +109,7 @@
     method_type* method;
     interface_item_type* interface_item;
     interface_type* interface_obj;
-    parcelable_type* parcelable;
+    user_data_type* user_data;
     document_item_type* document_item;
 } lexer_type;
 
diff --git a/tools/aidl/aidl_language_l.l b/tools/aidl/aidl_language_l.l
index 567b1cf..7c5290c 100644
--- a/tools/aidl/aidl_language_l.l
+++ b/tools/aidl/aidl_language_l.l
@@ -81,6 +81,8 @@
     /* keywords */
 parcelable      { SET_BUFFER(PARCELABLE); return PARCELABLE; }
 interface       { SET_BUFFER(INTERFACE); return INTERFACE; }
+flattenable     { SET_BUFFER(FLATTENABLE); return FLATTENABLE; }
+rpc             { SET_BUFFER(INTERFACE); return RPC; }
 in              { SET_BUFFER(IN); return IN; }
 out             { SET_BUFFER(OUT); return OUT; }
 inout           { SET_BUFFER(INOUT); return INOUT; }
diff --git a/tools/aidl/aidl_language_y.y b/tools/aidl/aidl_language_y.y
index 3d65f17..3c16e15 100644
--- a/tools/aidl/aidl_language_y.y
+++ b/tools/aidl/aidl_language_y.y
@@ -19,6 +19,8 @@
 %token ARRAY
 %token PARCELABLE
 %token INTERFACE
+%token FLATTENABLE
+%token RPC
 %token IN
 %token OUT
 %token INOUT
@@ -72,36 +74,61 @@
     ;
 
 declaration:
-        parcelable_decl                            { $$.document_item = (document_item_type*)$1.parcelable; }
+        parcelable_decl                            { $$.document_item = (document_item_type*)$1.user_data; }
     |   interface_decl                             { $$.document_item = (document_item_type*)$1.interface_item; }
     ;
 
 parcelable_decl:
-        PARCELABLE IDENTIFIER ';'                  { 
-                                                        parcelable_type* b = (parcelable_type*)malloc(sizeof(parcelable_type));
-                                                        b->document_item.item_type = PARCELABLE_TYPE;
+        PARCELABLE IDENTIFIER ';'                   {
+                                                        user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type));
+                                                        b->document_item.item_type = USER_DATA_TYPE;
                                                         b->document_item.next = NULL;
-                                                        b->parcelable_token = $1.buffer;
+                                                        b->keyword_token = $1.buffer;
                                                         b->name = $2.buffer;
                                                         b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
                                                         b->semicolon_token = $3.buffer;
-                                                        $$.parcelable = b;
+                                                        b->flattening_methods = PARCELABLE_DATA | RPC_DATA;
+                                                        $$.user_data = b;
                                                     }
     |   PARCELABLE ';'                              {
                                                         fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name.\n",
                                                                      g_currentFilename, $1.buffer.lineno);
-                                                        $$.parcelable = NULL;
+                                                        $$.user_data = NULL;
                                                     }
     |   PARCELABLE error ';'                        {
                                                         fprintf(stderr, "%s:%d syntax error in parcelable declaration. Expected type name, saw \"%s\".\n",
                                                                      g_currentFilename, $2.buffer.lineno, $2.buffer.data);
-                                                        $$.parcelable = NULL;
+                                                        $$.user_data = NULL;
                                                     }
+    |   FLATTENABLE IDENTIFIER ';'                  {
+                                                        user_data_type* b = (user_data_type*)malloc(sizeof(user_data_type));
+                                                        b->document_item.item_type = USER_DATA_TYPE;
+                                                        b->document_item.next = NULL;
+                                                        b->keyword_token = $1.buffer;
+                                                        b->name = $2.buffer;
+                                                        b->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
+                                                        b->semicolon_token = $3.buffer;
+                                                        b->flattening_methods = PARCELABLE_DATA | RPC_DATA;
+                                                        $$.user_data = b;
+                                                    }
+    |   FLATTENABLE ';'                             {
+                                                        fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name.\n",
+                                                                     g_currentFilename, $1.buffer.lineno);
+                                                        $$.user_data = NULL;
+                                                    }
+    |   FLATTENABLE error ';'                       {
+                                                        fprintf(stderr, "%s:%d syntax error in flattenable declaration. Expected type name, saw \"%s\".\n",
+                                                                     g_currentFilename, $2.buffer.lineno, $2.buffer.data);
+                                                        $$.user_data = NULL;
+                                                    }
+
     ;
 
 interface_header:
         INTERFACE                                  {
                                                         interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+                                                        c->document_item.item_type = INTERFACE_TYPE_BINDER;
+                                                        c->document_item.next = NULL;
                                                         c->interface_token = $1.buffer;
                                                         c->oneway = false;
                                                         memset(&c->oneway_token, 0, sizeof(buffer_type));
@@ -110,19 +137,34 @@
                                                    }
     |   ONEWAY INTERFACE                           {
                                                         interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+                                                        c->document_item.item_type = INTERFACE_TYPE_BINDER;
+                                                        c->document_item.next = NULL;
                                                         c->interface_token = $2.buffer;
                                                         c->oneway = true;
                                                         c->oneway_token = $1.buffer;
                                                         c->comments_token = &c->oneway_token;
                                                         $$.interface_obj = c;
                                                    }
+    |   RPC                                        {
+                                                        interface_type* c = (interface_type*)malloc(sizeof(interface_type));
+                                                        c->document_item.item_type = INTERFACE_TYPE_RPC;
+                                                        c->document_item.next = NULL;
+                                                        c->interface_token = $1.buffer;
+                                                        c->oneway = false;
+                                                        memset(&c->oneway_token, 0, sizeof(buffer_type));
+                                                        c->comments_token = &c->interface_token;
+                                                        $$.interface_obj = c;
+                                                   }
+    ;
+
+interface_keywords:
+        INTERFACE
+    |   RPC
     ;
 
 interface_decl:
         interface_header IDENTIFIER '{' interface_items '}' { 
                                                         interface_type* c = $1.interface_obj;
-                                                        c->document_item.item_type = INTERFACE_TYPE;
-                                                        c->document_item.next = NULL;
                                                         c->name = $2.buffer;
                                                         c->package = g_currentPackage ? strdup(g_currentPackage) : NULL;
                                                         c->open_brace_token = $3.buffer;
@@ -130,12 +172,12 @@
                                                         c->close_brace_token = $5.buffer;
                                                         $$.interface_obj = c;
                                                     }
-    |   INTERFACE error '{' interface_items '}'     {
+    |   interface_keywords error '{' interface_items '}'     {
                                                         fprintf(stderr, "%s:%d: syntax error in interface declaration.  Expected type name, saw \"%s\"\n",
                                                                     g_currentFilename, $2.buffer.lineno, $2.buffer.data);
                                                         $$.document_item = NULL;
                                                     }
-    |   INTERFACE error '}'                             {
+    |   interface_keywords error '}'                {
                                                         fprintf(stderr, "%s:%d: syntax error in interface declaration.  Expected type name, saw \"%s\"\n",
                                                                     g_currentFilename, $2.buffer.lineno, $2.buffer.data);
                                                         $$.document_item = NULL;
diff --git a/tools/aidl/generate_java.cpp b/tools/aidl/generate_java.cpp
index 83e3bbc..9e57407 100644
--- a/tools/aidl/generate_java.cpp
+++ b/tools/aidl/generate_java.cpp
@@ -1,5 +1,4 @@
 #include "generate_java.h"
-#include "AST.h"
 #include "Type.h"
 #include <string.h>
 #include <stdio.h>
@@ -7,18 +6,6 @@
 #include <string.h>
 
 // =================================================
-class VariableFactory
-{
-public:
-    VariableFactory(const string& base); // base must be short
-    Variable* Get(Type* type);
-    Variable* Get(int index);
-private:
-    vector<Variable*> m_vars;
-    string m_base;
-    int m_index;
-};
-
 VariableFactory::VariableFactory(const string& base)
     :m_base(base),
      m_index(0)
@@ -43,195 +30,7 @@
 }
 
 // =================================================
-class StubClass : public Class
-{
-public:
-    StubClass(Type* type, Type* interfaceType);
-    virtual ~StubClass();
-
-    Variable* transact_code;
-    Variable* transact_data;
-    Variable* transact_reply;
-    Variable* transact_flags;
-    SwitchStatement* transact_switch;
-private:
-    void make_as_interface(Type* interfaceType);
-};
-
-StubClass::StubClass(Type* type, Type* interfaceType)
-    :Class()
-{
-    this->comment = "/** Local-side IPC implementation stub class. */";
-    this->modifiers = PUBLIC | ABSTRACT | STATIC;
-    this->what = Class::CLASS;
-    this->type = type;
-    this->extends = BINDER_NATIVE_TYPE;
-    this->interfaces.push_back(interfaceType);
-
-    // descriptor
-    Field* descriptor = new Field(STATIC | FINAL | PRIVATE,
-                            new Variable(STRING_TYPE, "DESCRIPTOR"));
-    descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
-    this->elements.push_back(descriptor);
-
-    // ctor
-    Method* ctor = new Method;
-        ctor->modifiers = PUBLIC;
-        ctor->comment = "/** Construct the stub at attach it to the "
-                        "interface. */";
-        ctor->name = "Stub";
-        ctor->statements = new StatementBlock;
-    MethodCall* attach = new MethodCall(THIS_VALUE, "attachInterface",
-                            2, THIS_VALUE, new LiteralExpression("DESCRIPTOR"));
-    ctor->statements->Add(attach);
-    this->elements.push_back(ctor);
-
-    // asInterface
-    make_as_interface(interfaceType);
-
-    // asBinder
-    Method* asBinder = new Method;
-        asBinder->modifiers = PUBLIC;
-        asBinder->returnType = IBINDER_TYPE;
-        asBinder->name = "asBinder";
-        asBinder->statements = new StatementBlock;
-    asBinder->statements->Add(new ReturnStatement(THIS_VALUE));
-    this->elements.push_back(asBinder);
-
-    // onTransact
-    this->transact_code = new Variable(INT_TYPE, "code");
-    this->transact_data = new Variable(PARCEL_TYPE, "data");
-    this->transact_reply = new Variable(PARCEL_TYPE, "reply");
-    this->transact_flags = new Variable(INT_TYPE, "flags");
-    Method* onTransact = new Method;
-        onTransact->modifiers = PUBLIC | OVERRIDE;
-        onTransact->returnType = BOOLEAN_TYPE;
-        onTransact->name = "onTransact";
-        onTransact->parameters.push_back(this->transact_code);
-        onTransact->parameters.push_back(this->transact_data);
-        onTransact->parameters.push_back(this->transact_reply);
-        onTransact->parameters.push_back(this->transact_flags);
-        onTransact->statements = new StatementBlock;
-        onTransact->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
-    this->elements.push_back(onTransact);
-    this->transact_switch = new SwitchStatement(this->transact_code);
-
-    onTransact->statements->Add(this->transact_switch);
-    MethodCall* superCall = new MethodCall(SUPER_VALUE, "onTransact", 4,
-                                    this->transact_code, this->transact_data,
-                                    this->transact_reply, this->transact_flags);
-    onTransact->statements->Add(new ReturnStatement(superCall));
-}
-
-StubClass::~StubClass()
-{
-}
-
-void
-StubClass::make_as_interface(Type *interfaceType)
-{
-    Variable* obj = new Variable(IBINDER_TYPE, "obj");
-
-    Method* m = new Method;
-        m->comment = "/**\n * Cast an IBinder object into an ";
-        m->comment += interfaceType->QualifiedName();
-        m->comment += " interface,\n";
-        m->comment += " * generating a proxy if needed.\n */";
-        m->modifiers = PUBLIC | STATIC;
-        m->returnType = interfaceType;
-        m->name = "asInterface";
-        m->parameters.push_back(obj);
-        m->statements = new StatementBlock;
-
-    IfStatement* ifstatement = new IfStatement();
-        ifstatement->expression = new Comparison(obj, "==", NULL_VALUE);
-        ifstatement->statements = new StatementBlock;
-        ifstatement->statements->Add(new ReturnStatement(NULL_VALUE));
-    m->statements->Add(ifstatement);
-
-    // IInterface iin = obj.queryLocalInterface(DESCRIPTOR)
-    MethodCall* queryLocalInterface = new MethodCall(obj, "queryLocalInterface");
-    queryLocalInterface->arguments.push_back(new LiteralExpression("DESCRIPTOR"));
-    IInterfaceType* iinType = new IInterfaceType();
-    Variable *iin = new Variable(iinType, "iin");
-    VariableDeclaration* iinVd = new VariableDeclaration(iin, queryLocalInterface, iinType);
-    m->statements->Add(iinVd);
-
-    // Ensure the instance type of the local object is as expected.
-    // One scenario where this is needed is if another package (with a
-    // different class loader) runs in the same process as the service.
-
-    // if (iin != null && iin instanceof <interfaceType>) return (<interfaceType>) iin;
-    Comparison* iinNotNull = new Comparison(iin, "!=", NULL_VALUE);
-    Comparison* instOfCheck = new Comparison(iin, " instanceof ",
-            new LiteralExpression(interfaceType->QualifiedName()));
-    IfStatement* instOfStatement = new IfStatement();
-        instOfStatement->expression = new Comparison(iinNotNull, "&&", instOfCheck);
-        instOfStatement->statements = new StatementBlock;
-        instOfStatement->statements->Add(new ReturnStatement(new Cast(interfaceType, iin)));
-    m->statements->Add(instOfStatement);
-
-    string proxyType = interfaceType->QualifiedName();
-    proxyType += ".Stub.Proxy";
-    NewExpression* ne = new NewExpression(NAMES.Find(proxyType));
-    ne->arguments.push_back(obj);
-    m->statements->Add(new ReturnStatement(ne));
-
-    this->elements.push_back(m);
-}
-
-
-
-// =================================================
-class ProxyClass : public Class
-{
-public:
-    ProxyClass(Type* type, InterfaceType* interfaceType);
-    virtual ~ProxyClass();
-
-    Variable* mRemote;
-    bool mOneWay;
-};
-
-ProxyClass::ProxyClass(Type* type, InterfaceType* interfaceType)
-    :Class()
-{
-    this->modifiers = PRIVATE | STATIC;
-    this->what = Class::CLASS;
-    this->type = type;
-    this->interfaces.push_back(interfaceType);
-
-    mOneWay = interfaceType->OneWay();
-
-    // IBinder mRemote
-    mRemote = new Variable(IBINDER_TYPE, "mRemote");
-    this->elements.push_back(new Field(PRIVATE, mRemote));
-
-    // Proxy()
-    Variable* remote = new Variable(IBINDER_TYPE, "remote");
-    Method* ctor = new Method;
-        ctor->name = "Proxy";
-        ctor->statements = new StatementBlock;
-        ctor->parameters.push_back(remote);
-    ctor->statements->Add(new Assignment(mRemote, remote));
-    this->elements.push_back(ctor);
-
-    // IBinder asBinder()
-    Method* asBinder = new Method;
-        asBinder->modifiers = PUBLIC;
-        asBinder->returnType = IBINDER_TYPE;
-        asBinder->name = "asBinder";
-        asBinder->statements = new StatementBlock;
-    asBinder->statements->Add(new ReturnStatement(mRemote));
-    this->elements.push_back(asBinder);
-}
-
-ProxyClass::~ProxyClass()
-{
-}
-
-// =================================================
-static string
+string
 gather_comments(extra_text_type* extra)
 {
     string s;
@@ -249,7 +48,7 @@
     return s;
 }
 
-static string
+string
 append(const char* a, const char* b)
 {
     string s = a;
@@ -257,379 +56,25 @@
     return s;
 }
 
-static void
-generate_new_array(Type* t, StatementBlock* addTo, Variable* v,
-                            Variable* parcel)
-{
-    Variable* len = new Variable(INT_TYPE, v->name + "_length");
-    addTo->Add(new VariableDeclaration(len, new MethodCall(parcel, "readInt")));
-    IfStatement* lencheck = new IfStatement();
-    lencheck->expression = new Comparison(len, "<", new LiteralExpression("0"));
-    lencheck->statements->Add(new Assignment(v, NULL_VALUE));
-    lencheck->elseif = new IfStatement();
-    lencheck->elseif->statements->Add(new Assignment(v,
-                new NewArrayExpression(t, len)));
-    addTo->Add(lencheck);
-}
-
-static void
-generate_write_to_parcel(Type* t, StatementBlock* addTo, Variable* v,
-                            Variable* parcel, int flags)
-{
-    if (v->dimension == 0) {
-        t->WriteToParcel(addTo, v, parcel, flags);
-    }
-    if (v->dimension == 1) {
-        t->WriteArrayToParcel(addTo, v, parcel, flags);
-    }
-}
-
-static void
-generate_create_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
-                            Variable* parcel, Variable** cl)
-{
-    if (v->dimension == 0) {
-        t->CreateFromParcel(addTo, v, parcel, cl);
-    }
-    if (v->dimension == 1) {
-        t->CreateArrayFromParcel(addTo, v, parcel, cl);
-    }
-}
-
-static void
-generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
-                            Variable* parcel, Variable** cl)
-{
-    if (v->dimension == 0) {
-        t->ReadFromParcel(addTo, v, parcel, cl);
-    }
-    if (v->dimension == 1) {
-        t->ReadArrayFromParcel(addTo, v, parcel, cl);
-    }
-}
-
-
-static void
-generate_method(const method_type* method, Class* interface,
-                    StubClass* stubClass, ProxyClass* proxyClass, int index)
-{
-    arg_type* arg;
-    int i;
-    bool hasOutParams = false;
-
-    const bool oneway = proxyClass->mOneWay || method->oneway;
-
-    // == the TRANSACT_ constant =============================================
-    string transactCodeName = "TRANSACTION_";
-    transactCodeName += method->name.data;
-
-    char transactCodeValue[50];
-    sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index);
-
-    Field* transactCode = new Field(STATIC | FINAL,
-                            new Variable(INT_TYPE, transactCodeName));
-    transactCode->value = transactCodeValue;
-    stubClass->elements.push_back(transactCode);
-
-    // == the declaration in the interface ===================================
-    Method* decl = new Method;
-        decl->comment = gather_comments(method->comments_token->extra);
-        decl->modifiers = PUBLIC;
-        decl->returnType = NAMES.Search(method->type.type.data);
-        decl->returnTypeDimension = method->type.dimension;
-        decl->name = method->name.data;
-
-    arg = method->args;
-    while (arg != NULL) {
-        decl->parameters.push_back(new Variable(
-                            NAMES.Search(arg->type.type.data), arg->name.data,
-                            arg->type.dimension));
-        arg = arg->next;
-    }
-
-    decl->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
-
-    interface->elements.push_back(decl);
-
-    // == the stub method ====================================================
-
-    Case* c = new Case(transactCodeName);
-
-    MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data);
-
-    // interface token validation is the very first thing we do
-    c->statements->Add(new MethodCall(stubClass->transact_data,
-            "enforceInterface", 1, new LiteralExpression("DESCRIPTOR")));
-
-    // args
-    Variable* cl = NULL;
-    VariableFactory stubArgs("_arg");
-    arg = method->args;
-    while (arg != NULL) {
-        Type* t = NAMES.Search(arg->type.type.data);
-        Variable* v = stubArgs.Get(t);
-        v->dimension = arg->type.dimension;
-
-        c->statements->Add(new VariableDeclaration(v));
-
-        if (convert_direction(arg->direction.data) & IN_PARAMETER) {
-            generate_create_from_parcel(t, c->statements, v,
-                    stubClass->transact_data, &cl);
-        } else {
-            if (arg->type.dimension == 0) {
-                c->statements->Add(new Assignment(
-                                                v, new NewExpression(v->type)));
-            }
-            else if (arg->type.dimension == 1) {
-                generate_new_array(v->type, c->statements, v,
-                        stubClass->transact_data);
-            }
-            else {
-                fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
-                        __LINE__);
-            }
-        }
-
-        realCall->arguments.push_back(v);
-
-        arg = arg->next;
-    }
-
-    // the real call
-    Variable* _result = NULL;
-    if (0 == strcmp(method->type.type.data, "void")) {
-        c->statements->Add(realCall);
-
-        if (!oneway) {
-            // report that there were no exceptions
-            MethodCall* ex = new MethodCall(stubClass->transact_reply,
-                    "writeNoException", 0);
-            c->statements->Add(ex);
-        }
-    } else {
-        _result = new Variable(decl->returnType, "_result",
-                                decl->returnTypeDimension);
-        c->statements->Add(new VariableDeclaration(_result, realCall));
-
-        if (!oneway) {
-            // report that there were no exceptions
-            MethodCall* ex = new MethodCall(stubClass->transact_reply,
-                    "writeNoException", 0);
-            c->statements->Add(ex);
-        }
-
-        // marshall the return value
-        generate_write_to_parcel(decl->returnType, c->statements, _result,
-                                    stubClass->transact_reply,
-                                    Type::PARCELABLE_WRITE_RETURN_VALUE);
-    }
-
-    // out parameters
-    i = 0;
-    arg = method->args;
-    while (arg != NULL) {
-        Type* t = NAMES.Search(arg->type.type.data);
-        Variable* v = stubArgs.Get(i++);
-
-        if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
-            generate_write_to_parcel(t, c->statements, v,
-                                stubClass->transact_reply,
-                                Type::PARCELABLE_WRITE_RETURN_VALUE);
-            hasOutParams = true;
-        }
-
-        arg = arg->next;
-    }
-
-    // return true
-    c->statements->Add(new ReturnStatement(TRUE_VALUE));
-    stubClass->transact_switch->cases.push_back(c);
-
-    // == the proxy method ===================================================
-    Method* proxy = new Method;
-        proxy->comment = gather_comments(method->comments_token->extra);
-        proxy->modifiers = PUBLIC;
-        proxy->returnType = NAMES.Search(method->type.type.data);
-        proxy->returnTypeDimension = method->type.dimension;
-        proxy->name = method->name.data;
-        proxy->statements = new StatementBlock;
-        arg = method->args;
-        while (arg != NULL) {
-            proxy->parameters.push_back(new Variable(
-                            NAMES.Search(arg->type.type.data), arg->name.data,
-                            arg->type.dimension));
-            arg = arg->next;
-        }
-        proxy->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
-    proxyClass->elements.push_back(proxy);
-
-    // the parcels
-    Variable* _data = new Variable(PARCEL_TYPE, "_data");
-    proxy->statements->Add(new VariableDeclaration(_data,
-                                new MethodCall(PARCEL_TYPE, "obtain")));
-    Variable* _reply = NULL;
-    if (!oneway) {
-        _reply = new Variable(PARCEL_TYPE, "_reply");
-        proxy->statements->Add(new VariableDeclaration(_reply,
-                                    new MethodCall(PARCEL_TYPE, "obtain")));
-    }
-
-    // the return value
-    _result = NULL;
-    if (0 != strcmp(method->type.type.data, "void")) {
-        _result = new Variable(proxy->returnType, "_result",
-                method->type.dimension);
-        proxy->statements->Add(new VariableDeclaration(_result));
-    }
-
-    // try and finally
-    TryStatement* tryStatement = new TryStatement();
-    proxy->statements->Add(tryStatement);
-    FinallyStatement* finallyStatement = new FinallyStatement();
-    proxy->statements->Add(finallyStatement);
-
-    // the interface identifier token: the DESCRIPTOR constant, marshalled as a string
-    tryStatement->statements->Add(new MethodCall(_data, "writeInterfaceToken",
-            1, new LiteralExpression("DESCRIPTOR")));
-
-    // the parameters
-    arg = method->args;
-    while (arg != NULL) {
-        Type* t = NAMES.Search(arg->type.type.data);
-        Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
-        int dir = convert_direction(arg->direction.data);
-        if (dir == OUT_PARAMETER && arg->type.dimension != 0) {
-            IfStatement* checklen = new IfStatement();
-            checklen->expression = new Comparison(v, "==", NULL_VALUE);
-            checklen->statements->Add(new MethodCall(_data, "writeInt", 1,
-                        new LiteralExpression("-1")));
-            checklen->elseif = new IfStatement();
-            checklen->elseif->statements->Add(new MethodCall(_data, "writeInt",
-                        1, new FieldVariable(v, "length")));
-            tryStatement->statements->Add(checklen);
-        }
-        else if (dir & IN_PARAMETER) {
-            generate_write_to_parcel(t, tryStatement->statements, v, _data, 0);
-        }
-        arg = arg->next;
-    }
-
-    // the transact call
-    MethodCall* call = new MethodCall(proxyClass->mRemote, "transact", 4,
-                            new LiteralExpression("Stub." + transactCodeName),
-                            _data, _reply ? _reply : NULL_VALUE,
-                            new LiteralExpression(
-                                oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0"));
-    tryStatement->statements->Add(call);
-
-    // throw back exceptions.
-    if (_reply) {
-        MethodCall* ex = new MethodCall(_reply, "readException", 0);
-        tryStatement->statements->Add(ex);
-    }
-
-    // returning and cleanup
-    if (_reply != NULL) {
-        if (_result != NULL) {
-            generate_create_from_parcel(proxy->returnType,
-                    tryStatement->statements, _result, _reply, &cl);
-        }
-
-        // the out/inout parameters
-        arg = method->args;
-        while (arg != NULL) {
-            Type* t = NAMES.Search(arg->type.type.data);
-            Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
-            if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
-                generate_read_from_parcel(t, tryStatement->statements,
-                                            v, _reply, &cl);
-            }
-            arg = arg->next;
-        }
-
-        finallyStatement->statements->Add(new MethodCall(_reply, "recycle"));
-    }
-    finallyStatement->statements->Add(new MethodCall(_data, "recycle"));
-
-    if (_result != NULL) {
-        proxy->statements->Add(new ReturnStatement(_result));
-    }
-}
-
-static void
-generate_interface_descriptors(StubClass* stub, ProxyClass* proxy)
-{
-    // the interface descriptor transaction handler
-    Case* c = new Case("INTERFACE_TRANSACTION");
-    c->statements->Add(new MethodCall(stub->transact_reply, "writeString",
-            1, new LiteralExpression("DESCRIPTOR")));
-    c->statements->Add(new ReturnStatement(TRUE_VALUE));
-    stub->transact_switch->cases.push_back(c);
-
-    // and the proxy-side method returning the descriptor directly
-    Method* getDesc = new Method;
-    getDesc->modifiers = PUBLIC;
-    getDesc->returnType = STRING_TYPE;
-    getDesc->returnTypeDimension = 0;
-    getDesc->name = "getInterfaceDescriptor";
-    getDesc->statements = new StatementBlock;
-    getDesc->statements->Add(new ReturnStatement(new LiteralExpression("DESCRIPTOR")));
-    proxy->elements.push_back(getDesc);
-}
-
-static Class*
-generate_interface_class(const interface_type* iface)
-{
-    InterfaceType* interfaceType = static_cast<InterfaceType*>(
-        NAMES.Find(iface->package, iface->name.data));
-
-    // the interface class
-    Class* interface = new Class;
-        interface->comment = gather_comments(iface->comments_token->extra);
-        interface->modifiers = PUBLIC;
-        interface->what = Class::INTERFACE;
-        interface->type = interfaceType;
-        interface->interfaces.push_back(IINTERFACE_TYPE);
-
-    // the stub inner class
-    StubClass* stub = new StubClass(
-        NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()),
-        interfaceType);
-    interface->elements.push_back(stub);
-
-    // the proxy inner class
-    ProxyClass* proxy = new ProxyClass(
-        NAMES.Find(iface->package,
-                         append(iface->name.data, ".Stub.Proxy").c_str()),
-        interfaceType);
-    stub->elements.push_back(proxy);
-
-    // stub and proxy support for getInterfaceDescriptor()
-    generate_interface_descriptors(stub, proxy);
-
-    // all the declared methods of the interface
-    int index = 0;
-    interface_item_type* item = iface->interface_items;
-    while (item != NULL) {
-        if (item->item_type == METHOD_TYPE) {
-            generate_method((method_type*)item, interface, stub, proxy, index);
-        }
-        item = item->next;
-        index++;
-    }
-
-    return interface;
-}
-
+// =================================================
 int
 generate_java(const string& filename, const string& originalSrc,
                 interface_type* iface)
 {
+    Class* cl;
+
+    if (iface->document_item.item_type == INTERFACE_TYPE_BINDER) {
+        cl = generate_binder_interface_class(iface);
+    }
+    else if (iface->document_item.item_type == INTERFACE_TYPE_RPC) {
+        cl = generate_rpc_interface_class(iface);
+    }
+
     Document* document = new Document;
         document->comment = "";
         if (iface->package) document->package = iface->package;
         document->originalSrc = originalSrc;
-        document->classes.push_back(generate_interface_class(iface));
+        document->classes.push_back(cl);
 
 //    printf("outputting... filename=%s\n", filename.c_str());
     FILE* to;
diff --git a/tools/aidl/generate_java.h b/tools/aidl/generate_java.h
index 203fe23..4bfcfeb 100644
--- a/tools/aidl/generate_java.h
+++ b/tools/aidl/generate_java.h
@@ -2,6 +2,7 @@
 #define GENERATE_JAVA_H
 
 #include "aidl_language.h"
+#include "AST.h"
 
 #include <string>
 
@@ -10,5 +11,23 @@
 int generate_java(const string& filename, const string& originalSrc,
                 interface_type* iface);
 
+Class* generate_binder_interface_class(const interface_type* iface);
+Class* generate_rpc_interface_class(const interface_type* iface);
+
+string gather_comments(extra_text_type* extra);
+string append(const char* a, const char* b);
+
+class VariableFactory
+{
+public:
+    VariableFactory(const string& base); // base must be short
+    Variable* Get(Type* type);
+    Variable* Get(int index);
+private:
+    vector<Variable*> m_vars;
+    string m_base;
+    int m_index;
+};
+
 #endif // GENERATE_JAVA_H
 
diff --git a/tools/aidl/generate_java_binder.cpp b/tools/aidl/generate_java_binder.cpp
new file mode 100644
index 0000000..2e459a8
--- /dev/null
+++ b/tools/aidl/generate_java_binder.cpp
@@ -0,0 +1,559 @@
+#include "generate_java.h"
+#include "Type.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// =================================================
+class StubClass : public Class
+{
+public:
+    StubClass(Type* type, Type* interfaceType);
+    virtual ~StubClass();
+
+    Variable* transact_code;
+    Variable* transact_data;
+    Variable* transact_reply;
+    Variable* transact_flags;
+    SwitchStatement* transact_switch;
+private:
+    void make_as_interface(Type* interfaceType);
+};
+
+StubClass::StubClass(Type* type, Type* interfaceType)
+    :Class()
+{
+    this->comment = "/** Local-side IPC implementation stub class. */";
+    this->modifiers = PUBLIC | ABSTRACT | STATIC;
+    this->what = Class::CLASS;
+    this->type = type;
+    this->extends = BINDER_NATIVE_TYPE;
+    this->interfaces.push_back(interfaceType);
+
+    // descriptor
+    Field* descriptor = new Field(STATIC | FINAL | PRIVATE,
+                            new Variable(STRING_TYPE, "DESCRIPTOR"));
+    descriptor->value = "\"" + interfaceType->QualifiedName() + "\"";
+    this->elements.push_back(descriptor);
+
+    // ctor
+    Method* ctor = new Method;
+        ctor->modifiers = PUBLIC;
+        ctor->comment = "/** Construct the stub at attach it to the "
+                        "interface. */";
+        ctor->name = "Stub";
+        ctor->statements = new StatementBlock;
+    MethodCall* attach = new MethodCall(THIS_VALUE, "attachInterface",
+                            2, THIS_VALUE, new LiteralExpression("DESCRIPTOR"));
+    ctor->statements->Add(attach);
+    this->elements.push_back(ctor);
+
+    // asInterface
+    make_as_interface(interfaceType);
+
+    // asBinder
+    Method* asBinder = new Method;
+        asBinder->modifiers = PUBLIC;
+        asBinder->returnType = IBINDER_TYPE;
+        asBinder->name = "asBinder";
+        asBinder->statements = new StatementBlock;
+    asBinder->statements->Add(new ReturnStatement(THIS_VALUE));
+    this->elements.push_back(asBinder);
+
+    // onTransact
+    this->transact_code = new Variable(INT_TYPE, "code");
+    this->transact_data = new Variable(PARCEL_TYPE, "data");
+    this->transact_reply = new Variable(PARCEL_TYPE, "reply");
+    this->transact_flags = new Variable(INT_TYPE, "flags");
+    Method* onTransact = new Method;
+        onTransact->modifiers = PUBLIC | OVERRIDE;
+        onTransact->returnType = BOOLEAN_TYPE;
+        onTransact->name = "onTransact";
+        onTransact->parameters.push_back(this->transact_code);
+        onTransact->parameters.push_back(this->transact_data);
+        onTransact->parameters.push_back(this->transact_reply);
+        onTransact->parameters.push_back(this->transact_flags);
+        onTransact->statements = new StatementBlock;
+        onTransact->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+    this->elements.push_back(onTransact);
+    this->transact_switch = new SwitchStatement(this->transact_code);
+
+    onTransact->statements->Add(this->transact_switch);
+    MethodCall* superCall = new MethodCall(SUPER_VALUE, "onTransact", 4,
+                                    this->transact_code, this->transact_data,
+                                    this->transact_reply, this->transact_flags);
+    onTransact->statements->Add(new ReturnStatement(superCall));
+}
+
+StubClass::~StubClass()
+{
+}
+
+void
+StubClass::make_as_interface(Type *interfaceType)
+{
+    Variable* obj = new Variable(IBINDER_TYPE, "obj");
+
+    Method* m = new Method;
+        m->comment = "/**\n * Cast an IBinder object into an ";
+        m->comment += interfaceType->QualifiedName();
+        m->comment += " interface,\n";
+        m->comment += " * generating a proxy if needed.\n */";
+        m->modifiers = PUBLIC | STATIC;
+        m->returnType = interfaceType;
+        m->name = "asInterface";
+        m->parameters.push_back(obj);
+        m->statements = new StatementBlock;
+
+    IfStatement* ifstatement = new IfStatement();
+        ifstatement->expression = new Comparison(obj, "==", NULL_VALUE);
+        ifstatement->statements = new StatementBlock;
+        ifstatement->statements->Add(new ReturnStatement(NULL_VALUE));
+    m->statements->Add(ifstatement);
+
+    // IInterface iin = obj.queryLocalInterface(DESCRIPTOR)
+    MethodCall* queryLocalInterface = new MethodCall(obj, "queryLocalInterface");
+    queryLocalInterface->arguments.push_back(new LiteralExpression("DESCRIPTOR"));
+    IInterfaceType* iinType = new IInterfaceType();
+    Variable *iin = new Variable(iinType, "iin");
+    VariableDeclaration* iinVd = new VariableDeclaration(iin, queryLocalInterface, iinType);
+    m->statements->Add(iinVd);
+
+    // Ensure the instance type of the local object is as expected.
+    // One scenario where this is needed is if another package (with a
+    // different class loader) runs in the same process as the service.
+
+    // if (iin != null && iin instanceof <interfaceType>) return (<interfaceType>) iin;
+    Comparison* iinNotNull = new Comparison(iin, "!=", NULL_VALUE);
+    Comparison* instOfCheck = new Comparison(iin, " instanceof ",
+            new LiteralExpression(interfaceType->QualifiedName()));
+    IfStatement* instOfStatement = new IfStatement();
+        instOfStatement->expression = new Comparison(iinNotNull, "&&", instOfCheck);
+        instOfStatement->statements = new StatementBlock;
+        instOfStatement->statements->Add(new ReturnStatement(new Cast(interfaceType, iin)));
+    m->statements->Add(instOfStatement);
+
+    string proxyType = interfaceType->QualifiedName();
+    proxyType += ".Stub.Proxy";
+    NewExpression* ne = new NewExpression(NAMES.Find(proxyType));
+    ne->arguments.push_back(obj);
+    m->statements->Add(new ReturnStatement(ne));
+
+    this->elements.push_back(m);
+}
+
+
+
+// =================================================
+class ProxyClass : public Class
+{
+public:
+    ProxyClass(Type* type, InterfaceType* interfaceType);
+    virtual ~ProxyClass();
+
+    Variable* mRemote;
+    bool mOneWay;
+};
+
+ProxyClass::ProxyClass(Type* type, InterfaceType* interfaceType)
+    :Class()
+{
+    this->modifiers = PRIVATE | STATIC;
+    this->what = Class::CLASS;
+    this->type = type;
+    this->interfaces.push_back(interfaceType);
+
+    mOneWay = interfaceType->OneWay();
+
+    // IBinder mRemote
+    mRemote = new Variable(IBINDER_TYPE, "mRemote");
+    this->elements.push_back(new Field(PRIVATE, mRemote));
+
+    // Proxy()
+    Variable* remote = new Variable(IBINDER_TYPE, "remote");
+    Method* ctor = new Method;
+        ctor->name = "Proxy";
+        ctor->statements = new StatementBlock;
+        ctor->parameters.push_back(remote);
+    ctor->statements->Add(new Assignment(mRemote, remote));
+    this->elements.push_back(ctor);
+
+    // IBinder asBinder()
+    Method* asBinder = new Method;
+        asBinder->modifiers = PUBLIC;
+        asBinder->returnType = IBINDER_TYPE;
+        asBinder->name = "asBinder";
+        asBinder->statements = new StatementBlock;
+    asBinder->statements->Add(new ReturnStatement(mRemote));
+    this->elements.push_back(asBinder);
+}
+
+ProxyClass::~ProxyClass()
+{
+}
+
+// =================================================
+static void
+generate_new_array(Type* t, StatementBlock* addTo, Variable* v,
+                            Variable* parcel)
+{
+    Variable* len = new Variable(INT_TYPE, v->name + "_length");
+    addTo->Add(new VariableDeclaration(len, new MethodCall(parcel, "readInt")));
+    IfStatement* lencheck = new IfStatement();
+    lencheck->expression = new Comparison(len, "<", new LiteralExpression("0"));
+    lencheck->statements->Add(new Assignment(v, NULL_VALUE));
+    lencheck->elseif = new IfStatement();
+    lencheck->elseif->statements->Add(new Assignment(v,
+                new NewArrayExpression(t, len)));
+    addTo->Add(lencheck);
+}
+
+static void
+generate_write_to_parcel(Type* t, StatementBlock* addTo, Variable* v,
+                            Variable* parcel, int flags)
+{
+    if (v->dimension == 0) {
+        t->WriteToParcel(addTo, v, parcel, flags);
+    }
+    if (v->dimension == 1) {
+        t->WriteArrayToParcel(addTo, v, parcel, flags);
+    }
+}
+
+static void
+generate_create_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
+                            Variable* parcel, Variable** cl)
+{
+    if (v->dimension == 0) {
+        t->CreateFromParcel(addTo, v, parcel, cl);
+    }
+    if (v->dimension == 1) {
+        t->CreateArrayFromParcel(addTo, v, parcel, cl);
+    }
+}
+
+static void
+generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v,
+                            Variable* parcel, Variable** cl)
+{
+    if (v->dimension == 0) {
+        t->ReadFromParcel(addTo, v, parcel, cl);
+    }
+    if (v->dimension == 1) {
+        t->ReadArrayFromParcel(addTo, v, parcel, cl);
+    }
+}
+
+
+static void
+generate_method(const method_type* method, Class* interface,
+                    StubClass* stubClass, ProxyClass* proxyClass, int index)
+{
+    arg_type* arg;
+    int i;
+    bool hasOutParams = false;
+
+    const bool oneway = proxyClass->mOneWay || method->oneway;
+
+    // == the TRANSACT_ constant =============================================
+    string transactCodeName = "TRANSACTION_";
+    transactCodeName += method->name.data;
+
+    char transactCodeValue[50];
+    sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index);
+
+    Field* transactCode = new Field(STATIC | FINAL,
+                            new Variable(INT_TYPE, transactCodeName));
+    transactCode->value = transactCodeValue;
+    stubClass->elements.push_back(transactCode);
+
+    // == the declaration in the interface ===================================
+    Method* decl = new Method;
+        decl->comment = gather_comments(method->comments_token->extra);
+        decl->modifiers = PUBLIC;
+        decl->returnType = NAMES.Search(method->type.type.data);
+        decl->returnTypeDimension = method->type.dimension;
+        decl->name = method->name.data;
+
+    arg = method->args;
+    while (arg != NULL) {
+        decl->parameters.push_back(new Variable(
+                            NAMES.Search(arg->type.type.data), arg->name.data,
+                            arg->type.dimension));
+        arg = arg->next;
+    }
+
+    decl->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+
+    interface->elements.push_back(decl);
+
+    // == the stub method ====================================================
+
+    Case* c = new Case(transactCodeName);
+
+    MethodCall* realCall = new MethodCall(THIS_VALUE, method->name.data);
+
+    // interface token validation is the very first thing we do
+    c->statements->Add(new MethodCall(stubClass->transact_data,
+            "enforceInterface", 1, new LiteralExpression("DESCRIPTOR")));
+
+    // args
+    Variable* cl = NULL;
+    VariableFactory stubArgs("_arg");
+    arg = method->args;
+    while (arg != NULL) {
+        Type* t = NAMES.Search(arg->type.type.data);
+        Variable* v = stubArgs.Get(t);
+        v->dimension = arg->type.dimension;
+
+        c->statements->Add(new VariableDeclaration(v));
+
+        if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+            generate_create_from_parcel(t, c->statements, v,
+                    stubClass->transact_data, &cl);
+        } else {
+            if (arg->type.dimension == 0) {
+                c->statements->Add(new Assignment(v, new NewExpression(v->type)));
+            }
+            else if (arg->type.dimension == 1) {
+                generate_new_array(v->type, c->statements, v,
+                        stubClass->transact_data);
+            }
+            else {
+                fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
+                        __LINE__);
+            }
+        }
+
+        realCall->arguments.push_back(v);
+
+        arg = arg->next;
+    }
+
+    // the real call
+    Variable* _result = NULL;
+    if (0 == strcmp(method->type.type.data, "void")) {
+        c->statements->Add(realCall);
+
+        if (!oneway) {
+            // report that there were no exceptions
+            MethodCall* ex = new MethodCall(stubClass->transact_reply,
+                    "writeNoException", 0);
+            c->statements->Add(ex);
+        }
+    } else {
+        _result = new Variable(decl->returnType, "_result",
+                                decl->returnTypeDimension);
+        c->statements->Add(new VariableDeclaration(_result, realCall));
+
+        if (!oneway) {
+            // report that there were no exceptions
+            MethodCall* ex = new MethodCall(stubClass->transact_reply,
+                    "writeNoException", 0);
+            c->statements->Add(ex);
+        }
+
+        // marshall the return value
+        generate_write_to_parcel(decl->returnType, c->statements, _result,
+                                    stubClass->transact_reply,
+                                    Type::PARCELABLE_WRITE_RETURN_VALUE);
+    }
+
+    // out parameters
+    i = 0;
+    arg = method->args;
+    while (arg != NULL) {
+        Type* t = NAMES.Search(arg->type.type.data);
+        Variable* v = stubArgs.Get(i++);
+
+        if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+            generate_write_to_parcel(t, c->statements, v,
+                                stubClass->transact_reply,
+                                Type::PARCELABLE_WRITE_RETURN_VALUE);
+            hasOutParams = true;
+        }
+
+        arg = arg->next;
+    }
+
+    // return true
+    c->statements->Add(new ReturnStatement(TRUE_VALUE));
+    stubClass->transact_switch->cases.push_back(c);
+
+    // == the proxy method ===================================================
+    Method* proxy = new Method;
+        proxy->comment = gather_comments(method->comments_token->extra);
+        proxy->modifiers = PUBLIC;
+        proxy->returnType = NAMES.Search(method->type.type.data);
+        proxy->returnTypeDimension = method->type.dimension;
+        proxy->name = method->name.data;
+        proxy->statements = new StatementBlock;
+        arg = method->args;
+        while (arg != NULL) {
+            proxy->parameters.push_back(new Variable(
+                            NAMES.Search(arg->type.type.data), arg->name.data,
+                            arg->type.dimension));
+            arg = arg->next;
+        }
+        proxy->exceptions.push_back(REMOTE_EXCEPTION_TYPE);
+    proxyClass->elements.push_back(proxy);
+
+    // the parcels
+    Variable* _data = new Variable(PARCEL_TYPE, "_data");
+    proxy->statements->Add(new VariableDeclaration(_data,
+                                new MethodCall(PARCEL_TYPE, "obtain")));
+    Variable* _reply = NULL;
+    if (!oneway) {
+        _reply = new Variable(PARCEL_TYPE, "_reply");
+        proxy->statements->Add(new VariableDeclaration(_reply,
+                                    new MethodCall(PARCEL_TYPE, "obtain")));
+    }
+
+    // the return value
+    _result = NULL;
+    if (0 != strcmp(method->type.type.data, "void")) {
+        _result = new Variable(proxy->returnType, "_result",
+                method->type.dimension);
+        proxy->statements->Add(new VariableDeclaration(_result));
+    }
+
+    // try and finally
+    TryStatement* tryStatement = new TryStatement();
+    proxy->statements->Add(tryStatement);
+    FinallyStatement* finallyStatement = new FinallyStatement();
+    proxy->statements->Add(finallyStatement);
+
+    // the interface identifier token: the DESCRIPTOR constant, marshalled as a string
+    tryStatement->statements->Add(new MethodCall(_data, "writeInterfaceToken",
+            1, new LiteralExpression("DESCRIPTOR")));
+
+    // the parameters
+    arg = method->args;
+    while (arg != NULL) {
+        Type* t = NAMES.Search(arg->type.type.data);
+        Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+        int dir = convert_direction(arg->direction.data);
+        if (dir == OUT_PARAMETER && arg->type.dimension != 0) {
+            IfStatement* checklen = new IfStatement();
+            checklen->expression = new Comparison(v, "==", NULL_VALUE);
+            checklen->statements->Add(new MethodCall(_data, "writeInt", 1,
+                        new LiteralExpression("-1")));
+            checklen->elseif = new IfStatement();
+            checklen->elseif->statements->Add(new MethodCall(_data, "writeInt",
+                        1, new FieldVariable(v, "length")));
+            tryStatement->statements->Add(checklen);
+        }
+        else if (dir & IN_PARAMETER) {
+            generate_write_to_parcel(t, tryStatement->statements, v, _data, 0);
+        }
+        arg = arg->next;
+    }
+
+    // the transact call
+    MethodCall* call = new MethodCall(proxyClass->mRemote, "transact", 4,
+                            new LiteralExpression("Stub." + transactCodeName),
+                            _data, _reply ? _reply : NULL_VALUE,
+                            new LiteralExpression(
+                                oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0"));
+    tryStatement->statements->Add(call);
+
+    // throw back exceptions.
+    if (_reply) {
+        MethodCall* ex = new MethodCall(_reply, "readException", 0);
+        tryStatement->statements->Add(ex);
+    }
+
+    // returning and cleanup
+    if (_reply != NULL) {
+        if (_result != NULL) {
+            generate_create_from_parcel(proxy->returnType,
+                    tryStatement->statements, _result, _reply, &cl);
+        }
+
+        // the out/inout parameters
+        arg = method->args;
+        while (arg != NULL) {
+            Type* t = NAMES.Search(arg->type.type.data);
+            Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+            if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+                generate_read_from_parcel(t, tryStatement->statements,
+                                            v, _reply, &cl);
+            }
+            arg = arg->next;
+        }
+
+        finallyStatement->statements->Add(new MethodCall(_reply, "recycle"));
+    }
+    finallyStatement->statements->Add(new MethodCall(_data, "recycle"));
+
+    if (_result != NULL) {
+        proxy->statements->Add(new ReturnStatement(_result));
+    }
+}
+
+static void
+generate_interface_descriptors(StubClass* stub, ProxyClass* proxy)
+{
+    // the interface descriptor transaction handler
+    Case* c = new Case("INTERFACE_TRANSACTION");
+    c->statements->Add(new MethodCall(stub->transact_reply, "writeString",
+            1, new LiteralExpression("DESCRIPTOR")));
+    c->statements->Add(new ReturnStatement(TRUE_VALUE));
+    stub->transact_switch->cases.push_back(c);
+
+    // and the proxy-side method returning the descriptor directly
+    Method* getDesc = new Method;
+    getDesc->modifiers = PUBLIC;
+    getDesc->returnType = STRING_TYPE;
+    getDesc->returnTypeDimension = 0;
+    getDesc->name = "getInterfaceDescriptor";
+    getDesc->statements = new StatementBlock;
+    getDesc->statements->Add(new ReturnStatement(new LiteralExpression("DESCRIPTOR")));
+    proxy->elements.push_back(getDesc);
+}
+
+Class*
+generate_binder_interface_class(const interface_type* iface)
+{
+    InterfaceType* interfaceType = static_cast<InterfaceType*>(
+        NAMES.Find(iface->package, iface->name.data));
+
+    // the interface class
+    Class* interface = new Class;
+        interface->comment = gather_comments(iface->comments_token->extra);
+        interface->modifiers = PUBLIC;
+        interface->what = Class::INTERFACE;
+        interface->type = interfaceType;
+        interface->interfaces.push_back(IINTERFACE_TYPE);
+
+    // the stub inner class
+    StubClass* stub = new StubClass(
+        NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()),
+        interfaceType);
+    interface->elements.push_back(stub);
+
+    // the proxy inner class
+    ProxyClass* proxy = new ProxyClass(
+        NAMES.Find(iface->package,
+                         append(iface->name.data, ".Stub.Proxy").c_str()),
+        interfaceType);
+    stub->elements.push_back(proxy);
+
+    // stub and proxy support for getInterfaceDescriptor()
+    generate_interface_descriptors(stub, proxy);
+
+    // all the declared methods of the interface
+    int index = 0;
+    interface_item_type* item = iface->interface_items;
+    while (item != NULL) {
+        if (item->item_type == METHOD_TYPE) {
+            generate_method((method_type*)item, interface, stub, proxy, index);
+        }
+        item = item->next;
+        index++;
+    }
+
+    return interface;
+}
+
diff --git a/tools/aidl/generate_java_rpc.cpp b/tools/aidl/generate_java_rpc.cpp
new file mode 100644
index 0000000..2b50b76
--- /dev/null
+++ b/tools/aidl/generate_java_rpc.cpp
@@ -0,0 +1,996 @@
+#include "generate_java.h"
+#include "Type.h"
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+Type* ANDROID_CONTEXT_TYPE = new Type("android.content",
+        "Context", Type::BUILT_IN, false, false, false);
+Type* PRESENTER_BASE_TYPE = new Type("android.support.place.connector",
+        "EventListener", Type::BUILT_IN, false, false, false);
+Type* PRESENTER_LISTENER_BASE_TYPE = new Type("android.support.place.connector",
+        "EventListener.Listener", Type::BUILT_IN, false, false, false);
+Type* RPC_BROKER_TYPE = new Type("android.support.place.connector", "Broker",
+        Type::BUILT_IN, false, false, false);
+Type* PLACE_INFO_TYPE = new Type("android.support.place.connector", "PlaceInfo",
+        Type::BUILT_IN, false, false, false);
+// TODO: Just use Endpoint, so this works for all endpoints.
+Type* RPC_CONNECTOR_TYPE = new Type("android.support.place.connector", "Connector",
+        Type::BUILT_IN, false, false, false);
+Type* RPC_ENDPOINT_INFO_TYPE = new UserDataType("android.support.place.rpc",
+        "EndpointInfo", true, __FILE__, __LINE__);
+Type* RPC_RESULT_HANDLER_TYPE = new UserDataType("android.support.place.rpc", "RpcResultHandler",
+        true, __FILE__, __LINE__);
+Type* RPC_ERROR_LISTENER_TYPE = new Type("android.support.place.rpc", "RpcErrorHandler",
+        Type::BUILT_IN, false, false, false);
+Type* RPC_CONTEXT_TYPE = new UserDataType("android.support.place.rpc", "RpcContext", true,
+        __FILE__, __LINE__);
+
+static void generate_create_from_data(Type* t, StatementBlock* addTo, const string& key,
+        Variable* v, Variable* data, Variable** cl);
+static void generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from);
+static void generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v,
+        Variable* data);
+
+static string
+format_int(int n)
+{
+    char str[20];
+    sprintf(str, "%d", n);
+    return string(str);
+}
+
+static string
+class_name_leaf(const string& str)
+{
+    string::size_type pos = str.rfind('.');
+    if (pos == string::npos) {
+        return str;
+    } else {
+        return string(str, pos+1);
+    }
+}
+
+static string
+results_class_name(const string& n)
+{
+    string str = n;
+    str[0] = toupper(str[0]);
+    str.insert(0, "On");
+    return str;
+}
+
+static string
+results_method_name(const string& n)
+{
+    string str = n;
+    str[0] = toupper(str[0]);
+    str.insert(0, "on");
+    return str;
+}
+
+static string
+push_method_name(const string& n)
+{
+    string str = n;
+    str[0] = toupper(str[0]);
+    str.insert(0, "push");
+    return str;
+}
+
+// =================================================
+class DispatcherClass : public Class
+{
+public:
+    DispatcherClass(const interface_type* iface, Expression* target);
+    virtual ~DispatcherClass();
+
+    void AddMethod(const method_type* method);
+    void DoneWithMethods();
+
+    Method* processMethod;
+    Variable* actionParam;
+    Variable* requestParam;
+    Variable* rpcContextParam;
+    Variable* errorParam;
+    Variable* requestData;
+    Variable* resultData;
+    IfStatement* dispatchIfStatement;
+    Expression* targetExpression;
+
+private:
+    void generate_process();
+};
+
+DispatcherClass::DispatcherClass(const interface_type* iface, Expression* target)
+    :Class(),
+     dispatchIfStatement(NULL),
+     targetExpression(target)
+{
+    generate_process();
+}
+
+DispatcherClass::~DispatcherClass()
+{
+}
+
+void
+DispatcherClass::generate_process()
+{
+    // byte[] process(String action, byte[] params, RpcContext context, RpcError status)
+    this->processMethod = new Method;
+        this->processMethod->modifiers = PUBLIC;
+        this->processMethod->returnType = BYTE_TYPE;
+        this->processMethod->returnTypeDimension = 1;
+        this->processMethod->name = "process";
+        this->processMethod->statements = new StatementBlock;
+
+    this->actionParam = new Variable(STRING_TYPE, "action");
+    this->processMethod->parameters.push_back(this->actionParam);
+
+    this->requestParam = new Variable(BYTE_TYPE, "requestParam", 1);
+    this->processMethod->parameters.push_back(this->requestParam);
+
+    this->rpcContextParam = new Variable(RPC_CONTEXT_TYPE, "context", 0);
+    this->processMethod->parameters.push_back(this->rpcContextParam);    
+
+    this->errorParam = new Variable(RPC_ERROR_TYPE, "errorParam", 0);
+    this->processMethod->parameters.push_back(this->errorParam);
+
+    this->requestData = new Variable(RPC_DATA_TYPE, "request");
+    this->processMethod->statements->Add(new VariableDeclaration(requestData,
+                new NewExpression(RPC_DATA_TYPE, 1, this->requestParam)));
+
+    this->resultData = new Variable(RPC_DATA_TYPE, "resultData");
+    this->processMethod->statements->Add(new VariableDeclaration(this->resultData,
+                NULL_VALUE));
+}
+
+void
+DispatcherClass::AddMethod(const method_type* method)
+{
+    arg_type* arg;
+
+    // The if/switch statement
+    IfStatement* ifs = new IfStatement();
+        ifs->expression = new MethodCall(new StringLiteralExpression(method->name.data), "equals",
+                1, this->actionParam);
+    StatementBlock* block = ifs->statements = new StatementBlock;
+    if (this->dispatchIfStatement == NULL) {
+        this->dispatchIfStatement = ifs;
+        this->processMethod->statements->Add(dispatchIfStatement);
+    } else {
+        this->dispatchIfStatement->elseif = ifs;
+        this->dispatchIfStatement = ifs;
+    }
+    
+    // The call to decl (from above)
+    MethodCall* realCall = new MethodCall(this->targetExpression, method->name.data);
+
+    // args
+    Variable* classLoader = NULL;
+    VariableFactory stubArgs("_arg");
+    arg = method->args;
+    while (arg != NULL) {
+        Type* t = NAMES.Search(arg->type.type.data);
+        Variable* v = stubArgs.Get(t);
+        v->dimension = arg->type.dimension;
+
+        // Unmarshall the parameter
+        block->Add(new VariableDeclaration(v));
+        if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+            generate_create_from_data(t, block, arg->name.data, v,
+                    this->requestData, &classLoader);
+        } else {
+            if (arg->type.dimension == 0) {
+                block->Add(new Assignment(v, new NewExpression(v->type)));
+            }
+            else if (arg->type.dimension == 1) {
+                generate_new_array(v->type, block, v, this->requestData);
+            }
+            else {
+                fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
+                        __LINE__);
+            }
+        }
+
+        // Add that parameter to the method call
+        realCall->arguments.push_back(v);
+
+        arg = arg->next;
+    }
+
+    // Add a final parameter: RpcContext. Contains data about
+    // incoming request (e.g., certificate)
+    realCall->arguments.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
+
+    Type* returnType = NAMES.Search(method->type.type.data);
+    if (returnType == EVENT_FAKE_TYPE) {
+        returnType = VOID_TYPE;
+    }
+    
+    // the real call
+    bool first = true;
+    Variable* _result = NULL;
+    if (returnType == VOID_TYPE) {
+        block->Add(realCall);
+    } else {
+        _result = new Variable(returnType, "_result",
+                                method->type.dimension);
+        block->Add(new VariableDeclaration(_result, realCall));
+
+        // need the result RpcData
+        if (first) {
+            block->Add(new Assignment(this->resultData,
+                        new NewExpression(RPC_DATA_TYPE)));
+            first = false;
+        }
+
+        // marshall the return value
+        generate_write_to_data(returnType, block,
+                new StringLiteralExpression("_result"), _result, this->resultData);
+    }
+
+    // out parameters
+    int i = 0;
+    arg = method->args;
+    while (arg != NULL) {
+        Type* t = NAMES.Search(arg->type.type.data);
+        Variable* v = stubArgs.Get(i++);
+
+        if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+            // need the result RpcData
+            if (first) {
+                block->Add(new Assignment(this->resultData, new NewExpression(RPC_DATA_TYPE)));
+                first = false;
+            }
+
+            generate_write_to_data(t, block, new StringLiteralExpression(arg->name.data),
+                    v, this->resultData);
+        }
+
+        arg = arg->next;
+    }
+}
+
+void
+DispatcherClass::DoneWithMethods()
+{
+    if (this->dispatchIfStatement == NULL) {
+        return;
+    }
+
+    this->elements.push_back(this->processMethod);
+
+    IfStatement* fallthrough = new IfStatement();
+        fallthrough->statements = new StatementBlock;
+        fallthrough->statements->Add(new ReturnStatement(
+                    new MethodCall(SUPER_VALUE, "process", 4, 
+                    this->actionParam, this->requestParam, 
+                    this->rpcContextParam,
+                    this->errorParam)));
+    this->dispatchIfStatement->elseif = fallthrough;
+    IfStatement* s = new IfStatement;
+        s->statements = new StatementBlock;
+    this->processMethod->statements->Add(s);
+    s->expression = new Comparison(this->resultData, "!=", NULL_VALUE);
+    s->statements->Add(new ReturnStatement(new MethodCall(this->resultData, "serialize")));
+    s->elseif = new IfStatement;
+    s = s->elseif;
+    s->statements->Add(new ReturnStatement(NULL_VALUE));
+}
+
+// =================================================
+class RpcProxyClass : public Class
+{
+public:
+    RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType);
+    virtual ~RpcProxyClass();
+
+    Variable* endpoint;
+    Variable* broker;
+
+private:
+    void generate_ctor();
+    void generate_get_endpoint_info();
+};
+
+RpcProxyClass::RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType)
+    :Class()
+{
+    this->comment = gather_comments(iface->comments_token->extra);
+    this->modifiers = PUBLIC;
+    this->what = Class::CLASS;
+    this->type = interfaceType;
+
+    // broker
+    this->broker = new Variable(RPC_BROKER_TYPE, "_broker");
+    this->elements.push_back(new Field(PRIVATE, this->broker));
+    // endpoint
+    this->endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "_endpoint");
+    this->elements.push_back(new Field(PRIVATE, this->endpoint));
+
+    // methods
+    generate_ctor();
+    generate_get_endpoint_info();
+}
+
+RpcProxyClass::~RpcProxyClass()
+{
+}
+
+void
+RpcProxyClass::generate_ctor()
+{
+    Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
+    Variable* endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "endpoint");
+    Method* ctor = new Method;
+        ctor->modifiers = PUBLIC;
+        ctor->name = class_name_leaf(this->type->Name());
+        ctor->statements = new StatementBlock;
+        ctor->parameters.push_back(broker);
+        ctor->parameters.push_back(endpoint);
+    this->elements.push_back(ctor);
+
+    ctor->statements->Add(new Assignment(this->broker, broker));
+    ctor->statements->Add(new Assignment(this->endpoint, endpoint));
+}
+
+void
+RpcProxyClass::generate_get_endpoint_info()
+{
+    Method* get = new Method;
+    get->modifiers = PUBLIC;
+    get->returnType = RPC_ENDPOINT_INFO_TYPE;
+    get->name = "getEndpointInfo";
+    get->statements = new StatementBlock;
+    this->elements.push_back(get);
+
+    get->statements->Add(new ReturnStatement(this->endpoint));
+}
+
+// =================================================
+class EventListenerClass : public DispatcherClass
+{
+public:
+    EventListenerClass(const interface_type* iface, Type* listenerType);
+    virtual ~EventListenerClass();
+
+    Variable* _listener;
+
+private:
+    void generate_ctor();
+};
+
+Expression*
+generate_get_listener_expression(Type* cast)
+{
+    return new Cast(cast, new MethodCall(THIS_VALUE, "getView"));
+}
+
+EventListenerClass::EventListenerClass(const interface_type* iface, Type* listenerType)
+    :DispatcherClass(iface, new FieldVariable(THIS_VALUE, "_listener"))
+{
+    this->modifiers = PRIVATE;
+    this->what = Class::CLASS;
+    this->type = new Type(iface->package ? iface->package : "",
+                        append(iface->name.data, ".Presenter"),
+                        Type::GENERATED, false, false, false);
+    this->extends = PRESENTER_BASE_TYPE;
+
+    this->_listener = new Variable(listenerType, "_listener");
+    this->elements.push_back(new Field(PRIVATE, this->_listener));
+
+    // methods
+    generate_ctor();
+}
+
+EventListenerClass::~EventListenerClass()
+{
+}
+
+void
+EventListenerClass::generate_ctor()
+{
+    Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
+    Variable* listener = new Variable(this->_listener->type, "listener");
+    Method* ctor = new Method;
+        ctor->modifiers = PUBLIC;
+        ctor->name = class_name_leaf(this->type->Name());
+        ctor->statements = new StatementBlock;
+        ctor->parameters.push_back(broker);
+        ctor->parameters.push_back(listener);
+    this->elements.push_back(ctor);
+
+    ctor->statements->Add(new MethodCall("super", 2, broker, listener));
+    ctor->statements->Add(new Assignment(this->_listener, listener));
+}
+
+// =================================================
+class ListenerClass : public Class
+{
+public:
+    ListenerClass(const interface_type* iface);
+    virtual ~ListenerClass();
+
+    bool needed;
+
+private:
+    void generate_ctor();
+};
+
+ListenerClass::ListenerClass(const interface_type* iface)
+    :Class(),
+     needed(false)
+{
+    this->comment = "/** Extend this to listen to the events from this class. */";
+    this->modifiers = STATIC | PUBLIC ;
+    this->what = Class::CLASS;
+    this->type = new Type(iface->package ? iface->package : "",
+                        append(iface->name.data, ".Listener"),
+                        Type::GENERATED, false, false, false);
+    this->extends = PRESENTER_LISTENER_BASE_TYPE;
+}
+
+ListenerClass::~ListenerClass()
+{
+}
+
+// =================================================
+class EndpointBaseClass : public DispatcherClass
+{
+public:
+    EndpointBaseClass(const interface_type* iface);
+    virtual ~EndpointBaseClass();
+
+    bool needed;
+
+private:
+    void generate_ctor();
+};
+
+EndpointBaseClass::EndpointBaseClass(const interface_type* iface)
+    :DispatcherClass(iface, THIS_VALUE),
+     needed(false)
+{
+    this->comment = "/** Extend this to implement a link service. */";
+    this->modifiers = STATIC | PUBLIC | ABSTRACT;
+    this->what = Class::CLASS;
+    this->type = new Type(iface->package ? iface->package : "",
+                        append(iface->name.data, ".EndpointBase"),
+                        Type::GENERATED, false, false, false);
+    this->extends = RPC_CONNECTOR_TYPE;
+
+    // methods
+    generate_ctor();
+}
+
+EndpointBaseClass::~EndpointBaseClass()
+{
+}
+
+void
+EndpointBaseClass::generate_ctor()
+{
+    Variable* container = new Variable(ANDROID_CONTEXT_TYPE, "context");
+    Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
+	Variable* place = new Variable(PLACE_INFO_TYPE, "placeInfo");
+    Method* ctor = new Method;
+        ctor->modifiers = PUBLIC;
+        ctor->name = class_name_leaf(this->type->Name());
+        ctor->statements = new StatementBlock;
+        ctor->parameters.push_back(container);
+        ctor->parameters.push_back(broker);
+        ctor->parameters.push_back(place);
+    this->elements.push_back(ctor);
+
+    ctor->statements->Add(new MethodCall("super", 3, container, broker, place));
+}
+
+// =================================================
+class ResultDispatcherClass : public Class
+{
+public:
+    ResultDispatcherClass();
+    virtual ~ResultDispatcherClass();
+
+    void AddMethod(int index, const string& name, Method** method, Variable** param);
+
+    bool needed;
+    Variable* methodId;
+    Variable* callback;
+    Method* onResultMethod;
+    Variable* resultParam;
+    SwitchStatement* methodSwitch;
+
+private:
+    void generate_ctor();
+    void generate_onResult();
+};
+
+ResultDispatcherClass::ResultDispatcherClass()
+    :Class(),
+     needed(false)
+{
+    this->modifiers = PRIVATE | FINAL;
+    this->what = Class::CLASS;
+    this->type = new Type("_ResultDispatcher", Type::GENERATED, false, false, false);
+    this->interfaces.push_back(RPC_RESULT_HANDLER_TYPE);
+
+    // methodId
+    this->methodId = new Variable(INT_TYPE, "methodId");
+    this->elements.push_back(new Field(PRIVATE, this->methodId));
+    this->callback = new Variable(OBJECT_TYPE, "callback");
+    this->elements.push_back(new Field(PRIVATE, this->callback));
+
+    // methods
+    generate_ctor();
+    generate_onResult();
+}
+
+ResultDispatcherClass::~ResultDispatcherClass()
+{
+}
+
+void
+ResultDispatcherClass::generate_ctor()
+{
+    Variable* methodIdParam = new Variable(INT_TYPE, "methId");
+    Variable* callbackParam = new Variable(OBJECT_TYPE, "cbObj");
+    Method* ctor = new Method;
+        ctor->modifiers = PUBLIC;
+        ctor->name = class_name_leaf(this->type->Name());
+        ctor->statements = new StatementBlock;
+        ctor->parameters.push_back(methodIdParam);
+        ctor->parameters.push_back(callbackParam);
+    this->elements.push_back(ctor);
+
+    ctor->statements->Add(new Assignment(this->methodId, methodIdParam));
+    ctor->statements->Add(new Assignment(this->callback, callbackParam));
+}
+
+void
+ResultDispatcherClass::generate_onResult()
+{
+    this->onResultMethod = new Method;
+        this->onResultMethod->modifiers = PUBLIC;
+        this->onResultMethod->returnType = VOID_TYPE;
+        this->onResultMethod->returnTypeDimension = 0;
+        this->onResultMethod->name = "onResult";
+        this->onResultMethod->statements = new StatementBlock;
+    this->elements.push_back(this->onResultMethod);
+
+    this->resultParam = new Variable(BYTE_TYPE, "result", 1);
+    this->onResultMethod->parameters.push_back(this->resultParam);
+
+    this->methodSwitch = new SwitchStatement(this->methodId);
+    this->onResultMethod->statements->Add(this->methodSwitch);
+}
+
+void
+ResultDispatcherClass::AddMethod(int index, const string& name, Method** method, Variable** param)
+{
+    Method* m = new Method;
+        m->modifiers = PUBLIC;
+        m->returnType = VOID_TYPE;
+        m->returnTypeDimension = 0;
+        m->name = name;
+        m->statements = new StatementBlock;
+    *param = new Variable(BYTE_TYPE, "result", 1);
+    m->parameters.push_back(*param);
+    this->elements.push_back(m);
+    *method = m;
+
+    Case* c = new Case(format_int(index));
+    c->statements->Add(new MethodCall(new LiteralExpression("this"), name, 1, this->resultParam));
+    c->statements->Add(new Break());
+
+    this->methodSwitch->cases.push_back(c);
+}
+
+// =================================================
+static void
+generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from)
+{
+    fprintf(stderr, "aidl: implement generate_new_array %s:%d\n", __FILE__, __LINE__);
+    exit(1);
+}
+
+static void
+generate_create_from_data(Type* t, StatementBlock* addTo, const string& key, Variable* v,
+                            Variable* data, Variable** cl)
+{
+    Expression* k = new StringLiteralExpression(key);
+    if (v->dimension == 0) {
+        t->CreateFromRpcData(addTo, k, v, data, cl);
+    }
+    if (v->dimension == 1) {
+        //t->ReadArrayFromRpcData(addTo, v, data, cl);
+        fprintf(stderr, "aidl: implement generate_create_from_data for arrays%s:%d\n",
+                __FILE__, __LINE__);
+    }
+}
+
+static void
+generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v, Variable* data)
+{
+    if (v->dimension == 0) {
+        t->WriteToRpcData(addTo, k, v, data, 0);
+    }
+    if (v->dimension == 1) {
+        //t->WriteArrayToParcel(addTo, v, data);
+        fprintf(stderr, "aidl: implement generate_write_to_data for arrays%s:%d\n",
+                __FILE__, __LINE__);
+    }
+}
+
+// =================================================
+static Type*
+generate_results_method(const method_type* method, RpcProxyClass* proxyClass)
+{
+    arg_type* arg;
+
+    string resultsMethodName = results_method_name(method->name.data);
+    Type* resultsInterfaceType = new Type(results_class_name(method->name.data),
+            Type::GENERATED, false, false, false);
+
+    if (!method->oneway) {
+        Class* resultsClass = new Class;
+            resultsClass->modifiers = STATIC | PUBLIC;
+            resultsClass->what = Class::INTERFACE;
+            resultsClass->type = resultsInterfaceType;
+
+        Method* resultMethod = new Method;
+            resultMethod->comment = gather_comments(method->comments_token->extra);
+            resultMethod->modifiers = PUBLIC;
+            resultMethod->returnType = VOID_TYPE;
+            resultMethod->returnTypeDimension = 0;
+            resultMethod->name = resultsMethodName;
+        if (0 != strcmp("void", method->type.type.data)) {
+            resultMethod->parameters.push_back(new Variable(NAMES.Search(method->type.type.data),
+                        "_result", method->type.dimension));
+        }
+        arg = method->args;
+        while (arg != NULL) {
+            if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+                resultMethod->parameters.push_back(new Variable(
+                                    NAMES.Search(arg->type.type.data), arg->name.data,
+                                    arg->type.dimension));
+            }
+            arg = arg->next;
+        }
+        resultsClass->elements.push_back(resultMethod);
+
+        if (resultMethod->parameters.size() > 0) {
+            proxyClass->elements.push_back(resultsClass);
+            return resultsInterfaceType;
+        } 
+    }
+    //delete resultsInterfaceType;
+    return NULL;
+}
+
+static void
+generate_proxy_method(const method_type* method, RpcProxyClass* proxyClass,
+        ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
+{
+    arg_type* arg;
+    Method* proxyMethod = new Method;
+        proxyMethod->comment = gather_comments(method->comments_token->extra);
+        proxyMethod->modifiers = PUBLIC;
+        proxyMethod->returnType = VOID_TYPE;
+        proxyMethod->returnTypeDimension = 0;
+        proxyMethod->name = method->name.data;
+        proxyMethod->statements = new StatementBlock;
+    proxyClass->elements.push_back(proxyMethod);
+
+    // The local variables
+    Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
+    proxyMethod->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));
+
+    // Add the arguments
+    arg = method->args;
+    while (arg != NULL) {
+        if (convert_direction(arg->direction.data) & IN_PARAMETER) {
+            // Function signature
+            Type* t = NAMES.Search(arg->type.type.data);
+            Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+            proxyMethod->parameters.push_back(v);
+
+            // Input parameter marshalling
+            generate_write_to_data(t, proxyMethod->statements,
+                    new StringLiteralExpression(arg->name.data), v, _data);
+        }
+        arg = arg->next;
+    }
+
+    // If there is a results interface for this class
+    Expression* resultParameter;
+    if (resultsInterfaceType != NULL) {
+        // Result interface parameter
+        Variable* resultListener = new Variable(resultsInterfaceType, "_result");
+        proxyMethod->parameters.push_back(resultListener);
+
+        // Add the results dispatcher callback
+        resultsDispatcherClass->needed = true;
+        resultParameter = new NewExpression(resultsDispatcherClass->type, 2,
+                new LiteralExpression(format_int(index)), resultListener);
+    } else {
+        resultParameter = NULL_VALUE;
+    }
+
+    // All proxy methods take an error parameter
+    Variable* errorListener = new Variable(RPC_ERROR_LISTENER_TYPE, "_errors");
+    proxyMethod->parameters.push_back(errorListener);
+
+    // Call the broker
+    proxyMethod->statements->Add(new MethodCall(new FieldVariable(THIS_VALUE, "_broker"),
+                "sendRpc", 5,
+                proxyClass->endpoint,
+                new StringLiteralExpression(method->name.data),
+                new MethodCall(_data, "serialize"),
+                resultParameter,
+                errorListener));
+}
+
+static void
+generate_result_dispatcher_method(const method_type* method,
+        ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
+{
+    arg_type* arg;
+    Method* dispatchMethod;
+    Variable* dispatchParam;
+    resultsDispatcherClass->AddMethod(index, method->name.data, &dispatchMethod, &dispatchParam);
+
+    Variable* classLoader = NULL;
+    Variable* resultData = new Variable(RPC_DATA_TYPE, "resultData");
+    dispatchMethod->statements->Add(new VariableDeclaration(resultData,
+                new NewExpression(RPC_DATA_TYPE, 1, dispatchParam)));
+
+    // The callback method itself
+    MethodCall* realCall = new MethodCall(
+            new Cast(resultsInterfaceType, new FieldVariable(THIS_VALUE, "callback")),
+            results_method_name(method->name.data));
+
+    // The return value
+    {
+        Type* t = NAMES.Search(method->type.type.data);
+        if (t != VOID_TYPE) {
+            Variable* rv = new Variable(t, "rv");
+            dispatchMethod->statements->Add(new VariableDeclaration(rv));
+            generate_create_from_data(t, dispatchMethod->statements, "_result", rv,
+                    resultData, &classLoader);
+            realCall->arguments.push_back(rv);
+        }
+    }
+
+    VariableFactory stubArgs("arg");
+    arg = method->args;
+    while (arg != NULL) {
+        if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
+            // Unmarshall the results
+            Type* t = NAMES.Search(arg->type.type.data);
+            Variable* v = stubArgs.Get(t);
+            dispatchMethod->statements->Add(new VariableDeclaration(v));
+
+            generate_create_from_data(t, dispatchMethod->statements, arg->name.data, v,
+                    resultData, &classLoader);
+
+            // Add the argument to the callback
+            realCall->arguments.push_back(v);
+        }
+        arg = arg->next;
+    }
+
+    // Call the callback method
+    dispatchMethod->statements->Add(realCall);
+}
+
+static void
+generate_regular_method(const method_type* method, RpcProxyClass* proxyClass,
+        EndpointBaseClass* serviceBaseClass, ResultDispatcherClass* resultsDispatcherClass,
+        int index)
+{
+    arg_type* arg;
+
+    // == the callback interface for results ================================
+    // the service base class
+    Type* resultsInterfaceType = generate_results_method(method, proxyClass);
+    
+    // == the method in the proxy class =====================================
+    generate_proxy_method(method, proxyClass, resultsDispatcherClass, resultsInterfaceType, index);
+
+    // == the method in the result dispatcher class =========================
+    if (resultsInterfaceType != NULL) {
+        generate_result_dispatcher_method(method, resultsDispatcherClass, resultsInterfaceType,
+                index);
+    }
+
+    // == The abstract method that the service developers implement ==========
+    Method* decl = new Method;
+        decl->comment = gather_comments(method->comments_token->extra);
+        decl->modifiers = PUBLIC | ABSTRACT;
+        decl->returnType = NAMES.Search(method->type.type.data);
+        decl->returnTypeDimension = method->type.dimension;
+        decl->name = method->name.data;
+    arg = method->args;
+    while (arg != NULL) {
+        decl->parameters.push_back(new Variable(
+                            NAMES.Search(arg->type.type.data), arg->name.data,
+                            arg->type.dimension));
+        arg = arg->next;
+    }
+
+    // Add the default RpcContext param to all methods
+    decl->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
+	
+    serviceBaseClass->elements.push_back(decl);
+    
+
+    // == the dispatch method in the service base class ======================
+    serviceBaseClass->AddMethod(method);
+}
+
+static void
+generate_event_method(const method_type* method, RpcProxyClass* proxyClass,
+        EndpointBaseClass* serviceBaseClass, ListenerClass* listenerClass,
+        EventListenerClass* presenterClass, int index)
+{
+    arg_type* arg;
+    listenerClass->needed = true;
+
+    // == the push method in the service base class =========================
+    Method* push = new Method;
+        push->modifiers = PUBLIC;
+        push->name = push_method_name(method->name.data);
+        push->statements = new StatementBlock;
+        push->returnType = VOID_TYPE;
+    serviceBaseClass->elements.push_back(push);
+
+    // The local variables
+    Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
+    push->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));
+
+    // Add the arguments
+    arg = method->args;
+    while (arg != NULL) {
+        // Function signature
+        Type* t = NAMES.Search(arg->type.type.data);
+        Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
+        push->parameters.push_back(v);
+
+        // Input parameter marshalling
+        generate_write_to_data(t, push->statements,
+                new StringLiteralExpression(arg->name.data), v, _data);
+
+        arg = arg->next;
+    }
+
+    // Send the notifications
+    push->statements->Add(new MethodCall("pushEvent", 2,
+                new StringLiteralExpression(method->name.data),
+                new MethodCall(_data, "serialize")));
+
+    // == the event callback dispatcher method  ====================================
+    presenterClass->AddMethod(method);
+
+    // == the event method in the listener base class =====================
+    Method* event = new Method;
+        event->modifiers = PUBLIC;
+        event->name = method->name.data;
+        event->statements = new StatementBlock;
+        event->returnType = VOID_TYPE;
+    listenerClass->elements.push_back(event);
+    arg = method->args;
+    while (arg != NULL) {
+        event->parameters.push_back(new Variable(
+                            NAMES.Search(arg->type.type.data), arg->name.data,
+                            arg->type.dimension));
+        arg = arg->next;
+    }
+
+    // Add a final parameter: RpcContext. Contains data about
+    // incoming request (e.g., certificate)
+    event->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
+}
+
+static void
+generate_listener_methods(RpcProxyClass* proxyClass, Type* presenterType, Type* listenerType)
+{
+    // AndroidAtHomePresenter _presenter;
+    // void startListening(Listener listener) {
+    //     stopListening();
+    //     _presenter = new Presenter(_broker, listener);
+    //     _presenter.startListening(_endpoint);
+    // }
+    // void stopListening() {
+    //     if (_presenter != null) {
+    //         _presenter.stopListening();
+    //     }
+    // }
+
+    Variable* _presenter = new Variable(presenterType, "_presenter");
+    proxyClass->elements.push_back(new Field(PRIVATE, _presenter));
+
+    Variable* listener = new Variable(listenerType, "listener");
+
+    Method* startListeningMethod = new Method;
+        startListeningMethod->modifiers = PUBLIC;
+        startListeningMethod->returnType = VOID_TYPE;
+        startListeningMethod->name = "startListening";
+        startListeningMethod->statements = new StatementBlock;
+        startListeningMethod->parameters.push_back(listener);
+    proxyClass->elements.push_back(startListeningMethod);
+
+    startListeningMethod->statements->Add(new MethodCall(THIS_VALUE, "stopListening"));
+    startListeningMethod->statements->Add(new Assignment(_presenter,
+                new NewExpression(presenterType, 2, proxyClass->broker, listener)));
+    startListeningMethod->statements->Add(new MethodCall(_presenter,
+                "startListening", 1, proxyClass->endpoint));
+
+    Method* stopListeningMethod = new Method;
+        stopListeningMethod->modifiers = PUBLIC;
+        stopListeningMethod->returnType = VOID_TYPE;
+        stopListeningMethod->name = "stopListening";
+        stopListeningMethod->statements = new StatementBlock;
+    proxyClass->elements.push_back(stopListeningMethod);
+
+    IfStatement* ifst = new IfStatement;
+        ifst->expression = new Comparison(_presenter, "!=", NULL_VALUE);
+    stopListeningMethod->statements->Add(ifst);
+
+    ifst->statements->Add(new MethodCall(_presenter, "stopListening"));
+    ifst->statements->Add(new Assignment(_presenter, NULL_VALUE));
+}
+
+Class*
+generate_rpc_interface_class(const interface_type* iface)
+{
+    // the proxy class
+    InterfaceType* interfaceType = static_cast<InterfaceType*>(
+        NAMES.Find(iface->package, iface->name.data));
+    RpcProxyClass* proxy = new RpcProxyClass(iface, interfaceType);
+
+    // the listener class
+    ListenerClass* listener = new ListenerClass(iface);
+
+    // the presenter class
+    EventListenerClass* presenter = new EventListenerClass(iface, listener->type);
+
+    // the service base class
+    EndpointBaseClass* base = new EndpointBaseClass(iface);
+    proxy->elements.push_back(base);
+
+    // the result dispatcher
+    ResultDispatcherClass* results = new ResultDispatcherClass();
+
+    // all the declared methods of the proxy
+    int index = 0;
+    interface_item_type* item = iface->interface_items;
+    while (item != NULL) {
+        if (item->item_type == METHOD_TYPE) {
+            if (NAMES.Search(((method_type*)item)->type.type.data) == EVENT_FAKE_TYPE) {
+                generate_event_method((method_type*)item, proxy, base, listener, presenter, index);
+            } else {
+                generate_regular_method((method_type*)item, proxy, base, results, index);
+            }
+        }
+        item = item->next;
+        index++;
+    }
+    presenter->DoneWithMethods();
+    base->DoneWithMethods();
+
+    // only add this if there are methods with results / out parameters
+    if (results->needed) {
+        proxy->elements.push_back(results);
+    }
+    if (listener->needed) {
+        proxy->elements.push_back(listener);
+        proxy->elements.push_back(presenter);
+        generate_listener_methods(proxy, presenter->type, listener->type);
+    }
+
+    return proxy;
+}
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 82abe3a..df622d6 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -415,7 +415,7 @@
      * Starting and shutting down driver too quick causes problems leading to driver
      * being in a bad state. Delay driver stop.
      */
-    private static final int DELAYED_DRIVER_STOP_MS = 2 * 60 * 1000; /* 2 minutes */
+    private final int mDriverStopDelayMs;
     private int mDelayedStopCounter;
     private boolean mInDelayedStop = false;
 
@@ -583,6 +583,9 @@
         mDefaultSupplicantScanIntervalMs = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_wifi_supplicant_scan_interval);
 
+        mDriverStopDelayMs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_wifi_driver_stop_delay);
+
         mContext.registerReceiver(
             new BroadcastReceiver() {
                 @Override
@@ -1742,7 +1745,9 @@
          * If we've exceeded the maximum number of retries for DHCP
          * to a given network, disable the network
          */
-        if (++mReconnectCount > getMaxDhcpRetries()) {
+        int maxRetries = getMaxDhcpRetries();
+        // maxRetries == 0 means keep trying forever
+        if (maxRetries > 0 && ++mReconnectCount > maxRetries) {
             loge("Failed " +
                     mReconnectCount + " times, Disabling " + mLastNetworkId);
             WifiConfigStore.disableNetwork(mLastNetworkId,
@@ -2597,7 +2602,7 @@
                     } else {
                         /* send regular delayed shut down */
                         sendMessageDelayed(obtainMessage(CMD_DELAYED_STOP_DRIVER,
-                                mDelayedStopCounter, 0), DELAYED_DRIVER_STOP_MS);
+                                mDelayedStopCounter, 0), mDriverStopDelayMs);
                     }
                     break;
                 case CMD_START_DRIVER: